Merge remote-tracking branch 'upstream/develop' into checkbox-goes-brrr
This commit is contained in:
commit
da58282e90
188 changed files with 4165 additions and 2321 deletions
|
@ -1,19 +1,41 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-rscss/config",
|
||||
"stylelint-config-recommended",
|
||||
"stylelint-config-standard"
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-recommended-scss",
|
||||
"stylelint-config-html",
|
||||
"stylelint-config-recommended-vue/scss"
|
||||
],
|
||||
"rules": {
|
||||
"declaration-no-important": true,
|
||||
"rscss/no-descendant-combinator": false,
|
||||
"rscss/class-format": [
|
||||
true,
|
||||
false,
|
||||
{
|
||||
"component": "pascal-case",
|
||||
"variant": "^-[a-z]\\w+",
|
||||
"element": "^[a-z]\\w+"
|
||||
}
|
||||
],
|
||||
"selector-class-pattern": null,
|
||||
"import-notation": null,
|
||||
"custom-property-pattern": null,
|
||||
"keyframes-name-pattern": null,
|
||||
"scss/operator-no-newline-after": null,
|
||||
"declaration-block-no-redundant-longhand-properties": [
|
||||
true,
|
||||
{
|
||||
"ignoreShorthands": [
|
||||
"grid-template",
|
||||
"margin",
|
||||
"padding",
|
||||
"border",
|
||||
"border-width",
|
||||
"border-style",
|
||||
"border-color",
|
||||
"border-radius"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -3,52 +3,76 @@ 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/).
|
||||
|
||||
## Unreleased
|
||||
## 2.5.0 - 23.12.2022
|
||||
### Fixed
|
||||
- AdminFE button no longer scrolls page to top when clicked
|
||||
- UI no longer lags when switching between mobile and desktop mode
|
||||
- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
|
||||
- Emoji autocomplete popover and picker popover stick to the text cursor.
|
||||
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
|
||||
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
|
||||
- Fixed many many bugs related to new mentions, including spacing and alignment issues
|
||||
- Links in profile bios now properly open in new tabs
|
||||
- "Always show mobile button" is working now
|
||||
- Inline images now respect their intended width/height attributes
|
||||
- Links with `&` in them work properly now
|
||||
- Interaction list popovers now properly emojify names
|
||||
- Completely hidden posts still had 1px border
|
||||
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
|
||||
- Attachment description is prefilled with backend-provided default when uploading
|
||||
- Proper visual feedback that next image is loading when browsing
|
||||
- UI no longer lags when switching between mobile and desktop mode
|
||||
- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
|
||||
- "Always show mobile button" is working now
|
||||
- Additional HTML sanitization on frontend side in case backend sanitization fails
|
||||
- Interaction list popovers now properly emojify names
|
||||
- AdminFE button no longer scrolls page to top when clicked
|
||||
- User handles with non-ascii domains now have less intrusive indicator for the domain name
|
||||
- Completely hidden posts still no longer have 1px border
|
||||
- A lot of accessibility improvements
|
||||
|
||||
### Changed
|
||||
- Using Vue 3 now
|
||||
- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
|
||||
- A lot of internal dependencies updated
|
||||
- "(You)s" are optional (opt-in) now, bolding your nickname is also optional (opt-out)
|
||||
- User highlight background now also covers the `@`
|
||||
- Reverted back to textual `@`, svg version is opt-in.
|
||||
- Settings window has been thoroughly rearranged to make more sense and make navigation settings easier.
|
||||
- Uploaded attachments are uniform with displayed attachments
|
||||
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
|
||||
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post.
|
||||
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post. (You can expand them to full if need be)
|
||||
- Slight width/spacing adjustments
|
||||
- More sizing stuff is font-size dependent now
|
||||
- Scrollbars are styled/colorized now
|
||||
- Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in)
|
||||
- Updated localization files
|
||||
- Top bar is more useful in mobile mode now.
|
||||
- "Show new" button is way more compact in mobile mode
|
||||
- Slightly adjusted placement and spacing of the topbar buttons so it's less easy to accidentally log yourself out
|
||||
|
||||
### Added
|
||||
- 3 column mode: only enables when there's space for it (opt-out, customizable)
|
||||
- Apologetic pleroma-tan
|
||||
- New button on timeline header to change some of the new and often-used settings
|
||||
- Support for lists
|
||||
- Added ability to edit posts and view post edit history etc.
|
||||
- Added ability to add personal note to users
|
||||
- Added initial support for admin announcements
|
||||
- Added ui for account migration
|
||||
- Added ui for backups
|
||||
- Added ability to force-unfollow a user from you
|
||||
- Emoji are now grouped by pack
|
||||
- Ability to pin navigation items and collapse the navigation menu
|
||||
- Ability to rearrange order of attachments when uploading
|
||||
- Ability to scroll column (or page) to top via panel header button
|
||||
- Options to show domains in mentions
|
||||
- Option to show user avatars in mention links (opt-in)
|
||||
- Option to disable the tooltip for mentions
|
||||
- Option to completely hide muted threads
|
||||
- Option to customize what clicking user avatar does in user popover
|
||||
- Notifications for poll results
|
||||
- "Favorites" link in navigation
|
||||
- Very early and somewhat experimental system for automatic settings sync (used only for pinned navigation and apologetic pleroma-tan)
|
||||
- Implemented remote interaction with statuses for anon visitors
|
||||
- Ability to open videos in modal even if you disabled that feature, via an icon button
|
||||
- New button on attachment that indicates that attachment has a description and shows a bar filled with description
|
||||
- Attachments are truncated just like post contents
|
||||
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
|
||||
- Ability to rearrange order of attachments when uploading
|
||||
- Enabled users to zoom and pan images in media viewer with mouse and touch
|
||||
- Timelines/panels and conversations have sticky headers now
|
||||
- Added frontend ui for account migration
|
||||
- Implemented remote interaction with statuses
|
||||
- Timelines/panels and conversations have sticky headers now (a bit glitchy on some browsers like safari) (opt-out)
|
||||
|
||||
|
||||
## [2.4.2] - 2022-01-09
|
||||
|
|
27
README.md
27
README.md
|
@ -1,18 +1,19 @@
|
|||
# Pleroma-FE
|
||||
|
||||
> A single column frontend designed for Pleroma.
|
||||
> Highly-customizable frontend designed for Pleroma.
|
||||
|
||||

|
||||

|
||||
|
||||
# For Translators
|
||||
|
||||
To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
|
||||
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
|
||||
|
||||
# FOR ADMINS
|
||||
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
|
||||
|
||||
You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
|
||||
# For instance admins
|
||||
You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. Information of customizing PleromaFE settings/defaults is in our [guide](https://docs-develop.pleroma.social/frontend/CONFIGURATION/) and in case you want to build your own custom version there's [another](https://docs-develop.pleroma.social/frontend/HACKING/)
|
||||
|
||||
## Build Setup
|
||||
# Build Setup
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
|
@ -20,13 +21,13 @@ npm install -g yarn
|
|||
yarn
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
yarn dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
yarn build
|
||||
|
||||
# run unit tests
|
||||
npm run unit
|
||||
yarn unit
|
||||
```
|
||||
|
||||
# For Contributors:
|
||||
|
@ -40,10 +41,4 @@ FE Build process also leaves current commit hash in global variable `___pleromaf
|
|||
|
||||
# Configuration
|
||||
|
||||
Edit config.json for configuration.
|
||||
|
||||
## Options
|
||||
|
||||
### Login methods
|
||||
|
||||
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
|
||||
Set configuration settings in AdminFE, additionally you can edit config.json. For more details see [documentation](https://docs-develop.pleroma.social/frontend/CONFIGURATION/).
|
||||
|
|
|
@ -6,7 +6,7 @@ var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin')
|
|||
var CopyPlugin = require('copy-webpack-plugin');
|
||||
var { VueLoaderPlugin } = require('vue-loader')
|
||||
var ESLintPlugin = require('eslint-webpack-plugin');
|
||||
|
||||
var StylelintPlugin = require('stylelint-webpack-plugin');
|
||||
|
||||
var env = process.env.NODE_ENV
|
||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||
|
@ -111,6 +111,7 @@ module.exports = {
|
|||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-formatter-friendly')
|
||||
}),
|
||||
new StylelintPlugin({}),
|
||||
new VueLoaderPlugin(),
|
||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
||||
new CopyPlugin({
|
||||
|
|
|
@ -25,7 +25,17 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build*
|
|||
|
||||
### Replacing your instance's frontend with custom FE build
|
||||
|
||||
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
|
||||
#### New way (via AdminFE, a bit janky but works)
|
||||
|
||||
In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary.
|
||||
|
||||
You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended.
|
||||
|
||||
Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors.
|
||||
|
||||
#### Old way (replaces everything, hard to maintain, not recommended)
|
||||
|
||||
Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended.
|
||||
|
||||
### Running production build locally or on a separate server
|
||||
|
||||
|
|
BIN
image-1.png
Normal file
BIN
image-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 883 KiB |
BIN
image.png
Normal file
BIN
image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
|
@ -9,6 +9,7 @@
|
|||
<body class="hidden">
|
||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||
<div id="app"></div>
|
||||
<div id="modal"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<div id="popovers" />
|
||||
</body>
|
||||
|
|
86
package.json
86
package.json
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"name": "pleroma_fe",
|
||||
"version": "1.0.0",
|
||||
"description": "A Qvitter-style frontend for certain GS servers.",
|
||||
"author": "Roger Braun <roger@rogerbraun.net>",
|
||||
"private": true,
|
||||
"version": "2.5.0",
|
||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "node build/dev-server.js",
|
||||
"build": "node build/build.js",
|
||||
|
@ -11,17 +11,17 @@
|
|||
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
||||
"e2e": "node test/e2e/runner.js",
|
||||
"test": "npm run unit && npm run e2e",
|
||||
"stylelint": "npx stylelint src/components/status/status.scss",
|
||||
"stylelint": "npx stylelint '**/*.scss' '**/*.vue'",
|
||||
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
|
||||
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.20.0",
|
||||
"@babel/runtime": "7.20.7",
|
||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.0",
|
||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.2",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
|
||||
|
@ -34,51 +34,51 @@
|
|||
"escape-html": "1.0.3",
|
||||
"js-cookie": "3.0.1",
|
||||
"localforage": "1.10.0",
|
||||
"lozad": "1.16.0",
|
||||
"parse-link-header": "2.0.0",
|
||||
"phoenix": "1.6.2",
|
||||
"punycode.js": "2.1.0",
|
||||
"punycode.js": "2.3.0",
|
||||
"qrcode": "1.5.0",
|
||||
"querystring-es3": "0.2.1",
|
||||
"url": "0.11.0",
|
||||
"utf8": "3.0.0",
|
||||
"vue": "3.2.41",
|
||||
"vue": "3.2.45",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "4.1.6",
|
||||
"vue-template-compiler": "2.7.13",
|
||||
"vue-template-compiler": "2.7.14",
|
||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||
"vuex": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.19.6",
|
||||
"@babel/core": "7.20.7",
|
||||
"@babel/eslint-parser": "7.19.1",
|
||||
"@babel/plugin-transform-runtime": "7.19.6",
|
||||
"@babel/preset-env": "7.19.4",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/register": "7.18.9",
|
||||
"@intlify/vue-i18n-loader": "5.0.0",
|
||||
"@ungap/event-target": "0.2.3",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.1.1",
|
||||
"@vue/compiler-sfc": "3.2.41",
|
||||
"@vue/test-utils": "2.2.6",
|
||||
"autoprefixer": "10.4.12",
|
||||
"babel-loader": "8.2.5",
|
||||
"@vue/compiler-sfc": "3.2.45",
|
||||
"@vue/test-utils": "2.2.7",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"chai": "4.3.7",
|
||||
"chalk": "1.1.3",
|
||||
"chromedriver": "104.0.0",
|
||||
"chromedriver": "108.0.0",
|
||||
"connect-history-api-fallback": "2.0.0",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"cross-spawn": "7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"css-loader": "6.7.3",
|
||||
"css-minimizer-webpack-plugin": "4.2.2",
|
||||
"custom-event-polyfill": "1.0.7",
|
||||
"eslint": "8.29.0",
|
||||
"eslint": "8.32.0",
|
||||
"eslint-config-standard": "17.0.0",
|
||||
"eslint-formatter-friendly": "7.0.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-n": "15.6.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-n": "15.6.1",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.7.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"eslint-webpack-plugin": "3.2.0",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"express": "4.18.2",
|
||||
|
@ -94,36 +94,42 @@
|
|||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-sinon-chai": "2.0.2",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-spec-reporter": "0.0.34",
|
||||
"karma-spec-reporter": "0.0.36",
|
||||
"karma-webpack": "5.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.6.1",
|
||||
"mocha": "10.0.0",
|
||||
"nightwatch": "2.3.3",
|
||||
"mini-css-extract-plugin": "2.7.2",
|
||||
"mocha": "10.2.0",
|
||||
"nightwatch": "2.6.10",
|
||||
"opn": "5.5.0",
|
||||
"ora": "0.4.1",
|
||||
"postcss": "8.4.16",
|
||||
"postcss-loader": "7.0.1",
|
||||
"sass": "1.55.0",
|
||||
"sass-loader": "13.0.2",
|
||||
"postcss": "8.4.20",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-loader": "7.0.2",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"sass": "1.57.1",
|
||||
"sass-loader": "13.2.0",
|
||||
"selenium-server": "2.53.1",
|
||||
"semver": "7.3.8",
|
||||
"serviceworker-webpack5-plugin": "2.0.0",
|
||||
"shelljs": "0.8.5",
|
||||
"sinon": "14.0.2",
|
||||
"sinon": "15.0.1",
|
||||
"sinon-chai": "3.7.0",
|
||||
"stylelint": "13.13.1",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
"stylelint": "14.16.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended-scss": "^8.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "29.0.0",
|
||||
"stylelint-rscss": "0.4.0",
|
||||
"stylelint-webpack-plugin": "^3.3.0",
|
||||
"vue-loader": "17.0.1",
|
||||
"vue-style-loader": "4.1.3",
|
||||
"webpack": "5.74.0",
|
||||
"webpack": "5.75.0",
|
||||
"webpack-dev-middleware": "3.7.3",
|
||||
"webpack-hot-middleware": "2.25.2",
|
||||
"webpack-hot-middleware": "2.25.3",
|
||||
"webpack-merge": "0.20.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
"node": ">= 16.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
}
|
||||
|
|
76
src/App.scss
76
src/App.scss
|
@ -1,5 +1,7 @@
|
|||
// stylelint-disable rscss/class-format
|
||||
@import './_variables.scss';
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
@import "./variables";
|
||||
@import "./panel";
|
||||
|
||||
:root {
|
||||
--navbar-height: 3.5rem;
|
||||
|
@ -123,7 +125,7 @@ h4 {
|
|||
font-weight: 1000;
|
||||
}
|
||||
|
||||
i[class*=icon-],
|
||||
i[class*="icon-"],
|
||||
.svg-inline--fa,
|
||||
.iconLetter {
|
||||
color: $fallback--icon;
|
||||
|
@ -132,7 +134,7 @@ i[class*=icon-],
|
|||
|
||||
.button-unstyled:hover,
|
||||
a:hover {
|
||||
> i[class*=icon-],
|
||||
> i[class*="icon-"],
|
||||
> .svg-inline--fa,
|
||||
> .iconLetter {
|
||||
color: var(--text);
|
||||
|
@ -141,12 +143,11 @@ a:hover {
|
|||
|
||||
nav {
|
||||
z-index: var(--ZI_navbar);
|
||||
color: var(--topBarText);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--topBar, $fallback--fg);
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 0 4px rgb(0 0 0 / 60%);
|
||||
box-shadow: var(--topBarShadow);
|
||||
box-sizing: border-box;
|
||||
height: var(--navbar-height);
|
||||
|
@ -191,13 +192,11 @@ nav {
|
|||
}
|
||||
|
||||
.underlay {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: span 3;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 1;
|
||||
grid-column: 1 / span 3;
|
||||
grid-row: 1 / 1;
|
||||
pointer-events: none;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-color: var(--underlay, rgba(0, 0, 0, 0.15));
|
||||
background-color: rgb(0 0 0 / 15%);
|
||||
background-color: var(--underlay, rgb(0 0 0 / 15%));
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
|
@ -231,8 +230,7 @@ nav {
|
|||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
box-sizing: border-box;
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 1;
|
||||
grid-row: 1 / 1;
|
||||
margin: 0 calc(var(--___columnMargin) / 2);
|
||||
padding: calc(var(--___columnMargin)) 0;
|
||||
row-gap: var(--___columnMargin);
|
||||
|
@ -307,7 +305,7 @@ nav {
|
|||
align-content: start;
|
||||
}
|
||||
|
||||
&.-reverse:not(.-wide):not(.-mobile) {
|
||||
&.-reverse:not(.-wide, .-mobile) {
|
||||
grid-template-columns:
|
||||
var(--effectiveContentColumnWidth)
|
||||
var(--effectiveSidebarColumnWidth);
|
||||
|
@ -336,11 +334,8 @@ nav {
|
|||
padding: 0;
|
||||
|
||||
.column {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-top: 0;
|
||||
margin-top: var(--navbar-height);
|
||||
margin-bottom: 0;
|
||||
margin: var(--navbar-height) 0 0 0;
|
||||
}
|
||||
|
||||
.panel-heading,
|
||||
|
@ -389,7 +384,7 @@ nav {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
i[class*=icon-],
|
||||
i[class*="icon-"],
|
||||
.svg-inline--fa {
|
||||
color: $fallback--text;
|
||||
color: var(--btnText, $fallback--text);
|
||||
|
@ -400,12 +395,15 @@ nav {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 0 4px rgb(255 255 255 / 30%);
|
||||
box-shadow: var(--buttonHoverShadow);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
|
||||
box-shadow:
|
||||
0 0 4px 0 rgb(255 255 255 / 30%),
|
||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
|
||||
box-shadow: var(--buttonPressedShadow);
|
||||
color: $fallback--text;
|
||||
color: var(--btnPressedText, $fallback--text);
|
||||
|
@ -438,7 +436,10 @@ nav {
|
|||
color: var(--btnToggledText, $fallback--text);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btnToggled, $fallback--fg);
|
||||
box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
|
||||
box-shadow:
|
||||
0 0 4px 0 rgb(255 255 255 / 30%),
|
||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
|
||||
box-shadow: var(--buttonPressedShadow);
|
||||
|
||||
svg,
|
||||
|
@ -503,7 +504,10 @@ textarea,
|
|||
border: none;
|
||||
border-radius: $fallback--inputRadius;
|
||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset;
|
||||
box-shadow:
|
||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset,
|
||||
0 0 2px 0 rgb(0 0 0 / 100%) inset;
|
||||
box-shadow: var(--inputShadow);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--input, $fallback--fg);
|
||||
|
@ -521,13 +525,13 @@ textarea,
|
|||
padding: 0 var(--_padding);
|
||||
|
||||
&:disabled,
|
||||
&[disabled=disabled],
|
||||
&[disabled="disabled"],
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&[type=range] {
|
||||
&[type="range"] {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
@ -535,7 +539,7 @@ textarea,
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
&[type=radio] {
|
||||
&[type="radio"] {
|
||||
display: none;
|
||||
|
||||
&:checked + label::before {
|
||||
|
@ -555,7 +559,7 @@ textarea,
|
|||
+ label::before {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
content: '';
|
||||
content: "";
|
||||
transition: box-shadow 200ms;
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
|
@ -575,7 +579,7 @@ textarea,
|
|||
}
|
||||
}
|
||||
|
||||
&[type=checkbox] {
|
||||
&[type="checkbox"] {
|
||||
display: none;
|
||||
|
||||
&:checked + label::before {
|
||||
|
@ -594,7 +598,7 @@ textarea,
|
|||
+ label::before {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
content: '✓';
|
||||
content: "✓";
|
||||
transition: color 200ms;
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
|
@ -634,10 +638,10 @@ option {
|
|||
}
|
||||
|
||||
.hide-number-spinner {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
|
||||
&[type=number]::-webkit-inner-spin-button,
|
||||
&[type=number]::-webkit-outer-spin-button {
|
||||
&[type="number"]::-webkit-inner-spin-button,
|
||||
&[type="number"]::-webkit-outer-spin-button {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
@ -669,8 +673,6 @@ option {
|
|||
}
|
||||
}
|
||||
|
||||
@import './panel.scss';
|
||||
|
||||
.fa {
|
||||
color: grey;
|
||||
}
|
||||
|
@ -686,7 +688,7 @@ option {
|
|||
max-width: 10em;
|
||||
min-width: 1.7em;
|
||||
height: 1.3em;
|
||||
padding: 0.15em 0.15em;
|
||||
padding: 0.15em;
|
||||
vertical-align: middle;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
@ -789,7 +791,8 @@ option {
|
|||
|
||||
.fa-old-padding {
|
||||
&.iconLetter,
|
||||
&.svg-inline--fa, &-layer {
|
||||
&.svg-inline--fa,
|
||||
&-layer {
|
||||
padding: 0 0.3em;
|
||||
}
|
||||
}
|
||||
|
@ -883,3 +886,4 @@ option {
|
|||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
/* stylelint-enable no-descending-specificity */
|
||||
|
|
|
@ -71,7 +71,6 @@
|
|||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<UpdateNotification />
|
||||
<div id="modal" />
|
||||
<GlobalNoticeList />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
@mixin unfocused-style {
|
||||
@content;
|
||||
|
||||
&:focus:not(:focus-visible):not(:hover) {
|
||||
&:focus:not(:focus-visible, :hover) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin focused-style {
|
||||
&:hover, &:focus {
|
||||
&:hover,
|
||||
&:focus {
|
||||
@content;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,20 +4,20 @@ $darkened-background: whitesmoke;
|
|||
|
||||
$fallback--bg: #121a24;
|
||||
$fallback--fg: #182230;
|
||||
$fallback--faint: rgba(185, 185, 186, .5);
|
||||
$fallback--faint: rgb(185 185 186 / 50%);
|
||||
$fallback--text: #b9b9ba;
|
||||
$fallback--link: #d8a070;
|
||||
$fallback--icon: #666;
|
||||
$fallback--lightBg: rgb(21, 30, 42);
|
||||
$fallback--lightBg: rgb(21 30 42);
|
||||
$fallback--lightText: #b9b9ba;
|
||||
$fallback--border: #222;
|
||||
$fallback--cRed: #ff0000;
|
||||
$fallback--cRed: #f00;
|
||||
$fallback--cBlue: #0095ff;
|
||||
$fallback--cGreen: #0fa00f;
|
||||
$fallback--cOrange: orange;
|
||||
|
||||
$fallback--alertError: rgba(211,16,20,.5);
|
||||
$fallback--alertWarning: rgba(111,111,20,.5);
|
||||
$fallback--alertError: rgb(211 16 20 / 50%);
|
||||
$fallback--alertWarning: rgb(111 111 20 / 50%);
|
||||
|
||||
$fallback--panelRadius: 10px;
|
||||
$fallback--checkboxRadius: 2px;
|
||||
|
@ -29,6 +29,8 @@ $fallback--avatarAltRadius: 10px;
|
|||
$fallback--attachmentRadius: 10px;
|
||||
$fallback--chatMessageRadius: 10px;
|
||||
|
||||
$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
||||
$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%),
|
||||
0 1px 0 0 rgb(255 255 255 / 20%) inset,
|
||||
0 -1px 0 0 rgb(0 0 0 / 20%) inset;
|
||||
|
||||
$status-margin: 0.75em;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import vClickOutside from 'click-outside-vue3'
|
||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
|
@ -58,6 +60,8 @@ const getInstanceConfig = async ({ store }) => {
|
|||
|
||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
||||
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
|
||||
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
|
||||
|
||||
if (vapidPublicKey) {
|
||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||
|
@ -397,6 +401,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
|
||||
app.use(vClickOutside)
|
||||
app.use(VBodyScrollLock)
|
||||
app.use(VueVirtualScroller)
|
||||
|
||||
app.component('FAIcon', FontAwesomeIcon)
|
||||
app.component('FALayers', FontAwesomeLayers)
|
||||
|
|
|
@ -9,6 +9,3 @@
|
|||
</template>
|
||||
|
||||
<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 Popover from '../popover/popover.vue'
|
||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisV
|
||||
|
@ -16,14 +17,30 @@ const AccountActions = {
|
|||
'user', 'relationship'
|
||||
],
|
||||
data () {
|
||||
return { }
|
||||
return {
|
||||
showingConfirmBlock: false,
|
||||
showingConfirmRemoveFollower: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ProgressButton,
|
||||
Popover,
|
||||
UserListMenu
|
||||
UserListMenu,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
showConfirmBlock () {
|
||||
this.showingConfirmBlock = true
|
||||
},
|
||||
hideConfirmBlock () {
|
||||
this.showingConfirmBlock = false
|
||||
},
|
||||
showConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = true
|
||||
},
|
||||
hideConfirmRemoveUserFromFollowers () {
|
||||
this.showingConfirmRemoveFollower = false
|
||||
},
|
||||
showRepeats () {
|
||||
this.$store.dispatch('showReblogs', this.user.id)
|
||||
},
|
||||
|
@ -31,13 +48,29 @@ const AccountActions = {
|
|||
this.$store.dispatch('hideReblogs', this.user.id)
|
||||
},
|
||||
blockUser () {
|
||||
if (!this.shouldConfirmBlock) {
|
||||
this.doBlockUser()
|
||||
} else {
|
||||
this.showConfirmBlock()
|
||||
}
|
||||
},
|
||||
doBlockUser () {
|
||||
this.$store.dispatch('blockUser', this.user.id)
|
||||
this.hideConfirmBlock()
|
||||
},
|
||||
unblockUser () {
|
||||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
},
|
||||
removeUserFromFollowers () {
|
||||
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
||||
this.doRemoveUserFromFollowers()
|
||||
} else {
|
||||
this.showConfirmRemoveUserFromFollowers()
|
||||
}
|
||||
},
|
||||
doRemoveUserFromFollowers () {
|
||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||
this.hideConfirmRemoveUserFromFollowers()
|
||||
},
|
||||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
|
@ -50,6 +83,12 @@ const AccountActions = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmBlock () {
|
||||
return this.$store.getters.mergedConfig.modalOnBlock
|
||||
},
|
||||
shouldConfirmRemoveUserFromFollowers () {
|
||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
||||
},
|
||||
...mapState({
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||
})
|
||||
|
|
|
@ -74,13 +74,56 @@
|
|||
</button>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script src="./account_actions.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.AccountActions {
|
||||
.ellipsis-button {
|
||||
width: 2.5em;
|
||||
|
|
|
@ -27,6 +27,9 @@ const Announcement = {
|
|||
...mapState({
|
||||
currentUser: state => state.users.currentUser
|
||||
}),
|
||||
canEditAnnouncement () {
|
||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
||||
},
|
||||
content () {
|
||||
return this.announcement.content
|
||||
},
|
||||
|
|
|
@ -45,14 +45,14 @@
|
|||
{{ $t('announcements.mark_as_read_action') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
v-if="canEditAnnouncement"
|
||||
class="btn button-default"
|
||||
@click="enterEditMode"
|
||||
>
|
||||
{{ $t('announcements.edit_action') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
v-if="canEditAnnouncement"
|
||||
class="btn button-default"
|
||||
@click="deleteAnnouncement"
|
||||
>
|
||||
|
@ -102,19 +102,19 @@
|
|||
@import "../../variables";
|
||||
|
||||
.announcement {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
border-bottom: 1px solid var(--border, $fallback--border);
|
||||
border-radius: 0;
|
||||
padding: var(--status-margin, $status-margin);
|
||||
|
||||
.heading, .body {
|
||||
.heading,
|
||||
.body {
|
||||
margin-bottom: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.times {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -28,6 +28,9 @@ const AnnouncementsPage = {
|
|||
}),
|
||||
announcements () {
|
||||
return this.$store.state.announcements.announcements
|
||||
},
|
||||
canPostAnnouncement () {
|
||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</div>
|
||||
<div class="panel-body">
|
||||
<section
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
v-if="canPostAnnouncement"
|
||||
>
|
||||
<div class="post-form">
|
||||
<div class="heading">
|
||||
|
@ -67,7 +67,8 @@
|
|||
.post-form {
|
||||
padding: var(--status-margin, $status-margin);
|
||||
|
||||
.heading, .body {
|
||||
.heading,
|
||||
.body {
|
||||
margin-bottom: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,9 +34,10 @@ export default {
|
|||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.btn {
|
||||
margin: .5em;
|
||||
padding: .5em 2em;
|
||||
margin: 0.5em;
|
||||
padding: 0.5em 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -36,6 +36,7 @@ library.add(
|
|||
const Attachment = {
|
||||
props: [
|
||||
'attachment',
|
||||
'compact',
|
||||
'description',
|
||||
'hideDescription',
|
||||
'nsfw',
|
||||
|
@ -71,7 +72,8 @@ const Attachment = {
|
|||
{
|
||||
'-loading': this.loading,
|
||||
'-nsfw-placeholder': this.hidden,
|
||||
'-editable': this.edit !== undefined
|
||||
'-editable': this.edit !== undefined,
|
||||
'-compact': this.compact
|
||||
},
|
||||
'-type-' + this.type,
|
||||
this.size && '-size-' + this.size,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.Attachment {
|
||||
display: inline-flex;
|
||||
|
@ -102,14 +102,13 @@
|
|||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
font-size: 64px;
|
||||
top: calc(50% - 32px);
|
||||
left: calc(50% - 32px);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
|
||||
color: rgb(255 255 255 / 75%);
|
||||
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
|
||||
|
||||
&::before {
|
||||
margin: 0;
|
||||
|
@ -135,18 +134,32 @@
|
|||
margin-left: 0.5em;
|
||||
font-size: 1.25em;
|
||||
// TODO: theming? hard to theme with unknown background image color
|
||||
background: rgba(230, 230, 230, 0.7);
|
||||
background: rgb(230 230 230 / 70%);
|
||||
|
||||
.svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&:hover .svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
color: rgb(0 0 0 / 90%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.-contain-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&.-cover-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.oembed-container {
|
||||
line-height: 1.2em;
|
||||
flex: 1 0 100%;
|
||||
|
@ -160,8 +173,9 @@
|
|||
|
||||
.image {
|
||||
flex: 1;
|
||||
|
||||
img {
|
||||
border: 0px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
@ -172,9 +186,10 @@
|
|||
flex: 2;
|
||||
margin: 8px;
|
||||
word-break: break-all;
|
||||
|
||||
h1 {
|
||||
font-size: 1rem;
|
||||
margin: 0px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,17 +267,9 @@
|
|||
cursor: progress;
|
||||
}
|
||||
|
||||
&.-contain-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&.-cover-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: cover;
|
||||
&.-compact {
|
||||
.placeholder-container {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,10 +162,11 @@
|
|||
target="_blank"
|
||||
>
|
||||
<FAIcon
|
||||
size="5x"
|
||||
:size="compact ? '2x' : '5x'"
|
||||
:icon="placeholderIconClass"
|
||||
:title="localDescription"
|
||||
/>
|
||||
<p>
|
||||
<p v-if="!compact">
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
</a>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<script src="./autosuggest.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.autosuggest {
|
||||
position: relative;
|
||||
|
@ -50,7 +50,7 @@
|
|||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||
border-top-left-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);
|
||||
overflow-y: auto;
|
||||
z-index: 1;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<script src="./avatar_list.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
margin: 0;
|
||||
padding: 0.6em 1em;
|
||||
|
||||
--emoji-size: 14px;
|
||||
--emoji-size: 14px;
|
||||
|
||||
&-collapsed-content {
|
||||
margin-left: 0.7em;
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
.block-card-content-container {
|
||||
margin-top: 0.5em;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
width: 10em;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
width: 100%;
|
||||
overflow: visible;
|
||||
min-height: calc(100vh - var(--navbar-height));
|
||||
margin: 0 0 0 0;
|
||||
margin: 0;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
|||
display: flex;
|
||||
justify-content: 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;
|
||||
transition: 0.35s all;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
|
|
|
@ -95,6 +95,6 @@
|
|||
|
||||
<script src="./chat.js"></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import './chat.scss';
|
||||
@import "../../variables";
|
||||
@import "./chat";
|
||||
</style>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<script src="./chat_list.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.chat-list {
|
||||
min-height: 25em;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
&:hover {
|
||||
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 {
|
||||
|
@ -67,6 +67,7 @@
|
|||
canvas {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
visibility: visible;
|
||||
}
|
||||
|
@ -79,13 +80,11 @@
|
|||
|
||||
.chat-preview-body {
|
||||
--emoji-size: 1.4em;
|
||||
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.time-wrapper {
|
||||
line-height: var(--post-line-height);
|
||||
}
|
||||
|
||||
.chat-preview-body {
|
||||
padding-right: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,6 @@
|
|||
<script src="./chat_list_item.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import './chat_list_item.scss';
|
||||
@import "../../variables";
|
||||
@import "./chat_list_item";
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.chat-message-wrapper {
|
||||
|
||||
&.hovered-message-chain {
|
||||
.animated.Avatar {
|
||||
canvas {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
visibility: visible;
|
||||
}
|
||||
|
@ -28,7 +28,8 @@
|
|||
.menu-icon {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, .extra-button-popover.open & {
|
||||
&:hover,
|
||||
.extra-button-popover.open & {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
@ -54,27 +55,11 @@
|
|||
width: 32px;
|
||||
}
|
||||
|
||||
.link-preview, .attachments {
|
||||
.link-preview,
|
||||
.attachments {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.chat-message-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
max-width: 80%;
|
||||
min-width: 10em;
|
||||
width: 100%;
|
||||
|
||||
&.with-media {
|
||||
width: 100%;
|
||||
|
||||
.status {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
border-radius: $fallback--chatMessageRadius;
|
||||
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
||||
|
@ -86,7 +71,7 @@
|
|||
position: relative;
|
||||
float: right;
|
||||
font-size: 0.8em;
|
||||
margin: -1em 0 -0.5em 0;
|
||||
margin: -1em 0 -0.5em;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
@ -103,18 +88,54 @@
|
|||
}
|
||||
|
||||
.pending {
|
||||
.status-content.media-body, .created-at {
|
||||
.status-content.media-body,
|
||||
.created-at {
|
||||
color: var(--faint);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
.status-content.media-body, .created-at {
|
||||
.status-content.media-body,
|
||||
.created-at {
|
||||
color: $fallback--cRed;
|
||||
color: var(--badgeNotification, $fallback--cRed);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
max-width: 80%;
|
||||
min-width: 10em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.outgoing {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-content: end;
|
||||
justify-content: flex-end;
|
||||
|
||||
a {
|
||||
color: var(--chatMessageOutgoingLink, $fallback--link);
|
||||
}
|
||||
|
||||
.status {
|
||||
color: var(--chatMessageOutgoingText, $fallback--text);
|
||||
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
|
||||
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
|
||||
}
|
||||
|
||||
.chat-message-inner {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.chat-message-menu {
|
||||
right: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.incoming {
|
||||
a {
|
||||
color: var(--chatMessageIncomingLink, $fallback--link);
|
||||
|
@ -137,36 +158,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.outgoing {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: end;
|
||||
justify-content: flex-end;
|
||||
|
||||
a {
|
||||
color: var(--chatMessageOutgoingLink, $fallback--link);
|
||||
}
|
||||
.chat-message-inner.with-media {
|
||||
width: 100%;
|
||||
|
||||
.status {
|
||||
color: var(--chatMessageOutgoingText, $fallback--text);
|
||||
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
|
||||
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
|
||||
}
|
||||
|
||||
.chat-message-inner {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.chat-message-menu {
|
||||
right: 0.4rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chat-message-date-separator {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<div
|
||||
class="media status"
|
||||
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
|
||||
style="position: relative"
|
||||
style="position: relative;"
|
||||
@mouseenter="hovered = true"
|
||||
@mouseleave="hovered = false"
|
||||
>
|
||||
|
@ -98,6 +98,6 @@
|
|||
|
||||
<script src="./chat_message.js"></script>
|
||||
<style lang="scss">
|
||||
@import './chat_message.scss';
|
||||
@import "./chat_message";
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.chat-new {
|
||||
.input-wrap {
|
||||
display: flex;
|
||||
margin: 0.7em 0.5em 0.7em 0.5em;
|
||||
margin: 0.7em 0.5em;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
|
|
|
@ -46,6 +46,6 @@
|
|||
|
||||
<script src="./chat_new.js"></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import './chat_new.scss';
|
||||
@import "../../variables";
|
||||
@import "./chat_new";
|
||||
</style>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<script src="./chat_title.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.chat-title {
|
||||
display: flex;
|
||||
|
|
|
@ -32,7 +32,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
|
@ -54,11 +54,11 @@ export default {
|
|||
position: absolute;
|
||||
inset: 0;
|
||||
display: block;
|
||||
content: '✓';
|
||||
content: "✓";
|
||||
transition: color 200ms;
|
||||
border-radius: $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);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--input, $fallback--fg);
|
||||
|
@ -74,15 +74,16 @@ export default {
|
|||
&.disabled {
|
||||
.checkbox-indicator::before,
|
||||
.label {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
|
||||
&:checked + .checkbox-indicator::before {
|
||||
|
@ -91,15 +92,14 @@ export default {
|
|||
}
|
||||
|
||||
&:indeterminate + .checkbox-indicator::before {
|
||||
content: '–';
|
||||
content: "–";
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
& > span {
|
||||
margin-left: .5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.color-input {
|
||||
display: inline-flex;
|
||||
|
@ -8,7 +8,7 @@
|
|||
flex: 0 0 0;
|
||||
max-width: 9em;
|
||||
align-items: stretch;
|
||||
padding: .2em 8px;
|
||||
padding: 0.2em 8px;
|
||||
|
||||
input {
|
||||
background: none;
|
||||
|
@ -31,6 +31,7 @@
|
|||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.computedIndicator,
|
||||
.transparentIndicator {
|
||||
flex: 0 0 2em;
|
||||
|
@ -38,22 +39,27 @@
|
|||
align-self: stretch;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.transparentIndicator {
|
||||
// forgot to install counter-strike source, ooops
|
||||
background-color: #FF00FF;
|
||||
background-color: #f0f;
|
||||
position: relative;
|
||||
&::before, &::after {
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: block;
|
||||
content: '';
|
||||
background-color: #000000;
|
||||
content: "";
|
||||
background-color: #000;
|
||||
position: absolute;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
@ -64,5 +70,4 @@
|
|||
.label {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
37
src/components/confirm_modal/confirm_modal.js
Normal file
37
src/components/confirm_modal/confirm_modal.js
Normal file
|
@ -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
|
29
src/components/confirm_modal/confirm_modal.vue
Normal file
29
src/components/confirm_modal/confirm_modal.vue
Normal file
|
@ -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 {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-top: -4px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
|
|
|
@ -210,17 +210,16 @@
|
|||
<script src="./conversation.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.Conversation {
|
||||
z-index: 1;
|
||||
|
||||
.conversation-dive-to-top-level-box {
|
||||
padding: var(--status-margin, $status-margin);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
border-bottom: 1px solid var(--border, $fallback--border);
|
||||
border-radius: 0;
|
||||
|
||||
/* Make the button stretch along the whole row */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
@ -235,52 +234,48 @@
|
|||
.thread-ancestor.-faded .StatusContent {
|
||||
--link: var(--faintLink);
|
||||
--text: var(--faint);
|
||||
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.thread-ancestor-dive-box {
|
||||
padding-left: var(--status-margin, $status-margin);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
border-bottom: 1px solid var(--border, $fallback--border);
|
||||
border-radius: 0;
|
||||
|
||||
/* Make the button stretch along the whole row */
|
||||
&, &-inner {
|
||||
&,
|
||||
&-inner {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.thread-ancestor-dive-box-inner {
|
||||
padding: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.conversation-status {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
border-bottom: 1px solid var(--border, $fallback--border);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.thread-ancestor-has-other-replies .conversation-status,
|
||||
&:last-child .conversation-status,
|
||||
.thread-ancestor:last-child .conversation-status,
|
||||
.thread-ancestor:last-child .thread-ancestor-dive-box,
|
||||
&:last-child .conversation-status,
|
||||
&.-expanded .thread-tree .conversation-status {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.thread-ancestors + .thread-tree > .conversation-status {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
border-top-color: var(--border, $fallback--border);
|
||||
border-top: 1px solid var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
/* expanded conversation in timeline */
|
||||
&.status-fadein.-expanded .thread-body {
|
||||
border-left-width: 4px;
|
||||
border-left-style: solid;
|
||||
border-left-color: $fallback--cRed;
|
||||
border-left: 4px solid $fallback--cRed;
|
||||
border-left-color: var(--cRed, $fallback--cRed);
|
||||
border-radius: 0 0 $fallback--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 ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSignInAlt,
|
||||
|
@ -30,7 +31,8 @@ library.add(
|
|||
|
||||
export default {
|
||||
components: {
|
||||
SearchBar
|
||||
SearchBar,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
|
@ -40,7 +42,8 @@ export default {
|
|||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||
window.CSS.supports('-o-mask-size', 'contain')
|
||||
)
|
||||
),
|
||||
showingConfirmLogout: false
|
||||
}),
|
||||
computed: {
|
||||
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
||||
|
@ -73,15 +76,32 @@ export default {
|
|||
hideSitename () { return this.$store.state.instance.hideSitename },
|
||||
logoLeft () { return this.$store.state.instance.logoLeft },
|
||||
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: {
|
||||
scrollToTop () {
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
showConfirmLogout () {
|
||||
this.showingConfirmLogout = true
|
||||
},
|
||||
hideConfirmLogout () {
|
||||
this.showingConfirmLogout = false
|
||||
},
|
||||
logout () {
|
||||
if (!this.shouldConfirmLogout) {
|
||||
this.doLogout()
|
||||
} else {
|
||||
this.showConfirmLogout()
|
||||
}
|
||||
},
|
||||
doLogout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
this.hideConfirmLogout()
|
||||
},
|
||||
onSearchBarToggled (hidden) {
|
||||
this.searchBarHidden = hidden
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.DesktopNav {
|
||||
width: 100%;
|
||||
|
@ -27,20 +27,13 @@
|
|||
--miniColumn: 25rem;
|
||||
--maxiColumn: 45rem;
|
||||
--columnGap: 1em;
|
||||
max-width: calc(
|
||||
var(--sidebarColumnWidth, var(--miniColumn)) +
|
||||
var(--contentColumnWidth, var(--maxiColumn)) +
|
||||
var(--columnGap)
|
||||
);
|
||||
}
|
||||
|
||||
&.-column-stretch.-wide .inner-nav {
|
||||
max-width: calc(
|
||||
var(--sidebarColumnWidth, var(--miniColumn)) +
|
||||
var(--contentColumnWidth, var(--maxiColumn)) +
|
||||
var(--notifsColumnWidth, var(--miniColumn)) +
|
||||
var(--columnGap)
|
||||
);
|
||||
max-width:
|
||||
calc(
|
||||
var(--sidebarColumnWidth, var(--miniColumn)) +
|
||||
var(--contentColumnWidth, var(--maxiColumn)) +
|
||||
var(--columnGap)
|
||||
);
|
||||
}
|
||||
|
||||
&.-logoLeft .inner-nav {
|
||||
|
@ -48,8 +41,19 @@
|
|||
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 {
|
||||
&, svg {
|
||||
&,
|
||||
svg {
|
||||
color: $fallback--text;
|
||||
color: var(--btnTopBarText, $fallback--text);
|
||||
}
|
||||
|
@ -70,7 +74,7 @@
|
|||
color: $fallback--text;
|
||||
color: var(--btnToggledTopBarText, $fallback--text);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btnToggledTopBar, $fallback--fg)
|
||||
background-color: var(--btnToggledTopBar, $fallback--fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +86,7 @@
|
|||
transition-duration: 100ms;
|
||||
|
||||
@media all and (min-width: 800px) {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
/>
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
@click="openSettingsModal"
|
||||
@click.stop="openSettingsModal"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
|
@ -65,7 +65,7 @@
|
|||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon"
|
||||
@click.prevent="logout"
|
||||
@click.stop.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
|
@ -76,6 +76,18 @@
|
|||
</button>
|
||||
</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>
|
||||
</template>
|
||||
<script src="./desktop_nav.js"></script>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<script src="./dialog_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
// TODO: unify with other modals.
|
||||
.dark-overlay {
|
||||
|
@ -38,8 +38,8 @@
|
|||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: rgba(27,31,35,.5);
|
||||
z-index: 99;
|
||||
background: rgb(27 31 35 / 50%);
|
||||
z-index: 2000;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
margin: 15vh auto;
|
||||
position: fixed;
|
||||
transform: translateX(-50%);
|
||||
z-index: 999;
|
||||
z-index: 2001;
|
||||
cursor: default;
|
||||
display: block;
|
||||
background-color: $fallback--bg;
|
||||
|
@ -65,7 +65,7 @@
|
|||
|
||||
.dialog-modal-content {
|
||||
margin: 0;
|
||||
padding: 1rem 1rem;
|
||||
padding: 1rem;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
white-space: normal;
|
||||
|
@ -73,7 +73,7 @@
|
|||
|
||||
.dialog-modal-footer {
|
||||
margin: 0;
|
||||
padding: .5em .5em;
|
||||
padding: 0.5em;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
border-top: 1px solid $fallback--border;
|
||||
|
@ -83,7 +83,7 @@
|
|||
|
||||
button {
|
||||
width: auto;
|
||||
margin-left: .5rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
.modal-view.edit-form-modal-view {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.edit-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin-top: 25%;
|
||||
|
|
|
@ -91,22 +91,18 @@
|
|||
<script src="./emoji_input.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.emoji-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
&.with-picker input {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.emoji-picker-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: .2em .25em;
|
||||
margin: 0.2em 0.25em;
|
||||
font-size: 1.3em;
|
||||
cursor: pointer;
|
||||
line-height: 24px;
|
||||
|
@ -123,14 +119,19 @@
|
|||
margin-top: 2px;
|
||||
|
||||
&.hide {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
input,
|
||||
textarea {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
&.with-picker input {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.hidden-overlay {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
@ -140,8 +141,10 @@
|
|||
right: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
|
||||
/* DEBUG STUFF */
|
||||
color: red;
|
||||
|
||||
/* set opacity to non-zero to see the overlay */
|
||||
|
||||
.caret {
|
||||
|
@ -151,6 +154,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
&-panel {
|
||||
position: absolute;
|
||||
|
@ -160,7 +164,7 @@
|
|||
display: flex;
|
||||
cursor: pointer;
|
||||
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;
|
||||
|
||||
.image {
|
||||
|
@ -169,7 +173,6 @@
|
|||
line-height: 32px;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
|
||||
margin-right: 4px;
|
||||
|
||||
img {
|
||||
|
@ -199,6 +202,7 @@
|
|||
background-color: $fallback--fg;
|
||||
background-color: var(--selectedMenuPopover, $fallback--fg);
|
||||
color: var(--selectedMenuPopoverText, $fallback--text);
|
||||
|
||||
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
||||
|
|
|
@ -3,7 +3,6 @@ import Checkbox from '../checkbox/checkbox.vue'
|
|||
import Popover from 'src/components/popover/popover.vue'
|
||||
import StillImage from '../still-image/still-image.vue'
|
||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||
import lozad from 'lozad'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faBoxOpen,
|
||||
|
@ -19,7 +18,7 @@ import {
|
|||
faCode,
|
||||
faFlag
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { debounce, trim } from 'lodash'
|
||||
import { debounce, trim, chunk } from 'lodash'
|
||||
|
||||
library.add(
|
||||
faBoxOpen,
|
||||
|
@ -82,6 +81,17 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
|
|||
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 = {
|
||||
props: {
|
||||
enableStickerPicker: {
|
||||
|
@ -102,7 +112,8 @@ const EmojiPicker = {
|
|||
contentLoaded: false,
|
||||
groupRefs: {},
|
||||
emojiRefs: {},
|
||||
filteredEmojiGroups: []
|
||||
filteredEmojiGroups: [],
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -125,9 +136,6 @@ const EmojiPicker = {
|
|||
setGroupRef (name) {
|
||||
return el => { this.groupRefs[name] = el }
|
||||
},
|
||||
setEmojiRef (name) {
|
||||
return el => { this.emojiRefs[name] = el }
|
||||
},
|
||||
onPopoverShown () {
|
||||
this.$emit('show')
|
||||
},
|
||||
|
@ -147,18 +155,21 @@ const EmojiPicker = {
|
|||
}
|
||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
||||
},
|
||||
onScroll (e) {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
this.updateScrolledClass(target)
|
||||
this.scrolledGroup(target)
|
||||
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
|
||||
const target = this.$refs['emoji-groups'].$el
|
||||
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
|
||||
},
|
||||
scrolledGroup (target) {
|
||||
scrolledGroup (target, start, end) {
|
||||
const top = target.scrollTop + 5
|
||||
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]
|
||||
if (ref && ref.offsetTop <= top) {
|
||||
this.activeGroup = group.id
|
||||
if (!ref) { return }
|
||||
const elem = ref.$el.parentElement
|
||||
if (!elem) { return }
|
||||
if (elem && getOffset(elem) <= top) {
|
||||
this.activeGroup = headerId
|
||||
}
|
||||
})
|
||||
this.scrollHeader()
|
||||
|
@ -181,14 +192,10 @@ const EmojiPicker = {
|
|||
setScroll(right + margin - headerCont.clientWidth)
|
||||
}
|
||||
},
|
||||
highlight (key) {
|
||||
const ref = this.groupRefs['group-' + key]
|
||||
const top = ref.offsetTop
|
||||
highlight (groupId) {
|
||||
this.setShowStickers(false)
|
||||
this.activeGroup = key
|
||||
this.$nextTick(() => {
|
||||
this.$refs['emoji-groups'].scrollTop = top + 1
|
||||
})
|
||||
const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
|
||||
this.$refs['emoji-groups'].scrollToItem(indexInList)
|
||||
},
|
||||
updateScrolledClass (target) {
|
||||
if (target.scrollTop <= 5) {
|
||||
|
@ -208,43 +215,13 @@ const EmojiPicker = {
|
|||
filterByKeyword (list, keyword) {
|
||||
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 () {
|
||||
const oldContentLoaded = this.contentLoaded
|
||||
this.recalculateItemPerRow()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.search.focus()
|
||||
})
|
||||
this.contentLoaded = true
|
||||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
if (!oldContentLoaded) {
|
||||
this.$nextTick(() => {
|
||||
|
@ -261,6 +238,14 @@ const EmojiPicker = {
|
|||
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
||||
}))
|
||||
.filter(group => group.emojis.length > 0)
|
||||
},
|
||||
recalculateItemPerRow () {
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs['emoji-groups']) {
|
||||
return
|
||||
}
|
||||
this.width = this.$refs['emoji-groups'].$el.clientWidth
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -269,14 +254,22 @@ const EmojiPicker = {
|
|||
this.debouncedHandleKeywordChange()
|
||||
},
|
||||
allCustomGroups () {
|
||||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
this.destroyLazyLoad()
|
||||
},
|
||||
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 () {
|
||||
return this.showingStickers ? '' : this.activeGroup
|
||||
},
|
||||
|
@ -287,7 +280,11 @@ const EmojiPicker = {
|
|||
return 0
|
||||
},
|
||||
allCustomGroups () {
|
||||
return this.$store.getters.groupedCustomEmojis
|
||||
const emojis = this.$store.getters.groupedCustomEmojis
|
||||
if (emojis.unpacked) {
|
||||
emojis.unpacked.text = this.$t('emoji.unpacked')
|
||||
}
|
||||
return emojis
|
||||
},
|
||||
defaultGroup () {
|
||||
return Object.keys(this.allCustomGroups)[0]
|
||||
|
@ -310,10 +307,20 @@ const EmojiPicker = {
|
|||
},
|
||||
debouncedHandleKeywordChange () {
|
||||
return debounce(() => {
|
||||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
}, 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 () {
|
||||
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
$emoji-picker-header-height: 36px;
|
||||
$emoji-picker-header-picture-width: 32px;
|
||||
|
@ -7,14 +7,14 @@ $emoji-picker-emoji-size: 32px;
|
|||
|
||||
.emoji-picker {
|
||||
width: 25em;
|
||||
max-width: 100vw;
|
||||
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--popover, $fallback--bg);
|
||||
color: $fallback--link;
|
||||
color: var(--popoverText, $fallback--link);
|
||||
--lightText: var(--popoverLightText, $fallback--faint);
|
||||
|
||||
--faint: var(--popoverFaintText, $fallback--faint);
|
||||
--faintLink: var(--popoverFaintLink, $fallback--faint);
|
||||
--lightText: var(--popoverLightText, $fallback--lightText);
|
||||
|
@ -28,6 +28,7 @@ $emoji-picker-emoji-size: 32px;
|
|||
max-width: $emoji-picker-header-picture-width;
|
||||
height: $emoji-picker-header-picture-height;
|
||||
max-height: $emoji-picker-header-picture-height;
|
||||
|
||||
.still-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
@ -62,24 +63,18 @@ $emoji-picker-emoji-size: 32px;
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.emoji-tabs {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
flex-flow: row nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.emoji-groups {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.additional-tabs {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
border-left: 1px solid;
|
||||
border-left-color: $fallback--icon;
|
||||
border-left-color: var(--icon, $fallback--icon);
|
||||
|
@ -121,7 +116,7 @@ $emoji-picker-emoji-size: 32px;
|
|||
}
|
||||
|
||||
.sticker-picker {
|
||||
flex: 1 1 auto
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.stickers,
|
||||
|
@ -151,22 +146,27 @@ $emoji-picker-emoji-size: 32px;
|
|||
}
|
||||
|
||||
&-groups {
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
flex: 1 1 1px;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
user-select: none;
|
||||
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
||||
linear-gradient(to top, white, white);
|
||||
mask:
|
||||
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
||||
linear-gradient(to top, white, white);
|
||||
transition: mask-size 150ms;
|
||||
mask-size: 100% 20px, 100% 20px, auto;
|
||||
// Autoprefixed seem to ignore this one, and also syntax is different
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
|
||||
&.scrolled {
|
||||
&-top {
|
||||
mask-size: 100% 20px, 100% 0, auto;
|
||||
}
|
||||
|
||||
&-bottom {
|
||||
mask-size: 100% 0, 100% 20px, auto;
|
||||
}
|
||||
|
@ -200,7 +200,6 @@ $emoji-picker-emoji-size: 32px;
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.emoji-picker-emoji.-custom {
|
||||
|
@ -208,12 +207,11 @@ $emoji-picker-emoji-size: 32px;
|
|||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.emoji-picker-emoji.-unicode {
|
||||
font-size: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,45 +74,57 @@
|
|||
@input="$event.target.composing = false"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
<DynamicScroller
|
||||
ref="emoji-groups"
|
||||
class="emoji-groups"
|
||||
:class="groupsScrolledClass"
|
||||
@scroll="onScroll"
|
||||
:min-item-size="minItemSize"
|
||||
:items="emojiItems"
|
||||
:emit-update="true"
|
||||
@update="onScroll"
|
||||
@visible="recalculateItemPerRow"
|
||||
@resize="recalculateItemPerRow"
|
||||
>
|
||||
<div
|
||||
v-for="group in filteredEmojiGroups"
|
||||
:key="group.id"
|
||||
class="emoji-group"
|
||||
>
|
||||
<h6
|
||||
<template #default="{ item: group, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:ref="setGroupRef('group-' + group.id)"
|
||||
class="emoji-group-title"
|
||||
:item="group"
|
||||
:active="active"
|
||||
:data-index="index"
|
||||
:size-dependencies="[group.emojis.length]"
|
||||
>
|
||||
{{ group.text }}
|
||||
</h6>
|
||||
<span
|
||||
v-for="emoji in group.emojis"
|
||||
:key="group.id + emoji.displayText"
|
||||
:title="maybeLocalizedEmojiName(emoji)"
|
||||
class="emoji-item"
|
||||
@click.stop.prevent="onEmoji(emoji)"
|
||||
>
|
||||
<span
|
||||
v-if="!emoji.imageUrl"
|
||||
class="emoji-picker-emoji -unicode"
|
||||
>{{ emoji.replacement }}</span>
|
||||
<still-image
|
||||
v-else
|
||||
:ref="setEmojiRef(group.id + emoji.displayText)"
|
||||
class="emoji-picker-emoji -custom"
|
||||
:data-src="emoji.imageUrl"
|
||||
:data-emoji-name="group.id + emoji.displayText"
|
||||
/>
|
||||
</span>
|
||||
<span :ref="setGroupRef('group-end-' + group.id)" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="emoji-group"
|
||||
>
|
||||
<h6
|
||||
v-if="group.isFirstRow"
|
||||
class="emoji-group-title"
|
||||
>
|
||||
{{ group.text }}
|
||||
</h6>
|
||||
<span
|
||||
v-for="emoji in group.emojis"
|
||||
:key="group.id + emoji.displayText"
|
||||
:title="maybeLocalizedEmojiName(emoji)"
|
||||
class="emoji-item"
|
||||
@click.stop.prevent="onEmoji(emoji)"
|
||||
>
|
||||
<span
|
||||
v-if="!emoji.imageUrl"
|
||||
class="emoji-picker-emoji -unicode"
|
||||
>{{ emoji.replacement }}</span>
|
||||
<still-image
|
||||
v-else
|
||||
class="emoji-picker-emoji -custom"
|
||||
loading="lazy"
|
||||
:src="emoji.imageUrl"
|
||||
:data-emoji-name="group.id + emoji.displayText"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
<div class="keep-open">
|
||||
<Checkbox v-model="keepOpen">
|
||||
{{ $t('emoji.keep_open') }}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
<script src="./emoji_reactions.js"></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.EmojiReactions {
|
||||
display: flex;
|
||||
|
@ -55,6 +55,7 @@
|
|||
|
||||
&.not-clickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $fallback--buttonShadow;
|
||||
box-shadow: var(--buttonShadow);
|
||||
|
@ -75,10 +76,10 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisH,
|
||||
|
@ -32,10 +33,14 @@ library.add(
|
|||
|
||||
const ExtraButtons = {
|
||||
props: ['status'],
|
||||
components: { Popover },
|
||||
components: {
|
||||
Popover,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
expanded: false
|
||||
expanded: false,
|
||||
showingDeleteDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -46,11 +51,22 @@ const ExtraButtons = {
|
|||
this.expanded = false
|
||||
},
|
||||
deleteStatus () {
|
||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
if (this.shouldConfirmDelete) {
|
||||
this.showDeleteStatusConfirmDialog()
|
||||
} else {
|
||||
this.doDeleteStatus()
|
||||
}
|
||||
},
|
||||
doDeleteStatus () {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
this.hideDeleteStatusConfirmDialog()
|
||||
},
|
||||
showDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = true
|
||||
},
|
||||
hideDeleteStatusConfirmDialog () {
|
||||
this.showingDeleteDialog = false
|
||||
},
|
||||
pinStatus () {
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
|
@ -133,7 +149,10 @@ const ExtraButtons = {
|
|||
isEdited () {
|
||||
return this.status.edited_at !== null
|
||||
},
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable }
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||
shouldConfirmDelete () {
|
||||
return this.$store.getters.mergedConfig.modalOnDelete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -165,6 +165,18 @@
|
|||
/>
|
||||
</FALayers>
|
||||
</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>
|
||||
</Popover>
|
||||
</template>
|
||||
|
@ -172,15 +184,10 @@
|
|||
<script src="./extra_buttons.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import '../../_mixins.scss';
|
||||
@import "../../variables";
|
||||
@import "../../mixins";
|
||||
|
||||
.ExtraButtons {
|
||||
/* override of popover internal stuff */
|
||||
.popover-trigger-button {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.popover-trigger {
|
||||
position: static;
|
||||
padding: 10px;
|
||||
|
@ -190,10 +197,12 @@
|
|||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.popover-trigger-button {
|
||||
/* override of popover internal stuff */
|
||||
width: auto;
|
||||
|
||||
@include unfocused-style {
|
||||
.focus-marker {
|
||||
visibility: hidden;
|
||||
|
|
|
@ -58,8 +58,8 @@
|
|||
<script src="./favorite_button.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import '../../_mixins.scss';
|
||||
@import "../../variables";
|
||||
@import "../../mixins";
|
||||
|
||||
.FavoriteButton {
|
||||
display: flex;
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
<script src="./flash.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.Flash {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
@ -78,7 +79,7 @@
|
|||
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: 'hidden';
|
||||
visibility: "hidden";
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||
export default {
|
||||
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
||||
components: {
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
inProgress: false,
|
||||
showingConfirmUnfollow: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shouldConfirmUnfollow () {
|
||||
return this.$store.getters.mergedConfig.modalOnUnfollow
|
||||
},
|
||||
isPressed () {
|
||||
return this.inProgress || this.relationship.following
|
||||
},
|
||||
|
@ -35,6 +43,12 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
showConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = true
|
||||
},
|
||||
hideConfirmUnfollow () {
|
||||
this.showingConfirmUnfollow = false
|
||||
},
|
||||
onClick () {
|
||||
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
||||
},
|
||||
|
@ -45,12 +59,21 @@ export default {
|
|||
})
|
||||
},
|
||||
unfollow () {
|
||||
if (this.shouldConfirmUnfollow) {
|
||||
this.showConfirmUnfollow()
|
||||
} else {
|
||||
this.doUnfollow()
|
||||
}
|
||||
},
|
||||
doUnfollow () {
|
||||
const store = this.$store
|
||||
this.inProgress = true
|
||||
requestUnfollow(this.relationship.id, store).then(() => {
|
||||
this.inProgress = false
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
|
||||
})
|
||||
|
||||
this.hideConfirmUnfollow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,27 @@
|
|||
@click="onClick"
|
||||
>
|
||||
{{ 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>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
/>
|
||||
<RemoveFollowerButton
|
||||
v-if="noFollowsYou && relationship.followed_by"
|
||||
:user="user"
|
||||
:relationship="relationship"
|
||||
class="follow-card-button"
|
||||
/>
|
||||
|
@ -39,9 +40,8 @@
|
|||
&-content-container {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
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'
|
||||
|
||||
const FollowRequestCard = {
|
||||
props: ['user'],
|
||||
components: {
|
||||
BasicUserCard
|
||||
BasicUserCard,
|
||||
ConfirmModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showingApproveConfirmDialog: false,
|
||||
showingDenyConfirmDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findFollowRequestNotificationId () {
|
||||
|
@ -13,7 +21,26 @@ const FollowRequestCard = {
|
|||
)
|
||||
return notif && notif.id
|
||||
},
|
||||
showApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = true
|
||||
},
|
||||
hideApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = false
|
||||
},
|
||||
showDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = true
|
||||
},
|
||||
hideDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = false
|
||||
},
|
||||
approveUser () {
|
||||
if (this.shouldConfirmApprove) {
|
||||
this.showApproveConfirmDialog()
|
||||
} else {
|
||||
this.doApprove()
|
||||
}
|
||||
},
|
||||
doApprove () {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
|
||||
|
@ -25,14 +52,34 @@ const FollowRequestCard = {
|
|||
notification.type = 'follow'
|
||||
}
|
||||
})
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
denyUser () {
|
||||
if (this.shouldConfirmDeny) {
|
||||
this.showDenyConfirmDialog()
|
||||
} else {
|
||||
this.doDeny()
|
||||
}
|
||||
},
|
||||
doDeny () {
|
||||
const notifId = this.findFollowRequestNotificationId()
|
||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||
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') }}
|
||||
</button>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -22,8 +44,8 @@
|
|||
<style lang="scss">
|
||||
.follow-request-card-content-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
flex-flow: row wrap;
|
||||
|
||||
button {
|
||||
margin-top: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
|
|
|
@ -50,17 +50,20 @@
|
|||
<script src="./font_control.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.font-control {
|
||||
input.custom-font {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
&.custom {
|
||||
/* TODO Should make proper joiners... */
|
||||
.font-switcher {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.custom-font {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { sumBy, set } from 'lodash'
|
|||
const Gallery = {
|
||||
props: [
|
||||
'attachments',
|
||||
'compact',
|
||||
'limitRows',
|
||||
'descriptions',
|
||||
'limit',
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
v-for="(attachment, attachmentIndex) in row.items"
|
||||
:key="attachment.id"
|
||||
class="gallery-item"
|
||||
:compact="compact"
|
||||
:nsfw="nsfw"
|
||||
:attachment="attachment"
|
||||
:size="size"
|
||||
|
@ -86,7 +87,7 @@
|
|||
<script src='./gallery.js'></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.Gallery {
|
||||
.gallery-rows {
|
||||
|
@ -100,6 +101,53 @@
|
|||
width: 100%;
|
||||
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) {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
@ -114,7 +162,7 @@
|
|||
linear-gradient(to top, white, white);
|
||||
|
||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
}
|
||||
|
@ -138,54 +186,5 @@
|
|||
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>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<script src="./global_notice_list.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.global-notice-list {
|
||||
position: fixed;
|
||||
|
@ -73,6 +73,7 @@
|
|||
.global-success {
|
||||
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
||||
color: var(--alertPopupSuccessText, $fallback--text);
|
||||
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupSuccessText, $fallback--text);
|
||||
}
|
||||
|
@ -81,6 +82,7 @@
|
|||
.global-info {
|
||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
||||
color: var(--alertPopupNeutralText, $fallback--text);
|
||||
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupNeutralText, $fallback--text);
|
||||
}
|
||||
|
@ -88,6 +90,7 @@
|
|||
|
||||
.close-notice {
|
||||
padding-right: 0.2em;
|
||||
|
||||
.svg-inline--fa:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,44 @@
|
|||
<template>
|
||||
<div>
|
||||
<label for="interface-language-switcher">
|
||||
<div class="interface-language-switcher">
|
||||
<label>
|
||||
{{ promptText }}
|
||||
</label>
|
||||
{{ ' ' }}
|
||||
<Select
|
||||
id="interface-language-switcher"
|
||||
v-model="controlledLanguage"
|
||||
>
|
||||
<option
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
<ul class="setting-list">
|
||||
<li
|
||||
v-for="index of controlledLanguage.keys()"
|
||||
:key="index"
|
||||
>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</Select>
|
||||
<label>
|
||||
{{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
@ -34,7 +57,7 @@ export default {
|
|||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
type: [Array, String],
|
||||
required: true
|
||||
},
|
||||
setLanguage: {
|
||||
|
@ -48,7 +71,9 @@ export default {
|
|||
},
|
||||
|
||||
controlledLanguage: {
|
||||
get: function () { return this.language },
|
||||
get: function () {
|
||||
return Array.isArray(this.language) ? this.language : [this.language]
|
||||
},
|
||||
set: function (val) {
|
||||
this.setLanguage(val)
|
||||
}
|
||||
|
@ -58,7 +83,30 @@ export default {
|
|||
methods: {
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../variables";
|
||||
|
||||
.interface-language-switcher {
|
||||
.language-select {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<script src="./link-preview.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.link-preview-card {
|
||||
display: flex;
|
||||
|
@ -46,6 +46,7 @@
|
|||
flex-shrink: 0;
|
||||
width: 120px;
|
||||
max-width: 25%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -67,7 +68,7 @@
|
|||
}
|
||||
|
||||
.card-description {
|
||||
margin: 0.5em 0 0 0;
|
||||
margin: 0.5em 0 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
|
|
|
@ -35,7 +35,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.list {
|
||||
&-item:not(:last-child) {
|
||||
|
|
|
@ -21,12 +21,16 @@
|
|||
<script src="./lists_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.list-card {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-name,
|
||||
.button-list-edit {
|
||||
margin: 0;
|
||||
|
@ -39,13 +43,10 @@
|
|||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--link;
|
||||
color: var(--selectedMenuText, $fallback--link);
|
||||
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
|
||||
.list-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -95,10 +95,10 @@ const ListsNew = {
|
|||
return this.addedUserIds.has(user.id)
|
||||
},
|
||||
addUser (user) {
|
||||
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id })
|
||||
this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
|
||||
},
|
||||
removeUser (userId) {
|
||||
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
|
||||
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
|
||||
},
|
||||
onSearchLoading (results) {
|
||||
this.searchLoading = true
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
<script src="./lists_edit.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.ListEdit {
|
||||
--panel-body-padding: 0.5em;
|
||||
|
|
|
@ -27,12 +27,12 @@
|
|||
|
||||
<script src="./lists_user_search.js"></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.ListsUserSearch {
|
||||
.input-wrap {
|
||||
display: flex;
|
||||
margin: 0.7em 0.5em 0.7em 0.5em;
|
||||
margin: 0.7em 0.5em;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<script src="./login_form.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
|
@ -110,7 +110,7 @@
|
|||
}
|
||||
|
||||
.login-bottom {
|
||||
margin-top: 1.0em;
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
@ -121,7 +121,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.3em 0.5em 0.6em;
|
||||
line-height:24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.form-bottom {
|
||||
|
@ -142,7 +142,6 @@
|
|||
|
||||
.error {
|
||||
text-align: center;
|
||||
|
||||
animation-name: shakeError;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in-out;
|
||||
|
|
|
@ -63,6 +63,11 @@ const MediaModal = {
|
|||
},
|
||||
type () {
|
||||
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: {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
class="modal-image-container"
|
||||
:direction="swipeDirection"
|
||||
:threshold="swipeThreshold"
|
||||
:disable-click-threshold="swipeDisableClickThreshold"
|
||||
@preview-requested="handleSwipePreview"
|
||||
@swipe-finished="handleSwipeEnd"
|
||||
@swipeless-clicked="hide"
|
||||
|
@ -120,32 +121,12 @@ $modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2)
|
|||
$modal-view-button-icon-width: 3em;
|
||||
$modal-view-button-icon-margin: 0.5em;
|
||||
|
||||
.modal-view.media-modal-view {
|
||||
z-index: var(--ZI_media_modal);
|
||||
flex-direction: column;
|
||||
|
||||
.modal-view-button-arrow,
|
||||
.modal-view-button-hide {
|
||||
opacity: 0.75;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-modal-view {
|
||||
@keyframes media-fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -226,7 +207,7 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
appearance: none;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
transition: opacity 333ms cubic-bezier(.4,0,.22,1);
|
||||
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
|
||||
height: $modal-view-button-icon-height;
|
||||
width: $modal-view-button-icon-width;
|
||||
|
||||
|
@ -236,9 +217,9 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
width: $modal-view-button-icon-width;
|
||||
font-size: 1rem;
|
||||
line-height: $modal-view-button-icon-height;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
background-color: rgb(0 0 0 / 30%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,13 +235,14 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
position: absolute;
|
||||
top: 0;
|
||||
line-height: $modal-view-button-icon-height;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
background-color: rgb(0 0 0 / 30%);
|
||||
}
|
||||
|
||||
&--prev {
|
||||
left: 0;
|
||||
|
||||
.arrow-icon {
|
||||
left: $modal-view-button-icon-margin;
|
||||
}
|
||||
|
@ -268,6 +250,7 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
|
||||
&--next {
|
||||
right: 0;
|
||||
|
||||
.arrow-icon {
|
||||
right: $modal-view-button-icon-margin;
|
||||
}
|
||||
|
@ -278,10 +261,33 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
.button-icon {
|
||||
top: $modal-view-button-icon-margin;
|
||||
right: $modal-view-button-icon-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-view.media-modal-view {
|
||||
z-index: var(--ZI_media_modal);
|
||||
flex-direction: column;
|
||||
|
||||
.modal-view-button-arrow,
|
||||
.modal-view-button-hide {
|
||||
opacity: 0.75;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<script src="./media_upload.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.media-upload {
|
||||
cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.MentionLink {
|
||||
position: relative;
|
||||
|
@ -59,6 +59,7 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.-has-selection {
|
||||
color: var(--alertNeutralText, $fallback--text);
|
||||
background-color: var(--alertNeutral, $fallback--fg);
|
||||
|
@ -100,10 +101,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.full {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.serverName.-faded {
|
||||
color: var(--faintLink, $fallback--link);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
word-break: break-all;
|
||||
|
||||
.mention-link:not(:first-child)::before {
|
||||
content: ' ';
|
||||
content: " ";
|
||||
}
|
||||
|
||||
.showMoreLess {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import SideDrawer from '../side_drawer/side_drawer.vue'
|
||||
import Notifications from '../notifications/notifications.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
|
||||
|
@ -25,12 +26,14 @@ const MobileNav = {
|
|||
components: {
|
||||
SideDrawer,
|
||||
Notifications,
|
||||
NavigationPins
|
||||
NavigationPins,
|
||||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
notificationsCloseGesture: undefined,
|
||||
notificationsOpen: false,
|
||||
notificationsAtTop: true
|
||||
notificationsAtTop: true,
|
||||
showingConfirmLogout: false
|
||||
}),
|
||||
created () {
|
||||
this.notificationsCloseGesture = GestureService.swipeGesture(
|
||||
|
@ -57,7 +60,11 @@ const MobileNav = {
|
|||
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
|
||||
chatsPinned () {
|
||||
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
|
||||
}
|
||||
},
|
||||
shouldConfirmLogout () {
|
||||
return this.$store.getters.mergedConfig.modalOnLogout
|
||||
},
|
||||
...mapGetters(['unreadChatCount'])
|
||||
},
|
||||
methods: {
|
||||
toggleMobileSidebar () {
|
||||
|
@ -88,9 +95,23 @@ const MobileNav = {
|
|||
scrollMobileNotificationsToTop () {
|
||||
this.$refs.mobileNotifications.scrollTo(0, 0)
|
||||
},
|
||||
showConfirmLogout () {
|
||||
this.showingConfirmLogout = true
|
||||
},
|
||||
hideConfirmLogout () {
|
||||
this.showingConfirmLogout = false
|
||||
},
|
||||
logout () {
|
||||
if (!this.shouldConfirmLogout) {
|
||||
this.doLogout()
|
||||
} else {
|
||||
this.showConfirmLogout()
|
||||
}
|
||||
},
|
||||
doLogout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
this.hideConfirmLogout()
|
||||
},
|
||||
markNotificationsAsSeen () {
|
||||
// this.$refs.notifications.markAsSeen()
|
||||
|
|
|
@ -88,13 +88,25 @@
|
|||
ref="sideDrawer"
|
||||
:logout="logout"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./mobile_nav.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.MobileNav {
|
||||
z-index: var(--ZI_navbar);
|
||||
|
@ -127,7 +139,7 @@
|
|||
}
|
||||
|
||||
.site-name {
|
||||
padding: 0 .3em;
|
||||
padding: 0 0.3em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -156,7 +168,7 @@
|
|||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
|
||||
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
|
||||
box-shadow: var(--panelShadow);
|
||||
transition-property: transform;
|
||||
transition-duration: 0.25s;
|
||||
|
@ -182,7 +194,7 @@
|
|||
color: var(--topBarText);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--topBar, $fallback--fg);
|
||||
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
|
||||
box-shadow: 0 0 4px rgb(0 0 0 / 60%);
|
||||
box-shadow: var(--topBarShadow);
|
||||
|
||||
.spacer {
|
||||
|
@ -235,6 +247,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-modal.dark-overlay {
|
||||
&::before {
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.dialog-modal.panel {
|
||||
z-index: 3001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<script src="./mobile_post_status_button.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.MobilePostButton {
|
||||
&.button-default {
|
||||
|
@ -30,9 +30,8 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 2px 2px rgb(0 0 0 / 30%), 0 4px 6px rgb(0 0 0 / 30%);
|
||||
z-index: 10;
|
||||
|
||||
transition: 0.35s transform;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ export default {
|
|||
|
||||
&.modal-background {
|
||||
pointer-events: initial;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
&.open {
|
||||
|
@ -69,10 +69,11 @@ export default {
|
|||
|
||||
@keyframes modal-background-fadein {
|
||||
from {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-color: rgb(0 0 0 / 0%);
|
||||
}
|
||||
|
||||
to {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -166,18 +166,21 @@
|
|||
<script src="./moderation_tools.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.moderation-tools-popover {
|
||||
height: 100%;
|
||||
|
||||
.trigger {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
display: flex !important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.moderation-tools-button {
|
||||
svg,i {
|
||||
svg,
|
||||
i {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,19 +2,21 @@
|
|||
margin: 1em;
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-left:10px;
|
||||
padding-bottom:20px;
|
||||
padding-left: 10px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
width: 180px;
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
overflow: hidden;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
th+th, td+td {
|
||||
th + th,
|
||||
td + td {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,6 +227,6 @@
|
|||
<script src="./mrf_transparency_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import './mrf_transparency_panel.scss';
|
||||
@import "../../variables";
|
||||
@import "./mrf_transparency_panel";
|
||||
</style>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
.mute-card-content-container {
|
||||
margin-top: 0.5em;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
width: 10em;
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
<script src="./nav_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.NavPanel {
|
||||
.panel {
|
||||
|
@ -169,8 +169,9 @@
|
|||
}
|
||||
|
||||
.nav-panel-heading {
|
||||
// breaks without a unit
|
||||
--panel-heading-height-padding: 0em;
|
||||
// breaks without a unit
|
||||
// stylelint-disable-next-line length-zero-no-unit
|
||||
--panel-heading-height-padding: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<script src="./navigation_entry.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.NavigationEntry {
|
||||
display: flex;
|
||||
|
@ -102,6 +102,7 @@
|
|||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--link;
|
||||
color: var(--selectedMenuText, $fallback--link);
|
||||
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
|
@ -117,6 +118,7 @@
|
|||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--text;
|
||||
color: var(--selectedMenuText, $fallback--text);
|
||||
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
<script src="./navigation_pins.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.NavigationPins {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -8,6 +8,7 @@ import Report from '../report/report.vue'
|
|||
import UserLink from '../user_link/user_link.vue'
|
||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import UserPopover from '../user_popover/user_popover.vue'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
@ -20,7 +21,9 @@ import {
|
|||
faUserPlus,
|
||||
faEyeSlash,
|
||||
faUser,
|
||||
faSuitcaseRolling
|
||||
faSuitcaseRolling,
|
||||
faExpandAlt,
|
||||
faCompressAlt
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -31,15 +34,19 @@ library.add(
|
|||
faUserPlus,
|
||||
faUser,
|
||||
faEyeSlash,
|
||||
faSuitcaseRolling
|
||||
faSuitcaseRolling,
|
||||
faExpandAlt,
|
||||
faCompressAlt
|
||||
)
|
||||
|
||||
const Notification = {
|
||||
data () {
|
||||
return {
|
||||
userExpanded: false,
|
||||
statusExpanded: false,
|
||||
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
|
||||
unmuted: false
|
||||
unmuted: false,
|
||||
showingApproveConfirmDialog: false,
|
||||
showingDenyConfirmDialog: false
|
||||
}
|
||||
},
|
||||
props: ['notification'],
|
||||
|
@ -52,11 +59,12 @@ const Notification = {
|
|||
Report,
|
||||
RichContent,
|
||||
UserPopover,
|
||||
UserLink
|
||||
UserLink,
|
||||
ConfirmModal
|
||||
},
|
||||
methods: {
|
||||
toggleUserExpanded () {
|
||||
this.userExpanded = !this.userExpanded
|
||||
toggleStatusExpanded () {
|
||||
this.statusExpanded = !this.statusExpanded
|
||||
},
|
||||
generateUserProfileLink (user) {
|
||||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||
|
@ -67,7 +75,26 @@ const Notification = {
|
|||
toggleMute () {
|
||||
this.unmuted = !this.unmuted
|
||||
},
|
||||
showApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = true
|
||||
},
|
||||
hideApproveConfirmDialog () {
|
||||
this.showingApproveConfirmDialog = false
|
||||
},
|
||||
showDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = true
|
||||
},
|
||||
hideDenyConfirmDialog () {
|
||||
this.showingDenyConfirmDialog = false
|
||||
},
|
||||
approveUser () {
|
||||
if (this.shouldConfirmApprove) {
|
||||
this.showApproveConfirmDialog()
|
||||
} else {
|
||||
this.doApprove()
|
||||
}
|
||||
},
|
||||
doApprove () {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
|
||||
|
@ -77,13 +104,22 @@ const Notification = {
|
|||
notification.type = 'follow'
|
||||
}
|
||||
})
|
||||
this.hideApproveConfirmDialog()
|
||||
},
|
||||
denyUser () {
|
||||
if (this.shouldConfirmDeny) {
|
||||
this.showDenyConfirmDialog()
|
||||
} else {
|
||||
this.doDeny()
|
||||
}
|
||||
},
|
||||
doDeny () {
|
||||
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -113,6 +149,15 @@ const Notification = {
|
|||
isStatusNotification () {
|
||||
return isStatusNotification(this.notification.type)
|
||||
},
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
},
|
||||
shouldConfirmApprove () {
|
||||
return this.mergedConfig.modalOnApproveFollow
|
||||
},
|
||||
shouldConfirmDeny () {
|
||||
return this.mergedConfig.modalOnDenyFollow
|
||||
},
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser
|
||||
})
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
// TODO Copypaste from Status, should unify it somehow
|
||||
.Notification {
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
--emoji-size: 14px;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
||||
--emoji-size: 14px;
|
||||
|
||||
&:hover {
|
||||
--_still-image-img-visibility: visible;
|
||||
|
@ -54,7 +55,7 @@
|
|||
margin-left: 0.2em;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,13 +144,25 @@
|
|||
<router-link
|
||||
v-if="notification.status"
|
||||
:to="{ name: 'conversation', params: { id: notification.status.id } }"
|
||||
class="faint-link"
|
||||
class="timeago-link faint-link"
|
||||
>
|
||||
<Timeago
|
||||
:time="notification.created_at"
|
||||
:auto-update="240"
|
||||
/>
|
||||
</router-link>
|
||||
<button
|
||||
class="button-unstyled expand-icon"
|
||||
@click.prevent="toggleStatusExpanded"
|
||||
:title="$t('tool_tip.toggle_expand')"
|
||||
:aria-expanded="statusExpanded"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
fixed-width
|
||||
:icon="statusExpanded ? 'compress-alt' : 'expand-alt'"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
|
@ -166,6 +178,8 @@
|
|||
<button
|
||||
v-if="needMute"
|
||||
class="button-unstyled"
|
||||
:title="$t('tool_tip.toggle_mute')"
|
||||
:aria-expanded="!unmuted"
|
||||
@click.prevent="toggleMute"
|
||||
>
|
||||
<FAIcon
|
||||
|
@ -222,13 +236,35 @@
|
|||
/>
|
||||
<template v-else>
|
||||
<StatusContent
|
||||
class="faint"
|
||||
:compact="true"
|
||||
:class="{ faint: !statusExpanded }"
|
||||
:compact="!statusExpanded"
|
||||
:status="notification.action"
|
||||
/>
|
||||
</template>
|
||||
</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>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -101,6 +101,9 @@ const Notifications = {
|
|||
if (!this.scrollerRef) {
|
||||
this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
|
||||
}
|
||||
if (!this.scrollerRef) {
|
||||
this.scrollerRef = this.$refs.root.closest('.column.main')
|
||||
}
|
||||
this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
|
||||
},
|
||||
unmounted () {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.Notifications {
|
||||
&:not(.minimal) {
|
||||
|
@ -25,12 +25,13 @@
|
|||
|
||||
&.unseen {
|
||||
.notification-overlay {
|
||||
background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px)
|
||||
background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
.notification {
|
||||
box-sizing: border-box;
|
||||
|
||||
|
@ -38,6 +39,7 @@
|
|||
canvas {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
visibility: visible;
|
||||
}
|
||||
|
@ -79,7 +81,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.follow-text, .move-text {
|
||||
.follow-text,
|
||||
.move-text {
|
||||
padding: 0.5em 0;
|
||||
overflow-wrap: break-word;
|
||||
display: flex;
|
||||
|
@ -112,6 +115,16 @@
|
|||
min-width: 3em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.timeago-link {
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
.svg-inline--fa {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-reaction-emoji {
|
||||
|
|
|
@ -23,7 +23,7 @@ export default {}
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'src/_variables.scss';
|
||||
@import "src/variables";
|
||||
|
||||
.panel-loading {
|
||||
display: flex;
|
||||
|
@ -33,6 +33,7 @@ export default {}
|
|||
font-size: 2em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
|
||||
.loading-text svg {
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
<script src="./password_reset.js"></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.password-reset-form {
|
||||
display: flex;
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
<script src="./poll.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
@import "../../variables";
|
||||
|
||||
.poll {
|
||||
.votes {
|
||||
|
@ -98,9 +98,11 @@
|
|||
flex-direction: column;
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
||||
.poll-option {
|
||||
margin: 0.75em 0.5em;
|
||||
}
|
||||
|
||||
.option-result {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
@ -109,6 +111,7 @@
|
|||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
|
||||
.option-result-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -116,10 +119,12 @@
|
|||
z-index: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.result-percentage {
|
||||
width: 3.5em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.result-fill {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
|
@ -133,20 +138,25 @@
|
|||
left: 0;
|
||||
transition: width 0.5s;
|
||||
}
|
||||
|
||||
.option-vote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 3.5em;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.loading * {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.poll-vote-button {
|
||||
padding: 0 0.5em;
|
||||
margin-right: 0.5em;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue