aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.js12
-rw-r--r--src/App.scss25
-rw-r--r--src/App.vue6
-rw-r--r--src/boot/routes.js4
-rw-r--r--src/components/attachment/attachment.vue2
-rw-r--r--src/components/moderation_tools/moderation_tools.vue6
-rw-r--r--src/components/progress_button/progress_button.vue2
-rw-r--r--src/components/search/search.js98
-rw-r--r--src/components/search/search.vue211
-rw-r--r--src/components/search_bar/search_bar.js27
-rw-r--r--src/components/search_bar/search_bar.vue (renamed from src/components/user_finder/user_finder.vue)38
-rw-r--r--src/components/side_drawer/side_drawer.vue4
-rw-r--r--src/components/status/status.js17
-rw-r--r--src/components/tab_switcher/tab_switcher.js12
-rw-r--r--src/components/user_card/user_card.js10
-rw-r--r--src/components/user_card/user_card.vue213
-rw-r--r--src/components/user_finder/user_finder.js20
-rw-r--r--src/components/user_profile/user_profile.js2
-rw-r--r--src/components/user_search/user_search.js51
-rw-r--r--src/components/user_search/user_search.vue57
-rw-r--r--src/components/user_settings/user_settings.js8
-rw-r--r--src/i18n/en.json10
-rw-r--r--src/i18n/ru.json10
-rw-r--r--src/modules/statuses.js36
-rw-r--r--src/modules/users.js19
-rw-r--r--src/services/api/api.service.js65
-rw-r--r--src/services/backend_interactor_service/backend_interactor_service.js9
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js1
-rw-r--r--src/services/new_api/user_search.js20
-rw-r--r--src/services/new_api/utils.js36
30 files changed, 675 insertions, 356 deletions
diff --git a/src/App.js b/src/App.js
index e72c73e3..3624171e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,7 +1,7 @@
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import Notifications from './components/notifications/notifications.vue'
-import UserFinder from './components/user_finder/user_finder.vue'
+import SearchBar from './components/search_bar/search_bar.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'
@@ -19,7 +19,7 @@ export default {
UserPanel,
NavPanel,
Notifications,
- UserFinder,
+ SearchBar,
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
@@ -32,7 +32,7 @@ export default {
},
data: () => ({
mobileActivePanel: 'timeline',
- finderHidden: true,
+ searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
@@ -70,7 +70,7 @@ export default {
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
- opacity: this.finderHidden ? 1 : 0
+ opacity: this.searchBarHidden ? 1 : 0
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
@@ -101,8 +101,8 @@ export default {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
- onFinderToggled (hidden) {
- this.finderHidden = hidden
+ onSearchBarToggled (hidden) {
+ this.searchBarHidden = hidden
},
updateMobileState () {
const mobileLayout = windowWidth() <= 800
diff --git a/src/App.scss b/src/App.scss
index e4c764bf..1299e05d 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -283,6 +283,31 @@ i[class*=icon-] {
color: var(--icon, $fallback--icon)
}
+.btn-block {
+ display: block;
+ width: 100%;
+}
+
+.btn-group {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle;
+
+ button {
+ position: relative;
+ flex: 1 1 auto;
+
+ &:not(:last-child) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ &:not(:first-child) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+}
.container {
display: flex;
diff --git a/src/App.vue b/src/App.vue
index 758c9fce..be4d1f75 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -38,9 +38,9 @@
</router-link>
</div>
<div class="item right">
- <user-finder
- class="button-icon nav-icon mobile-hidden"
- @toggled="onFinderToggled"
+ <search-bar
+ class="nav-icon mobile-hidden"
+ @toggled="onSearchBarToggled"
/>
<router-link
class="mobile-hidden"
diff --git a/src/boot/routes.js b/src/boot/routes.js
index ca4a6a3e..22641f83 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -6,12 +6,12 @@ import ConversationPage from 'components/conversation-page/conversation-page.vue
import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
+import Search from 'components/search/search.vue'
import Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue'
import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
-import UserSearch from 'components/user_search/user_search.vue'
import Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import ChatPanel from 'components/chat_panel/chat_panel.vue'
@@ -45,7 +45,7 @@ export default (store) => {
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
- { name: 'user-search', path: '/user-search', component: UserSearch, props: (route) => ({ query: route.query.query }) },
+ { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow },
{ name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 108dc36e..ec326c45 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -100,7 +100,7 @@
<!-- eslint-disable vue/no-v-html -->
<h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
<div v-html="attachment.oembed.oembedHTML" />
- <!-- eslint-enabled vue/no-v-html -->
+ <!-- eslint-enable vue/no-v-html -->
</div>
</div>
</div>
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index c6f8354b..f1ab67a6 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -1,8 +1,5 @@
<template>
- <div
- class="block"
- style="position: relative"
- >
+ <div>
<Popper
trigger="click"
append-to-body
@@ -131,6 +128,7 @@
</div>
<button
slot="reference"
+ class="btn btn-default btn-block"
:class="{ pressed: showDropDown }"
@click="toggleMenu"
>
diff --git a/src/components/progress_button/progress_button.vue b/src/components/progress_button/progress_button.vue
index d19aa97d..283a51af 100644
--- a/src/components/progress_button/progress_button.vue
+++ b/src/components/progress_button/progress_button.vue
@@ -3,7 +3,7 @@
:disabled="progress || disabled"
@click="onClick"
>
- <template v-if="progress">
+ <template v-if="progress && $slots.progress">
<slot name="progress" />
</template>
<template v-else>
diff --git a/src/components/search/search.js b/src/components/search/search.js
new file mode 100644
index 00000000..b434e127
--- /dev/null
+++ b/src/components/search/search.js
@@ -0,0 +1,98 @@
+import FollowCard from '../follow_card/follow_card.vue'
+import Conversation from '../conversation/conversation.vue'
+import Status from '../status/status.vue'
+import map from 'lodash/map'
+
+const Search = {
+ components: {
+ FollowCard,
+ Conversation,
+ Status
+ },
+ props: [
+ 'query'
+ ],
+ data () {
+ return {
+ loaded: false,
+ loading: false,
+ searchTerm: this.query || '',
+ userIds: [],
+ statuses: [],
+ hashtags: [],
+ currenResultTab: 'statuses'
+ }
+ },
+ computed: {
+ users () {
+ return this.userIds.map(userId => this.$store.getters.findUser(userId))
+ },
+ visibleStatuses () {
+ const allStatusesObject = this.$store.state.statuses.allStatusesObject
+
+ return this.statuses.filter(status =>
+ allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
+ )
+ }
+ },
+ mounted () {
+ this.search(this.query)
+ },
+ watch: {
+ query (newValue) {
+ this.searchTerm = newValue
+ this.search(newValue)
+ }
+ },
+ methods: {
+ newQuery (query) {
+ this.$router.push({ name: 'search', query: { query } })
+ this.$refs.searchInput.focus()
+ },
+ search (query) {
+ if (!query) {
+ this.loading = false
+ return
+ }
+
+ this.loading = true
+ this.userIds = []
+ this.statuses = []
+ this.hashtags = []
+ this.$refs.searchInput.blur()
+
+ this.$store.dispatch('search', { q: query, resolve: true })
+ .then(data => {
+ this.loading = false
+ this.userIds = map(data.accounts, 'id')
+ this.statuses = data.statuses
+ this.hashtags = data.hashtags
+ this.currenResultTab = this.getActiveTab()
+ this.loaded = true
+ })
+ },
+ resultCount (tabName) {
+ const length = this[tabName].length
+ return length === 0 ? '' : ` (${length})`
+ },
+ onResultTabSwitch (_index, dataset) {
+ this.currenResultTab = dataset.filter
+ },
+ getActiveTab () {
+ if (this.visibleStatuses.length > 0) {
+ return 'statuses'
+ } else if (this.users.length > 0) {
+ return 'people'
+ } else if (this.hashtags.length > 0) {
+ return 'hashtags'
+ }
+
+ return 'statuses'
+ },
+ lastHistoryRecord (hashtag) {
+ return hashtag.history && hashtag.history[0]
+ }
+ }
+}
+
+export default Search
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
new file mode 100644
index 00000000..4350e672
--- /dev/null
+++ b/src/components/search/search.vue
@@ -0,0 +1,211 @@
+<template>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="title">
+ {{ $t('nav.search') }}
+ </div>
+ </div>
+ <div class="search-input-container">
+ <input
+ ref="searchInput"
+ v-model="searchTerm"
+ class="search-input"
+ :placeholder="$t('nav.search')"
+ @keyup.enter="newQuery(searchTerm)"
+ >
+ <button
+ class="btn search-button"
+ @click="newQuery(searchTerm)"
+ >
+ <i class="icon-search" />
+ </button>
+ </div>
+ <div
+ v-if="loading"
+ class="text-center loading-icon"
+ >
+ <i class="icon-spin3 animate-spin" />
+ </div>
+ <div v-else-if="loaded">
+ <div class="search-nav-heading">
+ <tab-switcher
+ ref="tabSwitcher"
+ :on-switch="onResultTabSwitch"
+ :custom-active="currenResultTab"
+ >
+ <span
+ data-tab-dummy
+ data-filter="statuses"
+ :label="$t('user_card.statuses') + resultCount('visibleStatuses')"
+ />
+ <span
+ data-tab-dummy
+ data-filter="people"
+ :label="$t('search.people') + resultCount('users')"
+ />
+ <span
+ data-tab-dummy
+ data-filter="hashtags"
+ :label="$t('search.hashtags') + resultCount('hashtags')"
+ />
+ </tab-switcher>
+ </div>
+ </div>
+ <div class="panel-body">
+ <div v-if="currenResultTab === 'statuses'">
+ <div
+ v-if="visibleStatuses.length === 0 && !loading && loaded"
+ class="search-result-heading"
+ >
+ <h4>{{ $t('search.no_results') }}</h4>
+ </div>
+ <Status
+ v-for="status in visibleStatuses"
+ :key="status.id"
+ :collapsable="false"
+ :expandable="false"
+ :compact="false"
+ class="search-result"
+ :statusoid="status"
+ :no-heading="false"
+ />
+ </div>
+ <div v-else-if="currenResultTab === 'people'">
+ <div
+ v-if="users.length === 0 && !loading && loaded"
+ class="search-result-heading"
+ >
+ <h4>{{ $t('search.no_results') }}</h4>
+ </div>
+ <FollowCard
+ v-for="user in users"
+ :key="user.id"
+ :user="user"
+ class="list-item search-result"
+ />
+ </div>
+ <div v-else-if="currenResultTab === 'hashtags'">
+ <div
+ v-if="hashtags.length === 0 && !loading && loaded"
+ class="search-result-heading"
+ >
+ <h4>{{ $t('search.no_results') }}</h4>
+ </div>
+ <div
+ v-for="hashtag in hashtags"
+ :key="hashtag.url"
+ class="status trend search-result"
+ >
+ <div class="hashtag">
+ <router-link :to="{ name: 'tag-timeline', params: { tag: hashtag.name } }">
+ #{{ hashtag.name }}
+ </router-link>
+ <div v-if="lastHistoryRecord(hashtag)">
+ <span v-if="lastHistoryRecord(hashtag).accounts == 1">
+ {{ $t('search.person_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+ </span>
+ <span v-else>
+ {{ $t('search.people_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+ </span>
+ </div>
+ </div>
+ <div
+ v-if="lastHistoryRecord(hashtag)"
+ class="count"
+ >
+ {{ lastHistoryRecord(hashtag).uses }}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="search-result-footer text-center panel-footer faint" />
+ </div>
+</template>
+
+<script src="./search.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.search-result-heading {
+ color: $fallback--faint;
+ color: var(--faint, $fallback--faint);
+ padding: 0.75rem;
+ text-align: center;
+}
+
+@media all and (max-width: 800px) {
+ .search-nav-heading {
+ .tab-switcher .tabs .tab-wrapper {
+ display: block;
+ justify-content: center;
+ flex: 1 1 auto;
+ text-align: center;
+ }
+ }
+}
+
+.search-result {
+ box-sizing: border-box;
+ border-bottom: 1px solid;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+}
+
+.search-result-footer {
+ border-width: 1px 0 0 0;
+ border-style: solid;
+ border-color: var(--border, $fallback--border);
+ padding: 10px;
+ background-color: $fallback--fg;
+ background-color: var(--panel, $fallback--fg);
+}
+
+.search-input-container {
+ padding: 0.8rem;
+ display: flex;
+ justify-content: center;
+
+ .search-input {
+ width: 100%;
+ line-height: 1.125rem;
+ font-size: 1rem;
+ padding: 0.5rem;
+ box-sizing: border-box;
+ }
+
+ .search-button {
+ margin-left: 0.5em;
+ }
+}
+
+.loading-icon {
+ padding: 1em;
+}
+
+.trend {
+ display: flex;
+ align-items: center;
+
+ .hashtag {
+ flex: 1 1 auto;
+ color: $fallback--text;
+ color: var(--text, $fallback--text);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .count {
+ flex: 0 0 auto;
+ width: 2rem;
+ font-size: 1.5rem;
+ line-height: 2.25rem;
+ font-weight: 500;
+ text-align: center;
+ color: $fallback--text;
+ color: var(--text, $fallback--text);
+ }
+}
+
+</style>
diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js
new file mode 100644
index 00000000..b8a792ee
--- /dev/null
+++ b/src/components/search_bar/search_bar.js
@@ -0,0 +1,27 @@
+const SearchBar = {
+ data: () => ({
+ searchTerm: undefined,
+ hidden: true,
+ error: false,
+ loading: false
+ }),
+ watch: {
+ '$route': function (route) {
+ if (route.name === 'search') {
+ this.searchTerm = route.query.query
+ }
+ }
+ },
+ methods: {
+ find (searchTerm) {
+ this.$router.push({ name: 'search', query: { query: searchTerm } })
+ this.$refs.searchInput.focus()
+ },
+ toggleHidden () {
+ this.hidden = !this.hidden
+ this.$emit('toggled', this.hidden)
+ }
+ }
+}
+
+export default SearchBar
diff --git a/src/components/user_finder/user_finder.vue b/src/components/search_bar/search_bar.vue
index 39d49237..4d5a1aec 100644
--- a/src/components/user_finder/user_finder.vue
+++ b/src/components/search_bar/search_bar.vue
@@ -1,36 +1,36 @@
<template>
<div>
- <div class="user-finder-container">
+ <div class="search-bar-container">
<i
v-if="loading"
- class="icon-spin4 user-finder-icon animate-spin-slow"
+ class="icon-spin4 finder-icon animate-spin-slow"
/>
<a
v-if="hidden"
href="#"
- :title="$t('finder.find_user')"
+ :title="$t('nav.search')"
><i
- class="icon-user-plus user-finder-icon"
+ class="button-icon icon-search"
@click.prevent.stop="toggleHidden"
/></a>
<template v-else>
<input
- id="user-finder-input"
- ref="userSearchInput"
- v-model="username"
- class="user-finder-input"
- :placeholder="$t('finder.find_user')"
+ id="search-bar-input"
+ ref="searchInput"
+ v-model="searchTerm"
+ class="search-bar-input"
+ :placeholder="$t('nav.search')"
type="text"
- @keyup.enter="findUser(username)"
+ @keyup.enter="find(searchTerm)"
>
<button
class="btn search-button"
- @click="findUser(username)"
+ @click="find(searchTerm)"
>
<i class="icon-search" />
</button>
<i
- class="button-icon icon-cancel user-finder-icon"
+ class="button-icon icon-cancel"
@click.prevent.stop="toggleHidden"
/>
</template>
@@ -38,22 +38,24 @@
</div>
</template>
-<script src="./user_finder.js"></script>
+<script src="./search_bar.js"></script>
<style lang="scss">
@import '../../_variables.scss';
-.user-finder-container {
+.search-bar-container {
max-width: 100%;
display: inline-flex;
align-items: baseline;
vertical-align: baseline;
+ justify-content: flex-end;
- .user-finder-input,
+ .search-bar-input,
.search-button {
height: 29px;
}
- .user-finder-input {
+
+ .search-bar-input {
// TODO: do this properly without a rough guesstimate of 2 icons + paddings
max-width: calc(100% - 30px - 30px - 20px);
}
@@ -62,6 +64,10 @@
margin-left: .5em;
margin-right: .5em;
}
+
+ .icon-cancel {
+ cursor: pointer;
+ }
}
</style>
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 80b75ce5..5b2d4473 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -100,8 +100,8 @@
</ul>
<ul>
<li @click="toggleDrawer">
- <router-link :to="{ name: 'user-search' }">
- {{ $t("nav.user_search") }}
+ <router-link :to="{ name: 'search' }">
+ {{ $t("nav.search") }}
</router-link>
</li>
<li
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 199351cb..7911d40d 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -173,12 +173,13 @@ const Status = {
if (this.status.type === 'retweet') {
return false
}
- var checkFollowing = this.$store.state.config.replyVisibility === 'following'
+ const checkFollowing = this.$store.state.config.replyVisibility === 'following'
for (var i = 0; i < this.status.attentions.length; ++i) {
if (this.status.user.id === this.status.attentions[i].id) {
continue
}
- if (checkFollowing && this.$store.getters.findUser(this.status.attentions[i].id).following) {
+ const taggedUser = this.$store.getters.findUser(this.status.attentions[i].id)
+ if (checkFollowing && taggedUser && taggedUser.following) {
return false
}
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
@@ -418,6 +419,18 @@ const Status = {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
+ },
+ 'status.repeat_num': function (num) {
+ // refetch repeats when repeat_num is changed in any way
+ if (this.isFocused && this.statusFromGlobalRepository.rebloggedBy && this.statusFromGlobalRepository.rebloggedBy.length !== num) {
+ this.$store.dispatch('fetchRepeats', this.status.id)
+ }
+ },
+ 'status.fave_num': function (num) {
+ // refetch favs when fave_num is changed in any way
+ if (this.isFocused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num) {
+ this.$store.dispatch('fetchFavs', this.status.id)
+ }
}
},
filters: {
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 81e4d333..67835231 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -4,7 +4,7 @@ import './tab_switcher.scss'
export default Vue.component('tab-switcher', {
name: 'TabSwitcher',
- props: ['renderOnlyFocused', 'onSwitch'],
+ props: ['renderOnlyFocused', 'onSwitch', 'customActive'],
data () {
return {
active: this.$slots.default.findIndex(_ => _.tag)
@@ -24,6 +24,14 @@ export default Vue.component('tab-switcher', {
}
this.active = index
}
+ },
+ isActiveTab (index) {
+ const customActiveIndex = this.$slots.default.findIndex(slot => {
+ const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter']
+ return this.customActive && this.customActive === dataFilter
+ })
+
+ return customActiveIndex > -1 ? customActiveIndex === index : index === this.active
}
},
render (h) {
@@ -33,7 +41,7 @@ export default Vue.component('tab-switcher', {
const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
- if (index === this.active) {
+ if (this.isActiveTab(index)) {
classesTab.push('active')
classesWrapper.push('active')
}
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 92cd0e54..e019ebbd 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -1,5 +1,6 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
+import ProgressButton from '../progress_button/progress_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
@@ -104,7 +105,8 @@ export default {
components: {
UserAvatar,
RemoteFollow,
- ModerationTools
+ ModerationTools,
+ ProgressButton
},
methods: {
followUser () {
@@ -135,6 +137,12 @@ export default {
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
},
+ subscribeUser () {
+ return this.$store.dispatch('subscribeUser', this.user.id)
+ },
+ unsubscribeUser () {
+ return this.$store.dispatch('unsubscribeUser', this.user.id)
+ },
setProfileView (v) {
if (this.switcher) {
const store = this.$store
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 5a5a4881..f987fbbb 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -112,101 +112,120 @@
</div>
</div>
<div
- v-if="isOtherUser"
+ v-if="loggedIn && isOtherUser"
class="user-interactions"
>
- <div
- v-if="loggedIn"
- class="follow"
- >
- <span v-if="user.following">
- <!--Following them!-->
- <button
- class="pressed"
- :disabled="followRequestInProgress"
- :title="$t('user_card.follow_unfollow')"
- @click="unfollowUser"
- >
- <template v-if="followRequestInProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else>
- {{ $t('user_card.following') }}
- </template>
- </button>
- </span>
- <span v-if="!user.following">
- <button
- :disabled="followRequestInProgress"
- :title="followRequestSent ? $t('user_card.follow_again') : ''"
- @click="followUser"
- >
- <template v-if="followRequestInProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else-if="followRequestSent">
- {{ $t('user_card.follow_sent') }}
- </template>
- <template v-else>
- {{ $t('user_card.follow') }}
- </template>
- </button>
- </span>
+ <div v-if="!user.following">
+ <button
+ class="btn btn-default btn-block"
+ :disabled="followRequestInProgress"
+ :title="followRequestSent ? $t('user_card.follow_again') : ''"
+ @click="followUser"
+ >
+ <template v-if="followRequestInProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else-if="followRequestSent">
+ {{ $t('user_card.follow_sent') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow') }}
+ </template>
+ </button>
+ </div>
+ <div v-else-if="followRequestInProgress">
+ <button
+ class="btn btn-default btn-block pressed"
+ disabled
+ :title="$t('user_card.follow_unfollow')"
+ @click="unfollowUser"
+ >
+ {{ $t('user_card.follow_progress') }}
+ </button>
</div>
<div
- v-if="isOtherUser && loggedIn"
- class="mute"
+ v-else
+ class="btn-group"
>
- <span v-if="user.muted">
- <button
- class="pressed"
- @click="unmuteUser"
- >
- {{ $t('user_card.muted') }}
- </button>
- </span>
- <span v-if="!user.muted">
- <button @click="muteUser">
- {{ $t('user_card.mute') }}
- </button>
- </span>
+ <button
+ class="btn btn-default pressed"
+ :title="$t('user_card.follow_unfollow')"
+ @click="unfollowUser"
+ >
+ {{ $t('user_card.following') }}
+ </button>
+ <ProgressButton
+ v-if="!user.subscribed"
+ class="btn btn-default"
+ :click="subscribeUser"
+ :title="$t('user_card.subscribe')"
+ >
+ <i class="icon-bell-alt" />
+ </ProgressButton>
+ <ProgressButton
+ v-else
+ class="btn btn-default pressed"
+ :click="unsubscribeUser"
+ :title="$t('user_card.unsubscribe')"
+ >
+ <i class="icon-bell-ringing-o" />
+ </ProgressButton>
</div>
- <div v-if="!loggedIn && user.is_local">
- <RemoteFollow :user="user" />
+
+ <div>
+ <button
+ v-if="user.muted"
+ class="btn btn-default btn-block pressed"
+ @click="unmuteUser"
+ >
+ {{ $t('user_card.muted') }}
+ </button>
+ <button
+ v-else
+ class="btn btn-default btn-block"
+ @click="muteUser"
+ >
+ {{ $t('user_card.mute') }}
+ </button>
</div>
- <div
- v-if="isOtherUser && loggedIn"
- class="block"
- >
- <span v-if="user.statusnet_blocking">
- <button
- class="pressed"
- @click="unblockUser"
- >
- {{ $t('user_card.blocked') }}
- </button>
- </span>
- <span v-if="!user.statusnet_blocking">
- <button @click="blockUser">
- {{ $t('user_card.block') }}
- </button>
- </span>
+
+ <div>
+ <button
+ v-if="user.statusnet_blocking"
+ class="btn btn-default btn-block pressed"
+ @click="unblockUser"
+ >
+ {{ $t('user_card.blocked') }}
+ </button>
+ <button
+ v-else
+ class="btn btn-default btn-block"
+ @click="blockUser"
+ >
+ {{ $t('user_card.block') }}
+ </button>
</div>
- <div
- v-if="isOtherUser && loggedIn"
- class="block"
- >
- <span>
- <button @click="reportUser">
- {{ $t('user_card.report') }}
- </button>
- </span>
+
+ <div>
+ <button
+ class="btn btn-default btn-block"
+ @click="reportUser"
+ >
+ {{ $t('user_card.report') }}
+ </button>
</div>
+
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
/>
</div>
+ <div
+ v-if="!loggedIn && user.is_local"
+ class="user-interactions"
+ >
+ <RemoteFollow :user="user" />
+ </div>
</div>
</div>
<div
@@ -487,40 +506,22 @@
display: flex;
flex-flow: row wrap;
justify-content: space-between;
-
margin-right: -.75em;
- div {
+ > * {
flex: 1 0 0;
- margin-right: .75em;
- margin-bottom: .6em;
+ margin: 0 .75em .6em 0;
white-space: nowrap;
}
- .mute {
- max-width: 220px;
- min-height: 28px;
- }
-
- .follow {
- max-width: 220px;
- min-height: 28px;
- }
-
button {
- width: 100%;
- height: 100%;
margin: 0;
- }
-
- .remote-button {
- height: 28px !important;
- width: 92%;
- }
- .pressed {
- border-bottom-color: rgba(255, 255, 255, 0.2);
- border-top-color: rgba(0, 0, 0, 0.2);
+ &.pressed {
+ // TODO: This should be themed.
+ border-bottom-color: rgba(255, 255, 255, 0.2);
+ border-top-color: rgba(0, 0, 0, 0.2);
+ }
}
}
}
diff --git a/src/components/user_finder/user_finder.js b/src/components/user_finder/user_finder.js
deleted file mode 100644
index 27153f45..00000000
--- a/src/components/user_finder/user_finder.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const UserFinder = {
- data: () => ({
- username: undefined,
- hidden: true,
- error: false,
- loading: false
- }),
- methods: {
- findUser (username) {
- this.$router.push({ name: 'user-search', query: { query: username } })
- this.$refs.userSearchInput.focus()
- },
- toggleHidden () {
- this.hidden = !this.hidden
- this.$emit('toggled', this.hidden)
- }
- }
-}
-
-export default UserFinder
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 7eb4ed3a..39b99dac 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -3,7 +3,6 @@ import UserCard from '../user_card/user_card.vue'
import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue'
import Conversation from '../conversation/conversation.vue'
-import ModerationTools from '../moderation_tools/moderation_tools.vue'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
@@ -132,7 +131,6 @@ const UserProfile = {
Timeline,
FollowerList,
FriendList,
- ModerationTools,
FollowCard,
Conversation
}
diff --git a/src/components/user_search/user_search.js b/src/components/user_search/user_search.js
deleted file mode 100644
index 62dafdf1..00000000
--- a/src/components/user_search/user_search.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import FollowCard from '../follow_card/follow_card.vue'
-import map from 'lodash/map'
-
-const userSearch = {
- components: {
- FollowCard
- },
- props: [
- 'query'
- ],
- data () {
- return {
- username: '',
- userIds: [],
- loading: false
- }
- },
- computed: {
- users () {
- return this.userIds.map(userId => this.$store.getters.findUser(userId))
- }
- },
- mounted () {
- this.search(this.query)
- },
- watch: {
- query (newV) {
- this.search(newV)
- }
- },
- methods: {
- newQuery (query) {
- this.$router.push({ name: 'user-search', query: { query } })
- this.$refs.userSearchInput.focus()
- },
- search (query) {
- if (!query) {
- this.users = []
- return
- }
- this.loading = true
- this.$store.dispatch('searchUsers', query)
- .then((res) => {
- this.loading = false
- this.userIds = map(res, 'id')
- })
- }
- }
-}
-
-export default userSearch
diff --git a/src/components/user_search/user_search.vue b/src/components/user_search/user_search.vue
deleted file mode 100644
index e1c6074c..00000000
--- a/src/components/user_search/user_search.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<template>
- <div class="user-search panel panel-default">
- <div class="panel-heading">
- {{ $t('nav.user_search') }}
- </div>
- <div class="user-search-input-container">
- <input
- ref="userSearchInput"
- v-model="username"
- class="user-finder-input"
- :placeholder="$t('finder.find_user')"
- @keyup.enter="newQuery(username)"
- >
- <button
- class="btn search-button"
- @click="newQuery(username)"
- >
- <i class="icon-search" />
- </button>
- </div>
- <div
- v-if="loading"
- class="text-center loading-icon"
- >
- <i class="icon-spin3 animate-spin" />
- </div>
- <div
- v-else
- class="panel-body"
- >
- <FollowCard
- v-for="user in users"
- :key="user.id"
- :user="user"
- class="list-item"
- />
- </div>
- </div>
-</template>
-
-<script src="./user_search.js"></script>
-
-<style lang="scss">
-.user-search-input-container {
- margin: 0.5em;
- display: flex;
- justify-content: center;
-
- .search-button {
- margin-left: 0.5em;
- }
-}
-
-.loading-icon {
- padding: 1em;
-}
-</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 19f4604f..0c3b2aef 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -17,7 +17,6 @@ import Autosuggest from '../autosuggest/autosuggest.vue'
import Importer from '../importer/importer.vue'
import Exporter from '../exporter/exporter.vue'
import withSubscription from '../../hocs/with_subscription/with_subscription'
-import userSearchApi from '../../services/new_api/user_search.js'
import Mfa from './mfa.vue'
const BlockList = withSubscription({
@@ -322,11 +321,8 @@ const UserSettings = {
})
},
queryUserIds (query) {
- return userSearchApi.search({ query, store: this.$store })
- .then((users) => {
- this.$store.dispatch('addNewUsers', users)
- return map(users, 'id')
- })
+ return this.$store.dispatch('searchUsers', query)
+ .then((users) => map(users, 'id'))
},
blockUsers (ids) {
return this.$store.dispatch('blockUsers', ids)
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 0875ae78..ec90cfcf 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -78,6 +78,7 @@
"timeline": "Timeline",
"twkn": "The Whole Known Network",
"user_search": "User Search",
+ "search": "Search",
"who_to_follow": "Who to follow",
"preferences": "Preferences"
},
@@ -531,6 +532,8 @@
"remote_follow": "Remote follow",
"report": "Report",
"statuses": "Statuses",
+ "subscribe": "Subscribe",
+ "unsubscribe": "Unsubscribe",
"unblock": "Unblock",
"unblock_progress": "Unblocking...",
"block_progress": "Blocking...",
@@ -595,5 +598,12 @@
"GiB": "GiB",
"TiB": "TiB"
}
+ },
+ "search": {
+ "people": "People",
+ "hashtags": "Hashtags",
+ "person_talking": "{count} person talking",
+ "people_talking": "{count} people talking",
+ "no_results": "No results"
}
}
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index d24ef0cb..90ed6664 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -38,7 +38,8 @@
"interactions": "Взаимодействия",
"public_tl": "Публичная лента",
"timeline": "Лента",
- "twkn": "Федеративная лента"
+ "twkn": "Федеративная лента",
+ "search": "Поиск"
},
"notifications": {
"broken_favorite": "Неизвестный статус, ищем...",
@@ -381,5 +382,12 @@
},
"user_profile": {
"timeline_title": "Лента пользователя"
+ },
+ "search": {
+ "people": "Люди",
+ "hashtags": "Хэштэги",
+ "person_talking": "Популярно у {count} человека",
+ "people_talking": "Популярно у {count} человек",
+ "no_results": "Ничего не найдено"
}
}
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 8bf13d44..e863d8a5 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -496,10 +496,19 @@ export const mutations = {
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
- addFavsAndRepeats (state, { id, favoritedByUsers, rebloggedByUsers }) {
+ addRepeats (state, { id, rebloggedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[id]
- newStatus.favoritedBy = favoritedByUsers.filter(_ => _)
newStatus.rebloggedBy = rebloggedByUsers.filter(_ => _)
+ // repeats stats can be incorrect based on polling condition, let's update them using the most recent data
+ newStatus.repeat_num = newStatus.rebloggedBy.length
+ newStatus.repeated = !!newStatus.rebloggedBy.find(({ id }) => currentUser.id === id)
+ },
+ addFavs (state, { id, favoritedByUsers, currentUser }) {
+ const newStatus = state.allStatusesObject[id]
+ newStatus.favoritedBy = favoritedByUsers.filter(_ => _)
+ // favorites stats can be incorrect based on polling condition, let's update them using the most recent data
+ newStatus.fave_num = newStatus.favoritedBy.length
+ newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
},
updateStatusWithPoll (state, { id, poll }) {
const status = state.allStatusesObject[id]
@@ -593,9 +602,26 @@ const statuses = {
Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers(id),
rootState.api.backendInteractor.fetchRebloggedByUsers(id)
- ]).then(([favoritedByUsers, rebloggedByUsers]) =>
- commit('addFavsAndRepeats', { id, favoritedByUsers, rebloggedByUsers })
- )
+ ]).then(([favoritedByUsers, rebloggedByUsers]) => {
+ commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser })
+ commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
+ })
+ },
+ fetchFavs ({ rootState, commit }, id) {
+ rootState.api.backendInteractor.fetchFavoritedByUsers(id)
+ .then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
+ },
+ fetchRepeats ({ rootState, commit }, id) {
+ rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+ .then(rebloggedByUsers => commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser }))
+ },
+ search (store, { q, resolve, limit, offset, following }) {
+ return store.rootState.api.backendInteractor.search2({ q, resolve, limit, offset, following })
+ .then((data) => {
+ store.commit('addNewUsers', data.accounts)
+ store.commit('addNewStatuses', { statuses: data.statuses })
+ return data
+ })
}
},
mutations
diff --git a/src/modules/users.js b/src/modules/users.js
index 6c7a96f6..57d3a3e3 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,5 +1,4 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
-import userSearchApi from '../services/new_api/user_search.js'
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue'
@@ -136,6 +135,7 @@ export const mutations = {
user.following = relationship.following
user.muted = relationship.muting
user.statusnet_blocking = relationship.blocking
+ user.subscribed = relationship.subscribing
}
})
},
@@ -305,6 +305,14 @@ const users = {
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
+ subscribeUser ({ rootState, commit }, id) {
+ return rootState.api.backendInteractor.subscribeUser(id)
+ .then((relationship) => commit('updateUserRelationship', [relationship]))
+ },
+ unsubscribeUser ({ rootState, commit }, id) {
+ return rootState.api.backendInteractor.unsubscribeUser(id)
+ .then((relationship) => commit('updateUserRelationship', [relationship]))
+ },
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey
@@ -356,14 +364,7 @@ const users = {
})
},
searchUsers (store, query) {
- // TODO: Move userSearch api into api.service
- return userSearchApi.search({
- query,
- store: {
- state: store.rootState,
- getters: store.rootGetters
- }
- })
+ return store.rootState.api.backendInteractor.searchUsers(query)
.then((users) => {
store.commit('addNewUsers', users)
return users
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 5bce85a3..670f7fca 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -55,6 +55,8 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
+const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
+const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes`
@@ -67,6 +69,7 @@ const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
+const MASTODON_SEARCH_2 = `/api/v2/search`
const oldfetch = window.fetch
@@ -78,7 +81,7 @@ let fetch = (url, options) => {
return oldfetch(fullUrl, options)
}
-const promisedRequest = ({ method, url, payload, credentials, headers = {} }) => {
+const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => {
const options = {
method,
headers: {
@@ -87,6 +90,11 @@ const promisedRequest = ({ method, url, payload, credentials, headers = {} }) =>
...headers
}
}
+ if (params) {
+ url += '?' + Object.entries(params)
+ .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
+ .join('&')
+ }
if (payload) {
options.body = JSON.stringify(payload)
}
@@ -758,6 +766,14 @@ const unmuteUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
}
+const subscribeUser = ({ id, credentials }) => {
+ return promisedRequest({ url: MASTODON_SUBSCRIBE_USER(id), credentials, method: 'POST' })
+}
+
+const unsubscribeUser = ({ id, credentials }) => {
+ return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
+}
+
const fetchBlocks = ({ credentials }) => {
return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
.then((users) => users.map(parseUser))
@@ -849,6 +865,48 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
})
}
+const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
+ let url = MASTODON_SEARCH_2
+ let params = []
+
+ if (q) {
+ params.push(['q', encodeURIComponent(q)])
+ }
+
+ if (resolve) {
+ params.push(['resolve', resolve])
+ }
+
+ if (limit) {
+ params.push(['limit', limit])
+ }
+
+ if (offset) {
+ params.push(['offset', offset])
+ }
+
+ if (following) {
+ params.push(['following', true])
+ }
+
+ let queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
+ url += `?${queryString}`
+
+ return fetch(url, { headers: authHeaders(credentials) })
+ .then((data) => {
+ if (data.ok) {
+ return data
+ }
+ throw new Error('Error fetching search result', data)
+ })
+ .then((data) => { return data.json() })
+ .then((data) => {
+ data.accounts = data.accounts.slice(0, limit).map(u => parseUser(u))
+ data.statuses = data.statuses.slice(0, limit).map(s => parseStatus(s))
+ return data
+ })
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
@@ -878,6 +936,8 @@ const apiService = {
fetchMutes,
muteUser,
unmuteUser,
+ subscribeUser,
+ unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
@@ -913,7 +973,8 @@ const apiService = {
fetchFavoritedByUsers,
fetchRebloggedByUsers,
reportUser,
- updateNotificationSettings
+ updateNotificationSettings,
+ search2
}
export default apiService
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 46c097a3..0f40edf1 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -108,6 +108,8 @@ const backendInteractorService = credentials => {
const fetchMutes = () => apiService.fetchMutes({ credentials })
const muteUser = (id) => apiService.muteUser({ credentials, id })
const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
+ const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
+ const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
const fetchBlocks = () => apiService.fetchBlocks({ credentials })
const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
@@ -148,6 +150,8 @@ const backendInteractorService = credentials => {
const unfavorite = (id) => apiService.unfavorite({ id, credentials })
const retweet = (id) => apiService.retweet({ id, credentials })
const unretweet = (id) => apiService.unretweet({ id, credentials })
+ const search2 = ({ q, resolve, limit, offset, following }) =>
+ apiService.search2({ credentials, q, resolve, limit, offset, following })
const backendInteractorServiceInstance = {
fetchStatus,
@@ -167,6 +171,8 @@ const backendInteractorService = credentials => {
fetchMutes,
muteUser,
unmuteUser,
+ subscribeUser,
+ unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
@@ -209,7 +215,8 @@ const backendInteractorService = credentials => {
unfavorite,
retweet,
unretweet,
- updateNotificationSettings
+ updateNotificationSettings,
+ search2
}
return backendInteractorServiceInstance
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 6a5f1408..a79c6f07 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -68,6 +68,7 @@ export const parseUser = (data) => {
output.following = relationship.following
output.statusnet_blocking = relationship.blocking
output.muted = relationship.muting
+ output.subscribed = relationship.subscribing
}
output.hide_follows = data.pleroma.hide_follows
diff --git a/src/services/new_api/user_search.js b/src/services/new_api/user_search.js
deleted file mode 100644
index 5936fef9..00000000
--- a/src/services/new_api/user_search.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import utils from './utils.js'
-import { parseUser } from '../entity_normalizer/entity_normalizer.service.js'
-
-const search = ({ query, store }) => {
- return utils.request({
- store,
- url: '/api/v1/accounts/search',
- params: {
- q: query,
- resolve: true
- }
- })
- .then((data) => data.json())
- .then((data) => data.map(parseUser))
-}
-const UserSearch = {
- search
-}
-
-export default UserSearch
diff --git a/src/services/new_api/utils.js b/src/services/new_api/utils.js
deleted file mode 100644
index 57111026..00000000
--- a/src/services/new_api/utils.js
+++ /dev/null
@@ -1,36 +0,0 @@
-const queryParams = (params) => {
- return Object.keys(params)
- .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
- .join('&')
-}
-
-const headers = (store) => {
- const accessToken = store.getters.getToken()
- if (accessToken) {
- return { 'Authorization': `Bearer ${accessToken}` }
- } else {
- return {}
- }
-}
-
-const request = ({ method = 'GET', url, params, store }) => {
- const instance = store.state.instance.server
- let fullUrl = `${instance}${url}`
-
- if (method === 'GET' && params) {
- fullUrl = fullUrl + `?${queryParams(params)}`
- }
-
- return window.fetch(fullUrl, {
- method,
- headers: headers(store),
- credentials: 'same-origin'
- })
-}
-
-const utils = {
- queryParams,
- request
-}
-
-export default utils