Merge branch 'develop' into 'master'
Update master branch See merge request pleroma/pleroma-fe!1861
This commit is contained in:
commit
b6accf9e7f
|
@ -0,0 +1 @@
|
||||||
|
/build/webpack.prod.conf.js export-subst
|
|
@ -4,11 +4,36 @@
|
||||||
image: node:16
|
image: node:16
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- check-changelog
|
||||||
- lint
|
- lint
|
||||||
- build
|
- build
|
||||||
- test
|
- test
|
||||||
- deploy
|
- 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:
|
lint:
|
||||||
stage: lint
|
stage: lint
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -1,19 +1,41 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"stylelint-rscss/config",
|
"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": {
|
"rules": {
|
||||||
"declaration-no-important": true,
|
"declaration-no-important": true,
|
||||||
"rscss/no-descendant-combinator": false,
|
"rscss/no-descendant-combinator": false,
|
||||||
"rscss/class-format": [
|
"rscss/class-format": [
|
||||||
true,
|
false,
|
||||||
{
|
{
|
||||||
"component": "pascal-case",
|
"component": "pascal-case",
|
||||||
"variant": "^-[a-z]\\w+",
|
"variant": "^-[a-z]\\w+",
|
||||||
"element": "^[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"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
CHANGELOG.md
28
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/).
|
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
|
## 2.5.0 - 23.12.2022
|
||||||
### Fixed
|
### Fixed
|
||||||
- UI no longer lags when switching between mobile and desktop mode
|
- UI no longer lags when switching between mobile and desktop mode
|
||||||
|
|
|
@ -6,7 +6,7 @@ var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin')
|
||||||
var CopyPlugin = require('copy-webpack-plugin');
|
var CopyPlugin = require('copy-webpack-plugin');
|
||||||
var { VueLoaderPlugin } = require('vue-loader')
|
var { VueLoaderPlugin } = require('vue-loader')
|
||||||
var ESLintPlugin = require('eslint-webpack-plugin');
|
var ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
|
var StylelintPlugin = require('stylelint-webpack-plugin');
|
||||||
|
|
||||||
var env = process.env.NODE_ENV
|
var env = process.env.NODE_ENV
|
||||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||||
|
@ -111,6 +111,7 @@ module.exports = {
|
||||||
extensions: ['js', 'vue'],
|
extensions: ['js', 'vue'],
|
||||||
formatter: require('eslint-formatter-friendly')
|
formatter: require('eslint-formatter-friendly')
|
||||||
}),
|
}),
|
||||||
|
new StylelintPlugin({}),
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
// This copies Ruffle's WASM to a directory so that JS side can access it
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
|
|
|
@ -11,9 +11,16 @@ var env = process.env.NODE_ENV === 'testing'
|
||||||
? require('../config/test.env')
|
? require('../config/test.env')
|
||||||
: config.build.env
|
: config.build.env
|
||||||
|
|
||||||
let commitHash = require('child_process')
|
let commitHash = (() => {
|
||||||
.execSync('git rev-parse --short HEAD')
|
const subst = "$Format:%h$";
|
||||||
.toString();
|
if(!subst.match(/Format:/)) {
|
||||||
|
return subst;
|
||||||
|
} else {
|
||||||
|
return require('child_process')
|
||||||
|
.execSync('git rev-parse --short HEAD')
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
var webpackConfig = merge(baseWebpackConfig, {
|
var webpackConfig = merge(baseWebpackConfig, {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files.
|
|
@ -0,0 +1 @@
|
||||||
|
Implemented a very basic instance administration screen
|
|
@ -0,0 +1 @@
|
||||||
|
Keep aspect ratio of custom emoji reaction in notification
|
|
@ -0,0 +1 @@
|
||||||
|
Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal
|
|
@ -0,0 +1 @@
|
||||||
|
Add alt text to emoji picker buttons
|
|
@ -0,0 +1 @@
|
||||||
|
Use export-subst gitattribute to allow tarball builds
|
|
@ -0,0 +1 @@
|
||||||
|
fix reports now showing reason/content:w
|
|
@ -0,0 +1 @@
|
||||||
|
Fix HTML attribute parsing, discard attributes not strating with a letter
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a bug where mentioning a user twice will not fill the mention into the textarea
|
|
@ -0,0 +1 @@
|
||||||
|
Make MentionsLine aware of line breaking by non-br elements
|
|
@ -0,0 +1 @@
|
||||||
|
Fix parsing non-ascii tags
|
|
@ -0,0 +1 @@
|
||||||
|
Fix OAuth2 token lingering after revocation
|
|
@ -0,0 +1 @@
|
||||||
|
fix typo in code that prevented cards from showing at all
|
|
@ -0,0 +1 @@
|
||||||
|
don't display quoted status twice
|
|
@ -0,0 +1 @@
|
||||||
|
Implement quoting
|
|
@ -0,0 +1 @@
|
||||||
|
Fix react button misalignment on safari ios
|
|
@ -0,0 +1 @@
|
||||||
|
Fix react button not working if reaction accounts are not loaded
|
|
@ -0,0 +1 @@
|
||||||
|
Fix pinned statuses gone when reloading user timeline
|
|
@ -0,0 +1 @@
|
||||||
|
Fix scrolling emoji selector in modal in safari ios
|
|
@ -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
|
### 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
|
### Running production build locally or on a separate server
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<body class="hidden">
|
<body class="hidden">
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<div id="modal"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<div id="popovers" />
|
<div id="popovers" />
|
||||||
</body>
|
</body>
|
||||||
|
|
96
package.json
96
package.json
|
@ -11,115 +11,121 @@
|
||||||
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
||||||
"e2e": "node test/e2e/runner.js",
|
"e2e": "node test/e2e/runner.js",
|
||||||
"test": "npm run unit && npm run e2e",
|
"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": "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"
|
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.20.0",
|
"@babel/runtime": "7.21.5",
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.2.0",
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.2.0",
|
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.2.0",
|
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
|
"@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",
|
"@vuelidate/validators": "2.0.0",
|
||||||
"body-scroll-lock": "3.1.5",
|
"body-scroll-lock": "3.1.5",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"click-outside-vue3": "4.0.1",
|
||||||
"cropperjs": "1.5.12",
|
"cropperjs": "1.5.13",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"localforage": "1.10.0",
|
"localforage": "1.10.0",
|
||||||
"lozad": "1.16.0",
|
|
||||||
"parse-link-header": "2.0.0",
|
"parse-link-header": "2.0.0",
|
||||||
"phoenix": "1.6.2",
|
"phoenix": "1.7.7",
|
||||||
"punycode.js": "2.1.0",
|
"punycode.js": "2.3.0",
|
||||||
"qrcode": "1.5.0",
|
"qrcode": "1.5.3",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"url": "0.11.0",
|
"url": "0.11.0",
|
||||||
"utf8": "3.0.0",
|
"utf8": "3.0.0",
|
||||||
"vue": "3.2.41",
|
"vue": "3.2.45",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-router": "4.1.6",
|
"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"
|
"vuex": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.19.6",
|
"@babel/core": "7.21.8",
|
||||||
"@babel/eslint-parser": "7.19.1",
|
"@babel/eslint-parser": "7.21.8",
|
||||||
"@babel/plugin-transform-runtime": "7.19.6",
|
"@babel/plugin-transform-runtime": "7.21.4",
|
||||||
"@babel/preset-env": "7.19.4",
|
"@babel/preset-env": "7.21.5",
|
||||||
"@babel/register": "7.18.9",
|
"@babel/register": "7.21.0",
|
||||||
"@intlify/vue-i18n-loader": "5.0.0",
|
"@intlify/vue-i18n-loader": "5.0.1",
|
||||||
"@ungap/event-target": "0.2.3",
|
"@ungap/event-target": "0.2.3",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||||
"@vue/babel-plugin-jsx": "1.1.1",
|
"@vue/babel-plugin-jsx": "1.1.1",
|
||||||
"@vue/compiler-sfc": "3.2.41",
|
"@vue/compiler-sfc": "3.2.45",
|
||||||
"@vue/test-utils": "2.2.6",
|
"@vue/test-utils": "2.2.8",
|
||||||
"autoprefixer": "10.4.12",
|
"autoprefixer": "10.4.14",
|
||||||
"babel-loader": "8.2.5",
|
"babel-loader": "9.1.2",
|
||||||
"babel-plugin-lodash": "3.3.4",
|
"babel-plugin-lodash": "3.3.4",
|
||||||
"chai": "4.3.7",
|
"chai": "4.3.7",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
"chromedriver": "104.0.0",
|
"chromedriver": "108.0.0",
|
||||||
"connect-history-api-fallback": "2.0.0",
|
"connect-history-api-fallback": "2.0.0",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
"css-loader": "6.7.1",
|
"css-loader": "6.7.3",
|
||||||
"css-minimizer-webpack-plugin": "4.2.2",
|
"css-minimizer-webpack-plugin": "4.2.2",
|
||||||
"custom-event-polyfill": "1.0.7",
|
"custom-event-polyfill": "1.0.7",
|
||||||
"eslint": "8.29.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-standard": "17.0.0",
|
"eslint-config-standard": "17.0.0",
|
||||||
"eslint-formatter-friendly": "7.0.0",
|
"eslint-formatter-friendly": "7.0.0",
|
||||||
"eslint-plugin-import": "2.26.0",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-n": "15.6.0",
|
"eslint-plugin-n": "15.6.1",
|
||||||
"eslint-plugin-promise": "6.1.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",
|
"eslint-webpack-plugin": "3.2.0",
|
||||||
"eventsource-polyfill": "0.9.6",
|
"eventsource-polyfill": "0.9.6",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"function-bind": "1.1.1",
|
"function-bind": "1.1.1",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.1",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
"iso-639-1": "2.1.15",
|
"iso-639-1": "2.1.15",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"karma": "6.4.1",
|
"karma": "6.4.2",
|
||||||
"karma-coverage": "2.2.0",
|
"karma-coverage": "2.2.0",
|
||||||
"karma-firefox-launcher": "2.1.2",
|
"karma-firefox-launcher": "2.1.2",
|
||||||
"karma-mocha": "2.0.1",
|
"karma-mocha": "2.0.1",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"karma-sinon-chai": "2.0.2",
|
"karma-sinon-chai": "2.0.2",
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-spec-reporter": "0.0.34",
|
"karma-spec-reporter": "0.0.36",
|
||||||
"karma-webpack": "5.0.0",
|
"karma-webpack": "5.0.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mini-css-extract-plugin": "2.6.1",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"mocha": "10.0.0",
|
"mocha": "10.2.0",
|
||||||
"nightwatch": "2.3.3",
|
"nightwatch": "2.6.20",
|
||||||
"opn": "5.5.0",
|
"opn": "5.5.0",
|
||||||
"ora": "0.4.1",
|
"ora": "0.4.1",
|
||||||
"postcss": "8.4.16",
|
"postcss": "8.4.23",
|
||||||
"postcss-loader": "7.0.1",
|
"postcss-html": "^1.5.0",
|
||||||
"sass": "1.55.0",
|
"postcss-loader": "7.0.2",
|
||||||
"sass-loader": "13.0.2",
|
"postcss-scss": "^4.0.6",
|
||||||
|
"sass": "1.60.0",
|
||||||
|
"sass-loader": "13.2.2",
|
||||||
"selenium-server": "2.53.1",
|
"selenium-server": "2.53.1",
|
||||||
"semver": "7.3.8",
|
"semver": "7.3.8",
|
||||||
"serviceworker-webpack5-plugin": "2.0.0",
|
"serviceworker-webpack5-plugin": "2.0.0",
|
||||||
"shelljs": "0.8.5",
|
"shelljs": "0.8.5",
|
||||||
"sinon": "14.0.2",
|
"sinon": "15.0.4",
|
||||||
"sinon-chai": "3.7.0",
|
"sinon-chai": "3.7.0",
|
||||||
"stylelint": "13.13.1",
|
"stylelint": "14.16.1",
|
||||||
"stylelint-config-standard": "20.0.0",
|
"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-rscss": "0.4.0",
|
||||||
|
"stylelint-webpack-plugin": "^3.3.0",
|
||||||
"vue-loader": "17.0.1",
|
"vue-loader": "17.0.1",
|
||||||
"vue-style-loader": "4.1.3",
|
"vue-style-loader": "4.1.3",
|
||||||
"webpack": "5.74.0",
|
"webpack": "5.75.0",
|
||||||
"webpack-dev-middleware": "3.7.3",
|
"webpack-dev-middleware": "3.7.3",
|
||||||
"webpack-hot-middleware": "2.25.2",
|
"webpack-hot-middleware": "2.25.3",
|
||||||
"webpack-merge": "0.20.0"
|
"webpack-merge": "0.20.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
113
src/App.scss
113
src/App.scss
|
@ -1,5 +1,7 @@
|
||||||
// stylelint-disable rscss/class-format
|
// stylelint-disable rscss/class-format
|
||||||
@import './_variables.scss';
|
/* stylelint-disable no-descending-specificity */
|
||||||
|
@import "./variables";
|
||||||
|
@import "./panel";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--navbar-height: 3.5rem;
|
--navbar-height: 3.5rem;
|
||||||
|
@ -123,7 +125,7 @@ h4 {
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
i[class*=icon-],
|
i[class*="icon-"],
|
||||||
.svg-inline--fa,
|
.svg-inline--fa,
|
||||||
.iconLetter {
|
.iconLetter {
|
||||||
color: $fallback--icon;
|
color: $fallback--icon;
|
||||||
|
@ -132,7 +134,7 @@ i[class*=icon-],
|
||||||
|
|
||||||
.button-unstyled:hover,
|
.button-unstyled:hover,
|
||||||
a:hover {
|
a:hover {
|
||||||
> i[class*=icon-],
|
> i[class*="icon-"],
|
||||||
> .svg-inline--fa,
|
> .svg-inline--fa,
|
||||||
> .iconLetter {
|
> .iconLetter {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
@ -141,12 +143,11 @@ a:hover {
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
z-index: var(--ZI_navbar);
|
z-index: var(--ZI_navbar);
|
||||||
color: var(--topBarText);
|
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--topBar, $fallback--fg);
|
background-color: var(--topBar, $fallback--fg);
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--faint, $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-shadow: var(--topBarShadow);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: var(--navbar-height);
|
height: var(--navbar-height);
|
||||||
|
@ -191,13 +192,11 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
.underlay {
|
.underlay {
|
||||||
grid-column-start: 1;
|
grid-column: 1 / span 3;
|
||||||
grid-column-end: span 3;
|
grid-row: 1 / 1;
|
||||||
grid-row-start: 1;
|
|
||||||
grid-row-end: 1;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: rgba(0, 0, 0, 0.15);
|
background-color: rgb(0 0 0 / 15%);
|
||||||
background-color: var(--underlay, rgba(0, 0, 0, 0.15));
|
background-color: var(--underlay, rgb(0 0 0 / 15%));
|
||||||
z-index: -1000;
|
z-index: -1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +230,7 @@ nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100%;
|
grid-template-columns: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
grid-row-start: 1;
|
grid-row: 1 / 1;
|
||||||
grid-row-end: 1;
|
|
||||||
margin: 0 calc(var(--___columnMargin) / 2);
|
margin: 0 calc(var(--___columnMargin) / 2);
|
||||||
padding: calc(var(--___columnMargin)) 0;
|
padding: calc(var(--___columnMargin)) 0;
|
||||||
row-gap: var(--___columnMargin);
|
row-gap: var(--___columnMargin);
|
||||||
|
@ -307,7 +305,7 @@ nav {
|
||||||
align-content: start;
|
align-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-reverse:not(.-wide):not(.-mobile) {
|
&.-reverse:not(.-wide, .-mobile) {
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
var(--effectiveContentColumnWidth)
|
var(--effectiveContentColumnWidth)
|
||||||
var(--effectiveSidebarColumnWidth);
|
var(--effectiveSidebarColumnWidth);
|
||||||
|
@ -336,11 +334,8 @@ nav {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
margin-top: var(--navbar-height);
|
margin: var(--navbar-height) 0 0 0;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-heading,
|
.panel-heading,
|
||||||
|
@ -389,7 +384,7 @@ nav {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
i[class*=icon-],
|
i[class*="icon-"],
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnText, $fallback--text);
|
color: var(--btnText, $fallback--text);
|
||||||
|
@ -400,12 +395,15 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&: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);
|
box-shadow: var(--buttonHoverShadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&: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);
|
box-shadow: var(--buttonPressedShadow);
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnPressedText, $fallback--text);
|
color: var(--btnPressedText, $fallback--text);
|
||||||
|
@ -438,7 +436,10 @@ nav {
|
||||||
color: var(--btnToggledText, $fallback--text);
|
color: var(--btnToggledText, $fallback--text);
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--btnToggled, $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);
|
box-shadow: var(--buttonPressedShadow);
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
|
@ -503,7 +504,10 @@ textarea,
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $fallback--inputRadius;
|
border-radius: $fallback--inputRadius;
|
||||||
border-radius: var(--inputRadius, $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);
|
box-shadow: var(--inputShadow);
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--input, $fallback--fg);
|
background-color: var(--input, $fallback--fg);
|
||||||
|
@ -521,13 +525,13 @@ textarea,
|
||||||
padding: 0 var(--_padding);
|
padding: 0 var(--_padding);
|
||||||
|
|
||||||
&:disabled,
|
&:disabled,
|
||||||
&[disabled=disabled],
|
&[disabled="disabled"],
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type=range] {
|
&[type="range"] {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -535,7 +539,7 @@ textarea,
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type=radio] {
|
&[type="radio"] {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
&:checked + label::before {
|
&:checked + label::before {
|
||||||
|
@ -555,7 +559,7 @@ textarea,
|
||||||
+ label::before {
|
+ label::before {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
content: '';
|
content: "";
|
||||||
transition: box-shadow 200ms;
|
transition: box-shadow 200ms;
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
|
@ -575,9 +579,7 @@ textarea,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type=checkbox] {
|
&[type="checkbox"] {
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:checked + label::before {
|
&:checked + label::before {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--inputText, $fallback--text);
|
color: var(--inputText, $fallback--text);
|
||||||
|
@ -594,7 +596,7 @@ textarea,
|
||||||
+ label::before {
|
+ label::before {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
content: '✓';
|
content: "✓";
|
||||||
transition: color 200ms;
|
transition: color 200ms;
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
|
@ -634,15 +636,29 @@ option {
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-number-spinner {
|
.hide-number-spinner {
|
||||||
-moz-appearance: textfield;
|
appearance: textfield;
|
||||||
|
|
||||||
&[type=number]::-webkit-inner-spin-button,
|
&[type="number"]::-webkit-inner-spin-button,
|
||||||
&[type=number]::-webkit-outer-spin-button {
|
&[type="number"]::-webkit-outer-spin-button {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
display: none;
|
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 {
|
.btn-block {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -653,24 +669,25 @@ option {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
button {
|
button,
|
||||||
|
.button-dropdown {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
&:not(:last-child) {
|
&:not(:last-child),
|
||||||
|
&:not(:last-child) .button-default {
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-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-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './panel.scss';
|
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
@ -686,7 +703,7 @@ option {
|
||||||
max-width: 10em;
|
max-width: 10em;
|
||||||
min-width: 1.7em;
|
min-width: 1.7em;
|
||||||
height: 1.3em;
|
height: 1.3em;
|
||||||
padding: 0.15em 0.15em;
|
padding: 0.15em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -789,7 +806,8 @@ option {
|
||||||
|
|
||||||
.fa-old-padding {
|
.fa-old-padding {
|
||||||
&.iconLetter,
|
&.iconLetter,
|
||||||
&.svg-inline--fa, &-layer {
|
&.svg-inline--fa,
|
||||||
|
&-layer {
|
||||||
padding: 0 0.3em;
|
padding: 0 0.3em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,3 +901,16 @@ option {
|
||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
opacity: 0;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
<StatusHistoryModal v-if="editingAvailable" />
|
<StatusHistoryModal v-if="editingAvailable" />
|
||||||
<SettingsModal />
|
<SettingsModal />
|
||||||
<UpdateNotification />
|
<UpdateNotification />
|
||||||
<div id="modal" />
|
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
@mixin unfocused-style {
|
@mixin unfocused-style {
|
||||||
@content;
|
@content;
|
||||||
|
|
||||||
&:focus:not(:focus-visible):not(:hover) {
|
&:focus:not(:focus-visible, :hover) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin focused-style {
|
@mixin focused-style {
|
||||||
&:hover, &:focus {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,20 @@ $darkened-background: whitesmoke;
|
||||||
|
|
||||||
$fallback--bg: #121a24;
|
$fallback--bg: #121a24;
|
||||||
$fallback--fg: #182230;
|
$fallback--fg: #182230;
|
||||||
$fallback--faint: rgba(185, 185, 186, .5);
|
$fallback--faint: rgb(185 185 186 / 50%);
|
||||||
$fallback--text: #b9b9ba;
|
$fallback--text: #b9b9ba;
|
||||||
$fallback--link: #d8a070;
|
$fallback--link: #d8a070;
|
||||||
$fallback--icon: #666;
|
$fallback--icon: #666;
|
||||||
$fallback--lightBg: rgb(21, 30, 42);
|
$fallback--lightBg: rgb(21 30 42);
|
||||||
$fallback--lightText: #b9b9ba;
|
$fallback--lightText: #b9b9ba;
|
||||||
$fallback--border: #222;
|
$fallback--border: #222;
|
||||||
$fallback--cRed: #ff0000;
|
$fallback--cRed: #f00;
|
||||||
$fallback--cBlue: #0095ff;
|
$fallback--cBlue: #0095ff;
|
||||||
$fallback--cGreen: #0fa00f;
|
$fallback--cGreen: #0fa00f;
|
||||||
$fallback--cOrange: orange;
|
$fallback--cOrange: orange;
|
||||||
|
|
||||||
$fallback--alertError: rgba(211,16,20,.5);
|
$fallback--alertError: rgb(211 16 20 / 50%);
|
||||||
$fallback--alertWarning: rgba(111,111,20,.5);
|
$fallback--alertWarning: rgb(111 111 20 / 50%);
|
||||||
|
|
||||||
$fallback--panelRadius: 10px;
|
$fallback--panelRadius: 10px;
|
||||||
$fallback--checkboxRadius: 2px;
|
$fallback--checkboxRadius: 2px;
|
||||||
|
@ -29,6 +29,8 @@ $fallback--avatarAltRadius: 10px;
|
||||||
$fallback--attachmentRadius: 10px;
|
$fallback--attachmentRadius: 10px;
|
||||||
$fallback--chatMessageRadius: 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;
|
$status-margin: 0.75em;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import vClickOutside from 'click-outside-vue3'
|
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'
|
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: 'textlimit', value: textlimit })
|
||||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
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) {
|
if (vapidPublicKey) {
|
||||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: 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: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
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: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
|
||||||
|
|
||||||
const uploadLimits = metadata.uploadLimits
|
const uploadLimits = metadata.uploadLimits
|
||||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
||||||
|
@ -397,6 +403,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
app.use(vClickOutside)
|
app.use(vClickOutside)
|
||||||
app.use(VBodyScrollLock)
|
app.use(VBodyScrollLock)
|
||||||
|
app.use(VueVirtualScroller)
|
||||||
|
|
||||||
app.component('FAIcon', FontAwesomeIcon)
|
app.component('FAIcon', FontAwesomeIcon)
|
||||||
app.component('FALayers', FontAwesomeLayers)
|
app.component('FALayers', FontAwesomeLayers)
|
||||||
|
|
|
@ -9,6 +9,3 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./about.js"></script>
|
<script src="./about.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { mapState } from 'vuex'
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.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 { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faEllipsisV
|
faEllipsisV
|
||||||
|
@ -16,14 +17,30 @@ const AccountActions = {
|
||||||
'user', 'relationship'
|
'user', 'relationship'
|
||||||
],
|
],
|
||||||
data () {
|
data () {
|
||||||
return { }
|
return {
|
||||||
|
showingConfirmBlock: false,
|
||||||
|
showingConfirmRemoveFollower: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressButton,
|
ProgressButton,
|
||||||
Popover,
|
Popover,
|
||||||
UserListMenu
|
UserListMenu,
|
||||||
|
ConfirmModal
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
showConfirmBlock () {
|
||||||
|
this.showingConfirmBlock = true
|
||||||
|
},
|
||||||
|
hideConfirmBlock () {
|
||||||
|
this.showingConfirmBlock = false
|
||||||
|
},
|
||||||
|
showConfirmRemoveUserFromFollowers () {
|
||||||
|
this.showingConfirmRemoveFollower = true
|
||||||
|
},
|
||||||
|
hideConfirmRemoveUserFromFollowers () {
|
||||||
|
this.showingConfirmRemoveFollower = false
|
||||||
|
},
|
||||||
showRepeats () {
|
showRepeats () {
|
||||||
this.$store.dispatch('showReblogs', this.user.id)
|
this.$store.dispatch('showReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
|
@ -31,13 +48,29 @@ const AccountActions = {
|
||||||
this.$store.dispatch('hideReblogs', this.user.id)
|
this.$store.dispatch('hideReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser () {
|
||||||
|
if (!this.shouldConfirmBlock) {
|
||||||
|
this.doBlockUser()
|
||||||
|
} else {
|
||||||
|
this.showConfirmBlock()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doBlockUser () {
|
||||||
this.$store.dispatch('blockUser', this.user.id)
|
this.$store.dispatch('blockUser', this.user.id)
|
||||||
|
this.hideConfirmBlock()
|
||||||
},
|
},
|
||||||
unblockUser () {
|
unblockUser () {
|
||||||
this.$store.dispatch('unblockUser', this.user.id)
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
},
|
},
|
||||||
removeUserFromFollowers () {
|
removeUserFromFollowers () {
|
||||||
|
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
||||||
|
this.doRemoveUserFromFollowers()
|
||||||
|
} else {
|
||||||
|
this.showConfirmRemoveUserFromFollowers()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doRemoveUserFromFollowers () {
|
||||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||||
|
this.hideConfirmRemoveUserFromFollowers()
|
||||||
},
|
},
|
||||||
reportUser () {
|
reportUser () {
|
||||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||||
|
@ -50,6 +83,12 @@ const AccountActions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
shouldConfirmBlock () {
|
||||||
|
return this.$store.getters.mergedConfig.modalOnBlock
|
||||||
|
},
|
||||||
|
shouldConfirmRemoveUserFromFollowers () {
|
||||||
|
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
||||||
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||||
})
|
})
|
||||||
|
|
|
@ -74,13 +74,56 @@
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingConfirmBlock"
|
||||||
|
:title="$t('user_card.block_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
||||||
|
@accepted="doBlockUser"
|
||||||
|
@cancelled="hideConfirmBlock"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="user_card.block_confirm"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<template #user>
|
||||||
|
<span
|
||||||
|
v-text="user.screen_name_ui"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</confirm-modal>
|
||||||
|
</teleport>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingConfirmRemoveFollower"
|
||||||
|
:title="$t('user_card.remove_follower_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
|
||||||
|
@accepted="doRemoveUserFromFollowers"
|
||||||
|
@cancelled="hideConfirmRemoveUserFromFollowers"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="user_card.remove_follower_confirm"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<template #user>
|
||||||
|
<span
|
||||||
|
v-text="user.screen_name_ui"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</confirm-modal>
|
||||||
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./account_actions.js"></script>
|
<script src="./account_actions.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.AccountActions {
|
.AccountActions {
|
||||||
.ellipsis-button {
|
.ellipsis-button {
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
|
|
|
@ -27,6 +27,9 @@ const Announcement = {
|
||||||
...mapState({
|
...mapState({
|
||||||
currentUser: state => state.users.currentUser
|
currentUser: state => state.users.currentUser
|
||||||
}),
|
}),
|
||||||
|
canEditAnnouncement () {
|
||||||
|
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
||||||
|
},
|
||||||
content () {
|
content () {
|
||||||
return this.announcement.content
|
return this.announcement.content
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,14 +45,14 @@
|
||||||
{{ $t('announcements.mark_as_read_action') }}
|
{{ $t('announcements.mark_as_read_action') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="canEditAnnouncement"
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="enterEditMode"
|
@click="enterEditMode"
|
||||||
>
|
>
|
||||||
{{ $t('announcements.edit_action') }}
|
{{ $t('announcements.edit_action') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="canEditAnnouncement"
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="deleteAnnouncement"
|
@click="deleteAnnouncement"
|
||||||
>
|
>
|
||||||
|
@ -102,19 +102,19 @@
|
||||||
@import "../../variables";
|
@import "../../variables";
|
||||||
|
|
||||||
.announcement {
|
.announcement {
|
||||||
border-bottom-width: 1px;
|
border-bottom: 1px solid var(--border, $fallback--border);
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin, $status-margin);
|
||||||
|
|
||||||
.heading, .body {
|
.heading,
|
||||||
|
.body {
|
||||||
margin-bottom: var(--status-margin, $status-margin);
|
margin-bottom: var(--status-margin, $status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.times {
|
.times {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -28,6 +28,9 @@ const AnnouncementsPage = {
|
||||||
}),
|
}),
|
||||||
announcements () {
|
announcements () {
|
||||||
return this.$store.state.announcements.announcements
|
return this.$store.state.announcements.announcements
|
||||||
|
},
|
||||||
|
canPostAnnouncement () {
|
||||||
|
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<section
|
<section
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="canPostAnnouncement"
|
||||||
>
|
>
|
||||||
<div class="post-form">
|
<div class="post-form">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
|
@ -67,7 +67,8 @@
|
||||||
.post-form {
|
.post-form {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin, $status-margin);
|
||||||
|
|
||||||
.heading, .body {
|
.heading,
|
||||||
|
.body {
|
||||||
margin-bottom: var(--status-margin, $status-margin);
|
margin-bottom: var(--status-margin, $status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,10 @@ export default {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin: .5em;
|
margin: 0.5em;
|
||||||
padding: .5em 2em;
|
padding: 0.5em 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,6 +36,7 @@ library.add(
|
||||||
const Attachment = {
|
const Attachment = {
|
||||||
props: [
|
props: [
|
||||||
'attachment',
|
'attachment',
|
||||||
|
'compact',
|
||||||
'description',
|
'description',
|
||||||
'hideDescription',
|
'hideDescription',
|
||||||
'nsfw',
|
'nsfw',
|
||||||
|
@ -71,7 +72,8 @@ const Attachment = {
|
||||||
{
|
{
|
||||||
'-loading': this.loading,
|
'-loading': this.loading,
|
||||||
'-nsfw-placeholder': this.hidden,
|
'-nsfw-placeholder': this.hidden,
|
||||||
'-editable': this.edit !== undefined
|
'-editable': this.edit !== undefined,
|
||||||
|
'-compact': this.compact
|
||||||
},
|
},
|
||||||
'-type-' + this.type,
|
'-type-' + this.type,
|
||||||
this.size && '-size-' + this.size,
|
this.size && '-size-' + this.size,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.Attachment {
|
.Attachment {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -102,14 +102,13 @@
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.play-icon {
|
.play-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 64px;
|
font-size: 64px;
|
||||||
top: calc(50% - 32px);
|
top: calc(50% - 32px);
|
||||||
left: calc(50% - 32px);
|
left: calc(50% - 32px);
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: rgb(255 255 255 / 75%);
|
||||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
|
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -135,18 +134,32 @@
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
// TODO: theming? hard to theme with unknown background image color
|
// 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 {
|
.svg-inline--fa {
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: rgb(0 0 0 / 60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .svg-inline--fa {
|
&: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 {
|
.oembed-container {
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
flex: 1 0 100%;
|
flex: 1 0 100%;
|
||||||
|
@ -160,8 +173,9 @@
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border: 0px;
|
border: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
@ -172,9 +186,10 @@
|
||||||
flex: 2;
|
flex: 2;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,17 +267,9 @@
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-contain-fit {
|
&.-compact {
|
||||||
img,
|
.placeholder-container {
|
||||||
canvas {
|
padding-bottom: 0.5em;
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-cover-fit {
|
|
||||||
img,
|
|
||||||
canvas {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,10 +162,11 @@
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
size="5x"
|
:size="compact ? '2x' : '5x'"
|
||||||
:icon="placeholderIconClass"
|
:icon="placeholderIconClass"
|
||||||
|
:title="localDescription"
|
||||||
/>
|
/>
|
||||||
<p>
|
<p v-if="!compact">
|
||||||
{{ localDescription }}
|
{{ localDescription }}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<script src="./autosuggest.js"></script>
|
<script src="./autosuggest.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.autosuggest {
|
.autosuggest {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
|
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
|
||||||
box-shadow: var(--panelShadow);
|
box-shadow: var(--panelShadow);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<script src="./avatar_list.js"></script>
|
<script src="./avatar_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.6em 1em;
|
padding: 0.6em 1em;
|
||||||
|
|
||||||
--emoji-size: 14px;
|
--emoji-size: 14px;
|
||||||
|
|
||||||
&-collapsed-content {
|
&-collapsed-content {
|
||||||
margin-left: 0.7em;
|
margin-left: 0.7em;
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
.block-card-content-container {
|
.block-card-content-container {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
min-height: calc(100vh - var(--navbar-height));
|
min-height: calc(100vh - var(--navbar-height));
|
||||||
margin: 0 0 0 0;
|
margin: 0;
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
|
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 1px rgb(0 0 0 / 30%), 0 2px 4px rgb(0 0 0 / 30%);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
transition: 0.35s all;
|
transition: 0.35s all;
|
||||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||||
|
|
|
@ -95,6 +95,6 @@
|
||||||
|
|
||||||
<script src="./chat.js"></script>
|
<script src="./chat.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
@import './chat.scss';
|
@import "./chat";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<script src="./chat_list.js"></script>
|
<script src="./chat_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.chat-list {
|
.chat-list {
|
||||||
min-height: 25em;
|
min-height: 25em;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--selectedPost, $fallback--lightBg);
|
background-color: var(--selectedPost, $fallback--lightBg);
|
||||||
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 3px 1px rgb(0 0 0 / 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-item-left {
|
.chat-list-item-left {
|
||||||
|
@ -67,6 +67,7 @@
|
||||||
canvas {
|
canvas {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
@ -79,13 +80,11 @@
|
||||||
|
|
||||||
.chat-preview-body {
|
.chat-preview-body {
|
||||||
--emoji-size: 1.4em;
|
--emoji-size: 1.4em;
|
||||||
|
|
||||||
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-wrapper {
|
.time-wrapper {
|
||||||
line-height: var(--post-line-height);
|
line-height: var(--post-line-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-preview-body {
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,6 @@
|
||||||
<script src="./chat_list_item.js"></script>
|
<script src="./chat_list_item.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
@import './chat_list_item.scss';
|
@import "./chat_list_item";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.chat-message-wrapper {
|
.chat-message-wrapper {
|
||||||
|
|
||||||
&.hovered-message-chain {
|
&.hovered-message-chain {
|
||||||
.animated.Avatar {
|
.animated.Avatar {
|
||||||
canvas {
|
canvas {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover, .extra-button-popover.open & {
|
&:hover,
|
||||||
|
.extra-button-popover.open & {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
|
@ -54,27 +55,11 @@
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-preview, .attachments {
|
.link-preview,
|
||||||
|
.attachments {
|
||||||
margin-bottom: 1em;
|
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 {
|
.status {
|
||||||
border-radius: $fallback--chatMessageRadius;
|
border-radius: $fallback--chatMessageRadius;
|
||||||
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
||||||
|
@ -86,7 +71,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
margin: -1em 0 -0.5em 0;
|
margin: -1em 0 -0.5em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
@ -103,18 +88,54 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pending {
|
.pending {
|
||||||
.status-content.media-body, .created-at {
|
.status-content.media-body,
|
||||||
|
.created-at {
|
||||||
color: var(--faint);
|
color: var(--faint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
.status-content.media-body, .created-at {
|
.status-content.media-body,
|
||||||
|
.created-at {
|
||||||
color: $fallback--cRed;
|
color: $fallback--cRed;
|
||||||
color: var(--badgeNotification, $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 {
|
.incoming {
|
||||||
a {
|
a {
|
||||||
color: var(--chatMessageIncomingLink, $fallback--link);
|
color: var(--chatMessageIncomingLink, $fallback--link);
|
||||||
|
@ -137,36 +158,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.outgoing {
|
.chat-message-inner.with-media {
|
||||||
display: flex;
|
width: 100%;
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: end;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--chatMessageOutgoingLink, $fallback--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
color: var(--chatMessageOutgoingText, $fallback--text);
|
width: 100%;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.visible {
|
.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-date-separator {
|
.chat-message-date-separator {
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<div
|
<div
|
||||||
class="media status"
|
class="media status"
|
||||||
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
|
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
|
||||||
style="position: relative"
|
style="position: relative;"
|
||||||
@mouseenter="hovered = true"
|
@mouseenter="hovered = true"
|
||||||
@mouseleave="hovered = false"
|
@mouseleave="hovered = false"
|
||||||
>
|
>
|
||||||
|
@ -98,6 +98,6 @@
|
||||||
|
|
||||||
<script src="./chat_message.js"></script>
|
<script src="./chat_message.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import './chat_message.scss';
|
@import "./chat_message";
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.chat-new {
|
.chat-new {
|
||||||
.input-wrap {
|
.input-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0.7em 0.5em 0.7em 0.5em;
|
margin: 0.7em 0.5em;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -46,6 +46,6 @@
|
||||||
|
|
||||||
<script src="./chat_new.js"></script>
|
<script src="./chat_new.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
@import './chat_new.scss';
|
@import "./chat_new";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<script src="./chat_title.js"></script>
|
<script src="./chat_title.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.chat-title {
|
.chat-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
:class="{ disabled, indeterminate }"
|
:class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
class="visible-for-screenreader-only"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:checked="modelValue"
|
:checked="modelValue"
|
||||||
:indeterminate="indeterminate"
|
:indeterminate="indeterminate"
|
||||||
@change="$emit('update:modelValue', $event.target.checked)"
|
@change="$emit('update:modelValue', $event.target.checked)"
|
||||||
>
|
>
|
||||||
<i class="checkbox-indicator" />
|
<i
|
||||||
|
class="checkbox-indicator"
|
||||||
|
:aria-hidden="true"
|
||||||
|
@transitionend.capture="onTransitionEnd"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="!!$slots.default"
|
v-if="!!$slots.default"
|
||||||
class="label"
|
class="label"
|
||||||
|
@ -27,12 +32,30 @@ export default {
|
||||||
'indeterminate',
|
'indeterminate',
|
||||||
'disabled'
|
'disabled'
|
||||||
],
|
],
|
||||||
emits: ['update:modelValue']
|
emits: ['update:modelValue'],
|
||||||
|
data: (vm) => ({
|
||||||
|
indeterminateTransitionFix: vm.indeterminate
|
||||||
|
}),
|
||||||
|
watch: {
|
||||||
|
indeterminate (e) {
|
||||||
|
if (e) {
|
||||||
|
this.indeterminateTransitionFix = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onTransitionEnd (e) {
|
||||||
|
if (!this.indeterminate) {
|
||||||
|
this.indeterminateTransitionFix = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
@import "../../mixins";
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -49,13 +72,13 @@ export default {
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: block;
|
display: block;
|
||||||
content: '✓';
|
content: "✓";
|
||||||
transition: color 200ms;
|
transition: color 200ms;
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
border-radius: $fallback--checkboxRadius;
|
border-radius: $fallback--checkboxRadius;
|
||||||
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
|
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
|
||||||
box-shadow: 0px 0px 2px black inset;
|
box-shadow: 0 0 2px black inset;
|
||||||
box-shadow: var(--inputShadow);
|
box-shadow: var(--inputShadow);
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--input, $fallback--fg);
|
background-color: var(--input, $fallback--fg);
|
||||||
|
@ -71,32 +94,36 @@ export default {
|
||||||
&.disabled {
|
&.disabled {
|
||||||
.checkbox-indicator::before,
|
.checkbox-indicator::before,
|
||||||
.label {
|
.label {
|
||||||
opacity: .5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--faint, $fallback--faint);
|
color: var(--faint, $fallback--faint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type="checkbox"] {
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:checked + .checkbox-indicator::before {
|
&:checked + .checkbox-indicator::before {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--inputText, $fallback--text);
|
color: var(--inputText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:indeterminate + .checkbox-indicator::before {
|
&:indeterminate + .checkbox-indicator::before {
|
||||||
content: '–';
|
content: "–";
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--inputText, $fallback--text);
|
color: var(--inputText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.indeterminate-fix {
|
||||||
|
input[type="checkbox"] + .checkbox-indicator::before {
|
||||||
|
content: "–";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
margin-left: .5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.color-input {
|
.color-input {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
flex: 0 0 0;
|
flex: 0 0 0;
|
||||||
max-width: 9em;
|
max-width: 9em;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: .2em 8px;
|
padding: 0.2em 8px;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.computedIndicator,
|
.computedIndicator,
|
||||||
.transparentIndicator {
|
.transparentIndicator {
|
||||||
flex: 0 0 2em;
|
flex: 0 0 2em;
|
||||||
|
@ -38,22 +39,27 @@
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transparentIndicator {
|
.transparentIndicator {
|
||||||
// forgot to install counter-strike source, ooops
|
// forgot to install counter-strike source, ooops
|
||||||
background-color: #FF00FF;
|
background-color: #f0f;
|
||||||
position: relative;
|
position: relative;
|
||||||
&::before, &::after {
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: "";
|
||||||
background-color: #000000;
|
background-color: #000;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 50%;
|
height: 50%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -64,5 +70,4 @@
|
||||||
.label {
|
.label {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<dialog-modal
|
||||||
|
v-body-scroll-lock="true"
|
||||||
|
class="confirm-modal"
|
||||||
|
:on-cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span v-text="title" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click.prevent="onAccept"
|
||||||
|
v-text="confirmText"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click.prevent="onCancel"
|
||||||
|
v-text="cancelText"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</dialog-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./confirm_modal.js"></script>
|
|
@ -87,7 +87,6 @@ export default {
|
||||||
.contrast-ratio {
|
.contrast-ratio {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
|
|
@ -210,17 +210,16 @@
|
||||||
<script src="./conversation.js"></script>
|
<script src="./conversation.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.Conversation {
|
.Conversation {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.conversation-dive-to-top-level-box {
|
.conversation-dive-to-top-level-box {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin, $status-margin);
|
||||||
border-bottom-width: 1px;
|
border-bottom: 1px solid var(--border, $fallback--border);
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
/* Make the button stretch along the whole row */
|
/* Make the button stretch along the whole row */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
@ -235,52 +234,48 @@
|
||||||
.thread-ancestor.-faded .StatusContent {
|
.thread-ancestor.-faded .StatusContent {
|
||||||
--link: var(--faintLink);
|
--link: var(--faintLink);
|
||||||
--text: var(--faint);
|
--text: var(--faint);
|
||||||
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-dive-box {
|
.thread-ancestor-dive-box {
|
||||||
padding-left: var(--status-margin, $status-margin);
|
padding-left: var(--status-margin, $status-margin);
|
||||||
border-bottom-width: 1px;
|
border-bottom: 1px solid var(--border, $fallback--border);
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
/* Make the button stretch along the whole row */
|
/* Make the button stretch along the whole row */
|
||||||
&, &-inner {
|
&,
|
||||||
|
&-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-dive-box-inner {
|
.thread-ancestor-dive-box-inner {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin, $status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-status {
|
.conversation-status {
|
||||||
border-bottom-width: 1px;
|
border-bottom: 1px solid var(--border, $fallback--border);
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-has-other-replies .conversation-status,
|
.thread-ancestor-has-other-replies .conversation-status,
|
||||||
|
&:last-child .conversation-status,
|
||||||
.thread-ancestor:last-child .conversation-status,
|
.thread-ancestor:last-child .conversation-status,
|
||||||
.thread-ancestor:last-child .thread-ancestor-dive-box,
|
.thread-ancestor:last-child .thread-ancestor-dive-box,
|
||||||
&:last-child .conversation-status,
|
|
||||||
&.-expanded .thread-tree .conversation-status {
|
&.-expanded .thread-tree .conversation-status {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestors + .thread-tree > .conversation-status {
|
.thread-ancestors + .thread-tree > .conversation-status {
|
||||||
border-top-width: 1px;
|
border-top: 1px solid var(--border, $fallback--border);
|
||||||
border-top-style: solid;
|
|
||||||
border-top-color: var(--border, $fallback--border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* expanded conversation in timeline */
|
/* expanded conversation in timeline */
|
||||||
&.status-fadein.-expanded .thread-body {
|
&.status-fadein.-expanded .thread-body {
|
||||||
border-left-width: 4px;
|
border-left: 4px solid $fallback--cRed;
|
||||||
border-left-style: solid;
|
|
||||||
border-left-color: $fallback--cRed;
|
|
||||||
border-left-color: var(--cRed, $fallback--cRed);
|
border-left-color: var(--cRed, $fallback--cRed);
|
||||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import SearchBar from 'components/search_bar/search_bar.vue'
|
import SearchBar from 'components/search_bar/search_bar.vue'
|
||||||
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faSignInAlt,
|
faSignInAlt,
|
||||||
|
@ -30,7 +31,8 @@ library.add(
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SearchBar
|
SearchBar,
|
||||||
|
ConfirmModal
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
searchBarHidden: true,
|
searchBarHidden: true,
|
||||||
|
@ -40,7 +42,8 @@ export default {
|
||||||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||||
window.CSS.supports('-o-mask-size', 'contain')
|
window.CSS.supports('-o-mask-size', 'contain')
|
||||||
)
|
),
|
||||||
|
showingConfirmLogout: false
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
||||||
|
@ -73,21 +76,41 @@ export default {
|
||||||
hideSitename () { return this.$store.state.instance.hideSitename },
|
hideSitename () { return this.$store.state.instance.hideSitename },
|
||||||
logoLeft () { return this.$store.state.instance.logoLeft },
|
logoLeft () { return this.$store.state.instance.logoLeft },
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
currentUser () { return this.$store.state.users.currentUser },
|
||||||
privateMode () { return this.$store.state.instance.private }
|
privateMode () { return this.$store.state.instance.private },
|
||||||
|
shouldConfirmLogout () {
|
||||||
|
return this.$store.getters.mergedConfig.modalOnLogout
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollToTop () {
|
scrollToTop () {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
},
|
},
|
||||||
|
showConfirmLogout () {
|
||||||
|
this.showingConfirmLogout = true
|
||||||
|
},
|
||||||
|
hideConfirmLogout () {
|
||||||
|
this.showingConfirmLogout = false
|
||||||
|
},
|
||||||
logout () {
|
logout () {
|
||||||
|
if (!this.shouldConfirmLogout) {
|
||||||
|
this.doLogout()
|
||||||
|
} else {
|
||||||
|
this.showConfirmLogout()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doLogout () {
|
||||||
this.$router.replace('/main/public')
|
this.$router.replace('/main/public')
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
|
this.hideConfirmLogout()
|
||||||
},
|
},
|
||||||
onSearchBarToggled (hidden) {
|
onSearchBarToggled (hidden) {
|
||||||
this.searchBarHidden = hidden
|
this.searchBarHidden = hidden
|
||||||
},
|
},
|
||||||
openSettingsModal () {
|
openSettingsModal () {
|
||||||
this.$store.dispatch('openSettingsModal')
|
this.$store.dispatch('openSettingsModal', 'user')
|
||||||
|
},
|
||||||
|
openAdminModal () {
|
||||||
|
this.$store.dispatch('openSettingsModal', 'admin')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.DesktopNav {
|
.DesktopNav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -27,20 +27,13 @@
|
||||||
--miniColumn: 25rem;
|
--miniColumn: 25rem;
|
||||||
--maxiColumn: 45rem;
|
--maxiColumn: 45rem;
|
||||||
--columnGap: 1em;
|
--columnGap: 1em;
|
||||||
max-width: calc(
|
|
||||||
var(--sidebarColumnWidth, var(--miniColumn)) +
|
|
||||||
var(--contentColumnWidth, var(--maxiColumn)) +
|
|
||||||
var(--columnGap)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-column-stretch.-wide .inner-nav {
|
max-width:
|
||||||
max-width: calc(
|
calc(
|
||||||
var(--sidebarColumnWidth, var(--miniColumn)) +
|
var(--sidebarColumnWidth, var(--miniColumn)) +
|
||||||
var(--contentColumnWidth, var(--maxiColumn)) +
|
var(--contentColumnWidth, var(--maxiColumn)) +
|
||||||
var(--notifsColumnWidth, var(--miniColumn)) +
|
var(--columnGap)
|
||||||
var(--columnGap)
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-logoLeft .inner-nav {
|
&.-logoLeft .inner-nav {
|
||||||
|
@ -48,8 +41,19 @@
|
||||||
grid-template-areas: "logo sitename actions";
|
grid-template-areas: "logo sitename actions";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.-column-stretch.-wide .inner-nav {
|
||||||
|
max-width:
|
||||||
|
calc(
|
||||||
|
var(--sidebarColumnWidth, var(--miniColumn)) +
|
||||||
|
var(--contentColumnWidth, var(--maxiColumn)) +
|
||||||
|
var(--notifsColumnWidth, var(--miniColumn)) +
|
||||||
|
var(--columnGap)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
.button-default {
|
.button-default {
|
||||||
&, svg {
|
&,
|
||||||
|
svg {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnTopBarText, $fallback--text);
|
color: var(--btnTopBarText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +74,7 @@
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnToggledTopBarText, $fallback--text);
|
color: var(--btnToggledTopBarText, $fallback--text);
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--btnToggledTopBar, $fallback--fg)
|
background-color: var(--btnToggledTopBar, $fallback--fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +86,7 @@
|
||||||
transition-duration: 100ms;
|
transition-duration: 100ms;
|
||||||
|
|
||||||
@media all and (min-width: 800px) {
|
@media all and (min-width: 800px) {
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
class="logo"
|
class="logo"
|
||||||
:to="{ name: 'root' }"
|
:to="{ name: 'root' }"
|
||||||
:style="logoBgStyle"
|
:style="logoBgStyle"
|
||||||
|
:title="sitename"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mask"
|
class="mask"
|
||||||
|
@ -38,44 +39,55 @@
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="button-unstyled nav-icon"
|
class="button-unstyled nav-icon"
|
||||||
@click="openSettingsModal"
|
:title="$t('nav.preferences')"
|
||||||
|
@click.stop="openSettingsModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="cog"
|
icon="cog"
|
||||||
:title="$t('nav.preferences')"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<a
|
<button
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="currentUser && currentUser.role === 'admin'"
|
||||||
href="/pleroma/admin/#/login-pleroma"
|
class="button-unstyled nav-icon"
|
||||||
class="nav-icon"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@click.stop
|
:title="$t('nav.administration')"
|
||||||
|
@click.stop="openAdminModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="tachometer-alt"
|
icon="tachometer-alt"
|
||||||
:title="$t('nav.administration')"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</button>
|
||||||
<span class="spacer" />
|
<span class="spacer" />
|
||||||
<button
|
<button
|
||||||
v-if="currentUser"
|
v-if="currentUser"
|
||||||
class="button-unstyled nav-icon"
|
class="button-unstyled nav-icon"
|
||||||
@click.prevent="logout"
|
:title="$t('login.logout')"
|
||||||
|
@click.stop.prevent="logout"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="sign-out-alt"
|
icon="sign-out-alt"
|
||||||
:title="$t('login.logout')"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingConfirmLogout"
|
||||||
|
:title="$t('login.logout_confirm_title')"
|
||||||
|
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||||
|
@accepted="doLogout"
|
||||||
|
@cancelled="hideConfirmLogout"
|
||||||
|
>
|
||||||
|
{{ $t('login.logout_confirm') }}
|
||||||
|
</confirm-modal>
|
||||||
|
</teleport>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
<script src="./desktop_nav.js"></script>
|
<script src="./desktop_nav.js"></script>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<script src="./dialog_modal.js"></script>
|
<script src="./dialog_modal.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
// TODO: unify with other modals.
|
// TODO: unify with other modals.
|
||||||
.dark-overlay {
|
.dark-overlay {
|
||||||
|
@ -38,8 +38,8 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: rgba(27,31,35,.5);
|
background: rgb(27 31 35 / 50%);
|
||||||
z-index: 99;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
margin: 15vh auto;
|
margin: 15vh auto;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 999;
|
z-index: 2001;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: $fallback--bg;
|
background-color: $fallback--bg;
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
|
|
||||||
.dialog-modal-content {
|
.dialog-modal-content {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1rem 1rem;
|
padding: 1rem;
|
||||||
background-color: $fallback--bg;
|
background-color: $fallback--bg;
|
||||||
background-color: var(--bg, $fallback--bg);
|
background-color: var(--bg, $fallback--bg);
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
.dialog-modal-footer {
|
.dialog-modal-footer {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: .5em .5em;
|
padding: 0.5em;
|
||||||
background-color: $fallback--bg;
|
background-color: $fallback--bg;
|
||||||
background-color: var(--bg, $fallback--bg);
|
background-color: var(--bg, $fallback--bg);
|
||||||
border-top: 1px solid $fallback--border;
|
border-top: 1px solid $fallback--border;
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: auto;
|
width: auto;
|
||||||
margin-left: .5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
.modal-view.edit-form-modal-view {
|
.modal-view.edit-form-modal-view {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-form-modal-panel {
|
.edit-form-modal-panel {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-top: 25%;
|
margin-top: 25%;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Completion from '../../services/completion/completion.js'
|
import Completion from '../../services/completion/completion.js'
|
||||||
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||||
import Popover from 'src/components/popover/popover.vue'
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
|
import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
|
||||||
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||||
import { take } from 'lodash'
|
import { take } from 'lodash'
|
||||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||||
|
@ -109,9 +110,10 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
randomSeed: `${Math.random()}`.replace('.', '-'),
|
||||||
input: undefined,
|
input: undefined,
|
||||||
caretEl: undefined,
|
caretEl: undefined,
|
||||||
highlighted: 0,
|
highlighted: -1,
|
||||||
caret: 0,
|
caret: 0,
|
||||||
focused: false,
|
focused: false,
|
||||||
blurTimeout: null,
|
blurTimeout: null,
|
||||||
|
@ -125,12 +127,16 @@ const EmojiInput = {
|
||||||
components: {
|
components: {
|
||||||
Popover,
|
Popover,
|
||||||
EmojiPicker,
|
EmojiPicker,
|
||||||
UnicodeDomainIndicator
|
UnicodeDomainIndicator,
|
||||||
|
ScreenReaderNotice
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
padEmoji () {
|
padEmoji () {
|
||||||
return this.$store.getters.mergedConfig.padEmoji
|
return this.$store.getters.mergedConfig.padEmoji
|
||||||
},
|
},
|
||||||
|
defaultCandidateIndex () {
|
||||||
|
return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
|
||||||
|
},
|
||||||
preText () {
|
preText () {
|
||||||
return this.modelValue.slice(0, this.caret)
|
return this.modelValue.slice(0, this.caret)
|
||||||
},
|
},
|
||||||
|
@ -203,6 +209,12 @@ const EmojiInput = {
|
||||||
top: this.input.scrollTop,
|
top: this.input.scrollTop,
|
||||||
left: this.input.scrollLeft
|
left: this.input.scrollLeft
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
suggestionListId () {
|
||||||
|
return `suggestions-${this.randomSeed}`
|
||||||
|
},
|
||||||
|
suggestionItemId () {
|
||||||
|
return (index) => `suggestion-item-${index}-${this.randomSeed}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
@ -278,6 +290,11 @@ const EmojiInput = {
|
||||||
...rest,
|
...rest,
|
||||||
img: imageUrl || ''
|
img: imageUrl || ''
|
||||||
}))
|
}))
|
||||||
|
this.highlighted = this.defaultCandidateIndex
|
||||||
|
this.$refs.screenReaderNotice.announce(
|
||||||
|
this.$tc('tool_tip.autocomplete_available',
|
||||||
|
this.suggestions.length,
|
||||||
|
{ number: this.suggestions.length }))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -374,26 +391,27 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
cycleBackward (e) {
|
cycleBackward (e) {
|
||||||
const len = this.suggestions.length || 0
|
const len = this.suggestions.length || 0
|
||||||
if (len > 1) {
|
|
||||||
this.highlighted -= 1
|
this.highlighted -= 1
|
||||||
if (this.highlighted < 0) {
|
if (this.highlighted === -1) {
|
||||||
this.highlighted = this.suggestions.length - 1
|
this.input.focus()
|
||||||
}
|
} else if (this.highlighted < -1) {
|
||||||
|
this.highlighted = len - 1
|
||||||
|
}
|
||||||
|
if (len > 0) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
} else {
|
|
||||||
this.highlighted = 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cycleForward (e) {
|
cycleForward (e) {
|
||||||
const len = this.suggestions.length || 0
|
const len = this.suggestions.length || 0
|
||||||
if (len > 1) {
|
|
||||||
this.highlighted += 1
|
this.highlighted += 1
|
||||||
if (this.highlighted >= len) {
|
if (this.highlighted >= len) {
|
||||||
this.highlighted = 0
|
this.highlighted = -1
|
||||||
}
|
this.input.focus()
|
||||||
|
}
|
||||||
|
if (len > 0) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
} else {
|
|
||||||
this.highlighted = 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollIntoView () {
|
scrollIntoView () {
|
||||||
|
@ -540,6 +558,13 @@ const EmojiInput = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resize () {
|
resize () {
|
||||||
|
},
|
||||||
|
autoCompleteItemLabel (suggestion) {
|
||||||
|
if (suggestion.user) {
|
||||||
|
return suggestion.displayText + ' ' + suggestion.detailText
|
||||||
|
} else {
|
||||||
|
return this.maybeLocalizedEmojiName(suggestion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,19 @@
|
||||||
class="emoji-input"
|
class="emoji-input"
|
||||||
:class="{ 'with-picker': !hideEmojiButton }"
|
:class="{ 'with-picker': !hideEmojiButton }"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot
|
||||||
|
:id="'textbox-' + randomSeed"
|
||||||
|
:aria-owns="suggestionListId"
|
||||||
|
aria-autocomplete="both"
|
||||||
|
:aria-expanded="showSuggestions"
|
||||||
|
:aria-activedescendant="(!showSuggestions || highlighted === -1) ? '' : suggestionItemId(highlighted)"
|
||||||
|
/>
|
||||||
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
||||||
<div
|
<div
|
||||||
ref="hiddenOverlay"
|
ref="hiddenOverlay"
|
||||||
class="hidden-overlay"
|
class="hidden-overlay"
|
||||||
:style="overlayStyle"
|
:style="overlayStyle"
|
||||||
|
:aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span>{{ preText }}</span>
|
<span>{{ preText }}</span>
|
||||||
<span
|
<span
|
||||||
|
@ -18,11 +25,16 @@
|
||||||
>x</span>
|
>x</span>
|
||||||
<span>{{ postText }}</span>
|
<span>{{ postText }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<screen-reader-notice
|
||||||
|
ref="screenReaderNotice"
|
||||||
|
aria-live="assertive"
|
||||||
|
/>
|
||||||
<template v-if="enableEmojiPicker">
|
<template v-if="enableEmojiPicker">
|
||||||
<button
|
<button
|
||||||
v-if="!hideEmojiButton"
|
v-if="!hideEmojiButton"
|
||||||
class="button-unstyled emoji-picker-icon"
|
class="button-unstyled emoji-picker-icon"
|
||||||
type="button"
|
type="button"
|
||||||
|
:title="$t('emoji.add_emoji')"
|
||||||
@click.prevent="togglePicker"
|
@click.prevent="togglePicker"
|
||||||
>
|
>
|
||||||
<FAIcon :icon="['far', 'smile-beam']" />
|
<FAIcon :icon="['far', 'smile-beam']" />
|
||||||
|
@ -43,17 +55,24 @@
|
||||||
ref="suggestorPopover"
|
ref="suggestorPopover"
|
||||||
class="autocomplete-panel"
|
class="autocomplete-panel"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
:trigger-attrs="{ 'aria-hidden': true }"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div
|
<div
|
||||||
|
:id="suggestionListId"
|
||||||
ref="panel-body"
|
ref="panel-body"
|
||||||
class="autocomplete-panel-body"
|
class="autocomplete-panel-body"
|
||||||
|
role="listbox"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(suggestion, index) in suggestions"
|
v-for="(suggestion, index) in suggestions"
|
||||||
|
:id="suggestionItemId(index)"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="autocomplete-item"
|
class="autocomplete-item"
|
||||||
|
role="option"
|
||||||
:class="{ highlighted: index === highlighted }"
|
:class="{ highlighted: index === highlighted }"
|
||||||
|
:aria-label="autoCompleteItemLabel(suggestion)"
|
||||||
|
:aria-selected="index === highlighted"
|
||||||
@click.stop.prevent="onClick($event, suggestion)"
|
@click.stop.prevent="onClick($event, suggestion)"
|
||||||
>
|
>
|
||||||
<span class="image">
|
<span class="image">
|
||||||
|
@ -91,22 +110,18 @@
|
||||||
<script src="./emoji_input.js"></script>
|
<script src="./emoji_input.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.emoji-input {
|
.emoji-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.with-picker input {
|
|
||||||
padding-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-picker-icon {
|
.emoji-picker-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: .2em .25em;
|
margin: 0.2em 0.25em;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -123,14 +138,19 @@
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
|
||||||
&.hide {
|
&.hide {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input, textarea {
|
input,
|
||||||
|
textarea {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.with-picker input {
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden-overlay {
|
.hidden-overlay {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -140,8 +160,10 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
/* DEBUG STUFF */
|
/* DEBUG STUFF */
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
/* set opacity to non-zero to see the overlay */
|
/* set opacity to non-zero to see the overlay */
|
||||||
|
|
||||||
.caret {
|
.caret {
|
||||||
|
@ -151,6 +173,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete {
|
.autocomplete {
|
||||||
&-panel {
|
&-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -160,7 +183,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
|
border-bottom: 1px solid rgb(0 0 0 / 40%);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
@ -169,7 +192,6 @@
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
|
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -199,6 +221,7 @@
|
||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--selectedMenuPopover, $fallback--fg);
|
background-color: var(--selectedMenuPopover, $fallback--fg);
|
||||||
color: var(--selectedMenuPopoverText, $fallback--text);
|
color: var(--selectedMenuPopoverText, $fallback--text);
|
||||||
|
|
||||||
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
||||||
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
||||||
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
||||||
|
|
|
@ -94,8 +94,9 @@ export const suggestUsers = ({ dispatch, state }) => {
|
||||||
|
|
||||||
const newSuggestions = state.users.users.filter(
|
const newSuggestions = state.users.users.filter(
|
||||||
user =>
|
user =>
|
||||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
user.screen_name && user.name && (
|
||||||
user.name.toLowerCase().startsWith(noPrefix)
|
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||||
|
user.name.toLowerCase().startsWith(noPrefix))
|
||||||
).slice(0, 20).sort((a, b) => {
|
).slice(0, 20).sort((a, b) => {
|
||||||
let aScore = 0
|
let aScore = 0
|
||||||
let bScore = 0
|
let bScore = 0
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Checkbox from '../checkbox/checkbox.vue'
|
||||||
import Popover from 'src/components/popover/popover.vue'
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
import StillImage from '../still-image/still-image.vue'
|
import StillImage from '../still-image/still-image.vue'
|
||||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||||
import lozad from 'lozad'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faBoxOpen,
|
faBoxOpen,
|
||||||
|
@ -19,7 +18,7 @@ import {
|
||||||
faCode,
|
faCode,
|
||||||
faFlag
|
faFlag
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { debounce, trim } from 'lodash'
|
import { debounce, trim, chunk } from 'lodash'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faBoxOpen,
|
faBoxOpen,
|
||||||
|
@ -82,14 +81,31 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
|
||||||
return orderedEmojiList.flat()
|
return orderedEmojiList.flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOffset = (elem) => {
|
||||||
|
const style = elem.style.transform
|
||||||
|
const res = /translateY\((\d+)px\)/.exec(style)
|
||||||
|
if (!res) { return 0 }
|
||||||
|
return res[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toHeaderId = id => {
|
||||||
|
return id.replace(/^row-\d+-/, '')
|
||||||
|
}
|
||||||
|
|
||||||
const EmojiPicker = {
|
const EmojiPicker = {
|
||||||
props: {
|
props: {
|
||||||
enableStickerPicker: {
|
enableStickerPicker: {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
hideCustomEmoji: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
inject: ['popoversZLayer'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
@ -102,7 +118,8 @@ const EmojiPicker = {
|
||||||
contentLoaded: false,
|
contentLoaded: false,
|
||||||
groupRefs: {},
|
groupRefs: {},
|
||||||
emojiRefs: {},
|
emojiRefs: {},
|
||||||
filteredEmojiGroups: []
|
filteredEmojiGroups: [],
|
||||||
|
width: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -125,9 +142,6 @@ const EmojiPicker = {
|
||||||
setGroupRef (name) {
|
setGroupRef (name) {
|
||||||
return el => { this.groupRefs[name] = el }
|
return el => { this.groupRefs[name] = el }
|
||||||
},
|
},
|
||||||
setEmojiRef (name) {
|
|
||||||
return el => { this.emojiRefs[name] = el }
|
|
||||||
},
|
|
||||||
onPopoverShown () {
|
onPopoverShown () {
|
||||||
this.$emit('show')
|
this.$emit('show')
|
||||||
},
|
},
|
||||||
|
@ -147,18 +161,21 @@ const EmojiPicker = {
|
||||||
}
|
}
|
||||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||||
},
|
},
|
||||||
onScroll (e) {
|
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
|
||||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
const target = this.$refs['emoji-groups'].$el
|
||||||
this.updateScrolledClass(target)
|
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
|
||||||
this.scrolledGroup(target)
|
|
||||||
},
|
},
|
||||||
scrolledGroup (target) {
|
scrolledGroup (target, start, end) {
|
||||||
const top = target.scrollTop + 5
|
const top = target.scrollTop + 5
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.allEmojiGroups.forEach(group => {
|
this.emojiItems.slice(start, end + 1).forEach(group => {
|
||||||
|
const headerId = toHeaderId(group.id)
|
||||||
const ref = this.groupRefs['group-' + group.id]
|
const ref = this.groupRefs['group-' + group.id]
|
||||||
if (ref && ref.offsetTop <= top) {
|
if (!ref) { return }
|
||||||
this.activeGroup = group.id
|
const elem = ref.$el.parentElement
|
||||||
|
if (!elem) { return }
|
||||||
|
if (elem && getOffset(elem) <= top) {
|
||||||
|
this.activeGroup = headerId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.scrollHeader()
|
this.scrollHeader()
|
||||||
|
@ -181,14 +198,10 @@ const EmojiPicker = {
|
||||||
setScroll(right + margin - headerCont.clientWidth)
|
setScroll(right + margin - headerCont.clientWidth)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
highlight (key) {
|
highlight (groupId) {
|
||||||
const ref = this.groupRefs['group-' + key]
|
|
||||||
const top = ref.offsetTop
|
|
||||||
this.setShowStickers(false)
|
this.setShowStickers(false)
|
||||||
this.activeGroup = key
|
const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
|
||||||
this.$nextTick(() => {
|
this.$refs['emoji-groups'].scrollToItem(indexInList)
|
||||||
this.$refs['emoji-groups'].scrollTop = top + 1
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
updateScrolledClass (target) {
|
updateScrolledClass (target) {
|
||||||
if (target.scrollTop <= 5) {
|
if (target.scrollTop <= 5) {
|
||||||
|
@ -208,43 +221,13 @@ const EmojiPicker = {
|
||||||
filterByKeyword (list, keyword) {
|
filterByKeyword (list, keyword) {
|
||||||
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
|
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
|
||||||
},
|
},
|
||||||
initializeLazyLoad () {
|
|
||||||
this.destroyLazyLoad()
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$lozad = lozad('.still-image.emoji-picker-emoji', {
|
|
||||||
load: el => {
|
|
||||||
const name = el.getAttribute('data-emoji-name')
|
|
||||||
const vn = this.emojiRefs[name]
|
|
||||||
if (!vn) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vn.loadLazy()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.$lozad.observe()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
waitForDomAndInitializeLazyLoad () {
|
|
||||||
this.$nextTick(() => this.initializeLazyLoad())
|
|
||||||
},
|
|
||||||
destroyLazyLoad () {
|
|
||||||
if (this.$lozad) {
|
|
||||||
if (this.$lozad.observer) {
|
|
||||||
this.$lozad.observer.disconnect()
|
|
||||||
}
|
|
||||||
if (this.$lozad.mutationObserver) {
|
|
||||||
this.$lozad.mutationObserver.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onShowing () {
|
onShowing () {
|
||||||
const oldContentLoaded = this.contentLoaded
|
const oldContentLoaded = this.contentLoaded
|
||||||
|
this.recalculateItemPerRow()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.search.focus()
|
this.$refs.search.focus()
|
||||||
})
|
})
|
||||||
this.contentLoaded = true
|
this.contentLoaded = true
|
||||||
this.waitForDomAndInitializeLazyLoad()
|
|
||||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||||
if (!oldContentLoaded) {
|
if (!oldContentLoaded) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
@ -261,6 +244,14 @@ const EmojiPicker = {
|
||||||
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
||||||
}))
|
}))
|
||||||
.filter(group => group.emojis.length > 0)
|
.filter(group => group.emojis.length > 0)
|
||||||
|
},
|
||||||
|
recalculateItemPerRow () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (!this.$refs['emoji-groups']) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.width = this.$refs['emoji-groups'].$el.clientWidth
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -269,14 +260,22 @@ const EmojiPicker = {
|
||||||
this.debouncedHandleKeywordChange()
|
this.debouncedHandleKeywordChange()
|
||||||
},
|
},
|
||||||
allCustomGroups () {
|
allCustomGroups () {
|
||||||
this.waitForDomAndInitializeLazyLoad()
|
|
||||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
|
||||||
this.destroyLazyLoad()
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
|
minItemSize () {
|
||||||
|
return this.emojiHeight
|
||||||
|
},
|
||||||
|
emojiHeight () {
|
||||||
|
return 32 + 4
|
||||||
|
},
|
||||||
|
emojiWidth () {
|
||||||
|
return 32 + 4
|
||||||
|
},
|
||||||
|
itemPerRow () {
|
||||||
|
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
|
||||||
|
},
|
||||||
activeGroupView () {
|
activeGroupView () {
|
||||||
return this.showingStickers ? '' : this.activeGroup
|
return this.showingStickers ? '' : this.activeGroup
|
||||||
},
|
},
|
||||||
|
@ -287,7 +286,14 @@ const EmojiPicker = {
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
allCustomGroups () {
|
allCustomGroups () {
|
||||||
return this.$store.getters.groupedCustomEmojis
|
if (this.hideCustomEmoji) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const emojis = this.$store.getters.groupedCustomEmojis
|
||||||
|
if (emojis.unpacked) {
|
||||||
|
emojis.unpacked.text = this.$t('emoji.unpacked')
|
||||||
|
}
|
||||||
|
return emojis
|
||||||
},
|
},
|
||||||
defaultGroup () {
|
defaultGroup () {
|
||||||
return Object.keys(this.allCustomGroups)[0]
|
return Object.keys(this.allCustomGroups)[0]
|
||||||
|
@ -310,10 +316,20 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
debouncedHandleKeywordChange () {
|
debouncedHandleKeywordChange () {
|
||||||
return debounce(() => {
|
return debounce(() => {
|
||||||
this.waitForDomAndInitializeLazyLoad()
|
|
||||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
|
emojiItems () {
|
||||||
|
return this.filteredEmojiGroups.map(group =>
|
||||||
|
chunk(group.emojis, this.itemPerRow)
|
||||||
|
.map((items, index) => ({
|
||||||
|
...group,
|
||||||
|
id: index === 0 ? group.id : `row-${index}-${group.id}`,
|
||||||
|
emojis: items,
|
||||||
|
isFirstRow: index === 0
|
||||||
|
})))
|
||||||
|
.reduce((a, c) => a.concat(c), [])
|
||||||
|
},
|
||||||
languages () {
|
languages () {
|
||||||
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
||||||
},
|
},
|
||||||
|
@ -335,6 +351,9 @@ const EmojiPicker = {
|
||||||
|
|
||||||
return emoji.displayText
|
return emoji.displayText
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isInModal () {
|
||||||
|
return this.popoversZLayer === 'modals'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
$emoji-picker-header-height: 36px;
|
$emoji-picker-header-height: 36px;
|
||||||
$emoji-picker-header-picture-width: 32px;
|
$emoji-picker-header-picture-width: 32px;
|
||||||
|
@ -7,14 +7,14 @@ $emoji-picker-emoji-size: 32px;
|
||||||
|
|
||||||
.emoji-picker {
|
.emoji-picker {
|
||||||
width: 25em;
|
width: 25em;
|
||||||
max-width: 100vw;
|
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: $fallback--bg;
|
background-color: $fallback--bg;
|
||||||
background-color: var(--popover, $fallback--bg);
|
background-color: var(--popover, $fallback--bg);
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
color: var(--popoverText, $fallback--link);
|
color: var(--popoverText, $fallback--link);
|
||||||
--lightText: var(--popoverLightText, $fallback--faint);
|
|
||||||
--faint: var(--popoverFaintText, $fallback--faint);
|
--faint: var(--popoverFaintText, $fallback--faint);
|
||||||
--faintLink: var(--popoverFaintLink, $fallback--faint);
|
--faintLink: var(--popoverFaintLink, $fallback--faint);
|
||||||
--lightText: var(--popoverLightText, $fallback--lightText);
|
--lightText: var(--popoverLightText, $fallback--lightText);
|
||||||
|
@ -28,6 +28,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
max-width: $emoji-picker-header-picture-width;
|
max-width: $emoji-picker-header-picture-width;
|
||||||
height: $emoji-picker-header-picture-height;
|
height: $emoji-picker-header-picture-height;
|
||||||
max-height: $emoji-picker-header-picture-height;
|
max-height: $emoji-picker-header-picture-height;
|
||||||
|
|
||||||
.still-image {
|
.still-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
@ -62,24 +63,18 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0px;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-tabs {
|
.emoji-tabs {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-groups {
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additional-tabs {
|
.additional-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
|
||||||
border-left: 1px solid;
|
border-left: 1px solid;
|
||||||
border-left-color: $fallback--icon;
|
border-left-color: $fallback--icon;
|
||||||
border-left-color: var(--icon, $fallback--icon);
|
border-left-color: var(--icon, $fallback--icon);
|
||||||
|
@ -121,7 +116,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticker-picker {
|
.sticker-picker {
|
||||||
flex: 1 1 auto
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stickers,
|
.stickers,
|
||||||
|
@ -151,22 +146,27 @@ $emoji-picker-emoji-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-groups {
|
&-groups {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
mask:
|
||||||
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||||
linear-gradient(to top, white, white);
|
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
||||||
|
linear-gradient(to top, white, white);
|
||||||
transition: mask-size 150ms;
|
transition: mask-size 150ms;
|
||||||
mask-size: 100% 20px, 100% 20px, auto;
|
mask-size: 100% 20px, 100% 20px, auto;
|
||||||
// Autoprefixed seem to ignore this one, and also syntax is different
|
// Autoprefixed seem to ignore this one, and also syntax is different
|
||||||
-webkit-mask-composite: xor;
|
mask-composite: xor;
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
|
|
||||||
&.scrolled {
|
&.scrolled {
|
||||||
&-top {
|
&-top {
|
||||||
mask-size: 100% 20px, 100% 0, auto;
|
mask-size: 100% 20px, 100% 0, auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-bottom {
|
&-bottom {
|
||||||
mask-size: 100% 0, 100% 20px, auto;
|
mask-size: 100% 0, 100% 20px, auto;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,6 @@ $emoji-picker-emoji-size: 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.emoji-picker-emoji.-custom {
|
.emoji-picker-emoji.-custom {
|
||||||
|
@ -208,12 +207,11 @@ $emoji-picker-emoji-size: 32px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-emoji.-unicode {
|
.emoji-picker-emoji.-unicode {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,20 @@
|
||||||
ref="popover"
|
ref="popover"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popover-class="emoji-picker popover-default"
|
popover-class="emoji-picker popover-default"
|
||||||
|
:trigger-attrs="{ 'aria-hidden': true }"
|
||||||
@show="onPopoverShown"
|
@show="onPopoverShown"
|
||||||
@close="onPopoverClosed"
|
@close="onPopoverClosed"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
|
<!--
|
||||||
|
Body scroll lock needs to be on every scrollable element on safari iOS.
|
||||||
|
Here we tell it to enable scrolling for this element.
|
||||||
|
See https://github.com/willmcpo/body-scroll-lock#vanilla-js
|
||||||
|
-->
|
||||||
<span
|
<span
|
||||||
ref="header"
|
ref="header"
|
||||||
|
v-body-scroll-lock="isInModal"
|
||||||
class="emoji-tabs"
|
class="emoji-tabs"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
@ -21,6 +28,7 @@
|
||||||
active: activeGroupView === group.id
|
active: activeGroupView === group.id
|
||||||
}"
|
}"
|
||||||
:title="group.text"
|
:title="group.text"
|
||||||
|
role="button"
|
||||||
@click.prevent="highlight(group.id)"
|
@click.prevent="highlight(group.id)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
@ -74,45 +82,61 @@
|
||||||
@input="$event.target.composing = false"
|
@input="$event.target.composing = false"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<!-- Enables scrolling for this element on safari iOS. See comments for header. -->
|
||||||
|
<DynamicScroller
|
||||||
ref="emoji-groups"
|
ref="emoji-groups"
|
||||||
|
v-body-scroll-lock="isInModal"
|
||||||
class="emoji-groups"
|
class="emoji-groups"
|
||||||
:class="groupsScrolledClass"
|
:class="groupsScrolledClass"
|
||||||
@scroll="onScroll"
|
:min-item-size="minItemSize"
|
||||||
|
:items="emojiItems"
|
||||||
|
:emit-update="true"
|
||||||
|
@update="onScroll"
|
||||||
|
@visible="recalculateItemPerRow"
|
||||||
|
@resize="recalculateItemPerRow"
|
||||||
>
|
>
|
||||||
<div
|
<template #default="{ item: group, index, active }">
|
||||||
v-for="group in filteredEmojiGroups"
|
<DynamicScrollerItem
|
||||||
:key="group.id"
|
|
||||||
class="emoji-group"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
:ref="setGroupRef('group-' + group.id)"
|
:ref="setGroupRef('group-' + group.id)"
|
||||||
class="emoji-group-title"
|
:item="group"
|
||||||
|
:active="active"
|
||||||
|
:data-index="index"
|
||||||
|
:size-dependencies="[group.emojis.length]"
|
||||||
>
|
>
|
||||||
{{ group.text }}
|
<div
|
||||||
</h6>
|
class="emoji-group"
|
||||||
<span
|
>
|
||||||
v-for="emoji in group.emojis"
|
<h6
|
||||||
:key="group.id + emoji.displayText"
|
v-if="group.isFirstRow"
|
||||||
:title="maybeLocalizedEmojiName(emoji)"
|
class="emoji-group-title"
|
||||||
class="emoji-item"
|
>
|
||||||
@click.stop.prevent="onEmoji(emoji)"
|
{{ group.text }}
|
||||||
>
|
</h6>
|
||||||
<span
|
<span
|
||||||
v-if="!emoji.imageUrl"
|
v-for="emoji in group.emojis"
|
||||||
class="emoji-picker-emoji -unicode"
|
:key="group.id + emoji.displayText"
|
||||||
>{{ emoji.replacement }}</span>
|
:title="maybeLocalizedEmojiName(emoji)"
|
||||||
<still-image
|
class="emoji-item"
|
||||||
v-else
|
role="button"
|
||||||
:ref="setEmojiRef(group.id + emoji.displayText)"
|
@click.stop.prevent="onEmoji(emoji)"
|
||||||
class="emoji-picker-emoji -custom"
|
>
|
||||||
:data-src="emoji.imageUrl"
|
<span
|
||||||
:data-emoji-name="group.id + emoji.displayText"
|
v-if="!emoji.imageUrl"
|
||||||
/>
|
class="emoji-picker-emoji -unicode"
|
||||||
</span>
|
>{{ emoji.replacement }}</span>
|
||||||
<span :ref="setGroupRef('group-end-' + group.id)" />
|
<still-image
|
||||||
</div>
|
v-else
|
||||||
</div>
|
class="emoji-picker-emoji -custom"
|
||||||
|
loading="lazy"
|
||||||
|
:alt="maybeLocalizedEmojiName(emoji)"
|
||||||
|
:src="emoji.imageUrl"
|
||||||
|
:data-emoji-name="group.id + emoji.displayText"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DynamicScrollerItem>
|
||||||
|
</template>
|
||||||
|
</DynamicScroller>
|
||||||
<div class="keep-open">
|
<div class="keep-open">
|
||||||
<Checkbox v-model="keepOpen">
|
<Checkbox v-model="keepOpen">
|
||||||
{{ $t('emoji.keep_open') }}
|
{{ $t('emoji.keep_open') }}
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faPlus,
|
||||||
|
faMinus,
|
||||||
|
faCheck
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faPlus,
|
||||||
|
faMinus,
|
||||||
|
faCheck
|
||||||
|
)
|
||||||
|
|
||||||
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
||||||
|
|
||||||
|
@ -33,6 +45,9 @@ const EmojiReactions = {
|
||||||
},
|
},
|
||||||
loggedIn () {
|
loggedIn () {
|
||||||
return !!this.$store.state.users.currentUser
|
return !!this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
remoteInteractionLink () {
|
||||||
|
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -42,10 +57,10 @@ const EmojiReactions = {
|
||||||
reactedWith (emoji) {
|
reactedWith (emoji) {
|
||||||
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
||||||
},
|
},
|
||||||
fetchEmojiReactionsByIfMissing () {
|
async fetchEmojiReactionsByIfMissing () {
|
||||||
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
||||||
if (hasNoAccounts) {
|
if (hasNoAccounts) {
|
||||||
this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
|
return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reactWith (emoji) {
|
reactWith (emoji) {
|
||||||
|
@ -54,14 +69,26 @@ const EmojiReactions = {
|
||||||
unreact (emoji) {
|
unreact (emoji) {
|
||||||
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
},
|
},
|
||||||
emojiOnClick (emoji, event) {
|
async emojiOnClick (emoji, event) {
|
||||||
if (!this.loggedIn) return
|
if (!this.loggedIn) return
|
||||||
|
|
||||||
|
await this.fetchEmojiReactionsByIfMissing()
|
||||||
if (this.reactedWith(emoji)) {
|
if (this.reactedWith(emoji)) {
|
||||||
this.unreact(emoji)
|
this.unreact(emoji)
|
||||||
} else {
|
} else {
|
||||||
this.reactWith(emoji)
|
this.reactWith(emoji)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
counterTriggerAttrs (reaction) {
|
||||||
|
return {
|
||||||
|
class: [
|
||||||
|
'btn',
|
||||||
|
'button-default',
|
||||||
|
'emoji-reaction-count-button',
|
||||||
|
{ '-picked-reaction': this.reactedWith(reaction.name) }
|
||||||
|
],
|
||||||
|
'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="EmojiReactions">
|
<div class="EmojiReactions">
|
||||||
<UserListPopover
|
<span
|
||||||
v-for="(reaction) in emojiReactions"
|
v-for="(reaction) in emojiReactions"
|
||||||
:key="reaction.name"
|
:key="reaction.url || reaction.name"
|
||||||
:users="accountsForEmoji[reaction.name]"
|
class="emoji-reaction-container btn-group"
|
||||||
>
|
>
|
||||||
<button
|
<component
|
||||||
|
:is="loggedIn ? 'button' : 'a'"
|
||||||
|
v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
|
||||||
|
role="button"
|
||||||
class="emoji-reaction btn button-default"
|
class="emoji-reaction btn button-default"
|
||||||
:class="{ '-picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
:class="{ '-picked-reaction': reactedWith(reaction.name) }"
|
||||||
|
:title="reaction.url ? reaction.name : undefined"
|
||||||
|
:aria-pressed="reactedWith(reaction.name)"
|
||||||
@click="emojiOnClick(reaction.name, $event)"
|
@click="emojiOnClick(reaction.name, $event)"
|
||||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
|
||||||
>
|
>
|
||||||
<span class="reaction-emoji">{{ reaction.name }}</span>
|
<span
|
||||||
<span>{{ reaction.count }}</span>
|
class="reaction-emoji"
|
||||||
</button>
|
>
|
||||||
</UserListPopover>
|
<img
|
||||||
|
v-if="reaction.url"
|
||||||
|
:src="reaction.url"
|
||||||
|
class="reaction-emoji-content"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="reaction-emoji reaction-emoji-content"
|
||||||
|
>{{ reaction.name }}</span>
|
||||||
|
</span>
|
||||||
|
<FALayers>
|
||||||
|
<FAIcon
|
||||||
|
v-if="reactedWith(reaction.name)"
|
||||||
|
class="active-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="check"
|
||||||
|
/>
|
||||||
|
<FAIcon
|
||||||
|
v-if="!reactedWith(reaction.name)"
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="plus"
|
||||||
|
/>
|
||||||
|
<FAIcon
|
||||||
|
v-else
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="minus"
|
||||||
|
/>
|
||||||
|
</FALayers>
|
||||||
|
</component>
|
||||||
|
<UserListPopover
|
||||||
|
:users="accountsForEmoji[reaction.name]"
|
||||||
|
class="emoji-reaction-popover"
|
||||||
|
:trigger-attrs="counterTriggerAttrs(reaction)"
|
||||||
|
@show="fetchEmojiReactionsByIfMissing()"
|
||||||
|
>
|
||||||
|
<span class="emoji-reaction-counts">{{ reaction.count }}</span>
|
||||||
|
</UserListPopover>
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
v-if="tooManyReactions"
|
v-if="tooManyReactions"
|
||||||
class="emoji-reaction-expand faint"
|
class="emoji-reaction-expand faint"
|
||||||
|
@ -28,43 +72,121 @@
|
||||||
|
|
||||||
<script src="./emoji_reactions.js"></script>
|
<script src="./emoji_reactions.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
@import "../../mixins";
|
||||||
|
|
||||||
.EmojiReactions {
|
.EmojiReactions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.emoji-reaction {
|
--emoji-size: calc(1.25em * var(--emojiReactionsScale, 1));
|
||||||
padding: 0 0.5em;
|
|
||||||
margin-right: 0.5em;
|
.emoji-reaction-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
|
||||||
|
.emoji-reaction-popover {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.emoji-reaction-count-button {
|
||||||
|
background-color: var(--btn);
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 2em;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--btnText, $fallback--text);
|
||||||
|
|
||||||
|
&.-picked-reaction {
|
||||||
|
border: 1px solid var(--accent, $fallback--link);
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-reaction {
|
||||||
|
padding-left: 0.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
.reaction-emoji {
|
.reaction-emoji {
|
||||||
width: 1.25em;
|
width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
|
line-height: var(--emoji-size);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reaction-emoji-content {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
line-height: inherit;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: calc(var(--emoji-size) * 0.8);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-clickable {
|
.svg-inline--fa {
|
||||||
cursor: default;
|
color: $fallback--text;
|
||||||
&:hover {
|
color: var(--btnText, $fallback--text);
|
||||||
box-shadow: $fallback--buttonShadow;
|
|
||||||
box-shadow: var(--buttonShadow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-picked-reaction {
|
&.-picked-reaction {
|
||||||
border: 1px solid var(--accent, $fallback--link);
|
border: 1px solid var(--accent, $fallback--link);
|
||||||
margin-left: -1px; // offset the border, can't use inset shadows either
|
margin-left: -1px; // offset the border, can't use inset shadows either
|
||||||
margin-right: calc(0.5em - 1px);
|
margin-right: -1px;
|
||||||
|
|
||||||
|
.svg-inline--fa {
|
||||||
|
color: $fallback--link;
|
||||||
|
color: var(--accent, $fallback--link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include unfocused-style {
|
||||||
|
.focus-marker {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-marker {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focused-style {
|
||||||
|
.svg-inline--fa {
|
||||||
|
color: $fallback--link;
|
||||||
|
color: var(--accent, $fallback--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-marker {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-marker {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +197,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faEllipsisH,
|
faEllipsisH,
|
||||||
|
@ -32,10 +33,14 @@ library.add(
|
||||||
|
|
||||||
const ExtraButtons = {
|
const ExtraButtons = {
|
||||||
props: ['status'],
|
props: ['status'],
|
||||||
components: { Popover },
|
components: {
|
||||||
|
Popover,
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
expanded: false
|
expanded: false,
|
||||||
|
showingDeleteDialog: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -46,11 +51,22 @@ const ExtraButtons = {
|
||||||
this.expanded = false
|
this.expanded = false
|
||||||
},
|
},
|
||||||
deleteStatus () {
|
deleteStatus () {
|
||||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
if (this.shouldConfirmDelete) {
|
||||||
if (confirmed) {
|
this.showDeleteStatusConfirmDialog()
|
||||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
} else {
|
||||||
|
this.doDeleteStatus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
doDeleteStatus () {
|
||||||
|
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||||
|
this.hideDeleteStatusConfirmDialog()
|
||||||
|
},
|
||||||
|
showDeleteStatusConfirmDialog () {
|
||||||
|
this.showingDeleteDialog = true
|
||||||
|
},
|
||||||
|
hideDeleteStatusConfirmDialog () {
|
||||||
|
this.showingDeleteDialog = false
|
||||||
|
},
|
||||||
pinStatus () {
|
pinStatus () {
|
||||||
this.$store.dispatch('pinStatus', this.status.id)
|
this.$store.dispatch('pinStatus', this.status.id)
|
||||||
.then(() => this.$emit('onSuccess'))
|
.then(() => this.$emit('onSuccess'))
|
||||||
|
@ -133,7 +149,10 @@ const ExtraButtons = {
|
||||||
isEdited () {
|
isEdited () {
|
||||||
return this.status.edited_at !== null
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,18 @@
|
||||||
/>
|
/>
|
||||||
</FALayers>
|
</FALayers>
|
||||||
</span>
|
</span>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="showingDeleteDialog"
|
||||||
|
:title="$t('status.delete_confirm_title')"
|
||||||
|
:cancel-text="$t('status.delete_confirm_cancel_button')"
|
||||||
|
:confirm-text="$t('status.delete_confirm_accept_button')"
|
||||||
|
@cancelled="hideDeleteStatusConfirmDialog"
|
||||||
|
@accepted="doDeleteStatus"
|
||||||
|
>
|
||||||
|
{{ $t('status.delete_confirm') }}
|
||||||
|
</ConfirmModal>
|
||||||
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
@ -172,15 +184,10 @@
|
||||||
<script src="./extra_buttons.js"></script>
|
<script src="./extra_buttons.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
@import '../../_mixins.scss';
|
@import "../../mixins";
|
||||||
|
|
||||||
.ExtraButtons {
|
.ExtraButtons {
|
||||||
/* override of popover internal stuff */
|
|
||||||
.popover-trigger-button {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-trigger {
|
.popover-trigger {
|
||||||
position: static;
|
position: static;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -190,10 +197,12 @@
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-trigger-button {
|
.popover-trigger-button {
|
||||||
|
/* override of popover internal stuff */
|
||||||
|
width: auto;
|
||||||
|
|
||||||
@include unfocused-style {
|
@include unfocused-style {
|
||||||
.focus-marker {
|
.focus-marker {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
|
@ -38,13 +38,20 @@
|
||||||
class="button-unstyled interactive"
|
class="button-unstyled interactive"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
role="button"
|
role="button"
|
||||||
|
:title="$t('tool_tip.favorite')"
|
||||||
:href="remoteInteractionLink"
|
:href="remoteInteractionLink"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FALayers class="fa-scale-110 fa-old-padding-layer">
|
||||||
class="fa-scale-110 fa-old-padding"
|
<FAIcon
|
||||||
:title="$t('tool_tip.favorite')"
|
class="fa-scale-110"
|
||||||
:icon="['far', 'star']"
|
:icon="['far', 'star']"
|
||||||
/>
|
/>
|
||||||
|
<FAIcon
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9 right-12"
|
||||||
|
icon="plus"
|
||||||
|
/>
|
||||||
|
</FALayers>
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
||||||
|
@ -58,8 +65,8 @@
|
||||||
<script src="./favorite_button.js"></script>
|
<script src="./favorite_button.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
@import '../../_mixins.scss';
|
@import "../../mixins";
|
||||||
|
|
||||||
.FavoriteButton {
|
.FavoriteButton {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -42,7 +42,8 @@
|
||||||
<script src="./flash.js"></script>
|
<script src="./flash.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.Flash {
|
.Flash {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
visibility: 'hidden';
|
visibility: "hidden";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||||
export default {
|
export default {
|
||||||
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
inProgress: false
|
inProgress: false,
|
||||||
|
showingConfirmUnfollow: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
shouldConfirmUnfollow () {
|
||||||
|
return this.$store.getters.mergedConfig.modalOnUnfollow
|
||||||
|
},
|
||||||
isPressed () {
|
isPressed () {
|
||||||
return this.inProgress || this.relationship.following
|
return this.inProgress || this.relationship.following
|
||||||
},
|
},
|
||||||
|
@ -35,6 +43,12 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
showConfirmUnfollow () {
|
||||||
|
this.showingConfirmUnfollow = true
|
||||||
|
},
|
||||||
|
hideConfirmUnfollow () {
|
||||||
|
this.showingConfirmUnfollow = false
|
||||||
|
},
|
||||||
onClick () {
|
onClick () {
|
||||||
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
||||||
},
|
},
|
||||||
|
@ -45,12 +59,21 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
unfollow () {
|
unfollow () {
|
||||||
|
if (this.shouldConfirmUnfollow) {
|
||||||
|
this.showConfirmUnfollow()
|
||||||
|
} else {
|
||||||
|
this.doUnfollow()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doUnfollow () {
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
this.inProgress = true
|
this.inProgress = true
|
||||||
requestUnfollow(this.relationship.id, store).then(() => {
|
requestUnfollow(this.relationship.id, store).then(() => {
|
||||||
this.inProgress = false
|
this.inProgress = false
|
||||||
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
|
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.hideConfirmUnfollow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,27 @@
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
<teleport to="#modal">
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingConfirmUnfollow"
|
||||||
|
:title="$t('user_card.unfollow_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.unfollow_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
|
||||||
|
@accepted="doUnfollow"
|
||||||
|
@cancelled="hideConfirmUnfollow"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="user_card.unfollow_confirm"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<template #user>
|
||||||
|
<span
|
||||||
|
v-text="user.screen_name_ui"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</confirm-modal>
|
||||||
|
</teleport>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
/>
|
/>
|
||||||
<RemoveFollowerButton
|
<RemoveFollowerButton
|
||||||
v-if="noFollowsYou && relationship.followed_by"
|
v-if="noFollowsYou && relationship.followed_by"
|
||||||
|
:user="user"
|
||||||
:relationship="relationship"
|
:relationship="relationship"
|
||||||
class="follow-card-button"
|
class="follow-card-button"
|
||||||
/>
|
/>
|
||||||
|
@ -39,9 +40,8 @@
|
||||||
&-content-container {
|
&-content-container {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
|
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
|
||||||
|
|
||||||
const FollowRequestCard = {
|
const FollowRequestCard = {
|
||||||
props: ['user'],
|
props: ['user'],
|
||||||
components: {
|
components: {
|
||||||
BasicUserCard
|
BasicUserCard,
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
showingApproveConfirmDialog: false,
|
||||||
|
showingDenyConfirmDialog: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
findFollowRequestNotificationId () {
|
findFollowRequestNotificationId () {
|
||||||
|
@ -13,7 +21,26 @@ const FollowRequestCard = {
|
||||||
)
|
)
|
||||||
return notif && notif.id
|
return notif && notif.id
|
||||||
},
|
},
|
||||||
|
showApproveConfirmDialog () {
|
||||||
|
this.showingApproveConfirmDialog = true
|
||||||
|
},
|
||||||
|
hideApproveConfirmDialog () {
|
||||||
|
this.showingApproveConfirmDialog = false
|
||||||
|
},
|
||||||
|
showDenyConfirmDialog () {
|
||||||
|
this.showingDenyConfirmDialog = true
|
||||||
|
},
|
||||||
|
hideDenyConfirmDialog () {
|
||||||
|
this.showingDenyConfirmDialog = false
|
||||||
|
},
|
||||||
approveUser () {
|
approveUser () {
|
||||||
|
if (this.shouldConfirmApprove) {
|
||||||
|
this.showApproveConfirmDialog()
|
||||||
|
} else {
|
||||||
|
this.doApprove()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doApprove () {
|
||||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
|
|
||||||
|
@ -25,14 +52,34 @@ const FollowRequestCard = {
|
||||||
notification.type = 'follow'
|
notification.type = 'follow'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this.hideApproveConfirmDialog()
|
||||||
},
|
},
|
||||||
denyUser () {
|
denyUser () {
|
||||||
|
if (this.shouldConfirmDeny) {
|
||||||
|
this.showDenyConfirmDialog()
|
||||||
|
} else {
|
||||||
|
this.doDeny()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doDeny () {
|
||||||
const notifId = this.findFollowRequestNotificationId()
|
const notifId = this.findFollowRequestNotificationId()
|
||||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||||
this.$store.dispatch('removeFollowRequest', this.user)
|
this.$store.dispatch('removeFollowRequest', this.user)
|
||||||
})
|
})
|
||||||
|
this.hideDenyConfirmDialog()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mergedConfig () {
|
||||||
|
return this.$store.getters.mergedConfig
|
||||||
|
},
|
||||||
|
shouldConfirmApprove () {
|
||||||
|
return this.mergedConfig.modalOnApproveFollow
|
||||||
|
},
|
||||||
|
shouldConfirmDeny () {
|
||||||
|
return this.mergedConfig.modalOnDenyFollow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,28 @@
|
||||||
{{ $t('user_card.deny') }}
|
{{ $t('user_card.deny') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingApproveConfirmDialog"
|
||||||
|
:title="$t('user_card.approve_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
||||||
|
@accepted="doApprove"
|
||||||
|
@cancelled="hideApproveConfirmDialog"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
||||||
|
</confirm-modal>
|
||||||
|
<confirm-modal
|
||||||
|
v-if="showingDenyConfirmDialog"
|
||||||
|
:title="$t('user_card.deny_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
||||||
|
@accepted="doDeny"
|
||||||
|
@cancelled="hideDenyConfirmDialog"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
||||||
|
</confirm-modal>
|
||||||
|
</teleport>
|
||||||
</basic-user-card>
|
</basic-user-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -22,8 +44,8 @@
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.follow-request-card-content-container {
|
.follow-request-card-content-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row wrap;
|
||||||
flex-wrap: wrap;
|
|
||||||
button {
|
button {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
:class="{ custom: isCustom }"
|
:class="{ custom: isCustom }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
:id="name + '-label'"
|
||||||
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
class="label"
|
class="label"
|
||||||
>
|
>
|
||||||
|
@ -12,7 +13,8 @@
|
||||||
<input
|
<input
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="typeof fallback !== 'undefined'"
|
||||||
:id="name + '-o'"
|
:id="name + '-o'"
|
||||||
class="opt exlcude-disabled"
|
:aria-labelledby="name + '-label'"
|
||||||
|
class="opt exlcude-disabled visible-for-screenreader-only"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="present"
|
:checked="present"
|
||||||
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="typeof fallback !== 'undefined'"
|
||||||
class="opt-l"
|
class="opt-l"
|
||||||
:for="name + '-o'"
|
:for="name + '-o'"
|
||||||
|
:aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{{ ' ' }}
|
{{ ' ' }}
|
||||||
<Select
|
<Select
|
||||||
|
@ -50,17 +53,20 @@
|
||||||
<script src="./font_control.js"></script>
|
<script src="./font_control.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.font-control {
|
.font-control {
|
||||||
input.custom-font {
|
input.custom-font {
|
||||||
min-width: 10em;
|
min-width: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.custom {
|
&.custom {
|
||||||
/* TODO Should make proper joiners... */
|
/* TODO Should make proper joiners... */
|
||||||
.font-switcher {
|
.font-switcher {
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-font {
|
.custom-font {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { sumBy, set } from 'lodash'
|
||||||
const Gallery = {
|
const Gallery = {
|
||||||
props: [
|
props: [
|
||||||
'attachments',
|
'attachments',
|
||||||
|
'compact',
|
||||||
'limitRows',
|
'limitRows',
|
||||||
'descriptions',
|
'descriptions',
|
||||||
'limit',
|
'limit',
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
v-for="(attachment, attachmentIndex) in row.items"
|
v-for="(attachment, attachmentIndex) in row.items"
|
||||||
:key="attachment.id"
|
:key="attachment.id"
|
||||||
class="gallery-item"
|
class="gallery-item"
|
||||||
|
:compact="compact"
|
||||||
:nsfw="nsfw"
|
:nsfw="nsfw"
|
||||||
:attachment="attachment"
|
:attachment="attachment"
|
||||||
:size="size"
|
:size="size"
|
||||||
|
@ -86,7 +87,7 @@
|
||||||
<script src='./gallery.js'></script>
|
<script src='./gallery.js'></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.Gallery {
|
.Gallery {
|
||||||
.gallery-rows {
|
.gallery-rows {
|
||||||
|
@ -100,6 +101,53 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.gallery-row-inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-content: stretch;
|
||||||
|
|
||||||
|
.gallery-item {
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// to make failed images a bit more noticeable on chromium
|
||||||
|
min-width: 2em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-grid {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0.5em;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
||||||
|
|
||||||
|
.gallery-item {
|
||||||
|
margin: 0;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-grid,
|
||||||
|
&.-minimal {
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.gallery-row-inner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +162,7 @@
|
||||||
linear-gradient(to top, white, white);
|
linear-gradient(to top, white, white);
|
||||||
|
|
||||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||||
-webkit-mask-composite: xor;
|
mask-composite: xor;
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,54 +186,5 @@
|
||||||
padding: 0 2em;
|
padding: 0 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-row {
|
|
||||||
&.-grid,
|
|
||||||
&.-minimal {
|
|
||||||
height: auto;
|
|
||||||
.gallery-row-inner {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-row-inner {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-content: stretch;
|
|
||||||
|
|
||||||
&.-grid {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-column-gap: 0.5em;
|
|
||||||
grid-row-gap: 0.5em;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
|
||||||
|
|
||||||
.gallery-item {
|
|
||||||
margin: 0;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item {
|
|
||||||
margin: 0 0.5em 0 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
// to make failed images a bit more noticeable on chromium
|
|
||||||
min-width: 2em;
|
|
||||||
&:last-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<script src="./global_notice_list.js"></script>
|
<script src="./global_notice_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.global-notice-list {
|
.global-notice-list {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
.global-success {
|
.global-success {
|
||||||
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
color: var(--alertPopupSuccessText, $fallback--text);
|
||||||
|
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
color: var(--alertPopupSuccessText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +82,7 @@
|
||||||
.global-info {
|
.global-info {
|
||||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
background-color: var(--alertPopupNeutral, $fallback--fg);
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
color: var(--alertPopupNeutralText, $fallback--text);
|
||||||
|
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
color: var(--alertPopupNeutralText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +90,7 @@
|
||||||
|
|
||||||
.close-notice {
|
.close-notice {
|
||||||
padding-right: 0.2em;
|
padding-right: 0.2em;
|
||||||
|
|
||||||
.svg-inline--fa:hover {
|
.svg-inline--fa:hover {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="interface-language-switcher">
|
||||||
<label for="interface-language-switcher">
|
<label>
|
||||||
{{ promptText }}
|
{{ promptText }}
|
||||||
</label>
|
</label>
|
||||||
{{ ' ' }}
|
<ul class="setting-list">
|
||||||
<Select
|
<li
|
||||||
id="interface-language-switcher"
|
v-for="index of controlledLanguage.keys()"
|
||||||
v-model="controlledLanguage"
|
:key="index"
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="lang in languages"
|
|
||||||
:key="lang.code"
|
|
||||||
:value="lang.code"
|
|
||||||
>
|
>
|
||||||
{{ lang.name }}
|
<label>
|
||||||
</option>
|
{{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
|
||||||
</Select>
|
<Select
|
||||||
|
class="language-select"
|
||||||
|
:model-value="controlledLanguage[index]"
|
||||||
|
@update:modelValue="val => setLanguageAt(index, val)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="lang in languages"
|
||||||
|
:key="lang.code"
|
||||||
|
:value="lang.code"
|
||||||
|
>
|
||||||
|
{{ lang.name }}
|
||||||
|
</option>
|
||||||
|
</Select>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
v-if="controlledLanguage.length > 1 && index !== 0"
|
||||||
|
class="button-default btn"
|
||||||
|
@click="() => removeLanguageAt(index)"
|
||||||
|
>
|
||||||
|
{{ $t('settings.remove_language') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="button-default btn"
|
||||||
|
@click="addLanguage"
|
||||||
|
>
|
||||||
|
{{ $t('settings.add_language') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -34,7 +59,7 @@ export default {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
language: {
|
language: {
|
||||||
type: String,
|
type: [Array, String],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
setLanguage: {
|
setLanguage: {
|
||||||
|
@ -48,7 +73,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
controlledLanguage: {
|
controlledLanguage: {
|
||||||
get: function () { return this.language },
|
get: function () {
|
||||||
|
return Array.isArray(this.language) ? this.language : [this.language]
|
||||||
|
},
|
||||||
set: function (val) {
|
set: function (val) {
|
||||||
this.setLanguage(val)
|
this.setLanguage(val)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +85,30 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
getLanguageName (code) {
|
getLanguageName (code) {
|
||||||
return localeService.getLanguageName(code)
|
return localeService.getLanguageName(code)
|
||||||
|
},
|
||||||
|
addLanguage () {
|
||||||
|
this.controlledLanguage = [...this.controlledLanguage, '']
|
||||||
|
},
|
||||||
|
setLanguageAt (index, val) {
|
||||||
|
const lang = [...this.controlledLanguage]
|
||||||
|
lang[index] = val
|
||||||
|
this.controlledLanguage = lang
|
||||||
|
},
|
||||||
|
removeLanguageAt (index) {
|
||||||
|
const lang = [...this.controlledLanguage]
|
||||||
|
lang.splice(index, 1)
|
||||||
|
this.controlledLanguage = lang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../variables";
|
||||||
|
|
||||||
|
.interface-language-switcher {
|
||||||
|
.language-select {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<script src="./link-preview.js"></script>
|
<script src="./link-preview.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.link-preview-card {
|
.link-preview-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -46,6 +46,7 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
max-width: 25%;
|
max-width: 25%;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-description {
|
.card-description {
|
||||||
margin: 0.5em 0 0 0;
|
margin: 0.5em 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="list">
|
<div
|
||||||
|
class="list"
|
||||||
|
role="list"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="getKey(item)"
|
:key="getKey(item)"
|
||||||
class="list-item"
|
class="list-item"
|
||||||
|
role="listitem"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
name="item"
|
name="item"
|
||||||
|
@ -35,7 +39,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
&-item:not(:last-child) {
|
&-item:not(:last-child) {
|
||||||
|
|
|
@ -21,12 +21,16 @@
|
||||||
<script src="./lists_card.js"></script>
|
<script src="./lists_card.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.list-card {
|
.list-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.list-name,
|
.list-name,
|
||||||
.button-list-edit {
|
.button-list-edit {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -39,13 +43,10 @@
|
||||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
color: var(--selectedMenuText, $fallback--link);
|
color: var(--selectedMenuText, $fallback--link);
|
||||||
|
|
||||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-name {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -95,10 +95,10 @@ const ListsNew = {
|
||||||
return this.addedUserIds.has(user.id)
|
return this.addedUserIds.has(user.id)
|
||||||
},
|
},
|
||||||
addUser (user) {
|
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) {
|
removeUser (userId) {
|
||||||
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
|
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
|
||||||
},
|
},
|
||||||
onSearchLoading (results) {
|
onSearchLoading (results) {
|
||||||
this.searchLoading = true
|
this.searchLoading = true
|
||||||
|
|
|
@ -164,7 +164,7 @@
|
||||||
<script src="./lists_edit.js"></script>
|
<script src="./lists_edit.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.ListEdit {
|
.ListEdit {
|
||||||
--panel-body-padding: 0.5em;
|
--panel-body-padding: 0.5em;
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
|
|
||||||
<script src="./lists_user_search.js"></script>
|
<script src="./lists_user_search.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.ListsUserSearch {
|
.ListsUserSearch {
|
||||||
.input-wrap {
|
.input-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0.7em 0.5em 0.7em 0.5em;
|
margin: 0.7em 0.5em;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
<script src="./login_form.js"></script>
|
<script src="./login_form.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import "../../variables";
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-bottom {
|
.login-bottom {
|
||||||
margin-top: 1.0em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0.3em 0.5em 0.6em;
|
padding: 0.3em 0.5em 0.6em;
|
||||||
line-height:24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-bottom {
|
.form-bottom {
|
||||||
|
@ -142,7 +142,6 @@
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
animation-name: shakeError;
|
animation-name: shakeError;
|
||||||
animation-duration: 0.4s;
|
animation-duration: 0.4s;
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
|
|
|
@ -63,6 +63,11 @@ const MediaModal = {
|
||||||
},
|
},
|
||||||
type () {
|
type () {
|
||||||
return this.currentMedia ? this.getType(this.currentMedia) : null
|
return this.currentMedia ? this.getType(this.currentMedia) : null
|
||||||
|
},
|
||||||
|
swipeDisableClickThreshold () {
|
||||||
|
// If there is only one media, allow more mouse movements to close the modal
|
||||||
|
// because there is less chance that the user wants to switch to another image
|
||||||
|
return () => this.canNavigate ? 1 : 30
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue