Merge branch 'master' into 6339-bug-webapp-event-update-page

This commit is contained in:
Hannes Heine 2023-06-05 21:20:34 +02:00 committed by GitHub
commit 3cd55051f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 429 additions and 76 deletions

View File

@ -12,7 +12,6 @@ docker-compose*.yml
./*.log ./*.log
node_modules/ node_modules/
scripts/
build/ build/
maintenance-worker/ maintenance-worker/

View File

@ -74,7 +74,7 @@ FROM code as build
# yarn install # yarn install
RUN yarn install --production=false --frozen-lockfile --non-interactive RUN yarn install --production=false --frozen-lockfile --non-interactive
# yarn build # yarn build
RUN yarn run build RUN /bin/sh -c "yarn run build"
################################################################################## ##################################################################################
# TEST ########################################################################### # TEST ###########################################################################

View File

@ -11,7 +11,7 @@
"__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations", "__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations",
"prod:migrate": "migrate --migrations-dir ./build/db/migrations --store ./build/db/migrate/store.js", "prod:migrate": "migrate --migrations-dir ./build/db/migrations --store ./build/db/migrate/store.js",
"start": "node build/", "start": "node build/",
"build": "tsc && mkdir -p build/middleware/helpers/email/templates/ && cp -r src/middleware/helpers/email/templates/*.html build/middleware/helpers/email/templates/ && mkdir -p build/middleware/helpers/email/templates/en/ && cp -r src/middleware/helpers/email/templates/en/*.html build/middleware/helpers/email/templates/en/ && mkdir -p build/middleware/helpers/email/templates/de/ && cp -r src/middleware/helpers/email/templates/de/*.html build/middleware/helpers/email/templates/de/ && mkdir -p build/schema/types/ && cp -r src/schema/types/*.gql build/schema/types/ && mkdir -p build/schema/types/enum/ && cp -r src/schema/types/enum/*.gql build/schema/types/enum/ && mkdir -p build/schema/types/scalar/ && cp -r src/schema/types/scalar/*.gql build/schema/types/scalar/ && mkdir -p build/schema/types/type/ && cp -r src/schema/types/type/*.gql build/schema/types/type/", "build": "tsc && ./scripts/build.copy.files.sh",
"dev": "nodemon --exec ts-node src/ -e js,ts,gql", "dev": "nodemon --exec ts-node src/ -e js,ts,gql",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql", "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
"lint": "eslint src --config .eslintrc.js", "lint": "eslint src --config .eslintrc.js",

View File

@ -0,0 +1,24 @@
#!/bin/sh
# html files
mkdir -p build/middleware/helpers/email/templates/
cp -r src/middleware/helpers/email/templates/*.html build/middleware/helpers/email/templates/
mkdir -p build/middleware/helpers/email/templates/en/
cp -r src/middleware/helpers/email/templates/en/*.html build/middleware/helpers/email/templates/en/
mkdir -p build/middleware/helpers/email/templates/de/
cp -r src/middleware/helpers/email/templates/de/*.html build/middleware/helpers/email/templates/de/
# gql files
mkdir -p build/schema/types/
cp -r src/schema/types/*.gql build/schema/types/
mkdir -p build/schema/types/enum/
cp -r src/schema/types/enum/*.gql build/schema/types/enum/
mkdir -p build/schema/types/scalar/
cp -r src/schema/types/scalar/*.gql build/schema/types/scalar/
mkdir -p build/schema/types/type/
cp -r src/schema/types/type/*.gql build/schema/types/type/

View File

@ -158,7 +158,7 @@ describe('Filter Posts', () => {
}) })
describe('order events by event start descending', () => { describe('order events by event start descending', () => {
it('finds the events orderd accordingly', async () => { it('finds the events ordered accordingly', async () => {
const { const {
data: { Post: result }, data: { Post: result },
} = await query({ } = await query({
@ -180,7 +180,7 @@ describe('Filter Posts', () => {
}) })
describe('order events by event start ascending', () => { describe('order events by event start ascending', () => {
it('finds the events orderd accordingly', async () => { it('finds the events ordered accordingly', async () => {
const { const {
data: { Post: result }, data: { Post: result },
} = await query({ } = await query({

View File

@ -90,6 +90,9 @@ $background-color-primary-inverse: rgb(241, 253, 244);
$background-color-secondary: rgb(0, 142, 230); $background-color-secondary: rgb(0, 142, 230);
$background-color-secondary-active: rgb(10, 161, 255); $background-color-secondary-active: rgb(10, 161, 255);
$background-color-secondary-inverse: rgb(240, 249, 255); $background-color-secondary-inverse: rgb(240, 249, 255);
$background-color-third: rgb(126, 82, 204);
$background-color-third-active: rgb(160, 103, 255);
$background-color-third-inverse: rgb(239, 230, 255);
$background-color-success: rgb(23, 181, 63); $background-color-success: rgb(23, 181, 63);
$background-color-success-active: rgb(26, 203, 71); $background-color-success-active: rgb(26, 203, 71);
$background-color-success-inverse: rgb(241, 253, 244); $background-color-success-inverse: rgb(241, 253, 244);
@ -395,3 +398,12 @@ $color-toast-orange: $color-warning;
$color-toast-yellow: $color-yellow; $color-toast-yellow: $color-yellow;
$color-toast-blue: $color-secondary; $color-toast-blue: $color-secondary;
$color-toast-green: $color-success; $color-toast-green: $color-success;
/**
* @tokens Ribbon Color
*/
$color-ribbon-event: $background-color-third;
$color-ribbon-event-active: $background-color-third-active;
$color-ribbon-article: $background-color-secondary;
$color-ribbon-article-active: $background-color-secondary-active;

