mirror of
https://akkoma.dev/lamp/akkoma-fe.git
synced 2026-06-05 06:40:04 -04:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7567ce6d0 | |||
| 8e87e3d88b | |||
| 6aa276374f | |||
| 23232e1c8f | |||
| b225c3578f | |||
| 30efbaab34 | |||
| 8ca0586c0f | |||
| 938887ef91 | |||
| f31bc5310e | |||
| 925bf5b5a4 | |||
| e768ec1fca | |||
| d09f43ba7a | |||
| 748c4d8c71 | |||
| c7ddfefe34 | |||
| c0205d582a | |||
| 4ac882a3b0 | |||
| bdbc4b27b6 | |||
| 42598fc675 | |||
| 5d49edc823 | |||
| 0bc0a8d5f5 | |||
| 726d5279c1 | |||
| a0f780c455 |
@@ -3,85 +3,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [2.4.0] - 2021-08-08
|
|
||||||
### Added
|
|
||||||
- Added a quick settings to timeline header for easier access
|
|
||||||
- Added option to mark posts as sensitive by default
|
|
||||||
- Added quick filters for notifications
|
|
||||||
- Implemented user option to change sidebar position to the right side
|
|
||||||
- Implemented user option to hide floating shout panel
|
|
||||||
- Implemented "edit profile" button if viewing own profile which opens profile settings
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed follow request count showing in the wrong location in mobile view
|
|
||||||
|
|
||||||
## [2.3.0] - 2021-03-01
|
|
||||||
### Fixed
|
|
||||||
- Button to remove uploaded media in post status form is now properly placed and sized.
|
|
||||||
- Fixed shoutbox not working in mobile layout
|
|
||||||
- Fixed missing highlighted border in expanded conversations again
|
|
||||||
- Fixed some UI jumpiness when opening images particularly in chat view
|
|
||||||
- Fixed chat unread badge looking weird
|
|
||||||
- Fixed punycode names not working properly
|
|
||||||
- Fixed notifications crashing on an invalid notification
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Display 'people voted' instead of 'votes' for multi-choice polls
|
|
||||||
- Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel
|
|
||||||
- Renamed "Timeline" to "Home Timeline" to be more clear
|
|
||||||
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
|
|
||||||
- When opening emoji picker or react picker, it automatically focuses the search field
|
|
||||||
- Language picker now uses native language names
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Added reason field for registration when approval is required
|
|
||||||
- Group staff members by role in the About page
|
|
||||||
|
|
||||||
|
|
||||||
## [2.2.3] - 2021-01-18
|
|
||||||
### Added
|
|
||||||
- Added Report button to status ellipsis menu for easier reporting
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Follows/Followers tabs on user profiles now display the content properly.
|
|
||||||
- Handle punycode in screen names
|
|
||||||
- Fixed local dev mode having non-functional websockets in some cases
|
|
||||||
- Show notices for websocket events (errors, abnormal closures, reconnections)
|
|
||||||
- Fix not being able to re-enable websocket until page refresh
|
|
||||||
- Fix annoying issue where timeline might have few posts when streaming is enabled
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Don't filter own posts when they hit your wordfilter
|
|
||||||
|
|
||||||
|
|
||||||
## [2.2.2] - 2020-12-22
|
|
||||||
### Added
|
|
||||||
- Mouseover titles for emojis in reaction picker
|
|
||||||
- Support to input emoji into the search box in reaction picker
|
|
||||||
- Added some missing unicode emoji
|
|
||||||
- Added the upload limit to the Features panel in the About page
|
|
||||||
- Support for solid color wallpaper, instance doesn't have to define a wallpaper anymore
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
|
|
||||||
- Fixed timeline errors locking timelines
|
|
||||||
- Fixed missing highlighted border in expanded conversations
|
|
||||||
- Fixed custom emoji not working in profile field names
|
|
||||||
- Fixed pinned statuses not appearing in user profiles
|
|
||||||
- Fixed some elements not being keyboard navigation friendly
|
|
||||||
- Fixed error handling when updating various profile images
|
|
||||||
- Fixed your latest chat messages disappearing when closing chat view and opening it again during the same session
|
|
||||||
- Fixed custom emoji not showing in poll options before voting
|
|
||||||
- Fixed link color not applied to instance name in topbar
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
|
|
||||||
- Made reply/fav/repeat etc buttons easier to hit
|
|
||||||
- Adjusted timeline menu clickable area to match the visible button
|
|
||||||
- Moved external source link from status heading to the ellipsis menu
|
|
||||||
- Disabled horizontal textarea resize
|
|
||||||
- Wallpaper is now top-aligned, horizontally centered.
|
|
||||||
|
|
||||||
|
|
||||||
## [2.2.1] - 2020-11-11
|
## [2.2.1] - 2020-11-11
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -95,8 +16,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Import/export a muted users
|
- Import/export a muted users
|
||||||
- Proper handling of deletes when using websocket streaming
|
- Proper handling of deletes when using websocket streaming
|
||||||
- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
|
- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent
|
||||||
- Added a small red badge to the favicon when there's unread notifications
|
|
||||||
- Added the NSFW alert to link previews
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed clicking NSFW hider through status popover
|
- Fixed clicking NSFW hider through status popover
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ Contributors of this project.
|
|||||||
- Constance Variable (lambadalambda@social.heldscal.la): Code
|
- Constance Variable (lambadalambda@social.heldscal.la): Code
|
||||||
- Coco Snuss (cocosnuss@social.heldscal.la): Code
|
- Coco Snuss (cocosnuss@social.heldscal.la): Code
|
||||||
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
|
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
|
||||||
- eris (eris@disqordia.space): Code
|
|
||||||
- dtluna (dtluna@social.heldscal.la): Code
|
- dtluna (dtluna@social.heldscal.la): Code
|
||||||
- sonyam (sonyam@social.heldscal.la): Background images
|
- sonyam (sonyam@social.heldscal.la): Background images
|
||||||
- hakui (hakui@freezepeach.xyz): CSS and styling
|
- hakui (hakui@freezepeach.xyz): CSS and styling
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ var compiler = webpack(webpackConfig)
|
|||||||
|
|
||||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||||
publicPath: webpackConfig.output.publicPath,
|
publicPath: webpackConfig.output.publicPath,
|
||||||
writeToDisk: true,
|
|
||||||
stats: {
|
stats: {
|
||||||
colors: true,
|
colors: true,
|
||||||
chunks: false
|
chunks: false
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ var config = require('../config')
|
|||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var projectRoot = path.resolve(__dirname, '../')
|
var projectRoot = path.resolve(__dirname, '../')
|
||||||
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
|
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
|
||||||
var CopyPlugin = require('copy-webpack-plugin');
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV
|
var env = process.env.NODE_ENV
|
||||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||||
@@ -94,19 +93,6 @@ module.exports = {
|
|||||||
new ServiceWorkerWebpackPlugin({
|
new ServiceWorkerWebpackPlugin({
|
||||||
entry: path.join(__dirname, '..', 'src/sw.js'),
|
entry: path.join(__dirname, '..', 'src/sw.js'),
|
||||||
filename: 'sw-pleroma.js'
|
filename: 'sw-pleroma.js'
|
||||||
}),
|
|
||||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
from: "node_modules/ruffle-mirror/*",
|
|
||||||
to: "static/ruffle",
|
|
||||||
flatten: true
|
|
||||||
},
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
concurrency: 100,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,6 @@ const path = require('path')
|
|||||||
let settings = {}
|
let settings = {}
|
||||||
try {
|
try {
|
||||||
settings = require('./local.json')
|
settings = require('./local.json')
|
||||||
if (settings.target && settings.target.endsWith('/')) {
|
|
||||||
// replacing trailing slash since it can conflict with some apis
|
|
||||||
// and that's how actual BE reports its url
|
|
||||||
settings.target = settings.target.replace(/\/$/, '')
|
|
||||||
}
|
|
||||||
console.log('Using local dev server settings (/config/local.json):')
|
console.log('Using local dev server settings (/config/local.json):')
|
||||||
console.log(JSON.stringify(settings, null, 2))
|
console.log(JSON.stringify(settings, null, 2))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
||||||
|
<title>Pleroma</title>
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
+4
-6
@@ -31,10 +31,9 @@
|
|||||||
"parse-link-header": "^1.0.1",
|
"parse-link-header": "^1.0.1",
|
||||||
"phoenix": "^1.3.0",
|
"phoenix": "^1.3.0",
|
||||||
"portal-vue": "^2.1.4",
|
"portal-vue": "^2.1.4",
|
||||||
"punycode.js": "^2.1.0",
|
|
||||||
"ruffle-mirror": "^2021.4.10",
|
|
||||||
"v-click-outside": "^2.1.1",
|
"v-click-outside": "^2.1.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
|
"vue-chat-scroll": "^1.2.1",
|
||||||
"vue-i18n": "^7.3.2",
|
"vue-i18n": "^7.3.2",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
@@ -56,9 +55,8 @@
|
|||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"chromedriver": "^87.0.1",
|
"chromedriver": "^2.21.2",
|
||||||
"connect-history-api-fallback": "^1.1.0",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
|
||||||
"cross-spawn": "^4.0.2",
|
"cross-spawn": "^4.0.2",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
@@ -104,7 +102,7 @@
|
|||||||
"selenium-server": "2.53.1",
|
"selenium-server": "2.53.1",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.3.0",
|
||||||
"serviceworker-webpack-plugin": "^1.0.0",
|
"serviceworker-webpack-plugin": "^1.0.0",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.7.4",
|
||||||
"sinon": "^2.1.0",
|
"sinon": "^2.1.0",
|
||||||
"sinon-chai": "^2.8.0",
|
"sinon-chai": "^2.8.0",
|
||||||
"stylelint": "^13.6.1",
|
"stylelint": "^13.6.1",
|
||||||
@@ -113,7 +111,7 @@
|
|||||||
"url-loader": "^1.1.2",
|
"url-loader": "^1.1.2",
|
||||||
"vue-loader": "^14.0.0",
|
"vue-loader": "^14.0.0",
|
||||||
"vue-style-loader": "^4.0.0",
|
"vue-style-loader": "^4.0.0",
|
||||||
"webpack": "^4.44.0",
|
"webpack": "^4.0.0",
|
||||||
"webpack-dev-middleware": "^3.6.0",
|
"webpack-dev-middleware": "^3.6.0",
|
||||||
"webpack-hot-middleware": "^2.12.2",
|
"webpack-hot-middleware": "^2.12.2",
|
||||||
"webpack-merge": "^0.14.1"
|
"webpack-merge": "^0.14.1"
|
||||||
|
|||||||
+12
-18
@@ -4,7 +4,7 @@ import Notifications from './components/notifications/notifications.vue'
|
|||||||
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||||
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
||||||
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||||
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
import ChatPanel from './components/chat_panel/chat_panel.vue'
|
||||||
import SettingsModal from './components/settings_modal/settings_modal.vue'
|
import SettingsModal from './components/settings_modal/settings_modal.vue'
|
||||||
import MediaModal from './components/media_modal/media_modal.vue'
|
import MediaModal from './components/media_modal/media_modal.vue'
|
||||||
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||||
@@ -15,7 +15,6 @@ import UserReportingModal from './components/user_reporting_modal/user_reporting
|
|||||||
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||||
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||||
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
@@ -26,7 +25,7 @@ export default {
|
|||||||
InstanceSpecificPanel,
|
InstanceSpecificPanel,
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
WhoToFollowPanel,
|
WhoToFollowPanel,
|
||||||
ShoutPanel,
|
ChatPanel,
|
||||||
MediaModal,
|
MediaModal,
|
||||||
SideDrawer,
|
SideDrawer,
|
||||||
MobilePostStatusButton,
|
MobilePostStatusButton,
|
||||||
@@ -51,21 +50,20 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
currentUser () { return this.$store.state.users.currentUser },
|
||||||
userBackground () { return this.currentUser.background_image },
|
background () {
|
||||||
instanceBackground () {
|
return this.currentUser.background_image || this.$store.state.instance.background
|
||||||
return this.mergedConfig.hideInstanceWallpaper
|
|
||||||
? null
|
|
||||||
: this.$store.state.instance.background
|
|
||||||
},
|
},
|
||||||
background () { return this.userBackground || this.instanceBackground },
|
|
||||||
bgStyle () {
|
bgStyle () {
|
||||||
if (this.background) {
|
return {
|
||||||
|
'background-image': `url(${this.background})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bgAppStyle () {
|
||||||
return {
|
return {
|
||||||
'--body-background-image': `url(${this.background})`
|
'--body-background-image': `url(${this.background})`
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
shout () { return this.$store.state.shout.channel.state === 'joined' },
|
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
||||||
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
||||||
showInstanceSpecificPanel () {
|
showInstanceSpecificPanel () {
|
||||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||||
@@ -73,17 +71,13 @@ export default {
|
|||||||
this.$store.state.instance.instanceSpecificPanelContent
|
this.$store.state.instance.instanceSpecificPanelContent
|
||||||
},
|
},
|
||||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||||
hideShoutbox () {
|
|
||||||
return this.$store.getters.mergedConfig.hideShoutbox
|
|
||||||
},
|
|
||||||
isMobileLayout () { return this.$store.state.interface.mobileLayout },
|
isMobileLayout () { return this.$store.state.interface.mobileLayout },
|
||||||
privateMode () { return this.$store.state.instance.private },
|
privateMode () { return this.$store.state.instance.private },
|
||||||
sidebarAlign () {
|
sidebarAlign () {
|
||||||
return {
|
return {
|
||||||
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
|
'order': this.$store.state.instance.sidebarRight ? 99 : 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
...mapGetters(['mergedConfig'])
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMobileState () {
|
updateMobileState () {
|
||||||
|
|||||||
+72
-94
@@ -14,9 +14,7 @@
|
|||||||
right: -20px;
|
right: -20px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: var(--wallpaper);
|
background-position: 0 50%;
|
||||||
background-image: var(--body-background-image);
|
|
||||||
background-position: 50% 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i[class^='icon-'] {
|
i[class^='icon-'] {
|
||||||
@@ -35,7 +33,6 @@ h4 {
|
|||||||
max-width: 980px;
|
max-width: 980px;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.underlay {
|
.underlay {
|
||||||
background-color: rgba(0,0,0,0.15);
|
background-color: rgba(0,0,0,0.15);
|
||||||
background-color: var(--underlay, rgba(0,0,0,0.15));
|
background-color: var(--underlay, rgba(0,0,0,0.15));
|
||||||
@@ -72,7 +69,7 @@ a {
|
|||||||
color: var(--link, $fallback--link);
|
color: var(--link, $fallback--link);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-default {
|
button {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnText, $fallback--text);
|
color: var(--btnText, $fallback--text);
|
||||||
@@ -88,8 +85,7 @@ a {
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--interfaceFont, sans-serif);
|
font-family: var(--interfaceFont, sans-serif);
|
||||||
|
|
||||||
i[class*=icon-],
|
i[class*=icon-], .svg-inline--fa {
|
||||||
.svg-inline--fa {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnText, $fallback--text);
|
color: var(--btnText, $fallback--text);
|
||||||
}
|
}
|
||||||
@@ -111,8 +107,7 @@ a {
|
|||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--btnPressed, $fallback--fg);
|
background-color: var(--btnPressed, $fallback--fg);
|
||||||
|
|
||||||
svg,
|
svg, i {
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnPressedText, $fallback--text);
|
color: var(--btnPressedText, $fallback--text);
|
||||||
}
|
}
|
||||||
@@ -125,8 +120,7 @@ a {
|
|||||||
background-color: $fallback--fg;
|
background-color: $fallback--fg;
|
||||||
background-color: var(--btnDisabled, $fallback--fg);
|
background-color: var(--btnDisabled, $fallback--fg);
|
||||||
|
|
||||||
svg,
|
svg, i {
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnDisabledText, $fallback--text);
|
color: var(--btnDisabledText, $fallback--text);
|
||||||
}
|
}
|
||||||
@@ -140,8 +134,7 @@ a {
|
|||||||
box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
|
||||||
box-shadow: var(--buttonPressedShadow);
|
box-shadow: var(--buttonPressedShadow);
|
||||||
|
|
||||||
svg,
|
svg, i {
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnToggledText, $fallback--text);
|
color: var(--btnToggledText, $fallback--text);
|
||||||
}
|
}
|
||||||
@@ -156,38 +149,7 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-unstyled {
|
input, textarea, .select, .input {
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
display: inline;
|
|
||||||
text-align: initial;
|
|
||||||
font-size: 100%;
|
|
||||||
font-family: inherit;
|
|
||||||
padding: 0;
|
|
||||||
line-height: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: content-box;
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&.-link {
|
|
||||||
color: $fallback--link;
|
|
||||||
color: var(--link, $fallback--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-fullwidth {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-hover-highlight {
|
|
||||||
&:hover svg {
|
|
||||||
color: $fallback--lightText;
|
|
||||||
color: var(--lightText, $fallback--lightText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea, .input {
|
|
||||||
|
|
||||||
&.unstyled {
|
&.unstyled {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -217,11 +179,47 @@ input, textarea, .input {
|
|||||||
hyphens: none;
|
hyphens: none;
|
||||||
padding: 8px .5em;
|
padding: 8px .5em;
|
||||||
|
|
||||||
&:disabled, &[disabled=disabled], &.disabled {
|
&.select {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled, &[disabled=disabled] {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-down-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 5px;
|
||||||
|
height: 100%;
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--inputText, $fallback--text);
|
||||||
|
line-height: 28px;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--inputText, --text, $fallback--text);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 2em 0 .2em;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-family: var(--inputFont, sans-serif);
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&[type=range] {
|
&[type=range] {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -305,10 +303,6 @@ input, textarea, .input {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.resize-height {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
option {
|
option {
|
||||||
@@ -448,7 +442,6 @@ main-router {
|
|||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--panelFaint, $fallback--faint);
|
color: var(--panelFaint, $fallback--faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
.faint-link {
|
.faint-link {
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--faintLink, $fallback--faint);
|
color: var(--faintLink, $fallback--faint);
|
||||||
@@ -460,8 +453,11 @@ main-router {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-default,
|
button {
|
||||||
.alert {
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .alert {
|
||||||
// height: 100%;
|
// height: 100%;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -472,11 +468,8 @@ main-router {
|
|||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-default {
|
button {
|
||||||
flex-shrink: 0;
|
&, i[class*=icon-] {
|
||||||
|
|
||||||
&,
|
|
||||||
i[class*=icon-] {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnPanelText, $fallback--text);
|
color: var(--btnPanelText, $fallback--text);
|
||||||
}
|
}
|
||||||
@@ -499,8 +492,7 @@ main-router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a {
|
||||||
.-link {
|
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
color: var(--panelLink, $fallback--link)
|
color: var(--panelLink, $fallback--link)
|
||||||
}
|
}
|
||||||
@@ -511,31 +503,19 @@ main-router {
|
|||||||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Should remove timeline-footer from here when we refactor panels into
|
.panel-footer {
|
||||||
* separate component and utilize slots
|
|
||||||
*/
|
|
||||||
.panel-footer, .timeline-footer {
|
|
||||||
display: flex;
|
|
||||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||||
flex: none;
|
|
||||||
padding: 0.6em 0.6em;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 28px;
|
|
||||||
align-items: baseline;
|
|
||||||
border-width: 1px 0 0 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--border, $fallback--border);
|
|
||||||
|
|
||||||
.faint {
|
.faint {
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--panelFaint, $fallback--faint);
|
color: var(--panelFaint, $fallback--faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a {
|
||||||
.-link {
|
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
color: var(--panelLink, $fallback--link);
|
color: var(--panelLink, $fallback--link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +542,6 @@ nav {
|
|||||||
color: var(--faint, $fallback--faint);
|
color: var(--faint, $fallback--faint);
|
||||||
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
|
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
|
||||||
box-shadow: var(--topBarShadow);
|
box-shadow: var(--topBarShadow);
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter-active, .fade-leave-active {
|
.fade-enter-active, .fade-leave-active {
|
||||||
@@ -682,15 +661,6 @@ nav {
|
|||||||
color: var(--alertWarningPanelText, $fallback--text);
|
color: var(--alertWarningPanelText, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.success {
|
|
||||||
background-color: var(--alertSuccess, $fallback--alertWarning);
|
|
||||||
color: var(--alertSuccessText, $fallback--text);
|
|
||||||
|
|
||||||
.panel-heading & {
|
|
||||||
color: var(--alertSuccessPanelText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.faint {
|
.faint {
|
||||||
@@ -794,6 +764,13 @@ nav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-multiple {
|
||||||
|
display: flex;
|
||||||
|
.option-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
.setting-list,
|
.setting-list,
|
||||||
.option-list{
|
.option-list{
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@@ -820,7 +797,7 @@ nav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.button-default {
|
.btn.btn-default {
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,10 +817,16 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-status-notification {
|
.new-status-notification {
|
||||||
position: relative;
|
position:relative;
|
||||||
|
margin-top: -1px;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
padding: 10px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
flex: 1;
|
background-color: $fallback--fg;
|
||||||
|
background-color: var(--panel, $fallback--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-layout {
|
.chat-layout {
|
||||||
@@ -851,11 +834,6 @@ nav {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
// Get rid of scrollbar on body as scrolling happens on different element
|
|
||||||
body {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures the fixed position of the mobile browser bars on scroll up / down events.
|
// Ensures the fixed position of the mobile browser bars on scroll up / down events.
|
||||||
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
|
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
|
||||||
@media all and (max-width: 800px) {
|
@media all and (max-width: 800px) {
|
||||||
|
|||||||
+5
-4
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="app"
|
id="app"
|
||||||
:style="bgStyle"
|
:style="bgAppStyle"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="app_bg_wrapper"
|
id="app_bg_wrapper"
|
||||||
class="app-bg-wrapper"
|
class="app-bg-wrapper"
|
||||||
|
:style="bgStyle"
|
||||||
/>
|
/>
|
||||||
<MobileNav v-if="isMobileLayout" />
|
<MobileNav v-if="isMobileLayout" />
|
||||||
<DesktopNav v-else />
|
<DesktopNav v-else />
|
||||||
@@ -49,10 +50,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<media-modal />
|
<media-modal />
|
||||||
</div>
|
</div>
|
||||||
<shout-panel
|
<chat-panel
|
||||||
v-if="currentUser && shout && !hideShoutbox"
|
v-if="currentUser && chat"
|
||||||
:floating="true"
|
:floating="true"
|
||||||
class="floating-shout mobile-hidden"
|
class="floating-chat mobile-hidden"
|
||||||
/>
|
/>
|
||||||
<MobilePostStatusButton />
|
<MobilePostStatusButton />
|
||||||
<UserReportingModal />
|
<UserReportingModal />
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
|||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||||
import { applyTheme } from '../services/style_setter/style_setter.js'
|
import { applyTheme } from '../services/style_setter/style_setter.js'
|
||||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
|
||||||
|
|
||||||
let staticInitialResults = null
|
let staticInitialResults = null
|
||||||
|
|
||||||
@@ -51,7 +50,6 @@ const getInstanceConfig = async ({ store }) => {
|
|||||||
const vapidPublicKey = data.pleroma.vapid_public_key
|
const vapidPublicKey = data.pleroma.vapid_public_key
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
||||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
|
||||||
|
|
||||||
if (vapidPublicKey) {
|
if (vapidPublicKey) {
|
||||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||||
@@ -240,7 +238,7 @@ const getNodeInfo = async ({ store }) => {
|
|||||||
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
||||||
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
||||||
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
||||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||||
@@ -328,8 +326,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||||||
const width = windowWidth()
|
const width = windowWidth()
|
||||||
store.dispatch('setMobileLayout', width <= 800)
|
store.dispatch('setMobileLayout', width <= 800)
|
||||||
|
|
||||||
FaviconService.initFaviconService()
|
|
||||||
|
|
||||||
const overrides = window.___pleromafe_dev_overrides || {}
|
const overrides = window.___pleromafe_dev_overrides || {}
|
||||||
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
|
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
|
||||||
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
|||||||
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
||||||
import Notifications from 'components/notifications/notifications.vue'
|
import Notifications from 'components/notifications/notifications.vue'
|
||||||
import AuthForm from 'components/auth_form/auth_form.js'
|
import AuthForm from 'components/auth_form/auth_form.js'
|
||||||
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
|
import ChatPanel from 'components/chat_panel/chat_panel.vue'
|
||||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||||
import About from 'components/about/about.vue'
|
import About from 'components/about/about.vue'
|
||||||
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
||||||
@@ -64,7 +64,7 @@ export default (store) => {
|
|||||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'login', path: '/login', component: AuthForm },
|
{ name: 'login', path: '/login', component: AuthForm },
|
||||||
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
|
{ name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
|
||||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
||||||
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
||||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const AccountActions = {
|
|||||||
this.$store.dispatch('unblockUser', this.user.id)
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
},
|
},
|
||||||
reportUser () {
|
reportUser () {
|
||||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
this.$store.dispatch('openUserReportingModal', this.user.id)
|
||||||
},
|
},
|
||||||
openChat () {
|
openChat () {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
|
|||||||
@@ -4,21 +4,23 @@
|
|||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
:bound-to="{ x: 'container' }"
|
:bound-to="{ x: 'container' }"
|
||||||
remove-padding
|
|
||||||
>
|
>
|
||||||
<template v-slot:content>
|
<div
|
||||||
|
slot="content"
|
||||||
|
class="account-tools-popover"
|
||||||
|
>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<template v-if="relationship.following">
|
<template v-if="relationship.following">
|
||||||
<button
|
<button
|
||||||
v-if="relationship.showing_reblogs"
|
v-if="relationship.showing_reblogs"
|
||||||
class="btn button-default dropdown-item"
|
class="btn btn-default dropdown-item"
|
||||||
@click="hideRepeats"
|
@click="hideRepeats"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.hide_repeats') }}
|
{{ $t('user_card.hide_repeats') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!relationship.showing_reblogs"
|
v-if="!relationship.showing_reblogs"
|
||||||
class="btn button-default dropdown-item"
|
class="btn btn-default dropdown-item"
|
||||||
@click="showRepeats"
|
@click="showRepeats"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.show_repeats') }}
|
{{ $t('user_card.show_repeats') }}
|
||||||
@@ -30,41 +32,42 @@
|
|||||||
</template>
|
</template>
|
||||||
<button
|
<button
|
||||||
v-if="relationship.blocking"
|
v-if="relationship.blocking"
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="btn btn-default btn-block dropdown-item"
|
||||||
@click="unblockUser"
|
@click="unblockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.unblock') }}
|
{{ $t('user_card.unblock') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="btn btn-default btn-block dropdown-item"
|
||||||
@click="blockUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.block') }}
|
{{ $t('user_card.block') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="btn btn-default btn-block dropdown-item"
|
||||||
@click="reportUser"
|
@click="reportUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.report') }}
|
{{ $t('user_card.report') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="pleromaChatMessagesAvailable"
|
v-if="pleromaChatMessagesAvailable"
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="btn btn-default btn-block dropdown-item"
|
||||||
@click="openChat"
|
@click="openChat"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.message') }}
|
{{ $t('user_card.message') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-slot:trigger>
|
<div
|
||||||
<button class="button-unstyled ellipsis-button">
|
slot="trigger"
|
||||||
|
class="btn btn-default ellipsis-button"
|
||||||
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="icon"
|
class="icon"
|
||||||
icon="ellipsis-v"
|
icon="ellipsis-v"
|
||||||
/>
|
/>
|
||||||
</button>
|
</div>
|
||||||
</template>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -79,6 +82,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis-button {
|
.ellipsis-button {
|
||||||
|
cursor: pointer;
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
margin: -0.5em 0;
|
margin: -0.5em 0;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{{ $t('general.error_retry') }}
|
{{ $t('general.error_retry') }}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn"
|
||||||
@click="retry"
|
@click="retry"
|
||||||
>
|
>
|
||||||
{{ $t('general.retry') }}
|
{{ $t('general.retry') }}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import StillImage from '../still-image/still-image.vue'
|
import StillImage from '../still-image/still-image.vue'
|
||||||
import Flash from '../flash/flash.vue'
|
|
||||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||||
import nsfwImage from '../../assets/nsfw.png'
|
import nsfwImage from '../../assets/nsfw.png'
|
||||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
@@ -9,18 +8,14 @@ import {
|
|||||||
faFile,
|
faFile,
|
||||||
faMusic,
|
faMusic,
|
||||||
faImage,
|
faImage,
|
||||||
faVideo,
|
faVideo
|
||||||
faPlayCircle,
|
|
||||||
faTimes
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faFile,
|
faFile,
|
||||||
faMusic,
|
faMusic,
|
||||||
faImage,
|
faImage,
|
||||||
faVideo,
|
faVideo
|
||||||
faPlayCircle,
|
|
||||||
faTimes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Attachment = {
|
const Attachment = {
|
||||||
@@ -44,7 +39,6 @@ const Attachment = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Flash,
|
|
||||||
StillImage,
|
StillImage,
|
||||||
VideoAttachment
|
VideoAttachment
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,13 +42,15 @@
|
|||||||
icon="play-circle"
|
icon="play-circle"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<div
|
||||||
v-if="nsfw && hideNsfwLocal && !hidden"
|
v-if="nsfw && hideNsfwLocal && !hidden"
|
||||||
class="button-unstyled hider"
|
class="hider"
|
||||||
@click.prevent="toggleHidden"
|
|
||||||
>
|
>
|
||||||
<FAIcon icon="times" />
|
<a
|
||||||
</button>
|
href="#"
|
||||||
|
@click.prevent="toggleHidden"
|
||||||
|
>Hide</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
v-if="type === 'image' && (!hidden || preloadImage)"
|
v-if="type === 'image' && (!hidden || preloadImage)"
|
||||||
@@ -117,11 +119,6 @@
|
|||||||
<!-- eslint-enable vue/no-v-html -->
|
<!-- eslint-enable vue/no-v-html -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Flash
|
|
||||||
v-if="type === 'flash'"
|
|
||||||
:src="attachment.large_thumb_url || attachment.url"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -177,7 +174,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.non-gallery.attachment {
|
.non-gallery.attachment {
|
||||||
&.flash,
|
|
||||||
&.video {
|
&.video {
|
||||||
flex: 1 0 40%;
|
flex: 1 0 40%;
|
||||||
}
|
}
|
||||||
@@ -238,23 +234,15 @@
|
|||||||
.hider {
|
.hider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
white-space: nowrap;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
padding: 0;
|
padding: 5px;
|
||||||
|
background: rgba(230,230,230,0.6);
|
||||||
|
font-weight: bold;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
line-height: 1;
|
||||||
border-radius: $fallback--tooltipRadius;
|
border-radius: $fallback--tooltipRadius;
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||||
text-align: center;
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
font-size: 1.25em;
|
|
||||||
// TODO: theming? hard to theme with unknown background image color
|
|
||||||
background: rgba(230, 230, 230, 0.7);
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
&:hover .svg-inline--fa {
|
|
||||||
color: rgba(0, 0, 0, 0.9);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
class="basic-user-card-screen-name"
|
class="basic-user-card-screen-name"
|
||||||
:to="userProfileLink(user)"
|
:to="userProfileLink(user)"
|
||||||
>
|
>
|
||||||
@{{ user.screen_name_ui }}
|
@{{ user.screen_name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="block-card-content-container">
|
<div class="block-card-content-container">
|
||||||
<button
|
<button
|
||||||
v-if="blocked"
|
v-if="blocked"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
:disabled="progress"
|
:disabled="progress"
|
||||||
@click="unblockUser"
|
@click="unblockUser"
|
||||||
>
|
>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
:disabled="progress"
|
:disabled="progress"
|
||||||
@click="blockUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const Chat = {
|
|||||||
},
|
},
|
||||||
formPlaceholder () {
|
formPlaceholder () {
|
||||||
if (this.recipient) {
|
if (this.recipient) {
|
||||||
return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
|
return this.$t('chats.message_user', { nickname: this.recipient.screen_name })
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@@ -234,13 +234,6 @@ const Chat = {
|
|||||||
const scrollable = this.$refs.scrollable
|
const scrollable = this.$refs.scrollable
|
||||||
return scrollable && scrollable.scrollTop <= 0
|
return scrollable && scrollable.scrollTop <= 0
|
||||||
},
|
},
|
||||||
cullOlderCheck () {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
|
||||||
this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
},
|
|
||||||
handleScroll: _.throttle(function () {
|
handleScroll: _.throttle(function () {
|
||||||
if (!this.currentChat) { return }
|
if (!this.currentChat) { return }
|
||||||
|
|
||||||
@@ -248,7 +241,6 @@ const Chat = {
|
|||||||
this.fetchChat({ maxId: this.currentChatMessageService.minId })
|
this.fetchChat({ maxId: this.currentChatMessageService.minId })
|
||||||
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||||
this.jumpToBottomButtonVisible = false
|
this.jumpToBottomButtonVisible = false
|
||||||
this.cullOlderCheck()
|
|
||||||
if (this.newMessageCount > 0) {
|
if (this.newMessageCount > 0) {
|
||||||
// Use a delay before marking as read to prevent situation where new messages
|
// Use a delay before marking as read to prevent situation where new messages
|
||||||
// arrive just as you're leaving the view and messages that you didn't actually
|
// arrive just as you're leaving the view and messages that you didn't actually
|
||||||
|
|||||||
@@ -98,10 +98,10 @@
|
|||||||
.unread-message-count {
|
.unread-message-count {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
border-radius: 100%;
|
||||||
margin-top: -1rem;
|
margin-top: -1rem;
|
||||||
padding: 0.1em;
|
padding: 0;
|
||||||
border-radius: 50px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-loading-error {
|
.chat-loading-error {
|
||||||
|
|||||||
@@ -10,10 +10,7 @@
|
|||||||
<span class="title">
|
<span class="title">
|
||||||
{{ $t("chats.chats") }}
|
{{ $t("chats.chats") }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button @click="newChat">
|
||||||
class="button-default"
|
|
||||||
@click="newChat"
|
|
||||||
>
|
|
||||||
{{ $t("chats.new") }}
|
{{ $t("chats.new") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,7 +20,10 @@
|
|||||||
class="timeline"
|
class="timeline"
|
||||||
>
|
>
|
||||||
<List :items="sortedChatList">
|
<List :items="sortedChatList">
|
||||||
<template v-slot:item="{item}">
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
<ChatListItem
|
<ChatListItem
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:compact="false"
|
:compact="false"
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
border-radius: $fallback--chatMessageRadius;
|
||||||
|
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
|
|||||||
@@ -50,37 +50,35 @@
|
|||||||
@show="menuOpened = true"
|
@show="menuOpened = true"
|
||||||
@close="menuOpened = false"
|
@close="menuOpened = false"
|
||||||
>
|
>
|
||||||
<template v-slot:content>
|
<div slot="content">
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click="deleteMessage"
|
@click="deleteMessage"
|
||||||
>
|
>
|
||||||
<FAIcon icon="times" /> {{ $t("chats.delete") }}
|
<FAIcon icon="times" /> {{ $t("chats.delete") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-slot:trigger>
|
|
||||||
<button
|
<button
|
||||||
class="button-default menu-icon"
|
slot="trigger"
|
||||||
|
class="menu-icon"
|
||||||
:title="$t('chats.more')"
|
:title="$t('chats.more')"
|
||||||
>
|
>
|
||||||
<FAIcon icon="ellipsis-h" />
|
<FAIcon icon="ellipsis-h" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<StatusContent
|
<StatusContent
|
||||||
:status="messageForStatusContent"
|
:status="messageForStatusContent"
|
||||||
:full-content="true"
|
:full-content="true"
|
||||||
>
|
>
|
||||||
<template v-slot:footer>
|
|
||||||
<span
|
<span
|
||||||
|
slot="footer"
|
||||||
class="created-at"
|
class="created-at"
|
||||||
>
|
>
|
||||||
{{ createdAt }}
|
{{ createdAt }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
|
||||||
</StatusContent>
|
</StatusContent>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import localeService from 'src/services/locale/locale.service.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Timeago',
|
name: 'Timeago',
|
||||||
props: ['date'],
|
props: ['date'],
|
||||||
@@ -18,7 +16,7 @@ export default {
|
|||||||
if (this.date.getTime() === today.getTime()) {
|
if (this.date.getTime() === today.getTime()) {
|
||||||
return this.$t('display_date.today')
|
return this.$t('display_date.today')
|
||||||
} else {
|
} else {
|
||||||
return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
|
return this.date.toLocaleDateString('en', { day: 'numeric', month: 'long' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-16
@@ -10,7 +10,7 @@ library.add(
|
|||||||
faTimes
|
faTimes
|
||||||
)
|
)
|
||||||
|
|
||||||
const shoutPanel = {
|
const chatPanel = {
|
||||||
props: [ 'floating' ],
|
props: [ 'floating' ],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -21,12 +21,12 @@ const shoutPanel = {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
messages () {
|
messages () {
|
||||||
return this.$store.state.shout.messages
|
return this.$store.state.chat.messages
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit (message) {
|
submit (message) {
|
||||||
this.$store.state.shout.channel.push('new_msg', { text: message }, 10000)
|
this.$store.state.chat.channel.push('new_msg', { text: message }, 10000)
|
||||||
this.currentMessage = ''
|
this.currentMessage = ''
|
||||||
},
|
},
|
||||||
togglePanel () {
|
togglePanel () {
|
||||||
@@ -35,19 +35,7 @@ const shoutPanel = {
|
|||||||
userProfileLink (user) {
|
userProfileLink (user) {
|
||||||
return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
|
return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
messages (newVal) {
|
|
||||||
const scrollEl = this.$el.querySelector('.chat-window')
|
|
||||||
if (!scrollEl) return
|
|
||||||
if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (!scrollEl) return
|
|
||||||
scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default shoutPanel
|
export default chatPanel
|
||||||
+26
-31
@@ -1,50 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="!collapsed || !floating"
|
v-if="!collapsed || !floating"
|
||||||
class="shout-panel"
|
class="chat-panel"
|
||||||
>
|
>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div
|
<div
|
||||||
class="panel-heading timeline-heading"
|
class="panel-heading timeline-heading"
|
||||||
:class="{ 'shout-heading': floating }"
|
:class="{ 'chat-heading': floating }"
|
||||||
@click.stop.prevent="togglePanel"
|
@click.stop.prevent="togglePanel"
|
||||||
>
|
>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
{{ $t('shoutbox.title') }}
|
<span>{{ $t('shoutbox.title') }}</span>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
v-if="floating"
|
v-if="floating"
|
||||||
icon="times"
|
icon="times"
|
||||||
class="close-icon"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shout-window">
|
<div
|
||||||
|
v-chat-scroll
|
||||||
|
class="chat-window"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="message in messages"
|
v-for="message in messages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
class="shout-message"
|
class="chat-message"
|
||||||
>
|
>
|
||||||
<span class="shout-avatar">
|
<span class="chat-avatar">
|
||||||
<img :src="message.author.avatar">
|
<img :src="message.author.avatar">
|
||||||
</span>
|
</span>
|
||||||
<div class="shout-content">
|
<div class="chat-content">
|
||||||
<router-link
|
<router-link
|
||||||
class="shout-name"
|
class="chat-name"
|
||||||
:to="userProfileLink(message.author)"
|
:to="userProfileLink(message.author)"
|
||||||
>
|
>
|
||||||
{{ message.author.username }}
|
{{ message.author.username }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<br>
|
<br>
|
||||||
<span class="shout-text">
|
<span class="chat-text">
|
||||||
{{ message.text }}
|
{{ message.text }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shout-input">
|
<div class="chat-input">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="currentMessage"
|
v-model="currentMessage"
|
||||||
class="shout-input-textarea"
|
class="chat-input-textarea"
|
||||||
rows="1"
|
rows="1"
|
||||||
@keyup.enter="submit(currentMessage)"
|
@keyup.enter="submit(currentMessage)"
|
||||||
/>
|
/>
|
||||||
@@ -53,11 +55,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="shout-panel"
|
class="chat-panel"
|
||||||
>
|
>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div
|
<div
|
||||||
class="panel-heading stub timeline-heading shout-heading"
|
class="panel-heading stub timeline-heading chat-heading"
|
||||||
@click.stop.prevent="togglePanel"
|
@click.stop.prevent="togglePanel"
|
||||||
>
|
>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
@@ -72,12 +74,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./shout_panel.js"></script>
|
<script src="./chat_panel.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.floating-shout {
|
.floating-chat {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
@@ -85,39 +87,32 @@
|
|||||||
max-width: 25em;
|
max-width: 25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-panel {
|
.chat-panel {
|
||||||
.shout-heading {
|
.chat-heading {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-window {
|
.chat-window {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
max-height: 20em;
|
max-height: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-window-container {
|
.chat-window-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-message {
|
.chat-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0.2em 0.5em
|
padding: 0.2em 0.5em
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-avatar {
|
.chat-avatar {
|
||||||
img {
|
img {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
@@ -128,7 +123,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-input {
|
.chat-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
textarea {
|
textarea {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -138,7 +133,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-panel {
|
.chat-panel {
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -12,7 +12,7 @@ export default Vue.component('chat-title', {
|
|||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
title () {
|
title () {
|
||||||
return this.user ? this.user.screen_name_ui : ''
|
return this.user ? this.user.screen_name : ''
|
||||||
},
|
},
|
||||||
htmlTitle () {
|
htmlTitle () {
|
||||||
return this.user ? this.user.name_html : ''
|
return this.user ? this.user.name_html : ''
|
||||||
|
|||||||
@@ -10,13 +10,12 @@
|
|||||||
class="panel-heading conversation-heading"
|
class="panel-heading conversation-heading"
|
||||||
>
|
>
|
||||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||||
<button
|
<span v-if="collapsable">
|
||||||
v-if="collapsable"
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
@click.prevent="toggleExpanded"
|
@click.prevent="toggleExpanded"
|
||||||
>
|
>{{ $t('timeline.collapse') }}</a>
|
||||||
{{ $t('timeline.collapse') }}
|
</span>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<status
|
<status
|
||||||
v-for="status in conversation"
|
v-for="status in conversation"
|
||||||
@@ -50,6 +49,7 @@
|
|||||||
|
|
||||||
.Conversation {
|
.Conversation {
|
||||||
.conversation-status {
|
.conversation-status {
|
||||||
|
border-left: none;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-bottom-color: var(--border, $fallback--border);
|
border-bottom-color: var(--border, $fallback--border);
|
||||||
@@ -57,6 +57,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.-expanded {
|
&.-expanded {
|
||||||
|
.conversation-status {
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
border-left-color: $fallback--cRed;
|
||||||
|
border-left-color: var(--cRed, $fallback--cRed);
|
||||||
|
}
|
||||||
|
|
||||||
.conversation-status:last-child {
|
.conversation-status:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--topBarLink, $fallback--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner-nav {
|
.inner-nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 50px;
|
grid-template-rows: 50px;
|
||||||
@@ -25,7 +21,7 @@
|
|||||||
grid-template-areas: "logo sitename actions";
|
grid-template-areas: "logo sitename actions";
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-default {
|
button {
|
||||||
&, svg {
|
&, svg {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnTopBarText, $fallback--text);
|
color: var(--btnTopBarText, $fallback--text);
|
||||||
@@ -84,14 +80,13 @@
|
|||||||
.nav-icon {
|
.nav-icon {
|
||||||
margin-left: 0.2em;
|
margin-left: 0.2em;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-inline--fa {
|
a, a svg {
|
||||||
color: $fallback--link;
|
color: $fallback--link;
|
||||||
color: var(--topBarLink, $fallback--link);
|
color: var(--topBarLink, $fallback--link);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.sitename {
|
.sitename {
|
||||||
grid-area: sitename;
|
grid-area: sitename;
|
||||||
|
|||||||
@@ -36,8 +36,9 @@
|
|||||||
@toggled="onSearchBarToggled"
|
@toggled="onSearchBarToggled"
|
||||||
@click.stop.native
|
@click.stop.native
|
||||||
/>
|
/>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled nav-icon"
|
href="#"
|
||||||
|
class="nav-icon"
|
||||||
@click.stop="openSettingsModal"
|
@click.stop="openSettingsModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -46,32 +47,29 @@
|
|||||||
icon="cog"
|
icon="cog"
|
||||||
:title="$t('nav.preferences')"
|
:title="$t('nav.preferences')"
|
||||||
/>
|
/>
|
||||||
</button>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="currentUser && currentUser.role === 'admin'"
|
||||||
href="/pleroma/admin/#/login-pleroma"
|
href="/pleroma/admin/#/login-pleroma"
|
||||||
class="nav-icon"
|
class="nav-icon"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
><FAIcon
|
||||||
<FAIcon
|
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="tachometer-alt"
|
icon="tachometer-alt"
|
||||||
:title="$t('nav.administration')"
|
:title="$t('nav.administration')"
|
||||||
/>
|
/></a>
|
||||||
</a>
|
<a
|
||||||
<button
|
|
||||||
v-if="currentUser"
|
v-if="currentUser"
|
||||||
class="button-unstyled nav-icon"
|
href="#"
|
||||||
|
class="nav-icon"
|
||||||
@click.prevent="logout"
|
@click.prevent="logout"
|
||||||
>
|
><FAIcon
|
||||||
<FAIcon
|
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="sign-out-alt"
|
icon="sign-out-alt"
|
||||||
:title="$t('login.logout')"
|
:title="$t('login.logout')"
|
||||||
/>
|
/></a>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -6,20 +6,20 @@
|
|||||||
<ProgressButton
|
<ProgressButton
|
||||||
v-if="muted"
|
v-if="muted"
|
||||||
:click="unmuteDomain"
|
:click="unmuteDomain"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('domain_mute_card.unmute') }}
|
{{ $t('domain_mute_card.unmute') }}
|
||||||
<template v-slot:progress>
|
<template slot="progress">
|
||||||
{{ $t('domain_mute_card.unmute_progress') }}
|
{{ $t('domain_mute_card.unmute_progress') }}
|
||||||
</template>
|
</template>
|
||||||
</ProgressButton>
|
</ProgressButton>
|
||||||
<ProgressButton
|
<ProgressButton
|
||||||
v-else
|
v-else
|
||||||
:click="muteDomain"
|
:click="muteDomain"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('domain_mute_card.mute') }}
|
{{ $t('domain_mute_card.mute') }}
|
||||||
<template v-slot:progress>
|
<template slot="progress">
|
||||||
{{ $t('domain_mute_card.mute_progress') }}
|
{{ $t('domain_mute_card.mute_progress') }}
|
||||||
</template>
|
</template>
|
||||||
</ProgressButton>
|
</ProgressButton>
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ const EmojiInput = {
|
|||||||
required: true,
|
required: true,
|
||||||
type: Function
|
type: Function
|
||||||
},
|
},
|
||||||
// TODO VUE3: change to modelValue, change 'input' event to 'input'
|
|
||||||
value: {
|
value: {
|
||||||
/**
|
/**
|
||||||
* Used for v-model
|
* Used for v-model
|
||||||
@@ -115,8 +114,7 @@ const EmojiInput = {
|
|||||||
showPicker: false,
|
showPicker: false,
|
||||||
temporarilyHideSuggestions: false,
|
temporarilyHideSuggestions: false,
|
||||||
keepOpen: false,
|
keepOpen: false,
|
||||||
disableClickOutside: false,
|
disableClickOutside: false
|
||||||
suggestions: []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@@ -126,6 +124,21 @@ const EmojiInput = {
|
|||||||
padEmoji () {
|
padEmoji () {
|
||||||
return this.$store.getters.mergedConfig.padEmoji
|
return this.$store.getters.mergedConfig.padEmoji
|
||||||
},
|
},
|
||||||
|
suggestions () {
|
||||||
|
const firstchar = this.textAtCaret.charAt(0)
|
||||||
|
if (this.textAtCaret === firstchar) { return [] }
|
||||||
|
const matchedSuggestions = this.suggest(this.textAtCaret)
|
||||||
|
if (matchedSuggestions.length <= 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return take(matchedSuggestions, 5)
|
||||||
|
.map(({ imageUrl, ...rest }, index) => ({
|
||||||
|
...rest,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
img: imageUrl || '',
|
||||||
|
highlighted: index === this.highlighted
|
||||||
|
}))
|
||||||
|
},
|
||||||
showSuggestions () {
|
showSuggestions () {
|
||||||
return this.focused &&
|
return this.focused &&
|
||||||
this.suggestions &&
|
this.suggestions &&
|
||||||
@@ -144,68 +157,45 @@ const EmojiInput = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const { root } = this.$refs
|
const slots = this.$slots.default
|
||||||
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
|
if (!slots || slots.length === 0) return
|
||||||
|
const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
|
||||||
if (!input) return
|
if (!input) return
|
||||||
this.input = input
|
this.input = input
|
||||||
this.resize()
|
this.resize()
|
||||||
input.addEventListener('blur', this.onBlur)
|
input.elm.addEventListener('blur', this.onBlur)
|
||||||
input.addEventListener('focus', this.onFocus)
|
input.elm.addEventListener('focus', this.onFocus)
|
||||||
input.addEventListener('paste', this.onPaste)
|
input.elm.addEventListener('paste', this.onPaste)
|
||||||
input.addEventListener('keyup', this.onKeyUp)
|
input.elm.addEventListener('keyup', this.onKeyUp)
|
||||||
input.addEventListener('keydown', this.onKeyDown)
|
input.elm.addEventListener('keydown', this.onKeyDown)
|
||||||
input.addEventListener('click', this.onClickInput)
|
input.elm.addEventListener('click', this.onClickInput)
|
||||||
input.addEventListener('transitionend', this.onTransition)
|
input.elm.addEventListener('transitionend', this.onTransition)
|
||||||
input.addEventListener('input', this.onInput)
|
input.elm.addEventListener('input', this.onInput)
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
const { input } = this
|
const { input } = this
|
||||||
if (input) {
|
if (input) {
|
||||||
input.removeEventListener('blur', this.onBlur)
|
input.elm.removeEventListener('blur', this.onBlur)
|
||||||
input.removeEventListener('focus', this.onFocus)
|
input.elm.removeEventListener('focus', this.onFocus)
|
||||||
input.removeEventListener('paste', this.onPaste)
|
input.elm.removeEventListener('paste', this.onPaste)
|
||||||
input.removeEventListener('keyup', this.onKeyUp)
|
input.elm.removeEventListener('keyup', this.onKeyUp)
|
||||||
input.removeEventListener('keydown', this.onKeyDown)
|
input.elm.removeEventListener('keydown', this.onKeyDown)
|
||||||
input.removeEventListener('click', this.onClickInput)
|
input.elm.removeEventListener('click', this.onClickInput)
|
||||||
input.removeEventListener('transitionend', this.onTransition)
|
input.elm.removeEventListener('transitionend', this.onTransition)
|
||||||
input.removeEventListener('input', this.onInput)
|
input.elm.removeEventListener('input', this.onInput)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
showSuggestions: function (newValue) {
|
showSuggestions: function (newValue) {
|
||||||
this.$emit('shown', newValue)
|
this.$emit('shown', newValue)
|
||||||
},
|
|
||||||
textAtCaret: async function (newWord) {
|
|
||||||
const firstchar = newWord.charAt(0)
|
|
||||||
this.suggestions = []
|
|
||||||
if (newWord === firstchar) return
|
|
||||||
const matchedSuggestions = await this.suggest(newWord)
|
|
||||||
// Async: cancel if textAtCaret has changed during wait
|
|
||||||
if (this.textAtCaret !== newWord) return
|
|
||||||
if (matchedSuggestions.length <= 0) return
|
|
||||||
this.suggestions = take(matchedSuggestions, 5)
|
|
||||||
.map(({ imageUrl, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
img: imageUrl || ''
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
suggestions (newValue) {
|
|
||||||
this.$nextTick(this.resize)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
focusPickerInput () {
|
|
||||||
const pickerEl = this.$refs.picker.$el
|
|
||||||
if (!pickerEl) return
|
|
||||||
const pickerInput = pickerEl.querySelector('input')
|
|
||||||
if (pickerInput) pickerInput.focus()
|
|
||||||
},
|
|
||||||
triggerShowPicker () {
|
triggerShowPicker () {
|
||||||
this.showPicker = true
|
this.showPicker = true
|
||||||
this.$refs.picker.startEmojiLoad()
|
this.$refs.picker.startEmojiLoad()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
this.focusPickerInput()
|
|
||||||
})
|
})
|
||||||
// This temporarily disables "click outside" handler
|
// This temporarily disables "click outside" handler
|
||||||
// since external trigger also means click originates
|
// since external trigger also means click originates
|
||||||
@@ -216,12 +206,11 @@ const EmojiInput = {
|
|||||||
}, 0)
|
}, 0)
|
||||||
},
|
},
|
||||||
togglePicker () {
|
togglePicker () {
|
||||||
this.input.focus()
|
this.input.elm.focus()
|
||||||
this.showPicker = !this.showPicker
|
this.showPicker = !this.showPicker
|
||||||
if (this.showPicker) {
|
if (this.showPicker) {
|
||||||
this.scrollIntoView()
|
this.scrollIntoView()
|
||||||
this.$refs.picker.startEmojiLoad()
|
this.$refs.picker.startEmojiLoad()
|
||||||
this.$nextTick(this.focusPickerInput)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
replace (replacement) {
|
replace (replacement) {
|
||||||
@@ -262,13 +251,13 @@ const EmojiInput = {
|
|||||||
this.$emit('input', newValue)
|
this.$emit('input', newValue)
|
||||||
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
|
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
|
||||||
if (!keepOpen) {
|
if (!keepOpen) {
|
||||||
this.input.focus()
|
this.input.elm.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
// Re-focus inputbox after clicking suggestion
|
// Re-focus inputbox after clicking suggestion
|
||||||
// Set selection right after the replacement instead of the very end
|
// Set selection right after the replacement instead of the very end
|
||||||
this.input.setSelectionRange(position, position)
|
this.input.elm.setSelectionRange(position, position)
|
||||||
this.caret = position
|
this.caret = position
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -285,9 +274,9 @@ const EmojiInput = {
|
|||||||
|
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
// Re-focus inputbox after clicking suggestion
|
// Re-focus inputbox after clicking suggestion
|
||||||
this.input.focus()
|
this.input.elm.focus()
|
||||||
// Set selection right after the replacement instead of the very end
|
// Set selection right after the replacement instead of the very end
|
||||||
this.input.setSelectionRange(position, position)
|
this.input.elm.setSelectionRange(position, position)
|
||||||
this.caret = position
|
this.caret = position
|
||||||
})
|
})
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -349,7 +338,7 @@ const EmojiInput = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const { offsetHeight } = this.input
|
const { offsetHeight } = this.input.elm
|
||||||
const { picker } = this.$refs
|
const { picker } = this.$refs
|
||||||
const pickerBottom = picker.$el.getBoundingClientRect().bottom
|
const pickerBottom = picker.$el.getBoundingClientRect().bottom
|
||||||
if (pickerBottom > window.innerHeight) {
|
if (pickerBottom > window.innerHeight) {
|
||||||
@@ -414,8 +403,8 @@ const EmojiInput = {
|
|||||||
|
|
||||||
// Scroll the input element to the position of the cursor
|
// Scroll the input element to the position of the cursor
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.input.blur()
|
this.input.elm.blur()
|
||||||
this.input.focus()
|
this.input.elm.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Disable suggestions hotkeys if suggestions are hidden
|
// Disable suggestions hotkeys if suggestions are hidden
|
||||||
@@ -444,7 +433,7 @@ const EmojiInput = {
|
|||||||
// de-focuses the element (i.e. default browser behavior)
|
// de-focuses the element (i.e. default browser behavior)
|
||||||
if (key === 'Escape') {
|
if (key === 'Escape') {
|
||||||
if (!this.temporarilyHideSuggestions) {
|
if (!this.temporarilyHideSuggestions) {
|
||||||
this.input.focus()
|
this.input.elm.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +469,7 @@ const EmojiInput = {
|
|||||||
if (!panel) return
|
if (!panel) return
|
||||||
const picker = this.$refs.picker.$el
|
const picker = this.$refs.picker.$el
|
||||||
const panelBody = this.$refs['panel-body']
|
const panelBody = this.$refs['panel-body']
|
||||||
const { offsetHeight, offsetTop } = this.input
|
const { offsetHeight, offsetTop } = this.input.elm
|
||||||
const offsetBottom = offsetTop + offsetHeight
|
const offsetBottom = offsetTop + offsetHeight
|
||||||
|
|
||||||
this.setPlacement(panelBody, panel, offsetBottom)
|
this.setPlacement(panelBody, panel, offsetBottom)
|
||||||
@@ -494,7 +483,7 @@ const EmojiInput = {
|
|||||||
|
|
||||||
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
|
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
|
||||||
target.style.top = 'auto'
|
target.style.top = 'auto'
|
||||||
target.style.bottom = this.input.offsetHeight + 'px'
|
target.style.bottom = this.input.elm.offsetHeight + 'px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
overflowsBottom (el) {
|
overflowsBottom (el) {
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="root"
|
|
||||||
v-click-outside="onClickOutside"
|
v-click-outside="onClickOutside"
|
||||||
class="emoji-input"
|
class="emoji-input"
|
||||||
:class="{ 'with-picker': !hideEmojiButton }"
|
:class="{ 'with-picker': !hideEmojiButton }"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<template v-if="enableEmojiPicker">
|
<template v-if="enableEmojiPicker">
|
||||||
<button
|
<div
|
||||||
v-if="!hideEmojiButton"
|
v-if="!hideEmojiButton"
|
||||||
class="button-unstyled emoji-picker-icon"
|
class="emoji-picker-icon"
|
||||||
type="button"
|
|
||||||
@click.prevent="togglePicker"
|
@click.prevent="togglePicker"
|
||||||
>
|
>
|
||||||
<FAIcon :icon="['far', 'smile-beam']" />
|
<FAIcon :icon="['far', 'smile-beam']" />
|
||||||
</button>
|
</div>
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
v-if="enableEmojiPicker"
|
v-if="enableEmojiPicker"
|
||||||
ref="picker"
|
ref="picker"
|
||||||
@@ -39,7 +37,7 @@
|
|||||||
v-for="(suggestion, index) in suggestions"
|
v-for="(suggestion, index) in suggestions"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="autocomplete-item"
|
class="autocomplete-item"
|
||||||
:class="{ highlighted: index === highlighted }"
|
:class="{ highlighted: suggestion.highlighted }"
|
||||||
@click.stop.prevent="onClick($event, suggestion)"
|
@click.stop.prevent="onClick($event, suggestion)"
|
||||||
>
|
>
|
||||||
<span class="image">
|
<span class="image">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { debounce } from 'lodash'
|
||||||
/**
|
/**
|
||||||
* suggest - generates a suggestor function to be used by emoji-input
|
* suggest - generates a suggestor function to be used by emoji-input
|
||||||
* data: object providing source information for specific types of suggestions:
|
* data: object providing source information for specific types of suggestions:
|
||||||
@@ -10,19 +11,19 @@
|
|||||||
* doesn't support user linking you can just provide only emoji.
|
* doesn't support user linking you can just provide only emoji.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default data => {
|
const debounceUserSearch = debounce((data, input) => {
|
||||||
const emojiCurry = suggestEmoji(data.emoji)
|
data.updateUsersList(input)
|
||||||
const usersCurry = data.store && suggestUsers(data.store)
|
}, 500)
|
||||||
return input => {
|
|
||||||
|
export default data => input => {
|
||||||
const firstChar = input[0]
|
const firstChar = input[0]
|
||||||
if (firstChar === ':' && data.emoji) {
|
if (firstChar === ':' && data.emoji) {
|
||||||
return emojiCurry(input)
|
return suggestEmoji(data.emoji)(input)
|
||||||
}
|
}
|
||||||
if (firstChar === '@' && usersCurry) {
|
if (firstChar === '@' && data.users) {
|
||||||
return usersCurry(input)
|
return suggestUsers(data)(input)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const suggestEmoji = emojis => input => {
|
export const suggestEmoji = emojis => input => {
|
||||||
@@ -56,46 +57,19 @@ export const suggestEmoji = emojis => input => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const suggestUsers = ({ dispatch, state }) => {
|
export const suggestUsers = data => input => {
|
||||||
// Keep some persistent values in closure, most importantly for the
|
|
||||||
// custom debounce to work. Lodash debounce does not return a promise.
|
|
||||||
let suggestions = []
|
|
||||||
let previousQuery = ''
|
|
||||||
let timeout = null
|
|
||||||
let cancelUserSearch = null
|
|
||||||
|
|
||||||
const userSearch = (query) => dispatch('searchUsers', { query })
|
|
||||||
const debounceUserSearch = (query) => {
|
|
||||||
cancelUserSearch && cancelUserSearch()
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
userSearch(query).then(resolve).catch(reject)
|
|
||||||
}, 300)
|
|
||||||
cancelUserSearch = () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
resolve([])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return async input => {
|
|
||||||
const noPrefix = input.toLowerCase().substr(1)
|
const noPrefix = input.toLowerCase().substr(1)
|
||||||
if (previousQuery === noPrefix) return suggestions
|
const users = data.users
|
||||||
|
|
||||||
suggestions = []
|
const newUsers = users.filter(
|
||||||
previousQuery = noPrefix
|
|
||||||
// Fetch more and wait, don't fetch if there's the 2nd @ because
|
|
||||||
// the backend user search can't deal with it.
|
|
||||||
// Reference semantics make it so that we get the updated data after
|
|
||||||
// the await.
|
|
||||||
if (!noPrefix.includes('@')) {
|
|
||||||
await debounceUserSearch(noPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSuggestions = state.users.users.filter(
|
|
||||||
user =>
|
user =>
|
||||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||||
user.name.toLowerCase().startsWith(noPrefix)
|
user.name.toLowerCase().startsWith(noPrefix)
|
||||||
|
|
||||||
|
/* taking only 20 results so that sorting is a bit cheaper, we display
|
||||||
|
* only 5 anyway. could be inaccurate, but we ideally we should query
|
||||||
|
* backend anyway
|
||||||
|
*/
|
||||||
).slice(0, 20).sort((a, b) => {
|
).slice(0, 20).sort((a, b) => {
|
||||||
let aScore = 0
|
let aScore = 0
|
||||||
let bScore = 0
|
let bScore = 0
|
||||||
@@ -116,15 +90,17 @@ export const suggestUsers = ({ dispatch, state }) => {
|
|||||||
|
|
||||||
return diff + nameAlphabetically + screenNameAlphabetically
|
return diff + nameAlphabetically + screenNameAlphabetically
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
}).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
|
}).map(({ screen_name, name, profile_image_url_original }) => ({
|
||||||
displayText: screen_name_ui,
|
displayText: screen_name,
|
||||||
detailText: name,
|
detailText: name,
|
||||||
imageUrl: profile_image_url_original,
|
imageUrl: profile_image_url_original,
|
||||||
replacement: '@' + screen_name + ' '
|
replacement: '@' + screen_name + ' '
|
||||||
}))
|
}))
|
||||||
/* eslint-enable camelcase */
|
|
||||||
|
|
||||||
suggestions = newSuggestions || []
|
// BE search users to get more comprehensive results
|
||||||
return suggestions
|
if (data.updateUsersList) {
|
||||||
|
debounceUserSearch(data, noPrefix)
|
||||||
}
|
}
|
||||||
|
return newUsers
|
||||||
|
/* eslint-enable camelcase */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
:users="accountsForEmoji[reaction.name]"
|
:users="accountsForEmoji[reaction.name]"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="emoji-reaction btn button-default"
|
class="emoji-reaction btn btn-default"
|
||||||
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
||||||
@click="emojiOnClick(reaction.name, $event)"
|
@click="emojiOnClick(reaction.name, $event)"
|
||||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="import-export-container">
|
||||||
|
<slot name="before" />
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click="exportData"
|
||||||
|
>
|
||||||
|
{{ exportLabel }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click="importData"
|
||||||
|
>
|
||||||
|
{{ importLabel }}
|
||||||
|
</button>
|
||||||
|
<slot name="afterButtons" />
|
||||||
|
<p
|
||||||
|
v-if="importFailed"
|
||||||
|
class="alert error"
|
||||||
|
>
|
||||||
|
{{ importFailedText }}
|
||||||
|
</p>
|
||||||
|
<slot name="afterError" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'exportObject',
|
||||||
|
'importLabel',
|
||||||
|
'exportLabel',
|
||||||
|
'importFailedText',
|
||||||
|
'validator',
|
||||||
|
'onImport',
|
||||||
|
'onImportFailure'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
importFailed: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
exportData () {
|
||||||
|
const stringified = JSON.stringify(this.exportObject, null, 2) // Pretty-print and indent with 2 spaces
|
||||||
|
|
||||||
|
// Create an invisible link with a data url and simulate a click
|
||||||
|
const e = document.createElement('a')
|
||||||
|
e.setAttribute('download', 'pleroma_theme.json')
|
||||||
|
e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified))
|
||||||
|
e.style.display = 'none'
|
||||||
|
|
||||||
|
document.body.appendChild(e)
|
||||||
|
e.click()
|
||||||
|
document.body.removeChild(e)
|
||||||
|
},
|
||||||
|
importData () {
|
||||||
|
this.importFailed = false
|
||||||
|
const filePicker = document.createElement('input')
|
||||||
|
filePicker.setAttribute('type', 'file')
|
||||||
|
filePicker.setAttribute('accept', '.json')
|
||||||
|
|
||||||
|
filePicker.addEventListener('change', event => {
|
||||||
|
if (event.target.files[0]) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = ({ target }) => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(target.result)
|
||||||
|
const valid = this.validator(parsed)
|
||||||
|
if (valid) {
|
||||||
|
this.onImport(parsed)
|
||||||
|
} else {
|
||||||
|
this.importFailed = true
|
||||||
|
// this.onImportFailure(valid)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// This will happen both if there is a JSON syntax error or the theme is missing components
|
||||||
|
this.importFailed = true
|
||||||
|
// this.onImportFailure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsText(event.target.files[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.body.appendChild(filePicker)
|
||||||
|
filePicker.click()
|
||||||
|
document.body.removeChild(filePicker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.import-export-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@click="process"
|
@click="process"
|
||||||
>
|
>
|
||||||
{{ exportButtonLabel }}
|
{{ exportButtonLabel }}
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import {
|
|||||||
faBookmark,
|
faBookmark,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faThumbtack,
|
faThumbtack,
|
||||||
faShareAlt,
|
faShareAlt
|
||||||
faExternalLinkAlt
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {
|
import {
|
||||||
faBookmark as faBookmarkReg,
|
faBookmark as faBookmarkReg
|
||||||
faFlag
|
|
||||||
} from '@fortawesome/free-regular-svg-icons'
|
} from '@fortawesome/free-regular-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@@ -19,9 +17,7 @@ library.add(
|
|||||||
faBookmarkReg,
|
faBookmarkReg,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faThumbtack,
|
faThumbtack,
|
||||||
faShareAlt,
|
faShareAlt
|
||||||
faExternalLinkAlt,
|
|
||||||
faFlag
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const ExtraButtons = {
|
const ExtraButtons = {
|
||||||
@@ -68,9 +64,6 @@ const ExtraButtons = {
|
|||||||
this.$store.dispatch('unbookmark', { id: this.status.id })
|
this.$store.dispatch('unbookmark', { id: this.status.id })
|
||||||
.then(() => this.$emit('onSuccess'))
|
.then(() => this.$emit('onSuccess'))
|
||||||
.catch(err => this.$emit('onError', err.error.error))
|
.catch(err => this.$emit('onError', err.error.error))
|
||||||
},
|
|
||||||
reportStatus () {
|
|
||||||
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<Popover
|
<Popover
|
||||||
class="ExtraButtons"
|
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="top"
|
placement="top"
|
||||||
:offset="{ y: 5 }"
|
class="extra-button-popover"
|
||||||
:bound-to="{ x: 'container' }"
|
:bound-to="{ x: 'container' }"
|
||||||
remove-padding
|
|
||||||
>
|
>
|
||||||
<template v-slot:content="{close}">
|
<div
|
||||||
|
slot="content"
|
||||||
|
slot-scope="{close}"
|
||||||
|
>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<button
|
<button
|
||||||
v-if="canMute && !status.thread_muted"
|
v-if="canMute && !status.thread_muted"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="muteConversation"
|
@click.prevent="muteConversation"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="canMute && status.thread_muted"
|
v-if="canMute && status.thread_muted"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="unmuteConversation"
|
@click.prevent="unmuteConversation"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!status.pinned && canPin"
|
v-if="!status.pinned && canPin"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="pinStatus"
|
@click.prevent="pinStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="status.pinned && canPin"
|
v-if="status.pinned && canPin"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="unpinStatus"
|
@click.prevent="unpinStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!status.bookmarked"
|
v-if="!status.bookmarked"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="bookmarkStatus"
|
@click.prevent="bookmarkStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="status.bookmarked"
|
v-if="status.bookmarked"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="unbookmarkStatus"
|
@click.prevent="unbookmarkStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="canDelete"
|
v-if="canDelete"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="deleteStatus"
|
@click.prevent="deleteStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
/><span>{{ $t("status.delete") }}</span>
|
/><span>{{ $t("status.delete") }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="dropdown-item dropdown-item-icon"
|
||||||
@click.prevent="copyLink"
|
@click.prevent="copyLink"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
@@ -94,38 +95,14 @@
|
|||||||
icon="share-alt"
|
icon="share-alt"
|
||||||
/><span>{{ $t("status.copy_link") }}</span>
|
/><span>{{ $t("status.copy_link") }}</span>
|
||||||
</button>
|
</button>
|
||||||
<a
|
|
||||||
v-if="!status.is_local"
|
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
|
||||||
title="Source"
|
|
||||||
:href="status.external_url"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
fixed-width
|
|
||||||
icon="external-link-alt"
|
|
||||||
/><span>{{ $t("status.external_source") }}</span>
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
|
||||||
@click.prevent="reportStatus"
|
|
||||||
@click="close"
|
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
fixed-width
|
|
||||||
:icon="['far', 'flag']"
|
|
||||||
/><span>{{ $t("user_card.report") }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-slot:trigger>
|
<span slot="trigger">
|
||||||
<button class="button-unstyled popover-trigger">
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="ExtraButtons fa-scale-110 fa-old-padding"
|
||||||
icon="ellipsis-h"
|
icon="ellipsis-h"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
</template>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -135,20 +112,13 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.ExtraButtons {
|
.ExtraButtons {
|
||||||
/* override of popover internal stuff */
|
cursor: pointer;
|
||||||
.popover-trigger-button {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-trigger {
|
|
||||||
position: static;
|
position: static;
|
||||||
padding: 10px;
|
|
||||||
margin: -10px;
|
|
||||||
|
|
||||||
&:hover .svg-inline--fa {
|
&:hover,
|
||||||
|
.extra-button-popover.open & {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ const FavoriteButton = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
classes () {
|
||||||
|
return {
|
||||||
|
'-favorited': this.status.favorited
|
||||||
|
}
|
||||||
|
},
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="FavoriteButton">
|
<div v-if="loggedIn">
|
||||||
<button
|
|
||||||
v-if="loggedIn"
|
|
||||||
class="button-unstyled interactive"
|
|
||||||
:class="status.favorited && '-favorited'"
|
|
||||||
:title="$t('tool_tip.favorite')"
|
|
||||||
@click.prevent="favorite()"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
:class="classes"
|
||||||
|
class="FavoriteButton fa-scale-110 fa-old-padding -interactive"
|
||||||
|
:title="$t('tool_tip.favorite')"
|
||||||
:icon="[status.favorited ? 'fas' : 'far', 'star']"
|
:icon="[status.favorited ? 'fas' : 'far', 'star']"
|
||||||
:spin="animated"
|
:spin="animated"
|
||||||
|
@click.prevent="favorite()"
|
||||||
/>
|
/>
|
||||||
</button>
|
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
|
||||||
<span v-else>
|
</div>
|
||||||
|
<div v-else>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
:class="classes"
|
||||||
|
class="FavoriteButton fa-scale-110 fa-old-padding"
|
||||||
:title="$t('tool_tip.favorite')"
|
:title="$t('tool_tip.favorite')"
|
||||||
:icon="['far', 'star']"
|
:icon="['far', 'star']"
|
||||||
/>
|
/>
|
||||||
</span>
|
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
|
||||||
<span
|
|
||||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
|
||||||
class="action-counter"
|
|
||||||
>
|
|
||||||
{{ status.fave_num }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,28 +27,19 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.FavoriteButton {
|
.FavoriteButton {
|
||||||
display: flex;
|
&.-interactive {
|
||||||
|
cursor: pointer;
|
||||||
> :first-child {
|
|
||||||
padding: 10px;
|
|
||||||
margin: -10px -8px -10px -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-counter {
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.interactive {
|
|
||||||
.svg-inline--fa {
|
|
||||||
animation-duration: 0.6s;
|
animation-duration: 0.6s;
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .svg-inline--fa,
|
&:hover {
|
||||||
&.-favorited .svg-inline--fa {
|
|
||||||
color: $fallback--cOrange;
|
color: $fallback--cOrange;
|
||||||
color: var(--cOrange, $fallback--cOrange);
|
color: var(--cOrange, $fallback--cOrange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.-favorited {
|
||||||
|
color: $fallback--cOrange;
|
||||||
|
color: var(--cOrange, $fallback--cOrange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
|
||||||
|
|
||||||
const FeaturesPanel = {
|
const FeaturesPanel = {
|
||||||
computed: {
|
computed: {
|
||||||
shout: function () { return this.$store.state.instance.shoutAvailable },
|
chat: function () { return this.$store.state.instance.chatAvailable },
|
||||||
pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
|
pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
|
||||||
gopher: function () { return this.$store.state.instance.gopherAvailable },
|
gopher: function () { return this.$store.state.instance.gopherAvailable },
|
||||||
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
|
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
|
||||||
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
|
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
|
||||||
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
|
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
|
||||||
textlimit: function () { return this.$store.state.instance.textlimit },
|
textlimit: function () { return this.$store.state.instance.textlimit }
|
||||||
uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body features-panel">
|
<div class="panel-body features-panel">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="shout">
|
<li v-if="chat">
|
||||||
{{ $t('features_panel.shout') }}
|
{{ $t('features_panel.chat') }}
|
||||||
</li>
|
</li>
|
||||||
<li v-if="pleromaChatMessages">
|
<li v-if="pleromaChatMessages">
|
||||||
{{ $t('features_panel.pleroma_chat_messages') }}
|
{{ $t('features_panel.pleroma_chat_messages') }}
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>{{ $t('features_panel.scope_options') }}</li>
|
<li>{{ $t('features_panel.scope_options') }}</li>
|
||||||
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
|
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
|
||||||
<li>{{ $t('features_panel.upload_limit') }} = {{ uploadlimit.num }} {{ $t('upload.file_size_units.' + uploadlimit.unit) }}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import RuffleService from '../../services/ruffle_service/ruffle_service.js'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {
|
|
||||||
faStop,
|
|
||||||
faExclamationTriangle
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
library.add(
|
|
||||||
faStop,
|
|
||||||
faExclamationTriangle
|
|
||||||
)
|
|
||||||
|
|
||||||
const Flash = {
|
|
||||||
props: [ 'src' ],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
player: false, // can be true, "hidden", false. hidden = element exists
|
|
||||||
loaded: false,
|
|
||||||
ruffleInstance: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openPlayer () {
|
|
||||||
if (this.player) return // prevent double-loading, or re-loading on failure
|
|
||||||
this.player = 'hidden'
|
|
||||||
RuffleService.getRuffle().then((ruffle) => {
|
|
||||||
const player = ruffle.newest().createPlayer()
|
|
||||||
player.config = {
|
|
||||||
letterbox: 'on'
|
|
||||||
}
|
|
||||||
const container = this.$refs.container
|
|
||||||
container.appendChild(player)
|
|
||||||
player.style.width = '100%'
|
|
||||||
player.style.height = '100%'
|
|
||||||
player.load(this.src).then(() => {
|
|
||||||
this.player = true
|
|
||||||
}).catch((e) => {
|
|
||||||
console.error('Error loading ruffle', e)
|
|
||||||
this.player = 'error'
|
|
||||||
})
|
|
||||||
this.ruffleInstance = player
|
|
||||||
})
|
|
||||||
},
|
|
||||||
closePlayer () {
|
|
||||||
console.log(this.ruffleInstance)
|
|
||||||
this.ruffleInstance.remove()
|
|
||||||
this.player = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Flash
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="Flash">
|
|
||||||
<div
|
|
||||||
v-if="player === true || player === 'hidden'"
|
|
||||||
ref="container"
|
|
||||||
class="player"
|
|
||||||
:class="{ hidden: player === 'hidden' }"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
v-if="player !== true"
|
|
||||||
class="button-unstyled placeholder"
|
|
||||||
@click="openPlayer"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="player === 'hidden'"
|
|
||||||
class="label"
|
|
||||||
>
|
|
||||||
{{ $t('general.loading') }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="player === 'error'"
|
|
||||||
class="label"
|
|
||||||
>
|
|
||||||
{{ $t('general.flash_fail') }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="label"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{{ $t('general.flash_content') }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<FAIcon icon="exclamation-triangle" />
|
|
||||||
{{ $t('general.flash_security') }}
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="player"
|
|
||||||
class="button-unstyled hider"
|
|
||||||
@click="closePlayer"
|
|
||||||
>
|
|
||||||
<FAIcon icon="stop" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./flash.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../_variables.scss';
|
|
||||||
.Flash {
|
|
||||||
width: 100%;
|
|
||||||
height: 260px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.player {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hider {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
text-align: center;
|
|
||||||
flex: 1 1 0;
|
|
||||||
line-height: 1.2;
|
|
||||||
white-space: normal;
|
|
||||||
word-wrap: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
visibility: 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="btn button-default follow-button"
|
class="btn btn-default follow-button"
|
||||||
:class="{ toggled: isPressed }"
|
:class="{ toggled: isPressed }"
|
||||||
:disabled="inProgress"
|
:disabled="inProgress"
|
||||||
:title="title"
|
:title="title"
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<basic-user-card :user="user">
|
<basic-user-card :user="user">
|
||||||
<div class="follow-request-card-content-container">
|
<div class="follow-request-card-content-container">
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@click="approveUser"
|
@click="approveUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.approve') }}
|
{{ $t('user_card.approve') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@click="denyUser"
|
@click="denyUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.deny') }}
|
{{ $t('user_card.deny') }}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { set } from 'vue'
|
import { set } from 'vue'
|
||||||
import Select from '../select/select.vue'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faChevronDown
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faChevronDown
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
Select
|
|
||||||
},
|
|
||||||
props: [
|
props: [
|
||||||
'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
|
'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22,7 +22,12 @@
|
|||||||
class="opt-l"
|
class="opt-l"
|
||||||
:for="name + '-o'"
|
:for="name + '-o'"
|
||||||
/>
|
/>
|
||||||
<Select
|
<label
|
||||||
|
:for="name + '-font-switcher'"
|
||||||
|
class="select"
|
||||||
|
:disabled="!present"
|
||||||
|
>
|
||||||
|
<select
|
||||||
:id="name + '-font-switcher'"
|
:id="name + '-font-switcher'"
|
||||||
v-model="preset"
|
v-model="preset"
|
||||||
:disabled="!present"
|
:disabled="!present"
|
||||||
@@ -35,7 +40,12 @@
|
|||||||
>
|
>
|
||||||
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
|
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</select>
|
||||||
|
<FAIcon
|
||||||
|
class="select-down-icon"
|
||||||
|
icon="chevron-down"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
v-if="isCustom"
|
v-if="isCustom"
|
||||||
:id="name"
|
:id="name"
|
||||||
@@ -55,8 +65,7 @@
|
|||||||
min-width: 10em;
|
min-width: 10em;
|
||||||
}
|
}
|
||||||
&.custom {
|
&.custom {
|
||||||
/* TODO Should make proper joiners... */
|
.select {
|
||||||
.font-switcher {
|
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,11 @@
|
|||||||
<div class="notice-message">
|
<div class="notice-message">
|
||||||
{{ $t(notice.messageKey, notice.messageArgs) }}
|
{{ $t(notice.messageKey, notice.messageArgs) }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
class="button-unstyled close-notice"
|
|
||||||
@click="closeNotice(notice)"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="times"
|
icon="times"
|
||||||
|
@click="closeNotice(notice)"
|
||||||
/>
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,7 +54,7 @@
|
|||||||
.global-error {
|
.global-error {
|
||||||
background-color: var(--alertPopupError, $fallback--cRed);
|
background-color: var(--alertPopupError, $fallback--cRed);
|
||||||
color: var(--alertPopupErrorText, $fallback--text);
|
color: var(--alertPopupErrorText, $fallback--text);
|
||||||
.svg-inline--fa {
|
i {
|
||||||
color: var(--alertPopupErrorText, $fallback--text);
|
color: var(--alertPopupErrorText, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,32 +62,17 @@
|
|||||||
.global-warning {
|
.global-warning {
|
||||||
background-color: var(--alertPopupWarning, $fallback--cOrange);
|
background-color: var(--alertPopupWarning, $fallback--cOrange);
|
||||||
color: var(--alertPopupWarningText, $fallback--text);
|
color: var(--alertPopupWarningText, $fallback--text);
|
||||||
.svg-inline--fa {
|
i {
|
||||||
color: var(--alertPopupWarningText, $fallback--text);
|
color: var(--alertPopupWarningText, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-success {
|
|
||||||
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-info {
|
.global-info {
|
||||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
background-color: var(--alertPopupNeutral, $fallback--fg);
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
color: var(--alertPopupNeutralText, $fallback--text);
|
||||||
.svg-inline--fa {
|
i {
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
color: var(--alertPopupNeutralText, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-notice {
|
|
||||||
padding-right: 0.2em;
|
|
||||||
.svg-inline--fa:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import Cropper from 'cropperjs'
|
|||||||
import 'cropperjs/dist/cropper.css'
|
import 'cropperjs/dist/cropper.css'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
faTimes,
|
||||||
faCircleNotch
|
faCircleNotch
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
faTimes,
|
||||||
faCircleNotch
|
faCircleNotch
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +53,8 @@ const ImageCropper = {
|
|||||||
cropper: undefined,
|
cropper: undefined,
|
||||||
dataUrl: undefined,
|
dataUrl: undefined,
|
||||||
filename: undefined,
|
filename: undefined,
|
||||||
submitting: false
|
submitting: false,
|
||||||
|
submitError: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -63,6 +66,9 @@ const ImageCropper = {
|
|||||||
},
|
},
|
||||||
cancelText () {
|
cancelText () {
|
||||||
return this.cancelButtonLabel || this.$t('image_cropper.cancel')
|
return this.cancelButtonLabel || this.$t('image_cropper.cancel')
|
||||||
|
},
|
||||||
|
submitErrorMsg () {
|
||||||
|
return this.submitError && this.submitError instanceof Error ? this.submitError.toString() : this.submitError
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -76,8 +82,12 @@ const ImageCropper = {
|
|||||||
},
|
},
|
||||||
submit (cropping = true) {
|
submit (cropping = true) {
|
||||||
this.submitting = true
|
this.submitting = true
|
||||||
|
this.avatarUploadError = null
|
||||||
this.submitHandler(cropping && this.cropper, this.file)
|
this.submitHandler(cropping && this.cropper, this.file)
|
||||||
.then(() => this.destroy())
|
.then(() => this.destroy())
|
||||||
|
.catch((err) => {
|
||||||
|
this.submitError = err
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.submitting = false
|
this.submitting = false
|
||||||
})
|
})
|
||||||
@@ -103,6 +113,9 @@ const ImageCropper = {
|
|||||||
reader.readAsDataURL(this.file)
|
reader.readAsDataURL(this.file)
|
||||||
this.$emit('changed', this.file, reader)
|
this.$emit('changed', this.file, reader)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
clearError () {
|
||||||
|
this.submitError = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
|||||||
@@ -11,21 +11,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="image-cropper-buttons-wrapper">
|
<div class="image-cropper-buttons-wrapper">
|
||||||
<button
|
<button
|
||||||
class="button-default btn"
|
class="btn"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
@click="submit()"
|
@click="submit()"
|
||||||
v-text="saveText"
|
v-text="saveText"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="button-default btn"
|
class="btn"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
@click="destroy"
|
@click="destroy"
|
||||||
v-text="cancelText"
|
v-text="cancelText"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="button-default btn"
|
class="btn"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
@click="submit(false)"
|
@click="submit(false)"
|
||||||
@@ -37,6 +37,17 @@
|
|||||||
icon="circle-notch"
|
icon="circle-notch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="submitError"
|
||||||
|
class="alert error"
|
||||||
|
>
|
||||||
|
{{ submitErrorMsg }}
|
||||||
|
<FAIcon
|
||||||
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
icon="times"
|
||||||
|
@click="clearError"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>
|
>
|
||||||
{{ submitButtonLabel }}
|
{{ submitButtonLabel }}
|
||||||
|
|||||||
@@ -3,35 +3,51 @@
|
|||||||
<label for="interface-language-switcher">
|
<label for="interface-language-switcher">
|
||||||
{{ $t('settings.interfaceLanguage') }}
|
{{ $t('settings.interfaceLanguage') }}
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<label
|
||||||
|
for="interface-language-switcher"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
id="interface-language-switcher"
|
id="interface-language-switcher"
|
||||||
v-model="language"
|
v-model="language"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="lang in languages"
|
v-for="(langCode, i) in languageCodes"
|
||||||
:key="lang.code"
|
:key="langCode"
|
||||||
:value="lang.code"
|
:value="langCode"
|
||||||
>
|
>
|
||||||
{{ lang.name }}
|
{{ languageNames[i] }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</select>
|
||||||
|
<FAIcon
|
||||||
|
class="select-down-icon"
|
||||||
|
icon="chevron-down"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import languagesObject from '../../i18n/messages'
|
import languagesObject from '../../i18n/messages'
|
||||||
import localeService from '../../services/locale/locale.service.js'
|
|
||||||
import ISO6391 from 'iso-639-1'
|
import ISO6391 from 'iso-639-1'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Select from '../select/select.vue'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faChevronDown
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faChevronDown
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
Select
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
languages () {
|
languageCodes () {
|
||||||
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
|
return languagesObject.languages
|
||||||
|
},
|
||||||
|
|
||||||
|
languageNames () {
|
||||||
|
return _.map(this.languageCodes, this.getLanguageName)
|
||||||
},
|
},
|
||||||
|
|
||||||
language: {
|
language: {
|
||||||
@@ -45,13 +61,11 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
getLanguageName (code) {
|
getLanguageName (code) {
|
||||||
const specialLanguageNames = {
|
const specialLanguageNames = {
|
||||||
'ja_easy': 'やさしいにほんご',
|
'ja': 'Japanese (日本語)',
|
||||||
'zh': '简体中文',
|
'ja_easy': 'Japanese (やさしいにほんご)',
|
||||||
'zh_Hant': '繁體中文'
|
'zh': 'Chinese (简体中文)'
|
||||||
}
|
}
|
||||||
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
|
return specialLanguageNames[code] || ISO6391.getName(code)
|
||||||
const browserLocale = localeService.internalToBrowserLocale(code)
|
|
||||||
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { mapGetters } from 'vuex'
|
|
||||||
|
|
||||||
const LinkPreview = {
|
const LinkPreview = {
|
||||||
name: 'LinkPreview',
|
name: 'LinkPreview',
|
||||||
props: [
|
props: [
|
||||||
@@ -17,20 +15,11 @@ const LinkPreview = {
|
|||||||
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
|
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
|
||||||
// as it makes sure to hide the image if somehow NSFW tagged preview can
|
// as it makes sure to hide the image if somehow NSFW tagged preview can
|
||||||
// exist.
|
// exist.
|
||||||
return this.card.image && !this.censored && this.size !== 'hide'
|
return this.card.image && !this.nsfw && this.size !== 'hide'
|
||||||
},
|
|
||||||
censored () {
|
|
||||||
return this.nsfw && this.hideNsfwConfig
|
|
||||||
},
|
},
|
||||||
useDescription () {
|
useDescription () {
|
||||||
return this.card.description && /\S/.test(this.card.description)
|
return this.card.description && /\S/.test(this.card.description)
|
||||||
},
|
}
|
||||||
hideNsfwConfig () {
|
|
||||||
return this.mergedConfig.hideNsfw
|
|
||||||
},
|
|
||||||
...mapGetters([
|
|
||||||
'mergedConfig'
|
|
||||||
])
|
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
if (this.useImage) {
|
if (this.useImage) {
|
||||||
|
|||||||
@@ -9,17 +9,12 @@
|
|||||||
<div
|
<div
|
||||||
v-if="useImage && imageLoaded"
|
v-if="useImage && imageLoaded"
|
||||||
class="card-image"
|
class="card-image"
|
||||||
|
:class="{ 'small-image': size === 'small' }"
|
||||||
>
|
>
|
||||||
<img :src="card.image">
|
<img :src="card.image">
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<span class="card-host faint">
|
<span class="card-host faint">{{ card.provider_name }}</span>
|
||||||
<span
|
|
||||||
v-if="censored"
|
|
||||||
class="nsfw-alert alert warning"
|
|
||||||
>{{ $t('status.nsfw') }}</span>
|
|
||||||
{{ card.provider_name }}
|
|
||||||
</span>
|
|
||||||
<h4 class="card-title">{{ card.title }}</h4>
|
<h4 class="card-title">{{ card.title }}</h4>
|
||||||
<p
|
<p
|
||||||
v-if="useDescription"
|
v-if="useDescription"
|
||||||
@@ -55,6 +50,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small-image {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-content {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
@@ -77,10 +76,6 @@
|
|||||||
max-height: calc(1.2em * 3 - 1px);
|
max-height: calc(1.2em * 3 - 1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nsfw-alert {
|
|
||||||
margin: 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<button
|
<button
|
||||||
:disabled="loggingIn"
|
:disabled="loggingIn"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('login.login') }}
|
{{ $t('login.login') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -73,21 +73,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes media-fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-image {
|
.modal-image {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
max-height: 90%;
|
max-height: 90%;
|
||||||
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
|
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
|
||||||
image-orientation: from-image; // NOTE: only FF supports this
|
image-orientation: from-image; // NOTE: only FF supports this
|
||||||
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-view-button-arrow {
|
.modal-view-button-arrow {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<label
|
<div
|
||||||
class="media-upload"
|
class="media-upload"
|
||||||
:class="{ disabled: disabled }"
|
:class="{ disabled: disabled }"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="label"
|
||||||
:title="$t('tool_tip.media_upload')"
|
:title="$t('tool_tip.media_upload')"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
@change="change"
|
@change="change"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./media_upload.js" ></script>
|
<script src="./media_upload.js" ></script>
|
||||||
@@ -32,6 +36,12 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.media-upload {
|
.media-upload {
|
||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,25 +23,23 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="login-bottom">
|
<div class="login-bottom">
|
||||||
<div>
|
<div>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
type="button"
|
|
||||||
@click.prevent="requireTOTP"
|
@click.prevent="requireTOTP"
|
||||||
>
|
>
|
||||||
{{ $t('login.enter_two_factor_code') }}
|
{{ $t('login.enter_two_factor_code') }}
|
||||||
</button>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
type="button"
|
|
||||||
@click.prevent="abortMFA"
|
@click.prevent="abortMFA"
|
||||||
>
|
>
|
||||||
{{ $t('general.cancel') }}
|
{{ $t('general.cancel') }}
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('general.verify') }}
|
{{ $t('general.verify') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -25,25 +25,23 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="login-bottom">
|
<div class="login-bottom">
|
||||||
<div>
|
<div>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
type="button"
|
|
||||||
@click.prevent="requireRecovery"
|
@click.prevent="requireRecovery"
|
||||||
>
|
>
|
||||||
{{ $t('login.enter_recovery_code') }}
|
{{ $t('login.enter_recovery_code') }}
|
||||||
</button>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
type="button"
|
|
||||||
@click.prevent="abortMFA"
|
@click.prevent="abortMFA"
|
||||||
>
|
>
|
||||||
{{ $t('general.cancel') }}
|
{{ $t('general.cancel') }}
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('general.verify') }}
|
{{ $t('general.verify') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
@click="scrollToTop()"
|
@click="scrollToTop()"
|
||||||
>
|
>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<button
|
<a
|
||||||
class="button-unstyled mobile-nav-button"
|
href="#"
|
||||||
|
class="mobile-nav-button"
|
||||||
@click.stop.prevent="toggleMobileSidebar()"
|
@click.stop.prevent="toggleMobileSidebar()"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
v-if="unreadChatCount"
|
v-if="unreadChatCount"
|
||||||
class="alert-dot"
|
class="alert-dot"
|
||||||
/>
|
/>
|
||||||
</button>
|
</a>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="!hideSitename"
|
v-if="!hideSitename"
|
||||||
class="site-name"
|
class="site-name"
|
||||||
@@ -32,9 +33,10 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="item right">
|
<div class="item right">
|
||||||
<button
|
<a
|
||||||
v-if="currentUser"
|
v-if="currentUser"
|
||||||
class="button-unstyled mobile-nav-button"
|
class="mobile-nav-button"
|
||||||
|
href="#"
|
||||||
@click.stop.prevent="openMobileNotifications()"
|
@click.stop.prevent="openMobileNotifications()"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
v-if="unseenNotificationsCount"
|
v-if="unseenNotificationsCount"
|
||||||
class="alert-dot"
|
class="alert-dot"
|
||||||
/>
|
/>
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isLoggedIn">
|
<div v-if="isLoggedIn">
|
||||||
<button
|
<button
|
||||||
class="button-default new-status-button"
|
class="new-status-button"
|
||||||
:class="{ 'hidden': isHidden }"
|
:class="{ 'hidden': isHidden }"
|
||||||
@click="openPostForm"
|
@click="openPostForm"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
import DialogModal from '../dialog_modal/dialog_modal.vue'
|
import DialogModal from '../dialog_modal/dialog_modal.vue'
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
|
|
||||||
library.add(faChevronDown)
|
|
||||||
|
|
||||||
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
|
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
|
||||||
const STRIP_MEDIA = 'mrf_tag:media-strip'
|
const STRIP_MEDIA = 'mrf_tag:media-strip'
|
||||||
const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
|
const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
@show="setToggled(true)"
|
@show="setToggled(true)"
|
||||||
@close="setToggled(false)"
|
@close="setToggled(false)"
|
||||||
>
|
>
|
||||||
<template v-slot:content>
|
<div slot="content">
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<span v-if="user.is_local">
|
<span v-if="user.is_local">
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleRight("admin")"
|
@click="toggleRight("admin")"
|
||||||
>
|
>
|
||||||
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
|
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleRight("moderator")"
|
@click="toggleRight("moderator")"
|
||||||
>
|
>
|
||||||
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
|
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleActivationStatus()"
|
@click="toggleActivationStatus()"
|
||||||
>
|
>
|
||||||
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
|
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="deleteUserDialog(true)"
|
@click="deleteUserDialog(true)"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.admin_menu.delete_account') }}
|
{{ $t('user_card.admin_menu.delete_account') }}
|
||||||
@@ -47,109 +47,107 @@
|
|||||||
/>
|
/>
|
||||||
<span v-if="hasTagPolicy">
|
<span v-if="hasTagPolicy">
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.FORCE_NSFW)"
|
@click="toggleTag(tags.FORCE_NSFW)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.force_nsfw') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.force_nsfw') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.STRIP_MEDIA)"
|
@click="toggleTag(tags.STRIP_MEDIA)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.strip_media') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.strip_media') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.FORCE_UNLISTED)"
|
@click="toggleTag(tags.FORCE_UNLISTED)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.force_unlisted') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.force_unlisted') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.SANDBOX)"
|
@click="toggleTag(tags.SANDBOX)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.sandbox') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.sandbox') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="user.is_local"
|
v-if="user.is_local"
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
|
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="user.is_local"
|
v-if="user.is_local"
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
|
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.disable_any_subscription') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.disable_any_subscription') }}
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="user.is_local"
|
v-if="user.is_local"
|
||||||
class="button-default dropdown-item"
|
class="dropdown-item"
|
||||||
@click="toggleTag(tags.QUARANTINE)"
|
@click="toggleTag(tags.QUARANTINE)"
|
||||||
>
|
>
|
||||||
|
{{ $t('user_card.admin_menu.quarantine') }}
|
||||||
<span
|
<span
|
||||||
class="menu-checkbox"
|
class="menu-checkbox"
|
||||||
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
|
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
|
||||||
/>
|
/>
|
||||||
{{ $t('user_card.admin_menu.quarantine') }}
|
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-slot:trigger>
|
|
||||||
<button
|
<button
|
||||||
class="btn button-default btn-block moderation-tools-button"
|
slot="trigger"
|
||||||
|
class="btn btn-default btn-block"
|
||||||
:class="{ toggled }"
|
:class="{ toggled }"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.admin_menu.moderation') }}
|
{{ $t('user_card.admin_menu.moderation') }}
|
||||||
<FAIcon icon="chevron-down" />
|
|
||||||
</button>
|
</button>
|
||||||
</template>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
<portal to="modal">
|
<portal to="modal">
|
||||||
<DialogModal
|
<DialogModal
|
||||||
v-if="showDeleteUserDialog"
|
v-if="showDeleteUserDialog"
|
||||||
:on-cancel="deleteUserDialog.bind(this, false)"
|
:on-cancel="deleteUserDialog.bind(this, false)"
|
||||||
>
|
>
|
||||||
<template v-slot:header>
|
<template slot="header">
|
||||||
{{ $t('user_card.admin_menu.delete_user') }}
|
{{ $t('user_card.admin_menu.delete_user') }}
|
||||||
</template>
|
</template>
|
||||||
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
|
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
|
||||||
<template v-slot:footer>
|
<template slot="footer">
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@click="deleteUserDialog(false)"
|
@click="deleteUserDialog(false)"
|
||||||
>
|
>
|
||||||
{{ $t('general.cancel') }}
|
{{ $t('general.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn button-default danger"
|
class="btn btn-default danger"
|
||||||
@click="deleteUser()"
|
@click="deleteUser()"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.admin_menu.delete_user') }}
|
{{ $t('user_card.admin_menu.delete_user') }}
|
||||||
@@ -165,6 +163,25 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.menu-checkbox {
|
||||||
|
float: right;
|
||||||
|
min-width: 22px;
|
||||||
|
max-width: 22px;
|
||||||
|
min-height: 22px;
|
||||||
|
max-height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0px;
|
||||||
|
background-color: $fallback--fg;
|
||||||
|
background-color: var(--input, $fallback--fg);
|
||||||
|
box-shadow: 0px 0px 2px black inset;
|
||||||
|
box-shadow: var(--inputShadow);
|
||||||
|
|
||||||
|
&.menu-checkbox-checked::after {
|
||||||
|
content: '✓';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.moderation-tools-popover {
|
.moderation-tools-popover {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.trigger {
|
.trigger {
|
||||||
@@ -172,10 +189,4 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.moderation-tools-button {
|
|
||||||
svg,i {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="mute-card-content-container">
|
<div class="mute-card-content-container">
|
||||||
<button
|
<button
|
||||||
v-if="muted"
|
v-if="muted"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
:disabled="progress"
|
:disabled="progress"
|
||||||
@click="unmuteUser"
|
@click="unmuteUser"
|
||||||
>
|
>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
:disabled="progress"
|
:disabled="progress"
|
||||||
@click="muteUser"
|
@click="muteUser"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
|
import { timelineNames } from '../timeline_menu/timeline_menu.js'
|
||||||
import { mapState, mapGetters } from 'vuex'
|
import { mapState, mapGetters } from 'vuex'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
@@ -7,12 +7,10 @@ import {
|
|||||||
faGlobe,
|
faGlobe,
|
||||||
faBookmark,
|
faBookmark,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faChevronDown,
|
faHome,
|
||||||
faChevronUp,
|
|
||||||
faComments,
|
faComments,
|
||||||
faBell,
|
faBell,
|
||||||
faInfoCircle,
|
faInfoCircle
|
||||||
faStream
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
@@ -20,12 +18,10 @@ library.add(
|
|||||||
faGlobe,
|
faGlobe,
|
||||||
faBookmark,
|
faBookmark,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faChevronDown,
|
faHome,
|
||||||
faChevronUp,
|
|
||||||
faComments,
|
faComments,
|
||||||
faBell,
|
faBell,
|
||||||
faInfoCircle,
|
faInfoCircle
|
||||||
faStream
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const NavPanel = {
|
const NavPanel = {
|
||||||
@@ -34,20 +30,16 @@ const NavPanel = {
|
|||||||
this.$store.dispatch('startFetchingFollowRequests')
|
this.$store.dispatch('startFetchingFollowRequests')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
TimelineMenuContent
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showTimelines: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleTimelines () {
|
|
||||||
this.showTimelines = !this.showTimelines
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
|
onTimelineRoute () {
|
||||||
|
return !!timelineNames()[this.$route.name]
|
||||||
|
},
|
||||||
|
timelinesRoute () {
|
||||||
|
if (this.$store.state.interface.lastTimeline) {
|
||||||
|
return this.$store.state.interface.lastTimeline
|
||||||
|
}
|
||||||
|
return this.currentUser ? 'friends' : 'public-timeline'
|
||||||
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
currentUser: state => state.users.currentUser,
|
currentUser: state => state.users.currentUser,
|
||||||
followRequestCount: state => state.api.followRequests.length,
|
followRequestCount: state => state.api.followRequests.length,
|
||||||
|
|||||||
@@ -3,33 +3,19 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="currentUser || !privateMode">
|
<li v-if="currentUser || !privateMode">
|
||||||
<button
|
<router-link
|
||||||
class="button-unstyled menu-item"
|
:to="{ name: timelinesRoute }"
|
||||||
@click="toggleTimelines"
|
:class="onTimelineRoute && 'router-link-active'"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110"
|
class="fa-scale-110"
|
||||||
icon="stream"
|
icon="home"
|
||||||
/>{{ $t("nav.timelines") }}
|
/>{{ $t("nav.timelines") }}
|
||||||
<FAIcon
|
</router-link>
|
||||||
class="timelines-chevron"
|
|
||||||
fixed-width
|
|
||||||
:icon="showTimelines ? 'chevron-up' : 'chevron-down'"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-show="showTimelines"
|
|
||||||
class="timelines-background"
|
|
||||||
>
|
|
||||||
<TimelineMenuContent class="timelines" />
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentUser">
|
<li v-if="currentUser">
|
||||||
<router-link
|
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||||
class="menu-item"
|
|
||||||
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110"
|
class="fa-scale-110"
|
||||||
@@ -38,10 +24,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentUser && pleromaChatMessagesAvailable">
|
<li v-if="currentUser && pleromaChatMessagesAvailable">
|
||||||
<router-link
|
<router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
|
||||||
class="menu-item"
|
|
||||||
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-if="unreadChatCount"
|
v-if="unreadChatCount"
|
||||||
class="badge badge-notification"
|
class="badge badge-notification"
|
||||||
@@ -56,10 +39,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentUser && currentUser.locked">
|
<li v-if="currentUser && currentUser.locked">
|
||||||
<router-link
|
<router-link :to="{ name: 'friend-requests' }">
|
||||||
class="menu-item"
|
|
||||||
:to="{ name: 'friend-requests' }"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110"
|
class="fa-scale-110"
|
||||||
@@ -74,10 +54,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link
|
<router-link :to="{ name: 'about' }">
|
||||||
class="menu-item"
|
|
||||||
:to="{ name: 'about' }"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110"
|
class="fa-scale-110"
|
||||||
@@ -114,14 +91,14 @@
|
|||||||
border-color: var(--border, $fallback--border);
|
border-color: var(--border, $fallback--border);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&:first-child .menu-item {
|
&:first-child a {
|
||||||
border-top-right-radius: $fallback--panelRadius;
|
border-top-right-radius: $fallback--panelRadius;
|
||||||
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
border-top-left-radius: $fallback--panelRadius;
|
border-top-left-radius: $fallback--panelRadius;
|
||||||
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child .menu-item {
|
&:last-child a {
|
||||||
border-bottom-right-radius: $fallback--panelRadius;
|
border-bottom-right-radius: $fallback--panelRadius;
|
||||||
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
border-bottom-left-radius: $fallback--panelRadius;
|
border-bottom-left-radius: $fallback--panelRadius;
|
||||||
@@ -133,15 +110,13 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
align-items: stretch;
|
||||||
height: 3.5em;
|
height: 3.5em;
|
||||||
line-height: 3.5em;
|
line-height: 3.5em;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
width: 100%;
|
|
||||||
color: $fallback--link;
|
|
||||||
color: var(--link, $fallback--link);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $fallback--lightBg;
|
background-color: $fallback--lightBg;
|
||||||
@@ -171,25 +146,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timelines-chevron {
|
|
||||||
margin-left: 0.8em;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timelines-background {
|
|
||||||
padding: 0 0 0 0.6em;
|
|
||||||
background-color: $fallback--lightBg;
|
|
||||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
|
||||||
border-top: 1px solid;
|
|
||||||
border-color: $fallback--border;
|
|
||||||
border-color: var(--border, $fallback--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timelines {
|
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-scale-110 {
|
.fa-scale-110 {
|
||||||
margin-right: 0.8em;
|
margin-right: 0.8em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,17 @@
|
|||||||
>
|
>
|
||||||
<small>
|
<small>
|
||||||
<router-link :to="userProfileLink">
|
<router-link :to="userProfileLink">
|
||||||
{{ notification.from_profile.screen_name_ui }}
|
{{ notification.from_profile.screen_name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</small>
|
</small>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled unmute"
|
href="#"
|
||||||
|
class="unmute"
|
||||||
@click.prevent="toggleMute"
|
@click.prevent="toggleMute"
|
||||||
>
|
><FAIcon
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="eye-slash"
|
icon="eye-slash"
|
||||||
/>
|
/></a>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -54,14 +53,14 @@
|
|||||||
<bdi
|
<bdi
|
||||||
v-if="!!notification.from_profile.name_html"
|
v-if="!!notification.from_profile.name_html"
|
||||||
class="username"
|
class="username"
|
||||||
:title="'@'+notification.from_profile.screen_name_ui"
|
:title="'@'+notification.from_profile.screen_name"
|
||||||
v-html="notification.from_profile.name_html"
|
v-html="notification.from_profile.name_html"
|
||||||
/>
|
/>
|
||||||
<!-- eslint-enable vue/no-v-html -->
|
<!-- eslint-enable vue/no-v-html -->
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
class="username"
|
class="username"
|
||||||
:title="'@'+notification.from_profile.screen_name_ui"
|
:title="'@'+notification.from_profile.screen_name"
|
||||||
>{{ notification.from_profile.name }}</span>
|
>{{ notification.from_profile.name }}</span>
|
||||||
<span v-if="notification.type === 'like'">
|
<span v-if="notification.type === 'like'">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -133,16 +132,14 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<a
|
||||||
v-if="needMute"
|
v-if="needMute"
|
||||||
class="button-unstyled"
|
href="#"
|
||||||
@click.prevent="toggleMute"
|
@click.prevent="toggleMute"
|
||||||
>
|
><FAIcon
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="eye-slash"
|
icon="eye-slash"
|
||||||
/>
|
/></a>
|
||||||
</button>
|
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
||||||
@@ -152,7 +149,7 @@
|
|||||||
:to="userProfileLink"
|
:to="userProfileLink"
|
||||||
class="follow-name"
|
class="follow-name"
|
||||||
>
|
>
|
||||||
@{{ notification.from_profile.screen_name_ui }}
|
@{{ notification.from_profile.screen_name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'follow_request'"
|
v-if="notification.type === 'follow_request'"
|
||||||
@@ -177,7 +174,7 @@
|
|||||||
class="move-text"
|
class="move-text"
|
||||||
>
|
>
|
||||||
<router-link :to="targetUserProfileLink">
|
<router-link :to="targetUserProfileLink">
|
||||||
@{{ notification.target.screen_name_ui }}
|
@{{ notification.target.screen_name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Popover
|
|
||||||
trigger="click"
|
|
||||||
class="NotificationFilters"
|
|
||||||
placement="bottom"
|
|
||||||
:bound-to="{ x: 'container' }"
|
|
||||||
>
|
|
||||||
<template v-slot:content>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('likes')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.likes }"
|
|
||||||
/>{{ $t('settings.notification_visibility_likes') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('repeats')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.repeats }"
|
|
||||||
/>{{ $t('settings.notification_visibility_repeats') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('follows')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.follows }"
|
|
||||||
/>{{ $t('settings.notification_visibility_follows') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('mentions')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.mentions }"
|
|
||||||
/>{{ $t('settings.notification_visibility_mentions') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('emojiReactions')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.emojiReactions }"
|
|
||||||
/>{{ $t('settings.notification_visibility_emoji_reactions') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button-default dropdown-item"
|
|
||||||
@click="toggleNotificationFilter('moves')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="menu-checkbox"
|
|
||||||
:class="{ 'menu-checkbox-checked': filters.moves }"
|
|
||||||
/>{{ $t('settings.notification_visibility_moves') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:trigger>
|
|
||||||
<button class="button-unstyled">
|
|
||||||
<FAIcon icon="filter" />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Popover from '../popover/popover.vue'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import { faFilter } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
library.add(
|
|
||||||
faFilter
|
|
||||||
)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { Popover },
|
|
||||||
computed: {
|
|
||||||
filters () {
|
|
||||||
return this.$store.getters.mergedConfig.notificationVisibility
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleNotificationFilter (type) {
|
|
||||||
this.$store.dispatch('setOption', {
|
|
||||||
name: 'notificationVisibility',
|
|
||||||
value: {
|
|
||||||
...this.filters,
|
|
||||||
[type]: !this.filters[type]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
.NotificationFilters {
|
|
||||||
align-self: stretch;
|
|
||||||
|
|
||||||
> button {
|
|
||||||
font-size: 1.2em;
|
|
||||||
padding-left: 0.7em;
|
|
||||||
padding-right: 0.2em;
|
|
||||||
line-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import Notification from '../notification/notification.vue'
|
import Notification from '../notification/notification.vue'
|
||||||
import NotificationFilters from './notification_filters.vue'
|
|
||||||
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
|
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
|
||||||
import {
|
import {
|
||||||
notificationsFromStore,
|
notificationsFromStore,
|
||||||
filteredNotificationsFromStore,
|
filteredNotificationsFromStore,
|
||||||
unseenNotificationsFromStore
|
unseenNotificationsFromStore
|
||||||
} from '../../services/notification_utils/notification_utils.js'
|
} from '../../services/notification_utils/notification_utils.js'
|
||||||
import FaviconService from '../../services/favicon_service/favicon_service.js'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
@@ -18,10 +16,6 @@ library.add(
|
|||||||
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
|
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
|
||||||
|
|
||||||
const Notifications = {
|
const Notifications = {
|
||||||
components: {
|
|
||||||
Notification,
|
|
||||||
NotificationFilters
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
// Disables display of panel header
|
// Disables display of panel header
|
||||||
noHeading: Boolean,
|
noHeading: Boolean,
|
||||||
@@ -40,6 +34,11 @@ const Notifications = {
|
|||||||
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
|
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
const store = this.$store
|
||||||
|
const credentials = store.state.users.currentUser.credentials
|
||||||
|
notificationsFetcher.fetchAndUpdate({ store, credentials })
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mainClass () {
|
mainClass () {
|
||||||
return this.minimalMode ? '' : 'panel panel-default'
|
return this.minimalMode ? '' : 'panel panel-default'
|
||||||
@@ -70,13 +69,14 @@ const Notifications = {
|
|||||||
},
|
},
|
||||||
...mapGetters(['unreadChatCount'])
|
...mapGetters(['unreadChatCount'])
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Notification
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
unseenCountTitle (count) {
|
unseenCountTitle (count) {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
FaviconService.drawFaviconBadge()
|
|
||||||
this.$store.dispatch('setPageTitle', `(${count})`)
|
this.$store.dispatch('setPageTitle', `(${count})`)
|
||||||
} else {
|
} else {
|
||||||
FaviconService.clearFaviconBadge()
|
|
||||||
this.$store.dispatch('setPageTitle', '')
|
this.$store.dispatch('setPageTitle', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.Notifications {
|
.notifications {
|
||||||
&:not(.minimal) {
|
&:not(.minimal) {
|
||||||
// a bit of a hack to allow scrolling below notifications
|
// a bit of a hack to allow scrolling below notifications
|
||||||
padding-bottom: 15em;
|
padding-bottom: 15em;
|
||||||
@@ -11,10 +11,6 @@
|
|||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-footer {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -86,6 +82,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.follow-text, .move-text {
|
.follow-text, .move-text {
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{ minimal: minimalMode }"
|
:class="{ minimal: minimalMode }"
|
||||||
class="Notifications"
|
class="notifications"
|
||||||
>
|
>
|
||||||
<div :class="mainClass">
|
<div :class="mainClass">
|
||||||
<div
|
<div
|
||||||
@@ -15,14 +15,20 @@
|
|||||||
class="badge badge-notification unseen-count"
|
class="badge badge-notification unseen-count"
|
||||||
>{{ unseenCount }}</span>
|
>{{ unseenCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="error"
|
||||||
|
class="loadmore-error alert error"
|
||||||
|
@click.prevent
|
||||||
|
>
|
||||||
|
{{ $t('timeline.error_fetching') }}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="unseenCount"
|
v-if="unseenCount"
|
||||||
class="button-default read-button"
|
class="read-button"
|
||||||
@click.prevent="markAsSeen"
|
@click.prevent="markAsSeen"
|
||||||
>
|
>
|
||||||
{{ $t('notifications.read') }}
|
{{ $t('notifications.read') }}
|
||||||
</button>
|
</button>
|
||||||
<NotificationFilters />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div
|
<div
|
||||||
@@ -35,25 +41,25 @@
|
|||||||
<notification :notification="notification" />
|
<notification :notification="notification" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-footer notifications-footer">
|
<div class="panel-footer">
|
||||||
<div
|
<div
|
||||||
v-if="bottomedOut"
|
v-if="bottomedOut"
|
||||||
class="new-status-notification text-center faint"
|
class="new-status-notification text-center panel-footer faint"
|
||||||
>
|
>
|
||||||
{{ $t('notifications.no_more_notifications') }}
|
{{ $t('notifications.no_more_notifications') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<a
|
||||||
v-else-if="!loading"
|
v-else-if="!loading"
|
||||||
class="button-unstyled -link -fullwidth"
|
href="#"
|
||||||
@click.prevent="fetchOlderNotifications()"
|
@click.prevent="fetchOlderNotifications()"
|
||||||
>
|
>
|
||||||
<div class="new-status-notification text-center">
|
<div class="new-status-notification text-center panel-footer">
|
||||||
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
|
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</a>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="new-status-notification text-center"
|
class="new-status-notification text-center panel-footer"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="circle-notch"
|
icon="circle-notch"
|
||||||
|
|||||||
@@ -51,9 +51,9 @@
|
|||||||
<button
|
<button
|
||||||
:disabled="isPending"
|
:disabled="isPending"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn button-default btn-block"
|
class="btn btn-default btn-block"
|
||||||
>
|
>
|
||||||
{{ $t('settings.save') }}
|
{{ $t('general.submit') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,15 +42,14 @@
|
|||||||
:value="index"
|
:value="index"
|
||||||
>
|
>
|
||||||
<label class="option-vote">
|
<label class="option-vote">
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<div>{{ option.title }}</div>
|
||||||
<div v-html="option.title_html" />
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer faint">
|
<div class="footer faint">
|
||||||
<button
|
<button
|
||||||
v-if="!showResults"
|
v-if="!showResults"
|
||||||
class="btn button-default poll-vote-button"
|
class="btn btn-default poll-vote-button"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
@click="vote"
|
@click="vote"
|
||||||
@@ -58,12 +57,7 @@
|
|||||||
{{ $t('polls.vote') }}
|
{{ $t('polls.vote') }}
|
||||||
</button>
|
</button>
|
||||||
<div class="total">
|
<div class="total">
|
||||||
<template v-if="typeof poll.voters_count === 'number'">
|
{{ totalVotesCount }} {{ $t("polls.votes") }} ·
|
||||||
{{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }} ·
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }} ·
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
||||||
<Timeago
|
<Timeago
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import * as DateUtils from 'src/services/date_utils/date_utils.js'
|
import * as DateUtils from 'src/services/date_utils/date_utils.js'
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import Select from '../select/select.vue'
|
|
||||||
import {
|
import {
|
||||||
faTimes,
|
faTimes,
|
||||||
|
faChevronDown,
|
||||||
faPlus
|
faPlus
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faTimes,
|
faTimes,
|
||||||
|
faChevronDown,
|
||||||
faPlus
|
faPlus
|
||||||
)
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
Select
|
|
||||||
},
|
|
||||||
name: 'PollForm',
|
name: 'PollForm',
|
||||||
props: ['visible'],
|
props: ['visible'],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|||||||
@@ -21,17 +21,20 @@
|
|||||||
@keydown.enter.stop.prevent="nextOption(index)"
|
@keydown.enter.stop.prevent="nextOption(index)"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div
|
||||||
v-if="options.length > 2"
|
v-if="options.length > 2"
|
||||||
class="delete-option button-unstyled -hover-highlight"
|
class="icon-container"
|
||||||
@click="deleteOption(index)"
|
|
||||||
>
|
>
|
||||||
<FAIcon icon="times" />
|
<FAIcon
|
||||||
</button>
|
icon="times"
|
||||||
|
class="delete"
|
||||||
|
@click="deleteOption(index)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</div>
|
||||||
|
<a
|
||||||
v-if="options.length < maxOptions"
|
v-if="options.length < maxOptions"
|
||||||
class="add-option faint button-unstyled -hover-highlight"
|
class="add-option faint"
|
||||||
@click="addOption"
|
@click="addOption"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -40,25 +43,29 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{{ $t("polls.add_option") }}
|
{{ $t("polls.add_option") }}
|
||||||
</button>
|
</a>
|
||||||
<div class="poll-type-expiry">
|
<div class="poll-type-expiry">
|
||||||
<div
|
<div
|
||||||
class="poll-type"
|
class="poll-type"
|
||||||
:title="$t('polls.type')"
|
:title="$t('polls.type')"
|
||||||
>
|
>
|
||||||
<Select
|
<label
|
||||||
|
for="poll-type-selector"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
v-model="pollType"
|
v-model="pollType"
|
||||||
class="poll-type-select"
|
class="select"
|
||||||
unstyled="true"
|
|
||||||
@change="updatePollToParent"
|
@change="updatePollToParent"
|
||||||
>
|
>
|
||||||
<option value="single">
|
<option value="single">{{ $t('polls.single_choice') }}</option>
|
||||||
{{ $t('polls.single_choice') }}
|
<option value="multiple">{{ $t('polls.multiple_choices') }}</option>
|
||||||
</option>
|
</select>
|
||||||
<option value="multiple">
|
<FAIcon
|
||||||
{{ $t('polls.multiple_choices') }}
|
class="select-down-icon"
|
||||||
</option>
|
icon="chevron-down"
|
||||||
</Select>
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="poll-expiry"
|
class="poll-expiry"
|
||||||
@@ -72,10 +79,9 @@
|
|||||||
:max="maxExpirationInCurrentUnit"
|
:max="maxExpirationInCurrentUnit"
|
||||||
@change="expiryAmountChange"
|
@change="expiryAmountChange"
|
||||||
>
|
>
|
||||||
<Select
|
<label class="expiry-unit select">
|
||||||
|
<select
|
||||||
v-model="expiryUnit"
|
v-model="expiryUnit"
|
||||||
unstyled="true"
|
|
||||||
class="expiry-unit"
|
|
||||||
@change="expiryAmountChange"
|
@change="expiryAmountChange"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
@@ -85,7 +91,12 @@
|
|||||||
>
|
>
|
||||||
{{ $t(`time.${unit}_short`, ['']) }}
|
{{ $t(`time.${unit}_short`, ['']) }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</select>
|
||||||
|
<FAIcon
|
||||||
|
class="select-down-icon"
|
||||||
|
icon="chevron-down"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,6 +116,7 @@
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding-top: 0.25em;
|
padding-top: 0.25em;
|
||||||
padding-left: 0.1em;
|
padding-left: 0.1em;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-option {
|
.poll-option {
|
||||||
@@ -123,11 +135,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-option {
|
.icon-container {
|
||||||
// Hack: Move the icon over the input box
|
// Hack: Move the icon over the input box
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
margin-left: -1.5em;
|
margin-left: -1.5em;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-type-expiry {
|
.poll-type-expiry {
|
||||||
@@ -139,9 +159,10 @@
|
|||||||
.poll-type {
|
.poll-type {
|
||||||
margin-right: 0.75em;
|
margin-right: 0.75em;
|
||||||
flex: 1 1 60%;
|
flex: 1 1 60%;
|
||||||
|
.select {
|
||||||
.poll-type-select {
|
border: none;
|
||||||
padding-right: 0.75em;
|
box-shadow: none;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +173,12 @@
|
|||||||
width: 3em;
|
width: 3em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expiry-unit {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,35 +3,25 @@ const Popover = {
|
|||||||
props: {
|
props: {
|
||||||
// Action to trigger popover: either 'hover' or 'click'
|
// Action to trigger popover: either 'hover' or 'click'
|
||||||
trigger: String,
|
trigger: String,
|
||||||
|
|
||||||
// Either 'top' or 'bottom'
|
// Either 'top' or 'bottom'
|
||||||
placement: String,
|
placement: String,
|
||||||
|
|
||||||
// Takes object with properties 'x' and 'y', values of these can be
|
// Takes object with properties 'x' and 'y', values of these can be
|
||||||
// 'container' for using offsetParent as boundaries for either axis
|
// 'container' for using offsetParent as boundaries for either axis
|
||||||
// or 'viewport'
|
// or 'viewport'
|
||||||
boundTo: Object,
|
boundTo: Object,
|
||||||
|
|
||||||
// Takes a selector to use as a replacement for the parent container
|
// Takes a selector to use as a replacement for the parent container
|
||||||
// for getting boundaries for x an y axis
|
// for getting boundaries for x an y axis
|
||||||
boundToSelector: String,
|
boundToSelector: String,
|
||||||
|
|
||||||
// Takes a top/bottom/left/right object, how much space to leave
|
// Takes a top/bottom/left/right object, how much space to leave
|
||||||
// between boundary and popover element
|
// between boundary and popover element
|
||||||
margin: Object,
|
margin: Object,
|
||||||
|
|
||||||
// Takes a x/y object and tells how many pixels to offset from
|
// Takes a x/y object and tells how many pixels to offset from
|
||||||
// anchor point on either axis
|
// anchor point on either axis
|
||||||
offset: Object,
|
offset: Object,
|
||||||
|
|
||||||
// Replaces the classes you may want for the popover container.
|
// Replaces the classes you may want for the popover container.
|
||||||
// Use 'popover-default' in addition to get the default popover
|
// Use 'popover-default' in addition to get the default popover
|
||||||
// styles with your custom class.
|
// styles with your custom class.
|
||||||
popoverClass: String,
|
popoverClass: String
|
||||||
|
|
||||||
// If true, subtract padding when calculating position for the popover,
|
|
||||||
// use it when popover offset looks to be different on top vs bottom.
|
|
||||||
removePadding: Boolean
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -54,11 +44,8 @@ const Popover = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Popover will be anchored around this element, trigger ref is the container, so
|
// Popover will be anchored around this element, trigger ref is the container, so
|
||||||
// its children are what are inside the slot. Expect only one v-slot:trigger.
|
// its children are what are inside the slot. Expect only one slot="trigger".
|
||||||
const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
|
const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
|
||||||
// SVGs don't have offsetWidth/Height, use fallback
|
|
||||||
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
|
|
||||||
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
|
|
||||||
const screenBox = anchorEl.getBoundingClientRect()
|
const screenBox = anchorEl.getBoundingClientRect()
|
||||||
// Screen position of the origin point for popover
|
// Screen position of the origin point for popover
|
||||||
const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
|
const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
|
||||||
@@ -109,19 +96,13 @@ const Popover = {
|
|||||||
if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
|
if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
|
||||||
if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
|
if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
|
||||||
|
|
||||||
let vPadding = 0
|
|
||||||
if (this.removePadding && usingTop) {
|
|
||||||
const anchorStyle = getComputedStyle(anchorEl)
|
|
||||||
vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom)
|
|
||||||
}
|
|
||||||
|
|
||||||
const yOffset = (this.offset && this.offset.y) || 0
|
const yOffset = (this.offset && this.offset.y) || 0
|
||||||
const translateY = usingTop
|
const translateY = usingTop
|
||||||
? -anchorHeight + vPadding - yOffset - content.offsetHeight
|
? -anchorEl.offsetHeight - yOffset - content.offsetHeight
|
||||||
: yOffset
|
: yOffset
|
||||||
|
|
||||||
const xOffset = (this.offset && this.offset.x) || 0
|
const xOffset = (this.offset && this.offset.x) || 0
|
||||||
const translateX = anchorWidth * 0.5 - content.offsetWidth * 0.5 + horizOffset + xOffset
|
const translateX = (anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset + xOffset
|
||||||
|
|
||||||
// Note, separate translateX and translateY avoids blurry text on chromium,
|
// Note, separate translateX and translateY avoids blurry text on chromium,
|
||||||
// single translate or translate3d resulted in blurry text.
|
// single translate or translate3d resulted in blurry text.
|
||||||
@@ -131,12 +112,9 @@ const Popover = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showPopover () {
|
showPopover () {
|
||||||
const wasHidden = this.hidden
|
if (this.hidden) this.$emit('show')
|
||||||
this.hidden = false
|
this.hidden = false
|
||||||
this.$nextTick(() => {
|
this.$nextTick(this.updateStyles)
|
||||||
if (wasHidden) this.$emit('show')
|
|
||||||
this.updateStyles()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
hidePopover () {
|
hidePopover () {
|
||||||
if (!this.hidden) this.$emit('close')
|
if (!this.hidden) this.$emit('close')
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
@mouseenter="onMouseenter"
|
@mouseenter="onMouseenter"
|
||||||
@mouseleave="onMouseleave"
|
@mouseleave="onMouseleave"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
ref="trigger"
|
ref="trigger"
|
||||||
class="button-unstyled -fullwidth popover-trigger-button"
|
|
||||||
type="button"
|
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<slot name="trigger" />
|
<slot name="trigger" />
|
||||||
</button>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!hidden"
|
v-if="!hidden"
|
||||||
ref="content"
|
ref="content"
|
||||||
@@ -32,10 +30,6 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.popover-trigger-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
z-index: 8;
|
z-index: 8;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -82,9 +76,10 @@
|
|||||||
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
|
margin-right: 5px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
display: block;
|
display: block;
|
||||||
padding: .5em 0.75em;
|
padding: .25rem 1.0rem .25rem 1.5rem;
|
||||||
clear: both;
|
clear: both;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
@@ -95,14 +90,14 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
--btnText: var(--popoverText, $fallback--text);
|
--btnText: var(--popoverText, $fallback--text);
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 22px;
|
margin-right: 0.25rem;
|
||||||
margin-right: 0.75rem;
|
|
||||||
color: var(--menuPopoverIcon, $fallback--icon)
|
color: var(--menuPopoverIcon, $fallback--icon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,33 +116,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-checkbox {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
min-width: 22px;
|
|
||||||
max-width: 22px;
|
|
||||||
min-height: 22px;
|
|
||||||
max-height: 22px;
|
|
||||||
line-height: 22px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 0px;
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--input, $fallback--fg);
|
|
||||||
box-shadow: 0px 0px 2px black inset;
|
|
||||||
box-shadow: var(--inputShadow);
|
|
||||||
margin-right: 0.75em;
|
|
||||||
|
|
||||||
&.menu-checkbox-checked::after {
|
|
||||||
font-size: 1.25em;
|
|
||||||
content: '✓';
|
|
||||||
}
|
|
||||||
|
|
||||||
&.menu-checkbox-radio::after {
|
|
||||||
font-size: 2em;
|
|
||||||
content: '•';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { reject, map, uniqBy, debounce } from 'lodash'
|
|||||||
import suggestor from '../emoji_input/suggestor.js'
|
import suggestor from '../emoji_input/suggestor.js'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
import Select from '../select/select.vue'
|
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
faChevronDown,
|
||||||
faSmileBeam,
|
faSmileBeam,
|
||||||
faPollH,
|
faPollH,
|
||||||
faUpload,
|
faUpload,
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
faChevronDown,
|
||||||
faSmileBeam,
|
faSmileBeam,
|
||||||
faPollH,
|
faPollH,
|
||||||
faUpload,
|
faUpload,
|
||||||
@@ -83,7 +84,6 @@ const PostStatusForm = {
|
|||||||
PollForm,
|
PollForm,
|
||||||
ScopeSelector,
|
ScopeSelector,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Select,
|
|
||||||
Attachment,
|
Attachment,
|
||||||
StatusContent
|
StatusContent
|
||||||
},
|
},
|
||||||
@@ -115,7 +115,7 @@ const PostStatusForm = {
|
|||||||
? this.copyMessageScope
|
? this.copyMessageScope
|
||||||
: this.$store.state.users.currentUser.default_scope
|
: this.$store.state.users.currentUser.default_scope
|
||||||
|
|
||||||
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
|
const { postContentType: contentType } = this.$store.getters.mergedConfig
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dropFiles: [],
|
dropFiles: [],
|
||||||
@@ -126,7 +126,7 @@ const PostStatusForm = {
|
|||||||
newStatus: {
|
newStatus: {
|
||||||
spoilerText: this.subject || '',
|
spoilerText: this.subject || '',
|
||||||
status: statusText,
|
status: statusText,
|
||||||
nsfw: !!sensitiveByDefault,
|
nsfw: false,
|
||||||
files: [],
|
files: [],
|
||||||
poll: {},
|
poll: {},
|
||||||
mediaDescriptions: {},
|
mediaDescriptions: {},
|
||||||
@@ -159,7 +159,8 @@ const PostStatusForm = {
|
|||||||
...this.$store.state.instance.emoji,
|
...this.$store.state.instance.emoji,
|
||||||
...this.$store.state.instance.customEmoji
|
...this.$store.state.instance.customEmoji
|
||||||
],
|
],
|
||||||
store: this.$store
|
users: this.$store.state.users.users,
|
||||||
|
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
emojiSuggestor () {
|
emojiSuggestor () {
|
||||||
@@ -530,7 +531,7 @@ const PostStatusForm = {
|
|||||||
!(isFormBiggerThanScroller &&
|
!(isFormBiggerThanScroller &&
|
||||||
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
|
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
|
||||||
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
|
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
|
||||||
const targetScroll = Math.round(currentScroll + totalDelta)
|
const targetScroll = currentScroll + totalDelta
|
||||||
|
|
||||||
if (scrollerRef === window) {
|
if (scrollerRef === window) {
|
||||||
scrollerRef.scroll(0, targetScroll)
|
scrollerRef.scroll(0, targetScroll)
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
tag="p"
|
tag="p"
|
||||||
class="visibility-notice"
|
class="visibility-notice"
|
||||||
>
|
>
|
||||||
<button
|
<a
|
||||||
class="button-unstyled -link"
|
href="#"
|
||||||
@click="openProfileTab"
|
@click="openProfileTab"
|
||||||
>
|
>
|
||||||
{{ $t('post_status.account_not_locked_warning_link') }}
|
{{ $t('post_status.account_not_locked_warning_link') }}
|
||||||
</button>
|
</a>
|
||||||
</i18n>
|
</i18n>
|
||||||
<p
|
<p
|
||||||
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
||||||
@@ -189,7 +189,11 @@
|
|||||||
v-if="postFormats.length > 1"
|
v-if="postFormats.length > 1"
|
||||||
class="text-format"
|
class="text-format"
|
||||||
>
|
>
|
||||||
<Select
|
<label
|
||||||
|
for="post-content-type"
|
||||||
|
class="select"
|
||||||
|
>
|
||||||
|
<select
|
||||||
id="post-content-type"
|
id="post-content-type"
|
||||||
v-model="newStatus.contentType"
|
v-model="newStatus.contentType"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@@ -201,7 +205,12 @@
|
|||||||
>
|
>
|
||||||
{{ $t(`post_status.content_type["${postFormat}"]`) }}
|
{{ $t(`post_status.content_type["${postFormat}"]`) }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</select>
|
||||||
|
<FAIcon
|
||||||
|
class="select-down-icon"
|
||||||
|
icon="chevron-down"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
|
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
|
||||||
@@ -234,46 +243,50 @@
|
|||||||
@upload-failed="uploadFailed"
|
@upload-failed="uploadFailed"
|
||||||
@all-uploaded="finishedUploadingFiles"
|
@all-uploaded="finishedUploadingFiles"
|
||||||
/>
|
/>
|
||||||
<button
|
<div
|
||||||
class="emoji-icon button-unstyled"
|
class="emoji-icon"
|
||||||
|
>
|
||||||
|
<div
|
||||||
:title="$t('emoji.add_emoji')"
|
:title="$t('emoji.add_emoji')"
|
||||||
|
class="btn btn-default"
|
||||||
@click="showEmojiPicker"
|
@click="showEmojiPicker"
|
||||||
>
|
>
|
||||||
<FAIcon icon="smile-beam" />
|
<FAIcon icon="smile-beam" />
|
||||||
</button>
|
</div>
|
||||||
<button
|
</div>
|
||||||
|
<div
|
||||||
v-if="pollsAvailable"
|
v-if="pollsAvailable"
|
||||||
class="poll-icon button-unstyled"
|
class="poll-icon"
|
||||||
:class="{ selected: pollFormVisible }"
|
:class="{ selected: pollFormVisible }"
|
||||||
:title="$t('polls.add_poll')"
|
:title="$t('polls.add_poll')"
|
||||||
@click="togglePollForm"
|
@click="togglePollForm"
|
||||||
>
|
>
|
||||||
<FAIcon icon="poll-h" />
|
<FAIcon icon="poll-h" />
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="posting"
|
v-if="posting"
|
||||||
disabled
|
disabled
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('post_status.posting') }}
|
{{ $t('post_status.posting') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="isOverLengthLimit"
|
v-else-if="isOverLengthLimit"
|
||||||
disabled
|
disabled
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('post_status.post') }}
|
{{ $t('general.submit') }}
|
||||||
</button>
|
</button>
|
||||||
<!-- touchstart is used to keep the OSK at the same position after a message send -->
|
<!-- touchstart is used to keep the OSK at the same position after a message send -->
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
:disabled="uploadingFiles || disableSubmit"
|
:disabled="uploadingFiles || disableSubmit"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
@touchstart.stop.prevent="postStatus($event, newStatus)"
|
@touchstart.stop.prevent="postStatus($event, newStatus)"
|
||||||
@click.stop.prevent="postStatus($event, newStatus)"
|
@click.stop.prevent="postStatus($event, newStatus)"
|
||||||
>
|
>
|
||||||
{{ $t('post_status.post') }}
|
{{ $t('general.submit') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -293,12 +306,11 @@
|
|||||||
:key="file.url"
|
:key="file.url"
|
||||||
class="media-upload-wrapper"
|
class="media-upload-wrapper"
|
||||||
>
|
>
|
||||||
<button
|
<FAIcon
|
||||||
class="button-unstyled hider"
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
icon="times"
|
||||||
@click="removeMediaFile(file)"
|
@click="removeMediaFile(file)"
|
||||||
>
|
/>
|
||||||
<FAIcon icon="times" />
|
|
||||||
</button>
|
|
||||||
<attachment
|
<attachment
|
||||||
:attachment="file"
|
:attachment="file"
|
||||||
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
|
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
|
||||||
@@ -508,11 +520,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attachments .media-upload-wrapper {
|
.attachments .media-upload-wrapper {
|
||||||
position: relative;
|
padding: 0 0.5em;
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-scale-110 fa-old-padding {
|
||||||
|
position: absolute;
|
||||||
|
margin: 10px;
|
||||||
|
margin: .75em;
|
||||||
|
padding: .5em;
|
||||||
|
background: rgba(230,230,230,0.6);
|
||||||
|
z-index: 2;
|
||||||
|
color: black;
|
||||||
|
border-radius: $fallback--attachmentRadius;
|
||||||
|
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,31 +23,17 @@ const ReactButton = {
|
|||||||
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
},
|
|
||||||
focusInput () {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const input = this.$el.querySelector('input')
|
|
||||||
if (input) input.focus()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
commonEmojis () {
|
commonEmojis () {
|
||||||
return [
|
return ['👍', '😠', '👀', '😂', '🔥']
|
||||||
{ displayText: 'thumbsup', replacement: '👍' },
|
|
||||||
{ displayText: 'angry', replacement: '😠' },
|
|
||||||
{ displayText: 'eyes', replacement: '👀' },
|
|
||||||
{ displayText: 'joy', replacement: '😂' },
|
|
||||||
{ displayText: 'fire', replacement: '🔥' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
emojis () {
|
emojis () {
|
||||||
if (this.filterWord !== '') {
|
if (this.filterWord !== '') {
|
||||||
const filterWordLowercase = this.filterWord.toLowerCase()
|
const filterWordLowercase = this.filterWord.toLowerCase()
|
||||||
let orderedEmojiList = []
|
let orderedEmojiList = []
|
||||||
for (const emoji of this.$store.state.instance.emoji) {
|
for (const emoji of this.$store.state.instance.emoji) {
|
||||||
if (emoji.replacement === this.filterWord) return [emoji]
|
|
||||||
|
|
||||||
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
|
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
|
||||||
if (indexOfFilterWord > -1) {
|
if (indexOfFilterWord > -1) {
|
||||||
if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) {
|
if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<Popover
|
<Popover
|
||||||
trigger="click"
|
trigger="click"
|
||||||
class="ReactButton"
|
|
||||||
placement="top"
|
placement="top"
|
||||||
:offset="{ y: 5 }"
|
:offset="{ y: 5 }"
|
||||||
|
class="react-button-popover"
|
||||||
:bound-to="{ x: 'container' }"
|
:bound-to="{ x: 'container' }"
|
||||||
remove-padding
|
|
||||||
@show="focusInput"
|
|
||||||
>
|
>
|
||||||
<template v-slot:content="{close}">
|
<div
|
||||||
|
slot="content"
|
||||||
|
slot-scope="{close}"
|
||||||
|
>
|
||||||
<div class="reaction-picker-filter">
|
<div class="reaction-picker-filter">
|
||||||
<input
|
<input
|
||||||
v-model="filterWord"
|
v-model="filterWord"
|
||||||
@@ -19,37 +20,31 @@
|
|||||||
<div class="reaction-picker">
|
<div class="reaction-picker">
|
||||||
<span
|
<span
|
||||||
v-for="emoji in commonEmojis"
|
v-for="emoji in commonEmojis"
|
||||||
:key="emoji.replacement"
|
:key="emoji"
|
||||||
class="emoji-button"
|
class="emoji-button"
|
||||||
:title="emoji.displayText"
|
@click="addReaction($event, emoji, close)"
|
||||||
@click="addReaction($event, emoji.replacement, close)"
|
|
||||||
>
|
>
|
||||||
{{ emoji.replacement }}
|
{{ emoji }}
|
||||||
</span>
|
</span>
|
||||||
<div class="reaction-picker-divider" />
|
<div class="reaction-picker-divider" />
|
||||||
<span
|
<span
|
||||||
v-for="(emoji, key) in emojis"
|
v-for="(emoji, key) in emojis"
|
||||||
:key="key"
|
:key="key"
|
||||||
class="emoji-button"
|
class="emoji-button"
|
||||||
:title="emoji.displayText"
|
|
||||||
@click="addReaction($event, emoji.replacement, close)"
|
@click="addReaction($event, emoji.replacement, close)"
|
||||||
>
|
>
|
||||||
{{ emoji.replacement }}
|
{{ emoji.replacement }}
|
||||||
</span>
|
</span>
|
||||||
<div class="reaction-bottom-fader" />
|
<div class="reaction-bottom-fader" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template v-slot:trigger>
|
<span slot="trigger">
|
||||||
<button
|
|
||||||
class="button-unstyled popover-trigger"
|
|
||||||
:title="$t('tool_tip.add_reaction')"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding add-reaction-button"
|
||||||
:icon="['far', 'smile-beam']"
|
:icon="['far', 'smile-beam']"
|
||||||
|
:title="$t('tool_tip.add_reaction')"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
</template>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -58,24 +53,22 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.ReactButton {
|
.reaction-picker-filter {
|
||||||
.reaction-picker-filter {
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reaction-picker-divider {
|
.reaction-picker-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
background-color: var(--border, $fallback--border);
|
background-color: var(--border, $fallback--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reaction-picker {
|
.reaction-picker {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
height: 9em;
|
height: 9em;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@@ -92,8 +85,7 @@
|
|||||||
linear-gradient(to top, white, white);
|
linear-gradient(to top, white, white);
|
||||||
transition: mask-size 150ms;
|
transition: mask-size 150ms;
|
||||||
mask-size: 100% 20px, 100% 20px, auto;
|
mask-size: 100% 20px, 100% 20px, auto;
|
||||||
|
// Autoprefixed seem to ignore this one, and also syntax is different
|
||||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
|
|
||||||
@@ -108,22 +100,15 @@
|
|||||||
transform: scale(1.25);
|
transform: scale(1.25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* override of popover internal stuff */
|
.add-reaction-button {
|
||||||
.popover-trigger-button {
|
cursor: pointer;
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-trigger {
|
&:hover {
|
||||||
padding: 10px;
|
|
||||||
margin: -10px;
|
|
||||||
|
|
||||||
&:hover .svg-inline--fa {
|
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ const registration = {
|
|||||||
fullname: '',
|
fullname: '',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirm: '',
|
confirm: ''
|
||||||
reason: ''
|
|
||||||
},
|
},
|
||||||
captcha: {}
|
captcha: {}
|
||||||
}),
|
}),
|
||||||
@@ -25,8 +24,7 @@ const registration = {
|
|||||||
confirm: {
|
confirm: {
|
||||||
required,
|
required,
|
||||||
sameAsPassword: sameAs('password')
|
sameAsPassword: sameAs('password')
|
||||||
},
|
}
|
||||||
reason: { required: requiredIf(() => this.accountApprovalRequired) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,10 +38,7 @@ const registration = {
|
|||||||
computed: {
|
computed: {
|
||||||
token () { return this.$route.params.token },
|
token () { return this.$route.params.token },
|
||||||
bioPlaceholder () {
|
bioPlaceholder () {
|
||||||
return this.replaceNewlines(this.$t('registration.bio_placeholder'))
|
return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n')
|
||||||
},
|
|
||||||
reasonPlaceholder () {
|
|
||||||
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
|
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
registrationOpen: (state) => state.instance.registrationOpen,
|
registrationOpen: (state) => state.instance.registrationOpen,
|
||||||
@@ -51,8 +46,7 @@ const registration = {
|
|||||||
isPending: (state) => state.users.signUpPending,
|
isPending: (state) => state.users.signUpPending,
|
||||||
serverValidationErrors: (state) => state.users.signUpErrors,
|
serverValidationErrors: (state) => state.users.signUpErrors,
|
||||||
termsOfService: (state) => state.instance.tos,
|
termsOfService: (state) => state.instance.tos,
|
||||||
accountActivationRequired: (state) => state.instance.accountActivationRequired,
|
accountActivationRequired: (state) => state.instance.accountActivationRequired
|
||||||
accountApprovalRequired: (state) => state.instance.accountApprovalRequired
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -79,9 +73,6 @@ const registration = {
|
|||||||
},
|
},
|
||||||
setCaptcha () {
|
setCaptcha () {
|
||||||
this.getCaptcha().then(cpt => { this.captcha = cpt })
|
this.getCaptcha().then(cpt => { this.captcha = cpt })
|
||||||
},
|
|
||||||
replaceNewlines (str) {
|
|
||||||
return str.replace(/\s*\n\s*/g, ' \n')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,23 +162,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="accountApprovalRequired"
|
|
||||||
class="form-group"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="form--label"
|
|
||||||
for="reason"
|
|
||||||
>{{ $t('registration.reason') }}</label>
|
|
||||||
<textarea
|
|
||||||
id="reason"
|
|
||||||
v-model="user.reason"
|
|
||||||
:disabled="isPending"
|
|
||||||
class="form-control"
|
|
||||||
:placeholder="reasonPlaceholder"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="captcha.type != 'none'"
|
v-if="captcha.type != 'none'"
|
||||||
id="captcha-group"
|
id="captcha-group"
|
||||||
@@ -228,9 +211,9 @@
|
|||||||
<button
|
<button
|
||||||
:disabled="isPending"
|
:disabled="isPending"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn button-default"
|
class="btn btn-default"
|
||||||
>
|
>
|
||||||
{{ $t('registration.register') }}
|
{{ $t('general.submit') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
click="submit"
|
click="submit"
|
||||||
class="button-default remote-button"
|
class="remote-button"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.remote_follow') }}
|
{{ $t('user_card.remote_follow') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ReplyButton">
|
<div>
|
||||||
<button
|
<FAIcon
|
||||||
v-if="loggedIn"
|
v-if="loggedIn"
|
||||||
class="button-unstyled interactive"
|
class="ReplyButton fa-scale-110 fa-old-padding -interactive"
|
||||||
|
icon="reply"
|
||||||
|
:title="$t('tool_tip.reply')"
|
||||||
:class="{'-active': replying}"
|
:class="{'-active': replying}"
|
||||||
:title="$t('tool_tip.reply')"
|
|
||||||
@click.prevent="$emit('toggle')"
|
@click.prevent="$emit('toggle')"
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
|
||||||
icon="reply"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
|
||||||
<span v-else>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
v-else
|
||||||
icon="reply"
|
icon="reply"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="ReplyButton fa-scale-110 fa-old-padding"
|
||||||
:title="$t('tool_tip.reply')"
|
:title="$t('tool_tip.reply')"
|
||||||
/>
|
/>
|
||||||
</span>
|
<span v-if="status.replies_count > 0">
|
||||||
<span
|
|
||||||
v-if="status.replies_count > 0"
|
|
||||||
class="action-counter"
|
|
||||||
>
|
|
||||||
{{ status.replies_count }}
|
{{ status.replies_count }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,25 +26,14 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.ReplyButton {
|
.ReplyButton {
|
||||||
display: flex;
|
&.-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
> :first-child {
|
&:hover,
|
||||||
padding: 10px;
|
&.-active {
|
||||||
margin: -10px -8px -10px -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-counter {
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.interactive {
|
|
||||||
&:hover .svg-inline--fa,
|
|
||||||
&.-active .svg-inline--fa {
|
|
||||||
color: $fallback--cBlue;
|
color: $fallback--cBlue;
|
||||||
color: var(--cBlue, $fallback--cBlue);
|
color: var(--cBlue, $fallback--cBlue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ const RetweetButton = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
classes () {
|
||||||
|
return {
|
||||||
|
'-repeated': this.status.repeated
|
||||||
|
}
|
||||||
|
},
|
||||||
mergedConfig () {
|
mergedConfig () {
|
||||||
return this.$store.getters.mergedConfig
|
return this.$store.getters.mergedConfig
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="RetweetButton">
|
<div v-if="loggedIn">
|
||||||
<button
|
<template v-if="visibility !== 'private' && visibility !== 'direct'">
|
||||||
v-if="visibility !== 'private' && visibility !== 'direct' && loggedIn"
|
|
||||||
class="button-unstyled interactive"
|
|
||||||
:class="status.repeated && '-repeated'"
|
|
||||||
:title="$t('tool_tip.repeat')"
|
|
||||||
@click.prevent="retweet()"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
:class="classes"
|
||||||
|
class="RetweetButton fa-scale-110 fa-old-padding -interactive"
|
||||||
icon="retweet"
|
icon="retweet"
|
||||||
:spin="animated"
|
:spin="animated"
|
||||||
|
:title="$t('tool_tip.repeat')"
|
||||||
|
@click.prevent="retweet()"
|
||||||
/>
|
/>
|
||||||
</button>
|
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span>
|
||||||
<span v-else-if="loggedIn">
|
</template>
|
||||||
|
<template v-else>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
:class="classes"
|
||||||
|
class="RetweetButton fa-scale-110 fa-old-padding"
|
||||||
icon="lock"
|
icon="lock"
|
||||||
:title="$t('timeline.no_retweet_hint')"
|
:title="$t('timeline.no_retweet_hint')"
|
||||||
/>
|
/>
|
||||||
</span>
|
</template>
|
||||||
<span v-else>
|
</div>
|
||||||
|
<div v-else-if="!loggedIn">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
:class="classes"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="retweet"
|
icon="retweet"
|
||||||
:title="$t('tool_tip.repeat')"
|
:title="$t('tool_tip.repeat')"
|
||||||
/>
|
/>
|
||||||
</span>
|
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span>
|
||||||
<span
|
|
||||||
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
|
|
||||||
class="no-event"
|
|
||||||
>
|
|
||||||
{{ status.repeat_num }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,28 +37,19 @@
|
|||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.RetweetButton {
|
.RetweetButton {
|
||||||
display: flex;
|
&.-interactive {
|
||||||
|
cursor: pointer;
|
||||||
> :first-child {
|
|
||||||
padding: 10px;
|
|
||||||
margin: -10px -8px -10px -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-counter {
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.interactive {
|
|
||||||
.svg-inline--fa {
|
|
||||||
animation-duration: 0.6s;
|
animation-duration: 0.6s;
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .svg-inline--fa,
|
&:hover {
|
||||||
&.-repeated .svg-inline--fa {
|
|
||||||
color: $fallback--cGreen;
|
color: $fallback--cGreen;
|
||||||
color: var(--cGreen, $fallback--cGreen);
|
color: var(--cGreen, $fallback--cGreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.-repeated {
|
||||||
|
color: $fallback--cGreen;
|
||||||
|
color: var(--cGreen, $fallback--cGreen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,58 +3,54 @@
|
|||||||
v-if="!showNothing"
|
v-if="!showNothing"
|
||||||
class="ScopeSelector"
|
class="ScopeSelector"
|
||||||
>
|
>
|
||||||
<button
|
<span
|
||||||
v-if="showDirect"
|
v-if="showDirect"
|
||||||
class="button-unstyled scope"
|
class="scope"
|
||||||
:class="css.direct"
|
:class="css.direct"
|
||||||
:title="$t('post_status.scope.direct')"
|
:title="$t('post_status.scope.direct')"
|
||||||
type="button"
|
|
||||||
@click="changeVis('direct')"
|
@click="changeVis('direct')"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="envelope"
|
icon="envelope"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
<button
|
<span
|
||||||
v-if="showPrivate"
|
v-if="showPrivate"
|
||||||
class="button-unstyled scope"
|
class="scope"
|
||||||
:class="css.private"
|
:class="css.private"
|
||||||
:title="$t('post_status.scope.private')"
|
:title="$t('post_status.scope.private')"
|
||||||
type="button"
|
|
||||||
@click="changeVis('private')"
|
@click="changeVis('private')"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="lock"
|
icon="lock"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
<button
|
<span
|
||||||
v-if="showUnlisted"
|
v-if="showUnlisted"
|
||||||
class="button-unstyled scope"
|
class="scope"
|
||||||
:class="css.unlisted"
|
:class="css.unlisted"
|
||||||
:title="$t('post_status.scope.unlisted')"
|
:title="$t('post_status.scope.unlisted')"
|
||||||
type="button"
|
|
||||||
@click="changeVis('unlisted')"
|
@click="changeVis('unlisted')"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="lock-open"
|
icon="lock-open"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
<button
|
<span
|
||||||
v-if="showPublic"
|
v-if="showPublic"
|
||||||
class="button-unstyled scope"
|
class="scope"
|
||||||
:class="css.public"
|
:class="css.public"
|
||||||
:title="$t('post_status.scope.public')"
|
:title="$t('post_status.scope.public')"
|
||||||
type="button"
|
|
||||||
@click="changeVis('public')"
|
@click="changeVis('public')"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="globe"
|
icon="globe"
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
@keyup.enter="newQuery(searchTerm)"
|
@keyup.enter="newQuery(searchTerm)"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="btn button-default search-button"
|
class="btn search-button"
|
||||||
type="submit"
|
|
||||||
@click="newQuery(searchTerm)"
|
@click="newQuery(searchTerm)"
|
||||||
>
|
>
|
||||||
<FAIcon icon="search" />
|
<FAIcon icon="search" />
|
||||||
|
|||||||
@@ -3,19 +3,17 @@
|
|||||||
class="SearchBar"
|
class="SearchBar"
|
||||||
:class="{ '-expanded': !hidden }"
|
:class="{ '-expanded': !hidden }"
|
||||||
>
|
>
|
||||||
<button
|
<a
|
||||||
v-if="hidden"
|
v-if="hidden"
|
||||||
class="button-unstyled nav-icon"
|
href="#"
|
||||||
|
class="nav-icon"
|
||||||
:title="$t('nav.search')"
|
:title="$t('nav.search')"
|
||||||
type="button"
|
><FAIcon
|
||||||
@click.prevent.stop="toggleHidden"
|
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="search"
|
icon="search"
|
||||||
/>
|
@click.prevent.stop="toggleHidden"
|
||||||
</button>
|
/></a>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input
|
<input
|
||||||
id="search-bar-input"
|
id="search-bar-input"
|
||||||
@@ -27,8 +25,7 @@
|
|||||||
@keyup.enter="find(searchTerm)"
|
@keyup.enter="find(searchTerm)"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="button-default search-button"
|
class="btn search-button"
|
||||||
type="submit"
|
|
||||||
@click="find(searchTerm)"
|
@click="find(searchTerm)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
@@ -36,17 +33,14 @@
|
|||||||
icon="search"
|
icon="search"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<span>
|
||||||
class="button-unstyled cancel-search"
|
|
||||||
type="button"
|
|
||||||
@click.prevent.stop="toggleHidden"
|
|
||||||
>
|
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
icon="times"
|
icon="times"
|
||||||
class="cancel-icon fa-scale-110 fa-old-padding"
|
class="cancel-icon fa-scale-110 fa-old-padding"
|
||||||
|
@click.prevent.stop="toggleHidden"
|
||||||
/>
|
/>
|
||||||
</button>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -75,11 +69,8 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-search {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-icon {
|
.cancel-icon {
|
||||||
|
cursor: pointer;
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--btnTopBarText, $fallback--text);
|
color: var(--btnTopBarText, $fallback--text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {
|
|
||||||
faChevronDown
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
library.add(
|
|
||||||
faChevronDown
|
|
||||||
)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
model: {
|
|
||||||
prop: 'value',
|
|
||||||
event: 'change'
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'value',
|
|
||||||
'disabled',
|
|
||||||
'unstyled',
|
|
||||||
'kind'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
|
|
||||||
<template>
|
|
||||||
<label
|
|
||||||
class="Select input"
|
|
||||||
:class="{ disabled, unstyled }"
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
:disabled="disabled"
|
|
||||||
:value="value"
|
|
||||||
@change="$emit('change', $event.target.value)"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</select>
|
|
||||||
<FAIcon
|
|
||||||
class="select-down-icon"
|
|
||||||
icon="chevron-down"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./select.js"> </script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../../_variables.scss';
|
|
||||||
|
|
||||||
.Select {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
select {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--inputText, --text, $fallback--text);
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 2em 0 .2em;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-family: var(--inputFont, sans-serif);
|
|
||||||
font-size: 14px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
height: 28px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-down-icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 5px;
|
|
||||||
height: 100%;
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--inputText, $fallback--text);
|
|
||||||
line-height: 28px;
|
|
||||||
z-index: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -24,7 +24,10 @@
|
|||||||
:items="items"
|
:items="items"
|
||||||
:get-key="getKey"
|
:get-key="getKey"
|
||||||
>
|
>
|
||||||
<template v-slot:item="{item}">
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="selectable-list-item-inner"
|
class="selectable-list-item-inner"
|
||||||
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
|
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
|
||||||
@@ -41,7 +44,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:empty>
|
<template slot="empty">
|
||||||
<slot name="empty" />
|
<slot name="empty" />
|
||||||
</template>
|
</template>
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { get, set } from 'lodash'
|
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
|
||||||
import ModifiedIndicator from './modified_indicator.vue'
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Checkbox,
|
|
||||||
ModifiedIndicator
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'path',
|
|
||||||
'disabled'
|
|
||||||
],
|
|
||||||
computed: {
|
|
||||||
pathDefault () {
|
|
||||||
const [firstSegment, ...rest] = this.path.split('.')
|
|
||||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
|
||||||
},
|
|
||||||
state () {
|
|
||||||
const value = get(this.$parent, this.path)
|
|
||||||
if (value === undefined) {
|
|
||||||
return this.defaultState
|
|
||||||
} else {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultState () {
|
|
||||||
return get(this.$parent, this.pathDefault)
|
|
||||||
},
|
|
||||||
isChanged () {
|
|
||||||
return this.state !== this.defaultState
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
update (e) {
|
|
||||||
set(this.$parent, this.path, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<label
|
|
||||||
class="BooleanSetting"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
:checked="state"
|
|
||||||
:disabled="disabled"
|
|
||||||
@change="update"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="!!$slots.default"
|
|
||||||
class="label"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
<ModifiedIndicator :changed="isChanged" />
|
|
||||||
</Checkbox>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./boolean_setting.js"></script>
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { get, set } from 'lodash'
|
|
||||||
import Select from 'src/components/select/select.vue'
|
|
||||||
import ModifiedIndicator from './modified_indicator.vue'
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Select,
|
|
||||||
ModifiedIndicator
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'path',
|
|
||||||
'disabled',
|
|
||||||
'options'
|
|
||||||
],
|
|
||||||
computed: {
|
|
||||||
pathDefault () {
|
|
||||||
const [firstSegment, ...rest] = this.path.split('.')
|
|
||||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
|
||||||
},
|
|
||||||
state () {
|
|
||||||
const value = get(this.$parent, this.path)
|
|
||||||
if (value === undefined) {
|
|
||||||
return this.defaultState
|
|
||||||
} else {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultState () {
|
|
||||||
return get(this.$parent, this.pathDefault)
|
|
||||||
},
|
|
||||||
isChanged () {
|
|
||||||
return this.state !== this.defaultState
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
update (e) {
|
|
||||||
set(this.$parent, this.path, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user