Compare commits

..

28 Commits

Author SHA1 Message Date
Shpuld Shpludson 320418d524 Merge branch 'rc/2.2.3' into 'master'
rc 2.2.3 to MASTER

See merge request pleroma/pleroma-fe!1328
2021-01-18 13:44:31 +00:00
Shpuld Shpludson 8f55cb151c Merge branch 'master' into 'rc/2.2.3'
# Conflicts:
#   CHANGELOG.md
2021-01-18 13:35:53 +00:00
Shpuld Shpuldson 4f0c43bd84 rc 2.2.3 2021-01-18 15:30:42 +02:00
Shpuld Shpludson a39866308c Merge branch 'rc/2.2.2' into 'master'
prepare master for 2.2.2

See merge request pleroma/pleroma-fe!1315
2020-12-22 16:00:12 +00:00
Shpuld Shpludson 00cb8d9dce Merge branch 'master' into 'rc/2.2.2'
# Conflicts:
#   CHANGELOG.md
2020-12-22 15:51:18 +00:00
Shpuld Shpuldson 19bde84f6d mark unreleased as 2.2.2 in changelog 2020-12-22 17:49:13 +02:00
Shpuld Shpuldson a7567ce6d0 set patch date to correct 2020-11-11 22:32:44 +02:00
Shpuld Shpuldson 8e87e3d88b Update MASTER with develop for 2.2.1 2020-11-09 11:13:49 +02:00
Shpuld Shpuldson 6aa276374f update changelog for 2.2.0 2020-11-06 19:35:01 +02:00
Shpuld Shpludson 23232e1c8f Merge branch 'develop' into 'master'
Merge develop into master for 2.2.0

See merge request pleroma/pleroma-fe!1278
2020-11-06 17:27:25 +00:00
Shpuld Shpludson b225c3578f Merge branch 'rc/2.1.2' into 'master'
Merge 2.1.2 to MASTER

See merge request pleroma/pleroma-fe!1236
2020-09-17 17:26:22 +00:00
Shpuld Shpludson 30efbaab34 Merge branch 'master' into 'rc/2.1.2'
# Conflicts:
#   CHANGELOG.md
2020-09-17 17:17:21 +00:00
Shpuld Shpuldson 8ca0586c0f update changelog for 2.1.2 2020-09-17 20:15:08 +03:00
Shpuld Shpludson 938887ef91 Merge branch 'rc/2.1.1' into 'master'
Update master with 2.1.1

See merge request pleroma/pleroma-fe!1231
2020-09-08 09:46:19 +00:00
Shpuld Shpludson f31bc5310e Merge branch 'master' into 'rc/2.1.1'
# Conflicts:
#   CHANGELOG.md
2020-09-08 09:35:36 +00:00
Shpuld Shpuldson 925bf5b5a4 update changelog with 2.1.1 2020-09-08 12:30:45 +03:00
Shpuld Shpludson e768ec1fca Merge branch '2.1.0-rc0' into 'master'
2.1.0 into master

See merge request pleroma/pleroma-fe!1217
2020-08-27 14:45:03 +00:00
Shpuld Shpuldson d09f43ba7a Merge branch '2.1.0-rc0' of git.pleroma.social:pleroma/pleroma-fe into 2.1.0-rc0 2020-08-27 17:13:43 +03:00
Shpuld Shpuldson 748c4d8c71 fix boomarks mistake in changelog 2020-08-27 17:13:14 +03:00
Shpuld Shpludson c7ddfefe34 Merge branch 'master' into '2.1.0-rc0'
# Conflicts:
#   CHANGELOG.md
#   src/services/api/api.service.js
2020-08-27 13:59:56 +00:00
Shpuld Shpuldson c0205d582a add missing release to changelog to fix conflicts 2020-08-27 16:56:47 +03:00
Shpuld Shpuldson 4ac882a3b0 add back unreleased/patch 2020-08-27 16:51:16 +03:00
Shpuld Shpuldson bdbc4b27b6 set 2.1.0 date and remove rc from changelog 2020-08-27 16:49:01 +03:00
Shpuld Shpuldson 42598fc675 change changelog 2020-08-26 14:28:43 +03:00
Shpuld Shpludson 5d49edc823 Merge branch 'rc/2.0.5' into 'master'
Update MASTER for 2.0.5 patch

See merge request pleroma/pleroma-fe!1105
2020-05-12 17:36:05 +00:00
Shpuld Shpuldson 0bc0a8d5f5 update changelog for 2.0.5 2020-05-12 20:27:37 +03:00
Shpuld Shpuldson 726d5279c1 Revert "remove with_move param"
This reverts commit 02c8a9e314.
2020-05-12 20:04:00 +03:00
Shpuld Shpludson a0f780c455 Merge branch 'rc/2.0.3' into 'master'
Update MASTER with 2.0.3 for real

