mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
feat(webapp): shout comments (#8600)
* shout comments * fix notifications * Remove whitespace for empty category sections * Overhaul post actions * Adjust spacing * Allow fine-grained size control for icons and circle buttons via css variables; adjust comments layout * Adjust spacing * Add test for ActionButton (WIP) * Rename import * Remove text and add count bubble * Use filled icons to indicate active states * Adjust sizes and orientation * Remove unused properties, add test * Fix ObserveButton test * Fix ShoutButton test * fix tests * Adapt styles * Adjust style for larger numbers * Remove unused icon * Fix test structure * Remove unused class names --------- Co-authored-by: Maximilian Harz <maxharz@gmail.com>
This commit is contained in:
parent
51564e5d9b
commit
4b3a26d517
@ -108,10 +108,14 @@ export default {
|
||||
count: {
|
||||
postObservingUsersCount:
|
||||
'-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(related:User) WHERE obs.active = true AND NOT related.deleted AND NOT related.disabled',
|
||||
shoutedCount:
|
||||
'<-[:SHOUTED]-(related:User) WHERE NOT related.deleted = true AND NOT related.disabled = true',
|
||||
},
|
||||
boolean: {
|
||||
isPostObservedByMe:
|
||||
'MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(related:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(related) >= 1',
|
||||
shoutedByCurrentUser:
|
||||
'MATCH (this) RETURN EXISTS((this)<-[:SHOUTED]-(:User {id: $cypherParams.currentUserId}))',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
enum ShoutTypeEnum {
|
||||
Post
|
||||
Comment
|
||||
}
|
||||
@ -53,6 +53,14 @@ type Comment {
|
||||
)
|
||||
postObservingUsersCount: Int!
|
||||
@cypher(statement: "MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(u:User) WHERE obs.active = true AND NOT u.disabled = true AND NOT u.deleted = true RETURN COUNT(DISTINCT u)")
|
||||
|
||||
shoutedByCurrentUser: Boolean!
|
||||
@cypher(statement: "MATCH (this) RETURN EXISTS((this)<-[:SHOUTED]-(:User {id: $cypherParams.currentUserId}))")
|
||||
|
||||
shoutedCount: Int!
|
||||
@cypher(
|
||||
statement: "MATCH (this)<-[:SHOUTED]-(related:User) WHERE NOT related.deleted = true AND NOT related.disabled = true RETURN COUNT(DISTINCT related)"
|
||||
)
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
||||
@ -243,9 +243,9 @@ type Mutation {
|
||||
markTeaserAsViewed(id: ID!): Post
|
||||
|
||||
# Shout the given Type and ID
|
||||
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||
shout(id: ID!, type: ShoutTypeEnum!): Boolean!
|
||||
# Unshout the given Type and ID
|
||||
unshout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||
unshout(id: ID!, type: ShoutTypeEnum!): Boolean!
|
||||
|
||||
toggleObservePost(id: ID!, value: Boolean!): Post!
|
||||
}
|
||||
|
||||
64
webapp/components/ActionButton.spec.js
Normal file
64
webapp/components/ActionButton.spec.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||
import '@testing-library/jest-dom'
|
||||
import ActionButton from './ActionButton.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('ActionButton.vue', () => {
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
})
|
||||
|
||||
let wrapper
|
||||
const Wrapper = ({ isDisabled = false } = {}) => {
|
||||
return render(ActionButton, {
|
||||
mocks,
|
||||
localVue,
|
||||
propsData: {
|
||||
icon: 'heart',
|
||||
text: 'Click me',
|
||||
count: 7,
|
||||
disabled: isDisabled,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('when not disabled', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows count', () => {
|
||||
const count = screen.getByText('7')
|
||||
expect(count).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('button emits click event', async () => {
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.emitted().click).toEqual([[]])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when disabled', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = Wrapper({ isDisabled: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('button does not emit click event', async () => {
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.emitted().click).toEqual([[]])
|
||||
})
|
||||
})
|
||||
})
|
||||
61
webapp/components/ActionButton.vue
Normal file
61
webapp/components/ActionButton.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="action-button">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:icon="icon"
|
||||
:aria-label="text"
|
||||
:filled="filled"
|
||||
circle
|
||||
@click="click"
|
||||
/>
|
||||
<div class="count">{{ count }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
count: { type: Number, required: true },
|
||||
text: { type: String, required: true },
|
||||
icon: { type: String, required: true },
|
||||
filled: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean },
|
||||
loading: { type: Boolean },
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: $space-xx-small;
|
||||
position: relative;
|
||||
--icon-size: calc(var(--circle-button-width, #{$size-button-base}) / 2);
|
||||
}
|
||||
.count {
|
||||
user-select: none;
|
||||
color: $color-primary-dark;
|
||||
background-color: $color-secondary-inverse;
|
||||
border: 1px solid $color-primary-dark;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: calc(100% - 16px);
|
||||
--diameter: calc(var(--circle-button-width, #{$size-button-base}) * 0.7);
|
||||
min-width: var(--diameter);
|
||||
height: var(--diameter);
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
padding-inline: 2px;
|
||||
}
|
||||
</style>
|
||||
@ -17,7 +17,7 @@ describe('CommentCard.vue', () => {
|
||||
postId: 'post42',
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: jest.fn((t) => t),
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
|
||||
@ -39,15 +39,25 @@
|
||||
{{ isCollapsed ? $t('comment.show.more') : $t('comment.show.less') }}
|
||||
</base-button>
|
||||
</template>
|
||||
<base-button
|
||||
:title="this.$t('post.comment.reply')"
|
||||
icon="level-down"
|
||||
class="reply-button"
|
||||
circle
|
||||
size="small"
|
||||
v-scroll-to="'.editor'"
|
||||
@click="reply"
|
||||
/>
|
||||
<div class="actions">
|
||||
<shout-button
|
||||
:disabled="isAuthor"
|
||||
:count="comment.shoutedCount"
|
||||
:is-shouted="comment.shoutedByCurrentUser"
|
||||
:node-id="comment.id"
|
||||
class="shout-button"
|
||||
node-type="Comment"
|
||||
/>
|
||||
<base-button
|
||||
:title="this.$t('post.comment.reply')"
|
||||
icon="level-down"
|
||||
class="reply-button"
|
||||
circle
|
||||
size="small"
|
||||
v-scroll-to="'.editor'"
|
||||
@click="reply"
|
||||
/>
|
||||
</div>
|
||||
</base-card>
|
||||
</template>
|
||||
|
||||
@ -59,6 +69,7 @@ import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import CommentForm from '~/components/CommentForm/CommentForm'
|
||||
import CommentMutations from '~/graphql/CommentMutations'
|
||||
import ShoutButton from '~/components/ShoutButton.vue'
|
||||
import scrollToAnchor from '~/mixins/scrollToAnchor.js'
|
||||
|
||||
export default {
|
||||
@ -67,6 +78,7 @@ export default {
|
||||
ContentMenu,
|
||||
ContentViewer,
|
||||
CommentForm,
|
||||
ShoutButton,
|
||||
},
|
||||
mixins: [scrollToAnchor],
|
||||
data() {
|
||||
@ -98,6 +110,11 @@ export default {
|
||||
hasLongContent() {
|
||||
return this.$filters.removeHtml(this.comment.content).length > COMMENT_MAX_UNTRUNCATED_LENGTH
|
||||
},
|
||||
isAuthor() {
|
||||
const { author } = this.comment
|
||||
if (!author) return false
|
||||
return this.$store.getters['auth/user'].id === author.id
|
||||
},
|
||||
isUnavailable() {
|
||||
return (this.comment.deleted || this.comment.disabled) && !this.isModerator
|
||||
},
|
||||
@ -192,19 +209,14 @@ export default {
|
||||
margin-bottom: $space-small;
|
||||
}
|
||||
|
||||
> .base-button {
|
||||
align-self: flex-end;
|
||||
.actions {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-button {
|
||||
float: right;
|
||||
top: 0px;
|
||||
}
|
||||
.reply-button:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@keyframes highlight {
|
||||
0% {
|
||||
border: $border-size-base solid $color-primary;
|
||||
@ -214,3 +226,17 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions {
|
||||
margin-top: $space-x-small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
gap: calc($space-base * 0.5);
|
||||
}
|
||||
|
||||
.shout-button {
|
||||
--circle-button-width: 28px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
</ds-chip>
|
||||
</div>
|
||||
<!-- group categories -->
|
||||
<div class="categories" v-if="categoriesActive">
|
||||
<div class="categories" v-if="categoriesActive && group.categories.length > 0">
|
||||
<category
|
||||
v-for="category in group.categories"
|
||||
:key="category.id"
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||
import ObserveButton from './ObserveButton.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('ObserveButton', () => {
|
||||
let mocks
|
||||
|
||||
const Wrapper = (count = 1, postId = '123', isObserved = true) => {
|
||||
return mount(ObserveButton, {
|
||||
mocks,
|
||||
return render(ObserveButton, {
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
},
|
||||
localVue,
|
||||
propsData: {
|
||||
count,
|
||||
@ -18,43 +18,39 @@ describe('ObserveButton', () => {
|
||||
})
|
||||
}
|
||||
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('observed', () => {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper(1, '123', true)
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('emits toggleObservePost with false when clicked', () => {
|
||||
const button = wrapper.find('.base-button')
|
||||
button.trigger('click')
|
||||
expect(wrapper.emitted('toggleObservePost')).toEqual([['123', false]])
|
||||
it('emits toggleObservePost with false when clicked', async () => {
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.emitted().toggleObservePost).toEqual([['123', false]])
|
||||
})
|
||||
})
|
||||
|
||||
describe('unobserved', () => {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper(1, '123', false)
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('emits toggleObservePost with true when clicked', () => {
|
||||
const button = wrapper.find('.base-button')
|
||||
button.trigger('click')
|
||||
expect(wrapper.emitted('toggleObservePost')).toEqual([['123', true]])
|
||||
it('emits toggleObservePost with true when clicked', async () => {
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.emitted().toggleObservePost).toEqual([['123', true]])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
<template>
|
||||
<ds-space margin="xx-small" class="text-align-center">
|
||||
<base-button :loading="loading" :filled="isObserved" icon="bell" circle @click="toggle" />
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-text color="soft" class="observe-button-text">
|
||||
<ds-heading style="display: inline" tag="h3">{{ count }}x</ds-heading>
|
||||
{{ $t('observeButton.observed') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<action-button
|
||||
:loading="false"
|
||||
:count="count"
|
||||
:text="$t('observeButton.observed')"
|
||||
:filled="isObserved"
|
||||
icon="bell"
|
||||
circle
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionButton from '~/components/ActionButton.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActionButton,
|
||||
},
|
||||
props: {
|
||||
count: { type: Number, default: 0 },
|
||||
postId: { type: String, default: null },
|
||||
isObserved: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.$emit('toggleObservePost', this.postId, !this.isObserved)
|
||||
@ -28,12 +29,3 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.observe-button-text {
|
||||
user-select: none;
|
||||
}
|
||||
.text-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
class="footer"
|
||||
v-observe-visibility="(isVisible, entry) => visibilityChanged(isVisible, entry, post.id)"
|
||||
>
|
||||
<div class="categories" v-if="categoriesActive">
|
||||
<div class="categories" v-if="categoriesActive && post.categories.length > 0">
|
||||
<category
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ShoutButton from './ShoutButton.vue'
|
||||
import { render, screen, fireEvent } from '@testing-library/vue'
|
||||
import '@testing-library/jest-dom'
|
||||
import Vue from 'vue'
|
||||
import ShoutButton from './ShoutButton.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -9,49 +10,54 @@ describe('ShoutButton.vue', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let wrapper
|
||||
const Wrapper = () => {
|
||||
return mount(ShoutButton, { mocks, localVue })
|
||||
}
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
const Wrapper = ({ isShouted = false } = {}) => {
|
||||
return render(ShoutButton, { mocks, localVue, propsData: { isShouted } })
|
||||
}
|
||||
|
||||
it('renders button and text', () => {
|
||||
expect(mocks.$t).toHaveBeenCalledWith('shoutButton.shouted')
|
||||
expect(wrapper.findAll('.base-button')).toHaveLength(1)
|
||||
expect(wrapper.findAll('.shout-button-text')).toHaveLength(1)
|
||||
expect(wrapper.vm.shouted).toBe(false)
|
||||
expect(wrapper.vm.shoutedCount).toBe(0)
|
||||
})
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('toggle the button', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue({ data: { shout: 'WeDoShout' } })
|
||||
wrapper.find('.base-button').trigger('click')
|
||||
expect(wrapper.vm.shouted).toBe(true)
|
||||
expect(wrapper.vm.shoutedCount).toBe(1)
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.vm.shouted).toBe(true)
|
||||
expect(wrapper.vm.shoutedCount).toBe(1)
|
||||
})
|
||||
it('renders button and text', () => {
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('toggle the button, but backend fails', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' })
|
||||
await wrapper.find('.base-button').trigger('click')
|
||||
expect(wrapper.vm.shouted).toBe(true)
|
||||
expect(wrapper.vm.shoutedCount).toBe(1)
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.vm.shouted).toBe(false)
|
||||
expect(wrapper.vm.shoutedCount).toBe(0)
|
||||
it('toggle the button', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue({ data: { shout: 'WeDoShout' } })
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
const shoutedCount = screen.getByText('1')
|
||||
expect(shoutedCount).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('toggle the button, but backend fails', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' })
|
||||
const button = screen.getByRole('button')
|
||||
await fireEvent.click(button)
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
let shoutedCount = screen.getByText('1')
|
||||
expect(shoutedCount).toBeInTheDocument()
|
||||
await Vue.nextTick()
|
||||
shoutedCount = screen.getByText('0')
|
||||
expect(shoutedCount).toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe('when shouted', () => {
|
||||
it('renders', () => {
|
||||
wrapper = Wrapper({ isShouted: true })
|
||||
expect(wrapper.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<ds-space margin="xx-small" class="text-align-center">
|
||||
<base-button
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:filled="shouted"
|
||||
icon="heart-o"
|
||||
circle
|
||||
@click="toggle"
|
||||
/>
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-text color="soft" class="shout-button-text">
|
||||
<ds-heading style="display: inline" tag="h3">{{ shoutedCount }}x</ds-heading>
|
||||
{{ $t('shoutButton.shouted') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<action-button
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:count="shoutedCount"
|
||||
:text="$t('shoutButton.shouted')"
|
||||
:filled="shouted"
|
||||
icon="heart-o"
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import ActionButton from '~/components/ActionButton.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActionButton,
|
||||
},
|
||||
props: {
|
||||
count: { type: Number, default: 0 },
|
||||
postId: { type: String, default: null },
|
||||
nodeType: { type: String },
|
||||
nodeId: { type: String, default: null },
|
||||
isShouted: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
@ -58,12 +58,13 @@ export default {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
${mutation}(id: $id, type: Post)
|
||||
mutation($id: ID!, $type: ShoutTypeEnum!) {
|
||||
${mutation}(id: $id, type: $type)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: this.postId,
|
||||
id: this.nodeId,
|
||||
type: this.nodeType,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
@ -82,12 +83,3 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.shout-button-text {
|
||||
user-select: none;
|
||||
}
|
||||
.text-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
60
webapp/components/__snapshots__/ActionButton.spec.js.snap
Normal file
60
webapp/components/__snapshots__/ActionButton.spec.js.snap
Normal file
@ -0,0 +1,60 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ActionButton.vue when disabled renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Click me"
|
||||
class="base-button --icon-only --circle"
|
||||
disabled="disabled"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
7
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ActionButton.vue when not disabled renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Click me"
|
||||
class="base-button --icon-only --circle"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
7
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -1,81 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ObserveButton observed renders 1`] = `
|
||||
<div
|
||||
class="ds-space text-align-center"
|
||||
style="margin-top: 4px; margin-bottom: 4px;"
|
||||
>
|
||||
<button
|
||||
class="base-button --icon-only --circle --filled"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 4px;"
|
||||
/>
|
||||
|
||||
<p
|
||||
class="ds-text observe-button-text ds-text-soft"
|
||||
circle=""
|
||||
class="action-button"
|
||||
>
|
||||
<h3
|
||||
class="ds-heading ds-heading-h3"
|
||||
style="display: inline;"
|
||||
<button
|
||||
aria-label="observeButton.observed"
|
||||
class="base-button --icon-only --circle --filled"
|
||||
type="button"
|
||||
>
|
||||
1x
|
||||
</h3>
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ObserveButton unobserved renders 1`] = `
|
||||
<div
|
||||
class="ds-space text-align-center"
|
||||
style="margin-top: 4px; margin-bottom: 4px;"
|
||||
>
|
||||
<button
|
||||
class="base-button --icon-only --circle"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 4px;"
|
||||
/>
|
||||
|
||||
<p
|
||||
class="ds-text observe-button-text ds-text-soft"
|
||||
circle=""
|
||||
class="action-button"
|
||||
>
|
||||
<h3
|
||||
class="ds-heading ds-heading-h3"
|
||||
style="display: inline;"
|
||||
<button
|
||||
aria-label="observeButton.observed"
|
||||
class="base-button --icon-only --circle"
|
||||
type="button"
|
||||
>
|
||||
1x
|
||||
</h3>
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
117
webapp/components/__snapshots__/ShoutButton.spec.js.snap
Normal file
117
webapp/components/__snapshots__/ShoutButton.spec.js.snap
Normal file
@ -0,0 +1,117 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ShoutButton.vue renders button and text 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="shoutButton.shouted"
|
||||
class="base-button --icon-only --circle"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ShoutButton.vue toggle the button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="shoutButton.shouted"
|
||||
class="base-button --icon-only --circle --filled"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ShoutButton.vue toggle the button, but backend fails 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="shoutButton.shouted"
|
||||
class="base-button --icon-only --circle --filled"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ShoutButton.vue when shouted renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="action-button"
|
||||
>
|
||||
<button
|
||||
aria-label="shoutButton.shouted"
|
||||
class="base-button --icon-only --circle --filled"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="base-icon"
|
||||
>
|
||||
<!---->
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="count"
|
||||
>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -112,7 +112,8 @@ export default {
|
||||
}
|
||||
|
||||
&.--circle {
|
||||
width: $size-button-base;
|
||||
width: var(--circle-button-width, $size-button-base);
|
||||
height: var(--circle-button-width, $size-button-base);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
}
|
||||
|
||||
&.--regular {
|
||||
height: 1.2em;
|
||||
height: var(--icon-size, 1.2em);
|
||||
}
|
||||
|
||||
&.--large {
|
||||
|
||||
@ -158,5 +158,7 @@ export const commentFragment = gql`
|
||||
contentExcerpt
|
||||
isPostObservedByMe
|
||||
postObservingUsersCount
|
||||
shoutedByCurrentUser
|
||||
shoutedCount
|
||||
}
|
||||
`
|
||||
|
||||
@ -131,7 +131,7 @@
|
||||
<ds-space margin="x-small" />
|
||||
</ds-space>
|
||||
<!-- group categories -->
|
||||
<template v-if="categoriesActive">
|
||||
<template v-if="categoriesActive && group && group.categories.length > 0">
|
||||
<hr />
|
||||
<ds-space margin-top="small" margin-bottom="small">
|
||||
<ds-text class="centered-text hyphenate-text" color="soft" size="small">
|
||||
|
||||
@ -50,7 +50,7 @@ describe('PostSlug', () => {
|
||||
})
|
||||
const propsData = {}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: jest.fn((t) => t),
|
||||
$filters: {
|
||||
truncate: (a) => a,
|
||||
removeHtml: (a) => a,
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
<!-- content -->
|
||||
<content-viewer class="content hyphenate-text" :content="post.content" />
|
||||
<!-- categories -->
|
||||
<div v-if="categoriesActive" class="categories">
|
||||
<div v-if="categoriesActive && post.categories.length > 0" class="categories">
|
||||
<ds-space margin="xx-large" />
|
||||
<ds-space margin="xx-small" />
|
||||
<hc-category
|
||||
@ -97,35 +97,23 @@
|
||||
<ds-space margin="xx-small" />
|
||||
<hc-hashtag v-for="tag in sortedTags" :key="tag.id" :id="tag.id" />
|
||||
</div>
|
||||
<ds-space margin-top="small">
|
||||
<ds-flex :gutter="{ lg: 'small' }">
|
||||
<!-- Shout Button -->
|
||||
<ds-flex-item
|
||||
:width="{ lg: '15%', md: '22%', sm: '22%', base: '100%' }"
|
||||
class="shout-button"
|
||||
>
|
||||
<hc-shout-button
|
||||
v-if="post.author"
|
||||
:disabled="isAuthor"
|
||||
:count="post.shoutedCount"
|
||||
:is-shouted="post.shoutedByCurrentUser"
|
||||
:post-id="post.id"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<!-- Follow Button -->
|
||||
<ds-flex-item
|
||||
:width="{ lg: '15%', md: '22%', sm: '22%', base: '100%' }"
|
||||
class="shout-button"
|
||||
>
|
||||
<observe-button
|
||||
:is-observed="post.isObservedByMe"
|
||||
:count="post.observingUsersCount"
|
||||
:post-id="post.id"
|
||||
@toggleObservePost="toggleObservePost"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
<div class="actions">
|
||||
<!-- Shout Button -->
|
||||
<shout-button
|
||||
:disabled="isAuthor"
|
||||
:count="post.shoutedCount"
|
||||
:is-shouted="post.shoutedByCurrentUser"
|
||||
:node-id="post.id"
|
||||
node-type="Post"
|
||||
/>
|
||||
<!-- Follow Button -->
|
||||
<observe-button
|
||||
:is-observed="post.isObservedByMe"
|
||||
:count="post.observingUsersCount"
|
||||
:post-id="post.id"
|
||||
@toggleObservePost="toggleObservePost"
|
||||
/>
|
||||
</div>
|
||||
<!-- Comments -->
|
||||
<ds-section>
|
||||
<comment-list
|
||||
@ -168,7 +156,7 @@ import CommentList from '~/components/CommentList/CommentList'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import DateTimeRange from '~/components/DateTimeRange/DateTimeRange'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||
import ShoutButton from '~/components/ShoutButton.vue'
|
||||
import ObserveButton from '~/components/ObserveButton.vue'
|
||||
import LocationTeaser from '~/components/LocationTeaser/LocationTeaser'
|
||||
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
|
||||
@ -198,7 +186,7 @@ export default {
|
||||
DateTimeRange,
|
||||
HcCategory,
|
||||
HcHashtag,
|
||||
HcShoutButton,
|
||||
ShoutButton,
|
||||
ObserveButton,
|
||||
LocationTeaser,
|
||||
PageParamsLink,
|
||||
@ -425,10 +413,15 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
.shout-button {
|
||||
float: left;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
gap: $space-small;
|
||||
margin-top: $space-small;
|
||||
margin-bottom: calc($space-base * 2);
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user