mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into 4548-single-person-org
This commit is contained in:
commit
22681b2f1f
19
CHANGELOG.md
19
CHANGELOG.md
@ -4,8 +4,27 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.0.7](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.6...1.0.7)
|
||||
|
||||
- Bump rosie from 2.0.1 to 2.1.0 [`#4520`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4520)
|
||||
- fix: 🍰 Renew JWT In Decode Test [`#4798`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4798)
|
||||
- docs: 🍰 Refine Main README.md With Test Tech Stack And Video Link [`#4772`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4772)
|
||||
- docs: 🍰 Change README.md DB Commands For Docker [`#4765`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4765)
|
||||
- Bump date-fns from 2.23.0 to 2.25.0 [`#4753`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4753)
|
||||
- Bump neo4j-driver from 4.0.2 to 4.3.4 [`#4729`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4729)
|
||||
- chore: 🍰 Update Neode To v0.4.7 [`#4751`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4751)
|
||||
- doc: 🍰 Update README.md Etc. [`#4733`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4733)
|
||||
- feat: 🍰 New CSS For Internal Pages [`#4741`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4741)
|
||||
- fix: 🍰 Change Notification E-Mails Settings Page Link [`#4742`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4742)
|
||||
- Refactor internal pages to new CSS [`acad80c`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/acad80c3c8262934dd2e38961c08c0fde769099a)
|
||||
- Renew JWT in decode test [`46eb6b8`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/46eb6b82ea802d4d6ca7294cd32d1fe16425bfea)
|
||||
- Revert "Renew JWT in decode test" only for changing the Neode version [`a0d92b4`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/a0d92b4853d09d725c1fb7886cbfed2a00e1f05c)
|
||||
|
||||
#### [1.0.6](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.5...1.0.6)
|
||||
|
||||
> 4 October 2021
|
||||
|
||||
- chore: 🍰 Set 'sendNotification' Emails Init Admin [`#4690`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4690)
|
||||
- feat: 🍰 Send Notification E-Mail [`#4623`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4623)
|
||||
- feat: 🍰 Implement Progress Bar Again [`#4357`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4357)
|
||||
- Bump nodemailer-html-to-text from 3.1.0 to 3.2.0 in /backend [`#4531`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4531)
|
||||
|
||||
13
README.md
13
README.md
@ -20,6 +20,11 @@ At the same time, it should be possible in the future to link these networks wit
|
||||
|
||||
In other words, we are interested in a network of networks and in keeping the data as close as possible to the user and the operator they trusts.
|
||||
|
||||
## Introduction
|
||||
|
||||
Have a look into our short video:
|
||||
[ocelot.social - GitHub - Developer Welcome - Tutorial (english)](https://www.youtube.com/watch?v=gZSL6KvBIiY&list=PLFMD5liPP01kbuReHxYXxv_1fI5rIgS1f&index=1)
|
||||
|
||||
## Directory Layout
|
||||
|
||||
There are three important directories:
|
||||
@ -192,6 +197,14 @@ The only deployment method in this repository for development purposes as descri
|
||||
* [NodeJS](https://nodejs.org/en/)
|
||||
* [Neo4J](https://neo4j.com/)
|
||||
|
||||
### For Testing
|
||||
|
||||
* [Cypress](https://docs.cypress.io/)
|
||||
* [Storybook](https://storybook.js.org/)
|
||||
* [Jest](https://jestjs.io/)
|
||||
* [Vue Test Utils](https://vue-test-utils.vuejs.org/)
|
||||
* [ESLint](https://eslint.org/)
|
||||
|
||||
## Attributions
|
||||
|
||||
Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
|
||||
|
||||
1
backend/.nvmrc
Normal file
1
backend/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v12.19.0
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -66,7 +66,6 @@
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
@ -120,6 +119,7 @@
|
||||
"xregexp": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "5.1.0",
|
||||
"apollo-server-testing": "~2.11.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~6.0.5",
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import faker from 'faker'
|
||||
import slugify from 'slug'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { Factory } from 'rosie'
|
||||
import faker from '@faker-js/faker'
|
||||
import { getDriver, getNeode } from './neo4j'
|
||||
import CONFIG from '../config/index.js'
|
||||
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode.js'
|
||||
|
||||
@ -19,8 +19,8 @@ export async function up(next) {
|
||||
SET donationInfo.createdAt = toString(datetime())
|
||||
SET donationInfo.updatedAt = donationInfo.createdAt
|
||||
SET donationInfo.showDonations = false
|
||||
SET donationInfo.goal = 15000
|
||||
SET donationInfo.progress = 1200
|
||||
SET donationInfo.goal = 15000.0
|
||||
SET donationInfo.progress = 1200.0
|
||||
RETURN donationInfo {.*}
|
||||
`,
|
||||
{ donationId },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import faker from 'faker'
|
||||
import sample from 'lodash/sample'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import faker from '@faker-js/faker'
|
||||
import Factory from '../db/factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { gql } from '../helpers/jest'
|
||||
|
||||
@ -21,8 +21,14 @@ const neode = getNeode()
|
||||
// iss: 'http://localhost:4000',
|
||||
// sub: 'u3'
|
||||
// }
|
||||
// !!! if the token expires go into the GraphQL Playground in the browser at 'http://localhost:4000' with a running backend and a seeded Neo4j database
|
||||
// now do the login mutation:
|
||||
// mutation {
|
||||
// login(email:"user@example.org", password:"1234")
|
||||
// }
|
||||
// replace this token here with the one you received as the result
|
||||
export const validAuthorizationHeader =
|
||||
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc'
|
||||
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTYzNzY0NDMwMCwiZXhwIjoxNzAwNzU5NTAwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.ispIfRfgkXuYoIhKx7x2jPxgvHDJVv1ogMycLmfUnsk'
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
@ -47,6 +53,7 @@ describe('decode', () => {
|
||||
beforeEach(() => {
|
||||
authorizationHeader = null
|
||||
})
|
||||
|
||||
it('returns null', returnsNull)
|
||||
})
|
||||
|
||||
@ -54,6 +61,7 @@ describe('decode', () => {
|
||||
beforeEach(() => {
|
||||
authorizationHeader = undefined
|
||||
})
|
||||
|
||||
it('returns null', returnsNull)
|
||||
})
|
||||
|
||||
@ -61,6 +69,7 @@ describe('decode', () => {
|
||||
beforeEach(() => {
|
||||
authorizationHeader = 'blah'
|
||||
})
|
||||
|
||||
it('returns null', returnsNull)
|
||||
})
|
||||
|
||||
@ -68,6 +77,7 @@ describe('decode', () => {
|
||||
beforeEach(() => {
|
||||
authorizationHeader = validAuthorizationHeader
|
||||
})
|
||||
|
||||
it('returns null', returnsNull)
|
||||
|
||||
describe('and corresponding user in the database', () => {
|
||||
|
||||
@ -41,7 +41,6 @@ const publishNotifications = async (context, promises) => {
|
||||
notifications.forEach((notificationAdded, index) => {
|
||||
pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
|
||||
if (notificationAdded.to.sendNotificationEmails) {
|
||||
// Wolle await
|
||||
sendMail(
|
||||
notificationTemplate({
|
||||
email: notificationsEmailAddresses[index].email,
|
||||
|
||||
@ -38,8 +38,8 @@ const newlyCreatedNodesWithLocales = [
|
||||
nameRU: 'Вельцхайм',
|
||||
nameNL: 'Welzheim',
|
||||
namePL: 'Welzheim',
|
||||
lng: 9.63444,
|
||||
lat: 48.87472,
|
||||
lng: 9.634741,
|
||||
lat: 48.874924,
|
||||
},
|
||||
state: {
|
||||
id: expect.stringContaining('region'),
|
||||
|
||||
@ -963,6 +963,11 @@
|
||||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@faker-js/faker@5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f"
|
||||
integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q==
|
||||
|
||||
"@graphql-toolkit/common@0.10.4":
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.10.4.tgz#7785f2a3f14559d0778859c49f4442078c196695"
|
||||
@ -4587,10 +4592,6 @@ extsprintf@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
faker@Marak/faker.js#master:
|
||||
version "4.1.0"
|
||||
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/3b2fa4aebccee52ae1bafc15d575061fb30c3cf1"
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { When } from "cypress-cucumber-preprocessor/steps";
|
||||
|
||||
When('I add a social media link', () => {
|
||||
cy.get('input#addSocialMedia')
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
cy.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
.get('#editSocialMedia')
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Save')
|
||||
.click()
|
||||
})
|
||||
@ -2,9 +2,12 @@ import { Given } from "cypress-cucumber-preprocessor/steps";
|
||||
|
||||
Given('I have added a social media link', () => {
|
||||
cy.visit('/settings/my-social-media')
|
||||
.get('input#addSocialMedia')
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
.get('#editSocialMedia')
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Save')
|
||||
.click()
|
||||
})
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "Free and open source software program code available to run social networks.",
|
||||
"author": "ocelot.social Community",
|
||||
"license": "MIT",
|
||||
@ -36,7 +36,7 @@
|
||||
"date-fns": "^2.25.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"expect": "^25.3.0",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"@faker-js/faker": "5.1.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"import": "^0.0.6",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
@ -44,7 +44,7 @@
|
||||
"neo4j-driver": "^4.3.4",
|
||||
"neode": "^0.4.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rosie": "^2.0.1",
|
||||
"rosie": "^2.1.0",
|
||||
"slug": "^5.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
1
webapp/.nvmrc
Normal file
1
webapp/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v12.19.0
|
||||
@ -1,8 +1,8 @@
|
||||
import faker from '@faker-js/faker'
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import HcCommentList from './CommentList.vue'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import faker from 'faker'
|
||||
|
||||
helpers.init()
|
||||
|
||||
|
||||
@ -3,12 +3,16 @@
|
||||
<template #default="{ openMenu, closeMenu }">
|
||||
<slot name="button">
|
||||
<menu-bar-button
|
||||
class="legend-question-button"
|
||||
icon="question-circle"
|
||||
circle
|
||||
ghost
|
||||
class="legend-question-button"
|
||||
@mouseover.native="openMenu()"
|
||||
@mouseleave.native="closeMenu()"
|
||||
:onClick="
|
||||
() => {
|
||||
isDropdownOpen ? closeMenu() : openMenu()
|
||||
isDropdownOpen = !isDropdownOpen
|
||||
}
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
@ -60,6 +64,7 @@ export default {
|
||||
{ iconName: 'quote-right', name: `editor.legend.quote`, shortcut: '> + space' },
|
||||
{ iconName: 'minus', name: `editor.legend.ruler`, shortcut: '---' },
|
||||
],
|
||||
isDropdownOpen: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -179,10 +179,24 @@ export default {
|
||||
}
|
||||
|
||||
.html {
|
||||
width: 100%;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
|
||||
// see this working solution here: https://stackoverflow.com/questions/35814653/automatic-height-when-embedding-a-youtube-video
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
|
||||
iframe {
|
||||
// width: 100%;
|
||||
// height: auto;
|
||||
|
||||
// same solution example as above
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import MySomethingList from './MySomethingList.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('MySomethingList.vue', () => {
|
||||
let wrapper
|
||||
let propsData
|
||||
let data
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
useFormData: { dummy: '' },
|
||||
useItems: [{ id: 'id', dummy: 'dummy' }],
|
||||
namePropertyKey: 'dummy',
|
||||
callbacks: { edit: jest.fn(), submit: jest.fn(), delete: jest.fn() },
|
||||
}
|
||||
data = () => {
|
||||
return {}
|
||||
}
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest.fn(),
|
||||
},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let form, slots
|
||||
const Wrapper = () => {
|
||||
slots = {
|
||||
'list-item': '<div class="list-item"></div>',
|
||||
'edit-item': '<div class="edit-item"></div>',
|
||||
}
|
||||
return mount(MySomethingList, {
|
||||
propsData,
|
||||
data,
|
||||
mocks,
|
||||
localVue,
|
||||
slots,
|
||||
})
|
||||
}
|
||||
|
||||
describe('given existing item', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('for each item it', () => {
|
||||
it('displays the item as slot "list-item"', () => {
|
||||
expect(wrapper.find('.list-item').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the edit button', () => {
|
||||
expect(wrapper.find('.base-button[data-test="edit-button"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the delete button', () => {
|
||||
expect(wrapper.find('.base-button[data-test="delete-button"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('editing item', () => {
|
||||
beforeEach(async () => {
|
||||
const editButton = wrapper.find('.base-button[data-test="edit-button"]')
|
||||
editButton.trigger('click')
|
||||
await Vue.nextTick()
|
||||
})
|
||||
|
||||
it('disables adding items while editing', () => {
|
||||
const submitButton = wrapper.find('.base-button[data-test="add-save-button"]')
|
||||
expect(submitButton.text()).not.toContain('settings.social-media.submit')
|
||||
})
|
||||
|
||||
it('allows the user to cancel editing', async () => {
|
||||
expect(wrapper.find('.edit-item').exists()).toBe(true)
|
||||
const cancelButton = wrapper.find('button#cancel')
|
||||
cancelButton.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.find('.edit-item').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('calls callback functions', () => {
|
||||
it('calls edit', async () => {
|
||||
const editButton = wrapper.find('.base-button[data-test="edit-button"]')
|
||||
editButton.trigger('click')
|
||||
await Vue.nextTick()
|
||||
const expectedItem = expect.objectContaining({ id: 'id', dummy: 'dummy' })
|
||||
expect(propsData.callbacks.edit).toHaveBeenCalledTimes(1)
|
||||
expect(propsData.callbacks.edit).toHaveBeenCalledWith(expect.any(Object), expectedItem)
|
||||
})
|
||||
|
||||
it('calls submit', async () => {
|
||||
form = wrapper.find('form')
|
||||
form.trigger('submit')
|
||||
await Vue.nextTick()
|
||||
form.trigger('submit')
|
||||
await Vue.nextTick()
|
||||
const expectedItem = expect.objectContaining({ id: '' })
|
||||
expect(propsData.callbacks.submit).toHaveBeenCalledTimes(1)
|
||||
expect(propsData.callbacks.submit).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
true,
|
||||
expectedItem,
|
||||
{ dummy: '' },
|
||||
)
|
||||
})
|
||||
|
||||
it('calls delete', async () => {
|
||||
const deleteButton = wrapper.find('.base-button[data-test="delete-button"]')
|
||||
deleteButton.trigger('click')
|
||||
await Vue.nextTick()
|
||||
const expectedItem = expect.objectContaining({ id: 'id', dummy: 'dummy' })
|
||||
expect(propsData.callbacks.delete).toHaveBeenCalledTimes(1)
|
||||
expect(propsData.callbacks.delete).toHaveBeenCalledWith(expect.any(Object), expectedItem)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
@submit="handleSubmitItem"
|
||||
>
|
||||
<div v-if="isEditing">
|
||||
<ds-space margin="base">
|
||||
<ds-heading tag="h5">
|
||||
{{
|
||||
isCreation
|
||||
? $t('settings.social-media.addNewTitle')
|
||||
: $t('settings.social-media.editTitle', { name: editingItem[namePropertyKey] })
|
||||
}}
|
||||
</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space v-if="items" margin-top="base">
|
||||
<slot name="edit-item" />
|
||||
</ds-space>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ds-space v-if="items" margin-top="base">
|
||||
<ds-list>
|
||||
<ds-list-item v-for="item in items" :key="item.id" class="list-item--high">
|
||||
<template>
|
||||
<slot name="list-item" :item="item" />
|
||||
<span class="divider">|</span>
|
||||
<base-button
|
||||
icon="edit"
|
||||
circle
|
||||
ghost
|
||||
@click="handleEditItem(item)"
|
||||
:title="$t('actions.edit')"
|
||||
data-test="edit-button"
|
||||
/>
|
||||
<base-button
|
||||
icon="trash"
|
||||
circle
|
||||
ghost
|
||||
@click="handleDeleteItem(item)"
|
||||
:title="$t('actions.delete')"
|
||||
data-test="delete-button"
|
||||
/>
|
||||
</template>
|
||||
</ds-list-item>
|
||||
</ds-list>
|
||||
</ds-space>
|
||||
</div>
|
||||
|
||||
<ds-space margin-top="base">
|
||||
<ds-space margin-top="base">
|
||||
<base-button
|
||||
filled
|
||||
:disabled="loading || !(!isEditing || (isEditing && !disabled))"
|
||||
:loading="loading"
|
||||
type="submit"
|
||||
data-test="add-save-button"
|
||||
>
|
||||
{{ isEditing ? $t('actions.save') : $t('settings.social-media.submit') }}
|
||||
</base-button>
|
||||
<base-button v-if="isEditing" id="cancel" danger @click="handleCancel()">
|
||||
{{ $t('actions.cancel') }}
|
||||
</base-button>
|
||||
</ds-space>
|
||||
</ds-space>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MySomethingList',
|
||||
props: {
|
||||
useFormData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
useFormSchema: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
useItems: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
defaultItem: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
namePropertyKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
callbacks: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
handleInput: () => {},
|
||||
handleInputValid: () => {},
|
||||
edit: () => {},
|
||||
submit: () => {},
|
||||
delete: () => {},
|
||||
}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: this.useFormData,
|
||||
formSchema: this.useFormSchema,
|
||||
items: this.useItems,
|
||||
disabled: true,
|
||||
loading: false,
|
||||
editingItem: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isEditing() {
|
||||
return this.editingItem !== null
|
||||
},
|
||||
isCreation() {
|
||||
return this.editingItem !== null && this.editingItem.id === ''
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// can change by a parents callback and again given trough by v-bind from there
|
||||
useItems(newItems) {
|
||||
this.items = newItems
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleInput(data) {
|
||||
this.callbacks.handleInput(this, data)
|
||||
this.disabled = true
|
||||
},
|
||||
handleInputValid(data) {
|
||||
this.callbacks.handleInputValid(this, data)
|
||||
},
|
||||
handleEditItem(item) {
|
||||
this.editingItem = item
|
||||
this.callbacks.edit(this, item)
|
||||
},
|
||||
async handleSubmitItem() {
|
||||
if (!this.isEditing) {
|
||||
this.handleEditItem({ ...this.defaultItem, id: '' })
|
||||
} else {
|
||||
this.loading = true
|
||||
if (await this.callbacks.submit(this, this.isCreation, this.editingItem, this.formData)) {
|
||||
this.disabled = true
|
||||
this.editingItem = null
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.editingItem = null
|
||||
this.disabled = true
|
||||
},
|
||||
async handleDeleteItem(item) {
|
||||
await this.callbacks.delete(this, item)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope>
|
||||
.divider {
|
||||
opacity: 0.4;
|
||||
padding: 0 $space-small;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-item--high {
|
||||
.ds-list-item-prefix {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.ds-list-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,36 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import SocialMediaListItem from './SocialMediaListItem.vue'
|
||||
|
||||
describe('SocialMediaListItem.vue', () => {
|
||||
let wrapper
|
||||
let propsData
|
||||
const socialMediaUrl = 'https://freeradical.zone/@mattwr18'
|
||||
const faviconUrl = 'https://freeradical.zone/favicon.ico'
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
const Wrapper = () => {
|
||||
return shallowMount(SocialMediaListItem, { propsData })
|
||||
}
|
||||
|
||||
describe('given existing social media links', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { item: { id: 's1', url: socialMediaUrl, favicon: faviconUrl } }
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('for each link item it', () => {
|
||||
it('displays the favicon', () => {
|
||||
expect(wrapper.find(`img[src="${faviconUrl}"]`).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the url', () => {
|
||||
expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<a :href="item.url" target="_blank">
|
||||
<img :src="item.favicon" alt="Link:" height="16" width="16" />
|
||||
{{ item.url }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SocialMediaListItem',
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,3 +1,4 @@
|
||||
import faker from '@faker-js/faker'
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import { withA11y } from '@storybook/addon-a11y'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
@ -8,7 +9,6 @@ import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation
|
||||
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||
import HcHashtag from '~/components/Hashtag/Hashtag'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import faker from 'faker'
|
||||
import { post } from '~/components/PostTeaser/PostTeaser.story.js'
|
||||
import { user } from '~/components/UserTeaser/UserTeaser.story.js'
|
||||
|
||||
|
||||
@ -773,6 +773,8 @@
|
||||
"name": "Sicherheit"
|
||||
},
|
||||
"social-media": {
|
||||
"addNewTitle": "Neuen Link hinzufügen",
|
||||
"editTitle": "Link \"{name}\" ändern",
|
||||
"name": "Soziale Netzwerke",
|
||||
"placeholder": "Deine Webadresse des Sozialen Netzwerkes",
|
||||
"requireUnique": "Dieser Link existiert bereits",
|
||||
|
||||
@ -773,6 +773,8 @@
|
||||
"name": "Security"
|
||||
},
|
||||
"social-media": {
|
||||
"addNewTitle": "Add new link",
|
||||
"editTitle": "Edit link \"{name}\"",
|
||||
"name": "Social media",
|
||||
"placeholder": "Your social media url",
|
||||
"requireUnique": "You added this url already",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ocelot-social/maintenance",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "Maintenance page for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -201,6 +201,7 @@ export default {
|
||||
/** * A Boolean indicating if the cookie transmission requires a
|
||||
* secure protocol (https). Defaults to false. */
|
||||
secure: CONFIG.COOKIE_HTTPS_ONLY,
|
||||
sameSite: 'lax', // for the meaning see https://www.thinktecture.com/de/identity/samesite/samesite-in-a-nutshell/
|
||||
},
|
||||
// includeNodeModules: true, // optional, default: false (this includes graphql-tag for node_modules folder)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-webapp",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "ocelot.social Frontend",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -108,6 +108,7 @@
|
||||
"@babel/core": "~7.12.3",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "~7.9.0",
|
||||
"@faker-js/faker": "5.1.0",
|
||||
"@storybook/addon-a11y": "^6.3.6",
|
||||
"@storybook/addon-actions": "^5.3.21",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
@ -136,7 +137,6 @@
|
||||
"eslint-plugin-promise": "~4.3.1",
|
||||
"eslint-plugin-standard": "~5.0.0",
|
||||
"eslint-plugin-vue": "~6.2.2",
|
||||
"faker": "^5.1.0",
|
||||
"flush-promises": "^1.0.2",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "~26.6.3",
|
||||
|
||||
@ -33,7 +33,7 @@ describe('my-social-media.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let form, input, submitButton
|
||||
let form, input
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
@ -42,11 +42,12 @@ describe('my-social-media.vue', () => {
|
||||
}
|
||||
|
||||
describe('adding social media link', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
wrapper = Wrapper()
|
||||
form = wrapper.find('form')
|
||||
input = wrapper.find('input#addSocialMedia')
|
||||
submitButton = wrapper.find('button')
|
||||
form.trigger('submit')
|
||||
await Vue.nextTick()
|
||||
input = wrapper.find('input#editSocialMedia')
|
||||
})
|
||||
|
||||
it('requires the link to be a valid url', async () => {
|
||||
@ -79,7 +80,6 @@ describe('my-social-media.vue', () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { url: newSocialMediaUrl },
|
||||
})
|
||||
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
@ -88,10 +88,10 @@ describe('my-social-media.vue', () => {
|
||||
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('clears the form', async () => {
|
||||
it('switches back to list', async () => {
|
||||
await flushPromises()
|
||||
expect(input.value).toBe(undefined)
|
||||
expect(submitButton.vm.$attrs.disabled).toBe(true)
|
||||
const submitButton = wrapper.find('.base-button[data-test="add-save-button"]')
|
||||
expect(submitButton.text()).not.toContain('settings.social-media.submit')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -100,10 +100,9 @@ describe('my-social-media.vue', () => {
|
||||
beforeEach(() => {
|
||||
getters = {
|
||||
'auth/user': () => ({
|
||||
socialMedia: [{ id: 's1', url: socialMediaUrl }],
|
||||
socialMedia: [{ id: 's1', url: socialMediaUrl, favicon: faviconUrl }],
|
||||
}),
|
||||
}
|
||||
|
||||
wrapper = Wrapper()
|
||||
form = wrapper.find('form')
|
||||
})
|
||||
@ -116,18 +115,12 @@ describe('my-social-media.vue', () => {
|
||||
it('displays the url', () => {
|
||||
expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the edit button', () => {
|
||||
expect(wrapper.find('.base-button[data-test="edit-button"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays the delete button', () => {
|
||||
expect(wrapper.find('.base-button[data-test="delete-button"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not accept a duplicate url', async () => {
|
||||
wrapper.find('input#addSocialMedia').setValue(socialMediaUrl)
|
||||
form.trigger('submit')
|
||||
await Vue.nextTick()
|
||||
wrapper.find('input#editSocialMedia').setValue(socialMediaUrl)
|
||||
form.trigger('submit')
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||
@ -141,12 +134,6 @@ describe('my-social-media.vue', () => {
|
||||
input = wrapper.find('input#editSocialMedia')
|
||||
})
|
||||
|
||||
it('disables adding new links while editing', () => {
|
||||
const addInput = wrapper.find('input#addSocialMedia')
|
||||
|
||||
expect(addInput.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('sends the new url to the backend', async () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { id: 's1', url: newSocialMediaUrl },
|
||||
@ -156,13 +143,6 @@ describe('my-social-media.vue', () => {
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
it('allows the user to cancel editing', async () => {
|
||||
const cancelButton = wrapper.find('button#cancel')
|
||||
cancelButton.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(wrapper.find('input#editSocialMedia').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleting social media link', () => {
|
||||
@ -176,7 +156,6 @@ describe('my-social-media.vue', () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { id: 's1' },
|
||||
})
|
||||
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
@ -1,97 +1,73 @@
|
||||
<template>
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
@submit="handleSubmitSocialMedia"
|
||||
>
|
||||
<base-card>
|
||||
<h2 class="title">{{ $t('settings.social-media.name') }}</h2>
|
||||
<ds-space v-if="socialMediaLinks" margin-top="base" margin="x-small">
|
||||
<ds-list>
|
||||
<ds-list-item v-for="link in socialMediaLinks" :key="link.id" class="list-item--high">
|
||||
<ds-input
|
||||
v-if="editingLink.id === link.id"
|
||||
id="editSocialMedia"
|
||||
model="socialMediaUrl"
|
||||
type="text"
|
||||
:placeholder="$t('settings.social-media.placeholder')"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<a :href="link.url" target="_blank">
|
||||
<img :src="link.favicon" alt="Link:" height="16" width="16" />
|
||||
{{ link.url }}
|
||||
</a>
|
||||
<span class="divider">|</span>
|
||||
<base-button
|
||||
icon="edit"
|
||||
circle
|
||||
ghost
|
||||
@click="handleEditSocialMedia(link)"
|
||||
:title="$t('actions.edit')"
|
||||
data-test="edit-button"
|
||||
/>
|
||||
<base-button
|
||||
icon="trash"
|
||||
circle
|
||||
ghost
|
||||
@click="handleDeleteSocialMedia(link)"
|
||||
:title="$t('actions.delete')"
|
||||
data-test="delete-button"
|
||||
/>
|
||||
</template>
|
||||
</ds-list-item>
|
||||
</ds-list>
|
||||
</ds-space>
|
||||
|
||||
<ds-space margin-top="base">
|
||||
<base-card>
|
||||
<ds-heading tag="h2" class="title">{{ $t('settings.social-media.name') }}</ds-heading>
|
||||
<my-something-list
|
||||
:useFormData="useFormData"
|
||||
:useFormSchema="useFormSchema"
|
||||
:useItems="socialMediaLinks"
|
||||
:defaultItem="{ url: '' }"
|
||||
:namePropertyKey="'url'"
|
||||
:callbacks="{
|
||||
handleInput: () => {},
|
||||
handleInputValid,
|
||||
edit: callbackEditSocialMedia,
|
||||
submit: handleSubmitSocialMedia,
|
||||
delete: callbackDeleteSocialMedia,
|
||||
}"
|
||||
>
|
||||
<template #list-item="{ item }">
|
||||
<social-media-list-item :item="item" />
|
||||
</template>
|
||||
<template #edit-item>
|
||||
<ds-input
|
||||
v-if="!editingLink.id"
|
||||
id="addSocialMedia"
|
||||
id="editSocialMedia"
|
||||
model="socialMediaUrl"
|
||||
type="text"
|
||||
:placeholder="$t('settings.social-media.placeholder')"
|
||||
/>
|
||||
<ds-space margin-top="base">
|
||||
<base-button filled :disabled="disabled" type="submit">
|
||||
{{ editingLink.id ? $t('actions.save') : $t('settings.social-media.submit') }}
|
||||
</base-button>
|
||||
<base-button v-if="editingLink.id" id="cancel" danger @click="handleCancel()">
|
||||
{{ $t('actions.cancel') }}
|
||||
</base-button>
|
||||
</ds-space>
|
||||
</ds-space>
|
||||
</base-card>
|
||||
</ds-form>
|
||||
</template>
|
||||
</my-something-list>
|
||||
</base-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import unionBy from 'lodash/unionBy'
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
import MySomethingList from '~/components/_new/features/MySomethingList/MySomethingList.vue'
|
||||
import SocialMediaListItem from '~/components/_new/features/SocialMedia/SocialMediaListItem.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MySomethingList,
|
||||
SocialMediaListItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
useFormData: {
|
||||
socialMediaUrl: '',
|
||||
},
|
||||
formSchema: {
|
||||
useFormSchema: {
|
||||
socialMediaUrl: {
|
||||
type: 'url',
|
||||
message: this.$t('common.validations.url'),
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
editingLink: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
currentSocialMediaLinks() {
|
||||
const domainRegex = /^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g
|
||||
const { socialMedia = [] } = this.currentUser
|
||||
return socialMedia.map(({ id, url }) => {
|
||||
const [domain] = url.match(domainRegex) || []
|
||||
const favicon = domain ? `${domain}/favicon.ico` : null
|
||||
return { id, url, favicon }
|
||||
})
|
||||
},
|
||||
socialMediaLinks() {
|
||||
const domainRegex = /^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g
|
||||
const { socialMedia = [] } = this.currentUser
|
||||
@ -106,28 +82,83 @@ export default {
|
||||
...mapMutations({
|
||||
setCurrentUser: 'auth/SET_USER',
|
||||
}),
|
||||
handleCancel() {
|
||||
this.editingLink = {}
|
||||
this.formData.socialMediaUrl = ''
|
||||
this.disabled = true
|
||||
},
|
||||
handleEditSocialMedia(link) {
|
||||
this.editingLink = link
|
||||
this.formData.socialMediaUrl = link.url
|
||||
},
|
||||
handleInput(data) {
|
||||
this.disabled = true
|
||||
},
|
||||
handleInputValid(data) {
|
||||
handleInputValid(thisList, data) {
|
||||
if (data.socialMediaUrl.length < 1) {
|
||||
this.disabled = true
|
||||
thisList.disabled = true
|
||||
} else {
|
||||
this.disabled = false
|
||||
thisList.disabled = false
|
||||
}
|
||||
},
|
||||
async handleDeleteSocialMedia(link) {
|
||||
callbackEditSocialMedia(thisList, link) {
|
||||
thisList.formData.socialMediaUrl = link.url
|
||||
// try to set focus on link edit field
|
||||
// thisList.$refs.socialMediaUrl.$el.focus()
|
||||
// !!! Check for existenz
|
||||
// this.$scopedSlots.default()[0].context.$refs
|
||||
// thisList.$scopedSlots['edit-item']()[0].$el.focus()
|
||||
// console.log(thisList.$scopedSlots['edit-item']()[0].context.$refs)
|
||||
// console.log(thisList.$scopedSlots['edit-item']()[0].context.$refs)
|
||||
// console.log(thisList.$refs)
|
||||
},
|
||||
async handleSubmitSocialMedia(thisList, isCreation, item, formData) {
|
||||
item.url = formData.socialMediaUrl
|
||||
|
||||
const items = this.socialMediaLinks
|
||||
const duplicateUrl = items.find((eleItem) => eleItem.url === item.url)
|
||||
if (duplicateUrl && duplicateUrl.id !== item.id) {
|
||||
return thisList.$toast.error(thisList.$t('settings.social-media.requireUnique'))
|
||||
}
|
||||
|
||||
let mutation, variables, successMessage
|
||||
if (isCreation) {
|
||||
mutation = gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables = { url: item.url }
|
||||
successMessage = thisList.$t('settings.social-media.successAdd')
|
||||
} else {
|
||||
mutation = gql`
|
||||
mutation($id: ID!, $url: String!) {
|
||||
UpdateSocialMedia(id: $id, url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables = { id: item.id, url: item.url }
|
||||
successMessage = thisList.$t('settings.data.success')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
await thisList.$apollo.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
update: (_store, { data }) => {
|
||||
const newSocialMedia = !isCreation ? data.UpdateSocialMedia : data.CreateSocialMedia
|
||||
this.setCurrentUser({
|
||||
...this.currentUser,
|
||||
socialMedia: unionBy([newSocialMedia], this.currentUser.socialMedia, 'id'),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
thisList.$toast.success(successMessage)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
thisList.$toast.error(err.message)
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
async callbackDeleteSocialMedia(thisList, item) {
|
||||
try {
|
||||
await thisList.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!) {
|
||||
DeleteSocialMedia(id: $id) {
|
||||
@ -137,11 +168,11 @@ export default {
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: link.id,
|
||||
id: item.id,
|
||||
},
|
||||
update: (store, { data }) => {
|
||||
const socialMedia = this.currentUser.socialMedia.filter(
|
||||
(element) => element.id !== link.id,
|
||||
(element) => element.id !== item.id,
|
||||
)
|
||||
this.setCurrentUser({
|
||||
...this.currentUser,
|
||||
@ -150,87 +181,11 @@ export default {
|
||||
},
|
||||
})
|
||||
|
||||
this.$toast.success(this.$t('settings.social-media.successDelete'))
|
||||
thisList.$toast.success(thisList.$t('settings.social-media.successDelete'))
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
async handleSubmitSocialMedia() {
|
||||
const isEditing = !!this.editingLink.id
|
||||
const url = this.formData.socialMediaUrl
|
||||
|
||||
const duplicateUrl = this.socialMediaLinks.find((link) => link.url === url)
|
||||
if (duplicateUrl && duplicateUrl.id !== this.editingLink.id) {
|
||||
return this.$toast.error(this.$t('settings.social-media.requireUnique'))
|
||||
}
|
||||
|
||||
let mutation = gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = { url }
|
||||
let successMessage = this.$t('settings.social-media.successAdd')
|
||||
|
||||
if (isEditing) {
|
||||
mutation = gql`
|
||||
mutation($id: ID!, $url: String!) {
|
||||
UpdateSocialMedia(id: $id, url: $url) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
variables.id = this.editingLink.id
|
||||
successMessage = this.$t('settings.data.success')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
update: (store, { data }) => {
|
||||
const newSocialMedia = isEditing ? data.UpdateSocialMedia : data.CreateSocialMedia
|
||||
this.setCurrentUser({
|
||||
...this.currentUser,
|
||||
socialMedia: unionBy([newSocialMedia], this.currentUser.socialMedia, 'id'),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
this.$toast.success(successMessage)
|
||||
this.formData.socialMediaUrl = ''
|
||||
this.disabled = true
|
||||
this.editingLink = {}
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
thisList.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
opacity: 0.4;
|
||||
padding: 0 $space-small;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-item--high {
|
||||
.ds-list-item-prefix {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.ds-list-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -21,18 +21,30 @@ export default ({ app, req, cookie, store }) => {
|
||||
const changeHandler = async (mutation) => {
|
||||
if (process.server) return
|
||||
|
||||
const newLocale = mutation.payload.locale
|
||||
const currentLocale = await app.$cookies.get(key)
|
||||
const isDifferent = newLocale !== currentLocale
|
||||
const localeInStore = mutation.payload.locale
|
||||
let cookieExists = true
|
||||
let localeInCookies = await app.$cookies.get(key)
|
||||
if (!localeInCookies) {
|
||||
cookieExists = false
|
||||
localeInCookies = navigator.language.split('-')[0] // get browser language
|
||||
}
|
||||
const isLocaleStoreSameAsCookies = localeInStore === localeInCookies
|
||||
|
||||
if (!isDifferent) {
|
||||
// cookie has to be set, otherwise Cypress test does not work
|
||||
if (cookieExists && isLocaleStoreSameAsCookies) {
|
||||
return
|
||||
}
|
||||
|
||||
app.$cookies.set(key, newLocale)
|
||||
if (!app.$i18n.localeExists(newLocale)) {
|
||||
import(`~/locales/${newLocale}.json`).then((res) => {
|
||||
app.$i18n.add(newLocale, res.default)
|
||||
const expires = new Date()
|
||||
expires.setDate(expires.getDate() + app.$env.COOKIE_EXPIRE_TIME)
|
||||
app.$cookies.set(key, localeInStore, {
|
||||
expires,
|
||||
// maxAge: app.$env.COOKIE_EXPIRE_TIME * 60 * 60 * 24, // days to seconds
|
||||
sameSite: 'lax', // for the meaning see https://www.thinktecture.com/de/identity/samesite/samesite-in-a-nutshell/
|
||||
})
|
||||
if (!app.$i18n.localeExists(localeInStore)) {
|
||||
import(`~/locales/${localeInStore}.json`).then((res) => {
|
||||
app.$i18n.add(localeInStore, res.default)
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,7 +54,7 @@ export default ({ app, req, cookie, store }) => {
|
||||
if (user && user._id && token) {
|
||||
// TODO: SAVE LOCALE
|
||||
// store.dispatch('usersettings/patch', {
|
||||
// uiLanguage: newLocale
|
||||
// uiLanguage: localeInStore
|
||||
// }, { root: true })
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import faker from 'faker'
|
||||
import vuexI18n from 'vuex-i18n/dist/vuex-i18n.umd.js'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import faker from '@faker-js/faker'
|
||||
import Filters from '~/plugins/vue-filters'
|
||||
import Directives from '~/plugins/vue-directives'
|
||||
import IziToast from '~/plugins/izi-toast'
|
||||
import layout from './layout.vue'
|
||||
import locales from '~/locales/index.js'
|
||||
|
||||
import '~/plugins/v-tooltip'
|
||||
|
||||
const helpers = {
|
||||
|
||||
@ -2379,6 +2379,11 @@
|
||||
ts-node "^8"
|
||||
tslib "^1"
|
||||
|
||||
"@faker-js/faker@5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f"
|
||||
integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a"
|
||||
@ -10235,11 +10240,6 @@ fake-tag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fake-tag/-/fake-tag-1.0.1.tgz#1d59da482240a02bd83500ca98976530ed154b0d"
|
||||
integrity sha512-qmewZoBpa71mM+y6oxXYW/d1xOYQmeIvnEXAt1oCmdP0sqcogWYLepR87QL1jQVLSVMVYDq2cjY6ec/Wu8/4pg==
|
||||
|
||||
faker@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/faker/-/faker-5.1.0.tgz#e10fa1dec4502551aee0eb771617a7e7b94692e8"
|
||||
integrity sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
|
||||
17
yarn.lock
17
yarn.lock
@ -1248,6 +1248,11 @@
|
||||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@faker-js/faker@5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f"
|
||||
integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a"
|
||||
@ -3058,10 +3063,6 @@ extsprintf@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
faker@Marak/faker.js#master:
|
||||
version "5.1.0"
|
||||
resolved "https://codeload.github.com/Marak/faker.js/tar.gz/91dc8a3372426bc691be56153b33e81a16459f49"
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
@ -5274,10 +5275,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rosie@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rosie/-/rosie-2.0.1.tgz#c250c4787ce450b72aa9eff26509f68589814fa2"
|
||||
integrity sha1-wlDEeHzkULcqqe/yZQn2hYmBT6I=
|
||||
rosie@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rosie/-/rosie-2.1.0.tgz#0213a9d2b0401a2549cbce5f1cd914caffa22358"
|
||||
integrity sha512-Dbzdc+prLXZuB/suRptDnBUY29SdGvND3bLg6cll8n7PNqzuyCxSlRfrkn8PqjS9n4QVsiM7RCvxCkKAkTQRjA==
|
||||
|
||||
rxjs@^6.3.3, rxjs@^6.6.3:
|
||||
version "6.6.7"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user