mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'Make-Header-Menu-To-Component' of github.com:Ocelot-Social-Community/Ocelot-Social into 5545-Footer-And-Header-Links-Configurable
This commit is contained in:
commit
6c45b1f3a1
13
README.md
13
README.md
@ -65,11 +65,18 @@ Change into the new folder.
|
||||
$ cd Ocelot-Social
|
||||
```
|
||||
|
||||
## Live Demo And Developer Logins
|
||||
|
||||
**Try out our deployed [development environment](https://stage.ocelot.social).**
|
||||
|
||||
Visit our staging networks:
|
||||
|
||||
* central staging network: [stage.ocelot.social](https://stage.ocelot.social)
|
||||
<!-- - rebranded staging network: [rebrand.ocelot.social](https://stage.ocelot.social). -->
|
||||
|
||||
### Login
|
||||
|
||||
<!-- Try out our deployed [development environment](https://develop.human-connection.org/). -->
|
||||
|
||||
Logins in the browser after the following installations:
|
||||
Logins for the live demos and developers (local developers after the following installations) in the browser:
|
||||
|
||||
| email | password | role |
|
||||
| :--- | :--- | :--- |
|
||||
|
||||
@ -8,7 +8,8 @@ export default {
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdateGroup: async (resolve, root, args, context, info) => {
|
||||
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||
if (args.description)
|
||||
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
|
||||
@ -249,6 +249,40 @@ const isMemberOfGroup = rule({
|
||||
}
|
||||
})
|
||||
|
||||
const canCommentPost = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
if (!(user && user.id)) return false
|
||||
const { postId } = args
|
||||
const userId = user.id
|
||||
const session = driver.session()
|
||||
const readTxPromise = session.readTransaction(async (transaction) => {
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (post:Post { id: $postId })
|
||||
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
||||
OPTIONAL MATCH (user:User { id: $userId })-[membership:MEMBER_OF]->(group)
|
||||
RETURN group AS group, membership AS membership
|
||||
`,
|
||||
{ postId, userId },
|
||||
)
|
||||
return {
|
||||
group: transactionResponse.records.map((record) => record.get('group'))[0],
|
||||
membership: transactionResponse.records.map((record) => record.get('membership'))[0],
|
||||
}
|
||||
})
|
||||
try {
|
||||
const { group, membership } = await readTxPromise
|
||||
return (
|
||||
!group || (membership && ['usual', 'admin', 'owner'].includes(membership.properties.role))
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAuthor = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
@ -361,7 +395,7 @@ export default shield(
|
||||
unshout: isAuthenticated,
|
||||
changePassword: isAuthenticated,
|
||||
review: isModerator,
|
||||
CreateComment: isAuthenticated,
|
||||
CreateComment: and(isAuthenticated, canCommentPost),
|
||||
UpdateComment: isAuthor,
|
||||
DeleteComment: isAuthor,
|
||||
DeleteUser: or(isDeletingOwnAccount, isAdmin),
|
||||
|
||||
@ -30,18 +30,12 @@ export default {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdateGroup: async (resolve, root, args, context, info) => {
|
||||
if (args.name) {
|
||||
args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group')))
|
||||
}
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
UpdatePost: async (resolve, root, args, context, info) => {
|
||||
// TODO: is this absolutely correct, see condition in 'UpdateGroup' above? may it works accidentally, because args.slug is always send?
|
||||
// TODO: is this absolutely correct? what happens if "args.title" is not defined? may it works accidentally, because "args.title" or "args.slug" is always send?
|
||||
args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
|
||||
@ -213,33 +213,6 @@ describe('slugifyMiddleware', () => {
|
||||
|
||||
describe('if group exists', () => {
|
||||
describe('if new slug not(!) exists', () => {
|
||||
describe('setting slug by group name', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'My Best Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
name: 'My Best Group',
|
||||
slug: 'my-best-group',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug explicitly', () => {
|
||||
it('has the new slug', async () => {
|
||||
await expect(
|
||||
@ -284,33 +257,6 @@ describe('slugifyMiddleware', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug by group name', () => {
|
||||
it('has unique slug "*-1"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateGroupMutation(),
|
||||
variables: {
|
||||
id: createGroupResult.data.CreateGroup.id,
|
||||
name: 'Pre-Existing Group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
UpdateGroup: {
|
||||
name: 'Pre-Existing Group',
|
||||
slug: 'pre-existing-group-1',
|
||||
about: 'Some about',
|
||||
description: 'Some description' + descriptionAdditional100,
|
||||
groupType: 'closed',
|
||||
actionRadius: 'national',
|
||||
myRole: 'owner',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setting slug explicitly', () => {
|
||||
it('rejects UpdateGroup', async (done) => {
|
||||
try {
|
||||
|
||||
@ -2714,7 +2714,7 @@ describe('in mode', () => {
|
||||
UpdateGroup: {
|
||||
id: 'my-group',
|
||||
name: 'The New Group For Our Country',
|
||||
slug: 'the-new-group-for-our-country', // changing the slug is tested in the slugifyMiddleware
|
||||
slug: 'the-best-group', // changing the slug is tested in the slugifyMiddleware
|
||||
about: 'We will change the land!',
|
||||
description: 'Some country relevant description' + descriptionAdditional100,
|
||||
descriptionExcerpt:
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
profilePagePosts,
|
||||
searchPosts,
|
||||
} from '../../db/graphql/posts'
|
||||
import { createCommentMutation } from '../../db/graphql/comments'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups'
|
||||
import CONFIG from '../../config'
|
||||
@ -378,6 +379,170 @@ describe('Posts in Groups', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('commenting posts in groups', () => {
|
||||
describe('without membership of group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await anyUser.toJson()
|
||||
})
|
||||
|
||||
it('throws an error for public groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-public-group',
|
||||
content:
|
||||
'I am commenting a post in a public group without being a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error for closed groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-closed-group',
|
||||
content:
|
||||
'I am commenting a post in a closed group without being a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error for hidden groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-hidden-group',
|
||||
content:
|
||||
'I am commenting a post in a hidden group without being a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as a pending member of group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await pendingUser.toJson()
|
||||
})
|
||||
|
||||
it('throws an error for public groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-public-group',
|
||||
content: 'I am commenting a post in a public group as a pending member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error for closed groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-closed-group',
|
||||
content: 'I am commenting a post in a closed group as a pending member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error for hidden groups', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-hidden-group',
|
||||
content: 'I am commenting a post in a hidden group as a pending member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as a member of group', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await allGroupsUser.toJson()
|
||||
})
|
||||
|
||||
it('comments a post in a public group', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-public-group',
|
||||
content: 'I am commenting a post in a public group as a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
CreateComment: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('comments a post in a closed group', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-closed-group',
|
||||
content: 'I am commenting a post in a closed group as a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
CreateComment: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('comments a post in a hidden group', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post-to-hidden-group',
|
||||
content: 'I am commenting a post in a hidden group as a member of the group',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
CreateComment: {
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('visibility of posts', () => {
|
||||
describe('query post by ID', () => {
|
||||
describe('without authentication', () => {
|
||||
|
||||
@ -333,10 +333,10 @@ export default {
|
||||
this.$refs.groupForm.update('description', value)
|
||||
},
|
||||
submit() {
|
||||
const { name, about, description, groupType, actionRadius, /* locationName, */ categoryIds } =
|
||||
this.formData
|
||||
const { name, slug, about, description, groupType, actionRadius, categoryIds } = this.formData
|
||||
const variables = {
|
||||
name,
|
||||
slug,
|
||||
about,
|
||||
description,
|
||||
groupType,
|
||||
|
||||
@ -7,7 +7,13 @@
|
||||
<ds-flex-item :width="{ base: LOGOS.LOGO_HEADER_WIDTH }" style="margin-right: 20px">
|
||||
<a
|
||||
v-if="LOGOS.LOGO_HEADER_CLICK.externalLink"
|
||||
:href="LOGOS.LOGO_HEADER_CLICK.externalLink"
|
||||
:href="LOGOS.LOGO_HEADER_CLICK.externalLink.url"
|
||||
:target="
|
||||
LOGOS.LOGO_HEADER_CLICK.externalLink.target ||
|
||||
LOGOS.LOGO_HEADER_CLICK.externalLink.target === ''
|
||||
? LOGOS.LOGO_HEADER_CLICK.externalLink.target
|
||||
: '_blank'
|
||||
"
|
||||
>
|
||||
<logo logoType="header" />
|
||||
</a>
|
||||
@ -91,7 +97,23 @@
|
||||
<!-- logo, hamburger-->
|
||||
<ds-flex>
|
||||
<ds-flex-item :width="{ base: LOGOS.LOGO_HEADER_WIDTH }" style="margin-right: 20px">
|
||||
<nuxt-link :to="{ name: 'index' }" v-scroll-to="'.main-navigation'">
|
||||
<a
|
||||
v-if="LOGOS.LOGO_HEADER_CLICK.externalLink"
|
||||
:href="LOGOS.LOGO_HEADER_CLICK.externalLink.url"
|
||||
:target="
|
||||
LOGOS.LOGO_HEADER_CLICK.externalLink.target ||
|
||||
LOGOS.LOGO_HEADER_CLICK.externalLink.target === ''
|
||||
? LOGOS.LOGO_HEADER_CLICK.externalLink.target
|
||||
: '_blank'
|
||||
"
|
||||
>
|
||||
<logo logoType="header" />
|
||||
</a>
|
||||
<nuxt-link
|
||||
v-else
|
||||
:to="LOGOS.LOGO_HEADER_CLICK.internalPath.to"
|
||||
v-scroll-to="LOGOS.LOGO_HEADER_CLICK.internalPath.scrollTo"
|
||||
>
|
||||
<logo logoType="header" />
|
||||
</nuxt-link>
|
||||
</ds-flex-item>
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
<div class="progress-bar-component">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__goal"></div>
|
||||
<div class="progress-bar__progress" :style="progressBarWidth"></div>
|
||||
<div
|
||||
:class="['progress-bar__progress', progressBarColorClass]"
|
||||
:style="progressBarWidth"
|
||||
></div>
|
||||
<div class="progress-bar__border" style="width: 100%">
|
||||
<span v-if="label" class="progress-bar__label">{{ label }}</span>
|
||||
</div>
|
||||
@ -14,6 +17,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { PROGRESS_BAR_COLOR_TYPE } from '~/constants/donation.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
goal: {
|
||||
@ -32,6 +37,11 @@ export default {
|
||||
progressBarWidth() {
|
||||
return `width: ${(this.progress / this.goal) * 100}%;`
|
||||
},
|
||||
progressBarColorClass() {
|
||||
return PROGRESS_BAR_COLOR_TYPE === 'gradient'
|
||||
? 'color-repeating-linear-gradient'
|
||||
: 'color-uni'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -66,15 +76,22 @@ export default {
|
||||
left: 0px;
|
||||
height: 26px; // styleguide-button-size
|
||||
max-width: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
120deg,
|
||||
$color-primary 0px,
|
||||
$color-primary 30px,
|
||||
$color-primary-light 50px,
|
||||
$color-primary-light 75px,
|
||||
$color-primary 95px
|
||||
);
|
||||
border-radius: $border-radius-base;
|
||||
|
||||
&.color-uni {
|
||||
background: $color-primary-light;
|
||||
}
|
||||
|
||||
&.color-repeating-linear-gradient {
|
||||
background: repeating-linear-gradient(
|
||||
120deg,
|
||||
$color-primary 0px,
|
||||
$color-primary 30px,
|
||||
$color-primary-light 50px,
|
||||
$color-primary-light 75px,
|
||||
$color-primary 95px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar__border {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
<ds-text align="center" bold color="success">
|
||||
{{ $t('components.registration.create-user-account.success') }}
|
||||
</ds-text>
|
||||
<ds-space margin="xxx-small" />
|
||||
</div>
|
||||
<div v-else-if="response === 'error'">
|
||||
<transition name="ds-transition-fade">
|
||||
@ -21,6 +22,7 @@
|
||||
<ds-space centered>
|
||||
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
|
||||
</ds-space>
|
||||
<ds-space margin="xxx-small" />
|
||||
</div>
|
||||
<div v-else class="create-account-card">
|
||||
<ds-form
|
||||
@ -112,6 +114,7 @@
|
||||
</label>
|
||||
</ds-text>
|
||||
</template>
|
||||
<ds-space margin="xxx-small" />
|
||||
</ds-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
{{ $t('components.registration.email.form.sendEmailAgain') }}
|
||||
</label>
|
||||
</ds-text>
|
||||
<ds-space margin="xxx-small" />
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
{{ $t('components.registration.invite-code.form.description') }}
|
||||
</ds-text>
|
||||
<slot></slot>
|
||||
<ds-space margin="xxx-small" />
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<ds-space centered>
|
||||
<hc-empty icon="events" :message="$t('components.registration.signup.unavailable')" />
|
||||
<slot></slot>
|
||||
<ds-space margin="xxx-small" />
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
{{ $t('components.registration.email-nonce.form.description') }}
|
||||
</ds-text>
|
||||
<slot></slot>
|
||||
<ds-space margin="xxx-small" />
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
|
||||
1
webapp/constants/donation.js
Normal file
1
webapp/constants/donation.js
Normal file
@ -0,0 +1 @@
|
||||
export const PROGRESS_BAR_COLOR_TYPE = 'gradient' // 'uni' is the other option
|
||||
@ -93,7 +93,9 @@ export const updateGroupMutation = () => {
|
||||
name
|
||||
icon
|
||||
}
|
||||
# avatar # test this as result
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
locationName
|
||||
myRole
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user