feat(backend): push posts (#8609)

* push posts

push posts

* unpush posts

* fix comment query

* locales

* fix locales

* fix tests

* Update webapp/locales/de.json

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>

* Update webapp/locales/de.json

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>

* Update webapp/locales/de.json

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>

* fix unpushedSuccessfully english message

* remove paremeters from unpushPost

* rename pushPostToTop -> pushPost, tests

* update locales & tests webapp

* fix lint

---------

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
This commit is contained in:
Ulf Gebhardt 2025-06-03 17:57:21 +02:00 committed by GitHub
parent d80ff05116
commit e87a33eb3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 688 additions and 84 deletions

View File

@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getDriver } from '@db/neo4j'
export const description = ''
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
MATCH (p:Post)
SET p.sortDate = p.createdAt
`)
await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}
export async function down(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
MATCH (p:Post)
REMOVE p.sortDate
`)
await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}

View File

@ -45,6 +45,7 @@ export default {
required: true,
default: () => new Date().toISOString(),
},
sortDate: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
language: { type: 'string', allow: [null] },
comments: {
type: 'relationship',

View File

@ -0,0 +1,12 @@
import gql from 'graphql-tag'
export const Post = gql`
query ($orderBy: [_PostOrdering]) {
Post(orderBy: $orderBy) {
id
pinned
createdAt
pinnedAt
}
}
`

View File

@ -0,0 +1,9 @@
import gql from 'graphql-tag'
export const pushPost = gql`
mutation pushPost($id: ID!) {
pushPost(id: $id) {
id
}
}
`

View File

@ -0,0 +1,9 @@
import gql from 'graphql-tag'
export const unpushPost = gql`
mutation unpushPost($id: ID!) {
unpushPost(id: $id) {
id
}
}
`

View File

@ -12,6 +12,9 @@ import Factory, { cleanDatabase } from '@db/factories'
import Image from '@db/models/Image'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { createPostMutation } from '@graphql/queries/createPostMutation'
import { Post } from '@graphql/queries/Post'
import { pushPost } from '@graphql/queries/pushPost'
import { unpushPost } from '@graphql/queries/unpushPost'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = true
@ -1009,6 +1012,281 @@ describe('UpdatePost', () => {
})
})
describe('push posts', () => {
let author
beforeEach(async () => {
author = await Factory.build('user', { slug: 'the-author' })
await Factory.build(
'post',
{
id: 'pFirst',
},
{
author,
categoryIds,
},
)
await Factory.build(
'post',
{
id: 'pSecond',
},
{
author,
categoryIds,
},
)
await Factory.build(
'post',
{
id: 'pThird',
},
{
author,
categoryIds,
},
)
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(
mutate({ mutation: pushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('ordinary users', () => {
it('throws authorization error', async () => {
await expect(
mutate({ mutation: pushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('moderators', () => {
let moderator
beforeEach(async () => {
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
authenticatedUser = await moderator.toJson()
})
it('throws authorization error', async () => {
await expect(
mutate({ mutation: pushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('admins', () => {
let admin
beforeEach(async () => {
admin = await Factory.build('user', {
id: 'admin',
role: 'admin',
})
authenticatedUser = await admin.toJson()
})
it('pushes the post to the front of the feed', async () => {
await expect(
query({ query: Post, variables: { orderBy: ['sortDate_desc'] } }),
).resolves.toMatchObject({
errors: undefined,
data: {
Post: [
{
id: 'pThird',
},
{
id: 'pSecond',
},
{
id: 'pFirst',
},
],
},
})
await expect(
mutate({ mutation: pushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: undefined,
data: {
pushPost: {
id: 'pSecond',
},
},
})
await expect(
query({ query: Post, variables: { orderBy: ['sortDate_desc'] } }),
).resolves.toMatchObject({
errors: undefined,
data: {
Post: [
{
id: 'pSecond',
},
{
id: 'pThird',
},
{
id: 'pFirst',
},
],
},
})
})
})
})
describe('unpush posts', () => {
let author
let admin
beforeEach(async () => {
author = await Factory.build('user', { slug: 'the-author' })
await Factory.build(
'post',
{
id: 'pFirst',
},
{
author,
categoryIds,
},
)
await Factory.build(
'post',
{
id: 'pSecond',
},
{
author,
categoryIds,
},
)
await Factory.build(
'post',
{
id: 'pThird',
},
{
author,
categoryIds,
},
)
admin = await Factory.build('user', {
id: 'admin',
role: 'admin',
})
authenticatedUser = await admin.toJson()
await mutate({ mutation: pushPost, variables: { id: 'pSecond' } })
authenticatedUser = null
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(
mutate({ mutation: unpushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('ordinary users', () => {
it('throws authorization error', async () => {
authenticatedUser = await user.toJson()
await expect(
mutate({ mutation: unpushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('moderators', () => {
let moderator
beforeEach(async () => {
moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() })
authenticatedUser = await moderator.toJson()
})
it('throws authorization error', async () => {
await expect(
mutate({ mutation: unpushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: null,
})
})
})
describe('admins', () => {
it('cancels the push of the post and puts it in the original order', async () => {
authenticatedUser = await admin.toJson()
await expect(
query({ query: Post, variables: { orderBy: ['sortDate_desc'] } }),
).resolves.toMatchObject({
errors: undefined,
data: {
Post: [
{
id: 'pSecond',
},
{
id: 'pThird',
},
{
id: 'pFirst',
},
],
},
})
await expect(
mutate({ mutation: unpushPost, variables: { id: 'pSecond' } }),
).resolves.toMatchObject({
errors: undefined,
data: {
unpushPost: {
id: 'pSecond',
},
},
})
await expect(
query({ query: Post, variables: { orderBy: ['sortDate_desc'] } }),
).resolves.toMatchObject({
errors: undefined,
data: {
Post: [
{
id: 'pThird',
},
{
id: 'pSecond',
},
{
id: 'pFirst',
},
],
},
})
})
})
})
describe('pin posts', () => {
let author
const pinPostMutation = gql`
@ -1097,17 +1375,6 @@ describe('pin posts', () => {
authenticatedUser = await admin.toJson()
})
const postOrderingQuery = gql`
query ($orderBy: [_PostOrdering]) {
Post(orderBy: $orderBy) {
id
pinned
createdAt
pinnedAt
}
}
`
describe('MAX_PINNED_POSTS is 0', () => {
beforeEach(async () => {
CONFIG.MAX_PINNED_POSTS = 0
@ -1451,7 +1718,7 @@ describe('pin posts', () => {
})
it('pinned post appear first even when created before other posts', async () => {
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject({
await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: {
Post: [
{
@ -1658,45 +1925,43 @@ describe('pin posts', () => {
})
it('places the pinned posts first, though they are much older', async () => {
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
{
data: {
Post: [
{
id: 'first-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2019-10-22T17:26:29.070Z',
},
{
id: 'second-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2018-10-22T17:26:29.070Z',
},
{
id: 'third-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2017-10-22T17:26:29.070Z',
},
{
id: 'another-post',
pinned: null,
pinnedAt: null,
createdAt: expect.any(String),
},
{
id: 'p9876',
pinned: null,
pinnedAt: null,
createdAt: expect.any(String),
},
],
},
errors: undefined,
await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: {
Post: [
{
id: 'first-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2019-10-22T17:26:29.070Z',
},
{
id: 'second-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2018-10-22T17:26:29.070Z',
},
{
id: 'third-post',
pinned: true,
pinnedAt: expect.any(String),
createdAt: '2017-10-22T17:26:29.070Z',
},
{
id: 'another-post',
pinned: null,
pinnedAt: null,
createdAt: expect.any(String),
},
{
id: 'p9876',
pinned: null,
pinnedAt: null,
createdAt: expect.any(String),
},
],
},
)
errors: undefined,
})
})
})
})

View File

@ -158,6 +158,7 @@ export default {
SET post += $params
SET post.createdAt = toString(datetime())
SET post.updatedAt = toString(datetime())
SET post.sortDate = toString(datetime())
SET post.clickedCount = 0
SET post.viewedTeaserCount = 0
SET post:${params.postType}
@ -493,6 +494,40 @@ export default {
session.close()
}
},
pushPost: async (_parent, params, context: Context, _resolveInfo) => {
const posts = (
await context.database.write({
query: `
MATCH (post:Post {id: $id})
SET post.sortDate = toString(datetime())
RETURN post {.*}`,
variables: params,
})
).records.map((record) => record.get('post'))
if (posts.length !== 1) {
throw new Error('Could not find Post')
}
return posts[0]
},
unpushPost: async (_parent, params, context: Context, _resolveInfo) => {
const posts = (
await context.database.write({
query: `
MATCH (post:Post {id: $id})
SET post.sortDate = post.createdAt
RETURN post {.*}`,
variables: params,
})
).records.map((record) => record.get('post'))
if (posts.length !== 1) {
throw new Error('Could not find Post')
}
return posts[0]
},
},
Post: {
...Resolver('Post', {

View File

@ -103,6 +103,8 @@ enum _PostOrdering {
createdAt_desc
updatedAt_asc
updatedAt_desc
sortDate_asc
sortDate_desc
language_asc
language_desc
pinned_asc
@ -128,6 +130,7 @@ type Post {
pinned: Boolean
createdAt: String
updatedAt: String
sortDate: String
language: String
pinnedAt: String @cypher(
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
@ -241,6 +244,8 @@ type Mutation {
pinPost(id: ID!): Post
unpinPost(id: ID!): Post
markTeaserAsViewed(id: ID!): Post
pushPost(id: ID!): Post!
unpushPost(id: ID!): Post!
# Shout the given Type and ID
shout(id: ID!, type: ShoutTypeEnum!): Boolean!

View File

@ -9,7 +9,7 @@ const defaultOrderBy = (resolve, root, args, context, resolveInfo) => {
const newestFirst = {
kind: 'Argument',
name: { kind: 'Name', value: 'orderBy' },
value: { kind: 'EnumValue', value: 'createdAt_desc' },
value: { kind: 'EnumValue', value: 'sortDate_desc' },
}
const [fieldNode] = copy.fieldNodes
if (fieldNode) fieldNode.arguments.push(newestFirst)

View File

@ -484,6 +484,8 @@ export default shield(
VerifyEmailAddress: isAuthenticated,
pinPost: isAdmin,
unpinPost: isAdmin,
pushPost: isAdmin,
unpushPost: isAdmin,
UpdateDonations: isAdmin,
// InviteCode

View File

@ -99,6 +99,82 @@ describe('ContentMenu.vue', () => {
})
describe('admin can', () => {
it('push post', async () => {
getters['auth/isAdmin'] = () => true
const wrapper = await openContentMenu({
isOwner: false,
resourceType: 'contribution',
resource: {
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
sortDate: 'some-date',
createdAt: 'some-date',
},
})
expect(
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.push'),
).toHaveLength(1)
wrapper
.findAll('.ds-menu-item')
.filter((item) => item.text() === 'post.menu.push')
.at(0)
.trigger('click')
expect(wrapper.emitted('pushPost')).toEqual([
[
{
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
sortDate: 'some-date',
createdAt: 'some-date',
},
],
])
})
it('not unpush post which was not pushed', async () => {
getters['auth/isAdmin'] = () => true
const wrapper = await openContentMenu({
isOwner: false,
resourceType: 'contribution',
resource: {
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
sortDate: 'some-date',
createdAt: 'some-date',
},
})
expect(
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
).toHaveLength(0)
})
it('unpush post which was pushed', async () => {
getters['auth/isAdmin'] = () => true
const wrapper = await openContentMenu({
isOwner: false,
resourceType: 'contribution',
resource: {
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
sortDate: 'some-date',
createdAt: 'some-other-date',
},
})
expect(
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
).toHaveLength(1)
wrapper
.findAll('.ds-menu-item')
.filter((item) => item.text() === 'post.menu.unpush')
.at(0)
.trigger('click')
expect(wrapper.emitted('unpushPost')).toEqual([
[
{
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
sortDate: 'some-date',
createdAt: 'some-other-date',
},
],
])
})
describe('when maxPinnedPosts = 0', () => {
beforeEach(() => {
maxPinnedPostsMock.mockReturnValue(0)

View File

@ -104,6 +104,26 @@ export default {
}
}
if (this.isAdmin) {
routes.push({
label: this.$t(`post.menu.push`),
callback: () => {
this.$emit('pushPost', this.resource)
},
icon: 'link',
})
}
if (this.isAdmin && this.resource.sortDate !== this.resource.createdAt) {
routes.push({
label: this.$t(`post.menu.unpush`),
callback: () => {
this.$emit('unpushPost', this.resource)
},
icon: 'link',
})
}
if (this.resource.isObservedByMe) {
routes.push({
label: this.$t(`post.menu.unobserve`),

View File

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

View File

@ -13,7 +13,7 @@ describe('OrderByFilter', () => {
const getters = {
'posts/filteredPostTypes': () => [],
'posts/orderedByCreationDate': () => true,
'posts/orderBy': () => 'createdAt_desc',
'posts/orderBy': () => 'sortDate_desc',
}
const actions = {
'categories/init': jest.fn(),
@ -54,7 +54,7 @@ describe('OrderByFilter', () => {
describe('if ordered by oldest', () => {
beforeEach(() => {
getters['posts/orderBy'] = jest.fn(() => 'createdAt_asc')
getters['posts/orderBy'] = jest.fn(() => 'sortDate_asc')
wrapper = Wrapper()
})
@ -76,20 +76,20 @@ describe('OrderByFilter', () => {
})
describe('click "newest-button"', () => {
it('calls TOGGLE_ORDER with "createdAt_desc"', () => {
it('calls TOGGLE_ORDER with "sortDate_desc"', () => {
wrapper
.find('.order-by-filter .filter-list .base-button[data-test="newest-button"]')
.trigger('click')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'createdAt_desc')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'sortDate_desc')
})
})
describe('click "oldest-button"', () => {
it('calls TOGGLE_ORDER with "createdAt_asc"', () => {
it('calls TOGGLE_ORDER with "sortDate_asc"', () => {
wrapper
.find('.order-by-filter .filter-list .base-button[data-test="oldest-button"]')
.trigger('click')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'createdAt_asc')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'sortDate_asc')
})
})
})

View File

@ -49,10 +49,10 @@ export default {
return !this.filteredPostTypes.includes('Event')
},
orderedAsc() {
return this.orderedByCreationDate ? 'createdAt_asc' : 'eventStart_desc'
return this.orderedByCreationDate ? 'sortDate_asc' : 'eventStart_desc'
},
orderedDesc() {
return this.orderedByCreationDate ? 'createdAt_desc' : 'eventStart_asc'
return this.orderedByCreationDate ? 'sortDate_desc' : 'eventStart_asc'
},
sectionTitle() {
return this.orderedByCreationDate

View File

@ -109,6 +109,8 @@
:is-owner="isAuthor"
@pinPost="pinPost"
@unpinPost="unpinPost"
@pushPost="pushPost"
@unpushPost="unpushPost"
@toggleObservePost="toggleObservePost"
/>
</client-only>
@ -222,6 +224,12 @@ export default {
unpinPost(post) {
this.$emit('unpinPost', post)
},
pushPost(post) {
this.$emit('pushPost', post)
},
unpushPost(post) {
this.$emit('unpushPost', post)
},
toggleObservePost(postId, value) {
this.$emit('toggleObservePost', postId, value)
},

View File

@ -48,6 +48,8 @@
@removePostFromList="posts = removePostFromList(post, posts)"
@pinPost="pinPost(post, refetchPostList)"
@unpinPost="unpinPost(post, refetchPostList)"
@pushPost="pushPost(post, refetchPostList)"
@unpushPost="unpushPost(post, refetchPostList)"
@toggleObservePost="
(postId, value) => toggleObservePost(postId, value, refetchPostList)
"

View File

@ -73,6 +73,7 @@ export const postFragment = gql`
contentExcerpt
createdAt
updatedAt
sortDate
disabled
deleted
slug

View File

@ -168,6 +168,40 @@ export default () => {
}
}
`,
pushPost: gql`
mutation ($id: ID!) {
pushPost(id: $id) {
id
title
slug
content
contentExcerpt
language
pinnedBy {
id
name
role
}
}
}
`,
unpushPost: gql`
mutation ($id: ID!) {
unpushPost(id: $id) {
id
title
slug
content
contentExcerpt
language
pinnedBy {
id
name
role
}
}
}
`,
markTeaserAsViewed: gql`
mutation ($id: ID!) {
markTeaserAsViewed(id: $id) {

View File

@ -856,10 +856,14 @@
"observedSuccessfully": "Du beobachtest diesen Beitrag!",
"pin": "Beitrag anheften",
"pinnedSuccessfully": "Beitrag erfolgreich angeheftet!",
"push": "Beitrag hochschieben",
"pushedSuccessfully": "Beitrag erfolgreich nach oben geschoben!",
"unobserve": "Beitrag nicht mehr beobachten",
"unobservedSuccessfully": "Du beobachtest diesen Beitrag nicht mehr!",
"unpin": "Beitrag loslösen",
"unpinnedSuccessfully": "Angehefteten Beitrag erfolgreich losgelöst!"
"unpinnedSuccessfully": "Angehefteten Beitrag erfolgreich losgelöst!",
"unpush": "Beitrag hochschieben aufheben",
"unpushedSuccessfully": "Hochschieben des Beitrags erfolgreich rückgängig gemacht!"
},
"name": "Beitrag",
"pinned": "Meldung",

View File

@ -856,10 +856,14 @@
"observedSuccessfully": "You are now observing this post!",
"pin": "Pin post",
"pinnedSuccessfully": "Post pinned successfully!",
"push": "Push to top",
"pushedSuccessfully": "Post pushed to top successfully!",
"unobserve": "Stop to observe post",
"unobservedSuccessfully": "You are no longer observing this post!",
"unpin": "Unpin post",
"unpinnedSuccessfully": "Post unpinned successfully!"
"unpinnedSuccessfully": "Post unpinned successfully!",
"unpush": "Cancel push",
"unpushedSuccessfully": "Post push has been canceled!"
},
"name": "Article",
"pinned": "Announcement",

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": "Anclar contribución",
"pinnedSuccessfully": "¡Contribución anclado con éxito!",
"push": null,
"pushedSuccessfully": null,
"unobserve": "Dejar de observar contribución",
"unobservedSuccessfully": null,
"unpin": "Desanclar contribución",
"unpinnedSuccessfully": "¡Contribución desanclado con éxito!"
"unpinnedSuccessfully": "¡Contribución desanclado con éxito!",
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Contribución",
"pinned": "Anuncio",

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": "Épingler le Post",
"pinnedSuccessfully": "Poste épinglé avec succès!",
"push": null,
"pushedSuccessfully": null,
"unobserve": "Ne plus observer le Post",
"unobservedSuccessfully": null,
"unpin": "Retirer l'épingle du poste",
"unpinnedSuccessfully": "Épingle retirer du Post avec succès!"
"unpinnedSuccessfully": "Épingle retirer du Post avec succès!",
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Post",
"pinned": "Annonce",

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": null,
"pinnedSuccessfully": null,
"push": null,
"pushedSuccessfully": null,
"unobserve": null,
"unobservedSuccessfully": null,
"unpin": null,
"unpinnedSuccessfully": null
"unpinnedSuccessfully": null,
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Messaggio",
"pinned": null,

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": null,
"pinnedSuccessfully": null,
"push": null,
"pushedSuccessfully": null,
"unobserve": null,
"unobservedSuccessfully": null,
"unpin": null,
"unpinnedSuccessfully": null
"unpinnedSuccessfully": null,
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Post",
"pinned": null,

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": null,
"pinnedSuccessfully": null,
"push": null,
"pushedSuccessfully": null,
"unobserve": null,
"unobservedSuccessfully": null,
"unpin": null,
"unpinnedSuccessfully": null
"unpinnedSuccessfully": null,
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Poczta",
"pinned": null,

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": "Fixar publicação",
"pinnedSuccessfully": "Publicação fixada com sucesso!",
"push": null,
"pushedSuccessfully": null,
"unobserve": "Deixar de observar publicação",
"unobservedSuccessfully": null,
"unpin": "Desafixar publicação",
"unpinnedSuccessfully": "Publicação desafixada com sucesso!"
"unpinnedSuccessfully": "Publicação desafixada com sucesso!",
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Postar",
"pinned": "Anúncio",

View File

@ -856,10 +856,14 @@
"observedSuccessfully": null,
"pin": "Закрепить пост",
"pinnedSuccessfully": "Пост больше не закреплен!",
"push": null,
"pushedSuccessfully": null,
"unobserve": null,
"unobservedSuccessfully": null,
"unpin": "Открепить пост",
"unpinnedSuccessfully": "Пост успешно не закреплено!"
"unpinnedSuccessfully": "Пост успешно не закреплено!",
"unpush": null,
"unpushedSuccessfully": null
},
"name": "Пост",
"pinned": "Объявление",

View File

@ -38,6 +38,34 @@ export default {
})
.catch((error) => this.$toast.error(error.message))
},
pushPost(post, refetchPostList = () => {}) {
this.$apollo
.mutate({
mutation: PostMutations().pushPost,
variables: {
id: post.id,
},
})
.then(() => {
this.$toast.success(this.$t('post.menu.pushedSuccessfully'))
refetchPostList()
})
.catch((error) => this.$toast.error(error.message))
},
unpushPost(post, refetchPostList = () => {}) {
this.$apollo
.mutate({
mutation: PostMutations().unpushPost,
variables: {
id: post.id,
},
})
.then(() => {
this.$toast.success(this.$t('post.menu.unpushedSuccessfully'))
refetchPostList()
})
.catch((error) => this.$toast.error(error.message))
},
toggleObservePost(postId, value, refetchPostList = () => {}) {
this.$apollo
.mutate({

View File

@ -267,6 +267,8 @@
@removePostFromList="posts = removePostFromList(post, posts)"
@pinPost="pinPost(post, refetchPostList)"
@unpinPost="unpinPost(post, refetchPostList)"
@pushPost="pushPost(post, refetchPostList)"
@unpushPost="unpushPost(post, refetchPostList)"
@toggleObservePost="
(postId, value) => toggleObservePost(postId, value, refetchPostList)
"
@ -493,7 +495,7 @@ export default {
offset: this.offset,
filter: this.filter,
first: this.pageSize,
orderBy: 'createdAt_desc',
orderBy: 'sortDate_desc',
},
updateQuery: UpdateQuery(this, { $state, pageKey: 'profilePagePosts' }),
})
@ -602,7 +604,7 @@ export default {
filter: this.filter,
first: this.pageSize,
offset: 0,
orderBy: 'createdAt_desc',
orderBy: 'sortDate_desc',
}
},
update({ profilePagePosts }) {

View File

@ -32,7 +32,7 @@ describe('PostIndex', () => {
'posts/articleSetInPostTypeFilter': () => false,
'posts/eventSetInPostTypeFilter': () => false,
'posts/eventsEnded': () => '',
'posts/orderBy': () => 'createdAt_desc',
'posts/orderBy': () => 'sortDate_desc',
'auth/user': () => {
return { id: 'u23' }
},

View File

@ -116,6 +116,8 @@
@removePostFromList="posts = removePostFromList(post, posts)"
@pinPost="pinPost(post, refetchPostList)"
@unpinPost="unpinPost(post, refetchPostList)"
@pushPost="pushPost(post, refetchPostList)"
@unpushPost="unpushPost(post, refetchPostList)"
@toggleObservePost="
(postId, value) => toggleObservePost(postId, value, refetchPostList)
"

View File

@ -49,6 +49,8 @@
:is-owner="isAuthor"
@pinPost="pinPost"
@unpinPost="unpinPost"
@pushPost="pushPost"
@unpushPost="unpushPost"
@toggleObservePost="toggleObservePost"
/>
</client-only>

View File

@ -159,6 +159,8 @@
@removePostFromList="posts = removePostFromList(post, posts)"
@pinPost="pinPost(post, refetchPostList)"
@unpinPost="unpinPost(post, refetchPostList)"
@pushPost="pushPost(post, refetchPostList)"
@unpushPost="unpushPost(post, refetchPostList)"
@toggleObservePost="
(postId, value) => toggleObservePost(postId, value, refetchPostList)
"
@ -329,7 +331,7 @@ export default {
offset: this.offset,
filter: this.filter,
first: this.pageSize,
orderBy: 'createdAt_desc',
orderBy: 'sortDate_desc',
},
updateQuery: UpdateQuery(this, { $state, pageKey: 'profilePagePosts' }),
})
@ -433,7 +435,7 @@ export default {
filter: this.filter,
first: this.pageSize,
offset: 0,
orderBy: 'createdAt_desc',
orderBy: 'sortDate_desc',
}
},
update({ profilePagePosts }) {

View File

@ -12,7 +12,7 @@ export const state = () => {
filter: {
...defaultFilter,
},
order: 'createdAt_desc',
order: 'sortDate_desc',
}
}
@ -89,7 +89,7 @@ export const mutations = {
const filter = clone(state.filter)
delete filter.eventStart_gte
delete filter.postType_in
state.order = 'createdAt_desc'
state.order = 'sortDate_desc'
state.filter = filter
},
TOGGLE_POST_TYPE(state, postType) {
@ -101,12 +101,12 @@ export const mutations = {
state.order = 'eventStart_asc'
} else {
delete filter.eventStart_gte
state.order = 'createdAt_desc'
state.order = 'sortDate_desc'
}
} else {
delete filter.eventStart_gte
delete filter.postType_in
state.order = 'createdAt_desc'
state.order = 'sortDate_desc'
}
state.filter = filter
},

View File

@ -107,9 +107,9 @@ describe('getters', () => {
describe('orderBy', () => {
it('returns value for graphql query', () => {
state = {
order: 'createdAt_desc',
order: 'sortDate_desc',
}
expect(getters.orderBy(state)).toEqual('createdAt_desc')
expect(getters.orderBy(state)).toEqual('sortDate_desc')
})
})
})
@ -270,7 +270,7 @@ describe('mutations', () => {
order: 'eventStart_asc',
}
expect(testMutation('Article')).toEqual({ postType_in: ['Article'] })
expect(getters.orderBy(state)).toEqual('createdAt_desc')
expect(getters.orderBy(state)).toEqual('sortDate_desc')
})
it('removes post type filter if same post type is present and sets order', () => {
@ -282,7 +282,7 @@ describe('mutations', () => {
order: 'eventStart_asc',
}
expect(testMutation('Event')).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
expect(getters.orderBy(state)).toEqual('sortDate_desc')
})
it('removes post type filter if called with null', () => {
@ -294,7 +294,7 @@ describe('mutations', () => {
order: 'eventStart_asc',
}
expect(testMutation(null)).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
expect(getters.orderBy(state)).toEqual('sortDate_desc')
})
it('does not get in the way of other filters', () => {
@ -325,7 +325,7 @@ describe('mutations', () => {
order: 'eventStart_asc',
}
expect(testMutation()).toEqual({})
expect(getters.orderBy(state)).toEqual('createdAt_desc')
expect(getters.orderBy(state)).toEqual('sortDate_desc')
})
})