...
 
Commits (35)
...@@ -99,3 +99,6 @@ Setting this will change the warning text that is displayed for direct messages. ...@@ -99,3 +99,6 @@ Setting this will change the warning text that is displayed for direct messages.
ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that. ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
DO NOT activate this without checking the backend configuration first! DO NOT activate this without checking the backend configuration first!
### Private Mode
If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.
...@@ -98,7 +98,8 @@ export default { ...@@ -98,7 +98,8 @@ export default {
this.$store.state.instance.instanceSpecificPanelContent this.$store.state.instance.instanceSpecificPanelContent
}, },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
isMobileLayout () { return this.$store.state.interface.mobileLayout } isMobileLayout () { return this.$store.state.interface.mobileLayout },
privateMode () { return this.$store.state.instance.private }
}, },
methods: { methods: {
scrollToTop () { scrollToTop () {
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
</div> </div>
<div class="item right"> <div class="item right">
<search-bar <search-bar
v-if="currentUser || !privateMode"
class="nav-icon mobile-hidden" class="nav-icon mobile-hidden"
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop.native
......
...@@ -219,12 +219,21 @@ const getNodeInfo = async ({ store }) => { ...@@ -219,12 +219,21 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') }) store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
const federation = metadata.federation const federation = metadata.federation
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation }) store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
? true
: federation.enabled
})
const accounts = metadata.staffAccounts const accounts = metadata.staffAccounts
await resolveStaffAccounts({ store, accounts }) await resolveStaffAccounts({ store, accounts })
......
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>
import { mapState } from 'vuex'
const NavPanel = { const NavPanel = {
created () { created () {
if (this.currentUser && this.currentUser.locked) { if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequest') this.$store.dispatch('startFetchingFollowRequest')
} }
}, },
computed: { computed: mapState({
currentUser () { currentUser: state => state.users.currentUser,
return this.$store.state.users.currentUser chat: state => state.chat.channel,
}, followRequestCount: state => state.api.followRequests.length,
chat () { privateMode: state => state.instance.private,
return this.$store.state.chat.channel federating: state => state.instance.federating
}, })
followRequestCount () {
return this.$store.state.api.followRequests.length
}
}
} }
export default NavPanel export default NavPanel
...@@ -28,12 +28,12 @@ ...@@ -28,12 +28,12 @@
</span> </span>
</router-link> </router-link>
</li> </li>
<li> <li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }"> <router-link :to="{ name: 'public-timeline' }">
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }} <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link> </router-link>
</li> </li>
<li> <li v-if="federating && !privateMode">
<router-link :to="{ name: 'public-external-timeline' }"> <router-link :to="{ name: 'public-external-timeline' }">
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }} <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link> </router-link>
......
...@@ -41,6 +41,12 @@ const SideDrawer = { ...@@ -41,6 +41,12 @@ const SideDrawer = {
}, },
followRequestCount () { followRequestCount () {
return this.$store.state.api.followRequests.length return this.$store.state.api.followRequests.length
},
privateMode () {
return this.$store.state.instance.private
},
federating () {
return this.$store.state.instance.federating
} }
}, },
methods: { methods: {
......
...@@ -79,12 +79,18 @@ ...@@ -79,12 +79,18 @@
</span> </span>
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
<router-link to="/main/public"> <router-link to="/main/public">
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }} <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li
v-if="federating && !privateMode"
@click="toggleDrawer"
>
<router-link to="/main/all"> <router-link to="/main/all">
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }} <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link> </router-link>
...@@ -99,7 +105,10 @@ ...@@ -99,7 +105,10 @@
</li> </li>
</ul> </ul>
<ul> <ul>
<li @click="toggleDrawer"> <li
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
<router-link :to="{ name: 'search' }"> <router-link :to="{ name: 'search' }">
<i class="button-icon icon-search" /> {{ $t("nav.search") }} <i class="button-icon icon-search" /> {{ $t("nav.search") }}
</router-link> </router-link>
......
...@@ -36,7 +36,12 @@ const Timeline = { ...@@ -36,7 +36,12 @@ const Timeline = {
} }
}, },
computed: { computed: {
timelineError () { return this.$store.state.statuses.error }, timelineError () {
return this.$store.state.statuses.error
},
errorData () {
return this.$store.state.statuses.errorData
},
newStatusCount () { newStatusCount () {
return this.timeline.newStatusCount return this.timeline.newStatusCount
}, },
......
...@@ -11,15 +11,22 @@ ...@@ -11,15 +11,22 @@
> >
{{ $t('timeline.error_fetching') }} {{ $t('timeline.error_fetching') }}
</div> </div>
<div
v-else-if="errorData"
class="loadmore-error alert error"
@click.prevent
>
{{ errorData.statusText }}
</div>
<button <button
v-if="timeline.newStatusCount > 0 && !timelineError" v-if="timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-button" class="loadmore-button"
@click.prevent="showNewStatuses" @click.prevent="showNewStatuses"
> >
{{ $t('timeline.show_new') }}{{ newStatusCountStr }} {{ $t('timeline.show_new') }}{{ newStatusCountStr }}
</button> </button>
<div <div
v-if="!timeline.newStatusCount > 0 && !timelineError" v-if="!timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-text faint" class="loadmore-text faint"
@click.prevent @click.prevent
> >
...@@ -67,12 +74,18 @@ ...@@ -67,12 +74,18 @@
{{ $t('timeline.no_more_statuses') }} {{ $t('timeline.no_more_statuses') }}
</div> </div>
<a <a
v-else-if="!timeline.loading" v-else-if="!timeline.loading && !errorData"
href="#" href="#"
@click.prevent="fetchOlderStatuses()" @click.prevent="fetchOlderStatuses()"
> >
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
</a> </a>
<a
v-else-if="errorData"
href="#"
>
<div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
</a>
<div <div
v-else v-else
class="new-status-notification text-center panel-footer" class="new-status-notification text-center panel-footer"
......
...@@ -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"
......
{ {
"about": {
"staff": "スタッフ",
"federation": "フェデレーション",
"mrf_policies": "ゆうこうなMRFポリシー",
"mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
"mrf_policy_simple": "インスタンスのポリシー",
"mrf_policy_simple_accept": "うけいれ",
"mrf_policy_simple_accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
"mrf_policy_simple_reject": "おことわり",
"mrf_policy_simple_reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
"mrf_policy_simple_quarantine": "けんえき",
"mrf_policy_simple_quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
"mrf_policy_simple_ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
"mrf_policy_simple_ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
"mrf_policy_simple_media_removal": "メディアをのぞく",
"mrf_policy_simple_media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
"mrf_policy_simple_media_nsfw": "メディアをすべてセンシティブにする",
"mrf_policy_simple_media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
},
"chat": { "chat": {
"title": "チャット" "title": "チャット"
}, },
...@@ -68,6 +87,7 @@ ...@@ -68,6 +87,7 @@
}, },
"nav": { "nav": {
"about": "これはなに?", "about": "これはなに?",
"administration": "アドミニストレーション",
"back": "もどる", "back": "もどる",
"chat": "ローカルチャット", "chat": "ローカルチャット",
"friend_requests": "フォローリクエスト", "friend_requests": "フォローリクエスト",
...@@ -113,7 +133,9 @@ ...@@ -113,7 +133,9 @@
"search_emoji": "えもじをさがす", "search_emoji": "えもじをさがす",
"add_emoji": "えもじをうちこむ", "add_emoji": "えもじをうちこむ",
"custom": "カスタムえもじ", "custom": "カスタムえもじ",
"unicode": "ユニコードえもじ" "unicode": "ユニコードえもじ",
"load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
"load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
}, },
"stickers": { "stickers": {
"add_sticker": "ステッカーをふやす" "add_sticker": "ステッカーをふやす"
...@@ -173,6 +195,11 @@ ...@@ -173,6 +195,11 @@
"password_confirmation_match": "パスワードがちがいます" "password_confirmation_match": "パスワードがちがいます"
} }
}, },
"remote_user_resolver": {
"remote_user_resolver": "リモートユーザーリゾルバー",
"searching_for": "さがしています:",
"error": "みつかりませんでした。"
},
"selectable_list": { "selectable_list": {
"select_all": "すべてえらぶ" "select_all": "すべてえらぶ"
}, },
...@@ -220,6 +247,9 @@ ...@@ -220,6 +247,9 @@
"cGreen": "リピート", "cGreen": "リピート",
"cOrange": "おきにいり", "cOrange": "おきにいり",
"cRed": "キャンセル", "cRed": "キャンセル",
"change_email": "メールアドレスをかえる",
"change_email_error": "メールアドレスをかえようとしましたが、なにかがおかしいです。",
"changed_email": "メールアドレスをかえることができました!",
"change_password": "パスワードをかえる", "change_password": "パスワードをかえる",
"change_password_error": "パスワードをかえることが、できなかったかもしれません。", "change_password_error": "パスワードをかえることが、できなかったかもしれません。",
"changed_password": "パスワードが、かわりました!", "changed_password": "パスワードが、かわりました!",
...@@ -279,6 +309,7 @@ ...@@ -279,6 +309,7 @@
"use_contain_fit": "がぞうのサムネイルを、きりぬかない", "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
"name": "なまえ", "name": "なまえ",
"name_bio": "なまえとプロフィール", "name_bio": "なまえとプロフィール",
"new_email": "あたらしいメールアドレス",
"new_password": "あたらしいパスワード", "new_password": "あたらしいパスワード",
"notification_visibility": "ひょうじするつうち", "notification_visibility": "ひょうじするつうち",
"notification_visibility_follows": "フォロー", "notification_visibility_follows": "フォロー",
...@@ -344,6 +375,8 @@ ...@@ -344,6 +375,8 @@
"false": "いいえ", "false": "いいえ",
"true": "はい" "true": "はい"
}, },
"fun": "おたのしみ",
"greentext": "ミームやじるし",
"notifications": "つうち", "notifications": "つうち",
"notification_setting": "つうちをうけとる:", "notification_setting": "つうちをうけとる:",
"notification_setting_follows": "あなたがフォローしているひとから", "notification_setting_follows": "あなたがフォローしているひとから",
...@@ -391,6 +424,7 @@ ...@@ -391,6 +424,7 @@
"_tab_label": "くわしく", "_tab_label": "くわしく",
"alert": "アラートのバックグラウンド", "alert": "アラートのバックグラウンド",
"alert_error": "エラー", "alert_error": "エラー",
"alert_warning": "けいこく",
"badge": "バッジのバックグラウンド", "badge": "バッジのバックグラウンド",
"badge_notification": "つうち", "badge_notification": "つうち",
"panel_header": "パネルヘッダー", "panel_header": "パネルヘッダー",
...@@ -542,6 +576,7 @@ ...@@ -542,6 +576,7 @@
"followers": "フォロワー", "followers": "フォロワー",
"following": "フォローしています!", "following": "フォローしています!",
"follows_you": "フォローされました!", "follows_you": "フォローされました!",
"hidden": "かくされています",
"its_you": "これはあなたです!", "its_you": "これはあなたです!",
"media": "メディア", "media": "メディア",
"mention": "メンション", "mention": "メンション",
...@@ -559,6 +594,8 @@ ...@@ -559,6 +594,8 @@
"unmute": "ミュートをやめる", "unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています...", "unmute_progress": "ミュートをとりけしています...",
"mute_progress": "ミュートしています...", "mute_progress": "ミュートしています...",
"hide_repeats": "リピートをかくす",
"show_repeats": "リピートをみる",
"admin_menu": { "admin_menu": {
"moderation": "モデレーション", "moderation": "モデレーション",
"grant_admin": "アドミンにする", "grant_admin": "アドミンにする",
...@@ -634,6 +671,8 @@ ...@@ -634,6 +671,8 @@
"return_home": "ホームページにもどる", "return_home": "ホームページにもどる",
"not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。", "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
"too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。", "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
"password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。" "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
"password_reset_required": "ログインするには、パスワードをリセットしてください。",
"password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
} }
} }
...@@ -38,6 +38,7 @@ export const defaultState = () => ({ ...@@ -38,6 +38,7 @@ export const defaultState = () => ({
notifications: emptyNotifications(), notifications: emptyNotifications(),
favorites: new Set(), favorites: new Set(),
error: false, error: false,
errorData: null,
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
...@@ -479,6 +480,9 @@ export const mutations = { ...@@ -479,6 +480,9 @@ export const mutations = {
setError (state, { value }) { setError (state, { value }) {
state.error = value state.error = value
}, },
setErrorData (state, { value }) {
state.errorData = value
},
setNotificationsLoading (state, { value }) { setNotificationsLoading (state, { value }) {
state.notifications.loading = value state.notifications.loading = value
}, },
...@@ -528,6 +532,9 @@ const statuses = { ...@@ -528,6 +532,9 @@ const statuses = {
setError ({ rootState, commit }, { value }) { setError ({ rootState, commit }, { value }) {
commit('setError', { value }) commit('setError', { value })
}, },
setErrorData ({ rootState, commit }, { value }) {
commit('setErrorData', { value })
},
setNotificationsLoading ({ rootState, commit }, { value }) { setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value }) commit('setNotificationsLoading', { value })
}, },
......
...@@ -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])
......
...@@ -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
...@@ -529,16 +530,24 @@ const fetchTimeline = ({ ...@@ -529,16 +530,24 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}` url += `?${queryString}`
let status = ''
let statusText = ''
return fetch(url, { headers: authHeaders(credentials) }) return fetch(url, { headers: authHeaders(credentials) })
.then((data) => { .then((data) => {
if (data.ok) { status = data.status
statusText = data.statusText
return data
})
.then((data) => data.json())
.then((data) => {
if (!data.error) {
return data.map(isNotifications ? parseNotification : parseStatus)
} else {
data.status = status
data.statusText = statusText
return data return data
} }
throw new Error('Error fetching timeline', data)
}) })
.then((data) => data.json())
.then((data) => data.map(isNotifications ? parseNotification : parseStatus))
} }
const fetchPinnedStatuses = ({ id, credentials }) => { const fetchPinnedStatuses = ({ id, credentials }) => {
...@@ -932,6 +941,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => { ...@@ -932,6 +941,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 +1031,10 @@ const apiService = { ...@@ -1000,7 +1031,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
......
...@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) => ...@@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`) form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
form.append('redirect_uris', REDIRECT_URI) form.append('redirect_uris', REDIRECT_URI)
form.append('scopes', 'read write follow') form.append('scopes', 'read write follow push admin')
return window.fetch(url, { return window.fetch(url, {
method: 'POST', method: 'POST',
...@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => { ...@@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
response_type: 'code', response_type: 'code',
client_id: clientId, client_id: clientId,
redirect_uri: REDIRECT_URI, redirect_uri: REDIRECT_URI,
scope: 'read write follow' scope: 'read write follow push admin'
} }
const dataString = reduce(data, (acc, v, k) => { const dataString = reduce(data, (acc, v, k) => {
......
...@@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => { ...@@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
const ccTimeline = camelCase(timeline) const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false }) store.dispatch('setError', { value: false })
store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', { store.dispatch('addNewStatuses', {
timeline: ccTimeline, timeline: ccTimeline,
...@@ -45,6 +46,10 @@ const fetchAndUpdate = ({ ...@@ -45,6 +46,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then((statuses) => { .then((statuses) => {
if (statuses.error) {
store.dispatch('setErrorData', { value: statuses })
return
}
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) { if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId }) store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
} }
......