View File

@ -12,12 +12,12 @@
<li class="item item-save-topics"> <li class="item item-save-topics">
<labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" /> <labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" />
</li> </li>
<hr />
<ds-space margin="base" />
</template> </template>
<template #filter-list> <template #filter-list>
<div class="category-filter-list"> <div class="category-filter-list">
<hr />
<ds-space margin="small" />
<base-button <base-button
v-for="category in categories" v-for="category in categories"
:key="category.id" :key="category.id"

View File

@ -0,0 +1,57 @@
<template>
<filter-menu-section class="order-by-filter" :title="sectionTitle" :divider="false">
<template #filter-list>
<li class="item">
<labeled-button
icon="check"
:label="$t('filter-menu.ended.all.label')"
:filled="!eventsEnded"
:title="$t('filter-menu.ended.all.hint')"
@click="toggleEventsEnded"
data-test="all-button"
/>
</li>
<li class="item">
<labeled-button
icon="calendar"
:label="$t('filter-menu.ended.onlyEnded.label')"
:filled="eventsEnded"
:title="$t('filter-menu.ended.onlyEnded.hint')"
@click="toggleEventsEnded"
data-test="not-ended-button"
/>
</li>
</template>
</filter-menu-section>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
name: 'EventsByFilter',
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({
postFilter: 'posts/filter',
}),
sectionTitle() {
// return $t('filter-menu.eventsEnded')
return null
},
eventsEnded() {
return !!this.postFilter.eventStart_gte
},
},
methods: {
...mapMutations({
toggleEventsEnded: 'posts/TOGGLE_EVENTS_ENDED',
}),
},
}
</script>

View File

@ -15,6 +15,7 @@ describe('FilterMenu.vue', () => {
const getters = { const getters = {
'posts/isActive': () => false, 'posts/isActive': () => false,
'posts/filteredPostTypes': () => [],
'posts/orderBy': () => 'createdAt_desc', 'posts/orderBy': () => 'createdAt_desc',
} }

View File

@ -6,6 +6,10 @@
<post-type-filter /> <post-type-filter />
<categories-filter v-if="categoriesActive" @showFilterMenu="$emit('showFilterMenu')" /> <categories-filter v-if="categoriesActive" @showFilterMenu="$emit('showFilterMenu')" />
</div> </div>
<div v-if="eventSetInPostTypeFilter" class="filter-menu-options">
<h2 class="title">{{ $t('filter-menu.eventsBy') }}</h2>
<events-by-filter />
</div>
<div class="filter-menu-options"> <div class="filter-menu-options">
<h2 class="title">{{ $t('filter-menu.order-by') }}</h2> <h2 class="title">{{ $t('filter-menu.order-by') }}</h2>
<order-by-filter /> <order-by-filter />
@ -14,6 +18,8 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import EventsByFilter from './EventsByFilter'
import PostTypeFilter from './PostTypeFilter' import PostTypeFilter from './PostTypeFilter'
import FollowingFilter from './FollowingFilter' import FollowingFilter from './FollowingFilter'
import OrderByFilter from './OrderByFilter' import OrderByFilter from './OrderByFilter'
@ -21,6 +27,7 @@ import CategoriesFilter from './CategoriesFilter'
export default { export default {
components: { components: {
EventsByFilter,
FollowingFilter, FollowingFilter,
OrderByFilter, OrderByFilter,
CategoriesFilter, CategoriesFilter,
@ -31,6 +38,14 @@ export default {
categoriesActive: this.$env.CATEGORIES_ACTIVE, categoriesActive: this.$env.CATEGORIES_ACTIVE,
} }
}, },
computed: {
...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes',
}),
eventSetInPostTypeFilter() {
return this.filteredPostTypes.includes('Event')
},
},
} }
</script> </script>

