mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into ts-base
This commit is contained in:
commit
a05ab40722
@ -0,0 +1,53 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
export const description = 'Add postType property Article to all posts'
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
SET post.postType = 'Article'
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
REMOVE post.postType
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} 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 {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -596,7 +596,79 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
}),
|
||||
])
|
||||
|
||||
// Create Posts
|
||||
// Create Events (by peter lustig)
|
||||
authenticatedUser = await peterLustig.toJson()
|
||||
const now = new Date()
|
||||
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: createPostMutation(),
|
||||
variables: {
|
||||
id: 'e0',
|
||||
title: 'Illegaler Kindergeburtstag',
|
||||
content: 'Elli hat nächste Woche Geburtstag. Wir feiern das!',
|
||||
categoryIds: ['cat4'],
|
||||
postType: 'Event',
|
||||
eventInput: {
|
||||
eventStart: new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() + 7,
|
||||
).toISOString(),
|
||||
eventVenue: 'Ellis Kinderzimmer',
|
||||
eventLocationName: 'Deutschland',
|
||||
},
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: createPostMutation(),
|
||||
variables: {
|
||||
id: 'e1',
|
||||
title: 'Wir Schützen den Stuttgarter Schlossgarten',
|
||||
content: 'Kein Baum wird gefällt werden!',
|
||||
categoryIds: ['cat5'],
|
||||
postType: 'Event',
|
||||
eventInput: {
|
||||
eventStart: new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() + 1,
|
||||
).toISOString(),
|
||||
eventVenue: 'Schlossgarten',
|
||||
eventLocationName: 'Stuttgart',
|
||||
},
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: createPostMutation(),
|
||||
variables: {
|
||||
id: 'e2',
|
||||
title: 'IT 4 Change Treffen',
|
||||
content: 'Wir sitzen eine Woche zusammen rum und glotzen uns blöde an.',
|
||||
categoryIds: ['cat5'],
|
||||
postType: 'Event',
|
||||
eventInput: {
|
||||
eventStart: new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate() + 1,
|
||||
).toISOString(),
|
||||
eventEnd: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 4).toISOString(),
|
||||
eventVenue: 'Ferienlager',
|
||||
eventLocationName: 'Bahra, Sachsen',
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
let passedEvent = await neode.find('Post', 'e1')
|
||||
await passedEvent.update({ eventStart: new Date(2010, 8, 30, 10).toISOString() })
|
||||
passedEvent = await neode.find('Post', 'e2')
|
||||
await passedEvent.update({
|
||||
eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() - 3).toISOString(),
|
||||
})
|
||||
|
||||
// Create Posts (Articles)
|
||||
|
||||
const [p0, p1, p3, p4, p5, p6, p9, p10, p11, p13, p14, p15] = await Promise.all([
|
||||
Factory.build(
|
||||
|
||||
@ -238,7 +238,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
|
||||
WITH resource, user, notification, authors, posts,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group'])[0], author: authors[0], post: posts[0]} AS finalResource
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
|
||||
@ -64,7 +64,7 @@ const validateReview = async (resolve, root, args, context, info) => {
|
||||
WHERE resource:User OR resource:Post OR resource:Comment
|
||||
OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource)
|
||||
OPTIONAL MATCH (resource)<-[:WROTE]-(author:User)
|
||||
RETURN labels(resource)[0] AS label, author, filed
|
||||
RETURN filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User'])[0] AS label, author, filed
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
|
||||
@ -57,4 +57,5 @@ export default {
|
||||
},
|
||||
},
|
||||
pinned: { type: 'boolean', default: null, valid: [null, true] },
|
||||
postType: { type: 'string', default: 'Article', valid: ['Article', 'Event'] },
|
||||
}
|
||||
|
||||
@ -1,29 +1,32 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
export const validateEventParams = (params) => {
|
||||
let locationName = null
|
||||
if (params.postType && params.postType === 'Event') {
|
||||
const { eventInput } = params
|
||||
validateEventDate(eventInput.eventStart)
|
||||
params.eventStart = eventInput.eventStart
|
||||
|
||||
if (eventInput.eventEnd) {
|
||||
validateEventEnd(eventInput.eventStart, eventInput.eventEnd)
|
||||
params.eventEnd = eventInput.eventEnd
|
||||
} else {
|
||||
params.eventEnd = null
|
||||
}
|
||||
|
||||
if (eventInput.eventLocationName && !eventInput.eventVenue) {
|
||||
throw new UserInputError('Event venue must be present if event location is given!')
|
||||
}
|
||||
params.eventVenue = eventInput.eventVenue
|
||||
params.eventLocationName = eventInput.eventLocationName
|
||||
params.eventLocationName = eventInput.eventLocationName && eventInput.eventLocationName.trim()
|
||||
if (params.eventLocationName) {
|
||||
locationName = params.eventLocationName
|
||||
} else {
|
||||
params.eventLocationName = null
|
||||
}
|
||||
params.eventIsOnline = !!eventInput.eventIsOnline
|
||||
}
|
||||
delete params.eventInput
|
||||
let locationName
|
||||
if (params.eventLocationName) {
|
||||
locationName = params.eventLocationName
|
||||
} else {
|
||||
params.eventLocationName = null
|
||||
locationName = null
|
||||
}
|
||||
return locationName
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ export default {
|
||||
SET report.updatedAt = $dateTime, report.disable = review.disable, report.closed = $params.closed
|
||||
SET resource.disabled = report.disable
|
||||
|
||||
WITH review, report, resource {.*, __typename: labels(resource)[0]} AS finalResource
|
||||
WITH review, report, resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User'])[0]} AS finalResource
|
||||
RETURN review {.*, report: properties(report), resource: properties(finalResource)}
|
||||
`
|
||||
const reviewWriteTxResultPromise = session.writeTransaction(async (txc) => {
|
||||
|
||||
@ -54,7 +54,7 @@ export default {
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts
|
||||
WITH resource, user, notification, authors, posts, relatedUser, membership,
|
||||
resource {.*,
|
||||
__typename: labels(resource)[0],
|
||||
__typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group'])[0],
|
||||
author: authors[0],
|
||||
post: posts[0],
|
||||
myRole: membership.role } AS finalResource
|
||||
@ -93,7 +93,7 @@ export default {
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts
|
||||
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user)
|
||||
WITH resource, user, notification, authors, posts, membership,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role } AS finalResource
|
||||
resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group'])[0], author: authors[0], post: posts[0], myRole: membership.role } AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
`,
|
||||
{ resourceId: args.id, id: currentUser.id },
|
||||
@ -123,7 +123,7 @@ export default {
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts
|
||||
OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user)
|
||||
WITH resource, user, notification, authors, posts, membership,
|
||||
resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role} AS finalResource
|
||||
resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group'])[0], author: authors[0], post: posts[0], myRole: membership.role} AS finalResource
|
||||
RETURN notification {.*, from: finalResource, to: properties(user)}
|
||||
`,
|
||||
{ id: currentUser.id },
|
||||
|
||||
@ -21,12 +21,22 @@ const maintainPinnedPosts = (params) => {
|
||||
return params
|
||||
}
|
||||
|
||||
const filterEventDates = (params) => {
|
||||
if (params.filter?.eventStart_gte) {
|
||||
const date = params.filter.eventStart_gte
|
||||
delete params.filter.eventStart_gte
|
||||
params.filter = { ...params.filter, OR: [{ eventStart_gte: date }, { eventEnd_gte: date }] }
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: async (object, params, context, resolveInfo) => {
|
||||
params = await filterPostsOfMyGroups(params, context)
|
||||
params = await filterInvisiblePosts(params, context)
|
||||
params = await filterForMutedUsers(params, context)
|
||||
params = filterEventDates(params)
|
||||
params = await maintainPinnedPosts(params)
|
||||
return neo4jgraphql(object, params, context, resolveInfo)
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@ export default {
|
||||
WITH submitter, resource, report
|
||||
CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
|
||||
|
||||
WITH filed, report, resource {.*, __typename: labels(resource)[0]} AS finalResource
|
||||
WITH filed, report, resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User'])[0]} AS finalResource
|
||||
RETURN filed {.*, reportId: report.id, resource: properties(finalResource)} AS filedReport
|
||||
`,
|
||||
{
|
||||
@ -93,7 +93,7 @@ export default {
|
||||
[(moderator:User)-[reviewed:REVIEWED]->(report) | reviewed {.*, moderator: properties(moderator)} ] as reviewed,
|
||||
[(resource)<-[:WROTE]-(author:User) | author {.*} ] as optionalAuthors,
|
||||
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] as optionalCommentedPosts,
|
||||
resource {.*, __typename: labels(resource)[0] } as resourceWithType
|
||||
resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'User'])[0] } as resourceWithType
|
||||
WITH report, optionalAuthors, optionalCommentedPosts, reviewed, filed,
|
||||
resourceWithType {.*, post: optionalCommentedPosts[0], author: optionalAuthors[0] } as finalResource
|
||||
RETURN report {.*, resource: finalResource, filed: filed, reviewed: reviewed }
|
||||
|
||||
@ -38,7 +38,7 @@ const searchPostsSetup = {
|
||||
[(resource)<-[:SHOUTED]-(user:User) | user] AS shouter`,
|
||||
returnClause: `resource {
|
||||
.*,
|
||||
__typename: labels(resource)[0],
|
||||
__typename: 'Post',
|
||||
author: properties(author),
|
||||
commentsCount: toString(size(comments)),
|
||||
shoutedCount: toString(size(shouter)),
|
||||
@ -53,7 +53,7 @@ const searchUsersSetup = {
|
||||
match: 'MATCH (resource:User)',
|
||||
whereClause: simpleWhereClause,
|
||||
withClause: '',
|
||||
returnClause: 'resource {.*, __typename: labels(resource)[0]}',
|
||||
returnClause: `resource {.*, __typename: 'User'}`,
|
||||
limit: 'LIMIT $limit',
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ const searchHashtagsSetup = {
|
||||
match: 'MATCH (resource:Tag)',
|
||||
whereClause: simpleWhereClause,
|
||||
withClause: '',
|
||||
returnClause: 'resource {.*, __typename: labels(resource)[0]}',
|
||||
returnClause: `resource {.*, __typename: 'Tag'}`,
|
||||
limit: 'LIMIT $limit',
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ const searchGroupsSetup = {
|
||||
AND (resource.groupType IN ['public', 'closed']
|
||||
OR membership.role IN ['usual', 'admin', 'owner'])`,
|
||||
withClause: 'WITH resource, membership',
|
||||
returnClause: 'resource { .*, myRole: membership.role, __typename: labels(resource)[0] }',
|
||||
returnClause: `resource { .*, myRole: membership.role, __typename: 'Group' }`,
|
||||
limit: 'LIMIT $limit',
|
||||
}
|
||||
|
||||
|
||||
@ -85,6 +85,7 @@ input _PostFilter {
|
||||
postsInMyGroups: Boolean
|
||||
postType_in: [PostType]
|
||||
eventStart_gte: String
|
||||
eventEnd_gte: String
|
||||
}
|
||||
|
||||
enum _PostOrdering {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionForm from './ContributionForm.vue'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
import Vuex from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations.js'
|
||||
|
||||
import ImageUploader from '~/components/Uploader/ImageUploader'
|
||||
import MutationObserver from 'mutation-observer'
|
||||
@ -108,6 +108,10 @@ describe('ContributionForm.vue', () => {
|
||||
await wrapper.vm.updateEditorContent(postContent)
|
||||
})
|
||||
|
||||
it('has no event data block', () => {
|
||||
expect(wrapper.find('div.eventDatas').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('title cannot be empty', async () => {
|
||||
postTitleInput.setValue('')
|
||||
wrapper.find('form').trigger('submit')
|
||||
@ -293,5 +297,88 @@ describe('ContributionForm.vue', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Events', () => {
|
||||
beforeEach(() => {
|
||||
propsData.createEvent = true
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has event data block', () => {
|
||||
expect(wrapper.find('div.eventDatas').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('is online event', () => {
|
||||
it('has false as default', () => {
|
||||
expect(wrapper.vm.formData.eventIsOnline).toBe(false)
|
||||
})
|
||||
|
||||
it('has input for event location', () => {
|
||||
expect(wrapper.find('input[name="eventLocationName"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('click is online event', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input[name="eventIsOnline"]').setChecked(true)
|
||||
})
|
||||
|
||||
it('has no input for event location', () => {
|
||||
expect(wrapper.find('input[name="eventLocationName"]').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid form', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input[name="title"]').setValue('Illegaler Kindergeburtstag')
|
||||
wrapper.vm.updateEditorContent('Elli hat Geburtstag!')
|
||||
})
|
||||
|
||||
it('has submit button disabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid form', () => {
|
||||
const now = new Date()
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper.find('input[name="title"]').setValue('Illegaler Kindergeburtstag')
|
||||
wrapper.vm.updateEditorContent('Elli hat Geburtstag!')
|
||||
wrapper
|
||||
.findComponent({ name: 'DatePicker' })
|
||||
.vm.$emit('change', new Date(now.getFullYear(), now.getMonth() + 1).toISOString())
|
||||
wrapper.find('input[name="eventVenue"]').setValue('Ellis Kinderzimmer')
|
||||
wrapper.find('input[name="eventLocationName"]').setValue('Deutschland')
|
||||
})
|
||||
|
||||
it('has submit button not disabled', () => {
|
||||
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe(undefined)
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('calls create post', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
|
||||
mutation: PostMutations().CreatePost,
|
||||
variables: expect.objectContaining({
|
||||
title: 'Illegaler Kindergeburtstag',
|
||||
content: 'Elli hat Geburtstag!',
|
||||
eventInput: {
|
||||
eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(),
|
||||
eventVenue: 'Ellis Kinderzimmer',
|
||||
eventLocationName: 'Deutschland',
|
||||
eventIsOnline: false,
|
||||
eventEnd: null,
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -54,13 +54,13 @@
|
||||
</ds-chip>
|
||||
|
||||
<!-- Eventdata -->
|
||||
<div v-if="creatEvent" class="eventDatas">
|
||||
<div v-if="createEvent" class="eventDatas">
|
||||
<hr />
|
||||
<ds-space margin-top="x-small" />
|
||||
<ds-grid>
|
||||
<ds-grid-item style="grid-row-end: span 3">
|
||||
<ds-grid-item class="event-grid-item">
|
||||
<!-- <label>Beginn</label> -->
|
||||
<div style="z-index: 20">
|
||||
<div class="event-grid-item-z-helper">
|
||||
<date-picker
|
||||
name="eventStart"
|
||||
v-model="formData.eventStart"
|
||||
@ -68,42 +68,45 @@
|
||||
value-type="format"
|
||||
:minute-step="15"
|
||||
Xformat="DD-MM-YYYY HH:mm"
|
||||
style="z-index: 20"
|
||||
class="event-grid-item-z-helper"
|
||||
:placeholder="$t('post.viewEvent.eventStart')"
|
||||
:disabled-date="notBeforeToday"
|
||||
:disabled-time="notBeforeNow"
|
||||
:show-second="false"
|
||||
@change="changeEventStart($event)"
|
||||
></date-picker>
|
||||
</div>
|
||||
<div class="chipbox" style="margin-top: 10px">
|
||||
<div v-if="errors && errors.eventStart" class="chipbox event-grid-item-margin-helper">
|
||||
<ds-chip size="base" :color="errors && errors.eventStart && 'danger'">
|
||||
<base-icon v-if="errors && errors.eventStart" name="warning" />
|
||||
<base-icon name="warning" />
|
||||
</ds-chip>
|
||||
</div>
|
||||
</ds-grid-item>
|
||||
<ds-grid-item style="grid-row-end: span 3">
|
||||
<ds-grid-item class="event-grid-item">
|
||||
<!-- <label>Ende (optional)</label> -->
|
||||
|
||||
<date-picker
|
||||
v-model="formData.eventEnd"
|
||||
name="eventEnd"
|
||||
type="datetime"
|
||||
value-type="format"
|
||||
:minute-step="15"
|
||||
:seconds-step="0"
|
||||
Xformat="DD-MM-YYYY HH:mm"
|
||||
:placeholder="$t('post.viewEvent.eventEnd')"
|
||||
style="font-size: larger"
|
||||
class="event-grid-item-font-helper"
|
||||
:disabled-date="notBeforeEventDay"
|
||||
:disabled-time="notBeforeEvent"
|
||||
:show-second="false"
|
||||
@change="changeEventEnd($event)"
|
||||
></date-picker>
|
||||
</ds-grid-item>
|
||||
</ds-grid>
|
||||
<ds-grid>
|
||||
<ds-grid-item style="grid-row-end: span 3">
|
||||
<ds-grid class="event-location-grid">
|
||||
<ds-grid-item class="event-grid-item">
|
||||
<ds-input
|
||||
model="eventVenue"
|
||||
name="location"
|
||||
name="eventVenue"
|
||||
:placeholder="$t('post.viewEvent.eventVenue')"
|
||||
/>
|
||||
<div class="chipbox">
|
||||
@ -113,10 +116,10 @@
|
||||
</ds-chip>
|
||||
</div>
|
||||
</ds-grid-item>
|
||||
<ds-grid-item style="grid-row-end: span 3">
|
||||
<ds-grid-item v-if="showEventLocationName" class="event-grid-item">
|
||||
<ds-input
|
||||
model="eventLocationName"
|
||||
name="venue"
|
||||
name="eventLocationName"
|
||||
:placeholder="$t('post.viewEvent.eventLocationName')"
|
||||
/>
|
||||
<div class="chipbox">
|
||||
@ -131,9 +134,11 @@
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
model="formData.eventIsOnline"
|
||||
v-model="formData.eventIsOnline"
|
||||
model="eventIsOnline"
|
||||
name="eventIsOnline"
|
||||
style="font-size: larger"
|
||||
class="event-grid-item-font-helper"
|
||||
@change="changeEventIsOnline($event)"
|
||||
/>
|
||||
{{ $t('post.viewEvent.eventIsOnline') }}
|
||||
</div>
|
||||
@ -153,7 +158,7 @@
|
||||
<base-icon v-if="errors && errors.categoryIds" name="warning" />
|
||||
</ds-chip>
|
||||
<ds-flex class="buttons-footer" gutter="xxx-small">
|
||||
<ds-flex-item width="3.5" style="margin-right: 16px; margin-bottom: 6px">
|
||||
<ds-flex-item width="3.5" class="buttons-footer-helper">
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<!-- TODO => remove v-html! only text ! no html! security first! -->
|
||||
<ds-text
|
||||
@ -210,7 +215,7 @@ export default {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
creatEvent: {
|
||||
createEvent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
@ -252,24 +257,6 @@ export default {
|
||||
eventVenue: eventVenue || '',
|
||||
eventIsOnline: eventIsOnline || false,
|
||||
},
|
||||
formSchema: {
|
||||
title: { required: true, min: 3, max: 100 },
|
||||
content: { required: true },
|
||||
imageBlurred: { required: false },
|
||||
categoryIds: {
|
||||
type: 'array',
|
||||
required: this.categoriesActive,
|
||||
validator: (_, value = []) => {
|
||||
if (this.categoriesActive && (value.length === 0 || value.length > 3)) {
|
||||
return [new Error(this.$t('common.validations.categories'))]
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
eventStart: { required: !!this.creatEvent },
|
||||
eventVenue: { required: !!this.creatEvent, min: 3, max: 100 },
|
||||
eventLocationName: { required: !!this.creatEvent, min: 3, max: 100 },
|
||||
},
|
||||
loading: false,
|
||||
users: [],
|
||||
hashtags: [],
|
||||
@ -283,14 +270,69 @@ export default {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
formSchema() {
|
||||
return {
|
||||
title: { required: true, min: 3, max: 100 },
|
||||
content: { required: true },
|
||||
imageBlurred: { required: false },
|
||||
categoryIds: {
|
||||
type: 'array',
|
||||
required: this.categoriesActive,
|
||||
validator: (_, value = []) => {
|
||||
if (this.categoriesActive && (value.length === 0 || value.length > 3)) {
|
||||
return [new Error(this.$t('common.validations.categories'))]
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
eventStart: { required: !!this.createEvent },
|
||||
eventVenue: {
|
||||
required: !!this.createEvent,
|
||||
min: 3,
|
||||
max: 100,
|
||||
validator: (_, value = '') => {
|
||||
if (!this.createEvent) return []
|
||||
if (!value.trim()) {
|
||||
return [new Error(this.$t('common.validations.eventVenueNotEmpty'))]
|
||||
}
|
||||
if (value.length < 3 || value.length > 100) {
|
||||
return [
|
||||
new Error(this.$t('common.validations.eventVenueLength', { min: 3, max: 100 })),
|
||||
]
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
eventLocationName: {
|
||||
required: !!this.createEvent && !this.formData.eventIsOnline,
|
||||
min: 3,
|
||||
max: 100,
|
||||
validator: (_, value = '') => {
|
||||
if (!this.createEvent) return []
|
||||
if (this.formData.eventIsOnline) return []
|
||||
if (!value.trim()) {
|
||||
return [new Error(this.$t('common.validations.eventLocationNameNotEmpty'))]
|
||||
}
|
||||
if (value.length < 3 || value.length > 100) {
|
||||
return [
|
||||
new Error(
|
||||
this.$t('common.validations.eventLocationNameLength', { min: 3, max: 100 }),
|
||||
),
|
||||
]
|
||||
}
|
||||
return []
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
eventInput() {
|
||||
if (this.creatEvent) {
|
||||
if (this.createEvent) {
|
||||
return {
|
||||
eventStart: this.formData.eventStart,
|
||||
eventVenue: this.formData.eventVenue,
|
||||
eventEnd: this.formData.eventEnd,
|
||||
eventIsOnline: this.formData.eventIsOnline,
|
||||
eventLocationName: this.formData.eventLocationName,
|
||||
eventLocationName: !this.formData.eventIsOnline ? this.formData.eventLocationName : null,
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
@ -310,6 +352,9 @@ export default {
|
||||
groupCategories() {
|
||||
return this.group && this.group.categories
|
||||
},
|
||||
showEventLocationName() {
|
||||
return !this.formData.eventIsOnline
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
groupCategories() {
|
||||
@ -356,7 +401,7 @@ export default {
|
||||
id: this.contribution.id || null,
|
||||
image,
|
||||
groupId: this.groupId,
|
||||
postType: !this.creatEvent ? 'Article' : 'Event',
|
||||
postType: !this.createEvent ? 'Article' : 'Event',
|
||||
eventInput: this.eventInput,
|
||||
},
|
||||
})
|
||||
@ -378,6 +423,15 @@ export default {
|
||||
updateEditorContent(value) {
|
||||
this.$refs.contributionForm.update('content', value)
|
||||
},
|
||||
changeEventIsOnline(event) {
|
||||
this.$refs.contributionForm.update('eventIsOnline', this.formData.eventIsOnline)
|
||||
},
|
||||
changeEventEnd(event) {
|
||||
this.$refs.contributionForm.update('eventEnd', event)
|
||||
},
|
||||
changeEventStart(event) {
|
||||
this.$refs.contributionForm.update('eventStart', event)
|
||||
},
|
||||
addHeroImage(file) {
|
||||
this.formData.image = null
|
||||
if (file) {
|
||||
@ -443,6 +497,24 @@ export default {
|
||||
margin-top: -10px;
|
||||
}
|
||||
}
|
||||
// style override to handle dynamic inputs
|
||||
.event-location-grid {
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
}
|
||||
|
||||
.event-grid-item {
|
||||
// important needed because of component inline style
|
||||
grid-row-end: span 3 !important;
|
||||
}
|
||||
.event-grid-item-z-helper {
|
||||
z-index: 20;
|
||||
}
|
||||
.event-grid-item-margin-helper {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.event-grid-item-font-helper {
|
||||
font-size: larger;
|
||||
}
|
||||
}
|
||||
|
||||
.contribution-form > .base-card {
|
||||
@ -491,6 +563,12 @@ export default {
|
||||
min-width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
> .buttons-footer-helper {
|
||||
margin-right: 16px;
|
||||
// important needed because of component inline style
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.blur-toggle {
|
||||
|
||||
101
webapp/components/DateTimeRange/DateTimeRange.vue
Normal file
101
webapp/components/DateTimeRange/DateTimeRange.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<ds-text class="date-time-range" align="left" color="soft" :size="size">
|
||||
<div>
|
||||
<div>
|
||||
<base-icon name="calendar" data-test="calendar" />
|
||||
{{ getStartDateString }}
|
||||
</div>
|
||||
<div>
|
||||
<base-icon name="clock" data-test="calendar" />
|
||||
{{
|
||||
getStartTimeString +
|
||||
(this.endDateAsDate && isSameDayLocal ? '—' + getEndTimeString : '')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="!isSameDayLocal">
|
||||
—
|
||||
<div>
|
||||
<div>
|
||||
<base-icon name="calendar" data-test="calendar" />
|
||||
{{ getEndDateString }}
|
||||
</div>
|
||||
<div>
|
||||
<base-icon name="clock" data-test="calendar" />
|
||||
{{ getEndTimeString }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ds-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format, isSameDay, isSameYear } from 'date-fns'
|
||||
|
||||
export default {
|
||||
name: 'DateTimeRange',
|
||||
props: {
|
||||
/**
|
||||
* The size used for the text.
|
||||
* @options small|base|large|x-large|xx-large|xxx-large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: (value) => {
|
||||
return value.match(/(small|base|large|x-large|xx-large|xxx-large)/)
|
||||
},
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
startDateAsDate() {
|
||||
return new Date(this.startDate)
|
||||
},
|
||||
endDateAsDate() {
|
||||
return this.endDate ? new Date(this.endDate) : null
|
||||
},
|
||||
isSameDayLocal() {
|
||||
return !this.endDateAsDate || isSameDay(this.endDateAsDate, this.startDateAsDate)
|
||||
},
|
||||
isSameYearLocal() {
|
||||
return !this.endDateAsDate || isSameYear(this.endDateAsDate, this.startDateAsDate)
|
||||
},
|
||||
getStartDateString() {
|
||||
let startDateFormat = this.$t('components.dateTimeRange.yearMonthDay')
|
||||
if (!this.isSameDayLocal && this.isSameYearLocal) {
|
||||
startDateFormat = this.$t('components.dateTimeRange.monthDay')
|
||||
}
|
||||
return format(this.startDateAsDate, startDateFormat)
|
||||
},
|
||||
getStartTimeString() {
|
||||
return format(new Date(this.startDate), this.$t('components.dateTimeRange.hourMinute'))
|
||||
},
|
||||
getEndDateString() {
|
||||
return this.endDate
|
||||
? format(new Date(this.endDate), this.$t('components.dateTimeRange.yearMonthDay'))
|
||||
: ''
|
||||
},
|
||||
getEndTimeString() {
|
||||
return this.endDate
|
||||
? format(new Date(this.endDate), this.$t('components.dateTimeRange.hourMinute'))
|
||||
: ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.date-time-range {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
||||
52
webapp/components/LocationTeaser/LocationTeaser.vue
Normal file
52
webapp/components/LocationTeaser/LocationTeaser.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<ds-text class="location-teaser" align="left" color="soft" :size="size">
|
||||
<base-icon name="map-marker" data-test="map-marker" />
|
||||
<span v-if="venue">{{ venue }}</span>
|
||||
<span v-if="venue"> — </span>
|
||||
<span v-if="!isOnline">
|
||||
{{ locationName }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t('post.viewEvent.eventIsOnline') }}
|
||||
</span>
|
||||
</ds-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LocationTeaser',
|
||||
props: {
|
||||
/**
|
||||
* The size used for the text.
|
||||
* @options small|base|large|x-large|xx-large|xxx-large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: (value) => {
|
||||
return value.match(/(small|base|large|x-large|xx-large|xxx-large)/)
|
||||
},
|
||||
},
|
||||
venue: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
locationName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
isOnline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.location-teaser {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
||||
@ -11,7 +11,6 @@
|
||||
}"
|
||||
:highlight="isPinned"
|
||||
>
|
||||
<!-- {{ post }} -->
|
||||
<template v-if="post.image" #heroImage>
|
||||
<img :src="post.image | proxyApiUrl" class="image" />
|
||||
</template>
|
||||
@ -31,25 +30,19 @@
|
||||
margin-bottom="small"
|
||||
style="padding: 5px"
|
||||
>
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<ds-text align="left" size="small" color="soft" class="event-info">
|
||||
<base-icon name="map-marker" data-test="map-marker" />
|
||||
<span v-if="post.eventIsOnline">
|
||||
{{ $t('post.viewEvent.eventIsOnline') }}
|
||||
</span>
|
||||
<span v-else-if="post.eventLocationName">
|
||||
{{ post.eventLocationName }}
|
||||
</span>
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-text align="left" color="soft" size="small" class="event-info">
|
||||
<base-icon name="calendar" data-test="calendar" />
|
||||
<span>{{ getEventDateString }}</span>
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<location-teaser
|
||||
class="event-info"
|
||||
size="small"
|
||||
:venue="post.eventVenue"
|
||||
:locationName="post.eventLocationName"
|
||||
:isOnline="post.eventIsOnline"
|
||||
/>
|
||||
<date-time-range
|
||||
class="event-info"
|
||||
size="small"
|
||||
:startDate="post.eventStart"
|
||||
:endDate="post.eventEnd"
|
||||
/>
|
||||
</ds-space>
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
@ -113,12 +106,13 @@
|
||||
import Category from '~/components/Category'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||
import DateTimeRange from '~/components/DateTimeRange/DateTimeRange'
|
||||
import HcRibbon from '~/components/Ribbon'
|
||||
import LocationTeaser from '~/components/LocationTeaser/LocationTeaser'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import { mapGetters } from 'vuex'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export default {
|
||||
name: 'PostTeaser',
|
||||
@ -126,7 +120,9 @@ export default {
|
||||
Category,
|
||||
ContentMenu,
|
||||
CounterIcon,
|
||||
DateTimeRange,
|
||||
HcRibbon,
|
||||
LocationTeaser,
|
||||
UserTeaser,
|
||||
},
|
||||
props: {
|
||||
@ -185,15 +181,6 @@ export default {
|
||||
if (this.post.postType[0] === 'Event') return this.$t('post.event')
|
||||
return this.$t('post.name')
|
||||
},
|
||||
getEventDateString() {
|
||||
if (this.post.eventEnd) {
|
||||
const eventStart = format(new Date(this.post.eventStart), 'dd.MM.')
|
||||
const eventEnd = format(new Date(this.post.eventEnd), 'dd.MM.yyyy')
|
||||
return `${eventStart} - ${eventEnd}`
|
||||
} else {
|
||||
return format(new Date(this.post.eventStart), 'dd.MM.yyyy')
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deletePostCallback() {
|
||||
@ -228,6 +215,7 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.post-teaser,
|
||||
.post-teaser:hover,
|
||||
|
||||
@ -61,6 +61,8 @@ export default () => {
|
||||
$content: String!
|
||||
$image: ImageInput
|
||||
$categoryIds: [ID]
|
||||
$postType: PostType
|
||||
$eventInput: _EventInput
|
||||
) {
|
||||
UpdatePost(
|
||||
id: $id
|
||||
@ -68,6 +70,8 @@ export default () => {
|
||||
content: $content
|
||||
image: $image
|
||||
categoryIds: $categoryIds
|
||||
postType: $postType
|
||||
eventInput: $eventInput
|
||||
) {
|
||||
id
|
||||
title
|
||||
@ -85,6 +89,14 @@ export default () => {
|
||||
name
|
||||
role
|
||||
}
|
||||
postType
|
||||
eventStart
|
||||
eventLocationName
|
||||
eventVenue
|
||||
eventLocation {
|
||||
lng
|
||||
lat
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@ -115,11 +115,20 @@
|
||||
"validations": {
|
||||
"categories": "es müssen eine bis drei Themen ausgewählt werden",
|
||||
"email": "muss eine gültige E-Mail-Adresse sein",
|
||||
"eventLocationNameLength": "Minimum {min}, Maximum {max} Zeichen",
|
||||
"eventLocationNameNotEmpty": "es dürfen nicht nur Freizeichen eingetragen werden",
|
||||
"eventVenueLength": "Minimum {min}, Maximum {max} Zeichen",
|
||||
"eventVenueNotEmpty": "es dürfen nicht nur Freizeichen eingetragen werden",
|
||||
"url": "muss eine gültige URL sein"
|
||||
},
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"dateTimeRange": {
|
||||
"hourMinute": "HH:mm",
|
||||
"monthDay": "dd.MM.",
|
||||
"yearMonthDay": "dd.MM.yyyy"
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Passwort Änderung fehlgeschlagen. Möglicherweise falscher Sicherheitscode?",
|
||||
@ -726,6 +735,7 @@
|
||||
},
|
||||
"edited": "bearbeitet",
|
||||
"editPost": {
|
||||
"event": "Bearbeite deine Veranstaltung",
|
||||
"forGroup": {
|
||||
"title": "Für die Gruppe „{name}“"
|
||||
},
|
||||
@ -747,7 +757,7 @@
|
||||
},
|
||||
"viewEvent": {
|
||||
"eventEnd": "Ende",
|
||||
"eventIsOnline": "Online Veranstaltung",
|
||||
"eventIsOnline": "Online",
|
||||
"eventLocationName": "Stadt",
|
||||
"eventStart": "Beginn",
|
||||
"eventVenue": "Veranstaltungsort",
|
||||
|
||||
@ -115,11 +115,20 @@
|
||||
"validations": {
|
||||
"categories": "at least one and at most three topics must be selected",
|
||||
"email": "must be a valid e-mail address",
|
||||
"eventLocationNameLength": "minimum {min} or maximum {max} characters",
|
||||
"eventLocationNameNotEmpty": "only empty characters are not allowed",
|
||||
"eventVenueLength": "minimum {min} or maximum {max} characters",
|
||||
"eventVenueNotEmpty": "only empty characters are not allowed",
|
||||
"url": "must be a valid URL"
|
||||
},
|
||||
"versus": "Versus"
|
||||
},
|
||||
"components": {
|
||||
"dateTimeRange": {
|
||||
"hourMinute": "HH:mm",
|
||||
"monthDay": "MM/dd/",
|
||||
"yearMonthDay": "MM/dd/yyyy"
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Changing your password failed. Maybe the security code was not correct?",
|
||||
@ -722,10 +731,11 @@
|
||||
"forGroup": {
|
||||
"title": "For The Group “{name}”"
|
||||
},
|
||||
"title": "Create A New Post"
|
||||
"title": "Create A New Article"
|
||||
},
|
||||
"edited": "edited",
|
||||
"editPost": {
|
||||
"event": "Edit Your Event",
|
||||
"forGroup": {
|
||||
"title": "For The Group “{name}”"
|
||||
},
|
||||
@ -747,7 +757,7 @@
|
||||
},
|
||||
"viewEvent": {
|
||||
"eventEnd": "End",
|
||||
"eventIsOnline": "Online Event",
|
||||
"eventIsOnline": "Online",
|
||||
"eventLocationName": "City",
|
||||
"eventStart": "Start",
|
||||
"eventVenue": "Venue",
|
||||
|
||||
@ -54,36 +54,28 @@
|
||||
</section>
|
||||
<ds-space margin-bottom="small" />
|
||||
<h2 class="title hyphenate-text">{{ post.title }}</h2>
|
||||
<!-- Eventdata -->
|
||||
<!-- event data -->
|
||||
<ds-space
|
||||
v-if="post && post.postType[0] === 'Event'"
|
||||
margin-bottom="small"
|
||||
style="padding: 10px"
|
||||
>
|
||||
<ds-text align="left" color="soft">
|
||||
<base-icon name="map-marker" data-test="map-marker" />
|
||||
<span v-if="post.eventVenue">{{ post.eventVenue }}</span>
|
||||
<span v-if="!post.eventIsOnline">
|
||||
<span v-if="post.eventVenue">-</span>
|
||||
{{ post.eventLocationName }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-if="post.eventVenue">-</span>
|
||||
{{ $t('post.viewEvent.eventIsOnline') }}
|
||||
</span>
|
||||
</ds-text>
|
||||
<ds-text align="left" color="soft" class="event-info">
|
||||
<base-icon name="calendar" data-test="calendar" />
|
||||
<span>{{ getEventDateString }}</span>
|
||||
</ds-text>
|
||||
<ds-text v-if="getEventTimeString" align="left" color="soft" class="event-info">
|
||||
<base-icon name="clock" data-test="calendar" />
|
||||
<span>{{ getEventTimeString }}</span>
|
||||
</ds-text>
|
||||
<location-teaser
|
||||
class="event-info"
|
||||
:venue="post.eventVenue"
|
||||
:locationName="post.eventLocationName"
|
||||
:isOnline="post.eventIsOnline"
|
||||
/>
|
||||
<date-time-range
|
||||
class="event-info"
|
||||
:startDate="post.eventStart"
|
||||
:endDate="post.eventEnd"
|
||||
/>
|
||||
</ds-space>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- content -->
|
||||
<content-viewer class="content hyphenate-text" :content="post.content" />
|
||||
<!-- Categories -->
|
||||
<!-- categories -->
|
||||
<div v-if="categoriesActive" class="categories">
|
||||
<ds-space margin="xx-large" />
|
||||
<ds-space margin="xx-small" />
|
||||
@ -158,11 +150,13 @@
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import HcCategory from '~/components/Category'
|
||||
import HcHashtag from '~/components/Hashtag/Hashtag'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||
import CommentForm from '~/components/CommentForm/CommentForm'
|
||||
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 LocationTeaser from '~/components/LocationTeaser/LocationTeaser'
|
||||
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
|
||||
import {
|
||||
postMenuModalsData,
|
||||
@ -174,7 +168,6 @@ import { groupQuery } from '~/graphql/groups'
|
||||
import PostMutations from '~/graphql/PostMutations'
|
||||
import links from '~/constants/links.js'
|
||||
import SortCategories from '~/mixins/sortCategoriesMixin.js'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export default {
|
||||
name: 'PostSlug',
|
||||
@ -187,9 +180,11 @@ export default {
|
||||
CommentForm,
|
||||
CommentList,
|
||||
ContentViewer,
|
||||
DateTimeRange,
|
||||
HcCategory,
|
||||
HcHashtag,
|
||||
HcShoutButton,
|
||||
LocationTeaser,
|
||||
PageParamsLink,
|
||||
UserTeaser,
|
||||
},
|
||||
@ -225,7 +220,10 @@ export default {
|
||||
const { slug, id } = this.$route.params
|
||||
return [
|
||||
{
|
||||
name: this.$t('common.post', null, 1),
|
||||
name:
|
||||
this.post?.postType[0] === 'Event'
|
||||
? this.$t('post.viewEvent.title')
|
||||
: this.$t('post.viewPost.title'),
|
||||
path: `/post/${id}/${slug}`,
|
||||
children: [
|
||||
{
|
||||
@ -288,27 +286,6 @@ export default {
|
||||
!this.post.group || (this.group && ['usual', 'admin', 'owner'].includes(this.group.myRole))
|
||||
)
|
||||
},
|
||||
getEventDateString() {
|
||||
if (this.post.eventEnd) {
|
||||
const eventStart = format(new Date(this.post.eventStart), 'dd.MM.')
|
||||
const eventEnd = format(new Date(this.post.eventEnd), 'dd.MM.yyyy')
|
||||
return `${eventStart} - ${eventEnd}`
|
||||
} else {
|
||||
return format(new Date(this.post.eventStart), 'dd.MM.yyyy')
|
||||
}
|
||||
},
|
||||
getEventTimeString() {
|
||||
if (this.post.eventEnd) {
|
||||
const eventStartTime = format(new Date(this.post.eventStart), 'HH:mm')
|
||||
const eventEndTime = format(new Date(this.post.eventEnd), 'HH:mm')
|
||||
/* assumption that if e.g. 00:00 == 00:00 is saved,
|
||||
it's not realistic because they are the default values, so don't show the time info.
|
||||
*/
|
||||
return eventStartTime !== eventEndTime ? `${eventStartTime} - ${eventEndTime}` : ''
|
||||
} else {
|
||||
return format(new Date(this.post.eventStart), 'HH:mm')
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reply(message) {
|
||||
@ -390,6 +367,7 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.post-side-navigation {
|
||||
position: sticky;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-flex :width="{ base: '100%' }">
|
||||
<ds-flex-item :width="{ base: '100%', md: 5 }">
|
||||
<ds-flex-item :width="{ base: '100%' }">
|
||||
<ds-flex gutter="base" :width="{ base: '100%', sm: 1 }">
|
||||
<ds-flex-item>
|
||||
<ds-card :primary="!creatEvent" centered>
|
||||
<ds-card class="create-form-btn" :primary="!createEvent" centered>
|
||||
<div>
|
||||
<ds-button
|
||||
v-if="!creatEvent"
|
||||
v-if="!createEvent"
|
||||
ghost
|
||||
fullwidth
|
||||
size="x-large"
|
||||
@ -15,26 +15,26 @@
|
||||
>
|
||||
{{ $t('post.createNewPost.title') }}
|
||||
</ds-button>
|
||||
<ds-button v-else ghost fullwidth size="x-large" @click="creatEvent = !creatEvent">
|
||||
<ds-button v-else ghost fullwidth size="x-large" @click="switchPostType()">
|
||||
{{ $t('post.createNewPost.title') }}
|
||||
</ds-button>
|
||||
</div>
|
||||
</ds-card>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-card :primary="!!creatEvent" centered>
|
||||
<ds-card class="create-form-btn" :primary="!!createEvent" centered>
|
||||
<div>
|
||||
<ds-button
|
||||
ghost
|
||||
fullwidth
|
||||
size="x-large"
|
||||
v-if="creatEvent"
|
||||
v-if="createEvent"
|
||||
hover
|
||||
class="inactive-tab-button"
|
||||
>
|
||||
{{ $t('post.createNewEvent.title') }}
|
||||
</ds-button>
|
||||
<ds-button ghost fullwidth size="x-large" v-else @click="creatEvent = !creatEvent">
|
||||
<ds-button ghost fullwidth size="x-large" v-else @click="switchPostType()">
|
||||
{{ $t('post.createNewEvent.title') }}
|
||||
</ds-button>
|
||||
</div>
|
||||
@ -45,14 +45,12 @@
|
||||
{{ $t('post.createNewPost.forGroup.title', { name: group.name }) }}
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
</ds-flex>
|
||||
|
||||
<ds-flex :width="{ base: '100%' }" gutter="base">
|
||||
<ds-flex-item :width="{ base: '100%', md: 5 }">
|
||||
<contribution-form :group="group" :creatEvent="creatEvent" />
|
||||
<ds-flex-item :width="{ base: '100%' }">
|
||||
<contribution-form :group="group" :createEvent="createEvent" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
</ds-flex>
|
||||
</div>
|
||||
</template>
|
||||
@ -69,7 +67,7 @@ export default {
|
||||
const { groupId = null } = this.$route.query
|
||||
return {
|
||||
groupId,
|
||||
creatEvent: false,
|
||||
createEvent: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -98,6 +96,11 @@ export default {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
switchPostType() {
|
||||
this.createEvent = !this.createEvent
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@ -109,4 +112,12 @@ export default {
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// copy hover effect from ghost button to use for ds-card
|
||||
.create-form-btn:not(.ds-card-primary):hover {
|
||||
background-color: #faf9fa;
|
||||
}
|
||||
.create-form-btn .ds-button-ghost:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -29,7 +29,12 @@ describe('post/_id.vue', () => {
|
||||
defaultClient: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
Post: [{ author: { id: authorId } }],
|
||||
Post: [
|
||||
{
|
||||
author: { id: authorId },
|
||||
postType: ['Article'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('post.editPost.title') }}</ds-heading>
|
||||
<ds-heading tag="h1">
|
||||
{{
|
||||
contribution && contribution.postType[0] === 'Event'
|
||||
? $t('post.editPost.event')
|
||||
: $t('post.editPost.title')
|
||||
}}
|
||||
</ds-heading>
|
||||
<ds-heading v-if="contribution && contribution.group" tag="h2">
|
||||
{{ $t('post.editPost.forGroup.title', { name: contribution.group.name }) }}
|
||||
</ds-heading>
|
||||
@ -12,6 +18,7 @@
|
||||
<contribution-form
|
||||
:contribution="contribution"
|
||||
:group="contribution && contribution.group ? contribution.group : null"
|
||||
:createEvent="contribution && contribution.postType[0] === 'Event'"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }"> </ds-flex-item>
|
||||
@ -34,7 +41,11 @@ export default {
|
||||
}),
|
||||
},
|
||||
data() {
|
||||
return { contribution: {} }
|
||||
return {
|
||||
contribution: {
|
||||
postType: ['Article'],
|
||||
},
|
||||
}
|
||||
},
|
||||
async asyncData(context) {
|
||||
const {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user