-
-
{{ $t(`contribution.category.name.${category.slug}`) }}
-
+
diff --git a/webapp/components/CommentForm/CommentForm.spec.js b/webapp/components/CommentForm/CommentForm.spec.js
index 47bc01982..5dde84008 100644
--- a/webapp/components/CommentForm/CommentForm.spec.js
+++ b/webapp/components/CommentForm/CommentForm.spec.js
@@ -74,7 +74,7 @@ describe('CommentForm.vue', () => {
it('calls `clear` method when the cancel button is clicked', async () => {
wrapper.vm.updateEditorContent('ok')
- await wrapper.find('.cancelBtn').trigger('submit')
+ await wrapper.find('[data-test="cancel-button"]').trigger('submit')
expect(cancelMethodSpy).toHaveBeenCalledTimes(1)
})
@@ -162,13 +162,13 @@ describe('CommentForm.vue', () => {
describe('cancel button is clicked', () => {
it('calls `closeEditWindow` method', async () => {
wrapper.vm.updateEditorContent('ok')
- await wrapper.find('.cancelBtn').trigger('submit')
+ await wrapper.find('[data-test="cancel-button"]').trigger('submit')
expect(closeMethodSpy).toHaveBeenCalledTimes(1)
})
it('emits `showEditCommentMenu` event', async () => {
wrapper.vm.updateEditorContent('ok')
- await wrapper.find('.cancelBtn').trigger('submit')
+ await wrapper.find('[data-test="cancel-button"]').trigger('submit')
expect(wrapper.emitted('showEditCommentMenu')).toEqual([[false]])
})
})
diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue
index 6cdd08af3..063a3d599 100644
--- a/webapp/components/CommentForm/CommentForm.vue
+++ b/webapp/components/CommentForm/CommentForm.vue
@@ -1,28 +1,22 @@
-
- {{ $t('common.comment', null, 0) }}
-
+
+
@@ -50,3 +49,15 @@ export default {
},
}
+
+
diff --git a/webapp/components/ContentMenu/ContentMenu.spec.js b/webapp/components/ContentMenu/ContentMenu.spec.js
index 6ca557a18..f079f6240 100644
--- a/webapp/components/ContentMenu/ContentMenu.spec.js
+++ b/webapp/components/ContentMenu/ContentMenu.spec.js
@@ -46,7 +46,7 @@ describe('ContentMenu.vue', () => {
store,
localVue,
})
- menuToggle = wrapper.find('.content-menu-trigger')
+ menuToggle = wrapper.find('[data-test="content-menu-button"]')
menuToggle.trigger('click')
return wrapper
}
diff --git a/webapp/components/ContentMenu/ContentMenu.vue b/webapp/components/ContentMenu/ContentMenu.vue
index ab1cc99f5..a22bc3267 100644
--- a/webapp/components/ContentMenu/ContentMenu.vue
+++ b/webapp/components/ContentMenu/ContentMenu.vue
@@ -2,9 +2,14 @@
-
-
-
+
diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue
index a8909f16a..80b52b61c 100644
--- a/webapp/components/ContributionForm/ContributionForm.vue
+++ b/webapp/components/ContributionForm/ContributionForm.vue
@@ -100,17 +100,12 @@
-
+
{{ $t('actions.cancel') }}
-
-
+
+
{{ $t('actions.save') }}
-
+
diff --git a/webapp/components/DeleteData/DeleteData.spec.js b/webapp/components/DeleteData/DeleteData.spec.js
index abcdf9101..484fd5492 100644
--- a/webapp/components/DeleteData/DeleteData.spec.js
+++ b/webapp/components/DeleteData/DeleteData.spec.js
@@ -81,7 +81,7 @@ describe('DeleteData.vue', () => {
})
it('does not call the delete user mutation if deleteEnabled is false', () => {
- deleteAccountBtn = wrapper.find('.ds-button-danger')
+ deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
@@ -90,7 +90,7 @@ describe('DeleteData.vue', () => {
beforeEach(() => {
enableDeletionInput = wrapper.find('.enable-deletion-input input')
enableDeletionInput.setValue(deleteAccountName)
- deleteAccountBtn = wrapper.find('.ds-button-danger')
+ deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
})
it('if deleteEnabled is true and only deletes user by default', () => {
@@ -168,7 +168,7 @@ describe('DeleteData.vue', () => {
it('shows an error toaster when the mutation rejects', async () => {
enableDeletionInput = wrapper.find('.enable-deletion-input input')
enableDeletionInput.setValue(deleteAccountName)
- deleteAccountBtn = wrapper.find('.ds-button-danger')
+ deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
await deleteAccountBtn.trigger('click')
// second submission causes mutation to reject
await deleteAccountBtn.trigger('click')
diff --git a/webapp/components/DeleteData/DeleteData.vue b/webapp/components/DeleteData/DeleteData.vue
index 045d00f26..66a31205a 100644
--- a/webapp/components/DeleteData/DeleteData.vue
+++ b/webapp/components/DeleteData/DeleteData.vue
@@ -55,16 +55,19 @@
-
+
-
+
{{ $t('settings.deleteUserAccount.name') }}
-
+
@@ -82,7 +85,6 @@ export default {
return {
deleteContributions: false,
deleteComments: false,
- deleteEnabled: false,
enableDeletionValue: null,
}
},
@@ -90,16 +92,14 @@ export default {
...mapGetters({
currentUser: 'auth/user',
}),
+ deleteEnabled() {
+ return this.enableDeletionValue === this.currentUser.name
+ },
},
methods: {
...mapActions({
logout: 'auth/logout',
}),
- enableDeletion() {
- if (this.enableDeletionValue === this.currentUser.name) {
- this.deleteEnabled = true
- }
- },
handleSubmit() {
const resourceArgs = []
if (this.deleteContributions) {
diff --git a/webapp/components/DonationInfo/DonationInfo.spec.js b/webapp/components/DonationInfo/DonationInfo.spec.js
index 307d997c4..157801dcf 100644
--- a/webapp/components/DonationInfo/DonationInfo.spec.js
+++ b/webapp/components/DonationInfo/DonationInfo.spec.js
@@ -32,7 +32,7 @@ describe('DonationInfo.vue', () => {
it('displays a call to action button', () => {
expect(
Wrapper()
- .find('.ds-button')
+ .find('.base-button')
.text(),
).toBe('donations.donate-now')
})
diff --git a/webapp/components/DonationInfo/DonationInfo.vue b/webapp/components/DonationInfo/DonationInfo.vue
index 10f42e880..e138bef97 100644
--- a/webapp/components/DonationInfo/DonationInfo.vue
+++ b/webapp/components/DonationInfo/DonationInfo.vue
@@ -2,7 +2,7 @@
- {{ $t('donations.donate-now') }}
+ {{ $t('donations.donate-now') }}
diff --git a/webapp/components/DropdownFilter/DropdownFilter.vue b/webapp/components/DropdownFilter/DropdownFilter.vue
index bfa78e709..f7bcf507d 100644
--- a/webapp/components/DropdownFilter/DropdownFilter.vue
+++ b/webapp/components/DropdownFilter/DropdownFilter.vue
@@ -65,10 +65,6 @@ export default {
}
}
-.dropdown-arrow {
- font-size: $font-size-xx-small;
-}
-
.dropdown-menu {
user-select: none;
display: flex;
diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue
index 6c8a1908a..235437c32 100644
--- a/webapp/components/Editor/Editor.vue
+++ b/webapp/components/Editor/Editor.vue
@@ -326,85 +326,4 @@ li > p {
margin: 0 0 $space-x-small;
}
}
-
-.ProseMirror[contenteditable='false'] {
- .embed-close-button {
- display: none;
- }
-}
-
-.embed-container {
- position: relative;
- padding: 0;
- margin: $space-small auto;
- overflow: hidden;
- border-radius: $border-radius-base;
- border: 1px solid $color-neutral-70;
- background-color: $color-neutral-90;
-}
-
-.embed-content {
- width: 100%;
- height: 100%;
-
- h4 {
- margin: $space-small 0 0 $space-small;
- }
-
- p,
- a {
- display: block;
- margin: 0 0 0 $space-small;
- }
-}
-
-.embed-preview-image {
- width: 100%;
- height: auto;
- max-height: 450px;
-}
-
-.embed-preview-image--clickable {
- cursor: pointer;
-}
-
-.embed-html {
- width: 100%;
-
- iframe {
- width: 100%;
- }
-}
-
-.embed-overlay {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-
- padding: $space-large;
- background-color: $color-neutral-100;
-}
-
-.embed-buttons {
- button {
- margin-right: $space-small;
- }
-}
-
-.embed-checkbox {
- display: flex;
-
- input {
- margin-right: $space-small;
- }
-}
-
-.embed-close-button {
- position: absolute;
- top: $space-x-small;
- right: $space-x-small;
- background-color: rgba(250, 249, 250, 0.6);
-}
diff --git a/webapp/components/Editor/MenuBarButton.vue b/webapp/components/Editor/MenuBarButton.vue
index 49e480ca5..2543352ca 100644
--- a/webapp/components/Editor/MenuBarButton.vue
+++ b/webapp/components/Editor/MenuBarButton.vue
@@ -1,7 +1,7 @@
-
+
{{ label }}
-
+
+
+
diff --git a/webapp/components/EmotionButton/EmotionButton.vue b/webapp/components/EmotionButton/EmotionButton.vue
new file mode 100644
index 000000000..f6a4b4938
--- /dev/null
+++ b/webapp/components/EmotionButton/EmotionButton.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp/components/Emotions/Emotions.vue b/webapp/components/Emotions/Emotions.vue
index 3be7ee790..96389d10d 100644
--- a/webapp/components/Emotions/Emotions.vue
+++ b/webapp/components/Emotions/Emotions.vue
@@ -1,27 +1,26 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/webapp/components/EmotionsButton/EmotionsButton.vue b/webapp/components/EmotionsButton/EmotionsButton.vue
deleted file mode 100644
index 7536c1632..000000000
--- a/webapp/components/EmotionsButton/EmotionsButton.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/webapp/components/EnterNonce/EnterNonce.vue b/webapp/components/EnterNonce/EnterNonce.vue
index d936544ad..eb44c3235 100644
--- a/webapp/components/EnterNonce/EnterNonce.vue
+++ b/webapp/components/EnterNonce/EnterNonce.vue
@@ -1,30 +1,27 @@
-
-
-
-
-
- {{ $t('components.enter-nonce.form.description') }}
-
-
-
- {{ $t('components.enter-nonce.form.next') }}
-
-
+
+
+
+ {{ $t('components.enter-nonce.form.description') }}
+
+
+ {{ $t('components.enter-nonce.form.next') }}
+
-
+
+
+
diff --git a/webapp/components/FilterMenu/FilterMenu.spec.js b/webapp/components/FilterMenu/FilterMenu.spec.js
index 079ef5487..d70af323f 100644
--- a/webapp/components/FilterMenu/FilterMenu.spec.js
+++ b/webapp/components/FilterMenu/FilterMenu.spec.js
@@ -39,7 +39,7 @@ describe('FilterMenu.vue', () => {
describe('click "clear-search-button" button', () => {
it('emits clearSearch', () => {
- wrapper.find({ name: 'clear-search-button' }).trigger('click')
+ wrapper.find('[name="clear-search-button"]').trigger('click')
expect(wrapper.emitted().clearSearch).toHaveLength(1)
})
})
diff --git a/webapp/components/FilterMenu/FilterMenu.vue b/webapp/components/FilterMenu/FilterMenu.vue
index e4c689263..e56925e56 100644
--- a/webapp/components/FilterMenu/FilterMenu.vue
+++ b/webapp/components/FilterMenu/FilterMenu.vue
@@ -6,15 +6,16 @@
-
diff --git a/webapp/components/FilterPosts/CategoriesFilterMenuItems.vue b/webapp/components/FilterPosts/CategoriesFilterMenuItems.vue
index 72835a660..9189f417d 100644
--- a/webapp/components/FilterPosts/CategoriesFilterMenuItems.vue
+++ b/webapp/components/FilterPosts/CategoriesFilterMenuItems.vue
@@ -12,10 +12,11 @@
-
@@ -37,10 +38,11 @@
-
diff --git a/webapp/components/FilterPosts/FilterPosts.spec.js b/webapp/components/FilterPosts/FilterPosts.spec.js
index f27074b7b..3dd1cebef 100644
--- a/webapp/components/FilterPosts/FilterPosts.spec.js
+++ b/webapp/components/FilterPosts/FilterPosts.spec.js
@@ -92,7 +92,7 @@ describe('FilterPosts.vue', () => {
it('starts with all categories button active', () => {
const wrapper = openFilterPosts()
allCategoriesButton = wrapper.findAll('button').at(1)
- expect(allCategoriesButton.attributes().class).toContain('ds-button-primary')
+ expect(allCategoriesButton.attributes().class).toContain('--filled')
})
it('calls TOGGLE_CATEGORY when clicked', () => {
@@ -111,35 +111,35 @@ describe('FilterPosts.vue', () => {
expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en')
})
- it('sets category button attribute `primary` when corresponding category is filtered', () => {
+ it('sets category button attribute `filled` when corresponding category is filtered', () => {
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
const wrapper = openFilterPosts()
democracyAndPoliticsButton = wrapper.findAll('button').at(4)
- expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary')
+ expect(democracyAndPoliticsButton.attributes().class).toContain('--filled')
})
- it('sets language button attribute `primary` when corresponding language is filtered', () => {
+ it('sets language button attribute `filled` when corresponding language is filtered', () => {
getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es'])
const wrapper = openFilterPosts()
spanishButton = wrapper
.findAll('button.language-buttons')
.at(languages.findIndex(l => l.code === 'es'))
- expect(spanishButton.attributes().class).toContain('ds-button-primary')
+ expect(spanishButton.attributes().class).toContain('--filled')
})
- it('sets "filter-by-followed-authors-only" button attribute `primary`', () => {
+ it('sets "filter-by-followed" button attribute `filled`', () => {
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
const wrapper = openFilterPosts()
- expect(
- wrapper.find({ name: 'filter-by-followed-authors-only' }).classes('ds-button-primary'),
- ).toBe(true)
+ expect(wrapper.find('.base-button[data-test="filter-by-followed"]').classes('--filled')).toBe(
+ true,
+ )
})
- describe('click "filter-by-followed-authors-only" button', () => {
+ describe('click "filter-by-followed" button', () => {
let wrapper
beforeEach(() => {
wrapper = openFilterPosts()
- wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
+ wrapper.find('.base-button[data-test="filter-by-followed"]').trigger('click')
})
it('calls TOGGLE_FILTER_BY_FOLLOWED', () => {
@@ -150,7 +150,7 @@ describe('FilterPosts.vue', () => {
describe('click on an "emotions-buttons" button', () => {
it('calls TOGGLE_EMOTION when clicked', () => {
const wrapper = openFilterPosts()
- happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
+ happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
happyEmotionButton.trigger('click')
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
})
@@ -158,7 +158,7 @@ describe('FilterPosts.vue', () => {
it('sets the attribute `src` to colorized image', () => {
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
const wrapper = openFilterPosts()
- happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
+ happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
const happyEmotionButtonImage = happyEmotionButton.find('img')
expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
})
diff --git a/webapp/components/FilterPosts/FilterPosts.vue b/webapp/components/FilterPosts/FilterPosts.vue
index b83e99fd8..787751004 100644
--- a/webapp/components/FilterPosts/FilterPosts.vue
+++ b/webapp/components/FilterPosts/FilterPosts.vue
@@ -1,15 +1,15 @@
-
-
+
diff --git a/webapp/components/FilterPosts/GeneralFilterMenuItems.vue b/webapp/components/FilterPosts/GeneralFilterMenuItems.vue
index 96b050713..f1cf6adae 100644
--- a/webapp/components/FilterPosts/GeneralFilterMenuItems.vue
+++ b/webapp/components/FilterPosts/GeneralFilterMenuItems.vue
@@ -6,58 +6,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/webapp/components/LoadMore.vue b/webapp/components/LoadMore.vue
deleted file mode 100644
index ff8d4e6c4..000000000
--- a/webapp/components/LoadMore.vue
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- {{ $t('actions.loadMore') }}
-
-
-
-
-
diff --git a/webapp/components/LoginForm/LoginForm.vue b/webapp/components/LoginForm/LoginForm.vue
index d61a5675a..c140edb47 100644
--- a/webapp/components/LoginForm/LoginForm.vue
+++ b/webapp/components/LoginForm/LoginForm.vue
@@ -50,16 +50,9 @@
{{ $t('login.forgotPassword') }}
-
+
{{ $t('login.login') }}
-
+
{{ $t('login.no-account') }}
{{ $t('login.register') }}
@@ -113,6 +106,11 @@ export default {
}
.login-card {
position: relative;
+
+ .base-button {
+ display: block;
+ width: 100%;
+ }
}
.login-locale-switch {
position: absolute;
diff --git a/webapp/components/Modal/ConfirmModal.vue b/webapp/components/Modal/ConfirmModal.vue
index 8297e6d0f..9eb81b5ea 100644
--- a/webapp/components/Modal/ConfirmModal.vue
+++ b/webapp/components/Modal/ConfirmModal.vue
@@ -10,11 +10,16 @@
-
+
{{ $t(modalData.buttons.cancel.textIdent) }}
-
+
-
{{ $t(modalData.buttons.confirm.textIdent) }}
-
+
diff --git a/webapp/components/Modal/DisableModal.vue b/webapp/components/Modal/DisableModal.vue
index d80ec0f55..19ef64332 100644
--- a/webapp/components/Modal/DisableModal.vue
+++ b/webapp/components/Modal/DisableModal.vue
@@ -4,11 +4,10 @@
- {{ $t('disable.cancel') }}
-
-
+ {{ $t('disable.cancel') }}
+
{{ $t('disable.submit') }}
-
+
diff --git a/webapp/components/Modal/ReportModal.vue b/webapp/components/Modal/ReportModal.vue
index 00fed2646..f6aca5f86 100644
--- a/webapp/components/Modal/ReportModal.vue
+++ b/webapp/components/Modal/ReportModal.vue
@@ -29,12 +29,13 @@
-
+
{{ $t('report.cancel') }}
-
+
-
{{ $t('report.submit') }}
-
+
@@ -161,7 +162,7 @@ export default {
.ds-modal {
max-width: 600px !important;
}
-.ds-radio-option:not(.ds-button) {
+.ds-radio-option {
width: 100% !important;
}
.ds-radio-option-label {
diff --git a/webapp/components/NotificationMenu/NotificationMenu.spec.js b/webapp/components/NotificationMenu/NotificationMenu.spec.js
index 01c972d05..8020c8bb4 100644
--- a/webapp/components/NotificationMenu/NotificationMenu.spec.js
+++ b/webapp/components/NotificationMenu/NotificationMenu.spec.js
@@ -1,11 +1,11 @@
-import { config, shallowMount } from '@vue/test-utils'
+import { config, mount } from '@vue/test-utils'
import NotificationMenu from './NotificationMenu'
const localVue = global.localVue
localVue.filter('truncate', string => string)
-config.stubs.dropdown = ' '
+config.stubs.dropdown = ' '
describe('NotificationMenu.vue', () => {
let wrapper
@@ -22,9 +22,9 @@ describe('NotificationMenu.vue', () => {
}
})
- describe('shallowMount', () => {
+ describe('mount', () => {
const Wrapper = () => {
- return shallowMount(NotificationMenu, {
+ return mount(NotificationMenu, {
data,
mocks,
localVue,
@@ -33,7 +33,7 @@ describe('NotificationMenu.vue', () => {
it('counter displays 0', () => {
wrapper = Wrapper()
- expect(wrapper.find('ds-button-stub').text()).toEqual('0')
+ expect(wrapper.find('.count').text()).toEqual('0')
})
it('no dropdown is rendered', () => {
@@ -67,12 +67,12 @@ describe('NotificationMenu.vue', () => {
it('counter displays 0', () => {
wrapper = Wrapper()
- expect(wrapper.find('ds-button-stub').text()).toEqual('0')
+ expect(wrapper.find('.count').text()).toEqual('0')
})
- it('button is not primary', () => {
+ it('counter is not colored', () => {
wrapper = Wrapper()
- expect(wrapper.find('ds-button-stub').props('primary')).toBe(false)
+ expect(wrapper.find('.count').classes()).toContain('--inactive')
})
})
@@ -130,12 +130,12 @@ describe('NotificationMenu.vue', () => {
it('displays the number of unread notifications', () => {
wrapper = Wrapper()
- expect(wrapper.find('ds-button-stub').text()).toEqual('2')
+ expect(wrapper.find('.count').text()).toEqual('2')
})
- it('renders primary button', () => {
+ it('renders the counter in red', () => {
wrapper = Wrapper()
- expect(wrapper.find('ds-button-stub').props('primary')).toBe(true)
+ expect(wrapper.find('.count').classes()).toContain('--danger')
})
})
})
diff --git a/webapp/components/NotificationMenu/NotificationMenu.vue b/webapp/components/NotificationMenu/NotificationMenu.vue
index 413102915..a3b085db9 100644
--- a/webapp/components/NotificationMenu/NotificationMenu.vue
+++ b/webapp/components/NotificationMenu/NotificationMenu.vue
@@ -1,12 +1,12 @@
-
- {{ unreadNotificationsCount }}
-
+
+
+
-
- {{ unreadNotificationsCount }}
-
+
+
+
@@ -22,17 +22,20 @@
diff --git a/webapp/components/Password/Change.vue b/webapp/components/Password/Change.vue
index dabc53d6f..547aab6a1 100644
--- a/webapp/components/Password/Change.vue
+++ b/webapp/components/Password/Change.vue
@@ -24,9 +24,9 @@
/>
-
+
{{ $t('settings.security.change-password.button') }}
-
+
diff --git a/webapp/components/PasswordReset/ChangePassword.vue b/webapp/components/PasswordReset/ChangePassword.vue
index e45612171..ab3334c6d 100644
--- a/webapp/components/PasswordReset/ChangePassword.vue
+++ b/webapp/components/PasswordReset/ChangePassword.vue
@@ -24,9 +24,9 @@
/>
-
+
{{ $t('settings.security.change-password.button') }}
-
+
diff --git a/webapp/components/PasswordReset/Request.vue b/webapp/components/PasswordReset/Request.vue
index 30b86e8c2..5f4baf357 100644
--- a/webapp/components/PasswordReset/Request.vue
+++ b/webapp/components/PasswordReset/Request.vue
@@ -20,17 +20,16 @@
{{ $t('components.password-reset.request.form.description') }}
-
{{ $t('components.password-reset.request.form.submit') }}
-
+
diff --git a/webapp/components/PostCard/PostCard.vue b/webapp/components/PostCard/PostCard.vue
index f9c1fa325..d54a86367 100644
--- a/webapp/components/PostCard/PostCard.vue
+++ b/webapp/components/PostCard/PostCard.vue
@@ -192,6 +192,8 @@ export default {
}
.content-menu {
+ position: relative;
+ z-index: $z-index-post-card-link;
display: inline-block;
margin-left: $space-xx-small;
margin-right: -$space-x-small;
diff --git a/webapp/components/Registration/CreateUserAccount.vue b/webapp/components/Registration/CreateUserAccount.vue
index 695d5cac9..50967d240 100644
--- a/webapp/components/Registration/CreateUserAccount.vue
+++ b/webapp/components/Registration/CreateUserAccount.vue
@@ -102,10 +102,11 @@
v-html="$t('components.registration.signup.form.no-political')"
>
-
{{ $t('actions.save') }}
-
+
diff --git a/webapp/components/Registration/Signup.vue b/webapp/components/Registration/Signup.vue
index a6dd63d7b..8a9447a69 100644
--- a/webapp/components/Registration/Signup.vue
+++ b/webapp/components/Registration/Signup.vue
@@ -30,17 +30,16 @@
name="email"
icon="envelope"
/>
-
{{ $t('components.registration.signup.form.submit') }}
-
+
diff --git a/webapp/components/ReleaseModal/ReleaseModal.vue b/webapp/components/ReleaseModal/ReleaseModal.vue
index 82f800ddb..fef0d79d0 100644
--- a/webapp/components/ReleaseModal/ReleaseModal.vue
+++ b/webapp/components/ReleaseModal/ReleaseModal.vue
@@ -4,11 +4,10 @@
- {{ $t('release.cancel') }}
-
-
+ {{ $t('release.cancel') }}
+
{{ $t('release.submit') }}
-
+
diff --git a/webapp/components/ShoutButton.vue b/webapp/components/ShoutButton.vue
index 94d76005b..3e5ecec38 100644
--- a/webapp/components/ShoutButton.vue
+++ b/webapp/components/ShoutButton.vue
@@ -1,12 +1,11 @@
-
diff --git a/webapp/components/TeaserImage/TeaserImage.vue b/webapp/components/TeaserImage/TeaserImage.vue
index a08b9e0ef..816be076a 100644
--- a/webapp/components/TeaserImage/TeaserImage.vue
+++ b/webapp/components/TeaserImage/TeaserImage.vue
@@ -9,10 +9,18 @@
@vdropzone-thumbnail="transformImage"
>
-
+
{{ $t('contribution.teaserImage.cropperConfirm') }}
-
-
+
+
With Text
+
+
+
+
+ `,
+ }))
+
+ .add('circle', () => ({
+ components: { BaseButton },
+ template: `
+
+
+ EN
+
+
+
+ `,
+ }))
+
+ .add('danger', () => ({
+ components: { BaseButton },
+ template: `
+
+ Danger
+ Disabled
+ Loading
+
+ `,
+ }))
+
+ .add('filled', () => ({
+ components: { BaseButton },
+ template: `
+
+ Filled
+ Filled Danger
+ Disabled
+ Loading
+
+ `,
+ }))
+
+ .add('small', () => ({
+ components: { BaseButton },
+ template: `
+
+ Small
+ S
+
+ `,
+ }))
+
+ .add('ghost', () => ({
+ // TODO: add documentation --> ghost button should only be used for very special occasions
+ // e.g. for the ContentMenu + for the EditorMenuBarButtons
+ components: { BaseButton },
+ template: `
+
+
+
+ `,
+ }))
diff --git a/webapp/components/_new/generic/BaseButton/BaseButton.vue b/webapp/components/_new/generic/BaseButton/BaseButton.vue
new file mode 100644
index 000000000..5997ea8f1
--- /dev/null
+++ b/webapp/components/_new/generic/BaseButton/BaseButton.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
diff --git a/webapp/components/_new/generic/BaseIcon/BaseIcon.story.js b/webapp/components/_new/generic/BaseIcon/BaseIcon.story.js
index 321ebc0de..5ddfe4ff4 100644
--- a/webapp/components/_new/generic/BaseIcon/BaseIcon.story.js
+++ b/webapp/components/_new/generic/BaseIcon/BaseIcon.story.js
@@ -29,7 +29,7 @@ const iconStyles = `
font-size: 20px;
`
-storiesOf('BaseIcon', module)
+storiesOf('Generic/BaseIcon', module)
.addDecorator(helpers.layout)
.add('pure icon', () => ({
diff --git a/webapp/components/_new/generic/BaseIcon/BaseIcon.vue b/webapp/components/_new/generic/BaseIcon/BaseIcon.vue
index c7752b7ec..a1c34555e 100644
--- a/webapp/components/_new/generic/BaseIcon/BaseIcon.vue
+++ b/webapp/components/_new/generic/BaseIcon/BaseIcon.vue
@@ -37,6 +37,7 @@ export default {
diff --git a/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.story.js b/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.story.js
new file mode 100644
index 000000000..d61f02db4
--- /dev/null
+++ b/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.story.js
@@ -0,0 +1,11 @@
+import { storiesOf } from '@storybook/vue'
+import helpers from '~/storybook/helpers'
+import LoadingSpinner from './LoadingSpinner.vue'
+
+storiesOf('Generic/LoadingSpinner', module)
+ .addDecorator(helpers.layout)
+
+ .add('default', () => ({
+ components: { LoadingSpinner },
+ template: ' ',
+ }))
diff --git a/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.vue b/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.vue
new file mode 100644
index 000000000..529a029d9
--- /dev/null
+++ b/webapp/components/_new/generic/LoadingSpinner/LoadingSpinner.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
diff --git a/webapp/components/Paginate/Paginate.spec.js b/webapp/components/_new/generic/PaginationButtons/PaginationButtons.spec.js
similarity index 66%
rename from webapp/components/Paginate/Paginate.spec.js
rename to webapp/components/_new/generic/PaginationButtons/PaginationButtons.spec.js
index f542533fd..f214ba55e 100644
--- a/webapp/components/Paginate/Paginate.spec.js
+++ b/webapp/components/_new/generic/PaginationButtons/PaginationButtons.spec.js
@@ -1,39 +1,35 @@
import { mount } from '@vue/test-utils'
-import Paginate from './Paginate'
+import PaginationButtons from './PaginationButtons'
const localVue = global.localVue
-describe('Paginate.vue', () => {
- let propsData, wrapper, nextButton, backButton
-
- beforeEach(() => {
- propsData = {}
- })
+describe('PaginationButtons.vue', () => {
+ let propsData = {}
+ let wrapper
+ let nextButton
+ let backButton
const Wrapper = () => {
- return mount(Paginate, { propsData, localVue })
+ return mount(PaginationButtons, { propsData, localVue })
}
- describe('mount', () => {
- beforeEach(() => {
- wrapper = Wrapper()
- })
+ describe('mount', () => {
describe('next button', () => {
beforeEach(() => {
propsData.hasNext = true
wrapper = Wrapper()
- nextButton = wrapper.findAll('.ds-button').at(0)
+ nextButton = wrapper.find('[data-test="next-button"]')
})
it('is disabled by default', () => {
propsData = {}
wrapper = Wrapper()
- nextButton = wrapper.findAll('.ds-button').at(0)
+ nextButton = wrapper.find('[data-test="next-button"]')
expect(nextButton.attributes().disabled).toEqual('disabled')
})
- it('is not disabled if hasNext is true', () => {
+ it('is enabled if hasNext is true', () => {
expect(nextButton.attributes().disabled).toBeUndefined()
})
@@ -47,17 +43,17 @@ describe('Paginate.vue', () => {
beforeEach(() => {
propsData.hasPrevious = true
wrapper = Wrapper()
- backButton = wrapper.findAll('.ds-button').at(1)
+ backButton = wrapper.find('[data-test="previous-button"]')
})
it('is disabled by default', () => {
propsData = {}
wrapper = Wrapper()
- backButton = wrapper.findAll('.ds-button').at(1)
+ backButton = wrapper.find('[data-test="previous-button"]')
expect(backButton.attributes().disabled).toEqual('disabled')
})
- it('is not disabled if hasPrevious is true', () => {
+ it('is enabled if hasPrevious is true', () => {
expect(backButton.attributes().disabled).toBeUndefined()
})
diff --git a/webapp/components/Paginate/Paginate.story.js b/webapp/components/_new/generic/PaginationButtons/PaginationButtons.story.js
similarity index 74%
rename from webapp/components/Paginate/Paginate.story.js
rename to webapp/components/_new/generic/PaginationButtons/PaginationButtons.story.js
index 6efc9353f..79d0fad69 100644
--- a/webapp/components/Paginate/Paginate.story.js
+++ b/webapp/components/_new/generic/PaginationButtons/PaginationButtons.story.js
@@ -1,16 +1,16 @@
import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y'
import { action } from '@storybook/addon-actions'
-import Paginate from '~/components/Paginate/Paginate'
+import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
import helpers from '~/storybook/helpers'
helpers.init()
-storiesOf('Paginate', module)
+storiesOf('PaginationButtons', module)
.addDecorator(withA11y)
.addDecorator(helpers.layout)
.add('basic pagination', () => ({
- components: { Paginate },
+ components: { PaginationButtons },
data: () => ({
hasNext: true,
hasPrevious: false,
@@ -19,7 +19,7 @@ storiesOf('Paginate', module)
back: action('back'),
next: action('next'),
},
- template: `
+
+
+
+
+
+
+
+
+
diff --git a/webapp/components/features/ReportList/ReportList.vue b/webapp/components/features/ReportList/ReportList.vue
index 3659daa9e..62a29e66b 100644
--- a/webapp/components/features/ReportList/ReportList.vue
+++ b/webapp/components/features/ReportList/ReportList.vue
@@ -7,7 +7,7 @@
-
+
diff --git a/webapp/components/generic/SearchableInput/SearchableInput.spec.js b/webapp/components/generic/SearchableInput/SearchableInput.spec.js
index db314630f..cc538a50b 100644
--- a/webapp/components/generic/SearchableInput/SearchableInput.spec.js
+++ b/webapp/components/generic/SearchableInput/SearchableInput.spec.js
@@ -32,81 +32,79 @@ describe('SearchableInput.vue', () => {
}
describe('mount', () => {
- describe('testing custom functions', () => {
- let select
+ let select
+ beforeEach(() => {
+ select = wrapper.find('.ds-select')
+ select.trigger('focus')
+ select.element.value = 'abcd'
+ })
+
+ it('opens the dropdown when focused', () => {
+ expect(wrapper.find('.ds-select-dropdown').isVisible()).toBe(true)
+ })
+
+ it('closes the dropdown when blurred', () => {
+ select.trigger('blur')
+ expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
+ })
+
+ it('closes the dropdown when cleared with esc key', () => {
+ select.trigger('input')
+ select.trigger('keyup.esc')
+ expect(wrapper.find('.ds-select-is-open').exists()).toBe(false)
+ })
+
+ it('changes the unprocessedSearchInput as the value changes', () => {
+ select.trigger('input')
+ expect(select.element.value).toBe('abcd')
+ })
+
+ it('searches for the term when enter is pressed', async () => {
+ select.element.value = 'ab'
+ select.trigger('input')
+ select.trigger('keyup.enter')
+ await expect(wrapper.emitted().query[0]).toEqual(['ab'])
+ })
+
+ it('calls onDelete when the delete key is pressed', () => {
+ const spy = jest.spyOn(wrapper.vm, 'onDelete')
+ select.trigger('input')
+ select.trigger('keyup.delete')
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
+
+ describe('navigating to resource', () => {
beforeEach(() => {
+ propsData = { options: searchResults }
+ wrapper = Wrapper()
select = wrapper.find('.ds-select')
select.trigger('focus')
- select.element.value = 'abcd'
})
- it('opens the select dropdown when focused on', () => {
- expect(wrapper.find('.is-open').exists()).toBe(true)
- })
-
- it('opens the select dropdown and blurs after focused on', () => {
- select.trigger('blur')
- expect(wrapper.find('.is-open').exists()).toBe(false)
- })
-
- it('is clearable', () => {
+ it('pushes to post page', async () => {
+ select.element.value = 'Post'
select.trigger('input')
- select.trigger('keyup.esc')
- expect(wrapper.find('.is-open').exists()).toBe(false)
- })
-
- it('changes the unprocessedSearchInput as the value changes', () => {
- select.trigger('input')
- expect(select.element.value).toBe('abcd')
- })
-
- it('searches for the term when enter is pressed', async () => {
- select.element.value = 'ab'
- select.trigger('input')
- select.trigger('keyup.enter')
- await expect(wrapper.emitted().query[0]).toEqual(['ab'])
- })
-
- it('calls onDelete when the delete key is pressed', () => {
- const spy = jest.spyOn(wrapper.vm, 'onDelete')
- select.trigger('input')
- select.trigger('keyup.delete')
- expect(spy).toHaveBeenCalledTimes(1)
- })
-
- describe('navigating to resource', () => {
- beforeEach(() => {
- propsData = { options: searchResults }
- wrapper = Wrapper()
- select = wrapper.find('.ds-select')
- select.trigger('focus')
- })
-
- it('pushes to post page', async () => {
- select.element.value = 'Post'
- select.trigger('input')
- const post = wrapper.find('.search-post')
- post.trigger('click')
- await Vue.nextTick().then(() => {
- expect(mocks.$router.push).toHaveBeenCalledWith({
- name: 'post-id-slug',
- params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' },
- })
+ const post = wrapper.find('.search-post')
+ post.trigger('click')
+ await Vue.nextTick().then(() => {
+ expect(mocks.$router.push).toHaveBeenCalledWith({
+ name: 'post-id-slug',
+ params: { id: 'post-by-jenny', slug: 'user-post-by-jenny' },
})
})
+ })
- it("pushes to user's profile", async () => {
- select.element.value = 'Bob'
- select.trigger('input')
- const users = wrapper.findAll('.userinfo')
- const bob = users.filter(item => item.text() === '@bob-der-baumeister')
- bob.trigger('click')
- await Vue.nextTick().then(() => {
- expect(mocks.$router.push).toHaveBeenCalledWith({
- name: 'profile-id-slug',
- params: { id: 'u2', slug: 'bob-der-baumeister' },
- })
+ it("pushes to user's profile", async () => {
+ select.element.value = 'Bob'
+ select.trigger('input')
+ const users = wrapper.findAll('.userinfo')
+ const bob = users.filter(item => item.text() === '@bob-der-baumeister')
+ bob.trigger('click')
+ await Vue.nextTick().then(() => {
+ expect(mocks.$router.push).toHaveBeenCalledWith({
+ name: 'profile-id-slug',
+ params: { id: 'u2', slug: 'bob-der-baumeister' },
})
})
})
diff --git a/webapp/components/generic/SearchableInput/SearchableInput.vue b/webapp/components/generic/SearchableInput/SearchableInput.vue
index cc9269ecf..a45f4104c 100644
--- a/webapp/components/generic/SearchableInput/SearchableInput.vue
+++ b/webapp/components/generic/SearchableInput/SearchableInput.vue
@@ -1,60 +1,46 @@
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
+
diff --git a/webapp/layouts/default.vue b/webapp/layouts/default.vue
index 6be75cd35..de0e94af7 100644
--- a/webapp/layouts/default.vue
+++ b/webapp/layouts/default.vue
@@ -4,21 +4,21 @@
-
-
+
-
+
@@ -26,8 +26,8 @@
-
@@ -164,17 +163,21 @@ export default {
}
.main-navigation-right {
display: flex;
- flex: 1;
+ justify-content: flex-end;
}
.main-navigation-right .desktop-view {
float: right;
}
-@media only screen and (min-width: 960px) {
+.ds-flex-item.mobile-hamburger-menu {
+ margin-left: auto;
+ text-align: right;
+}
+@media only screen and (min-width: 730px) {
.mobile-hamburger-menu {
display: none;
}
}
-@media only screen and (max-width: 960px) {
+@media only screen and (max-width: 730px) {
#nav-search-box,
.main-navigation-right {
margin: 10px 0px;
diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js
index 83a0a65f1..1e368f01a 100644
--- a/webapp/nuxt.config.js
+++ b/webapp/nuxt.config.js
@@ -94,7 +94,7 @@ export default {
/*
** Global CSS
*/
- css: ['~assets/styles/main.scss'],
+ css: ['~assets/_new/styles/resets.scss', '~assets/styles/main.scss'],
/*
** Global processed styles
diff --git a/webapp/package.json b/webapp/package.json
index 46c540b6a..fdd13309c 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -96,13 +96,13 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
- "@babel/core": "~7.7.7",
- "@babel/plugin-syntax-dynamic-import": "^7.8.0",
- "@babel/preset-env": "~7.7.7",
- "@storybook/addon-a11y": "^5.2.8",
- "@storybook/addon-actions": "^5.3.2",
- "@storybook/addon-notes": "^5.3.1",
- "@storybook/vue": "~5.3.1",
+ "@babel/core": "~7.8.3",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/preset-env": "~7.8.3",
+ "@storybook/addon-a11y": "^5.3.3",
+ "@storybook/addon-actions": "^5.3.3",
+ "@storybook/addon-notes": "^5.3.3",
+ "@storybook/vue": "~5.3.3",
"@vue/cli-shared-utils": "~4.1.2",
"@vue/eslint-config-prettier": "~6.0.0",
"@vue/server-test-utils": "~1.0.0-beta.30",
@@ -120,8 +120,8 @@
"eslint-config-prettier": "~6.9.0",
"eslint-config-standard": "~14.1.0",
"eslint-loader": "~3.0.3",
- "eslint-plugin-import": "~2.19.1",
- "eslint-plugin-jest": "~23.3.0",
+ "eslint-plugin-import": "~2.20.0",
+ "eslint-plugin-jest": "~23.6.0",
"eslint-plugin-node": "~11.0.0",
"eslint-plugin-prettier": "~3.1.2",
"eslint-plugin-promise": "~4.2.1",
@@ -135,7 +135,7 @@
"mutation-observer": "^1.0.3",
"node-sass": "~4.13.0",
"prettier": "~1.19.1",
- "sass-loader": "~8.0.0",
+ "sass-loader": "~8.0.2",
"storybook-design-token": "^0.5.0",
"storybook-vue-router": "^1.0.7",
"style-loader": "~0.23.1",
diff --git a/webapp/pages/admin/donations.vue b/webapp/pages/admin/donations.vue
index 7f0205be5..f40cb4c7d 100644
--- a/webapp/pages/admin/donations.vue
+++ b/webapp/pages/admin/donations.vue
@@ -8,9 +8,9 @@
placeholder="1200"
icon="money"
/>
-
+
{{ $t('actions.save') }}
-
+
diff --git a/webapp/pages/admin/users.vue b/webapp/pages/admin/users.vue
index 6a7bf8adb..610307704 100644
--- a/webapp/pages/admin/users.vue
+++ b/webapp/pages/admin/users.vue
@@ -12,7 +12,7 @@
/>
-
+
@@ -50,14 +50,7 @@
{{ scope.row.createdAt | dateTime }}
-
-
-
-
-
-
-
-
+
{{ $t('admin.users.empty') }}
@@ -69,8 +62,12 @@
import gql from 'graphql-tag'
import { isEmail } from 'validator'
import normalizeEmail from '~/components/utils/NormalizeEmail'
+import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
export default {
+ components: {
+ PaginationButtons,
+ },
data() {
const pageSize = 15
return {
diff --git a/webapp/pages/index.spec.js b/webapp/pages/index.spec.js
index d5d677af1..f25f44ca0 100644
--- a/webapp/pages/index.spec.js
+++ b/webapp/pages/index.spec.js
@@ -126,11 +126,6 @@ describe('PostIndex', () => {
.trigger('click')
expect(mutations['posts/SELECT_ORDER']).toHaveBeenCalledWith({}, 'createdAt_asc')
})
-
- it('updates offset when a user clicks on the load more button', () => {
- wrapper.find('.load-more button').trigger('click')
- expect(wrapper.vm.offset).toEqual(12)
- })
})
})
})
diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue
index 6d0f8c669..609c3d800 100644
--- a/webapp/pages/index.vue
+++ b/webapp/pages/index.vue
@@ -8,7 +8,7 @@
- {{ $t('donations.donate-now') }}
+ {{ $t('donations.donate-now') }}
@@ -43,19 +43,22 @@
-
+
+
+
-
-
-
+
@@ -65,7 +68,6 @@
import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
import HcEmpty from '~/components/Empty/Empty'
import HcPostCard from '~/components/PostCard/PostCard.vue'
-import HcLoadMore from '~/components/LoadMore.vue'
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
import { mapGetters, mapMutations } from 'vuex'
@@ -78,7 +80,6 @@ export default {
// DonationInfo,
FilterMenu,
HcPostCard,
- HcLoadMore,
HcEmpty,
MasonryGrid,
MasonryGridItem,
@@ -238,7 +239,10 @@ export default {
}
}
-.post-add-button {
+.base-button.--circle.post-add-button {
+ height: 54px;
+ width: 54px;
+ font-size: 26px;
z-index: 100;
position: fixed;
bottom: -5px;
diff --git a/webapp/pages/notifications/index.spec.js b/webapp/pages/notifications/index.spec.js
index 66f68aac7..70592b467 100644
--- a/webapp/pages/notifications/index.spec.js
+++ b/webapp/pages/notifications/index.spec.js
@@ -3,7 +3,7 @@ import NotificationsPage from './index.vue'
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
-import Paginate from '~/components/Paginate/Paginate'
+import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
const localVue = global.localVue
@@ -122,14 +122,14 @@ describe('PostIndex', () => {
})
})
- describe('Paginate', () => {
+ describe('PaginationButtons', () => {
beforeEach(() => {
wrapper = Wrapper()
})
describe('next: given a user is on the first page', () => {
it('adds offset to pageSize to skip first x notifications and display next page', () => {
- wrapper.find(Paginate).vm.$emit('next')
+ wrapper.find(PaginationButtons).vm.$emit('next')
expect(wrapper.vm.offset).toEqual(12)
})
})
@@ -137,7 +137,7 @@ describe('PostIndex', () => {
describe('back: given a user is on the third page', () => {
it('sets offset when back is emitted', () => {
wrapper.setData({ offset: 24 })
- wrapper.find(Paginate).vm.$emit('back')
+ wrapper.find(PaginationButtons).vm.$emit('back')
expect(wrapper.vm.offset).toEqual(12)
})
})
diff --git a/webapp/pages/notifications/index.vue b/webapp/pages/notifications/index.vue
index aa002cec7..c49d208be 100644
--- a/webapp/pages/notifications/index.vue
+++ b/webapp/pages/notifications/index.vue
@@ -15,21 +15,21 @@
@markNotificationAsRead="markNotificationAsRead"
:notifications="notifications"
/>
-
+
+
+ {{ $t('common.comment', null, 0) }}
{{ emotionCount }}x
+{{ $t(`contribution.emotions-label.${emotion}`) }}
-- {{ PostsEmotionsCountByEmotion[emotion] }}x -
-- - -
-
-
-
-
-
-
-
-
-
+
+
+ + +