View File

@ -44,13 +44,12 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.my-filter-button-selected { .my-filter-button-selected {
padding-right: 30px; padding-right: 36px;
margin-top: 4px;
} }
.base-button.filter-remove { .base-button.filter-remove {
position: relative; position: relative;
margin-left: -31px; margin-left: -37px;
top: -5px; top: -5px;
margin-right: 8px; margin-right: 8px;
} }

View File

@ -11,6 +11,8 @@ describe('OrderByFilter', () => {
'posts/TOGGLE_ORDER': jest.fn(), 'posts/TOGGLE_ORDER': jest.fn(),
} }
const getters = { const getters = {
'posts/filteredPostTypes': () => [],
'posts/orderedByCreationDate': () => true,
'posts/orderBy': () => 'createdAt_desc', 'posts/orderBy': () => 'createdAt_desc',
} }

View File

@ -1,23 +1,23 @@
<template> <template>
<filter-menu-section :divider="false" class="order-by-filter"> <filter-menu-section class="order-by-filter" :title="sectionTitle" :divider="false">
<template #filter-list> <template #filter-list>
<li class="item"> <li class="item">
<labeled-button <labeled-button
icon="sort-amount-asc" icon="sort-amount-asc"
:label="$t('filter-menu.order.newest.label')" :label="buttonLabel('desc')"
:filled="orderBy === 'createdAt_desc'" :filled="orderBy === orderedDesc"
:title="$t('filter-menu.order.newest.hint')" :title="buttonTitle('desc')"
@click="toggleOrder('createdAt_desc')" @click="toggleOrder(orderedDesc)"
data-test="newest-button" data-test="newest-button"
/> />
</li> </li>
<li class="item"> <li class="item">
<labeled-button <labeled-button
icon="sort-amount-desc" icon="sort-amount-desc"
:label="$t('filter-menu.order.oldest.label')" :label="buttonLabel('asc')"
:filled="orderBy === 'createdAt_asc'" :filled="orderBy === orderedAsc"
:title="$t('filter-menu.order.oldest.hint')" :title="buttonTitle('asc')"
@click="toggleOrder('createdAt_asc')" @click="toggleOrder(orderedAsc)"
data-test="oldest-button" data-test="oldest-button"
/> />
</li> </li>
@ -38,13 +38,56 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes',
orderBy: 'posts/orderBy', orderBy: 'posts/orderBy',
}), }),
orderedByCreationDate() {
return !this.filteredPostTypes.includes('Event')
},
orderedAsc() {
return this.orderedByCreationDate ? 'createdAt_asc' : 'eventStart_desc'
},
orderedDesc() {
return this.orderedByCreationDate ? 'createdAt_desc' : 'eventStart_asc'
},
sectionTitle() {
return this.orderedByCreationDate
? this.$t('filter-menu.creationDate')
: this.$t('filter-menu.startDate')
},
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
toggleOrder: 'posts/TOGGLE_ORDER', toggleOrder: 'posts/TOGGLE_ORDER',
}), }),
buttonLabel(buttonType) {
switch (buttonType) {
case 'asc':
return this.orderedByCreationDate
? this.$t('filter-menu.order.oldest.label')
: this.$t('filter-menu.order.last.label')
case 'desc':
return this.orderedByCreationDate
? this.$t('filter-menu.order.newest.label')
: this.$t('filter-menu.order.next.label')
default:
return ''
}
},
buttonTitle(buttonType) {
switch (buttonType) {
case 'asc':
return this.orderedByCreationDate
? this.$t('filter-menu.order.oldest.hint')
: this.$t('filter-menu.order.last.hint')
case 'desc':
return this.orderedByCreationDate
? this.$t('filter-menu.order.newest.hint')
: this.$t('filter-menu.order.next.hint')
default:
return ''
}
},
}, },
} }
</script> </script>

View File

