mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into dependabot/npm_and_yarn/webapp/vue/test-utils-1.0.0-beta.30
This commit is contained in:
commit
0a4defe36d
@ -71,7 +71,7 @@
|
||||
"metascraper-image": "^5.9.5",
|
||||
"metascraper-lang": "^5.10.3",
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.9.5",
|
||||
"metascraper-logo": "^5.10.3",
|
||||
"metascraper-publisher": "^5.10.3",
|
||||
"metascraper-soundcloud": "^5.9.5",
|
||||
"metascraper-title": "^5.10.3",
|
||||
|
||||
@ -6020,12 +6020,12 @@ metascraper-lang@^5.10.3:
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.10.3"
|
||||
|
||||
metascraper-logo@^5.9.5:
|
||||
version "5.9.5"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.9.5.tgz#1fee5b3de1c79380d50c8e2ecd3d102b6cb9f7d4"
|
||||
integrity sha512-rbBLYVX4xSIzzeQJaUyibQYzjlNGlkyri14ixpIlCdlri3KQ0rRKnwQSeOSUlAu1rVsL7JlsMrhMDCb21dX9sQ==
|
||||
metascraper-logo@^5.10.3:
|
||||
version "5.10.3"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.10.3.tgz#22847c9ba65d8ae8e8af05d838f1da0953215300"
|
||||
integrity sha512-8bliBXhe638D5A1gFh3q07rxYMCD6WOgywKFKPLcgOb0YybOU0MlrHkInXaGLMGCinULw3cXCn67b/+11mbmHA==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.9.5"
|
||||
"@metascraper/helpers" "^5.10.3"
|
||||
|
||||
metascraper-publisher@^5.10.3:
|
||||
version "5.10.3"
|
||||
|
||||
@ -240,6 +240,13 @@ $size-height-xlarge: 60px;
|
||||
$size-height-footer: 64px;
|
||||
$size-tappable-square: 44px;
|
||||
|
||||
/**
|
||||
* @tokens Size Width
|
||||
* @presenter Spacing
|
||||
*/
|
||||
|
||||
$size-width-paginate: 100px;
|
||||
|
||||
/**
|
||||
* @tokens Size Avatar
|
||||
* @presenter Spacing
|
||||
@ -259,7 +266,7 @@ $size-avatar-x-large: 114px;
|
||||
$size-button-small: 26px;
|
||||
|
||||
/**
|
||||
* @tokens Size Buttons
|
||||
* @tokens Size Icons
|
||||
* @presenter Spacing
|
||||
*/
|
||||
|
||||
|
||||
@ -175,3 +175,7 @@ hr {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
font-size: $font-size-xx-small;
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ describe('DeleteData.vue', () => {
|
||||
})
|
||||
|
||||
it('does not call the delete user mutation if deleteEnabled is false', () => {
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||
deleteAccountBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||
})
|
||||
@ -90,7 +90,7 @@ describe('DeleteData.vue', () => {
|
||||
beforeEach(() => {
|
||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||
enableDeletionInput.setValue(deleteAccountName)
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||
})
|
||||
|
||||
it('if deleteEnabled is true and only deletes user by default', () => {
|
||||
@ -169,7 +169,7 @@ describe('DeleteData.vue', () => {
|
||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||
enableDeletionInput.setValue(deleteAccountName)
|
||||
await Vue.nextTick()
|
||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
||||
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||
await deleteAccountBtn.trigger('click')
|
||||
// second submission causes mutation to reject
|
||||
await deleteAccountBtn.trigger('click')
|
||||
|
||||
@ -55,22 +55,19 @@
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'large' }">
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1.75 }">
|
||||
<ds-input
|
||||
v-model="enableDeletionValue"
|
||||
@input="enableDeletion"
|
||||
class="enable-deletion-input"
|
||||
/>
|
||||
<ds-input v-model="enableDeletionValue" class="enable-deletion-input" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
||||
<ds-button
|
||||
<base-button
|
||||
icon="trash"
|
||||
danger
|
||||
filled
|
||||
:disabled="!deleteEnabled"
|
||||
data-test="delete-button"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ $t('settings.deleteUserAccount.name') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
@ -88,7 +85,6 @@ export default {
|
||||
return {
|
||||
deleteContributions: false,
|
||||
deleteComments: false,
|
||||
deleteEnabled: false,
|
||||
enableDeletionValue: null,
|
||||
}
|
||||
},
|
||||
@ -96,16 +92,14 @@ export default {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
deleteEnabled() {
|
||||
return this.enableDeletionValue === this.currentUser.name
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
logout: 'auth/logout',
|
||||
}),
|
||||
enableDeletion() {
|
||||
if (this.enableDeletionValue === this.currentUser.name) {
|
||||
this.deleteEnabled = true
|
||||
}
|
||||
},
|
||||
handleSubmit() {
|
||||
const resourceArgs = []
|
||||
if (this.deleteContributions) {
|
||||
|
||||
@ -65,10 +65,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
font-size: $font-size-xx-small;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
|
||||
@ -326,85 +326,4 @@ li > p {
|
||||
margin: 0 0 $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror[contenteditable='false'] {
|
||||
.embed-close-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-container {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: $space-small auto;
|
||||
overflow: hidden;
|
||||
border-radius: $border-radius-base;
|
||||
border: 1px solid $color-neutral-70;
|
||||
background-color: $color-neutral-90;
|
||||
}
|
||||
|
||||
.embed-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
h4 {
|
||||
margin: $space-small 0 0 $space-small;
|
||||
}
|
||||
|
||||
p,
|
||||
a {
|
||||
display: block;
|
||||
margin: 0 0 0 $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-preview-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 450px;
|
||||
}
|
||||
|
||||
.embed-preview-image--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.embed-html {
|
||||
width: 100%;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
padding: $space-large;
|
||||
background-color: $color-neutral-100;
|
||||
}
|
||||
|
||||
.embed-buttons {
|
||||
button {
|
||||
margin-right: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-checkbox {
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
margin-right: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-close-button {
|
||||
position: absolute;
|
||||
top: $space-x-small;
|
||||
right: $space-x-small;
|
||||
background-color: rgba(250, 249, 250, 0.6);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -65,13 +65,13 @@ describe('EmbedComponent.vue', () => {
|
||||
})
|
||||
|
||||
it('shows the description', () => {
|
||||
expect(wrapper.find('.embed-content p').text()).toBe(
|
||||
expect(wrapper.find('.content p').text()).toBe(
|
||||
'Salut tout le monde ! Aujourd’hui, une vidéo sur le scepticisme, nous allons parler médiumnité avec le cas de Bruno CHARVET : « Bruno, un nouveau message ». Merci de rester respectueux dans les commentaires : SOURCES : Les sources des vi...',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows preview Images for link', () => {
|
||||
expect(wrapper.find('.embed-preview-image').exists()).toBe(true)
|
||||
expect(wrapper.find('.preview').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -92,7 +92,7 @@ describe('EmbedComponent.vue', () => {
|
||||
})
|
||||
|
||||
it('show the desciption', () => {
|
||||
expect(wrapper.find('.embed-content p').text()).toBe(
|
||||
expect(wrapper.find('.content p').text()).toBe(
|
||||
'She’s incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. That’s a sleep sack she’s in. Not a starfish outfit. Al...',
|
||||
)
|
||||
})
|
||||
@ -121,12 +121,12 @@ describe('EmbedComponent.vue', () => {
|
||||
})
|
||||
|
||||
it('shows a simple link when a user closes the embed preview', () => {
|
||||
wrapper.find('.embed-close-button').trigger('click')
|
||||
wrapper.find('.close-button').trigger('click')
|
||||
expect(wrapper.vm.showLinkOnly).toBe(true)
|
||||
})
|
||||
|
||||
it('opens the data privacy overlay when a user clicks on the preview image', () => {
|
||||
wrapper.find('.embed-preview-image--clickable').trigger('click')
|
||||
wrapper.find('.preview.--clickable').trigger('click')
|
||||
expect(wrapper.vm.showOverlay).toBe(true)
|
||||
})
|
||||
|
||||
@ -135,19 +135,19 @@ describe('EmbedComponent.vue', () => {
|
||||
wrapper.setData({ showOverlay: true })
|
||||
})
|
||||
|
||||
it('when user agress', () => {
|
||||
wrapper.find('.ds-button-primary').trigger('click')
|
||||
it('when user agrees', () => {
|
||||
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||
expect(wrapper.vm.showEmbed).toBe(true)
|
||||
})
|
||||
|
||||
it('does not show iframe when user clicks to cancel', () => {
|
||||
wrapper.find('.ds-button-ghost').trigger('click')
|
||||
wrapper.find('[data-test="cancel-button"]').trigger('click')
|
||||
expect(wrapper.vm.showEmbed).toBe(false)
|
||||
})
|
||||
|
||||
describe("doesn't set permanently", () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('.ds-button-primary').trigger('click')
|
||||
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||
})
|
||||
|
||||
it("if user doesn't give consent", () => {
|
||||
@ -162,7 +162,7 @@ describe('EmbedComponent.vue', () => {
|
||||
describe('sets permanently', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input[type=checkbox]').trigger('click')
|
||||
wrapper.find('.ds-button-primary').trigger('click')
|
||||
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||
})
|
||||
|
||||
it('changes setting permanetly when user requests', () => {
|
||||
@ -194,7 +194,7 @@ describe('EmbedComponent.vue', () => {
|
||||
})
|
||||
|
||||
it('does not display image to click', () => {
|
||||
expect(wrapper.find('.embed-preview-image--clickable').exists()).toBe(false)
|
||||
expect(wrapper.find('.preview.--clickable').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,17 +2,17 @@
|
||||
<a v-if="showLinkOnly" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
|
||||
{{ dataEmbedUrl }}
|
||||
</a>
|
||||
<ds-container v-else width="small" class="embed-container">
|
||||
<section class="embed-content">
|
||||
<div v-if="showEmbed" v-html="embedHtml" class="embed-html" />
|
||||
<ds-container v-else width="small" class="embed-component">
|
||||
<section class="content">
|
||||
<div v-if="showEmbed" v-html="embedHtml" class="html" />
|
||||
<template v-else>
|
||||
<img
|
||||
v-if="embedHtml && embedImage"
|
||||
:src="embedImage"
|
||||
class="embed-preview-image embed-preview-image--clickable"
|
||||
class="preview --clickable"
|
||||
@click.prevent="openOverlay()"
|
||||
/>
|
||||
<img v-else-if="embedImage" :src="embedImage" class="embed-preview-image" />
|
||||
<img v-else-if="embedImage" :src="embedImage" class="preview" />
|
||||
</template>
|
||||
<h4 v-if="embedTitle">{{ embedTitle }}</h4>
|
||||
<p v-if="embedDescription">{{ embedDescription }}</p>
|
||||
@ -20,25 +20,27 @@
|
||||
{{ dataEmbedUrl }}
|
||||
</a>
|
||||
</section>
|
||||
<aside v-if="showOverlay" class="embed-overlay">
|
||||
<aside v-if="showOverlay" class="overlay">
|
||||
<h3>{{ $t('editor.embed.data_privacy_warning') }}</h3>
|
||||
<ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text>
|
||||
<div class="embed-buttons">
|
||||
<ds-button primary @click.prevent="allowEmbed()">
|
||||
<div class="buttons">
|
||||
<base-button primary @click="allowEmbed()" data-test="play-now-button">
|
||||
{{ $t('editor.embed.play_now') }}
|
||||
</ds-button>
|
||||
<ds-button ghost @click.prevent="closeOverlay()">{{ $t('actions.cancel') }}</ds-button>
|
||||
</base-button>
|
||||
<base-button @click="closeOverlay()" data-test="cancel-button">
|
||||
{{ $t('actions.cancel') }}
|
||||
</base-button>
|
||||
</div>
|
||||
<label class="embed-checkbox">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="checkedAlwaysAllowEmbeds" />
|
||||
<span>{{ $t('editor.embed.always_allow') }}</span>
|
||||
</label>
|
||||
</aside>
|
||||
<ds-button
|
||||
<base-button
|
||||
icon="close"
|
||||
ghost
|
||||
size="small"
|
||||
class="embed-close-button"
|
||||
circle
|
||||
class="close-button"
|
||||
@click.prevent="removeEmbed()"
|
||||
/>
|
||||
</ds-container>
|
||||
@ -151,3 +153,86 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.embed-component {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: $space-small auto;
|
||||
overflow: hidden;
|
||||
border-radius: $border-radius-base;
|
||||
border: 1px solid $color-neutral-70;
|
||||
background-color: $color-neutral-90;
|
||||
|
||||
> .content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
h4 {
|
||||
margin: $space-small 0 0 $space-small;
|
||||
}
|
||||
|
||||
p,
|
||||
a {
|
||||
display: block;
|
||||
margin: 0 0 0 $space-small;
|
||||
}
|
||||
|
||||
.html {
|
||||
width: 100%;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 450px;
|
||||
}
|
||||
|
||||
.preview.--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
> .overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
padding: $space-large;
|
||||
background-color: $color-neutral-100;
|
||||
|
||||
> .buttons {
|
||||
.base-button {
|
||||
margin-right: $space-small;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
> .checkbox {
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
margin-right: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .close-button {
|
||||
position: absolute;
|
||||
top: $space-x-small;
|
||||
right: $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror[contenteditable='false'] {
|
||||
.close-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
61
webapp/components/EmotionButton/EmotionButton.vue
Normal file
61
webapp/components/EmotionButton/EmotionButton.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="emotion-button">
|
||||
<base-button :id="emotion" circle ghost @click="$emit('toggleEmotion', emotion)">
|
||||
<img class="image" :src="emojiPath" />
|
||||
</base-button>
|
||||
<label class="label" :for="emotion">{{ $t(`contribution.emotions-label.${emotion}`) }}</label>
|
||||
<p v-if="emotionCount !== null" class="count">{{ emotionCount }}x</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmotionButton',
|
||||
props: {
|
||||
emojiPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emotion: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emotionCount: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.emotion-button {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> .base-button {
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
padding: $space-xxx-small;
|
||||
}
|
||||
}
|
||||
|
||||
> .label {
|
||||
margin-top: $space-x-small;
|
||||
font-size: $font-size-small;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> .count {
|
||||
margin: $space-x-small 0;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: $size-button-base;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,27 +1,26 @@
|
||||
<template>
|
||||
<ds-flex :gutter="{ lg: 'large' }" class="emotions-flex">
|
||||
<div v-for="emotion in Object.keys(PostsEmotionsCountByEmotion)" :key="emotion">
|
||||
<ds-flex-item :width="{ lg: '100%' }">
|
||||
<hc-emotions-button
|
||||
@toggleEmotion="toggleEmotion"
|
||||
:PostsEmotionsCountByEmotion="PostsEmotionsCountByEmotion"
|
||||
:iconPath="iconPath(emotion)"
|
||||
:emotion="emotion"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
</div>
|
||||
</ds-flex>
|
||||
<div class="emotions-button-group">
|
||||
<emotion-button
|
||||
v-for="emotion in Object.keys(PostsEmotionsCountByEmotion)"
|
||||
:key="emotion"
|
||||
:emojiPath="iconPath(emotion)"
|
||||
:emotion="emotion"
|
||||
:emotionCount="PostsEmotionsCountByEmotion[emotion]"
|
||||
@toggleEmotion="toggleEmotion"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters } from 'vuex'
|
||||
import HcEmotionsButton from '~/components/EmotionsButton/EmotionsButton'
|
||||
import EmotionButton from '~/components/EmotionButton/EmotionButton'
|
||||
import { PostsEmotionsByCurrentUser } from '~/graphql/PostQuery.js'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmotionsButton,
|
||||
EmotionButton,
|
||||
},
|
||||
props: {
|
||||
post: { type: Object, default: () => {} },
|
||||
@ -113,3 +112,9 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.emotions-button-group {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-button size="large" ghost @click="toggleEmotion(emotion)" class="emotions-buttons">
|
||||
<img :src="iconPath" width="40" />
|
||||
</ds-button>
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<div class="emotions-mobile-space">
|
||||
<p class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</p>
|
||||
<p style="display: inline" :key="PostsEmotionsCountByEmotion[emotion]">
|
||||
{{ PostsEmotionsCountByEmotion[emotion] }}x
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
iconPath: { type: String, default: null },
|
||||
PostsEmotionsCountByEmotion: { type: Object, default: () => {} },
|
||||
emotion: { type: String, default: null },
|
||||
},
|
||||
methods: {
|
||||
toggleEmotion(emotion) {
|
||||
this.$emit('toggleEmotion', emotion)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.emotions-flex {
|
||||
justify-content: space-evenly;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emotions-label {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.emotions-buttons {
|
||||
&:hover {
|
||||
background-color: $background-color-base;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
.emotions-mobile-space {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -150,7 +150,7 @@ describe('FilterPosts.vue', () => {
|
||||
describe('click on an "emotions-buttons" button', () => {
|
||||
it('calls TOGGLE_EMOTION when clicked', () => {
|
||||
const wrapper = openFilterPosts()
|
||||
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
|
||||
happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
|
||||
happyEmotionButton.trigger('click')
|
||||
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
|
||||
})
|
||||
@ -158,7 +158,7 @@ describe('FilterPosts.vue', () => {
|
||||
it('sets the attribute `src` to colorized image', () => {
|
||||
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
|
||||
const wrapper = openFilterPosts()
|
||||
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
|
||||
happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
|
||||
const happyEmotionButtonImage = happyEmotionButton.find('img')
|
||||
expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
|
||||
})
|
||||
|
||||
@ -6,54 +6,42 @@
|
||||
</ds-flex>
|
||||
<ds-flex :gutter="{ lg: 'large' }">
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '100%', md: '100%', lg: '10%' }"
|
||||
class="categories-menu-item"
|
||||
:width="{ base: '100%', sm: '100%', md: '10%', lg: '10%' }"
|
||||
class="follow-filter"
|
||||
>
|
||||
<ds-flex>
|
||||
<ds-flex-item width="10%" />
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-flex-item width="100%">
|
||||
<div class="follow-filter-button">
|
||||
<base-button
|
||||
data-test="filter-by-followed"
|
||||
icon="user-plus"
|
||||
circle
|
||||
:filled="filteredByUsersFollowed"
|
||||
@click="toggleFilteredByFollowed(user.id)"
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
/>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<ds-flex-item>
|
||||
<label class="follow-label">{{ $t('filter-posts.followers.label') }}</label>
|
||||
</ds-flex-item>
|
||||
<ds-space />
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<base-button
|
||||
data-test="filter-by-followed"
|
||||
icon="user-plus"
|
||||
circle
|
||||
:filled="filteredByUsersFollowed"
|
||||
@click="toggleFilteredByFollowed(user.id)"
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
/>
|
||||
<label class="follow-label">{{ $t('filter-posts.followers.label') }}</label>
|
||||
</ds-flex-item>
|
||||
<div v-for="emotion in emotionsArray" :key="emotion">
|
||||
<ds-flex-item :width="{ lg: '100%' }">
|
||||
<base-button @click="toogleFilteredByEmotions(emotion)" class="emotions-buttons" circle>
|
||||
<img :src="iconPath(emotion)" width="40" />
|
||||
</base-button>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<ds-flex-item class="emotions-mobile-space text-center">
|
||||
<label class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</label>
|
||||
</ds-flex-item>
|
||||
</ds-flex-item>
|
||||
</div>
|
||||
<emotion-button
|
||||
v-for="emotion in emotionsArray"
|
||||
:key="emotion"
|
||||
:emojiPath="iconPath(emotion)"
|
||||
:emotion="emotion"
|
||||
@toggleEmotion="toogleFilteredByEmotions(emotion)"
|
||||
/>
|
||||
<ds-space margin-bottom="large" />
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import EmotionButton from '~/components/EmotionButton/EmotionButton'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmotionButton,
|
||||
},
|
||||
props: {
|
||||
user: { type: Object, required: true },
|
||||
},
|
||||
@ -91,13 +79,22 @@ export default {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.follow-filter.ds-flex-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: $space-base;
|
||||
|
||||
> .follow-label {
|
||||
margin-top: $space-x-small;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
#filter-posts-header {
|
||||
text-align: center;
|
||||
}
|
||||
.follow-filter-button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.text-center {
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<ds-space class="load-more" margin-top="large" style="text-align: center">
|
||||
<ds-button :loading="loading" icon="arrow-down" ghost @click="$emit('click')">
|
||||
{{ $t('actions.loadMore') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<ds-flex direction="row-reverse">
|
||||
<ds-flex-item width="50px">
|
||||
<ds-button @click="next" :disabled="!hasNext" icon="arrow-right" primary />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item width="50px">
|
||||
<ds-button @click="back" :disabled="!hasPrevious" icon="arrow-left" primary />
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
hasNext: { type: Boolean, default: false },
|
||||
hasPrevious: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
this.$emit('back')
|
||||
},
|
||||
next() {
|
||||
this.$emit('next')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,39 +1,35 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import Paginate from './Paginate'
|
||||
import PaginationButtons from './PaginationButtons'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('Paginate.vue', () => {
|
||||
let propsData, wrapper, nextButton, backButton
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
})
|
||||
describe('PaginationButtons.vue', () => {
|
||||
let propsData = {}
|
||||
let wrapper
|
||||
let nextButton
|
||||
let backButton
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Paginate, { propsData, localVue })
|
||||
return mount(PaginationButtons, { propsData, localVue })
|
||||
}
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
describe('next button', () => {
|
||||
beforeEach(() => {
|
||||
propsData.hasNext = true
|
||||
wrapper = Wrapper()
|
||||
nextButton = wrapper.findAll('.ds-button').at(0)
|
||||
nextButton = wrapper.find('[data-test="next-button"]')
|
||||
})
|
||||
|
||||
it('is disabled by default', () => {
|
||||
propsData = {}
|
||||
wrapper = Wrapper()
|
||||
nextButton = wrapper.findAll('.ds-button').at(0)
|
||||
nextButton = wrapper.find('[data-test="next-button"]')
|
||||
expect(nextButton.attributes().disabled).toEqual('disabled')
|
||||
})
|
||||
|
||||
it('is not disabled if hasNext is true', () => {
|
||||
it('is enabled if hasNext is true', () => {
|
||||
expect(nextButton.attributes().disabled).toBeUndefined()
|
||||
})
|
||||
|
||||
@ -47,17 +43,17 @@ describe('Paginate.vue', () => {
|
||||
beforeEach(() => {
|
||||
propsData.hasPrevious = true
|
||||
wrapper = Wrapper()
|
||||
backButton = wrapper.findAll('.ds-button').at(1)
|
||||
backButton = wrapper.find('[data-test="previous-button"]')
|
||||
})
|
||||
|
||||
it('is disabled by default', () => {
|
||||
propsData = {}
|
||||
wrapper = Wrapper()
|
||||
backButton = wrapper.findAll('.ds-button').at(1)
|
||||
backButton = wrapper.find('[data-test="previous-button"]')
|
||||
expect(backButton.attributes().disabled).toEqual('disabled')
|
||||
})
|
||||
|
||||
it('is not disabled if hasPrevious is true', () => {
|
||||
it('is enabled if hasPrevious is true', () => {
|
||||
expect(backButton.attributes().disabled).toBeUndefined()
|
||||
})
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import Paginate from '~/components/Paginate/Paginate'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
import helpers from '~/storybook/helpers'
|
||||
|
||||
helpers.init()
|
||||
|
||||
storiesOf('Paginate', module)
|
||||
storiesOf('PaginationButtons', module)
|
||||
.addDecorator(withA11y)
|
||||
.addDecorator(helpers.layout)
|
||||
.add('basic pagination', () => ({
|
||||
components: { Paginate },
|
||||
components: { PaginationButtons },
|
||||
data: () => ({
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
@ -19,7 +19,7 @@ storiesOf('Paginate', module)
|
||||
back: action('back'),
|
||||
next: action('next'),
|
||||
},
|
||||
template: `<paginate
|
||||
template: `<pagination-buttons
|
||||
:hasNext="hasNext"
|
||||
:hasPrevious="hasPrevious"
|
||||
@back="back"
|
||||
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="pagination-buttons">
|
||||
<base-button
|
||||
@click="$emit('back')"
|
||||
:disabled="!hasPrevious"
|
||||
icon="arrow-left"
|
||||
circle
|
||||
data-test="previous-button"
|
||||
/>
|
||||
<base-button
|
||||
@click="$emit('next')"
|
||||
:disabled="!hasNext"
|
||||
icon="arrow-right"
|
||||
circle
|
||||
data-test="next-button"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
hasNext: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasPrevious: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: $size-width-paginate;
|
||||
margin: $space-x-small auto;
|
||||
}
|
||||
</style>
|
||||
@ -7,7 +7,7 @@
|
||||
</client-only>
|
||||
</div>
|
||||
<reports-table :reports="reports" @confirm="openModal" />
|
||||
<paginate :hasNext="hasNext" :hasPrevious="hasPrevious" @back="back" @next="next" />
|
||||
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @back="back" @next="next" />
|
||||
</ds-card>
|
||||
</template>
|
||||
<script>
|
||||
@ -15,13 +15,13 @@ import { mapMutations } from 'vuex'
|
||||
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
|
||||
import ReportsTable from '~/components/features/ReportsTable/ReportsTable'
|
||||
import { reportsListQuery, reviewMutation } from '~/graphql/Moderation.js'
|
||||
import Paginate from '~/components/Paginate/Paginate'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownFilter,
|
||||
ReportsTable,
|
||||
Paginate,
|
||||
PaginationButtons,
|
||||
},
|
||||
data() {
|
||||
const pageSize = 25
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<ds-flex-item class="search-heading">
|
||||
<ds-heading soft size="h5">
|
||||
{{ $t(`search.heading.${resourceType}`) }}
|
||||
</ds-heading>
|
||||
</ds-flex-item>
|
||||
<ds-heading soft size="h5" class="search-heading">
|
||||
{{ $t(`search.heading.${resourceType}`) }}
|
||||
</ds-heading>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
@ -14,13 +12,16 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.search-heading {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-weight: bold;
|
||||
.search-heading.ds-heading {
|
||||
margin: -$space-x-small;
|
||||
padding: $space-x-small;
|
||||
background-color: $color-neutral-100;
|
||||
font-weight: $font-weight-bold;
|
||||
cursor: default;
|
||||
background-color: white;
|
||||
margin: -8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
// override styleguide styles
|
||||
.search-heading.ds-heading:first-child {
|
||||
margin-top: -$space-x-small;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -32,82 +32,78 @@ describe('SearchableInput.vue', () => {
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
describe('testing custom functions', () => {
|
||||
let select
|
||||
let select
|
||||
|
||||
beforeEach(() => {
|
||||
select = wrapper.find('.ds-select')
|
||||
select.trigger('focus')
|
||||
select.element.value = 'abcd'
|
||||
})
|
||||
|
||||
it('opens the dropdown when focused', () => {
|
||||
expect(wrapper.find('.ds-select-dropdown').isVisible()).toBe(true)
|
||||
})
|
||||
|
||||
it('closes the dropdown when blurred', () => {
|
||||
select.trigger('blur')
|
||||
expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('closes the dropdown when cleared with esc key', () => {
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.esc')
|
||||
expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('changes the unprocessedSearchInput as the value changes', () => {
|
||||
select.trigger('input')
|
||||
expect(select.element.value).toBe('abcd')
|
||||
})
|
||||
|
||||
it('searches for the term when enter is pressed', async () => {
|
||||
select.element.value = 'ab'
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.enter')
|
||||
await expect(wrapper.emitted().query[0]).toEqual(['ab'])
|
||||
})
|
||||
|
||||
it('calls onDelete when the delete key is pressed', () => {
|
||||
const spy = jest.spyOn(wrapper.vm, 'onDelete')
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.delete')
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('navigating to resource', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { options: searchResults }
|
||||
wrapper = Wrapper()
|
||||
select = wrapper.find('.ds-select')
|
||||
select.trigger('focus')
|
||||
select.element.value = 'abcd'
|
||||
})
|
||||
|
||||
it('opens the select dropdown when focused on', () => {
|
||||
expect(wrapper.find('.is-open').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('opens the select dropdown and blurs after focused on', async () => {
|
||||
select.trigger('blur')
|
||||
it('pushes to post page', async () => {
|
||||
select.element.value = 'Post'
|
||||
select.trigger('input')
|
||||
const post = wrapper.find('.search-post')
|
||||
post.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.find('.is-open').exists()).toBe(false)
|
||||
expect(mocks.$router.push).toHaveBeenCalledWith({
|
||||
name: 'post-id-slug',
|
||||
params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' },
|
||||
})
|
||||
})
|
||||
|
||||
it('is clearable', async () => {
|
||||
it("pushes to user's profile", async () => {
|
||||
select.element.value = 'Bob'
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.esc')
|
||||
const users = wrapper.findAll('.userinfo')
|
||||
const bob = users.filter(item => item.text() === '@bob-der-baumeister')
|
||||
bob.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.find('.is-open').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('changes the unprocessedSearchInput as the value changes', () => {
|
||||
select.trigger('input')
|
||||
expect(select.element.value).toBe('abcd')
|
||||
})
|
||||
|
||||
it('searches for the term when enter is pressed', async () => {
|
||||
select.element.value = 'ab'
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.enter')
|
||||
await expect(wrapper.emitted().query[0]).toEqual(['ab'])
|
||||
})
|
||||
|
||||
it('calls onDelete when the delete key is pressed', () => {
|
||||
const spy = jest.spyOn(wrapper.vm, 'onDelete')
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.delete')
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('navigating to resource', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { options: searchResults }
|
||||
wrapper = Wrapper()
|
||||
select = wrapper.find('.ds-select')
|
||||
select.trigger('focus')
|
||||
})
|
||||
|
||||
it('pushes to post page', async () => {
|
||||
select.element.value = 'Post'
|
||||
select.trigger('input')
|
||||
const post = wrapper.find('.search-post')
|
||||
post.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$router.push).toHaveBeenCalledWith({
|
||||
name: 'post-id-slug',
|
||||
params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' },
|
||||
})
|
||||
})
|
||||
|
||||
it("pushes to user's profile", async () => {
|
||||
select.element.value = 'Bob'
|
||||
select.trigger('input')
|
||||
const users = wrapper.findAll('.userinfo')
|
||||
const bob = users.filter(item => item.text() === '@bob-der-baumeister')
|
||||
bob.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$router.push).toHaveBeenCalledWith({
|
||||
name: 'profile-id-slug',
|
||||
params: { id: 'u2', slug: 'bob-der-baumeister' },
|
||||
})
|
||||
expect(mocks.$router.push).toHaveBeenCalledWith({
|
||||
name: 'profile-id-slug',
|
||||
params: { id: 'u2', slug: 'bob-der-baumeister' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,60 +1,46 @@
|
||||
<template>
|
||||
<div
|
||||
class="searchable-input"
|
||||
aria-label="search"
|
||||
role="search"
|
||||
:class="{
|
||||
'is-active': isActive,
|
||||
'is-open': isOpen,
|
||||
}"
|
||||
>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<ds-button v-if="isActive" icon="close" ghost class="search-clear-btn" @click="clear" />
|
||||
<ds-select
|
||||
type="search"
|
||||
icon="search"
|
||||
v-model="searchValue"
|
||||
:id="id"
|
||||
label-prop="id"
|
||||
:icon-right="isActive ? 'close' : null"
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
:filter="item => item"
|
||||
:no-options-available="emptyText"
|
||||
:auto-reset-search="!searchValue"
|
||||
:placeholder="$t('search.placeholder')"
|
||||
@click.capture.native="isOpen = true"
|
||||
@focus.capture.native="onFocus"
|
||||
@input.native="handleInput"
|
||||
@keyup.enter.native="onEnter"
|
||||
@keyup.delete.native="onDelete"
|
||||
@keyup.esc.native="clear"
|
||||
@blur.capture.native="onBlur"
|
||||
@input.exact="onSelect"
|
||||
<div class="searchable-input" aria-label="search" role="search">
|
||||
<ds-select
|
||||
type="search"
|
||||
icon="search"
|
||||
v-model="searchValue"
|
||||
:id="id"
|
||||
label-prop="id"
|
||||
:icon-right="null"
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
:filter="item => item"
|
||||
:no-options-available="emptyText"
|
||||
:auto-reset-search="!searchValue"
|
||||
:placeholder="$t('search.placeholder')"
|
||||
@focus.capture.native="onFocus"
|
||||
@input.native="handleInput"
|
||||
@keyup.enter.native="onEnter"
|
||||
@keyup.delete.native="onDelete"
|
||||
@keyup.esc.native="clear"
|
||||
@blur.capture.native="onBlur"
|
||||
@input.exact="onSelect"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<search-heading v-if="isFirstOfType(option)" :resource-type="option.__typename" />
|
||||
<p
|
||||
v-if="option.__typename === 'User'"
|
||||
:class="{ 'option-with-heading': isFirstOfType(option) }"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span v-if="isFirstOfType(option)" class="search-heading">
|
||||
<search-heading :resource-type="option.__typename" />
|
||||
</span>
|
||||
<span
|
||||
v-if="option.__typename === 'User'"
|
||||
:class="{ 'option-with-heading': isFirstOfType(option), 'flex-span': true }"
|
||||
>
|
||||
<hc-user :user="option" :showPopover="false" />
|
||||
</span>
|
||||
<span
|
||||
v-if="option.__typename === 'Post'"
|
||||
:class="{ 'option-with-heading': isFirstOfType(option), 'flex-span': true }"
|
||||
>
|
||||
<search-post :option="option" />
|
||||
</span>
|
||||
</template>
|
||||
</ds-select>
|
||||
</div>
|
||||
</div>
|
||||
<hc-user :user="option" :showPopover="false" />
|
||||
</p>
|
||||
<p
|
||||
v-if="option.__typename === 'Post'"
|
||||
:class="{ 'option-with-heading': isFirstOfType(option) }"
|
||||
>
|
||||
<search-post :option="option" />
|
||||
</p>
|
||||
</template>
|
||||
</ds-select>
|
||||
<base-button v-if="isActive" icon="close" circle ghost size="small" @click="clear" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty } from 'lodash'
|
||||
import SearchHeading from '~/components/generic/SearchHeading/SearchHeading.vue'
|
||||
@ -75,7 +61,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
searchValue: '',
|
||||
value: '',
|
||||
unprocessedSearchInput: '',
|
||||
@ -101,12 +86,10 @@ export default {
|
||||
},
|
||||
onFocus(event) {
|
||||
clearTimeout(this.searchProcess)
|
||||
this.isOpen = true
|
||||
},
|
||||
handleInput(event) {
|
||||
clearTimeout(this.searchProcess)
|
||||
this.value = event.target ? event.target.value.replace(/\s+/g, ' ').trim() : ''
|
||||
this.isOpen = true
|
||||
this.unprocessedSearchInput = this.value
|
||||
if (isEmpty(this.value) || this.value.replace(/\s+/g, '').length < 3) {
|
||||
return
|
||||
@ -120,7 +103,6 @@ export default {
|
||||
* TODO: on enter we should go to a dedicated search page!?
|
||||
*/
|
||||
onEnter(event) {
|
||||
this.isOpen = false
|
||||
clearTimeout(this.searchProcess)
|
||||
if (!this.pending) {
|
||||
this.previousSearchTerm = this.unprocessedSearchInput
|
||||
@ -137,7 +119,6 @@ export default {
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.isOpen = false
|
||||
this.unprocessedSearchInput = ''
|
||||
this.previousSearchTerm = ''
|
||||
this.searchValue = ''
|
||||
@ -146,11 +127,9 @@ export default {
|
||||
},
|
||||
onBlur(event) {
|
||||
this.searchValue = this.previousSearchTerm
|
||||
this.isOpen = false
|
||||
clearTimeout(this.searchProcess)
|
||||
},
|
||||
onSelect(item) {
|
||||
this.isOpen = false
|
||||
this.goToResource(item)
|
||||
this.$nextTick(() => {
|
||||
this.searchValue = this.previousSearchTerm
|
||||
@ -170,58 +149,32 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.searchable-input {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
$padding-left: $space-x-small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.ds-form-item {
|
||||
flex-basis: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ds-select-dropdown {
|
||||
max-height: 70vh;
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
|
||||
.option-with-heading {
|
||||
margin-top: $space-x-small;
|
||||
padding-top: $space-xx-small;
|
||||
}
|
||||
.flex-span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ds-select-dropdown {
|
||||
transition: box-shadow 100ms;
|
||||
max-height: 70vh;
|
||||
}
|
||||
&.is-open {
|
||||
.ds-select-dropdown {
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
}
|
||||
.ds-select-dropdown-message {
|
||||
opacity: 0.5;
|
||||
padding-left: $padding-left;
|
||||
}
|
||||
.search-clear-btn {
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
.base-button {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ds-select {
|
||||
z-index: $z-index-dropdown + 1;
|
||||
}
|
||||
.ds-select-option-hover {
|
||||
.ds-text-size-small,
|
||||
.ds-text-size-small-x {
|
||||
color: $text-color-soft;
|
||||
}
|
||||
}
|
||||
.field {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.control {
|
||||
width: 100%;
|
||||
right: $space-xx-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,21 +4,21 @@
|
||||
<ds-container class="main-navigation-container" style="padding: 10px 10px;">
|
||||
<div>
|
||||
<ds-flex class="main-navigation-flex">
|
||||
<ds-flex-item :width="{ lg: '3.5%' }" />
|
||||
<ds-flex-item :width="{ base: '80%', sm: '80%', md: '80%', lg: '15%' }">
|
||||
<ds-flex-item :width="{ base: '142px' }">
|
||||
<nuxt-link :to="{ name: 'index' }" v-scroll-to="'.main-navigation'">
|
||||
<ds-logo />
|
||||
</nuxt-link>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '20%', sm: '20%', md: '20%', lg: '0%' }"
|
||||
:width="{ base: '40%', sm: '40%', md: '40%', lg: '0%' }"
|
||||
class="mobile-hamburger-menu"
|
||||
>
|
||||
<ds-button icon="bars" @click="toggleMobileMenuView" right />
|
||||
<base-button icon="bars" @click="toggleMobileMenuView" circle />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
:width="{ base: '85%', sm: '85%', md: '50%', lg: '50%' }"
|
||||
:width="{ base: '45%', sm: '45%', md: '45%', lg: '50%' }"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-shrink: 0; flex-grow: 1;"
|
||||
id="nav-search-box"
|
||||
v-if="isLoggedIn"
|
||||
>
|
||||
@ -26,8 +26,8 @@
|
||||
</ds-flex-item>
|
||||
<ds-flex-item
|
||||
v-if="isLoggedIn"
|
||||
:width="{ base: '15%', sm: '15%', md: '10%', lg: '10%' }"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
style="flex-grow: 0; flex-basis: auto;"
|
||||
>
|
||||
<client-only>
|
||||
<filter-posts
|
||||
@ -38,10 +38,8 @@
|
||||
/>
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '10%', lg: '2%' }" />
|
||||
<ds-flex-item
|
||||
:width="{ base: '100%', sm: '100%', md: '100%', lg: '13%' }"
|
||||
style="background-color:white"
|
||||
style="background-color: white; flex-basis: auto;"
|
||||
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
|
||||
>
|
||||
<div
|
||||
@ -50,6 +48,7 @@
|
||||
'desktop-view': !toggleMobileMenu,
|
||||
'hide-mobile-menu': !toggleMobileMenu,
|
||||
}"
|
||||
style="flex-basis: auto;"
|
||||
>
|
||||
<client-only>
|
||||
<locale-switch class="topbar-locale-switch" placement="top" offset="8" />
|
||||
@ -164,17 +163,21 @@ export default {
|
||||
}
|
||||
.main-navigation-right {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.main-navigation-right .desktop-view {
|
||||
float: right;
|
||||
}
|
||||
@media only screen and (min-width: 960px) {
|
||||
.ds-flex-item.mobile-hamburger-menu {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
@media only screen and (min-width: 730px) {
|
||||
.mobile-hamburger-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
@media only screen and (max-width: 730px) {
|
||||
#nav-search-box,
|
||||
.main-navigation-right {
|
||||
margin: 10px 0px;
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
{{ scope.row.createdAt | dateTime }}
|
||||
</template>
|
||||
</ds-table>
|
||||
<hc-paginate :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
|
||||
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
|
||||
</ds-card>
|
||||
<ds-card v-else>
|
||||
<ds-placeholder>{{ $t('admin.users.empty') }}</ds-placeholder>
|
||||
@ -62,11 +62,11 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { isEmail } from 'validator'
|
||||
import normalizeEmail from '~/components/utils/NormalizeEmail'
|
||||
import HcPaginate from '~/components/Paginate/Paginate'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcPaginate,
|
||||
PaginationButtons,
|
||||
},
|
||||
data() {
|
||||
const pageSize = 15
|
||||
|
||||
@ -126,11 +126,6 @@ describe('PostIndex', () => {
|
||||
.trigger('click')
|
||||
expect(mutations['posts/SELECT_ORDER']).toHaveBeenCalledWith({}, 'createdAt_asc')
|
||||
})
|
||||
|
||||
it('updates offset when a user clicks on the load more button', () => {
|
||||
wrapper.find('.load-more button').trigger('click')
|
||||
expect(wrapper.vm.offset).toEqual(12)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -58,9 +58,7 @@
|
||||
</nuxt-link>
|
||||
</client-only>
|
||||
<client-only>
|
||||
<infinite-loading v-if="hasMore" @infinite="showMoreContributions">
|
||||
<hc-load-more :loading="$apollo.loading" @click="showMoreContributions" />
|
||||
</infinite-loading>
|
||||
<infinite-loading v-if="hasMore" @infinite="showMoreContributions" />
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
@ -70,7 +68,6 @@
|
||||
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
@ -83,7 +80,6 @@ export default {
|
||||
// DonationInfo,
|
||||
FilterMenu,
|
||||
HcPostCard,
|
||||
HcLoadMore,
|
||||
HcEmpty,
|
||||
MasonryGrid,
|
||||
MasonryGridItem,
|
||||
|
||||
@ -3,7 +3,7 @@ import NotificationsPage from './index.vue'
|
||||
|
||||
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
|
||||
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
|
||||
import Paginate from '~/components/Paginate/Paginate'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -122,14 +122,14 @@ describe('PostIndex', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Paginate', () => {
|
||||
describe('PaginationButtons', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('next: given a user is on the first page', () => {
|
||||
it('adds offset to pageSize to skip first x notifications and display next page', () => {
|
||||
wrapper.find(Paginate).vm.$emit('next')
|
||||
wrapper.find(PaginationButtons).vm.$emit('next')
|
||||
expect(wrapper.vm.offset).toEqual(12)
|
||||
})
|
||||
})
|
||||
@ -137,7 +137,7 @@ describe('PostIndex', () => {
|
||||
describe('back: given a user is on the third page', () => {
|
||||
it('sets offset when back is emitted', () => {
|
||||
wrapper.setData({ offset: 24 })
|
||||
wrapper.find(Paginate).vm.$emit('back')
|
||||
wrapper.find(PaginationButtons).vm.$emit('back')
|
||||
expect(wrapper.vm.offset).toEqual(12)
|
||||
})
|
||||
})
|
||||
|
||||
@ -15,21 +15,21 @@
|
||||
@markNotificationAsRead="markNotificationAsRead"
|
||||
:notifications="notifications"
|
||||
/>
|
||||
<paginate :hasNext="hasNext" :hasPrevious="hasPrevious" @back="back" @next="next" />
|
||||
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @back="back" @next="next" />
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
|
||||
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
|
||||
import Paginate from '~/components/Paginate/Paginate'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
import { notificationQuery, markAsReadMutation } from '~/graphql/User'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownFilter,
|
||||
NotificationsTable,
|
||||
Paginate,
|
||||
PaginationButtons,
|
||||
},
|
||||
data() {
|
||||
const pageSize = 12
|
||||
|
||||
@ -12,7 +12,12 @@
|
||||
>
|
||||
<aside v-show="post.imageBlurred" class="blur-toggle">
|
||||
<img v-show="blurred" :src="post.image | proxyApiUrl" class="preview" />
|
||||
<ds-button :icon="blurred ? 'eye' : 'eye-slash'" primary @click="blurred = !blurred" />
|
||||
<base-button
|
||||
:icon="blurred ? 'eye' : 'eye-slash'"
|
||||
filled
|
||||
circle
|
||||
@click="blurred = !blurred"
|
||||
/>
|
||||
</aside>
|
||||
<hc-user :user="post.author" :date-time="post.createdAt">
|
||||
<template v-slot:dateTime>
|
||||
@ -59,13 +64,9 @@
|
||||
</div>
|
||||
<ds-space margin-top="x-large">
|
||||
<ds-flex :gutter="{ lg: 'small' }">
|
||||
<ds-flex-item
|
||||
:width="{ lg: '75%', md: '75%', sm: '75%' }"
|
||||
class="emotions-buttons-mobile"
|
||||
>
|
||||
<ds-flex-item :width="{ lg: '75%', md: '75%', sm: '75%', base: '100%' }">
|
||||
<hc-emotions :post="post" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ lg: '10%', md: '3%', sm: '3%' }" />
|
||||
<!-- Shout Button -->
|
||||
<ds-flex-item
|
||||
:width="{ lg: '15%', md: '22%', sm: '22%', base: '100%' }"
|
||||
|
||||
@ -88,68 +88,6 @@ describe('ProfileSlug', () => {
|
||||
it('displays name of the user', () => {
|
||||
expect(wrapper.text()).toContain('Bob the builder')
|
||||
})
|
||||
|
||||
describe('load more button', () => {
|
||||
const aPost = {
|
||||
title: 'I am a post',
|
||||
content: 'This is my content',
|
||||
contentExcerpt: 'This is my content',
|
||||
}
|
||||
|
||||
describe('currently no posts available (e.g. after tab switching)', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setData({ posts: [], hasMore: false })
|
||||
})
|
||||
|
||||
it('displays no "load more" button', () => {
|
||||
expect(wrapper.find('.load-more').exists()).toBe(false)
|
||||
})
|
||||
|
||||
describe('apollo client in `loading` state', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$apollo.loading = true
|
||||
})
|
||||
|
||||
it('never displays more than one loading spinner', () => {
|
||||
expect(wrapper.findAll('.ds-spinner')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('displays a loading spinner below the posts list', () => {
|
||||
expect(wrapper.find('.ds-spinner').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('pagination returned at least as many posts as pageSize', () => {
|
||||
beforeEach(() => {
|
||||
const posts = [1, 2, 3, 4, 5, 6].map(id => {
|
||||
return {
|
||||
...aPost,
|
||||
id,
|
||||
}
|
||||
})
|
||||
wrapper.setData({ posts })
|
||||
})
|
||||
|
||||
it('displays "load more" button', () => {
|
||||
expect(wrapper.find('.load-more').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('apollo client in `loading` state', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$apollo.loading = true
|
||||
})
|
||||
|
||||
it('never displays more than one loading spinner', () => {
|
||||
expect(wrapper.findAll('.ds-spinner')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('displays a loading spinner below the posts list', () => {
|
||||
expect(wrapper.find('.load-more .ds-spinner').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -262,9 +262,7 @@
|
||||
</template>
|
||||
</masonry-grid>
|
||||
<client-only>
|
||||
<infinite-loading v-if="hasMore" @infinite="showMoreContributions">
|
||||
<hc-load-more :loading="$apollo.loading" @click="showMoreContributions" />
|
||||
</infinite-loading>
|
||||
<infinite-loading v-if="hasMore" @infinite="showMoreContributions" />
|
||||
</client-only>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
@ -278,7 +276,6 @@ import HcPostCard from '~/components/PostCard/PostCard.vue'
|
||||
import HcFollowButton from '~/components/FollowButton.vue'
|
||||
import HcCountTo from '~/components/CountTo.vue'
|
||||
import HcBadges from '~/components/Badges.vue'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import HcUpload from '~/components/Upload'
|
||||
@ -307,7 +304,6 @@ export default {
|
||||
HcFollowButton,
|
||||
HcCountTo,
|
||||
HcBadges,
|
||||
HcLoadMore,
|
||||
HcEmpty,
|
||||
HcAvatar,
|
||||
ContentMenu,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user