Commit 69a4bcb2 authored by Eugenij's avatar Eugenij Committed by Shpuld Shpludson

New search

parent 0c064105
import UserPanel from './components/user_panel/user_panel.vue' import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue' import NavPanel from './components/nav_panel/nav_panel.vue'
import Notifications from './components/notifications/notifications.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 InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
...@@ -19,7 +19,7 @@ export default { ...@@ -19,7 +19,7 @@ export default {
UserPanel, UserPanel,
NavPanel, NavPanel,
Notifications, Notifications,
UserFinder, SearchBar,
InstanceSpecificPanel, InstanceSpecificPanel,
FeaturesPanel, FeaturesPanel,
WhoToFollowPanel, WhoToFollowPanel,
...@@ -32,7 +32,7 @@ export default { ...@@ -32,7 +32,7 @@ export default {
}, },
data: () => ({ data: () => ({
mobileActivePanel: 'timeline', mobileActivePanel: 'timeline',
finderHidden: true, searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && ( supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') || window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') || window.CSS.supports('-webkit-mask-size', 'contain') ||
...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
logoBgStyle () { logoBgStyle () {
return Object.assign({ return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`, 'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.finderHidden ? 1 : 0 opacity: this.searchBarHidden ? 1 : 0
}, this.enableMask ? {} : { }, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent' 'background-color': this.enableMask ? '' : 'transparent'
}) })
...@@ -101,8 +101,8 @@ export default { ...@@ -101,8 +101,8 @@ export default {
this.$router.replace('/main/public') this.$router.replace('/main/public')
this.$store.dispatch('logout') this.$store.dispatch('logout')
}, },
onFinderToggled (hidden) { onSearchBarToggled (hidden) {
this.finderHidden = hidden this.searchBarHidden = hidden
}, },
updateMobileState () { updateMobileState () {
const mobileLayout = windowWidth() <= 800 const mobileLayout = windowWidth() <= 800
......
...@@ -38,9 +38,9 @@ ...@@ -38,9 +38,9 @@
</router-link> </router-link>
</div> </div>
<div class="item right"> <div class="item right">
<user-finder <search-bar
class="button-icon nav-icon mobile-hidden" class="nav-icon mobile-hidden"
@toggled="onFinderToggled" @toggled="onSearchBarToggled"
/> />
<router-link <router-link
class="mobile-hidden" class="mobile-hidden"
......
...@@ -6,12 +6,12 @@ import ConversationPage from 'components/conversation-page/conversation-page.vue ...@@ -6,12 +6,12 @@ import ConversationPage from 'components/conversation-page/conversation-page.vue
import Interactions from 'components/interactions/interactions.vue' import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue' import DMs from 'components/dm_timeline/dm_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.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 Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue' import Registration from 'components/registration/registration.vue'
import UserSettings from 'components/user_settings/user_settings.vue' import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.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 Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js' import AuthForm from 'components/auth_form/auth_form.js'
import ChatPanel from 'components/chat_panel/chat_panel.vue' import ChatPanel from 'components/chat_panel/chat_panel.vue'
...@@ -45,7 +45,7 @@ export default (store) => { ...@@ -45,7 +45,7 @@ export default (store) => {
{ name: 'login', path: '/login', component: AuthForm }, { name: 'login', path: '/login', component: AuthForm },
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) }, { 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: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow },
{ name: 'about', path: '/about', component: About }, { name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile } { name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
......
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
<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>
const UserFinder = { const SearchBar = {
data: () => ({ data: () => ({
username: undefined, searchTerm: undefined,
hidden: true, hidden: true,
error: false, error: false,
loading: false loading: false
}), }),
watch: {
'$route': function (route) {
if (route.name === 'search') {
this.searchTerm = route.query.query
}
}
},
methods: { methods: {
findUser (username) { find (searchTerm) {
this.$router.push({ name: 'user-search', query: { query: username } }) this.$router.push({ name: 'search', query: { query: searchTerm } })
this.$refs.userSearchInput.focus() this.$refs.searchInput.focus()
}, },
toggleHidden () { toggleHidden () {
this.hidden = !this.hidden this.hidden = !this.hidden
...@@ -17,4 +24,4 @@ const UserFinder = { ...@@ -17,4 +24,4 @@ const UserFinder = {
} }
} }
export default UserFinder export default SearchBar
<template> <template>
<div> <div>
<div class="user-finder-container"> <div class="search-bar-container">
<i <i
v-if="loading" v-if="loading"
class="icon-spin4 user-finder-icon animate-spin-slow" class="icon-spin4 finder-icon animate-spin-slow"
/> />
<a <a
v-if="hidden" v-if="hidden"
href="#" href="#"
:title="$t('finder.find_user')" :title="$t('nav.search')"
><i ><i
class="icon-user-plus user-finder-icon" class="button-icon icon-search"
@click.prevent.stop="toggleHidden" @click.prevent.stop="toggleHidden"
/></a> /></a>
<template v-else> <template v-else>
<input <input
id="user-finder-input" id="search-bar-input"
ref="userSearchInput" ref="searchInput"
v-model="username" v-model="searchTerm"
class="user-finder-input" class="search-bar-input"
:placeholder="$t('finder.find_user')" :placeholder="$t('nav.search')"
type="text" type="text"
@keyup.enter="findUser(username)" @keyup.enter="find(searchTerm)"
> >
<button <button
class="btn search-button" class="btn search-button"
@click="findUser(username)" @click="find(searchTerm)"
> >
<i class="icon-search" /> <i class="icon-search" />
</button> </button>
<i <i
class="button-icon icon-cancel user-finder-icon" class="button-icon icon-cancel"
@click.prevent.stop="toggleHidden" @click.prevent.stop="toggleHidden"
/> />
</template> </template>
...@@ -38,22 +38,24 @@ ...@@ -38,22 +38,24 @@
</div> </div>
</template> </template>
<script src="./user_finder.js"></script> <script src="./search_bar.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.user-finder-container { .search-bar-container {
max-width: 100%; max-width: 100%;
display: inline-flex; display: inline-flex;
align-items: baseline; align-items: baseline;
vertical-align: baseline; vertical-align: baseline;
justify-content: flex-end;
.user-finder-input, .search-bar-input,
.search-button { .search-button {
height: 29px; height: 29px;
} }
.user-finder-input {
.search-bar-input {
// TODO: do this properly without a rough guesstimate of 2 icons + paddings // TODO: do this properly without a rough guesstimate of 2 icons + paddings
max-width: calc(100% - 30px - 30px - 20px); max-width: calc(100% - 30px - 30px - 20px);
} }
...@@ -62,6 +64,10 @@ ...@@ -62,6 +64,10 @@
margin-left: .5em; margin-left: .5em;
margin-right: .5em; margin-right: .5em;
} }
.icon-cancel {
cursor: pointer;
}
} }
</style> </style>
...@@ -100,8 +100,8 @@ ...@@ -100,8 +100,8 @@
</ul> </ul>
<ul> <ul>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'user-search' }"> <router-link :to="{ name: 'search' }">
{{ $t("nav.user_search") }} {{ $t("nav.search") }}
</router-link> </router-link>
</li> </li>
<li <li
......
...@@ -4,7 +4,7 @@ import './tab_switcher.scss' ...@@ -4,7 +4,7 @@ import './tab_switcher.scss'
export default Vue.component('tab-switcher', { export default Vue.component('tab-switcher', {
name: 'TabSwitcher', name: 'TabSwitcher',
props: ['renderOnlyFocused', 'onSwitch'], props: ['renderOnlyFocused', 'onSwitch', 'customActive'],
data () { data () {
return { return {
active: this.$slots.default.findIndex(_ => _.tag) active: this.$slots.default.findIndex(_ => _.tag)
...@@ -24,6 +24,14 @@ export default Vue.component('tab-switcher', { ...@@ -24,6 +24,14 @@ export default Vue.component('tab-switcher', {
} }
this.active = index this.active = index
} }
},
isActiveTab (index) {