mirror of
https://github.com/pinry/pinry.git
synced 2025-11-15 17:35:50 +01:00
Merge pull request #179 from pinry/feature/search-boards-and-pins-by-tag
close #50 #115 search boards and pins by tag
This commit is contained in:
@@ -36,7 +36,8 @@ class PinViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
class BoardViewSet(viewsets.ModelViewSet):
|
class BoardViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = api.BoardSerializer
|
serializer_class = api.BoardSerializer
|
||||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
filter_backends = (DjangoFilterBackend, OrderingFilter, SearchFilter)
|
||||||
|
search_fields = ("name", )
|
||||||
filter_fields = ("submitter__username", )
|
filter_fields = ("submitter__username", )
|
||||||
ordering_fields = ('-id', )
|
ordering_fields = ('-id', )
|
||||||
ordering = ('-id', )
|
ordering = ('-id', )
|
||||||
|
|||||||
@@ -118,14 +118,15 @@ export default {
|
|||||||
BoardEditorUI,
|
BoardEditorUI,
|
||||||
},
|
},
|
||||||
data: initialData,
|
data: initialData,
|
||||||
props: ['boardUsername'],
|
props: ['filters'],
|
||||||
watch: {
|
watch: {
|
||||||
boardUsername() {
|
filters() {
|
||||||
this.reset();
|
this.reset();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initialize() {
|
initialize() {
|
||||||
|
this.initializeMeta();
|
||||||
this.fetchMore(true);
|
this.fetchMore(true);
|
||||||
},
|
},
|
||||||
initializeMeta() {
|
initializeMeta() {
|
||||||
@@ -205,8 +206,21 @@ export default {
|
|||||||
if (!this.shouldFetchMore(created)) {
|
if (!this.shouldFetchMore(created)) {
|
||||||
return;
|
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;
|
this.status.loading = true;
|
||||||
const promise = API.fetchBoardForUser(this.boardUsername, this.status.offset);
|
|
||||||
promise.then(
|
promise.then(
|
||||||
(resp) => {
|
(resp) => {
|
||||||
const { results, next } = resp.data;
|
const { results, next } = resp.data;
|
||||||
@@ -227,7 +241,6 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
bus.bus.$on(bus.events.refreshBoards, this.reset);
|
bus.bus.$on(bus.events.refreshBoards, this.reset);
|
||||||
this.registerScrollEvent();
|
this.registerScrollEvent();
|
||||||
this.initializeMeta();
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
v-if="user.loggedIn"
|
v-if="user.loggedIn"
|
||||||
class="navbar-item has-dropdown is-hoverable">
|
class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
My Collections
|
My
|
||||||
</a>
|
</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<router-link
|
<router-link
|
||||||
@@ -73,6 +73,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<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="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ const Board = {
|
|||||||
const url = `${API_PREFIX}boards-auto-complete/?submitter__username=${username}`;
|
const url = `${API_PREFIX}boards-auto-complete/?submitter__username=${username}`;
|
||||||
return axios.get(url);
|
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) {
|
saveChanges(boardId, fieldsForm) {
|
||||||
const url = `${API_PREFIX}boards/${boardId}/`;
|
const url = `${API_PREFIX}boards/${boardId}/`;
|
||||||
return axios.patch(
|
return axios.patch(
|
||||||
|
|||||||
129
pinry-spa/src/components/search/SearchPanel.vue
Normal file
129
pinry-spa/src/components/search/SearchPanel.vue
Normal file
@@ -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 Pins4Id from '../views/Pins4Id.vue';
|
||||||
import Boards4User from '../views/Boards4User.vue';
|
import Boards4User from '../views/Boards4User.vue';
|
||||||
import PinCreate from '../views/PinCreate.vue';
|
import PinCreate from '../views/PinCreate.vue';
|
||||||
|
import Search from '../views/Search.vue';
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
@@ -46,6 +47,11 @@ const routes = [
|
|||||||
name: 'pin-creation-from-url',
|
name: 'pin-creation-from-url',
|
||||||
component: PinCreate,
|
component: PinCreate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/search',
|
||||||
|
name: 'search',
|
||||||
|
component: Search,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="boards-for-user">
|
<div class="boards-for-user">
|
||||||
<PHeader></PHeader>
|
<PHeader></PHeader>
|
||||||
<Boards :boardUsername="username"></Boards>
|
<Boards :filters="filters"></Boards>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export default {
|
|||||||
name: 'Boards4User',
|
name: 'Boards4User',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: '',
|
filters: { boardUsername: null },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@@ -24,12 +24,12 @@ export default {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
},
|
},
|
||||||
beforeRouteUpdate(to, from, next) {
|
beforeRouteUpdate(to, from, next) {
|
||||||
this.username = to.params.username;
|
this.filters = { boardUsername: to.params.username };
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initialize() {
|
initialize() {
|
||||||
this.username = this.$route.params.username;
|
this.filters = { boardUsername: this.$route.params.username };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
47
pinry-spa/src/views/Search.vue
Normal file
47
pinry-spa/src/views/Search.vue
Normal file
@@ -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>
|
||||||
Reference in New Issue
Block a user