mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merged master in
This commit is contained in:
commit
1718a161c8
@ -1,108 +1,127 @@
|
||||
.tooltip {
|
||||
display: block !important;
|
||||
z-index: 10000;
|
||||
@mixin arrow($size, $type, $color) {
|
||||
|
||||
.tooltip-inner {
|
||||
background: white;
|
||||
color: $text-color-base;
|
||||
border-radius: $border-radius-large;
|
||||
padding: $space-x-small $space-small;
|
||||
box-shadow: $box-shadow-large;
|
||||
}
|
||||
--#{$type}-arrow-size: $size;
|
||||
|
||||
.tooltip-arrow {
|
||||
.#{$type}-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
border-color: white;
|
||||
margin: $size;
|
||||
border-color: $color;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: $size;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
.#{$type}-arrow {
|
||||
border-width: $size $size 0 $size;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
bottom: -$size;
|
||||
left: calc(50% - var(--#{$type}-arrow-size));
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
margin-top: $size;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
.#{$type}-arrow {
|
||||
border-width: 0 $size $size $size;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
top: -$size;
|
||||
left: calc(50% - var(--#{$type}-arrow-size));
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
margin-left: $size;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
.#{$type}-arrow {
|
||||
border-width: $size $size $size 0;
|
||||
border-left-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
left: -$size;
|
||||
top: calc(50% - var(--#{$type}-arrow-size));
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
margin-right: $size;
|
||||
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
.#{$type}-arrow {
|
||||
border-width: $size 0 $size $size;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
right: -$size;
|
||||
top: calc(50% - var(--#{$type}-arrow-size));
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: block !important;
|
||||
z-index: $z-index-modal - 2;
|
||||
|
||||
.tooltip-inner {
|
||||
background: $background-color-inverse-soft;
|
||||
color: $text-color-inverse;
|
||||
border-radius: $border-radius-base;
|
||||
padding: $space-x-small $space-small;
|
||||
box-shadow: $box-shadow-large;
|
||||
}
|
||||
|
||||
@include arrow(5px, "tooltip", $background-color-inverse-soft);
|
||||
|
||||
&.popover {
|
||||
.popover-inner {
|
||||
background: white;
|
||||
background: $background-color-soft;
|
||||
color: $text-color-base;
|
||||
border-radius: $border-radius-large;
|
||||
border-radius: $border-radius-base;
|
||||
padding: $space-x-small $space-small;
|
||||
box-shadow: $box-shadow-large;
|
||||
box-shadow: $box-shadow-x-large;
|
||||
|
||||
nav {
|
||||
margin-left: -$space-small;
|
||||
margin-right: -$space-small;
|
||||
|
||||
a {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-arrow {
|
||||
border-color: white;
|
||||
border-color: $background-color-soft;
|
||||
}
|
||||
|
||||
@include arrow(7px, "popover", $background-color-soft);
|
||||
}
|
||||
|
||||
|
||||
&[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .15s, visibility .15s;
|
||||
transition: opacity 60ms;
|
||||
}
|
||||
|
||||
&[aria-hidden='false'] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .15s;
|
||||
transition: opacity 60ms;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +125,18 @@ hr {
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
[class$=menu-trigger] {
|
||||
user-select: none;
|
||||
}
|
||||
[class$=menu-popover] {
|
||||
display: inline-block;
|
||||
|
||||
nav {
|
||||
margin-left: -17px;
|
||||
margin-right: -15px;
|
||||
}
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
@ -142,6 +154,7 @@ hr {
|
||||
.dropdown-open & {
|
||||
opacity: 1;
|
||||
transition-delay: 0;
|
||||
transition: opacity 80ms ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,6 +169,8 @@ hr {
|
||||
}
|
||||
|
||||
[class$="menu-popover"] {
|
||||
min-width: 130px;
|
||||
|
||||
a, button {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
117
components/ContentMenu.vue
Normal file
117
components/ContentMenu.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<dropdown
|
||||
class="content-menu"
|
||||
:placement="placement"
|
||||
offset="5"
|
||||
>
|
||||
<template
|
||||
slot="default"
|
||||
slot-scope="{toggleMenu}"
|
||||
>
|
||||
<ds-button
|
||||
class="content-menu-trigger"
|
||||
size="small"
|
||||
ghost
|
||||
@click.prevent="toggleMenu"
|
||||
>
|
||||
<ds-icon name="ellipsis-v" />
|
||||
</ds-button>
|
||||
</template>
|
||||
<div
|
||||
slot="popover"
|
||||
slot-scope="{toggleMenu}"
|
||||
class="content-menu-popover"
|
||||
>
|
||||
<ds-menu :routes="routes">
|
||||
<ds-menu-item
|
||||
slot="menuitem"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||
>
|
||||
<ds-icon :name="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
</div>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dropdown
|
||||
},
|
||||
props: {
|
||||
placement: { type: String, default: 'top-end' },
|
||||
itemId: { type: String, required: true },
|
||||
name: { type: String, required: true },
|
||||
context: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => {
|
||||
return value.match(/(contribution|comment|organization|user)/)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
let routes = [
|
||||
{
|
||||
name: this.$t(`report.${this.context}.title`),
|
||||
callback: this.openReportDialog,
|
||||
icon: 'flag'
|
||||
}
|
||||
]
|
||||
if (this.isModerator) {
|
||||
routes.push({
|
||||
name: this.$t(`disable.${this.context}.title`),
|
||||
callback: this.openDisableDialog,
|
||||
icon: 'eye-slash'
|
||||
})
|
||||
}
|
||||
return routes
|
||||
},
|
||||
isModerator() {
|
||||
return this.$store.getters['auth/isModerator']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openItem(route, toggleMenu) {
|
||||
if (route.callback) {
|
||||
route.callback()
|
||||
} else {
|
||||
this.$router.push(route.path)
|
||||
}
|
||||
toggleMenu()
|
||||
},
|
||||
openReportDialog() {
|
||||
this.$store.commit('modal/SET_OPEN', {
|
||||
name: 'report',
|
||||
data: {
|
||||
context: this.context,
|
||||
id: this.itemId,
|
||||
name: this.name
|
||||
}
|
||||
})
|
||||
},
|
||||
openDisableDialog() {
|
||||
this.$toast.error('NOT IMPLEMENTED!')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.content-menu-popover {
|
||||
nav {
|
||||
margin-top: -$space-xx-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
margin-left: -$space-x-small;
|
||||
margin-right: -$space-x-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,68 +1,77 @@
|
||||
<template>
|
||||
<a
|
||||
v-router-link
|
||||
:href="href(post)"
|
||||
<ds-card
|
||||
:header="post.title"
|
||||
:image="post.image"
|
||||
class="post-card"
|
||||
>
|
||||
<ds-card
|
||||
:header="post.title"
|
||||
:image="post.image"
|
||||
style="cursor: pointer; position: relative;"
|
||||
<a
|
||||
v-router-link
|
||||
class="post-link"
|
||||
:href="href(post)"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<ds-space margin-bottom="large">
|
||||
<div
|
||||
class="hc-editor-content"
|
||||
v-html="excerpt"
|
||||
{{ post.title }}
|
||||
</a>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<ds-space margin-bottom="large">
|
||||
<div
|
||||
class="hc-editor-content"
|
||||
v-html="excerpt"
|
||||
/>
|
||||
</ds-space>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ds-space
|
||||
margin="small"
|
||||
style="position: absolute; bottom: 44px; z-index: 1;"
|
||||
>
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
<no-ssr>
|
||||
<hc-author
|
||||
:post="post"
|
||||
:trunc="35"
|
||||
:show-author-popover="showAuthorPopover"
|
||||
/>
|
||||
</ds-space>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<ds-space
|
||||
margin="small"
|
||||
style="position: absolute; bottom: 44px;"
|
||||
>
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
<template slot="footer">
|
||||
<div style="display: inline-block; opacity: .5;">
|
||||
<ds-icon
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
v-tooltip="{content: category.name, placement: 'bottom-start', delay: { show: 500 }}"
|
||||
:name="category.icon"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: inline-block; float: right">
|
||||
<span :style="{ opacity: post.shoutedCount ? 1 : .5 }">
|
||||
<ds-icon name="bullhorn" /> <small>{{ post.shoutedCount }}</small>
|
||||
</span>
|
||||
|
||||
<span :style="{ opacity: post.commentsCount ? 1 : .5 }">
|
||||
<ds-icon name="comments" /> <small>{{ post.commentsCount }}</small>
|
||||
</span>
|
||||
<no-ssr>
|
||||
<hc-author
|
||||
:post="post"
|
||||
:trunc="35"
|
||||
:show-author-popover="showAuthorPopover"
|
||||
<content-menu
|
||||
context="contribution"
|
||||
:item-id="post.id"
|
||||
:name="post.title"
|
||||
/>
|
||||
</no-ssr>
|
||||
</ds-space>
|
||||
<template slot="footer">
|
||||
<div style="display: inline-block; opacity: .5;">
|
||||
<ds-icon
|
||||
v-for="category in post.categories"
|
||||
:key="category.id"
|
||||
v-tooltip="{content: category.name, placement: 'bottom-start', delay: { show: 500 }}"
|
||||
:name="category.icon"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: inline-block; float: right">
|
||||
<span :style="{ opacity: post.shoutedCount ? 1 : .5 }">
|
||||
<ds-icon name="bullhorn" />
|
||||
<small>{{ post.shoutedCount }}</small>
|
||||
</span>
|
||||
|
||||
<span :style="{ opacity: post.commentsCount ? 1 : .5 }">
|
||||
<ds-icon name="comments" />
|
||||
<small>{{ post.commentsCount }}</small>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</ds-card>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcAuthor from '~/components/Author.vue'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
export default {
|
||||
name: 'HcPostCard',
|
||||
components: {
|
||||
HcAuthor
|
||||
HcAuthor,
|
||||
ContentMenu
|
||||
},
|
||||
props: {
|
||||
post: {
|
||||
@ -96,3 +105,31 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.post-card {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.ds-card-footer {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-menu {
|
||||
display: inline-block;
|
||||
margin-left: $space-xx-small;
|
||||
margin-right: -$space-x-small;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.post-link {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-indent: -999999px;
|
||||
}
|
||||
</style>
|
||||
|
||||
144
components/ReportModal.vue
Normal file
144
components/ReportModal.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<ds-modal
|
||||
:title="title"
|
||||
:is-open="isOpen"
|
||||
:confirm-label="$t('report.submit')"
|
||||
:cancel-label="$t('report.cancel')"
|
||||
confrim-icon="warning"
|
||||
@confirm="report"
|
||||
@cancel="close"
|
||||
>
|
||||
<transition name="ds-transition-fade">
|
||||
<ds-flex
|
||||
v-if="success"
|
||||
class="hc-modal-success"
|
||||
centered
|
||||
>
|
||||
<sweetalert-icon icon="success" />
|
||||
</ds-flex>
|
||||
</transition>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<p v-html="$t(`report.${data.context}.message`, { name: name })" />
|
||||
|
||||
<template
|
||||
slot="footer"
|
||||
slot-scope="{ cancel, confirm, cancelLabel, confirmLabel }"
|
||||
>
|
||||
<ds-button
|
||||
ghost
|
||||
icon="close"
|
||||
:disabled="disabled || loading"
|
||||
@click.prevent="cancel('cancel')"
|
||||
>
|
||||
{{ cancelLabel }}
|
||||
</ds-button>
|
||||
<ds-button
|
||||
danger
|
||||
icon="exclamation-circle"
|
||||
:loading="loading"
|
||||
:disabled="disabled || loading"
|
||||
@click.prevent="confirm('confirm')"
|
||||
>
|
||||
{{ confirmLabel }}
|
||||
</ds-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
name: 'ReportModal',
|
||||
components: {
|
||||
SweetalertIcon
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
success: false,
|
||||
loading: false,
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data() {
|
||||
return this.$store.getters['modal/data'] || {}
|
||||
},
|
||||
title() {
|
||||
return this.$t(`report.${this.data.context}.title`)
|
||||
},
|
||||
name() {
|
||||
return this.$filters.truncate(this.data.name, 30)
|
||||
},
|
||||
isOpen() {
|
||||
return this.$store.getters['modal/open'] === 'report'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isOpen(open) {
|
||||
if (open) {
|
||||
this.success = false
|
||||
this.disabled = false
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$store.commit('modal/SET_OPEN', {})
|
||||
},
|
||||
report() {
|
||||
console.log('')
|
||||
this.loading = true
|
||||
this.disabled = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!, $type: ResourceEnum!, $description: String) {
|
||||
report(
|
||||
resource: { id: $id, type: $type }
|
||||
description: $description
|
||||
) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: this.data.id,
|
||||
type: this.data.context,
|
||||
description: '-'
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.success = true
|
||||
this.$toast.success('Thanks for reporting!')
|
||||
setTimeout(this.close, 1500)
|
||||
})
|
||||
.catch(err => {
|
||||
this.$toast.error(err.message)
|
||||
this.disabled = false
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.hc-modal-success {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
opacity: 1;
|
||||
z-index: $z-index-modal;
|
||||
border-radius: $border-radius-x-large;
|
||||
}
|
||||
</style>
|
||||
17
cypress/fixtures/users.json
Normal file
17
cypress/fixtures/users.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"admin": {
|
||||
"email": "admin@example.org",
|
||||
"password": "1234",
|
||||
"name": "Peter Lustig"
|
||||
},
|
||||
"moderator": {
|
||||
"email": "moderator@example.org",
|
||||
"password": "1234",
|
||||
"name": "Bob der Bausmeister"
|
||||
},
|
||||
"user": {
|
||||
"email": "user@example.org",
|
||||
"password": "1234",
|
||||
"name": "Jenny Rostock"
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
Feature: About me and and location
|
||||
Feature: About me and location
|
||||
As a user
|
||||
I would like to add some about me text and a location
|
||||
So others can get some info about me and my location
|
||||
|
||||
59
cypress/integration/05.ReportContent.feature
Normal file
59
cypress/integration/05.ReportContent.feature
Normal file
@ -0,0 +1,59 @@
|
||||
Feature: Report and Moderate
|
||||
As a user
|
||||
I would like to report content that viloates the community guidlines
|
||||
So the moderators can take action on it
|
||||
|
||||
As a moderator
|
||||
I would like to see all reported content
|
||||
So I can look into it and decide what to do
|
||||
|
||||
Background:
|
||||
Given we have the following posts in our database:
|
||||
| Author | Title | Content | Slug |
|
||||
| David Irving | The Truth about the Holocaust | It never existed! | the-truth-about-the-holocaust |
|
||||
|
||||
Scenario Outline: Report a post from various pages
|
||||
Given I am logged in with a "user" role
|
||||
And I see David Irving's post on the <Page>
|
||||
When I click on "Report Contribution" from the triple dot menu of the post
|
||||
And I confirm the reporting dialog because it is a criminal act under German law:
|
||||
"""
|
||||
Do you really want to report the contribution "The Truth about the Holocaust"?
|
||||
"""
|
||||
Then I see a success message:
|
||||
"""
|
||||
Thanks for reporting!
|
||||
"""
|
||||
Examples:
|
||||
| Page |
|
||||
| landing page |
|
||||
| post page |
|
||||
|
||||
Scenario: Report user
|
||||
Given I am logged in with a "user" role
|
||||
And I see David Irving's post on the post page
|
||||
When I click on the author
|
||||
And I click on "Report User" from the triple dot menu in the user info box
|
||||
And I confirm the reporting dialog because he is a holocaust denier:
|
||||
"""
|
||||
Do you really want to report the user "David Irving"?
|
||||
"""
|
||||
Then I see a success message:
|
||||
"""
|
||||
Thanks for reporting!
|
||||
"""
|
||||
|
||||
Scenario: Review reported content
|
||||
Given somebody reported the following posts:
|
||||
| Slug |
|
||||
| the-truth-about-the-holocaust |
|
||||
And I am logged in with a "moderator" role
|
||||
When I click on the avatar menu in the top right corner
|
||||
And I click on "Moderation"
|
||||
Then I see all the reported posts including the one from above
|
||||
And each list item links to the post page
|
||||
|
||||
Scenario: Normal user can't see the moderation page
|
||||
Given I am logged in with a "user" role
|
||||
When I click on the avatar menu in the top right corner
|
||||
Then I can't see the moderation menu item
|
||||
141
cypress/integration/common/report.js
Normal file
141
cypress/integration/common/report.js
Normal file
@ -0,0 +1,141 @@
|
||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||
|
||||
/* global cy */
|
||||
|
||||
let lastReportTitle
|
||||
let dummyReportedPostTitle = 'Hacker, Freaks und Funktionäre'
|
||||
let dummyReportedPostSlug = 'hacker-freaks-und-funktionareder-ccc'
|
||||
let dummyAuthorName = 'Jenny Rostock'
|
||||
|
||||
const savePostTitle = $post => {
|
||||
return $post
|
||||
.first()
|
||||
.find('.ds-heading')
|
||||
.first()
|
||||
.invoke('text')
|
||||
.then(title => {
|
||||
lastReportTitle = title
|
||||
})
|
||||
}
|
||||
|
||||
Given("I see David Irving's post on the landing page", page => {
|
||||
cy.openPage('landing')
|
||||
})
|
||||
|
||||
Given("I see David Irving's post on the post page", page => {
|
||||
cy.visit(`/post/${dummyReportedPostSlug}`)
|
||||
cy.contains(dummyReportedPostTitle) // wait
|
||||
})
|
||||
|
||||
Given('I am logged in with a {string} role', role => {
|
||||
cy.loginAs(role)
|
||||
})
|
||||
|
||||
When(
|
||||
'I click on "Report Contribution" from the triple dot menu of the post',
|
||||
() => {
|
||||
//TODO: match the created post title, not a dummy post title
|
||||
cy.contains('.ds-card', dummyReportedPostTitle)
|
||||
.find('.content-menu-trigger')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
.contains('Report Contribution')
|
||||
.click()
|
||||
}
|
||||
)
|
||||
|
||||
When(
|
||||
'I click on "Report User" from the triple dot menu in the user info box',
|
||||
() => {
|
||||
//TODO: match the created post author, not a dummy author
|
||||
cy.contains('.ds-card', dummyAuthorName)
|
||||
.find('.content-menu-trigger')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
.contains('Report User')
|
||||
.click()
|
||||
}
|
||||
)
|
||||
|
||||
When('I click on the author', () => {
|
||||
cy.get('a.author')
|
||||
.first()
|
||||
.click()
|
||||
.wait(200)
|
||||
})
|
||||
|
||||
When('I report the author', () => {
|
||||
cy.get('.page-name-profile-slug').then(() => {
|
||||
invokeReportOnElement('.ds-card').then(() => {
|
||||
cy.get('button')
|
||||
.contains('Send')
|
||||
.click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When('I click on send in the confirmation dialog', () => {
|
||||
cy.get('button')
|
||||
.contains('Send')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I get a success message', () => {
|
||||
cy.get('.iziToast-message').contains('Thanks')
|
||||
})
|
||||
|
||||
Then('I see my reported user', () => {
|
||||
cy.get('table').then(() => {
|
||||
cy.get('tbody tr')
|
||||
.first()
|
||||
.contains(lastReportTitle.trim())
|
||||
})
|
||||
})
|
||||
|
||||
Then(`I can't see the moderation menu item`, () => {
|
||||
cy.get('.avatar-menu-popover')
|
||||
.find('a[href="/settings"]', 'Settings')
|
||||
.should('exist') // OK, the dropdown is actually open
|
||||
|
||||
cy.get('.avatar-menu-popover')
|
||||
.find('a[href="/moderation"]', 'Moderation')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
When(/^I confirm the reporting dialog .*:$/, () => {
|
||||
//TODO: take message from method argument
|
||||
//TODO: match the right post
|
||||
const message = 'Do you really want to report the'
|
||||
cy.contains(message) // wait for element to become visible
|
||||
//TODO: cy.get('.ds-modal').contains(dummyReportedPostTitle)
|
||||
cy.get('.ds-modal').within(() => {
|
||||
cy.get('button')
|
||||
.contains('Send Report')
|
||||
.click()
|
||||
})
|
||||
})
|
||||
|
||||
Given('somebody reported the following posts:', table => {
|
||||
table.hashes().forEach(row => {
|
||||
//TODO: calll factory here
|
||||
// const options = Object.assign({}, row, { reported: true })
|
||||
//create('post', options)
|
||||
})
|
||||
})
|
||||
|
||||
Then('I see all the reported posts including the one from above', () => {
|
||||
//TODO: match the right post
|
||||
cy.get('table tbody').within(() => {
|
||||
cy.contains('tr', dummyReportedPostTitle)
|
||||
})
|
||||
})
|
||||
|
||||
Then('each list item links to the post page', () => {
|
||||
//TODO: match the right post
|
||||
cy.contains(dummyReportedPostTitle).click()
|
||||
cy.location('pathname').should('contain', '/post')
|
||||
})
|
||||
@ -1,20 +1,14 @@
|
||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||
import { getLangByName } from '../../support/helpers'
|
||||
import find from 'lodash/find'
|
||||
import users from '../../fixtures/users.json'
|
||||
|
||||
/* global cy */
|
||||
|
||||
const username = 'Peter Lustig'
|
||||
|
||||
const openPage = page => {
|
||||
if (page === 'landing') {
|
||||
page = ''
|
||||
}
|
||||
cy.visit(`/${page}`)
|
||||
}
|
||||
|
||||
Given('I am logged in', () => {
|
||||
cy.login('admin@example.org', 1234)
|
||||
cy.loginAs('admin')
|
||||
})
|
||||
Given('I am logged in as {string}', userType => {
|
||||
cy.loginAs(userType)
|
||||
})
|
||||
|
||||
Given('we have a selection of tags and categories as well as posts', () => {
|
||||
@ -32,10 +26,11 @@ Given('my user account has the role {string}', role => {
|
||||
When('I log out', cy.logout)
|
||||
|
||||
When('I visit the {string} page', page => {
|
||||
openPage(page)
|
||||
cy.openPage(page)
|
||||
})
|
||||
|
||||
Given('I am on the {string} page', page => {
|
||||
openPage(page)
|
||||
cy.openPage(page)
|
||||
})
|
||||
|
||||
When('I fill in my email and password combination and click submit', () => {
|
||||
@ -53,22 +48,22 @@ When('I log out through the menu in the top right corner', () => {
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I can click on my profile picture in the top right corner', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover')
|
||||
})
|
||||
|
||||
Then('I can see my name {string} in the dropdown menu', () => {
|
||||
cy.get('.avatar-menu-popover').should('contain', username)
|
||||
cy.get('.avatar-menu-popover').should('contain', users.admin.name)
|
||||
})
|
||||
|
||||
Then('I see the login screen again', () => {
|
||||
cy.location('pathname').should('contain', '/login')
|
||||
})
|
||||
|
||||
Then('I can click on my profile picture in the top right corner', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover')
|
||||
})
|
||||
|
||||
Then('I am still logged in', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
cy.get('.avatar-menu-popover').contains(username)
|
||||
cy.get('.avatar-menu-popover').contains(users.admin.name)
|
||||
})
|
||||
|
||||
When('I select {string} in the language menu', name => {
|
||||
@ -93,3 +88,18 @@ When(`I click on {string}`, linkOrButton => {
|
||||
When('I press {string}', label => {
|
||||
cy.contains(label).click()
|
||||
})
|
||||
|
||||
Given('we have the following posts in our database:', table => {
|
||||
table.hashes().forEach(row => {
|
||||
//TODO: calll factory here
|
||||
//create('post', row)
|
||||
})
|
||||
})
|
||||
|
||||
Then('I see a success message:', message => {
|
||||
cy.contains(message)
|
||||
})
|
||||
|
||||
When('I click on the avatar menu in the top right corner', () => {
|
||||
cy.get('.avatar-menu').click()
|
||||
})
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
/* globals Cypress cy */
|
||||
|
||||
import { getLangByName } from './helpers'
|
||||
import users from '../fixtures/users.json'
|
||||
|
||||
const switchLang = name => {
|
||||
cy.get('.locale-menu').click()
|
||||
@ -54,11 +55,24 @@ Cypress.Commands.add('login', (email, password) => {
|
||||
.click()
|
||||
cy.location('pathname').should('eq', '/') // we're in!
|
||||
})
|
||||
|
||||
Cypress.Commands.add('loginAs', role => {
|
||||
role = role || 'admin'
|
||||
cy.login(users[role].email, users[role].password)
|
||||
})
|
||||
|
||||
Cypress.Commands.add('logout', (email, password) => {
|
||||
cy.visit(`/logout`)
|
||||
cy.location('pathname').should('contain', '/login') // we're out
|
||||
})
|
||||
|
||||
Cypress.Commands.add('openPage', page => {
|
||||
if (page === 'landing') {
|
||||
page = ''
|
||||
}
|
||||
cy.visit(`/${page}`)
|
||||
})
|
||||
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
export default {
|
||||
users: {
|
||||
admin: {
|
||||
email: 'admin@example.org',
|
||||
password: 1234
|
||||
},
|
||||
moderator: {
|
||||
email: 'moderator@example.org',
|
||||
password: 1234
|
||||
},
|
||||
user: {
|
||||
email: 'user@example.org',
|
||||
password: 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import find from 'lodash/find'
|
||||
|
||||
const helpers = {
|
||||
|
||||
41
graphql/ModerationListQuery.js
Normal file
41
graphql/ModerationListQuery.js
Normal file
@ -0,0 +1,41 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default app => {
|
||||
return gql(`
|
||||
query {
|
||||
Report(first: 20, orderBy: createdAt_desc) {
|
||||
id
|
||||
description
|
||||
type
|
||||
createdAt
|
||||
reporter {
|
||||
name
|
||||
slug
|
||||
}
|
||||
user {
|
||||
name
|
||||
slug
|
||||
}
|
||||
comment {
|
||||
contentExcerpt
|
||||
author {
|
||||
name
|
||||
slug
|
||||
}
|
||||
post {
|
||||
title
|
||||
slug
|
||||
}
|
||||
}
|
||||
contribution {
|
||||
title
|
||||
slug
|
||||
author {
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -92,6 +92,12 @@
|
||||
</div>
|
||||
</ds-container>
|
||||
<div id="overlay" />
|
||||
<no-ssr>
|
||||
<portal-target name="modal" />
|
||||
</no-ssr>
|
||||
<no-ssr>
|
||||
<report-modal />
|
||||
</no-ssr>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -99,11 +105,13 @@
|
||||
import { mapGetters } from 'vuex'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import ReportModal from '~/components/ReportModal'
|
||||
import seo from '~/components/mixins/seo'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dropdown,
|
||||
ReportModal,
|
||||
LocaleSwitch
|
||||
},
|
||||
mixins: [seo],
|
||||
@ -111,6 +119,7 @@ export default {
|
||||
...mapGetters({
|
||||
user: 'auth/user',
|
||||
isLoggedIn: 'auth/isLoggedIn',
|
||||
isModerator: 'auth/isModerator',
|
||||
isAdmin: 'auth/isAdmin'
|
||||
}),
|
||||
routes() {
|
||||
@ -129,6 +138,13 @@ export default {
|
||||
icon: 'cogs'
|
||||
}
|
||||
]
|
||||
if (this.isModerator) {
|
||||
routes.push({
|
||||
name: this.$t('moderation.name'),
|
||||
path: `/moderation`,
|
||||
icon: 'balance-scale'
|
||||
})
|
||||
}
|
||||
if (this.isAdmin) {
|
||||
routes.push({
|
||||
name: this.$t('admin.name'),
|
||||
@ -178,8 +194,8 @@ export default {
|
||||
align-items: center;
|
||||
padding-left: $space-xx-small;
|
||||
}
|
||||
|
||||
.avatar-menu-popover {
|
||||
display: inline-block;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
@ -191,8 +207,17 @@ export default {
|
||||
.logout-link {
|
||||
margin-left: -$space-small;
|
||||
margin-right: -$space-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
padding: $space-xx-small $space-small;
|
||||
margin-top: -$space-xxx-small;
|
||||
margin-bottom: -$space-x-small;
|
||||
padding: $space-x-small $space-small;
|
||||
// subtract menu border with from padding
|
||||
padding-left: $space-small - 2;
|
||||
|
||||
color: $text-color-base;
|
||||
|
||||
&:hover {
|
||||
color: $text-color-link-active;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
@ -200,8 +225,6 @@ export default {
|
||||
margin-right: -$space-small;
|
||||
margin-top: -$space-xx-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
// padding-top: $space-xx-small;
|
||||
// padding-bottom: $space-xx-small;
|
||||
|
||||
a {
|
||||
padding-left: 12px;
|
||||
|
||||
@ -92,6 +92,31 @@
|
||||
"name": "Einstellungen"
|
||||
}
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Moderation",
|
||||
"reports": {
|
||||
"empty": "Glückwunsch, es gibt nichts zu moderieren.",
|
||||
"name": "Meldungen",
|
||||
"reporter": "gemeldet von"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"user": {
|
||||
"title": "Nutzer sperren",
|
||||
"type": "Nutzer",
|
||||
"message": "Bist du sicher, dass du den Nutzer \"<b>{name}</b>\" deaktivieren möchtest?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Beitrag sperren",
|
||||
"type": "Beitrag",
|
||||
"message": "Bist du sicher, dass du den Beitrag \"<b>{name}</b>\" deaktivieren möchtest?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Kommentar sperren",
|
||||
"type": "Kommentar",
|
||||
"message": "Bist du sicher, dass du den Kommentar \"<b>{name}</b>\" deaktivieren möchtest?"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Beitrag",
|
||||
"moreInfo": {
|
||||
@ -101,6 +126,25 @@
|
||||
"name": "Aktiv werden"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Meldung senden",
|
||||
"cancel": "Abbrechen",
|
||||
"user": {
|
||||
"title": "Nutzer melden",
|
||||
"type": "Nutzer",
|
||||
"message": "Bist du sicher, dass du den Nutzer \"<b>{name}</b>\" melden möchtest?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Beitrag melden",
|
||||
"type": "Beitrag",
|
||||
"message": "Bist du sicher, dass du den Beitrag \"<b>{name}</b>\" melden möchtest?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Kommentar melden",
|
||||
"type": "Kommentar",
|
||||
"message": "Bist du sicher, dass du den Kommentar von \"<b>{name}</b>\" melden möchtest?"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Viele kleine Leute, an vielen kleinen Orten, die viele kleine Dinge tun, werden das Antlitz dieser Welt verändern.",
|
||||
@ -108,6 +152,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"reportContent": "Melden",
|
||||
"post": "Beitrag ::: Beiträge",
|
||||
"comment": "Kommentar ::: Kommentare",
|
||||
"letsTalk": "Miteinander reden",
|
||||
|
||||
@ -92,6 +92,31 @@
|
||||
"name": "Settings"
|
||||
}
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Moderation",
|
||||
"reports": {
|
||||
"empty": "Congratulations, nothing to moderate.",
|
||||
"name": "Reports",
|
||||
"reporter": "reported by"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"user": {
|
||||
"title": "Disable User",
|
||||
"type": "User",
|
||||
"message": "Do you really want to disable the user \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Disable Contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Do you really want to disable the contribution \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Disable Comment",
|
||||
"type": "Comment",
|
||||
"message": "Do you really want to disable the comment from \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"name": "Post",
|
||||
"moreInfo": {
|
||||
@ -101,6 +126,25 @@
|
||||
"name": "Take action"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Send Report",
|
||||
"cancel": "Cancel",
|
||||
"user": {
|
||||
"title": "Report User",
|
||||
"type": "User",
|
||||
"message": "Do you really want to report the user \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Report Contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Do you really want to report the contribution \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Report Comment",
|
||||
"type": "Comment",
|
||||
"message": "Do you really want to report the comment from \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"quote": "Many small people in many small places do many small things, that can alter the face of the world.",
|
||||
@ -108,6 +152,7 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"reportContent": "Report",
|
||||
"post": "Post ::: Posts",
|
||||
"comment": "Comment ::: Comments",
|
||||
"letsTalk": "Let`s Talk",
|
||||
|
||||
5
middleware/isAdmin.js
Normal file
5
middleware/isAdmin.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default ({ store, error }) => {
|
||||
if (!store.getters['auth/isAdmin']) {
|
||||
return error({ statusCode: 403 })
|
||||
}
|
||||
}
|
||||
5
middleware/isModerator.js
Normal file
5
middleware/isModerator.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default ({ store, error }) => {
|
||||
if (!store.getters['auth/isModerator']) {
|
||||
return error({ statusCode: 403 })
|
||||
}
|
||||
}
|
||||
@ -74,7 +74,10 @@ module.exports = {
|
||||
router: {
|
||||
middleware: ['authenticated'],
|
||||
linkActiveClass: 'router-link-active',
|
||||
linkExactActiveClass: 'router-link-exact-active'
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
scrollBehavior: () => {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
@ -86,6 +89,7 @@ module.exports = {
|
||||
'cookie-universal-nuxt',
|
||||
'@nuxtjs/apollo',
|
||||
'@nuxtjs/axios',
|
||||
'portal-vue/nuxt',
|
||||
[
|
||||
'nuxt-sass-resources-loader',
|
||||
path.resolve(__dirname, './styleguide/src/system/styles/shared.scss')
|
||||
|
||||
@ -54,9 +54,11 @@
|
||||
"nuxt-sass-resources-loader": "^2.0.5",
|
||||
"tiptap": "~1.8.0",
|
||||
"tiptap-extensions": "~1.8.0",
|
||||
"portal-vue": "~1.5.1",
|
||||
"v-tooltip": "^2.0.0-rc.33",
|
||||
"vue-count-to": "^1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vue-sweetalert-icons": "^3.2.0",
|
||||
"vuex-i18n": "^1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -4,13 +4,13 @@
|
||||
{{ $t('admin.name') }}
|
||||
</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '200px' }">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu
|
||||
:routes="routes"
|
||||
:is-exact="() => true"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }">
|
||||
<transition
|
||||
name="slide-up"
|
||||
appear
|
||||
@ -24,6 +24,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
middleware: ['isAdmin'],
|
||||
computed: {
|
||||
routes() {
|
||||
return [
|
||||
|
||||
36
pages/moderation.vue
Normal file
36
pages/moderation.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-heading tag="h1">
|
||||
{{ $t('moderation.name') }}
|
||||
</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu :routes="routes" />
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }">
|
||||
<transition
|
||||
name="slide-up"
|
||||
appear
|
||||
>
|
||||
<nuxt-child />
|
||||
</transition>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
middleware: ['isModerator'],
|
||||
computed: {
|
||||
routes() {
|
||||
return [
|
||||
{
|
||||
name: this.$t('moderation.reports.name'),
|
||||
path: `/moderation`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
116
pages/moderation/index.vue
Normal file
116
pages/moderation/index.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<ds-card space="small">
|
||||
<ds-heading tag="h3">
|
||||
{{ $t('moderation.reports.name') }}
|
||||
</ds-heading>
|
||||
<ds-table
|
||||
v-if="Report && Report.length"
|
||||
:data="Report"
|
||||
:fields="fields"
|
||||
condensed
|
||||
>
|
||||
<template
|
||||
slot="name"
|
||||
slot-scope="scope"
|
||||
>
|
||||
<div v-if="scope.row.type === 'contribution'">
|
||||
<nuxt-link :to="{ name: 'post-slug', params: { slug: scope.row.contribution.slug } }">
|
||||
<b>{{ scope.row.contribution.title | truncate(50) }}</b>
|
||||
</nuxt-link><br>
|
||||
<ds-text
|
||||
size="small"
|
||||
color="soft"
|
||||
>
|
||||
{{ scope.row.contribution.author.name }}
|
||||
</ds-text>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'comment'">
|
||||
<nuxt-link :to="{ name: 'post-slug', params: { slug: scope.row.comment.post.slug } }">
|
||||
<b>{{ scope.row.comment.contentExcerpt | truncate(50) }}</b>
|
||||
</nuxt-link><br>
|
||||
<ds-text
|
||||
size="small"
|
||||
color="soft"
|
||||
>
|
||||
{{ scope.row.comment.author.name }}
|
||||
</ds-text>
|
||||
</div>
|
||||
<div v-else>
|
||||
<nuxt-link :to="{ name: 'profile-slug', params: { slug: scope.row.user.slug } }">
|
||||
<b>{{ scope.row.user.name | truncate(50) }}</b>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
slot="type"
|
||||
slot-scope="scope"
|
||||
>
|
||||
<ds-text
|
||||
color="soft"
|
||||
>
|
||||
<ds-icon
|
||||
v-if="scope.row.type === 'contribution'"
|
||||
v-tooltip="{ content: $t(`report.${scope.row.type}.type`), placement: 'right' }"
|
||||
name="bookmark"
|
||||
/>
|
||||
<ds-icon
|
||||
v-else-if="scope.row.type === 'comment'"
|
||||
v-tooltip="{ content: $t(`report.${scope.row.type}.type`), placement: 'right' }"
|
||||
name="comments"
|
||||
/>
|
||||
<ds-icon
|
||||
v-else
|
||||
v-tooltip="{ content: $t(`report.${scope.row.type}.type`), placement: 'right' }"
|
||||
name="user"
|
||||
/>
|
||||
</ds-text>
|
||||
</template>
|
||||
<template
|
||||
slot="reporter"
|
||||
slot-scope="scope"
|
||||
>
|
||||
<nuxt-link :to="{ name: 'profile-slug', params: { slug: scope.row.reporter.slug } }">
|
||||
{{ scope.row.reporter.name }}
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</ds-table>
|
||||
<hc-empty
|
||||
v-else
|
||||
icon="alert"
|
||||
:message="$t('moderation.reports.empty')"
|
||||
/>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import query from '~/graphql/ModerationListQuery.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmpty
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Report: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
type: ' ',
|
||||
name: ' ',
|
||||
reporter: this.$t('moderation.reports.reporter')
|
||||
// actions: ' '
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
Report: {
|
||||
query,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -6,6 +6,14 @@
|
||||
class="post-card"
|
||||
>
|
||||
<hc-author :post="post" />
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
context="contribution"
|
||||
:item-id="post.id"
|
||||
:name="post.title"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="small" />
|
||||
<!-- Content -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
@ -82,6 +90,15 @@
|
||||
<ds-space margin-bottom="x-small">
|
||||
<hc-author :post="comment" />
|
||||
</ds-space>
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
context="comment"
|
||||
style="float-right"
|
||||
:item-id="comment.id"
|
||||
:name="comment.author.name"
|
||||
/>
|
||||
</no-ssr>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<div
|
||||
@ -110,6 +127,7 @@
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
import HcAuthor from '~/components/Author.vue'
|
||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
@ -122,7 +140,8 @@ export default {
|
||||
components: {
|
||||
HcAuthor,
|
||||
HcShoutButton,
|
||||
HcEmpty
|
||||
HcEmpty,
|
||||
ContentMenu
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
@ -221,28 +240,41 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.post-card {
|
||||
// max-width: 800px;
|
||||
margin: auto;
|
||||
.page-name-post-slug {
|
||||
.content-menu {
|
||||
float: right;
|
||||
margin-right: -$space-x-small;
|
||||
margin-top: -$space-large;
|
||||
}
|
||||
|
||||
.comments {
|
||||
margin-top: $space-small;
|
||||
.post-card {
|
||||
// max-width: 800px;
|
||||
margin: auto;
|
||||
|
||||
.comment {
|
||||
.comments {
|
||||
margin-top: $space-small;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-card-image {
|
||||
img {
|
||||
max-height: 300px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
.comment {
|
||||
margin-top: $space-small;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-card-image {
|
||||
img {
|
||||
max-height: 300px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-card-footer {
|
||||
padding: 0;
|
||||
|
||||
.ds-section {
|
||||
padding: $space-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ds-card-footer {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -17,6 +17,14 @@
|
||||
class="profile-avatar"
|
||||
size="120px"
|
||||
/>
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
placement="bottom-end"
|
||||
context="user"
|
||||
:item-id="user.id"
|
||||
:name="user.name"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin="small">
|
||||
<ds-heading
|
||||
tag="h3"
|
||||
@ -284,6 +292,7 @@ import HcCountTo from '~/components/CountTo.vue'
|
||||
import HcBadges from '~/components/Badges.vue'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -293,7 +302,8 @@ export default {
|
||||
HcCountTo,
|
||||
HcBadges,
|
||||
HcLoadMore,
|
||||
HcEmpty
|
||||
HcEmpty,
|
||||
ContentMenu
|
||||
},
|
||||
transition: {
|
||||
name: 'slide-up',
|
||||
@ -402,6 +412,14 @@ export default {
|
||||
border: #fff 5px solid;
|
||||
}
|
||||
|
||||
.page-name-profile-slug {
|
||||
.ds-flex-item:first-child .content-menu {
|
||||
position: absolute;
|
||||
top: $space-x-small;
|
||||
right: $space-x-small;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-top-navigation {
|
||||
position: sticky;
|
||||
top: 53px;
|
||||
@ -416,10 +434,10 @@ export default {
|
||||
&.ds-tab-nav-item-active {
|
||||
border-bottom: 3px solid #17b53f;
|
||||
&:first-child {
|
||||
border-bottom-left-radius: $border-radius-large;
|
||||
border-bottom-left-radius: $border-radius-x-large;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom-right-radius: $border-radius-large;
|
||||
border-bottom-right-radius: $border-radius-x-large;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,13 +4,13 @@
|
||||
{{ $t('settings.name') }}
|
||||
</ds-heading>
|
||||
<ds-flex gutter="small">
|
||||
<ds-flex-item :width="{ base: '200px' }">
|
||||
<ds-flex-item :width="{ base: '100%', md: '200px' }">
|
||||
<ds-menu
|
||||
:routes="routes"
|
||||
:is-exact="() => true"
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', md: 1 }">
|
||||
<transition
|
||||
name="slide-up"
|
||||
appear
|
||||
|
||||
22
store/modal.js
Normal file
22
store/modal.js
Normal file
@ -0,0 +1,22 @@
|
||||
export const state = () => {
|
||||
return {
|
||||
open: null,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
SET_OPEN(state, ctx) {
|
||||
state.open = ctx.name || null
|
||||
state.data = ctx.data || {}
|
||||
}
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
open(state) {
|
||||
return state.open
|
||||
},
|
||||
data(state) {
|
||||
return state.data
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"portal-vue": "^1.5.1",
|
||||
"vue": "^2.5.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
$border-radius: $border-radius-large;
|
||||
$border-radius: $border-radius-x-large;
|
||||
|
||||
.ds-card {
|
||||
@include reset;
|
||||
@ -7,7 +7,7 @@ $border-radius: $border-radius-large;
|
||||
flex-direction: column;
|
||||
background-color: $background-color-base;
|
||||
color: $text-color-base;
|
||||
box-shadow: $box-shadow-large;
|
||||
box-shadow: $box-shadow-base;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
185
styleguide/src/system/components/layout/Modal/Modal.vue
Normal file
185
styleguide/src/system/components/layout/Modal/Modal.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div>
|
||||
<portal to="modal">
|
||||
<div :key="key" class="ds-modal-wrapper">
|
||||
<transition name="ds-transition-fade" appear>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="ds-modal-backdrop"
|
||||
ref="backdrop"
|
||||
@click="backdropHandler"
|
||||
>
|
||||
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="ds-transition-modal-appear" appear>
|
||||
<ds-card
|
||||
v-if="isOpen"
|
||||
class="ds-modal"
|
||||
:class="[extended && 'ds-modal-extended']"
|
||||
:header="title"
|
||||
tableindex="-1"
|
||||
role="dialog"
|
||||
ref="modal"
|
||||
style="display: block"
|
||||
>
|
||||
<ds-button
|
||||
v-if="!force"
|
||||
class="ds-modal-close"
|
||||
ghost
|
||||
size="small"
|
||||
icon="close"
|
||||
aria-hidden="true"
|
||||
@click="cancel('close')"
|
||||
/>
|
||||
<!-- @slot Modal content -->
|
||||
<slot ref="modalBody"/>
|
||||
<template slot="footer">
|
||||
<!-- @slot Modal footer with action buttons -->
|
||||
<slot
|
||||
name="footer"
|
||||
:confirm="confirm"
|
||||
:cancel="cancel"
|
||||
:cancelLabel="cancelLabel"
|
||||
:confirmLabel="confirmLabel"
|
||||
>
|
||||
<ds-button ghost icon="close" @click.prevent="cancel('cancel')">{{ cancelLabel }}</ds-button>
|
||||
<ds-button primary icon="check" @click.prevent="confirm('confirm')">{{ confirmLabel }}</ds-button>
|
||||
</slot>
|
||||
</template>
|
||||
</ds-card>
|
||||
</transition>
|
||||
</div>
|
||||
</portal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import portal from 'portal-vue'
|
||||
Vue.use(portal)
|
||||
|
||||
/* eslint-disable no-empty */
|
||||
|
||||
/**
|
||||
* Simple Modal Component
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export default {
|
||||
name: 'DsModal',
|
||||
props: {
|
||||
/**
|
||||
* Modal title
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* Open state
|
||||
*/
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Force user input by disabeling the ESC key, close button and click on the backdrop
|
||||
*/
|
||||
force: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Allow closing without choosing action by ESC key, close button or click on the backdrop
|
||||
*/
|
||||
extended: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Cancel button label
|
||||
*/
|
||||
cancelLabel: {
|
||||
type: String,
|
||||
default: 'Cancel'
|
||||
},
|
||||
/**
|
||||
* Confirm button label
|
||||
*/
|
||||
confirmLabel: {
|
||||
type: String,
|
||||
default: 'Confirm'
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'isOpen',
|
||||
event: 'update:isOpen'
|
||||
},
|
||||
watch: {
|
||||
isOpen: {
|
||||
immediate: true,
|
||||
handler(show) {
|
||||
try {
|
||||
if (show) {
|
||||
this.$emit('opened')
|
||||
document
|
||||
.getElementsByTagName('body')[0]
|
||||
.classList
|
||||
.add('modal-open')
|
||||
} else {
|
||||
document
|
||||
.getElementsByTagName('body')[0]
|
||||
.classList
|
||||
.remove('modal-open')
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm (type = 'confirm') {
|
||||
this.$emit('confirm')
|
||||
this.close(type)
|
||||
},
|
||||
cancel (type = 'cancel') {
|
||||
this.$emit('cancel')
|
||||
this.close(type)
|
||||
},
|
||||
close (type) {
|
||||
this.$emit('update:isOpen', false)
|
||||
this.$emit('close', type)
|
||||
},
|
||||
backdropHandler () {
|
||||
if (!this.force) {
|
||||
this.cancel('backdrop')
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
// create random key string
|
||||
this.key = Math.random()
|
||||
.toString(36)
|
||||
.replace(/[^a-z]+/g, '')
|
||||
.substr(0, 5)
|
||||
},
|
||||
mounted() {
|
||||
const keydownListener = document.addEventListener('keydown', e => {
|
||||
if (this.isOpen && !this.force && e.keyCode === 27) {
|
||||
this.cancel('backdrop')
|
||||
}
|
||||
})
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('keydown', keydownListener)
|
||||
})
|
||||
|
||||
if (this.isOpen) {
|
||||
this.$emit('opened')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
66
styleguide/src/system/components/layout/Modal/demo.md
Normal file
66
styleguide/src/system/components/layout/Modal/demo.md
Normal file
@ -0,0 +1,66 @@
|
||||
## Basic Modal
|
||||
|
||||
Basic modal usage
|
||||
|
||||
You will need to add the portal-target to the end of your html body to get the modal working properly
|
||||
```html
|
||||
<!-- put the following tag as last element to your html body / layout -->
|
||||
<!-- make sure you only include it once! -->
|
||||
<portal-target name="modal" style="position: absolute" />
|
||||
```
|
||||
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<ds-modal
|
||||
v-model="isOpen"
|
||||
title="Modal Title"
|
||||
>
|
||||
<p>Hello World</p>
|
||||
</ds-modal>
|
||||
<ds-button primary icon="rocket" @click="isOpen = true">Open Modal</ds-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Customize button labels
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<ds-modal
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
title="Custom Button Labels"
|
||||
force
|
||||
extended
|
||||
confirm-label="All right"
|
||||
cancel-label="Please not"
|
||||
>
|
||||
<p>Culpa amet sunt aperiam ratione est sed. Molestiae minus doloremque libero. Beatae nam repellendus aliquid maxime.</p>
|
||||
<p>Sint quasi provident natus id earum debitis. Et facilis a iure ullam. Velit autem eveniet ea reprehenderit ducimus doloribus earum quo.</p>
|
||||
<p>Consequatur ratione repudiandae aliquid ea. Ut eum architecto assumenda. Autem eaque provident quia et.</p>
|
||||
<p>Eaque quia aut dolorum sunt ea consequuntur. Labore reprehenderit placeat pariatur molestiae sit laborum nostrum. Deserunt est commodi et suscipit tenetur ipsa voluptas cupiditate. Porro laborum quidem ut corrupti. Dolorum et est placeat qui.</p>
|
||||
<p>Adipisci beatae cumque esse harum. Error quis nulla illo nemo est. Enim est quis explicabo voluptatem. Omnis maxime qui similique consequatur voluptatibus. Est necessitatibus iure aliquid omnis eum. Ut voluptatibus vel error exercitationem temporibus qui expedita.</p>
|
||||
</ds-modal>
|
||||
<ds-button primary icon="rocket" @click="isOpen = true">Open Modal</ds-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
100
styleguide/src/system/components/layout/Modal/style.scss
Normal file
100
styleguide/src/system/components/layout/Modal/style.scss
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
.ds-modal-wrapper {
|
||||
padding: $space-base;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ds-modal {
|
||||
position: fixed;
|
||||
z-index: $z-index-modal;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
width: calc(90vw - 40px);
|
||||
height: auto;
|
||||
max-height: 90vh;
|
||||
box-shadow: $box-shadow-x-large;
|
||||
|
||||
&.ds-modal-extended {
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-modal .ds-card-header {
|
||||
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 30px;
|
||||
background: linear-gradient(rgba(255,255,255,1), rgba(255,255,255,0));
|
||||
position: absolute;
|
||||
width: calc(100% - 10px);
|
||||
bottom: -30px;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-modal-close {
|
||||
position: absolute;
|
||||
top: $space-small;
|
||||
right: $space-small;
|
||||
}
|
||||
.ds-modal .ds-card-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
min-height: 50px;
|
||||
max-height: 50vh;
|
||||
padding-bottom: $space-large !important;
|
||||
}
|
||||
.ds-modal footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: visible;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
background-color: $background-color-softer;
|
||||
padding: $space-small;
|
||||
|
||||
& > button {
|
||||
margin-left: $space-x-small;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
height: 45px;
|
||||
background: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,.9));
|
||||
position: absolute;
|
||||
width: calc(100% - 10px);
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: -45px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.ds-modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: $z-index-modal - 1;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.ds-transition-modal-appear-enter-active {
|
||||
opacity: 1;
|
||||
transition: all 200ms $ease-out-bounce;
|
||||
transform: translate3d(-50%, -50%, 0) scale(1);
|
||||
}
|
||||
.ds-transition-modal-appear-enter,
|
||||
.ds-transition-modal-appear-leave-active {
|
||||
opacity: 0;
|
||||
transform: translate3d(-50%, -50%, 0) scale(0.8);
|
||||
}
|
||||
5
styleguide/src/system/icons/svg/flag.svg
Executable file
5
styleguide/src/system/icons/svg/flag.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>flag</title>
|
||||
<path d="M5 5h12v14h-10v10h-2v-24zM18 8h9v14h-9v-14z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 220 B |
@ -16,3 +16,5 @@
|
||||
@import "./shared/background";
|
||||
@import "./shared/spacing";
|
||||
@import "./shared/form";
|
||||
@import "./shared/transitions";
|
||||
@import "./shared/animations";
|
||||
|
||||
19
styleguide/src/system/styles/shared/_animations.scss
Normal file
19
styleguide/src/system/styles/shared/_animations.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@keyframes ds-animation-shake {
|
||||
from, to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
transform: translate3d(-5px, 0, 0);
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
transform: translate3d(5px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.ds-animated {
|
||||
animation-duration: 0.8s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
.ds-animation-shake {
|
||||
animation-name: ds-animation-shake;
|
||||
}
|
||||
66
styleguide/src/system/styles/shared/_transitions.scss
Normal file
66
styleguide/src/system/styles/shared/_transitions.scss
Normal file
@ -0,0 +1,66 @@
|
||||
$easeOut: cubic-bezier(0.19, 1, 0.22, 1);
|
||||
|
||||
// slide up ease
|
||||
.ds-transition-slide-up-enter-active {
|
||||
transition: all 500ms $easeOut;
|
||||
transition-delay: 20ms;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.ds-transition-slide-up-enter,
|
||||
.ds-transition-slide-up-leave-active {
|
||||
opacity: 0;
|
||||
box-shadow: none;
|
||||
transform: translateY(15px);
|
||||
}
|
||||
|
||||
// slide next / prev
|
||||
.ds-transition-slide-next-enter-active,
|
||||
.ds-transition-slide-prev-enter-active {
|
||||
transition: transform 500ms $easeOut, opacity 500ms $easeOut;
|
||||
transition-delay: 100ms;
|
||||
opacity: 1;
|
||||
}
|
||||
.ds-transition-slide-next-enter,
|
||||
.ds-transition-slide-next-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
.ds-transition-slide-prev-enter,
|
||||
.ds-transition-slide-prev-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
.ds-transition-slide-next-leave-active,
|
||||
.ds-transition-slide-prev-leave-active {
|
||||
display: none;
|
||||
}
|
||||
.ds-transition-slide-next-leave-active,
|
||||
.ds-transition-slide-prev-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(-2px);
|
||||
transition: transform 100ms $easeOut, opacity 100ms $easeOut;
|
||||
}
|
||||
|
||||
.ds-transition-fade-delayed-leave-active {
|
||||
transition: opacity 0ms;
|
||||
transition-delay: 0ms;
|
||||
}
|
||||
.ds-transition-fade-delayed-enter-active {
|
||||
transition: opacity 300ms ease-out;
|
||||
transition-delay: 100ms;
|
||||
opacity: 1;
|
||||
}
|
||||
.ds-transition-fade-delayed-enter,
|
||||
.ds-transition-fade-delayed-leave-active {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.ds-transition-fade-enter-active,
|
||||
.ds-transition-fade-leave-active {
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
.ds-transition-fade-enter,
|
||||
.ds-transition-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
#
|
||||
|
||||
props:
|
||||
- name: box-shadow-x-large
|
||||
value: "0 40px 120px 0 rgba(0, 0, 0, .8)"
|
||||
- name: box-shadow-large
|
||||
value: "0 20px 60px 0 rgba(0, 0, 0, .8)"
|
||||
- name: box-shadow-base
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
|
||||
props:
|
||||
- name: border-radius-x-large
|
||||
value: "8px"
|
||||
value: "5px"
|
||||
- name: border-radius-large
|
||||
value: "6px"
|
||||
value: "4px"
|
||||
- name: border-radius-base
|
||||
value: "3px"
|
||||
value: "4px"
|
||||
- name: border-radius-rounded
|
||||
value: "2em"
|
||||
- name: border-radius-circle
|
||||
|
||||
@ -8039,6 +8039,11 @@ pn@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
|
||||
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
|
||||
|
||||
portal-vue@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-1.5.1.tgz#6bed79ef168d9676bb79f41d43c5cd4cedf54dbc"
|
||||
integrity sha512-7T0K+qyY8bnjnEpQTiLbGsUaGlFcemK9gLurVSr6x1/qzr2HkHDNCOz5i+xhuTD1CrXckf/AGeCnLzvmAHMOHw==
|
||||
|
||||
portfinder@^1.0.19, portfinder@^1.0.9:
|
||||
version "1.0.19"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.19.tgz#07e87914a55242dcda5b833d42f018d6875b595f"
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@ -8785,6 +8785,11 @@ popper.js@^1.12.9:
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.5.tgz#98abcce7c7c34c4ee47fcbc6b3da8af2c0a127bc"
|
||||
integrity sha512-fs4Sd8bZLgEzrk8aS7Em1qh+wcawtE87kRUJQhK6+LndyV1HerX7+LURzAylVaTyWIn5NTB/lyjnWqw/AZ6Yrw==
|
||||
|
||||
portal-vue@~1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-1.5.1.tgz#6bed79ef168d9676bb79f41d43c5cd4cedf54dbc"
|
||||
integrity sha512-7T0K+qyY8bnjnEpQTiLbGsUaGlFcemK9gLurVSr6x1/qzr2HkHDNCOz5i+xhuTD1CrXckf/AGeCnLzvmAHMOHw==
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
@ -11880,6 +11885,11 @@ vue-svg-loader@^0.11.0:
|
||||
loader-utils "^1.1.0"
|
||||
svg-to-vue "^0.3.0"
|
||||
|
||||
vue-sweetalert-icons@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-sweetalert-icons/-/vue-sweetalert-icons-3.2.0.tgz#2926d3af5590b81c0ba3b104212922fc1709396d"
|
||||
integrity sha512-N18uG8++ZfdCnXO0gHNTmwpB2mAE8WWrwjGeWGa8CnHu6l1emn4RG6E8r1P9crVJ+fx3R9gTUezC+cdVu0mN7w==
|
||||
|
||||
vue-template-compiler@^2.5.17:
|
||||
version "2.5.17"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz#52a4a078c327deb937482a509ae85c06f346c3cb"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user