...
 
Commits (22)
...@@ -77,6 +77,9 @@ Use custom image for NSFW'd images ...@@ -77,6 +77,9 @@ Use custom image for NSFW'd images
### `showFeaturesPanel` ### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors Show panel showcasing instance features/settings to logged-out visitors
### `hideSitename`
Hide instance name in header
## Indirect configuration ## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it. Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
......
...@@ -90,6 +90,7 @@ export default { ...@@ -90,6 +90,7 @@ export default {
}, },
sitename () { return this.$store.state.instance.name }, sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' }, chat () { return this.$store.state.chat.channel.state === 'joined' },
hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel && return this.$store.state.instance.showInstanceSpecificPanel &&
......
...@@ -870,3 +870,16 @@ nav { ...@@ -870,3 +870,16 @@ nav {
transform: rotate(359deg); transform: rotate(359deg);
} }
} }
.new-status-notification {
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;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
</div> </div>
<div class="item"> <div class="item">
<router-link <router-link
v-if="!hideSitename"
class="site-name" class="site-name"
:to="{ name: 'root' }" :to="{ name: 'root' }"
active-class="home" active-class="home"
......
...@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { ...@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks') copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel') copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
return store.dispatch('setTheme', config['theme']) return store.dispatch('setTheme', config['theme'])
} }
......
import ProgressButton from '../progress_button/progress_button.vue'
const DomainMuteCard = {
props: ['domain'],
components: {
ProgressButton
},
methods: {
unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain)
}
}
}
export default DomainMuteCard
<template>
<div class="domain-mute-card">
<div class="domain-mute-card-domain">
{{ domain }}
</div>
<ProgressButton
:click="unmuteDomain"
class="btn btn-default"
>
{{ $t('domain_mute_card.unmute') }}
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<script src="./domain_mute_card.js"></script>
<style lang="scss">
.domain-mute-card {
flex: 1 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6em 1em 0.6em 0;
&-domain {
margin-right: 1em;
overflow: hidden;
text-overflow: ellipsis;
}
button {
width: 10em;
}
}
</style>
...@@ -29,6 +29,7 @@ const MobileNav = { ...@@ -29,6 +29,7 @@ const MobileNav = {
unseenNotificationsCount () { unseenNotificationsCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name } sitename () { return this.$store.state.instance.name }
}, },
methods: { methods: {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<i class="button-icon icon-menu" /> <i class="button-icon icon-menu" />
</a> </a>
<router-link <router-link
v-if="!hideSitename"
class="site-name" class="site-name"
:to="{ name: 'root' }" :to="{ name: 'root' }"
active-class="home" active-class="home"
......
...@@ -172,7 +172,7 @@ ...@@ -172,7 +172,7 @@
for="captcha-label" for="captcha-label"
>{{ $t('captcha') }}</label> >{{ $t('captcha') }}</label>
<template v-if="captcha.type == 'kocaptcha'"> <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img <img
:src="captcha.url" :src="captcha.url"
@click="setCaptcha" @click="setCaptcha"
......
...@@ -33,6 +33,9 @@ const SideDrawer = { ...@@ -33,6 +33,9 @@ const SideDrawer = {
logo () { logo () {
return this.$store.state.instance.logo return this.$store.state.instance.logo
}, },
hideSitename () {
return this.$store.state.instance.hideSitename
},
sitename () { sitename () {
return this.$store.state.instance.name return this.$store.state.instance.name
}, },
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
class="side-drawer-logo-wrapper" class="side-drawer-logo-wrapper"
> >
<img :src="logo"> <img :src="logo">
<span>{{ sitename }}</span> <span v-if="!hideSitename">{{ sitename }}</span>
</div> </div>
</div> </div>
<ul> <ul>
......
...@@ -93,17 +93,4 @@ ...@@ -93,17 +93,4 @@
opacity: 1; opacity: 1;
} }
} }
.new-status-notification {
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;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}
</style> </style>
...@@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue' ...@@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_card.vue' import BlockCard from '../block_card/block_card.vue'
import MuteCard from '../mute_card/mute_card.vue' import MuteCard from '../mute_card/mute_card.vue'
import DomainMuteCard from '../domain_mute_card/domain_mute_card.vue'
import SelectableList from '../selectable_list/selectable_list.vue' import SelectableList from '../selectable_list/selectable_list.vue'
import ProgressButton from '../progress_button/progress_button.vue' import ProgressButton from '../progress_button/progress_button.vue'
import EmojiInput from '../emoji_input/emoji_input.vue' import EmojiInput from '../emoji_input/emoji_input.vue'
...@@ -32,6 +33,12 @@ const MuteList = withSubscription({ ...@@ -32,6 +33,12 @@ const MuteList = withSubscription({
childPropName: 'items' childPropName: 'items'
})(SelectableList) })(SelectableList)
const DomainMuteList = withSubscription({
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
childPropName: 'items'
})(SelectableList)
const UserSettings = { const UserSettings = {
data () { data () {
return { return {
...@@ -67,7 +74,8 @@ const UserSettings = { ...@@ -67,7 +74,8 @@ const UserSettings = {
changedPassword: false, changedPassword: false,
changePasswordError: false, changePasswordError: false,
activeTab: 'profile', activeTab: 'profile',
notificationSettings: this.$store.state.users.currentUser.notification_settings notificationSettings: this.$store.state.users.currentUser.notification_settings,
newDomainToMute: ''
} }
}, },
created () { created () {
...@@ -80,10 +88,12 @@ const UserSettings = { ...@@ -80,10 +88,12 @@ const UserSettings = {
ImageCropper, ImageCropper,
BlockList, BlockList,
MuteList, MuteList,
DomainMuteList,
EmojiInput, EmojiInput,
Autosuggest, Autosuggest,
BlockCard, BlockCard,
MuteCard, MuteCard,
DomainMuteCard,
ProgressButton, ProgressButton,
Importer, Importer,
Exporter, Exporter,
...@@ -365,6 +375,13 @@ const UserSettings = { ...@@ -365,6 +375,13 @@ const UserSettings = {
unmuteUsers (ids) { unmuteUsers (ids) {
return this.$store.dispatch('unmuteUsers', ids) return this.$store.dispatch('unmuteUsers', ids)
}, },
unmuteDomains (domains) {
return this.$store.dispatch('unmuteDomains', domains)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.newDomainToMute)
.then(() => { this.newDomainToMute = '' })
},
identity (value) { identity (value) {
return value return value
} }
......
...@@ -563,6 +563,55 @@ ...@@ -563,6 +563,55 @@
</template> </template>
</MuteList> </MuteList>
</div> </div>
<div label="Domain Mutes">
<div class="profile-edit-domain-mute-form">
<input
v-model="newDomainToMute"
type="text"
>
<ProgressButton
class="btn btn-default"
:click="muteDomain"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
</div>
<DomainMuteList
:refresh="true"
:get-key="identity"
>
<template
slot="header"
slot-scope="{selected}"
>
<div class="profile-edit-bulk-actions">
<ProgressButton
v-if="selected.length > 0"
class="btn btn-default"
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
<template slot="progress">
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template
slot="item"
slot-scope="{item}"
>
<DomainMuteCard :domain="item" />
</template>
<template slot="empty">
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
</div>
</tab-switcher> </tab-switcher>
</div> </div>
</div> </div>
...@@ -639,6 +688,14 @@ ...@@ -639,6 +688,14 @@
} }
} }
&-domain-mute-form {
padding: 1em 10px;
button {
width: 10em;
}
}
.setting-subitem { .setting-subitem {
margin-left: 1.75em; margin-left: 1.75em;
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
"chat": { "chat": {
"title": "Chat" "title": "Chat"
}, },
"domain_mute_card": {
"mute": "Mute",
"mute_progress": "Muting...",
"unmute": "Unmute",
"unmute_progress": "Unmuting..."
},
"exporter": { "exporter": {
"export": "Export", "export": "Export",
"processing": "Processing, you'll soon be asked to download your file" "processing": "Processing, you'll soon be asked to download your file"
......
...@@ -27,6 +27,7 @@ const defaultState = { ...@@ -27,6 +27,7 @@ const defaultState = {
scopeCopy: true, scopeCopy: true,
subjectLineBehavior: 'email', subjectLineBehavior: 'email',
postContentType: 'text/plain', postContentType: 'text/plain',
hideSitename: false,
nsfwCensorImage: undefined, nsfwCensorImage: undefined,
vapidPublicKey: undefined, vapidPublicKey: undefined,
noAttachmentLinks: false, noAttachmentLinks: false,
......
...@@ -72,6 +72,16 @@ const showReblogs = (store, userId) => { ...@@ -72,6 +72,16 @@ const showReblogs = (store, userId) => {
.then((relationship) => store.commit('updateUserRelationship', [relationship])) .then((relationship) => store.commit('updateUserRelationship', [relationship]))
} }
const muteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.muteDomain(domain)
.then(() => store.commit('addDomainMute', domain))
}
const unmuteDomain = (store, domain) => {
return store.rootState.api.backendInteractor.unmuteDomain(domain)
.then(() => store.commit('removeDomainMute', domain))
}
export const mutations = { export const mutations = {
setMuted (state, { user: { id }, muted }) { setMuted (state, { user: { id }, muted }) {
const user = state.usersObject[id] const user = state.usersObject[id]
...@@ -177,6 +187,20 @@ export const mutations = { ...@@ -177,6 +187,20 @@ export const mutations = {
state.currentUser.muteIds.push(muteId) state.currentUser.muteIds.push(muteId)
} }
}, },
saveDomainMutes (state, domainMutes) {
state.currentUser.domainMutes = domainMutes
},
addDomainMute (state, domain) {
if (state.currentUser.domainMutes.indexOf(domain) === -1) {
state.currentUser.domainMutes.push(domain)
}
},
removeDomainMute (state, domain) {
const index = state.currentUser.domainMutes.indexOf(domain)
if (index !== -1) {
state.currentUser.domainMutes.splice(index, 1)
}
},
setPinnedToUser (state, status) { setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id] const user = state.usersObject[status.user.id]
const index = user.pinnedStatusIds.indexOf(status.id) const index = user.pinnedStatusIds.indexOf(status.id)
...@@ -297,6 +321,25 @@ const users = { ...@@ -297,6 +321,25 @@ const users = {
unmuteUsers (store, ids = []) { unmuteUsers (store, ids = []) {
return Promise.all(ids.map(id => unmuteUser(store, id))) return Promise.all(ids.map(id => unmuteUser(store, id)))
}, },
fetchDomainMutes (store) {
return store.rootState.api.backendInteractor.fetchDomainMutes()
.then((domainMutes) => {
store.commit('saveDomainMutes', domainMutes)
return domainMutes
})
},
muteDomain (store, domain) {
return muteDomain(store, domain)
},
unmuteDomain (store, domain) {
return unmuteDomain(store, domain)
},
muteDomains (store, domains = []) {
return Promise.all(domains.map(domain => muteDomain(store, domain)))
},
unmuteDomains (store, domain = []) {
return Promise.all(domain.map(domain => unmuteDomain(store, domain)))
},
fetchFriends ({ rootState, commit }, id) { fetchFriends ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id] const user = rootState.users.usersObject[id]
const maxId = last(user.friendIds) const maxId = last(user.friendIds)
...@@ -451,6 +494,7 @@ const users = { ...@@ -451,6 +494,7 @@ const users = {
user.credentials = accessToken user.credentials = accessToken
user.blockIds = [] user.blockIds = []
user.muteIds = [] user.muteIds = []
user.domainMutes = []
commit('setCurrentUser', user) commit('setCurrentUser', user)
commit('addNewUsers', [user]) commit('addNewUsers', [user])
......
...@@ -22,7 +22,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes' ...@@ -22,7 +22,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp' const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
...@@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute` ...@@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute` const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search` const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search' const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
const oldfetch = window.fetch const oldfetch = window.fetch
...@@ -932,6 +933,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { ...@@ -932,6 +933,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
}) })
} }
const fetchDomainMutes = ({ credentials }) => {
return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
}
const muteDomain = ({ domain, credentials }) => {
return promisedRequest({
url: MASTODON_DOMAIN_BLOCKS_URL,
method: 'POST',
payload: { domain },
credentials
})
}
const unmuteDomain = ({ domain, credentials }) => {
return promisedRequest({
url: MASTODON_DOMAIN_BLOCKS_URL,
method: 'DELETE',
payload: { domain },
credentials
})
}
const apiService = { const apiService = {
verifyCredentials, verifyCredentials,
fetchTimeline, fetchTimeline,
...@@ -1000,7 +1023,10 @@ const apiService = { ...@@ -1000,7 +1023,10 @@ const apiService = {
reportUser, reportUser,
updateNotificationSettings, updateNotificationSettings,
search2, search2,
searchUsers searchUsers,
fetchDomainMutes,
muteDomain,
unmuteDomain
} }
export default apiService export default apiService
...@@ -156,6 +156,9 @@ const backendInteractorService = credentials => { ...@@ -156,6 +156,9 @@ const backendInteractorService = credentials => {
const search2 = ({ q, resolve, limit, offset, following }) => const search2 = ({ q, resolve, limit, offset, following }) =>
apiService.search2({ credentials, q, resolve, limit, offset, following }) apiService.search2({ credentials, q, resolve, limit, offset, following })
const searchUsers = (query) => apiService.searchUsers({ query, credentials }) const searchUsers = (query) => apiService.searchUsers({ query, credentials })
const fetchDomainMutes = () => apiService.fetchDomainMutes({ credentials })
const muteDomain = (domain) => apiService.muteDomain({ domain, credentials })
const unmuteDomain = (domain) => apiService.unmuteDomain({ domain, credentials })
const backendInteractorServiceInstance = { const backendInteractorServiceInstance = {
fetchStatus, fetchStatus,
...@@ -221,7 +224,10 @@ const backendInteractorService = credentials => { ...@@ -221,7 +224,10 @@ const backendInteractorService = credentials => {
unretweet, unretweet,
updateNotificationSettings, updateNotificationSettings,
search2, search2,
searchUsers searchUsers,
fetchDomainMutes,
muteDomain,
unmuteDomain
} }
return backendInteractorServiceInstance return backendInteractorServiceInstance
......