@ -5,22 +5,31 @@
class="following-filter" class="following-filter"
> >
<template #filter-follower> <template #filter-follower>
<li class="item all-item">
<labeled-button
icon="check"
:label="$t('filter-menu.all')"
:filled="filteredPostTypes.length === 0"
:title="$t('filter-menu.all')"
@click="togglePostType(null)"
/>
</li>
<li class="item article-item"> <li class="item article-item">
<labeled-button <labeled-button
icon="book" icon="book"
:label="$t('filter-menu.article')" :label="$t('filter-menu.article')"
:filled="articleSet" :filled="filteredPostTypes.includes('Article')"
:title="$t('filter-menu.article')" :title="$t('filter-menu.article')"
@click="toggleFilterPostType('Article')" @click="togglePostType('Article')"
/> />
</li> </li>
<li class="item event-item"> <li class="item event-item">
<labeled-button <labeled-button
icon="calendar" icon="calendar"
:label="$t('filter-menu.events')" :label="$t('filter-menu.event')"
:filled="eventSet" :filled="filteredPostTypes.includes('Event')"
:title="$t('filter-menu.events')" :title="$t('filter-menu.event')"
@click="toggleFilterPostType('Event')" @click="togglePostType('Event')"
/> />
</li> </li>
</template> </template>
@ -41,18 +50,11 @@ export default {
computed: { computed: {
...mapGetters({ ...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes', filteredPostTypes: 'posts/filteredPostTypes',
currentUser: 'auth/user',
}), }),
articleSet() {
return this.filteredPostTypes.includes('Article')
},
eventSet() {
return this.filteredPostTypes.includes('Event')
},
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
toggleFilterPostType: 'posts/TOGGLE_POST_TYPE', togglePostType: 'posts/TOGGLE_POST_TYPE',
}), }),
}, },
} }

View File

