mirror of
https://github.com/pinry/pinry.git
synced 2025-11-15 09:25: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):
|
||||
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(
|
||||
|
||||
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 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 };
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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