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.vue b/src/App.vue
index 758c9fce..be4d1f75 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -38,9 +38,9 @@
-
{
{ 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/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 @@
+
+
+
+
+ {{ $t('nav.search') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('search.no_results') }}
+
+
+
+
+
+
{{ $t('search.no_results') }}
+
+
+
+
+
+
{{ $t('search.no_results') }}
+
+
+
+
+ #{{ hashtag.name }}
+
+
+
+ {{ $t('search.person_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+
+
+ {{ $t('search.people_talking', { count: lastHistoryRecord(hashtag).accounts }) }}
+
+
+
+
+ {{ lastHistoryRecord(hashtag).uses }}
+
+
+
+
+
+
+
+
+
+
+
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
similarity index 58%
rename from src/components/user_finder/user_finder.vue
rename to 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 @@
-
+
-
+
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 @@
-
-
- {{ $t("nav.user_search") }}
+
+ {{ $t("nav.search") }}
- _.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_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_search/user_search.js b/src/components/user_search/user_search.js
deleted file mode 100644
index 5c29d8f2..00000000
--- a/src/components/user_search/user_search.js
+++ /dev/null
@@ -1,49 +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) {
- return
- }
- this.loading = true
- this.userIds = []
- this.$store.dispatch('searchUsers', query)
- .then((res) => { this.userIds = map(res, 'id') })
- .finally(() => { this.loading = false })
- }
- }
-}
-
-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 @@
-
-
-
- {{ $t('nav.user_search') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 49989f78..b5c6b1d8 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"
},
@@ -595,5 +596,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 9d8d768c..7d5d5a67 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -602,6 +602,14 @@ const statuses = {
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/services/api/api.service.js b/src/services/api/api.service.js
index e417cf29..2de1c3b7 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -67,7 +67,7 @@ const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
-const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const MASTODON_SEARCH_2 = `/api/v2/search`
const oldfetch = window.fetch
@@ -853,16 +853,46 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
})
}
-const searchUsers = ({ credentials, query }) => {
- return promisedRequest({
- url: MASTODON_USER_SEARCH_URL,
- params: {
- q: query,
- resolve: true
- },
- credentials
- })
- .then((data) => data.map(parseUser))
+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 = {
@@ -930,7 +960,7 @@ const apiService = {
fetchRebloggedByUsers,
reportUser,
updateNotificationSettings,
- searchUsers
+ 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 4e1675c2..4f067df9 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -148,8 +148,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 searchUsers = (query) => apiService.searchUsers({ query, credentials })
+ const search2 = ({ q, resolve, limit, offset, following }) =>
+ apiService.search2({ credentials, q, resolve, limit, offset, following })
const backendInteractorServiceInstance = {
fetchStatus,
@@ -212,7 +212,7 @@ const backendInteractorService = credentials => {
retweet,
unretweet,
updateNotificationSettings,
- searchUsers
+ search2
}
return backendInteractorServiceInstance