@ -1,6 +1,8 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import InviteButton from './InviteButton.vue' import InviteButton from './InviteButton.vue'
const localVue = global.localVue
const stubs = { const stubs = {
'v-popover': { 'v-popover': {
template: '<span><slot /></span>', template: '<span><slot /></span>',
@ -26,7 +28,7 @@ describe('InviteButton.vue', () => {
describe('mount', () => { describe('mount', () => {
const Wrapper = () => { const Wrapper = () => {
return mount(InviteButton, { mocks, propsData, stubs }) return mount(InviteButton, { mocks, localVue, propsData, stubs })
} }
beforeEach(() => { beforeEach(() => {

View File

@ -25,7 +25,7 @@ export default {
padding: $size-ribbon $size-ribbon; padding: $size-ribbon $size-ribbon;
border-radius: $border-radius-small 0 0 $border-radius-small; border-radius: $border-radius-small 0 0 $border-radius-small;
color: $color-neutral-100; color: $color-neutral-100;
background-color: $background-color-secondary-active; background-color: $color-ribbon-article-active;
font-size: $font-size-x-small; font-size: $font-size-x-small;
font-weight: $font-weight-bold; font-weight: $font-weight-bold;
@ -36,7 +36,7 @@ export default {
bottom: -$size-ribbon; bottom: -$size-ribbon;
border-width: $border-size-large 4px $border-size-large $border-size-large; border-width: $border-size-large 4px $border-size-large $border-size-large;
border-style: solid; border-style: solid;
border-color: $background-color-secondary transparent transparent $background-color-secondary; border-color: $color-ribbon-article transparent transparent $color-ribbon-article;
} }
&.--pinned { &.--pinned {
@ -48,10 +48,10 @@ export default {
} }
} }
.eventBg { .eventBg {
background-color: $color-success-active; background-color: $color-ribbon-event-active;
&::before { &::before {
border-color: $color-success-active transparent transparent $color-success-active; border-color: $color-ribbon-event transparent transparent $color-ribbon-event;
} }
} }
</style> </style>

View File

@ -272,7 +272,9 @@
"myFriends": "Nutzer denen ich folge", "myFriends": "Nutzer denen ich folge",
"myGroups": "Aus meinen Gruppen", "myGroups": "Aus meinen Gruppen",
"myTopics": "Meine Themen", "myTopics": "Meine Themen",
"noFilter": "Beiträge filtern" "noFilter": "Inhalt filtern",
"onlyArticles": "Nur Beiträge",
"onlyEvents": "Nur Veranstaltungen"
}, },
"filterMyGroups": "Beiträge in meinen Gruppen", "filterMyGroups": "Beiträge in meinen Gruppen",
"inappropriatePicture": "Dieses Bild kann für einige Menschen unangemessen sein.", "inappropriatePicture": "Dieses Bild kann für einige Menschen unangemessen sein.",
@ -378,31 +380,53 @@
}, },
"filter-menu": { "filter-menu": {
"all": "Alle", "all": "Alle",
"article": "Artikel", "article": "Beitrag",
"categories": "Themen", "categories": "Themen",
"creationDate": "Erstellungszeitpunkt",
"deleteFilter": "Filter löschen", "deleteFilter": "Filter löschen",
"emotions": "Emotionen", "emotions": "Emotionen",
"events": "Veranstaltungen", "ended": {
"all": {
"hint": "Zeige alle, auch beendete",
"label": "Alle"
},
"onlyEnded": {
"hint": "Zeige nur noch nicht beendete",
"label": "Nicht beendete"
}
},
"event": "Veranstaltung",
"eventsBy": "Veranstaltungen zeige ...",
"eventsEnded": "Beendet",
"filter-by": "Filtern nach ...", "filter-by": "Filtern nach ...",
"following": "Nutzer denen ich folge", "following": "Nutzer denen ich folge",
"languages": "Sprachen", "languages": "Sprachen",
"my-groups": "Meinen Gruppen", "my-groups": "Meinen Gruppen",
"order": { "order": {
"last": {
"hint": "Sortiere die Letzten nach vorn",
"label": "Letzte zuerst"
},
"newest": { "newest": {
"hint": "Sortiere die Neuesten nach vorn", "hint": "Sortiere die Neuesten nach vorn",
"label": "Neueste zuerst" "label": "Neueste zuerst"
}, },
"next": {
"hint": "Sortiere die Nächsten nach vorn",
"label": "Nächste zuerst"
},
"oldest": { "oldest": {
"hint": "Sortiere die Ältesten nach vorn", "hint": "Sortiere die Ältesten nach vorn",
"label": "Älteste zuerst" "label": "Älteste zuerst"
} }
}, },
"order-by": "Sortieren nach ...", "order-by": "Sortieren nach ...",
"post-type": "Beitrags-Typ", "post-type": "Inhaltstyp",
"save": { "save": {
"error": "Themen konnten nicht gespeichert werden!", "error": "Themen konnten nicht gespeichert werden!",
"success": "Themen gespeichert!" "success": "Themen gespeichert!"
} },
"startDate": "Anfangszeitpunkt"
}, },
"followButton": { "followButton": {
"follow": "Folgen", "follow": "Folgen",

View File

@ -272,7 +272,9 @@
"myFriends": "Users I follow", "myFriends": "Users I follow",
"myGroups": "By my groups", "myGroups": "By my groups",
"myTopics": "My topics", "myTopics": "My topics",
"noFilter": "Filter posts" "noFilter": "Filter content",
"onlyArticles": "Only articles",
"onlyEvents": "Only events"
}, },
"filterMyGroups": "Contributions in my groups", "filterMyGroups": "Contributions in my groups",
"inappropriatePicture": "This image may be inappropriate for some people.", "inappropriatePicture": "This image may be inappropriate for some people.",
@ -380,29 +382,51 @@
"all": "All", "all": "All",
"article": "Article", "article": "Article",
"categories": "Topics", "categories": "Topics",
"creationDate": "Creation date",
"deleteFilter": "Delete filter", "deleteFilter": "Delete filter",
"emotions": "Emotions", "emotions": "Emotions",
"events": "Events", "ended": {
"all": {
"hint": "Show all, also ended",
"label": "All"
},
"onlyEnded": {
"hint": "Show only not ended",
"label": "Not ended"
}
},
"event": "Event",
"eventsBy": "Events show ...",
"eventsEnded": "Ended",
"filter-by": "Filter by ...", "filter-by": "Filter by ...",
"following": "Users I follow", "following": "Users I follow",
"languages": "Languages", "languages": "Languages",
"my-groups": "My groups", "my-groups": "My groups",
"order": { "order": {
"last": {
"hint": "Sort posts by the last first",
"label": "Last first"
},
"newest": { "newest": {
"hint": "Sort posts by the newest first", "hint": "Sort posts by the newest first",
"label": "Newest first" "label": "Newest first"
}, },
"next": {
"hint": "Sort posts by the next first",
"label": "Next first"
},
"oldest": { "oldest": {
"hint": "Sort posts by the oldest first", "hint": "Sort posts by the oldest first",
"label": "Oldest first" "label": "Oldest first"
} }
}, },
"order-by": "Order by ...", "order-by": "Order by ...",
"post-type": "Post type", "post-type": "Content type",
"save": { "save": {
"error": "Failed saving topic settings!", "error": "Failed saving topic settings!",
"success": "Topics saved!" "success": "Topics saved!"
} },
"startDate": "Start date"
}, },
"followButton": { "followButton": {
"follow": "Follow", "follow": "Follow",
@ -717,7 +741,7 @@
"unpin": "Unpin post", "unpin": "Unpin post",
"unpinnedSuccessfully": "Post unpinned successfully!" "unpinnedSuccessfully": "Post unpinned successfully!"
}, },
"name": "Post", "name": "Article",
"pinned": "Announcement", "pinned": "Announcement",
"takeAction": { "takeAction": {
"name": "Take action" "name": "Take action"

View File

@ -28,6 +28,10 @@ describe('PostIndex', () => {
store = new Vuex.Store({ store = new Vuex.Store({
getters: { getters: {
'posts/filter': () => ({}), 'posts/filter': () => ({}),
'posts/filteredPostTypes': () => [],
'posts/articleSetInPostTypeFilter': () => false,
'posts/eventSetInPostTypeFilter': () => false,
'posts/eventsEnded': () => '',
'posts/orderBy': () => 'createdAt_desc', 'posts/orderBy': () => 'createdAt_desc',
'auth/user': () => { 'auth/user': () => {
return { id: 'u23' } return { id: 'u23' }

View File

@ -25,6 +25,7 @@
<base-button <base-button
class="my-filter-button" class="my-filter-button"
v-if=" v-if="
!postsFilter['postType_in'] &&
!postsFilter['categories_some'] && !postsFilter['categories_some'] &&
!postsFilter['author'] && !postsFilter['author'] &&
!postsFilter['postsInMyGroups'] !postsFilter['postsInMyGroups']
@ -38,6 +39,22 @@
<base-icon class="my-filter-button" :name="filterButtonIcon"></base-icon> <base-icon class="my-filter-button" :name="filterButtonIcon"></base-icon>
</base-button> </base-button>
<header-button
v-if="filteredPostTypes.includes('Article')"
:title="$t('contribution.filterMasonryGrid.onlyArticles')"
:clickButton="openFilterMenu"
:titleRemove="$t('filter-menu.deleteFilter')"
:clickRemove="resetPostType"
/>
<header-button
v-if="filteredPostTypes.includes('Event')"
:title="$t('contribution.filterMasonryGrid.onlyEvents')"
:clickButton="openFilterMenu"
:titleRemove="$t('filter-menu.deleteFilter')"
:clickRemove="resetPostType"
/>
<header-button <header-button
v-if="postsFilter['categories_some']" v-if="postsFilter['categories_some']"
:title="$t('contribution.filterMasonryGrid.myTopics')" :title="$t('contribution.filterMasonryGrid.myTopics')"
@ -62,7 +79,7 @@
:clickRemove="resetByGroups" :clickRemove="resetByGroups"
/> />
<div id="my-filter" v-if="showFilter"> <div id="my-filter" v-if="showFilter">
<div @mouseleave="showFilter = false"> <div @mouseleave="mouseLeaveFilterMenu">
<filter-menu-component @showFilterMenu="showFilterMenu" /> <filter-menu-component @showFilterMenu="showFilterMenu" />
</div> </div>
</div> </div>
@ -120,6 +137,7 @@
<script> <script>
import postListActions from '~/mixins/postListActions' import postListActions from '~/mixins/postListActions'
import mobile from '~/mixins/mobile'
import DonationInfo from '~/components/DonationInfo/DonationInfo.vue' import DonationInfo from '~/components/DonationInfo/DonationInfo.vue'
import HashtagsFilter from '~/components/HashtagsFilter/HashtagsFilter.vue' import HashtagsFilter from '~/components/HashtagsFilter/HashtagsFilter.vue'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
@ -134,7 +152,6 @@ import UpdateQuery from '~/components/utils/UpdateQuery'
import FilterMenuComponent from '~/components/FilterMenu/FilterMenuComponent' import FilterMenuComponent from '~/components/FilterMenu/FilterMenuComponent'
import { SHOW_CONTENT_FILTER_MASONRY_GRID } from '~/constants/filter.js' import { SHOW_CONTENT_FILTER_MASONRY_GRID } from '~/constants/filter.js'
import { POST_ADD_BUTTON_POSITION_TOP } from '~/constants/posts.js' import { POST_ADD_BUTTON_POSITION_TOP } from '~/constants/posts.js'
import mobile from '~/mixins/mobile'
export default { export default {
components: { components: {
@ -154,12 +171,13 @@ export default {
hideByScroll: false, hideByScroll: false,
revScrollpos: 0, revScrollpos: 0,
showFilter: false, showFilter: false,
developerNoAutoClosingFilterMenu: false, // stops automatic closing of filter menu for developer purposes: default is 'false'
showDonations: false, showDonations: false,
goal: 15000, goal: 15000,
progress: 7000, progress: 7000,
posts: [], posts: [],
hasMore: true, hasMore: true,
// Initialize your apollo data // initialize your apollo data
offset: 0, offset: 0,
pageSize: 12, pageSize: 12,
hashtag, hashtag,
@ -170,14 +188,12 @@ export default {
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes',
postsFilter: 'posts/filter', postsFilter: 'posts/filter',
orderBy: 'posts/orderBy', orderBy: 'posts/orderBy',
}), }),
filterButtonIcon() { filterButtonIcon() {
if (Object.keys(this.postsFilter).length === 0) { return this.showFilter ? 'angle-up' : 'angle-down'
return this.showFilter ? 'angle-up' : 'angle-down'
}
return 'close'
}, },
finalFilters() { finalFilters() {
let filter = this.postsFilter let filter = this.postsFilter
@ -197,6 +213,11 @@ export default {
}, },
}, },
watchQuery: ['hashtag'], watchQuery: ['hashtag'],
watch: {
postsFilter() {
this.resetPostList()
},
},
mounted() { mounted() {
if (this.categoryId) { if (this.categoryId) {
this.resetCategories() this.resetCategories()
@ -207,6 +228,7 @@ export default {
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
resetPostType: 'posts/RESET_POST_TYPE',
resetByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED', resetByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
resetByGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS', resetByGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS',
resetCategories: 'posts/RESET_CATEGORIES', resetCategories: 'posts/RESET_CATEGORIES',
@ -215,6 +237,10 @@ export default {
openFilterMenu() { openFilterMenu() {
this.showFilter = !this.showFilter this.showFilter = !this.showFilter
}, },
mouseLeaveFilterMenu() {
if (this.developerNoAutoClosingFilterMenu) return
this.showFilter = false
},
showFilterMenu(e) { showFilterMenu(e) {
if (!e || (!e.target.closest('#my-filter') && !e.target.closest('.my-filter-button'))) { if (!e || (!e.target.closest('#my-filter') && !e.target.closest('.my-filter-button'))) {
if (!this.showFilter) return if (!this.showFilter) return

View File

@ -18,9 +18,13 @@ describe('create.vue', () => {
}, },
} }
const stubs = {
ContributionForm: true,
}
describe('mount', () => { describe('mount', () => {
const Wrapper = () => { const Wrapper = () => {
return mount(create, { mocks, localVue }) return mount(create, { mocks, localVue, stubs })
} }
beforeEach(() => { beforeEach(() => {
@ -28,7 +32,7 @@ describe('create.vue', () => {
}) })
it('renders', () => { it('renders', () => {
expect(wrapper.findAll('.contribution-form')).toHaveLength(1) expect(wrapper.findComponent({ name: 'ContributionForm' }).exists()).toBe(true)
}) })
}) })
}) })

View File

@ -63,12 +63,6 @@ export const mutations = {
if (isEmpty(get(filter, 'categories_some.id_in'))) delete filter.categories_some if (isEmpty(get(filter, 'categories_some.id_in'))) delete filter.categories_some
state.filter = filter state.filter = filter
}, },
TOGGLE_POST_TYPE(state, postType) {
const filter = clone(state.filter)
update(filter, 'postType_in', (postTypes) => xor(postTypes, [postType]))
if (isEmpty(get(filter, 'postType_in'))) delete filter.postType_in
state.filter = filter
},
TOGGLE_LANGUAGE(state, languageCode) { TOGGLE_LANGUAGE(state, languageCode) {
const filter = clone(state.filter) const filter = clone(state.filter)
update(filter, 'language_in', (languageCodes) => xor(languageCodes, [languageCode])) update(filter, 'language_in', (languageCodes) => xor(languageCodes, [languageCode]))
@ -81,6 +75,42 @@ export const mutations = {
if (isEmpty(get(filter, 'emotions_some.emotion_in'))) delete filter.emotions_some if (isEmpty(get(filter, 'emotions_some.emotion_in'))) delete filter.emotions_some
state.filter = filter state.filter = filter
}, },
RESET_POST_TYPE(state) {
const filter = clone(state.filter)
delete filter.eventStart_gte
delete filter.postType_in
state.order = 'createdAt_desc'
state.filter = filter
},
TOGGLE_POST_TYPE(state, postType) {
const filter = clone(state.filter)
if (postType && !(filter.postType_in && filter.postType_in.includes(postType))) {
filter.postType_in = [postType]
if (postType === 'Event') {
filter.eventStart_gte = new Date()
state.order = 'eventStart_asc'
} else {
delete filter.eventStart_gte
state.order = 'createdAt_desc'
}
} else {
delete filter.eventStart_gte
delete filter.postType_in
state.order = 'createdAt_desc'
}
state.filter = filter
},
TOGGLE_EVENTS_ENDED(state) {
const filter = clone(state.filter)
if (filter.eventStart_gte) {
delete filter.eventStart_gte
} else {
if (filter.postType_in && filter.postType_in.includes('Event')) {
filter.eventStart_gte = new Date()
}
}
state.filter = filter
},
TOGGLE_ORDER(state, value) { TOGGLE_ORDER(state, value) {
state.order = value state.order = value
}, },

View File

@ -233,25 +233,49 @@ describe('mutations', () => {
} }
}) })
it('creates post type filter if empty', () => { it('creates post type filter if empty, sets event start filter and event start order', () => {
state = { filter: {} } state = { filter: {} }
expect(testMutation('Event')).toEqual({ postType_in: ['Event'] }) expect(testMutation('Event')).toEqual({
postType_in: ['Event'],
eventStart_gte: expect.any(Date),
})
expect(getters.orderBy(state)).toEqual('eventStart_asc')
}) })
it('adds post type not present', () => { it('changes post type if present, resets filter event start and order', () => {
state = { filter: { postType_in: ['Event'] } } state = {
expect(testMutation('Article')).toEqual({ postType_in: ['Event', 'Article'] }) filter: {
postType_in: ['Event'],
eventStart_gte: new Date(),
},
order: 'eventStart_asc',
}
expect(testMutation('Article')).toEqual({ postType_in: ['Article'] })
expect(getters.orderBy(state)).toEqual('createdAt_desc')
}) })
it('removes category id if present', () => { it('removes post type filter if same post type is present and sets order', () => {
state = { filter: { postType_in: ['Event', 'Article'] } } state = {
const result = testMutation('Event') filter: {
expect(result).toEqual({ postType_in: ['Article'] }) postType_in: ['Event'],
}) eventStart_gte: new Date(),
},
it('removes category filter if empty', () => { order: 'eventStart_asc',
state = { filter: { postType_in: ['Event'] } } }
expect(testMutation('Event')).toEqual({}) expect(testMutation('Event')).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
})
it('removes post type filter if called with null', () => {
state = {
filter: {
postType_in: ['Event'],
eventStart_gte: new Date(),
},
order: 'eventStart_asc',
}
expect(testMutation(null)).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
}) })
it('does not get in the way of other filters', () => { it('does not get in the way of other filters', () => {
@ -265,6 +289,67 @@ describe('mutations', () => {
}) })
}) })
describe('RESET_POST_TYPE', () => {
beforeEach(() => {
testMutation = () => {
mutations.RESET_POST_TYPE(state)
return getters.filter(state)
}
})
it('resets the post type filter, event start and order', () => {
state = {
filter: {
postType_in: ['Event'],
eventStart_gte: new Date(),
},
order: 'eventStart_asc',
}
expect(testMutation()).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
})
})
describe('TOGGLE_EVENTS_ENDED', () => {
beforeEach(() => {
testMutation = (postType) => {
mutations.TOGGLE_EVENTS_ENDED(state, postType)
return getters.filter(state)
}
})
it('does not set events ended when post type is not Event', () => {
state = {
filter: {},
}
expect(testMutation()).toEqual({})
})
it('sets events ended when post type is Event', () => {
state = {
filter: {
postType_in: ['Event'],
},
}
expect(testMutation()).toEqual({
postType_in: ['Event'],
eventStart_gte: expect.any(Date),
})
})
it('unsets events ended when set', () => {
state = {
filter: {
postType_in: ['Event'],
eventStart_gte: new Date(),
},
}
expect(testMutation()).toEqual({
postType_in: ['Event'],
})
})
})
describe('TOGGLE_FILTER_BY_FOLLOWED', () => { describe('TOGGLE_FILTER_BY_FOLLOWED', () => {
beforeEach(() => { beforeEach(() => {
testMutation = (userId) => { testMutation = (userId) => {