See merge request pleroma/pleroma-fe!1099
2020-05-02 14:17:27 +00:00
158 changed files with 2348 additions and 5840 deletions
-1
View File
@@ -1 +0,0 @@
rinpatch <rin@patch.cx> <rinpatch@sdf.org>
-38
View File
@@ -3,40 +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/).
## [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
@@ -45,10 +11,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### 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
-1
View File
@@ -3,7 +3,6 @@ Contributors of this project.
- Constance Variable (lambadalambda@social.heldscal.la): Code
- Coco Snuss (cocosnuss@social.heldscal.la): Code
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
- eris (eris@disqordia.space): Code
- dtluna (dtluna@social.heldscal.la): Code
- sonyam (sonyam@social.heldscal.la): Background images
- hakui (hakui@freezepeach.xyz): CSS and styling
-1
View File
@@ -21,7 +21,6 @@ var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
writeToDisk: true,
stats: {
colors: true,
chunks: false
-14
View File
@@ -3,7 +3,6 @@ var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var CopyPlugin = require('copy-webpack-plugin');
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@@ -94,19 +93,6 @@ module.exports = {
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '..', 'src/sw.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,
},
})
]
}
-5
View File
@@ -3,11 +3,6 @@ const path = require('path')
let settings = {}
try {
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(JSON.stringify(settings, null, 2))
} catch (e) {
+3 -4
View File
@@ -32,9 +32,9 @@
"phoenix": "^1.3.0",
"portal-vue": "^2.1.4",
"punycode.js": "^2.1.0",
"ruffle-mirror": "^2021.4.10",
"v-click-outside": "^2.1.1",
"vue": "^2.6.11",
"vue-chat-scroll": "^1.2.1",
"vue-i18n": "^7.3.2",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.6.11",
@@ -58,7 +58,6 @@
"chalk": "^1.1.3",
"chromedriver": "^87.0.1",
"connect-history-api-fallback": "^1.1.0",
"copy-webpack-plugin": "^6.4.1",
"cross-spawn": "^4.0.2",
"css-loader": "^0.28.0",
"custom-event-polyfill": "^1.0.7",
@@ -104,7 +103,7 @@
"selenium-server": "2.53.1",
"semver": "^5.3.0",
"serviceworker-webpack-plugin": "^1.0.0",
"shelljs": "^0.8.4",
"shelljs": "^0.7.4",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"stylelint": "^13.6.1",
@@ -113,7 +112,7 @@
"url-loader": "^1.1.2",
"vue-loader": "^14.0.0",
"vue-style-loader": "^4.0.0",
"webpack": "^4.44.0",
"webpack": "^4.0.0",
"webpack-dev-middleware": "^3.6.0",
"webpack-hot-middleware": "^2.12.2",
"webpack-merge": "^0.14.1"
+4 -7
View File
@@ -4,7 +4,7 @@ import Notifications from './components/notifications/notifications.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_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 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 MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
@@ -26,7 +26,7 @@ export default {
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
ShoutPanel,
ChatPanel,
MediaModal,
SideDrawer,
MobilePostStatusButton,
@@ -65,7 +65,7 @@ export default {
}
}
},
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 },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
@@ -73,14 +73,11 @@ export default {
this.$store.state.instance.instanceSpecificPanelContent
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
isMobileLayout () { return this.$store.state.interface.mobileLayout },
privateMode () { return this.$store.state.instance.private },
sidebarAlign () {
return {
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
'order': this.$store.state.instance.sidebarRight ? 99 : 0
}
},
...mapGetters(['mergedConfig'])
+54 -39
View File
@@ -178,16 +178,9 @@ a {
&.-fullwidth {
width: 100%;
}
&.-hover-highlight {
&:hover svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
}
input, textarea, .input {
input, textarea, .select, .input {
&.unstyled {
border-radius: 0;
@@ -217,11 +210,47 @@ input, textarea, .input {
hyphens: none;
padding: 8px .5em;
&:disabled, &[disabled=disabled], &.disabled {
&.select {
padding: 0;
}
&:disabled, &[disabled=disabled] {
cursor: not-allowed;
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] {
background: none;
border: none;
@@ -511,21 +540,9 @@ main-router {
border-radius: var(--panelRadius, $fallback--panelRadius);
}
/* TODO Should remove timeline-footer from here when we refactor panels into
* separate component and utilize slots
*/
.panel-footer, .timeline-footer {
display: flex;
.panel-footer {
border-radius: 0 0 $fallback--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 {
color: $fallback--faint;
@@ -562,7 +579,6 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
box-sizing: border-box;
}
.fade-enter-active, .fade-leave-active {
@@ -682,15 +698,6 @@ nav {
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 {
@@ -794,6 +801,13 @@ nav {
}
}
.select-multiple {
display: flex;
.option-list {
margin: 0;
padding-left: .5em;
}
}
.setting-list,
.option-list{
list-style-type: none;
@@ -840,10 +854,16 @@ nav {
}
.new-status-notification {
position: relative;
position:relative;
margin-top: -1px;
font-size: 1.1em;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
padding: 10px;
z-index: 1;
flex: 1;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}
.chat-layout {
@@ -851,11 +871,6 @@ nav {
overflow: hidden;
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.
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
@media all and (max-width: 800px) {
+3 -3
View File
@@ -49,10 +49,10 @@
</div>
<media-modal />
</div>
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
<chat-panel
v-if="currentUser && chat"
:floating="true"
class="floating-shout mobile-hidden"
class="floating-chat mobile-hidden"
/>
<MobilePostStatusButton />
<UserReportingModal />
+1 -2
View File
@@ -51,7 +51,6 @@ const getInstanceConfig = async ({ store }) => {
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@@ -240,7 +239,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
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: '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: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
+2 -2
View File
@@ -16,7 +16,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue'
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 About from 'components/about/about.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: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
{ 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: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
@@ -6,7 +6,10 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:content>
<div
slot="content"
class="account-tools-popover"
>
<div class="dropdown-menu">
<template v-if="relationship.following">
<button
@@ -56,15 +59,16 @@
{{ $t('user_card.message') }}
</button>
</div>
</template>
<template v-slot:trigger>
<button class="button-unstyled ellipsis-button">
</div>
<div
slot="trigger"
class="ellipsis-button"
>
<FAIcon
class="icon"
icon="ellipsis-v"
/>
</button>
</template>
</div>
</Popover>
</div>
</template>
@@ -79,6 +83,7 @@
}
.ellipsis-button {
cursor: pointer;
width: 2.5em;
margin: -0.5em 0;
padding: 0.5em 0;
-2
View File
@@ -1,5 +1,4 @@
import StillImage from '../still-image/still-image.vue'
import Flash from '../flash/flash.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
@@ -44,7 +43,6 @@ const Attachment = {
}
},
components: {
Flash,
StillImage,
VideoAttachment
},
-6
View File
@@ -117,11 +117,6 @@
<!-- eslint-enable vue/no-v-html -->
</div>
</div>
<Flash
v-if="type === 'flash'"
:src="attachment.large_thumb_url || attachment.url"
/>
</div>
</template>
@@ -177,7 +172,6 @@
}
.non-gallery.attachment {
&.flash,
&.video {
flex: 1 0 40%;
}
@@ -42,7 +42,7 @@
class="basic-user-card-screen-name"
:to="userProfileLink(user)"
>
@{{ user.screen_name_ui }}
@{{ user.screen_name }}
</router-link>
</div>
<slot />
+1 -9
View File
@@ -73,7 +73,7 @@ const Chat = {
},
formPlaceholder () {
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 {
return ''
}
@@ -234,13 +234,6 @@ const Chat = {
const scrollable = this.$refs.scrollable
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 () {
if (!this.currentChat) { return }
@@ -248,7 +241,6 @@ const Chat = {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.jumpToBottomButtonVisible = false
this.cullOlderCheck()
if (this.newMessageCount > 0) {
// 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
+3 -3
View File
@@ -98,10 +98,10 @@
.unread-message-count {
font-size: 0.8em;
left: 50%;
transform: translate(-50%, 0);
border-radius: 100%;
margin-top: -1rem;
padding: 0.1em;
border-radius: 50px;
position: absolute;
padding: 0;
}
.chat-loading-error {
+4 -1
View File
@@ -23,7 +23,10 @@
class="timeline"
>
<List :items="sortedChatList">
<template v-slot:item="{item}">
<template
slot="item"
slot-scope="{item}"
>
<ChatListItem
:key="item.id"
:compact="false"
+4 -6
View File
@@ -50,7 +50,7 @@
@show="menuOpened = true"
@close="menuOpened = false"
>
<template v-slot:content>
<div slot="content">
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@@ -59,28 +59,26 @@
<FAIcon icon="times" /> {{ $t("chats.delete") }}
</button>
</div>
</template>
<template v-slot:trigger>
</div>
<button
slot="trigger"
class="button-default menu-icon"
:title="$t('chats.more')"
>
<FAIcon icon="ellipsis-h" />
</button>
</template>
</Popover>
</div>
<StatusContent
:status="messageForStatusContent"
:full-content="true"
>
<template v-slot:footer>
<span
slot="footer"
class="created-at"
>
{{ createdAt }}
</span>
</template>
</StatusContent>
</div>
</div>
@@ -5,8 +5,6 @@
</template>
<script>
import localeService from 'src/services/locale/locale.service.js'
export default {
name: 'Timeago',
props: ['date'],
@@ -18,7 +16,7 @@ export default {
if (this.date.getTime() === today.getTime()) {
return this.$t('display_date.today')
} else {
return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
return this.date.toLocaleDateString('en', { day: 'numeric', month: 'long' })
}
}
}
@@ -10,7 +10,7 @@ library.add(
faTimes
)
const shoutPanel = {
const chatPanel = {
props: [ 'floating' ],
data () {
return {
@@ -21,12 +21,12 @@ const shoutPanel = {
},
computed: {
messages () {
return this.$store.state.shout.messages
return this.$store.state.chat.messages
}
},
methods: {
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 = ''
},
togglePanel () {
@@ -35,19 +35,7 @@ const shoutPanel = {
userProfileLink (user) {
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
@@ -1,50 +1,52 @@
<template>
<div
v-if="!collapsed || !floating"
class="shout-panel"
class="chat-panel"
>
<div class="panel panel-default">
<div
class="panel-heading timeline-heading"
:class="{ 'shout-heading': floating }"
:class="{ 'chat-heading': floating }"
@click.stop.prevent="togglePanel"
>
<div class="title">
{{ $t('shoutbox.title') }}
<span>{{ $t('shoutbox.title') }}</span>
<FAIcon
v-if="floating"
icon="times"
class="close-icon"
/>
</div>
</div>
<div class="shout-window">
<div
v-chat-scroll
class="chat-window"
>
<div
v-for="message in messages"
:key="message.id"
class="shout-message"
class="chat-message"
>
<span class="shout-avatar">
<span class="chat-avatar">
<img :src="message.author.avatar">
</span>
<div class="shout-content">
<div class="chat-content">
<router-link
class="shout-name"
class="chat-name"
:to="userProfileLink(message.author)"
>
{{ message.author.username }}
</router-link>
<br>
<span class="shout-text">
<span class="chat-text">
{{ message.text }}
</span>
</div>
</div>
</div>
<div class="shout-input">
<div class="chat-input">
<textarea
v-model="currentMessage"
class="shout-input-textarea"
class="chat-input-textarea"
rows="1"
@keyup.enter="submit(currentMessage)"
/>
@@ -53,11 +55,11 @@
</div>
<div
v-else
class="shout-panel"
class="chat-panel"
>
<div class="panel panel-default">
<div
class="panel-heading stub timeline-heading shout-heading"
class="panel-heading stub timeline-heading chat-heading"
@click.stop.prevent="togglePanel"
>
<div class="title">
@@ -72,12 +74,12 @@
</div>
</template>
<script src="./shout_panel.js"></script>
<script src="./chat_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.floating-shout {
.floating-chat {
position: fixed;
right: 0px;
bottom: 0px;
@@ -85,39 +87,32 @@
max-width: 25em;
}
.shout-panel {
.shout-heading {
.chat-panel {
.chat-heading {
cursor: pointer;
.icon {
color: $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-x: hidden;
max-height: 20em;
}
.shout-window-container {
.chat-window-container {
height: 100%;
}
.shout-message {
.chat-message {
display: flex;
padding: 0.2em 0.5em
}
.shout-avatar {
.chat-avatar {
img {
height: 24px;
width: 24px;
@@ -128,7 +123,7 @@
}
}
.shout-input {
.chat-input {
display: flex;
textarea {
flex: 1;
@@ -138,7 +133,7 @@
}
}
.shout-panel {
.chat-panel {
.title {
display: flex;
justify-content: space-between;
+1 -1
View File
@@ -12,7 +12,7 @@ export default Vue.component('chat-title', {
],
computed: {
title () {
return this.user ? this.user.screen_name_ui : ''
return this.user ? this.user.screen_name : ''
},
htmlTitle () {
return this.user ? this.user.name_html : ''
@@ -50,6 +50,7 @@
.Conversation {
.conversation-status {
border-left: none;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
@@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
@@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
+30 -38
View File
@@ -57,7 +57,6 @@ const EmojiInput = {
required: true,
type: Function
},
// TODO VUE3: change to modelValue, change 'input' event to 'input'
value: {
/**
* Used for v-model
@@ -144,31 +143,32 @@ const EmojiInput = {
}
},
mounted () {
const { root } = this.$refs
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
const slots = this.$slots.default
if (!slots || slots.length === 0) return
const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
if (!input) return
this.input = input
this.resize()
input.addEventListener('blur', this.onBlur)
input.addEventListener('focus', this.onFocus)
input.addEventListener('paste', this.onPaste)
input.addEventListener('keyup', this.onKeyUp)
input.addEventListener('keydown', this.onKeyDown)
input.addEventListener('click', this.onClickInput)
input.addEventListener('transitionend', this.onTransition)
input.addEventListener('input', this.onInput)
input.elm.addEventListener('blur', this.onBlur)
input.elm.addEventListener('focus', this.onFocus)
input.elm.addEventListener('paste', this.onPaste)
input.elm.addEventListener('keyup', this.onKeyUp)
input.elm.addEventListener('keydown', this.onKeyDown)
input.elm.addEventListener('click', this.onClickInput)
input.elm.addEventListener('transitionend', this.onTransition)
input.elm.addEventListener('input', this.onInput)
},
unmounted () {
const { input } = this
if (input) {
input.removeEventListener('blur', this.onBlur)
input.removeEventListener('focus', this.onFocus)
input.removeEventListener('paste', this.onPaste)
input.removeEventListener('keyup', this.onKeyUp)
input.removeEventListener('keydown', this.onKeyDown)
input.removeEventListener('click', this.onClickInput)
input.removeEventListener('transitionend', this.onTransition)
input.removeEventListener('input', this.onInput)
input.elm.removeEventListener('blur', this.onBlur)
input.elm.removeEventListener('focus', this.onFocus)
input.elm.removeEventListener('paste', this.onPaste)
input.elm.removeEventListener('keyup', this.onKeyUp)
input.elm.removeEventListener('keydown', this.onKeyDown)
input.elm.removeEventListener('click', this.onClickInput)
input.elm.removeEventListener('transitionend', this.onTransition)
input.elm.removeEventListener('input', this.onInput)
}
},
watch: {
@@ -194,18 +194,11 @@ const EmojiInput = {
}
},
methods: {
focusPickerInput () {
const pickerEl = this.$refs.picker.$el
if (!pickerEl) return
const pickerInput = pickerEl.querySelector('input')
if (pickerInput) pickerInput.focus()
},
triggerShowPicker () {
this.showPicker = true
this.$refs.picker.startEmojiLoad()
this.$nextTick(() => {
this.scrollIntoView()
this.focusPickerInput()
})
// This temporarily disables "click outside" handler
// since external trigger also means click originates
@@ -216,12 +209,11 @@ const EmojiInput = {
}, 0)
},
togglePicker () {
this.input.focus()
this.input.elm.focus()
this.showPicker = !this.showPicker
if (this.showPicker) {
this.scrollIntoView()
this.$refs.picker.startEmojiLoad()
this.$nextTick(this.focusPickerInput)
}
},
replace (replacement) {
@@ -262,13 +254,13 @@ const EmojiInput = {
this.$emit('input', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) {
this.input.focus()
this.input.elm.focus()
}
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
// 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
})
},
@@ -285,9 +277,9 @@ const EmojiInput = {
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
this.input.focus()
this.input.elm.focus()
// 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
})
e.preventDefault()
@@ -349,7 +341,7 @@ const EmojiInput = {
}
this.$nextTick(() => {
const { offsetHeight } = this.input
const { offsetHeight } = this.input.elm
const { picker } = this.$refs
const pickerBottom = picker.$el.getBoundingClientRect().bottom
if (pickerBottom > window.innerHeight) {
@@ -414,8 +406,8 @@ const EmojiInput = {
// Scroll the input element to the position of the cursor
this.$nextTick(() => {
this.input.blur()
this.input.focus()
this.input.elm.blur()
this.input.elm.focus()
})
}
// Disable suggestions hotkeys if suggestions are hidden
@@ -444,7 +436,7 @@ const EmojiInput = {
// de-focuses the element (i.e. default browser behavior)
if (key === 'Escape') {
if (!this.temporarilyHideSuggestions) {
this.input.focus()
this.input.elm.focus()
}
}
@@ -480,7 +472,7 @@ const EmojiInput = {
if (!panel) return
const picker = this.$refs.picker.$el
const panelBody = this.$refs['panel-body']
const { offsetHeight, offsetTop } = this.input
const { offsetHeight, offsetTop } = this.input.elm
const offsetBottom = offsetTop + offsetHeight
this.setPlacement(panelBody, panel, offsetBottom)
@@ -494,7 +486,7 @@ const EmojiInput = {
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
target.style.top = 'auto'
target.style.bottom = this.input.offsetHeight + 'px'
target.style.bottom = this.input.elm.offsetHeight + 'px'
}
},
overflowsBottom (el) {
@@ -1,6 +1,5 @@
<template>
<div
ref="root"
v-click-outside="onClickOutside"
class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
@@ -10,7 +9,6 @@
<button
v-if="!hideEmojiButton"
class="button-unstyled emoji-picker-icon"
type="button"
@click.prevent="togglePicker"
>
<FAIcon :icon="['far', 'smile-beam']" />
+2 -2
View File
@@ -116,8 +116,8 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
}).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
displayText: screen_name_ui,
}).map(({ screen_name, name, profile_image_url_original }) => ({
displayText: screen_name,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '
@@ -0,0 +1,102 @@
<template>
<div class="import-export-container">
<slot name="before" />
<button
class="btn button-default"
@click="exportData"
>
{{ exportLabel }}
</button>
<button
class="btn button-default"
@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>
+10 -11
View File
@@ -7,7 +7,10 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:content="{close}">
<div
slot="content"
slot-scope="{close}"
>
<div class="dropdown-menu">
<button
v-if="canMute && !status.thread_muted"
@@ -117,15 +120,16 @@
/><span>{{ $t("user_card.report") }}</span>
</button>
</div>
</template>
<template v-slot:trigger>
<button class="button-unstyled popover-trigger">
</div>
<span
slot="trigger"
class="popover-trigger"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="ellipsis-h"
/>
</button>
</template>
</span>
</Popover>
</template>
@@ -135,11 +139,6 @@
@import '../../_variables.scss';
.ExtraButtons {
/* override of popover internal stuff */
.popover-trigger-button {
width: auto;
}
.popover-trigger {
position: static;
padding: 10px;
@@ -2,7 +2,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
const FeaturesPanel = {
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 },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
@@ -8,8 +8,8 @@
</div>
<div class="panel-body features-panel">
<ul>
<li v-if="shout">
{{ $t('features_panel.shout') }}
<li v-if="chat">
{{ $t('features_panel.chat') }}
</li>
<li v-if="pleromaChatMessages">
{{ $t('features_panel.pleroma_chat_messages') }}
-52
View File
@@ -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
-88
View File
@@ -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>
+8 -4
View File
@@ -1,10 +1,14 @@
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 {
components: {
Select
},
props: [
'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
],
+13 -4
View File
@@ -22,7 +22,12 @@
class="opt-l"
:for="name + '-o'"
/>
<Select
<label
:for="name + '-font-switcher'"
class="select"
:disabled="!present"
>
<select
:id="name + '-font-switcher'"
v-model="preset"
:disabled="!present"
@@ -35,7 +40,12 @@
>
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
<input
v-if="isCustom"
:id="name"
@@ -55,8 +65,7 @@
min-width: 10em;
}
&.custom {
/* TODO Should make proper joiners... */
.font-switcher {
.select {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
@@ -71,14 +71,6 @@
}
}
.global-success {
background-color: var(--alertPopupSuccess, $fallback--cGreen);
color: var(--alertPopupSuccessText, $fallback--text);
.svg-inline--fa {
color: var(--alertPopupSuccessText, $fallback--text);
}
}
.global-info {
background-color: var(--alertPopupNeutral, $fallback--fg);
color: var(--alertPopupNeutralText, $fallback--text);
@@ -3,35 +3,51 @@
<label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }}
</label>
<Select
<label
for="interface-language-switcher"
class="select"
>
<select
id="interface-language-switcher"
v-model="language"
>
<option
v-for="lang in languages"
:key="lang.code"
:value="lang.code"
v-for="(langCode, i) in languageCodes"
:key="langCode"
:value="langCode"
>
{{ lang.name }}
{{ languageNames[i] }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
</template>
<script>
import languagesObject from '../../i18n/messages'
import localeService from '../../services/locale/locale.service.js'
import ISO6391 from 'iso-639-1'
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 {
components: {
Select
},
computed: {
languages () {
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
languageCodes () {
return languagesObject.languages
},
languageNames () {
return _.map(this.languageCodes, this.getLanguageName)
},
language: {
@@ -45,13 +61,12 @@ export default {
methods: {
getLanguageName (code) {
const specialLanguageNames = {
'ja_easy': 'やさしいにほんご',
'zh': '简体中文',
'zh_Hant': '繁體中文'
'ja': 'Japanese (日本語)',
'ja_easy': 'Japanese (やさしいにほんご)',
'zh': 'Simplified Chinese (简体中文)',
'zh_Hant': 'Traditional Chinese (繁體中文)'
}
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
const browserLocale = localeService.internalToBrowserLocale(code)
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
return specialLanguageNames[code] || ISO6391.getName(code)
}
}
}
@@ -73,21 +73,11 @@
}
}
@keyframes media-fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-image {
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
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 {
@@ -25,7 +25,6 @@
<div>
<button
class="button-unstyled -link"
type="button"
@click.prevent="requireTOTP"
>
{{ $t('login.enter_two_factor_code') }}
@@ -33,7 +32,6 @@
<br>
<button
class="button-unstyled -link"
type="button"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
-2
View File
@@ -27,7 +27,6 @@
<div>
<button
class="button-unstyled -link"
type="button"
@click.prevent="requireRecovery"
>
{{ $t('login.enter_recovery_code') }}
@@ -35,7 +34,6 @@
<br>
<button
class="button-unstyled -link"
type="button"
@click.prevent="abortMFA"
>
{{ $t('general.cancel') }}
@@ -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 Popover from '../popover/popover.vue'
library.add(faChevronDown)
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
const STRIP_MEDIA = 'mrf_tag:media-strip'
const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
@@ -8,7 +8,7 @@
@show="setToggled(true)"
@close="setToggled(false)"
>
<template v-slot:content>
<div slot="content">
<div class="dropdown-menu">
<span v-if="user.is_local">
<button
@@ -50,98 +50,96 @@
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_NSFW)"
>
{{ $t('user_card.admin_menu.force_nsfw') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
{{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.STRIP_MEDIA)"
>
{{ $t('user_card.admin_menu.strip_media') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
{{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_UNLISTED)"
>
{{ $t('user_card.admin_menu.force_unlisted') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
{{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.SANDBOX)"
>
{{ $t('user_card.admin_menu.sandbox') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
{{ $t('user_card.admin_menu.sandbox') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.QUARANTINE)"
>
{{ $t('user_card.admin_menu.quarantine') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
{{ $t('user_card.admin_menu.quarantine') }}
</button>
</span>
</div>
</template>
<template v-slot:trigger>
</div>
<button
class="btn button-default btn-block moderation-tools-button"
slot="trigger"
class="btn button-default btn-block"
:class="{ toggled }"
>
{{ $t('user_card.admin_menu.moderation') }}
<FAIcon icon="chevron-down" />
</button>
</template>
</Popover>
<portal to="modal">
<DialogModal
v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)"
>
<template v-slot:header>
<template slot="header">
{{ $t('user_card.admin_menu.delete_user') }}
</template>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
<template v-slot:footer>
<template slot="footer">
<button
class="btn button-default"
@click="deleteUserDialog(false)"
@@ -165,6 +163,25 @@
<style lang="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 {
height: 100%;
.trigger {
@@ -172,10 +189,4 @@
height: 100%;
}
}
.moderation-tools-button {
svg,i {
font-size: 0.8em;
}
}
</style>
+14 -22
View File
@@ -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 { library } from '@fortawesome/fontawesome-svg-core'
@@ -7,12 +7,10 @@ import {
faGlobe,
faBookmark,
faEnvelope,
faChevronDown,
faChevronUp,
faHome,
faComments,
faBell,
faInfoCircle,
faStream
faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -20,12 +18,10 @@ library.add(
faGlobe,
faBookmark,
faEnvelope,
faChevronDown,
faChevronUp,
faHome,
faComments,
faBell,
faInfoCircle,
faStream
faInfoCircle
)
const NavPanel = {
@@ -34,20 +30,16 @@ const NavPanel = {
this.$store.dispatch('startFetchingFollowRequests')
}
},
components: {
TimelineMenuContent
},
data () {
return {
showTimelines: false
}
},
methods: {
toggleTimelines () {
this.showTimelines = !this.showTimelines
}
},
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({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
+13 -57
View File
@@ -3,33 +3,19 @@
<div class="panel panel-default">
<ul>
<li v-if="currentUser || !privateMode">
<button
class="button-unstyled menu-item"
@click="toggleTimelines"
<router-link
:to="{ name: timelinesRoute }"
:class="onTimelineRoute && 'router-link-active'"
>
<FAIcon
fixed-width
class="fa-scale-110"
icon="stream"
icon="home"
/>{{ $t("nav.timelines") }}
<FAIcon
class="timelines-chevron"
fixed-width
:icon="showTimelines ? 'chevron-up' : 'chevron-down'"
/>
</button>
<div
v-show="showTimelines"
class="timelines-background"
>
<TimelineMenuContent class="timelines" />
</div>
</router-link>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
>
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
<FAIcon
fixed-width
class="fa-scale-110"
@@ -38,10 +24,7 @@
</router-link>
</li>
<li v-if="currentUser && pleromaChatMessagesAvailable">
<router-link
class="menu-item"
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
>
<router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
<div
v-if="unreadChatCount"
class="badge badge-notification"
@@ -56,10 +39,7 @@
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
<router-link
class="menu-item"
:to="{ name: 'friend-requests' }"
>
<router-link :to="{ name: 'friend-requests' }">
<FAIcon
fixed-width
class="fa-scale-110"
@@ -74,10 +54,7 @@
</router-link>
</li>
<li>
<router-link
class="menu-item"
:to="{ name: 'about' }"
>
<router-link :to="{ name: 'about' }">
<FAIcon
fixed-width
class="fa-scale-110"
@@ -114,14 +91,14 @@
border-color: var(--border, $fallback--border);
padding: 0;
&:first-child .menu-item {
&:first-child a {
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
border-top-left-radius: $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: var(--panelRadius, $fallback--panelRadius);
border-bottom-left-radius: $fallback--panelRadius;
@@ -133,15 +110,13 @@
border: none;
}
.menu-item {
a {
display: block;
box-sizing: border-box;
align-items: stretch;
height: 3.5em;
line-height: 3.5em;
padding: 0 1em;
width: 100%;
color: $fallback--link;
color: var(--link, $fallback--link);
&:hover {
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 {
margin-right: 0.8em;
}
+5 -5
View File
@@ -11,7 +11,7 @@
>
<small>
<router-link :to="userProfileLink">
{{ notification.from_profile.screen_name_ui }}
{{ notification.from_profile.screen_name }}
</router-link>
</small>
<button
@@ -54,14 +54,14 @@
<bdi
v-if="!!notification.from_profile.name_html"
class="username"
:title="'@'+notification.from_profile.screen_name_ui"
:title="'@'+notification.from_profile.screen_name"
v-html="notification.from_profile.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span
v-else
class="username"
:title="'@'+notification.from_profile.screen_name_ui"
:title="'@'+notification.from_profile.screen_name"
>{{ notification.from_profile.name }}</span>
<span v-if="notification.type === 'like'">
<FAIcon
@@ -152,7 +152,7 @@
:to="userProfileLink"
class="follow-name"
>
@{{ notification.from_profile.screen_name_ui }}
@{{ notification.from_profile.screen_name }}
</router-link>
<div
v-if="notification.type === 'follow_request'"
@@ -177,7 +177,7 @@
class="move-text"
>
<router-link :to="targetUserProfileLink">
@{{ notification.target.screen_name_ui }}
@{{ notification.target.screen_name }}
</router-link>
</div>
<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,6 +1,5 @@
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import {
notificationsFromStore,
@@ -18,10 +17,6 @@ library.add(
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
const Notifications = {
components: {
Notification,
NotificationFilters
},
props: {
// Disables display of panel header
noHeading: Boolean,
@@ -40,6 +35,11 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
created () {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
notificationsFetcher.fetchAndUpdate({ store, credentials })
},
computed: {
mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
@@ -70,6 +70,9 @@ const Notifications = {
},
...mapGetters(['unreadChatCount'])
},
components: {
Notification
},
watch: {
unseenCountTitle (count) {
if (count > 0) {
@@ -1,6 +1,6 @@
@import '../../_variables.scss';
.Notifications {
.notifications {
&:not(.minimal) {
// a bit of a hack to allow scrolling below notifications
padding-bottom: 15em;
@@ -11,10 +11,6 @@
color: var(--text, $fallback--text);
}
.notifications-footer {
border: none;
}
.notification {
position: relative;
@@ -86,6 +82,7 @@
}
}
.follow-text, .move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
@@ -1,7 +1,7 @@
<template>
<div
:class="{ minimal: minimalMode }"
class="Notifications"
class="notifications"
>
<div :class="mainClass">
<div
@@ -22,7 +22,6 @@
>
{{ $t('notifications.read') }}
</button>
<NotificationFilters />
</div>
<div class="panel-body">
<div
@@ -35,10 +34,10 @@
<notification :notification="notification" />
</div>
</div>
<div class="panel-footer notifications-footer">
<div class="panel-footer">
<div
v-if="bottomedOut"
class="new-status-notification text-center faint"
class="new-status-notification text-center panel-footer faint"
>
{{ $t('notifications.no_more_notifications') }}
</div>
@@ -47,13 +46,13 @@
class="button-unstyled -link -fullwidth"
@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') }}
</div>
</button>
<div
v-else
class="new-status-notification text-center"
class="new-status-notification text-center panel-footer"
>
<FAIcon
icon="circle-notch"
@@ -53,7 +53,7 @@
type="submit"
class="btn button-default btn-block"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
</div>
+1 -6
View File
@@ -58,12 +58,7 @@
{{ $t('polls.vote') }}
</button>
<div class="total">
<template v-if="typeof poll.voters_count === 'number'">
{{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}&nbsp;·&nbsp;
</template>
<template v-else>
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
</template>
{{ totalVotesCount }} {{ $t("polls.votes") }}&nbsp;·&nbsp;
</div>
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
<Timeago
+2 -4
View File
@@ -1,21 +1,19 @@
import * as DateUtils from 'src/services/date_utils/date_utils.js'
import { uniq } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import Select from '../select/select.vue'
import {
faTimes,
faChevronDown,
faPlus
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faChevronDown,
faPlus
)
export default {
components: {
Select
},
name: 'PollForm',
props: ['visible'],
data: () => ({
+53 -26
View File
@@ -21,17 +21,20 @@
@keydown.enter.stop.prevent="nextOption(index)"
>
</div>
<button
<div
v-if="options.length > 2"
class="delete-option button-unstyled -hover-highlight"
@click="deleteOption(index)"
class="icon-container"
>
<FAIcon icon="times" />
</button>
<FAIcon
icon="times"
class="delete"
@click="deleteOption(index)"
/>
</div>
<button
</div>
<a
v-if="options.length < maxOptions"
class="add-option faint button-unstyled -hover-highlight"
class="add-option faint"
@click="addOption"
>
<FAIcon
@@ -40,25 +43,29 @@
/>
{{ $t("polls.add_option") }}
</button>
</a>
<div class="poll-type-expiry">
<div
class="poll-type"
:title="$t('polls.type')"
>
<Select
<label
for="poll-type-selector"
class="select"
>
<select
v-model="pollType"
class="poll-type-select"
unstyled="true"
class="select"
@change="updatePollToParent"
>
<option value="single">
{{ $t('polls.single_choice') }}
</option>
<option value="multiple">
{{ $t('polls.multiple_choices') }}
</option>
</Select>
<option value="single">{{ $t('polls.single_choice') }}</option>
<option value="multiple">{{ $t('polls.multiple_choices') }}</option>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
<div
class="poll-expiry"
@@ -72,10 +79,9 @@
:max="maxExpirationInCurrentUnit"
@change="expiryAmountChange"
>
<Select
<label class="expiry-unit select">
<select
v-model="expiryUnit"
unstyled="true"
class="expiry-unit"
@change="expiryAmountChange"
>
<option
@@ -85,7 +91,12 @@
>
{{ $t(`time.${unit}_short`, ['']) }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
</div>
</div>
@@ -105,6 +116,7 @@
align-self: flex-start;
padding-top: 0.25em;
padding-left: 0.1em;
cursor: pointer;
}
.poll-option {
@@ -123,11 +135,19 @@
}
}
.delete-option {
.icon-container {
// Hack: Move the icon over the input box
width: 1.5em;
margin-left: -1.5em;
z-index: 1;
.delete {
cursor: pointer;
&:hover {
color: inherit;
}
}
}
.poll-type-expiry {
@@ -139,9 +159,10 @@
.poll-type {
margin-right: 0.75em;
flex: 1 1 60%;
.poll-type-select {
padding-right: 0.75em;
.select {
border: none;
box-shadow: none;
background-color: transparent;
}
}
@@ -152,6 +173,12 @@
width: 3em;
text-align: right;
}
.expiry-unit {
border: none;
box-shadow: none;
background-color: transparent;
}
}
}
</style>
+5 -18
View File
@@ -3,32 +3,25 @@ const Popover = {
props: {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
// Either 'top' or 'bottom'
placement: String,
// Takes object with properties 'x' and 'y', values of these can be
// 'container' for using offsetParent as boundaries for either axis
// or 'viewport'
boundTo: Object,
// Takes a selector to use as a replacement for the parent container
// for getting boundaries for x an y axis
boundToSelector: String,
// Takes a top/bottom/left/right object, how much space to leave
// between boundary and popover element
margin: Object,
// Takes a x/y object and tells how many pixels to offset from
// anchor point on either axis
offset: Object,
// Replaces the classes you may want for the popover container.
// Use 'popover-default' in addition to get the default popover
// styles with your custom class.
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
@@ -54,11 +47,8 @@ const Popover = {
}
// 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
// SVGs don't have offsetWidth/Height, use fallback
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
const screenBox = anchorEl.getBoundingClientRect()
// Screen position of the origin point for popover
const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
@@ -117,11 +107,11 @@ const Popover = {
const yOffset = (this.offset && this.offset.y) || 0
const translateY = usingTop
? -anchorHeight + vPadding - yOffset - content.offsetHeight
? -anchorEl.offsetHeight + vPadding - yOffset - content.offsetHeight
: yOffset
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,
// single translate or translate3d resulted in blurry text.
@@ -131,12 +121,9 @@ const Popover = {
}
},
showPopover () {
const wasHidden = this.hidden
if (this.hidden) this.$emit('show')
this.hidden = false
this.$nextTick(() => {
if (wasHidden) this.$emit('show')
this.updateStyles()
})
this.$nextTick(this.updateStyles)
},
hidePopover () {
if (!this.hidden) this.$emit('close')
+5 -31
View File
@@ -6,7 +6,6 @@
<button
ref="trigger"
class="button-unstyled -fullwidth popover-trigger-button"
type="button"
@click="onClick"
>
<slot name="trigger" />
@@ -82,9 +81,10 @@
.dropdown-item {
line-height: 21px;
margin-right: 5px;
overflow: auto;
display: block;
padding: .5em 0.75em;
padding: .25rem 1.0rem .25rem 1.5rem;
clear: both;
font-weight: 400;
text-align: inherit;
@@ -100,9 +100,10 @@
--btnText: var(--popoverText, $fallback--text);
&-icon {
padding-left: 0.5rem;
svg {
width: 22px;
margin-right: 0.75rem;
margin-right: 0.25rem;
color: var(--menuPopoverIcon, $fallback--icon)
}
}
@@ -121,33 +122,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>
@@ -11,10 +11,10 @@ import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faSmileBeam,
faPollH,
faUpload,
@@ -24,6 +24,7 @@ import {
} from '@fortawesome/free-solid-svg-icons'
library.add(
faChevronDown,
faSmileBeam,
faPollH,
faUpload,
@@ -83,7 +84,6 @@ const PostStatusForm = {
PollForm,
ScopeSelector,
Checkbox,
Select,
Attachment,
StatusContent
},
@@ -115,7 +115,7 @@ const PostStatusForm = {
? this.copyMessageScope
: this.$store.state.users.currentUser.default_scope
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
const { postContentType: contentType } = this.$store.getters.mergedConfig
return {
dropFiles: [],
@@ -126,7 +126,7 @@ const PostStatusForm = {
newStatus: {
spoilerText: this.subject || '',
status: statusText,
nsfw: !!sensitiveByDefault,
nsfw: false,
files: [],
poll: {},
mediaDescriptions: {},
@@ -189,7 +189,11 @@
v-if="postFormats.length > 1"
class="text-format"
>
<Select
<label
for="post-content-type"
class="select"
>
<select
id="post-content-type"
v-model="newStatus.contentType"
class="form-control"
@@ -201,7 +205,12 @@
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
@@ -263,7 +272,7 @@
disabled
class="btn button-default"
>
{{ $t('post_status.post') }}
{{ $t('general.submit') }}
</button>
<!-- touchstart is used to keep the OSK at the same position after a message send -->
<button
@@ -273,7 +282,7 @@
@touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
>
{{ $t('post_status.post') }}
{{ $t('general.submit') }}
</button>
</div>
<div
@@ -293,12 +302,11 @@
:key="file.url"
class="media-upload-wrapper"
>
<button
class="button-unstyled hider"
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
@click="removeMediaFile(file)"
>
<FAIcon icon="times" />
</button>
/>
<attachment
:attachment="file"
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
@@ -508,11 +516,26 @@
}
.attachments .media-upload-wrapper {
position: relative;
padding: 0 0.5em;
.attachment {
margin: 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,12 +23,6 @@ const ReactButton = {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
}
close()
},
focusInput () {
this.$nextTick(() => {
const input = this.$el.querySelector('input')
if (input) input.focus()
})
}
},
computed: {
+17 -26
View File
@@ -1,14 +1,15 @@
<template>
<Popover
trigger="click"
class="ReactButton"
placement="top"
:offset="{ y: 5 }"
:bound-to="{ x: 'container' }"
remove-padding
@show="focusInput"
>
<template v-slot:content="{close}">
<div
slot="content"
slot-scope="{close}"
>
<div class="reaction-picker-filter">
<input
v-model="filterWord"
@@ -38,18 +39,17 @@
</span>
<div class="reaction-bottom-fader" />
</div>
</template>
<template v-slot:trigger>
<button
class="button-unstyled popover-trigger"
</div>
<span
slot="trigger"
class="ReactButton"
:title="$t('tool_tip.add_reaction')"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
:icon="['far', 'smile-beam']"
/>
</button>
</template>
</span>
</Popover>
</template>
@@ -58,24 +58,22 @@
<style lang="scss">
@import '../../_variables.scss';
.ReactButton {
.reaction-picker-filter {
.reaction-picker-filter {
padding: 0.5em;
display: flex;
input {
flex: 1;
}
}
}
.reaction-picker-divider {
.reaction-picker-divider {
height: 1px;
width: 100%;
margin: 0.5em;
background-color: var(--border, $fallback--border);
}
}
.reaction-picker {
.reaction-picker {
width: 10em;
height: 9em;
font-size: 1.5em;
@@ -92,8 +90,7 @@
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
/* Autoprefixed seem to ignore this one, and also syntax is different */
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
@@ -108,14 +105,9 @@
transform: scale(1.25);
}
}
}
}
/* override of popover internal stuff */
.popover-trigger-button {
width: auto;
}
.popover-trigger {
.ReactButton {
padding: 10px;
margin: -10px;
@@ -123,7 +115,6 @@
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
}
</style>
+4 -13
View File
@@ -10,8 +10,7 @@ const registration = {
fullname: '',
username: '',
password: '',
confirm: '',
reason: ''
confirm: ''
},
captcha: {}
}),
@@ -25,8 +24,7 @@ const registration = {
confirm: {
required,
sameAsPassword: sameAs('password')
},
reason: { required: requiredIf(() => this.accountApprovalRequired) }
}
}
}
},
@@ -40,10 +38,7 @@ const registration = {
computed: {
token () { return this.$route.params.token },
bioPlaceholder () {
return this.replaceNewlines(this.$t('registration.bio_placeholder'))
},
reasonPlaceholder () {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n')
},
...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
@@ -51,8 +46,7 @@ const registration = {
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired,
accountApprovalRequired: (state) => state.instance.accountApprovalRequired
accountActivationRequired: (state) => state.instance.accountActivationRequired
})
},
methods: {
@@ -79,9 +73,6 @@ const registration = {
},
setCaptcha () {
this.getCaptcha().then(cpt => { this.captcha = cpt })
},
replaceNewlines (str) {
return str.replace(/\s*\n\s*/g, ' \n')
}
}
}
+1 -18
View File
@@ -162,23 +162,6 @@
</ul>
</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
v-if="captcha.type != 'none'"
id="captcha-group"
@@ -230,7 +213,7 @@
type="submit"
class="btn button-default"
>
{{ $t('registration.register') }}
{{ $t('general.submit') }}
</button>
</div>
</div>
@@ -8,7 +8,6 @@
class="button-unstyled scope"
:class="css.direct"
:title="$t('post_status.scope.direct')"
type="button"
@click="changeVis('direct')"
>
<FAIcon
@@ -21,7 +20,6 @@
class="button-unstyled scope"
:class="css.private"
:title="$t('post_status.scope.private')"
type="button"
@click="changeVis('private')"
>
<FAIcon
@@ -34,7 +32,6 @@
class="button-unstyled scope"
:class="css.unlisted"
:title="$t('post_status.scope.unlisted')"
type="button"
@click="changeVis('unlisted')"
>
<FAIcon
@@ -47,7 +44,6 @@
class="button-unstyled scope"
:class="css.public"
:title="$t('post_status.scope.public')"
type="button"
@click="changeVis('public')"
>
<FAIcon
-1
View File
@@ -15,7 +15,6 @@
>
<button
class="btn button-default search-button"
type="submit"
@click="newQuery(searchTerm)"
>
<FAIcon icon="search" />
-3
View File
@@ -7,7 +7,6 @@
v-if="hidden"
class="button-unstyled nav-icon"
:title="$t('nav.search')"
type="button"
@click.prevent.stop="toggleHidden"
>
<FAIcon
@@ -28,7 +27,6 @@
>
<button
class="button-default search-button"
type="submit"
@click="find(searchTerm)"
>
<FAIcon
@@ -38,7 +36,6 @@
</button>
<button
class="button-unstyled cancel-search"
type="button"
@click.prevent.stop="toggleHidden"
>
<FAIcon
-21
View File
@@ -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'
]
}
-62
View File
@@ -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"
:get-key="getKey"
>
<template v-slot:item="{item}">
<template
slot="item"
slot-scope="{item}"
>
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@@ -41,7 +44,7 @@
/>
</div>
</template>
<template v-slot:empty>
<template slot="empty">
<slot name="empty" />
</template>
</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)
}
}
}
@@ -1,29 +0,0 @@
<template>
<label
class="ChoiceSetting"
>
<slot />
<Select
:value="state"
:disabled="disabled"
@change="update"
>
<option
v-for="option in options"
:key="option.key"
:value="option.value"
>
{{ option.label }}
{{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
</option>
</Select>
<ModifiedIndicator :changed="isChanged" />
</label>
</template>
<script src="./choice_setting.js"></script>
<style lang="scss">
.ChoiceSetting {
}
</style>
@@ -1,51 +0,0 @@
<template>
<span
v-if="changed"
class="ModifiedIndicator"
>
<Popover
trigger="hover"
>
<template v-slot:trigger>
&nbsp;
<FAIcon
icon="wrench"
:aria-label="$t('settings.setting_changed')"
/>
</template>
<template v-slot:content>
<div class="modified-tooltip">
{{ $t('settings.setting_changed') }}
</div>
</template>
</Popover>
</span>
</template>
<script>
import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faWrench } from '@fortawesome/free-solid-svg-icons'
library.add(
faWrench
)
export default {
components: { Popover },
props: ['changed']
}
</script>
<style lang="scss">
.ModifiedIndicator {
display: inline-block;
position: relative;
.modified-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
}
}
</style>
@@ -1,15 +1,29 @@
import { defaultState as configDefaultState } from 'src/modules/config.js'
import {
instanceDefaultProperties,
multiChoiceProperties,
defaultState as configDefaultState
} from 'src/modules/config.js'
const SharedComputedObject = () => ({
user () {
return this.$store.state.users.currentUser
},
// Getting values for default properties
...Object.keys(configDefaultState)
// Getting localized values for instance-default properties
...instanceDefaultProperties
.filter(key => multiChoiceProperties.includes(key))
.map(key => [
key + 'DefaultValue',
function () {
return this.$store.getters.defaultConfig[key]
return this.$store.getters.instanceDefaultConfig[key]
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
...instanceDefaultProperties
.filter(key => !multiChoiceProperties.includes(key))
.map(key => [
key + 'LocalizedValue',
function () {
return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
@@ -2,55 +2,10 @@ import Modal from 'src/components/modal/modal.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
import Popover from '../popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { cloneDeep } from 'lodash'
import {
newImporter,
newExporter
} from 'src/services/export_import/export_import.js'
import {
faTimes,
faFileUpload,
faFileDownload,
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
import {
faWindowMinimize
} from '@fortawesome/free-regular-svg-icons'
const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1
const PLEROMAFE_SETTINGS_MINOR_VERSION = 0
library.add(
faTimes,
faWindowMinimize,
faFileUpload,
faFileDownload,
faChevronDown
)
const SettingsModal = {
data () {
return {
dataImporter: newImporter({
validator: this.importValidator,
onImport: this.onImport,
onImportFailure: this.onImportFailure
}),
dataThemeExporter: newExporter({
filename: 'pleromafe_settings.full',
getExportedObject: () => this.generateExport(true)
}),
dataExporter: newExporter({
filename: 'pleromafe_settings',
getExportedObject: () => this.generateExport()
})
}
},
components: {
Modal,
Popover,
SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'),
{
@@ -66,85 +21,6 @@ const SettingsModal = {
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
},
importValidator (data) {
if (!Array.isArray(data._pleroma_settings_version)) {
return {
messageKey: 'settings.file_import_export.invalid_file'
}
}
const [major, minor] = data._pleroma_settings_version
if (major > PLEROMAFE_SETTINGS_MAJOR_VERSION) {
return {
messageKey: 'settings.file_export_import.errors.file_too_new',
messageArgs: {
fileMajor: major,
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
}
}
}
if (major < PLEROMAFE_SETTINGS_MAJOR_VERSION) {
return {
messageKey: 'settings.file_export_import.errors.file_too_old',
messageArgs: {
fileMajor: major,
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
}
}
}
if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) {
this.$store.dispatch('pushGlobalNotice', {
level: 'warning',
messageKey: 'settings.file_export_import.errors.file_slightly_new'
})
}
return true
},
onImportFailure (result) {
if (result.error) {
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' })
} else {
this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' })
}
},
onImport (data) {
if (data) { this.$store.dispatch('loadSettings', data) }
},
restore () {
this.dataImporter.importData()
},
backup () {
this.dataExporter.exportData()
},
backupWithTheme () {
this.dataThemeExporter.exportData()
},
generateExport (theme = false) {
const { config } = this.$store.state
let sample = config
if (!theme) {
const ignoreList = new Set([
'customTheme',
'customThemeSource',
'colors'
])
sample = Object.fromEntries(
Object
.entries(sample)
.filter(([key]) => !ignoreList.has(key))
)
}
const clone = cloneDeep(sample)
clone._pleroma_settings_version = [
PLEROMAFE_SETTINGS_MAJOR_VERSION,
PLEROMAFE_SETTINGS_MINOR_VERSION
]
return clone
}
},
computed: {
@@ -31,84 +31,20 @@
</transition>
<button
class="btn button-default"
:title="$t('general.peek')"
@click="peekModal"
>
<FAIcon
:icon="['far', 'window-minimize']"
fixed-width
/>
{{ $t('general.peek') }}
</button>
<button
class="btn button-default"
:title="$t('general.close')"
@click="closeModal"
>
<FAIcon
icon="times"
fixed-width
/>
{{ $t('general.close') }}
</button>
</div>
<div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" />
</div>
<div class="panel-footer">
<Popover
class="export"
trigger="click"
placement="top"
:offset="{ y: 5, x: 5 }"
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:trigger>
<button
class="btn button-default"
:title="$t('general.close')"
>
<span>{{ $t("settings.file_export_import.backup_restore") }}</span>
<FAIcon
icon="chevron-down"
/>
</button>
</template>
<template v-slot:content="{close}">
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="backup"
@click="close"
>
<FAIcon
icon="file-download"
fixed-width
/><span>{{ $t("settings.file_export_import.backup_settings") }}</span>
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="backupWithTheme"
@click="close"
>
<FAIcon
icon="file-download"
fixed-width
/><span>{{ $t("settings.file_export_import.backup_settings_theme") }}</span>
</button>
<button
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="restore"
@click="close"
>
<FAIcon
icon="file-upload"
fixed-width
/><span>{{ $t("settings.file_export_import.restore_settings") }}</span>
</button>
</div>
</template>
</Popover>
</div>
</div>
</Modal>
</template>
@@ -7,24 +7,13 @@
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
> div,
> label {
display: block;
> div {
margin-bottom: .5em;
&:last-child {
margin-bottom: 0;
}
}
.select-multiple {
display: flex;
.option-list {
margin: 0;
padding-left: .5em;
}
}
&:last-child {
border-bottom: none;
padding-bottom: 0;
@@ -1,23 +1,24 @@
import { filter, trim } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
library.add(
faChevronDown
)
const FilteringTab = {
data () {
return {
muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n'),
replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.reply_visibility_${mode}`)
}))
muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
}
},
components: {
BooleanSetting,
ChoiceSetting
Checkbox
},
computed: {
...SharedComputedObject(),
@@ -5,53 +5,69 @@
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
<BooleanSetting path="notificationVisibility.likes">
<Checkbox v-model="notificationVisibility.likes">
{{ $t('settings.notification_visibility_likes') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="notificationVisibility.repeats">
<Checkbox v-model="notificationVisibility.repeats">
{{ $t('settings.notification_visibility_repeats') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="notificationVisibility.follows">
<Checkbox v-model="notificationVisibility.follows">
{{ $t('settings.notification_visibility_follows') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="notificationVisibility.mentions">
<Checkbox v-model="notificationVisibility.mentions">
{{ $t('settings.notification_visibility_mentions') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="notificationVisibility.moves">
<Checkbox v-model="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="notificationVisibility.emojiReactions">
<Checkbox v-model="notificationVisibility.emojiReactions">
{{ $t('settings.notification_visibility_emoji_reactions') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
<ChoiceSetting
id="replyVisibility"
path="replyVisibility"
:options="replyVisibilityOptions"
>
{{ $t('settings.replies_in_timeline') }}
</ChoiceSetting>
<div>
<BooleanSetting path="hidePostStats">
{{ $t('settings.hide_post_stats') }}
</BooleanSetting>
{{ $t('settings.replies_in_timeline') }}
<label
for="replyVisibility"
class="select"
>
<select
id="replyVisibility"
v-model="replyVisibility"
>
<option
value="all"
selected
>{{ $t('settings.reply_visibility_all') }}</option>
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
<div>
<BooleanSetting path="hideUserStats">
{{ $t('settings.hide_user_stats') }}
</BooleanSetting>
<Checkbox v-model="hidePostStats">
{{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
</Checkbox>
</div>
<div>
<Checkbox v-model="hideUserStats">
{{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
</Checkbox>
</div>
</div>
<div class="setting-item">
@@ -59,14 +75,14 @@
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
v-model="muteWordsString"
class="resize-height"
v-model="muteWordsString"
/>
</div>
<div>
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
<Checkbox v-model="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
</Checkbox>
</div>
</div>
</div>
@@ -1,25 +1,21 @@
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faGlobe
} from '@fortawesome/free-solid-svg-icons'
library.add(
faChevronDown,
faGlobe
)
const GeneralTab = {
data () {
return {
subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
})),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
@@ -30,27 +26,18 @@ const GeneralTab = {
}
},
components: {
BooleanSetting,
ChoiceSetting,
Checkbox,
InterfaceLanguageSwitcher
},
computed: {
postFormats () {
return this.$store.state.instance.postFormats || []
},
postContentOptions () {
return this.postFormats.map(format => ({
key: format,
value: format,
label: this.$t(`post_status.content_type["${format}"]`)
}))
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceWallpaperUsed () {
return this.$store.state.instance.background &&
!this.$store.state.users.currentUser.background_image
},
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
...SharedComputedObject()
}
}
@@ -7,24 +7,14 @@
<interface-language-switcher />
</li>
<li v-if="instanceSpecificPanelPresent">
<BooleanSetting path="hideISP">
<Checkbox v-model="hideISP">
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="sidebarRight">
{{ $t('settings.right_sidebar') }}
</BooleanSetting>
</Checkbox>
</li>
<li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper">
<Checkbox v-model="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }}
</BooleanSetting>
</li>
<li v-if="instanceShoutboxPresent">
<BooleanSetting path="hideShoutbox">
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
@@ -32,51 +22,51 @@
<h2>{{ $t('nav.timeline') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="hideMutedPosts">
{{ $t('settings.hide_muted_posts') }}
</BooleanSetting>
<Checkbox v-model="hideMutedPosts">
{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
</Checkbox>
</li>
<li>
<BooleanSetting path="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }}
</BooleanSetting>
<Checkbox v-model="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
</Checkbox>
</li>
<li>
<BooleanSetting path="streaming">
<Checkbox v-model="streaming">
{{ $t('settings.streaming') }}
</BooleanSetting>
</Checkbox>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<BooleanSetting
path="pauseOnUnfocused"
<Checkbox
v-model="pauseOnUnfocused"
:disabled="!streaming"
>
{{ $t('settings.pause_on_unfocused') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</li>
<li>
<BooleanSetting path="useStreamingApi">
<Checkbox v-model="useStreamingApi">
{{ $t('settings.useStreamingApi') }}
<br>
<small>
{{ $t('settings.useStreamingApiWarning') }}
</small>
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="emojiReactionsOnTimeline">
<Checkbox v-model="emojiReactionsOnTimeline">
{{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="virtualScrolling">
<Checkbox v-model="virtualScrolling">
{{ $t('settings.virtual_scrolling') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
@@ -85,52 +75,87 @@
<h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="scopeCopy">
{{ $t('settings.scope_copy') }}
</BooleanSetting>
<Checkbox v-model="scopeCopy">
{{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
</Checkbox>
</li>
<li>
<BooleanSetting path="alwaysShowSubjectInput">
{{ $t('settings.subject_input_always_show') }}
</BooleanSetting>
<Checkbox v-model="alwaysShowSubjectInput">
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
</Checkbox>
</li>
<li>
<ChoiceSetting
id="subjectLineBehavior"
path="subjectLineBehavior"
:options="subjectLineOptions"
>
<div>
{{ $t('settings.subject_line_behavior') }}
</ChoiceSetting>
<label
for="subjectLineBehavior"
class="select"
>
<select
id="subjectLineBehavior"
v-model="subjectLineBehavior"
>
<option value="email">
{{ $t('settings.subject_line_email') }}
{{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="masto">
{{ $t('settings.subject_line_mastodon') }}
{{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="noop">
{{ $t('settings.subject_line_noop') }}
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
</li>
<li v-if="postFormats.length > 0">
<ChoiceSetting
id="postContentType"
path="postContentType"
:options="postContentOptions"
>
<div>
{{ $t('settings.post_status_content_type') }}
</ChoiceSetting>
<label
for="postContentType"
class="select"
>
<select
id="postContentType"
v-model="postContentType"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
</li>
<li>
<BooleanSetting path="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
<Checkbox v-model="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
</Checkbox>
</li>
<li>
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="autohideFloatingPostButton">
<Checkbox v-model="autohideFloatingPostButton">
{{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="padEmoji">
<Checkbox v-model="padEmoji">
{{ $t('settings.pad_emoji') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
@@ -139,14 +164,14 @@
<h2>{{ $t('settings.attachments') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="hideAttachments">
<Checkbox v-model="hideAttachments">
{{ $t('settings.hide_attachments_in_tl') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="hideAttachmentsInConv">
<Checkbox v-model="hideAttachmentsInConv">
{{ $t('settings.hide_attachments_in_convo') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<label for="maxThumbnails">
@@ -154,7 +179,7 @@
</label>
<input
id="maxThumbnails"
path.number="maxThumbnails"
v-model.number="maxThumbnails"
class="number-input"
type="number"
min="0"
@@ -162,48 +187,48 @@
>
</li>
<li>
<BooleanSetting path="hideNsfw">
<Checkbox v-model="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
</BooleanSetting>
</Checkbox>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="preloadImage"
<Checkbox
v-model="preloadImage"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting
path="useOneClickNsfw"
<Checkbox
v-model="useOneClickNsfw"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
<li>
<BooleanSetting path="stopGifs">
<Checkbox v-model="stopGifs">
{{ $t('settings.stop_gifs') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="loopVideo">
<Checkbox v-model="loopVideo">
{{ $t('settings.loop_video') }}
</BooleanSetting>
</Checkbox>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<BooleanSetting
path="loopVideoSilentOnly"
<Checkbox
v-model="loopVideoSilentOnly"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
</Checkbox>
<div
v-if="!loopSilentAvailable"
class="unavailable"
@@ -214,14 +239,14 @@
</ul>
</li>
<li>
<BooleanSetting path="playVideosInModal">
<Checkbox v-model="playVideosInModal">
{{ $t('settings.play_videos_in_modal') }}
</BooleanSetting>
</Checkbox>
</li>
<li>
<BooleanSetting path="useContainFit">
<Checkbox v-model="useContainFit">
{{ $t('settings.use_contain_fit') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
@@ -230,9 +255,9 @@
<h2>{{ $t('settings.notifications') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="webPushNotifications">
<Checkbox v-model="webPushNotifications">
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
</Checkbox>
</li>
</ul>
</div>
@@ -241,9 +266,9 @@
<h2>{{ $t('settings.fun') }}</h2>
<ul class="setting-list">
<li>
<BooleanSetting path="greentext">
{{ $t('settings.greentext') }}
</BooleanSetting>
<Checkbox v-model="greentext">
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
</Checkbox>
</li>
</ul>
</div>
@@ -10,18 +10,20 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
<template v-slot="row">
<BlockCard
slot-scope="row"
:user-id="row.item"
/>
</template>
</Autosuggest>
</div>
<BlockList
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template
slot="header"
slot-scope="{selected}"
>
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -29,7 +31,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@@ -39,16 +41,19 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template
slot="item"
slot-scope="{item}"
>
<BlockCard :user-id="item" />
</template>
<template v-slot:empty>
<template slot="empty">
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@@ -63,18 +68,20 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
<template v-slot="row">
<MuteCard
slot-scope="row"
:user-id="row.item"
/>
</template>
</Autosuggest>
</div>
<MuteList
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template
slot="header"
slot-scope="{selected}"
>
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -82,7 +89,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@@ -92,16 +99,19 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template
slot="item"
slot-scope="{item}"
>
<MuteCard :user-id="item" />
</template>
<template v-slot:empty>
<template slot="empty">
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@@ -114,18 +124,20 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
<template v-slot="row">
<DomainMuteCard
slot-scope="row"
:domain="row.item"
/>
</template>
</Autosuggest>
</div>
<DomainMuteList
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template
slot="header"
slot-scope="{selected}"
>
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -133,16 +145,19 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
<template v-slot:progress>
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template
slot="item"
slot-scope="{item}"
>
<DomainMuteCard :domain="item" />
</template>
<template v-slot:empty>
<template slot="empty">
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
@@ -24,7 +24,7 @@
class="btn button-default"
@click="updateNotificationSettings"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
</div>
@@ -111,17 +111,16 @@
.profile-fields {
display: flex;
& > .emoji-input {
&>.emoji-input {
flex: 1 1 auto;
margin: 0 0.2em 0.5em;
margin: 0 .2em .5em;
min-width: 0;
}
.delete-field {
&>.icon-container {
width: 20px;
align-self: center;
margin: 0 0.2em 0.5em;
padding: 0 0.5em;
margin: 0 .2em .5em;
}
}
}
@@ -124,24 +124,24 @@
:placeholder="$t('settings.profile_fields.value')"
>
</EmojiInput>
<button
class="delete-field button-unstyled -hover-highlight"
@click="deleteField(i)"
<div
class="icon-container"
>
<FAIcon
v-show="newFields.length > 1"
icon="times"
@click="deleteField(i)"
/>
</button>
</div>
<button
</div>
<a
v-if="newFields.length < maxFields"
class="add-field faint button-unstyled -hover-highlight"
class="add-field faint"
@click="addField"
>
<FAIcon icon="plus" />
{{ $t("settings.profile_fields.add_field") }}
</button>
</a>
</div>
<p>
<Checkbox v-model="bot">
@@ -153,7 +153,7 @@
class="btn button-default"
@click="updateProfile"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
<div class="setting-item">
@@ -227,7 +227,7 @@
class="btn button-default"
@click="submitBanner(banner)"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
<div class="setting-item">
@@ -266,7 +266,7 @@
class="btn button-default"
@click="submitBackground(background)"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
</div>
@@ -1,7 +1,6 @@
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Mfa from './mfa.vue'
import localeService from 'src/services/locale/locale.service.js'
const SecurityTab = {
data () {
@@ -38,7 +37,7 @@ const SecurityTab = {
return {
id: oauthToken.id,
appName: oauthToken.app_name,
validUntil: new Date(oauthToken.valid_until).toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale))
validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
}
})
}
@@ -22,7 +22,7 @@
class="btn button-default"
@click="changeEmail"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
<p v-if="changedEmail">
{{ $t('settings.changed_email') }}
@@ -60,7 +60,7 @@
class="btn button-default"
@click="changePassword"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
<p v-if="changedPassword">
{{ $t('settings.changed_password') }}
@@ -133,7 +133,7 @@
class="btn button-default"
@click="confirmDelete"
>
{{ $t('settings.save') }}
{{ $t('general.submit') }}
</button>
</div>
</div>
@@ -15,10 +15,6 @@ import {
shadows2to3,
colors2to3
} from 'src/services/style_setter/style_setter.js'
import {
newImporter,
newExporter
} from 'src/services/export_import/export_import.js'
import {
SLOT_INHERITANCE
} from 'src/services/theme_data/pleromafe.js'
@@ -35,10 +31,18 @@ import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
import FontControl from 'src/components/font_control/font_control.vue'
import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import ExportImport from 'src/components/export_import/export_import.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
import Preview from './preview.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
library.add(
faChevronDown
)
// List of color values used in v1
const v1OnlyNames = [
@@ -63,18 +67,8 @@ const colorConvert = (color) => {
export default {
data () {
return {
themeImporter: newImporter({
validator: this.importValidator,
onImport: this.onImport,
onImportFailure: this.onImportFailure
}),
themeExporter: newExporter({
filename: 'pleroma_theme',
getExportedObject: () => this.exportedTheme
}),
availableStyles: [],
selected: '',
selectedTheme: this.$store.getters.mergedConfig.theme,
selected: this.$store.getters.mergedConfig.theme,
themeWarning: undefined,
tempImportFile: undefined,
engineVersion: 0,
@@ -208,7 +202,7 @@ export default {
}
},
selectedVersion () {
return Array.isArray(this.selectedTheme) ? 1 : 2
return Array.isArray(this.selected) ? 1 : 2
},
currentColors () {
return Object.keys(SLOT_INHERITANCE)
@@ -389,8 +383,8 @@ export default {
FontControl,
TabSwitcher,
Preview,
Checkbox,
Select
ExportImport,
Checkbox
},
methods: {
loadTheme (
@@ -534,15 +528,10 @@ export default {
this.previewColors.mod
)
},
importTheme () { this.themeImporter.importData() },
exportTheme () { this.themeExporter.exportData() },
onImport (parsed, forceSource = false) {
this.tempImportFile = parsed
this.loadTheme(parsed, 'file', forceSource)
},
onImportFailure (result) {
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
},
importValidator (parsed) {
const version = parsed._pleroma_theme_version
return version >= 1 || version <= 2
@@ -746,16 +735,6 @@ export default {
}
},
selected () {
this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
if (Array.isArray(s)) {
console.log(s[0] === this.selected, this.selected)
return s[0] === this.selected
} else {
return s.name === this.selected
}
})[1]
},
selectedTheme () {
this.dismissWarning()
if (this.selectedVersion === 1) {
if (!this.keepRoundness) {
@@ -773,17 +752,17 @@ export default {
if (!this.keepColor) {
this.clearV1()
this.bgColorLocal = this.selectedTheme[1]
this.fgColorLocal = this.selectedTheme[2]
this.textColorLocal = this.selectedTheme[3]
this.linkColorLocal = this.selectedTheme[4]
this.cRedColorLocal = this.selectedTheme[5]
this.cGreenColorLocal = this.selectedTheme[6]
this.cBlueColorLocal = this.selectedTheme[7]
this.cOrangeColorLocal = this.selectedTheme[8]
this.bgColorLocal = this.selected[1]
this.fgColorLocal = this.selected[2]
this.textColorLocal = this.selected[3]
this.linkColorLocal = this.selected[4]
this.cRedColorLocal = this.selected[5]
this.cGreenColorLocal = this.selected[6]
this.cBlueColorLocal = this.selected[7]
this.cOrangeColorLocal = this.selected[8]
}
} else if (this.selectedVersion >= 2) {
this.normalizeLocalState(this.selectedTheme.theme, 2, this.selectedTheme.source)
this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
}
}
}
@@ -48,14 +48,22 @@
</template>
</div>
</div>
<div class="top">
<ExportImport
:export-object="exportedTheme"
:export-label="$t(&quot;settings.export_theme&quot;)"
:import-label="$t(&quot;settings.import_theme&quot;)"
:import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
:on-import="onImport"
:validator="importValidator"
>
<template slot="before">
<div class="presets">
{{ $t('settings.presets') }}
<label
for="preset-switcher"
class="select"
>
<Select
<select
id="preset-switcher"
v-model="selected"
class="preset-switcher"
@@ -63,7 +71,7 @@
<option
v-for="style in availableStyles"
:key="style.name"
:value="style.name || style[0]"
:value="style"
:style="{
backgroundColor: style[1] || (style.theme || style.source).colors.bg,
color: style[3] || (style.theme || style.source).colors.text
@@ -71,24 +79,15 @@
>
{{ style[0] || style.name }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
<div class="export-import">
<button
class="btn button-default"
@click="importTheme"
>
{{ $t(&quot;settings.import_theme&quot;) }}
</button>
<button
class="btn button-default"
@click="exportTheme"
>
{{ $t(&quot;settings.export_theme&quot;) }}
</button>
</div>
</div>
</template>
</ExportImport>
</div>
<div class="save-load-options">
<span class="keep-option">
@@ -903,7 +902,11 @@
<div class="tab-header shadow-selector">
<div class="select-container">
{{ $t('settings.style.shadows.component') }}
<Select
<label
for="shadow-switcher"
class="select"
>
<select
id="shadow-switcher"
v-model="shadowSelected"
class="shadow-switcher"
@@ -915,7 +918,12 @@
>
{{ $t('settings.style.shadows.components.' + shadow) }}
</option>
</Select>
</select>
<FAIcon
class="select-down-icon"
icon="chevron-down"
/>
</label>
</div>
<div class="override">
<label
@@ -1,6 +1,5 @@
import ColorInput from '../color_input/color_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue'
import Select from '../select/select.vue'
import { getCssShadow } from '../../services/style_setter/style_setter.js'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -46,8 +45,7 @@ export default {
},
components: {
ColorInput,
OpacityInput,
Select
OpacityInput
},
methods: {
add () {
@@ -59,7 +59,12 @@
:disabled="usingFallback"
class="id-control style-control"
>
<Select
<label
for="shadow-switcher"
class="select"
:disabled="!ready || usingFallback"
>
<select
id="shadow-switcher"
v-model="selectedId"
class="shadow-switcher"
@@ -72,7 +77,12 @@
>
{{ $t('settings.style.shadows.shadow_id', { value: index }) }}
</option>
</Select>
</select>
<FAIcon
icon="chevron-down"
class="select-down-icon"
/>
</label>
<button
class="btn button-default"
:disabled="!ready || !present"
@@ -306,20 +316,20 @@
.id-control {
align-items: stretch;
.shadow-switcher {
flex: 1;
}
.shadow-switcher, .btn {
.select, .btn {
min-width: 1px;
margin-right: 5px;
}
.btn {
padding: 0 .4em;
margin: 0 .1em;
}
.select {
flex: 1;
select {
align-self: initial;
}
}
}
}
}
@@ -49,6 +49,7 @@ const SideDrawer = {
currentUser () {
return this.$store.state.users.currentUser
},
chat () { return this.$store.state.chat.channel.state === 'joined' },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
+4 -2
View File
@@ -109,7 +109,7 @@
v-if="chat"
@click="toggleDrawer"
>
<router-link :to="{ name: 'chat-panel' }">
<router-link :to="{ name: 'chat' }">
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -273,7 +273,9 @@
--icon: var(--popoverIcon, $fallback--icon);
.badge {
margin-left: 10px;
position: absolute;
right: 0.7rem;
top: 1em;
}
}
+3 -17
View File
@@ -1,6 +1,4 @@
import map from 'lodash/map'
import groupBy from 'lodash/groupBy'
import { mapGetters, mapState } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const StaffPanel = {
@@ -12,21 +10,9 @@ const StaffPanel = {
BasicUserCard
},
computed: {
groupedStaffAccounts () {
const staffAccounts = map(this.staffAccounts, this.findUser).filter(_ => _)
const groupedStaffAccounts = groupBy(staffAccounts, 'role')
return [
{ role: 'admin', users: groupedStaffAccounts['admin'] },
{ role: 'moderator', users: groupedStaffAccounts['moderator'] }
].filter(group => group.users)
},
...mapGetters([
'findUser'
]),
...mapState({
staffAccounts: state => state.instance.staffAccounts
})
staffAccounts () {
return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _)
}
}
}
+1 -18
View File
@@ -7,34 +7,17 @@
</div>
</div>
<div class="panel-body">
<div
v-for="group in groupedStaffAccounts"
:key="group.role"
class="staff-group"
>
<h4>{{ $t('general.role.' + group.role) }}</h4>
<basic-user-card
v-for="user in group.users"
v-for="user in staffAccounts"
:key="user.screen_name"
:user="user"
/>
</div>
</div>
</div>
</div>
</template>
<script src="./staff_panel.js" ></script>
<style lang="scss">
.staff-group {
padding-left: 1em;
padding-top: 1em;
.basic-user-card {
padding-left: 0;
}
}
</style>
+2 -2
View File
@@ -136,7 +136,7 @@ const Status = {
}
},
retweet () { return !!this.statusoid.retweeted_status },
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name_ui },
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name },
retweeterHtml () { return this.statusoid.user.name_html },
retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) },
status () {
@@ -216,7 +216,7 @@ const Status = {
return this.status.in_reply_to_screen_name
} else {
const user = this.$store.getters.findUser(this.status.in_reply_to_user_id)
return user && user.screen_name_ui
return user && user.screen_name
}
},
replySubject () {
+3 -3
View File
@@ -26,7 +26,7 @@
icon="retweet"
/>
<router-link :to="userProfileLink">
{{ status.user.screen_name_ui }}
{{ status.user.screen_name }}
</router-link>
</small>
<small
@@ -156,10 +156,10 @@
</h4>
<router-link
class="account-name"
:title="status.user.screen_name_ui"
:title="status.user.screen_name"
:to="userProfileLink"
>
{{ status.user.screen_name_ui }}
{{ status.user.screen_name }}
</router-link>
<img
v-if="!!(status.user && status.user.favicon)"
@@ -5,10 +5,12 @@
:bound-to="{ x: 'container' }"
@show="enter"
>
<template v-slot:trigger>
<template slot="trigger">
<slot />
</template>
<template v-slot:content>
<div
slot="content"
>
<Status
v-if="status"
:is-preview="true"
@@ -31,7 +33,7 @@
size="2x"
/>
</div>
</template>
</div>
</Popover>
</template>
+1 -3
View File
@@ -93,9 +93,7 @@ export default Vue.component('tab-switcher', {
<button
disabled={slot.data.attrs.disabled}
onClick={this.clickTab(index)}
class={classesTab.join(' ')}
type="button"
>
class={classesTab.join(' ')}>
<img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
{slot.data.attrs.label ? '' : slot.data.attrs.label}
</button>

Some files were not shown because too many files have changed in this diff Show More