close #50 #115 search boards and pins by tagpull/180/head
@@ -36,7 +36,8 @@ class PinViewSet(viewsets.ModelViewSet): | |||
class BoardViewSet(viewsets.ModelViewSet): | |||
serializer_class = api.BoardSerializer | |||
filter_backends = (DjangoFilterBackend, OrderingFilter) | |||
filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter) | |||
search_fields = ("name", ) | |||
filter_fields = ("submitter__username", ) | |||
ordering_fields = ('-id', ) | |||
ordering = ('-id', ) | |||
@@ -118,14 +118,15 @@ export default { | |||
BoardEditorUI, | |||
}, | |||
data: initialData, | |||
props: ['boardUsername'], | |||
props: ['filters'], | |||
watch: { | |||
boardUsername() { | |||
filters() { | |||
this.reset(); | |||
}, | |||
}, | |||
methods: { | |||
initialize() { | |||
this.initializeMeta(); | |||
this.fetchMore(true); | |||
}, | |||
initializeMeta() { | |||
@@ -205,8 +206,21 @@ export default { | |||
if (!this.shouldFetchMore(created)) { | |||
return; | |||
} | |||
let promise; | |||
if (this.filters.boardUsername) { | |||
promise = API.fetchBoardForUser( | |||
this.filters.boardUsername, | |||
this.status.offset, | |||
); | |||
} else if (this.filters.boardNameContains) { | |||
promise = API.Board.fetchListWhichContains( | |||
this.filters.boardNameContains, | |||
this.status.offset, | |||
); | |||
} else { | |||
return; | |||
} | |||
this.status.loading = true; | |||
const promise = API.fetchBoardForUser(this.boardUsername, this.status.offset); | |||
promise.then( | |||
(resp) => { | |||
const { results, next } = resp.data; | |||
@@ -227,7 +241,6 @@ export default { | |||
created() { | |||
bus.bus.$on(bus.events.refreshBoards, this.reset); | |||
this.registerScrollEvent(); | |||
this.initializeMeta(); | |||
this.initialize(); | |||
}, | |||
}; | |||
@@ -43,7 +43,7 @@ | |||
v-if="user.loggedIn" | |||
class="navbar-item has-dropdown is-hoverable"> | |||
<a class="navbar-link"> | |||
My Collections | |||
My | |||
</a> | |||
<div class="navbar-dropdown"> | |||
<router-link | |||
@@ -73,6 +73,15 @@ | |||
</div> | |||
</div> | |||
<div class="navbar-end"> | |||
<router-link | |||
:to="{ name: 'search' }" | |||
class="navbar-item"> | |||
<b-icon | |||
type="is-dark" | |||
icon="magnify" | |||
custom-size="mdi-24px"> | |||
</b-icon> | |||
</router-link> | |||
<div class="navbar-item"> | |||
<div class="buttons"> | |||
<a | |||
@@ -31,6 +31,15 @@ const Board = { | |||
const url = `${API_PREFIX}boards-auto-complete/?submitter__username=${username}`; | |||
return axios.get(url); | |||
}, | |||
fetchSiteFullList() { | |||
const url = `${API_PREFIX}boards-auto-complete/`; | |||
return axios.get(url); | |||
}, | |||
fetchListWhichContains(text, offset = 0, limit = 50) { | |||
const prefix = `${API_PREFIX}boards/?search=${text}`; | |||
const url = `${prefix}&offset=${offset}&limit=${limit}`; | |||
return axios.get(url); | |||
}, | |||
saveChanges(boardId, fieldsForm) { | |||
const url = `${API_PREFIX}boards/${boardId}/`; | |||
return axios.patch( | |||
@@ -0,0 +1,129 @@ | |||
<template> | |||
<div class="search-panel"> | |||
<div class="filter-selector"> | |||
<div class="card-content"> | |||
<b-field> | |||
<b-select placeholder="Choose Filter" v-model="filterType"> | |||
<option>Tag</option> | |||
<option>Board</option> | |||
</b-select> | |||
<b-autocomplete | |||
v-show="filterType === 'Tag'" | |||
class="search-input" | |||
v-model="name" | |||
:data="filteredDataArray" | |||
:keep-first="true" | |||
:open-on-focus="true" | |||
placeholder="select a filter then type to filter" | |||
icon="magnify" | |||
@select="option => selected = option"> | |||
<template slot="empty">No results found</template> | |||
</b-autocomplete> | |||
<template v-if="filterType === 'Board'"> | |||
<b-input | |||
class="search-input" | |||
type="search" | |||
v-model="boardText" | |||
placeholder="type to search board" | |||
icon="magnify" | |||
> | |||
</b-input> | |||
<p class="control"> | |||
<b-button @click="searchBoard" class="button is-primary">Search</b-button> | |||
</p> | |||
</template> | |||
</b-field> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import api from '../api'; | |||
export default { | |||
name: 'FilterSelector', | |||
data() { | |||
return { | |||
filterType: null, | |||
selectedOption: [], | |||
options: { | |||
Tag: [], | |||
}, | |||
name: '', | |||
boardText: '', | |||
selected: null, | |||
}; | |||
}, | |||
methods: { | |||
selectOption(filterName) { | |||
this.name = ''; | |||
this.boardText = ''; | |||
if (filterName === 'Tag') { | |||
this.selectedOption = this.options.Tag; | |||
} | |||
}, | |||
searchBoard() { | |||
if (this.boardText === '') { | |||
return; | |||
} | |||
this.$emit( | |||
'selected', | |||
{ filterType: this.filterType, selected: this.boardText }, | |||
); | |||
}, | |||
}, | |||
watch: { | |||
filterType(newVal) { | |||
this.selectOption(newVal); | |||
}, | |||
selected(newVal) { | |||
this.$emit( | |||
'selected', | |||
{ filterType: this.filterType, selected: newVal }, | |||
); | |||
}, | |||
}, | |||
computed: { | |||
filteredDataArray() { | |||
return this.selectedOption.filter( | |||
(option) => { | |||
const ret = option | |||
.toString() | |||
.toLowerCase() | |||
.indexOf(this.name.toLowerCase()) >= 0; | |||
return ret; | |||
}, | |||
); | |||
}, | |||
}, | |||
created() { | |||
api.Tag.fetchList().then( | |||
(resp) => { | |||
const options = []; | |||
resp.data.forEach( | |||
(tag) => { | |||
options.push(tag.name); | |||
}, | |||
); | |||
this.options.Tag = options; | |||
}, | |||
); | |||
}, | |||
}; | |||
</script> | |||
<style scoped="scoped" lang="scss"> | |||
.search-panel { | |||
padding-top: 3rem; | |||
padding-left: 2rem; | |||
padding-right: 2rem; | |||
} | |||
.filter-selector { | |||
background-color: white; | |||
border-radius: 3px; | |||
.search-input { | |||
width: 100%; | |||
} | |||
} | |||
</style> |
@@ -7,6 +7,7 @@ import Pins4Board from '../views/Pins4Board.vue'; | |||
import Pins4Id from '../views/Pins4Id.vue'; | |||
import Boards4User from '../views/Boards4User.vue'; | |||
import PinCreate from '../views/PinCreate.vue'; | |||
import Search from '../views/Search.vue'; | |||
Vue.use(VueRouter); | |||
@@ -46,6 +47,11 @@ const routes = [ | |||
name: 'pin-creation-from-url', | |||
component: PinCreate, | |||
}, | |||
{ | |||
path: '/search', | |||
name: 'search', | |||
component: Search, | |||
}, | |||
]; | |||
const router = new VueRouter({ | |||
@@ -1,7 +1,7 @@ | |||
<template> | |||
<div class="boards-for-user"> | |||
<PHeader></PHeader> | |||
<Boards :boardUsername="username"></Boards> | |||
<Boards :filters="filters"></Boards> | |||
</div> | |||
</template> | |||
@@ -13,7 +13,7 @@ export default { | |||
name: 'Boards4User', | |||
data() { | |||
return { | |||
username: '', | |||
filters: { boardUsername: null }, | |||
}; | |||
}, | |||
components: { | |||
@@ -24,12 +24,12 @@ export default { | |||
this.initialize(); | |||
}, | |||
beforeRouteUpdate(to, from, next) { | |||
this.username = to.params.username; | |||
this.filters = { boardUsername: to.params.username }; | |||
next(); | |||
}, | |||
methods: { | |||
initialize() { | |||
this.username = this.$route.params.username; | |||
this.filters = { boardUsername: this.$route.params.username }; | |||
}, | |||
}, | |||
}; | |||
@@ -0,0 +1,47 @@ | |||
<template> | |||
<div class="pins-for-tag"> | |||
<PHeader></PHeader> | |||
<SearchPanel v-on:selected="doSearch"></SearchPanel> | |||
<Pins v-if="pinFilters" :pin-filters="pinFilters"></Pins> | |||
<Boards v-if="boardFilters" :filters="boardFilters"></Boards> | |||
</div> | |||
</template> | |||
<script> | |||
import PHeader from '../components/PHeader.vue'; | |||
import Pins from '../components/Pins.vue'; | |||
import Boards from '../components/Boards.vue'; | |||
import SearchPanel from '../components/search/SearchPanel.vue'; | |||
export default { | |||
name: 'Search', | |||
data() { | |||
return { | |||
pinFilters: null, | |||
boardFilters: null, | |||
}; | |||
}, | |||
components: { | |||
PHeader, | |||
Pins, | |||
Boards, | |||
SearchPanel, | |||
}, | |||
created() {}, | |||
methods: { | |||
doSearch(args) { | |||
this.pinFilters = null; | |||
this.boardFilters = null; | |||
if (args.filterType === 'Tag') { | |||
this.pinFilters = { tagFilter: args.selected }; | |||
} else if (args.filterType === 'Board') { | |||
this.boardFilters = { boardNameContains: args.selected }; | |||
} | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<!-- Add "scoped" attribute to limit CSS to this component only --> | |||
<style scoped lang="scss"> | |||
</style> |