diff --git a/README.md b/README.md index b2f42c8b9..b5064cd0d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Join our friendly open-source community on [Discord](https://discordapp.com/invi Just introduce yourself at `#introduce-yourself` and mention `@@Mentor` to get you onboard :neckbeard: Check out the [contribution guideline](./CONTRIBUTING.md), too! +[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/0)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/0)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/1)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/1)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/2)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/2)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/3)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/3)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/4)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/4)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/5)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/5)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/6)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/6)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/7)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/7) + ## Attributions diff --git a/backend/.env.template b/backend/.env.template index 6697f09c4..5ecc5a5c4 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -17,3 +17,4 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78" SENTRY_DSN_BACKEND= COMMIT= +PUBLIC_REGISTRATION=false diff --git a/backend/package.json b/backend/package.json index 3ddfa8d23..3a0cf6be8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -93,7 +93,7 @@ "neo4j-graphql-js": "^2.7.2", "neode": "^0.3.3", "node-fetch": "~2.6.0", - "nodemailer": "^6.3.0", + "nodemailer": "^6.3.1", "nodemailer-html-to-text": "^3.1.0", "npm-run-all": "~4.1.5", "request": "~2.88.0", diff --git a/backend/public/providers.json b/backend/public/providers.json new file mode 100644 index 000000000..ef9f04bff --- /dev/null +++ b/backend/public/providers.json @@ -0,0 +1,257 @@ +[ + { + "provider_name": "Codepen", + "provider_url": "https:\/\/codepen.io", + "endpoints": [ + { + "schemes": [ + "http:\/\/codepen.io\/*", + "https:\/\/codepen.io\/*" + ], + "url": "http:\/\/codepen.io\/api\/oembed" + } + ] + }, + { + "provider_name": "DTube", + "provider_url": "https:\/\/d.tube\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/d.tube\/v\/*" + ], + "url": "https:\/\/api.d.tube\/oembed", + "discovery": true + } + ] + }, + { + "provider_name": "Facebook (Post)", + "provider_url": "https:\/\/www.facebook.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/www.facebook.com\/*\/posts\/*", + "https:\/\/www.facebook.com\/photos\/*", + "https:\/\/www.facebook.com\/*\/photos\/*", + "https:\/\/www.facebook.com\/photo.php*", + "https:\/\/www.facebook.com\/photo.php", + "https:\/\/www.facebook.com\/*\/activity\/*", + "https:\/\/www.facebook.com\/permalink.php", + "https:\/\/www.facebook.com\/media\/set?set=*", + "https:\/\/www.facebook.com\/questions\/*", + "https:\/\/www.facebook.com\/notes\/*\/*\/*" + ], + "url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json", + "discovery": true + } + ] + }, + { + "provider_name": "Facebook (Video)", + "provider_url": "https:\/\/www.facebook.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/www.facebook.com\/*\/videos\/*", + "https:\/\/www.facebook.com\/video.php" + ], + "url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json", + "discovery": true + } + ] + }, + { + "provider_name": "Flickr", + "provider_url": "https:\/\/www.flickr.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/*.flickr.com\/photos\/*", + "http:\/\/flic.kr\/p\/*", + "https:\/\/*.flickr.com\/photos\/*", + "https:\/\/flic.kr\/p\/*" + ], + "url": "https:\/\/www.flickr.com\/services\/oembed\/", + "discovery": true + } + ] + }, + { + "provider_name": "GIPHY", + "provider_url": "https:\/\/giphy.com", + "endpoints": [ + { + "schemes": [ + "https:\/\/giphy.com\/gifs\/*", + "http:\/\/gph.is\/*", + "https:\/\/media.giphy.com\/media\/*\/giphy.gif" + ], + "url": "https:\/\/giphy.com\/services\/oembed", + "discovery": true + } + ] + }, + { + "provider_name": "Instagram", + "provider_url": "https:\/\/instagram.com", + "endpoints": [ + { + "schemes": [ + "http:\/\/instagram.com\/p\/*", + "http:\/\/instagr.am\/p\/*", + "http:\/\/www.instagram.com\/p\/*", + "http:\/\/www.instagr.am\/p\/*", + "https:\/\/instagram.com\/p\/*", + "https:\/\/instagr.am\/p\/*", + "https:\/\/www.instagram.com\/p\/*", + "https:\/\/www.instagr.am\/p\/*" + ], + "url": "https:\/\/api.instagram.com\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "Meetup", + "provider_url": "http:\/\/www.meetup.com", + "endpoints": [ + { + "schemes": [ + "http:\/\/meetup.com\/*", + "https:\/\/www.meetup.com\/*", + "https:\/\/meetup.com\/*", + "http:\/\/meetu.ps\/*" + ], + "url": "https:\/\/api.meetup.com\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "MixCloud", + "provider_url": "https:\/\/mixcloud.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/www.mixcloud.com\/*\/*\/", + "https:\/\/www.mixcloud.com\/*\/*\/" + ], + "url": "https:\/\/www.mixcloud.com\/oembed\/" + } + ] + }, + { + "provider_name": "Reddit", + "provider_url": "https:\/\/reddit.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/reddit.com\/r\/*\/comments\/*\/*", + "https:\/\/www.reddit.com\/r\/*\/comments\/*\/*" + ], + "url": "https:\/\/www.reddit.com\/oembed" + } + ] + }, + { + "provider_name": "SlideShare", + "provider_url": "http:\/\/www.slideshare.net\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/www.slideshare.net\/*\/*", + "http:\/\/fr.slideshare.net\/*\/*", + "http:\/\/de.slideshare.net\/*\/*", + "http:\/\/es.slideshare.net\/*\/*", + "http:\/\/pt.slideshare.net\/*\/*" + ], + "url": "http:\/\/www.slideshare.net\/api\/oembed\/2", + "discovery": true + } + ] + }, + { + "provider_name": "SoundCloud", + "provider_url": "http:\/\/soundcloud.com\/", + "endpoints": [ + { + "schemes": [ + "http:\/\/soundcloud.com\/*", + "https:\/\/soundcloud.com\/*" + ], + "url": "https:\/\/soundcloud.com\/oembed" + } + ] + }, + { + "provider_name": "Twitch", + "provider_url": "https:\/\/www.twitch.tv", + "endpoints": [ + { + "schemes": [ + "http:\/\/clips.twitch.tv\/*", + "https:\/\/clips.twitch.tv\/*", + "http:\/\/www.twitch.tv\/*", + "https:\/\/www.twitch.tv\/*", + "http:\/\/twitch.tv\/*", + "https:\/\/twitch.tv\/*" + ], + "url": "https:\/\/api.twitch.tv\/v4\/oembed", + "formats": [ + "json" + ] + } + ] + }, + { + "provider_name": "Twitter", + "provider_url": "http:\/\/www.twitter.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/twitter.com\/*\/status\/*", + "https:\/\/*.twitter.com\/*\/status\/*" + ], + "url": "https:\/\/publish.twitter.com\/oembed" + } + ] + }, + { + "provider_name": "Vimeo", + "provider_url": "https:\/\/vimeo.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/vimeo.com\/*", + "https:\/\/vimeo.com\/album\/*\/video\/*", + "https:\/\/vimeo.com\/channels\/*\/*", + "https:\/\/vimeo.com\/groups\/*\/videos\/*", + "https:\/\/vimeo.com\/ondemand\/*\/*", + "https:\/\/player.vimeo.com\/video\/*" + ], + "url": "https:\/\/vimeo.com\/api\/oembed.{format}", + "discovery": true + } + ] + }, + { + "provider_name": "YouTube", + "provider_url": "https:\/\/www.youtube.com\/", + "endpoints": [ + { + "schemes": [ + "https:\/\/*.youtube.com\/watch*", + "https:\/\/*.youtube.com\/v\/*", + "https:\/\/youtu.be\/*" + ], + "url": "https:\/\/www.youtube.com\/oembed", + "discovery": true + } + ] + } +] \ No newline at end of file diff --git a/backend/src/config/index.js b/backend/src/config/index.js index 3709b5cc3..00361ba63 100644 --- a/backend/src/config/index.js +++ b/backend/src/config/index.js @@ -21,7 +21,12 @@ const { GRAPHQL_URI = 'http://localhost:4000', } = process.env -export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE } +export const requiredConfigs = { + MAPBOX_TOKEN, + JWT_SECRET, + PRIVATE_KEY_PASSPHRASE, +} + export const smtpConfigs = { SMTP_HOST, SMTP_PORT, @@ -30,7 +35,12 @@ export const smtpConfigs = { SMTP_PASSWORD, } export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD } -export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI } +export const serverConfigs = { + GRAPHQL_PORT, + CLIENT_URI, + GRAPHQL_URI, + PUBLIC_REGISTRATION: process.env.PUBLIC_REGISTRATION === 'true', +} export const developmentConfigs = { DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG, diff --git a/backend/src/middleware/email/templateBuilder.js b/backend/src/middleware/email/templateBuilder.js index 4b7bcc7cd..b4b7b78ca 100644 --- a/backend/src/middleware/email/templateBuilder.js +++ b/backend/src/middleware/email/templateBuilder.js @@ -18,7 +18,7 @@ export const signupTemplate = ({ email, nonce }) => { subject, html: mustache.render( templates.layout, - { actionUrl, supportUrl, subject }, + { actionUrl, nonce, supportUrl, subject }, { content: templates.signup }, ), } diff --git a/backend/src/middleware/email/templates/signup.html b/backend/src/middleware/email/templates/signup.html index 79894b584..ad60d9323 100644 --- a/backend/src/middleware/email/templates/signup.html +++ b/backend/src/middleware/email/templates/signup.html @@ -60,7 +60,9 @@
-

Falls Du Dich nicht selbst bei Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: {{{ nonce }}}

+

Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.

+

Falls Du Dich nicht selbst bei Human Connection angemeldet hast, schau doch mal vorbei! Wir sind ein gemeinnütziges Aktionsnetzwerk – von Menschen für Menschen.

PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese @@ -169,7 +171,9 @@
-

If you didn't sign up for If the above button doesn't work, you can also copy the following code into your browser window: {{{ nonce }}}

+

However, this only works if you have registered through our website.

+

If you didn't sign up for Human Connection we recommend you to check it out! It's a social network from people for people who want to connect and change the world together.

PS: If you ignore this e-mail we will not create an account diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 8343443c9..df87f743e 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -111,6 +111,8 @@ const noEmailFilter = rule({ return !('email' in args) }) +const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION) + // Permissions const permissions = shield( { @@ -137,7 +139,7 @@ const permissions = shield( '*': deny, login: allow, SignupByInvitation: allow, - Signup: isAdmin, + Signup: or(publicRegistration, isAdmin), SignupVerification: allow, CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)), UpdateUser: onlyYourself, diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 6448d7450..86900c3ae 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -105,4 +105,8 @@ module.exports = { target: 'Location', direction: 'out', }, + allowEmbedIframes: { + type: 'boolean', + default: false, + }, } diff --git a/backend/src/schema/resolvers/embeds/findProvider.js b/backend/src/schema/resolvers/embeds/findProvider.js index 491cbb9e8..a4240895f 100644 --- a/backend/src/schema/resolvers/embeds/findProvider.js +++ b/backend/src/schema/resolvers/embeds/findProvider.js @@ -3,6 +3,7 @@ import path from 'path' import minimatch from 'minimatch' let oEmbedProvidersFile = fs.readFileSync(path.join(__dirname, './providers.json'), 'utf8') + // some providers allow a format parameter // we need JSON oEmbedProvidersFile = oEmbedProvidersFile.replace(/\{format\}/g, 'json') diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index b5df8111e..06b25b4fa 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -176,6 +176,7 @@ export default { 'about', 'termsAndConditionsAgreedVersion', 'termsAndConditionsAgreedAt', + 'allowEmbedIframes', ], boolean: { followedByCurrentUser: diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 784a48c06..986f4a41f 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -86,6 +86,7 @@ describe('UpdateUser', () => { name: 'John Doe', termsAndConditionsAgreedVersion: null, termsAndConditionsAgreedAt: null, + allowEmbedIframes: false, } variables = { diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index f1c38b8d6..d9084dd90 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -27,6 +27,8 @@ type User { termsAndConditionsAgreedVersion: String termsAndConditionsAgreedAt: String + allowEmbedIframes: Boolean + friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)") @@ -166,6 +168,7 @@ type Mutation { about: String termsAndConditionsAgreedVersion: String termsAndConditionsAgreedAt: String + allowEmbedIframes: Boolean ): User DeleteUser(id: ID!, resource: [Deletable]): User diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index 962f92781..b65be795d 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -16,6 +16,7 @@ export default function create() { about: faker.lorem.paragraph(), termsAndConditionsAgreedVersion: '0.0.1', termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z', + allowEmbedIframes: false, } defaults.slug = slugify(defaults.name, { lower: true }) args = { diff --git a/backend/yarn.lock b/backend/yarn.lock index ea3a38991..5adc174ce 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -6182,10 +6182,10 @@ nodemailer-html-to-text@^3.1.0: dependencies: html-to-text "^5.1.1" -nodemailer@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12" - integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw== +nodemailer@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346" + integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ== nodemon@~1.19.3: version "1.19.3" diff --git a/docker-compose.override.yml b/docker-compose.override.yml index ba9b32c18..df7d6ac92 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -15,6 +15,7 @@ services: - ./webapp:/nitro-web environment: - NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/` + - PUBLIC_REGISTRATION=true command: yarn run dev backend: build: @@ -28,6 +29,7 @@ services: - SMTP_PORT=25 - SMTP_IGNORE_TLS=true - "DEBUG=${DEBUG}" + - PUBLIC_REGISTRATION=true maintenance: image: humanconnection/maintenance:latest build: diff --git a/package.json b/package.json index 5427090dc..18a46e56d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "codecov": "^3.6.1", "cross-env": "^6.0.3", "cypress": "^3.4.1", - "cypress-cucumber-preprocessor": "^1.16.1", + "cypress-cucumber-preprocessor": "^1.16.2", "cypress-file-upload": "^3.3.4", "cypress-plugin-retries": "^1.3.0", "dotenv": "^8.1.0", diff --git a/styleguide b/styleguide index d46fc1570..808b3c5a9 160000 --- a/styleguide +++ b/styleguide @@ -1 +1 @@ -Subproject commit d46fc1570c6bcea328ae4cc3a4892745edea7319 +Subproject commit 808b3c5a9523505cb80b20b50348d29ba9932845 diff --git a/webapp/.env.template b/webapp/.env.template index bebdeaaaf..fdabcf003 100644 --- a/webapp/.env.template +++ b/webapp/.env.template @@ -1,3 +1,4 @@ MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" SENTRY_DSN_WEBAPP= COMMIT= +PUBLIC_REGISTRATION=false diff --git a/webapp/components/Editor/Editor.story.js b/webapp/components/Editor/Editor.story.js index aaaa0eb58..7a69b347f 100644 --- a/webapp/components/Editor/Editor.story.js +++ b/webapp/components/Editor/Editor.story.js @@ -5,8 +5,12 @@ import helpers from '~/storybook/helpers' import Vue from 'vue' const embed = { + image: 'https://i.ytimg.com/vi/ptCcgLM-p8k/maxresdefault_live.jpg', + title: 'Video Titel', + // html: null, + description: 'Video Description', html: - '', + '', } const plugins = [ @@ -114,15 +118,12 @@ storiesOf('Editor', module) }), template: ``, })) - .add('Embeds', () => ({ + .add('Embeds with iframe', () => ({ components: { HcEditor }, store: helpers.store, data: () => ({ users, content: ` -

- The following link should render a youtube video in addition to the link. -

https://www.youtube.com/watch?v=qkdXAtO40Fo @@ -130,3 +131,16 @@ storiesOf('Editor', module) }), template: ``, })) + .add('Embeds with plain link', () => ({ + components: { HcEditor }, + store: helpers.store, + data: () => ({ + users, + content: ` + + https://telegram.org/ + + `, + }), + template: ``, + })) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 8d3ce281b..fa37c64dc 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -342,4 +342,84 @@ 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; +} + +.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/defaultExtensions.spec.js b/webapp/components/Editor/defaultExtensions.spec.js index 5bf8126b0..78924db55 100644 --- a/webapp/components/Editor/defaultExtensions.spec.js +++ b/webapp/components/Editor/defaultExtensions.spec.js @@ -35,7 +35,7 @@ describe('defaultExtensions', () => { it('renders mentioning as link', () => { const editor = createEditor() const expected = - '

This is a post content mentioning @alicia-luettgen.

' + '

This is a post content mentioning @alicia-luettgen.

' expect(editor.getHTML()).toEqual(expected) }) }) @@ -49,7 +49,7 @@ describe('defaultExtensions', () => { it('renders hashtag as link', () => { const editor = createEditor() const expected = - '

This is a post content with a hashtag #metoo.

' + '

This is a post content with a hashtag #metoo.

' expect(editor.getHTML()).toEqual(expected) }) }) diff --git a/webapp/components/Editor/nodes/Embed.js b/webapp/components/Editor/nodes/Embed.js index 0a12e06ef..0d7a82a18 100644 --- a/webapp/components/Editor/nodes/Embed.js +++ b/webapp/components/Editor/nodes/Embed.js @@ -1,13 +1,12 @@ import { Node } from 'tiptap' import pasteRule from '../commands/pasteRule' import { compileToFunctions } from 'vue-template-compiler' +import Vue from 'vue' +import EmbedComponent from '~/components/Embed/EmbedComponent' + +Vue.component(EmbedComponent) +const template = `` -const template = ` - -
- {{ dataEmbedUrl }} - -` const compiledTemplate = compileToFunctions(template) export default class Embed extends Node { @@ -67,16 +66,13 @@ export default class Embed extends Node { embedData: {}, }), async created() { - if (!this.options) return {} - this.embedData = await this.options.onEmbed({ url: this.dataEmbedUrl }) + if (this.options) { + this.embedData = await this.options.onEmbed({ url: this.dataEmbedUrl }) + } }, computed: { - embedClass() { - return this.embedHtml ? 'embed' : '' - }, - embedHtml() { - const { html = '' } = this.embedData - return html + componentType() { + return EmbedComponent }, dataEmbedUrl: { get() { diff --git a/webapp/components/Editor/nodes/Embed.spec.js b/webapp/components/Editor/nodes/Embed.spec.js index e38bda061..639f99338 100644 --- a/webapp/components/Editor/nodes/Embed.spec.js +++ b/webapp/components/Editor/nodes/Embed.spec.js @@ -1,31 +1,30 @@ -import { shallowMount } from '@vue/test-utils' +import { shallowMount, createLocalVue } from '@vue/test-utils' +import Vuex from 'vuex' +import Styleguide from '@human-connection/styleguide' import Embed from './Embed' -let Wrapper -let propsData +let Wrapper, propsData, component const someUrl = 'https://www.youtube.com/watch?v=qkdXAtO40Fo' +const localVue = createLocalVue() + +localVue.use(Vuex) +localVue.use(Styleguide) describe('Embed.vue', () => { beforeEach(() => { propsData = {} - const component = new Embed() - Wrapper = ({ mocks, propsData }) => { + component = new Embed() + Wrapper = ({ propsData }) => { return shallowMount(component.view, { propsData }) } }) - it('renders anchor', () => { - propsData = { - node: { attrs: { href: someUrl } }, - } - expect(Wrapper({ propsData }).is('a')).toBe(true) - }) - describe('given a href', () => { describe('onEmbed returned embed data', () => { beforeEach(() => { propsData.options = { onEmbed: () => ({ + __typename: 'Embed', type: 'video', title: 'Baby Loves Cat', author: 'Merkley Family', @@ -49,9 +48,7 @@ describe('Embed.vue', () => { propsData.node = { attrs: { href: 'https://www.youtube.com/watch?v=qkdXAtO40Fo' } } const wrapper = Wrapper({ propsData }) await wrapper.html() - expect(wrapper.find('div iframe').attributes('src')).toEqual( - 'https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed', - ) + expect(wrapper.contains('embed-component-stub')).toBe(true) }) }) diff --git a/webapp/components/Editor/nodes/Link.js b/webapp/components/Editor/nodes/Link.js index 6c015c030..4856566d6 100644 --- a/webapp/components/Editor/nodes/Link.js +++ b/webapp/components/Editor/nodes/Link.js @@ -27,6 +27,7 @@ export default class Link extends TipTapLink { { ...node.attrs, rel: 'noopener noreferrer nofollow', + target: '_blank', }, 0, ], diff --git a/webapp/components/Editor/plugins/eventHandler.js b/webapp/components/Editor/plugins/eventHandler.js index c390a066d..807949aa8 100644 --- a/webapp/components/Editor/plugins/eventHandler.js +++ b/webapp/components/Editor/plugins/eventHandler.js @@ -10,7 +10,6 @@ export default class EventHandler extends Extension { new Plugin({ props: { transformPastedText(text) { - // console.log('#### transformPastedText', text) return text.trim() }, transformPastedHTML(html) { @@ -33,7 +32,6 @@ export default class EventHandler extends Extension { .replace(/

(\s*
\s*)+/gim, '

') // remove additional linebreaks when last child inside p tags .replace(/(\s*
\s*)+<\/p>/gim, '

') - // console.log('#### transformPastedHTML', html) return html }, // transformPasted(slice) { diff --git a/webapp/components/Embed/EmbedComponent.spec.js b/webapp/components/Embed/EmbedComponent.spec.js new file mode 100644 index 000000000..5ad8dd324 --- /dev/null +++ b/webapp/components/Embed/EmbedComponent.spec.js @@ -0,0 +1,206 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import Vuex from 'vuex' +import Styleguide from '@human-connection/styleguide' +import EmbedComponent from './EmbedComponent' + +let wrapper, propsData, getters, mocks +const someUrl = 'https://www.youtube.com/watch?v=qkdXAtO40Fo' +const localVue = createLocalVue() + +localVue.use(Vuex) +localVue.use(Styleguide) + +describe('EmbedComponent.vue', () => { + const Wrapper = () => { + const store = new Vuex.Store({ + getters, + }) + return mount(EmbedComponent, { propsData, localVue, store, mocks }) + } + + beforeEach(() => { + mocks = { + $t: a => a, + $apollo: { + mutate: jest + .fn() + .mockResolvedValueOnce({ data: { UpdateUser: { allowEmbedIframes: true } } }), + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + } + propsData = {} + getters = { + 'auth/user': () => { + return { id: 'u5', allowEmbedIframes: false } + }, + } + }) + + describe('given a href only for a link ', () => { + beforeEach(() => { + propsData.embedData = { + __typename: 'Embed', + type: 'link', + title: '👻 ✉️ Bruno... le ciel sur répondeur ! 🔮 🧠 - Clément FREZE', + author: null, + publisher: 'PeerTube.social', + date: null, + description: + 'Salut tout le monde ! Aujourd’hui, une vidéo sur le scepticisme, nous allons parler médiumnité avec le cas de Bruno CHARVET : « Bruno, un nouveau message ». Merci de rester respectueux dans les commentaires : SOURCES : Les sources des vi...', + url: 'https://peertube.social/videos/watch/f3cb1945-a8f7-481f-a465-946c6f884e50', + image: 'https://peertube.social/static/thumbnails/f3cb1945-a8f7-481f-a465-946c6f884e50.jpg', + audio: null, + video: null, + lang: 'fr', + sources: ['resource', 'oembed'], + html: null, + } + wrapper = Wrapper() + }) + + it('shows the title', () => { + expect(wrapper.find('h4').text()).toBe( + '👻 ✉️ Bruno... le ciel sur répondeur ! 🔮 🧠 - Clément FREZE', + ) + }) + + it('shows the description', () => { + expect(wrapper.find('.embed-content p').text()).toBe( + 'Salut tout le monde ! Aujourd’hui, une vidéo sur le scepticisme, nous allons parler médiumnité avec le cas de Bruno CHARVET : « Bruno, un nouveau message ». Merci de rester respectueux dans les commentaires : SOURCES : Les sources des vi...', + ) + }) + + it('shows preview Images for link', () => { + expect(wrapper.find('.embed-preview-image').exists()).toBe(true) + }) + }) + + describe('given a href with embed html', () => { + describe('onEmbed returned title and description', () => { + beforeEach(() => { + propsData.embedData = { + __typename: 'Embed', + title: 'Baby Loves Cat', + description: + 'She’s incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. That’s a sleep sack she’s in. Not a starfish outfit. Al...', + } + wrapper = Wrapper() + }) + + it('show the title', () => { + expect(wrapper.find('h4').text()).toBe('Baby Loves Cat') + }) + + it('show the desciption', () => { + expect(wrapper.find('.embed-content p').text()).toBe( + 'She’s incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. That’s a sleep sack she’s in. Not a starfish outfit. Al...', + ) + }) + + describe('onEmbed returned embed data with html', () => { + beforeEach(() => { + propsData.embedData = { + __typename: 'Embed', + type: 'video', + title: 'Baby Loves Cat', + author: 'Merkley Family', + publisher: 'YouTube', + date: '2015-08-16T00:00:00.000Z', + description: + 'She’s incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. That’s a sleep sack she’s in. Not a starfish outfit. Al...', + url: someUrl, + image: 'https://i.ytimg.com/vi/qkdXAtO40Fo/maxresdefault.jpg', + audio: null, + video: null, + lang: 'de', + sources: ['resource', 'oembed'], + html: + '', + } + wrapper = Wrapper() + }) + + it('shows a simple link when a user closes the embed preview', () => { + wrapper.find('.embed-close-button').trigger('click') + expect(wrapper.vm.showLinkOnly).toBe(true) + }) + + it('opens the data privacy overlay when a user clicks on the preview image', () => { + wrapper.find('.embed-preview-image--clickable').trigger('click') + expect(wrapper.vm.showOverlay).toBe(true) + }) + + describe('shows iframe', () => { + beforeEach(() => { + wrapper.setData({ showOverlay: true }) + }) + + it('when user agress', () => { + wrapper.find('.ds-button-primary').trigger('click') + expect(wrapper.vm.showEmbed).toBe(true) + }) + + it('does not show iframe when user clicks to cancel', () => { + wrapper.find('.ds-button-ghost').trigger('click') + expect(wrapper.vm.showEmbed).toBe(false) + }) + + describe("doesn't set permanently", () => { + beforeEach(() => { + wrapper.find('.ds-button-primary').trigger('click') + }) + + it("if user doesn't give consent", () => { + expect(wrapper.vm.checkedAlwaysAllowEmbeds).toBe(false) + }) + + it("doesn't update the user's profile", () => { + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + }) + + describe('sets permanently', () => { + beforeEach(() => { + wrapper.find('input[type=checkbox]').trigger('click') + wrapper.find('.ds-button-primary').trigger('click') + }) + + it('changes setting permanetly when user requests', () => { + expect(wrapper.vm.checkedAlwaysAllowEmbeds).toBe(true) + }) + + it("updates the user's profile", () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + }) + }) + }) + + describe('immediately shows', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { id: 'u5', allowEmbedIframes: true } + }, + } + wrapper = Wrapper() + }) + + it('sets showEmbed to true', () => { + expect(wrapper.vm.showEmbed).toBe(true) + }) + + it('the iframe returned from oEmbed', () => { + expect(wrapper.find('iframe').html()).toEqual(propsData.embedData.html) + }) + + it('does not display image to click', () => { + expect(wrapper.find('.embed-preview-image--clickable').exists()).toBe(false) + }) + }) + }) + }) + }) +}) diff --git a/webapp/components/Embed/EmbedComponent.vue b/webapp/components/Embed/EmbedComponent.vue new file mode 100644 index 000000000..5dc8ad00c --- /dev/null +++ b/webapp/components/Embed/EmbedComponent.vue @@ -0,0 +1,153 @@ + + + diff --git a/webapp/components/PasswordReset/VerifyNonce.spec.js b/webapp/components/EnterNonce/EnterNonce.spec.js similarity index 67% rename from webapp/components/PasswordReset/VerifyNonce.spec.js rename to webapp/components/EnterNonce/EnterNonce.spec.js index ebe552f0d..67f1f9073 100644 --- a/webapp/components/PasswordReset/VerifyNonce.spec.js +++ b/webapp/components/EnterNonce/EnterNonce.spec.js @@ -1,12 +1,12 @@ import { mount, createLocalVue } from '@vue/test-utils' -import VerifyNonce from './VerifyNonce.vue' +import EnterNonce from './EnterNonce.vue' import Styleguide from '@human-connection/styleguide' const localVue = createLocalVue() localVue.use(Styleguide) -describe('VerifyNonce ', () => { +describe('EnterNonce ', () => { let wrapper let Wrapper let mocks @@ -25,28 +25,28 @@ describe('VerifyNonce ', () => { beforeEach(jest.useFakeTimers) Wrapper = () => { - return mount(VerifyNonce, { + return mount(EnterNonce, { mocks, localVue, propsData, }) } - it('renders a verify nonce form', () => { + it('renders an enter nonce form', () => { wrapper = Wrapper() - expect(wrapper.find('.verify-nonce').exists()).toBe(true) + expect(wrapper.find('form').exists()).toBe(true) }) - describe('after verification nonce given', () => { + describe('after nonce entered', () => { beforeEach(() => { wrapper = Wrapper() wrapper.find('input#nonce').setValue('123456') wrapper.find('form').trigger('submit') }) - it('emits `verification`', () => { + it('emits `nonceEntered`', () => { const expected = [[{ nonce: '123456', email: 'mail@example.org' }]] - expect(wrapper.emitted('verification')).toEqual(expected) + expect(wrapper.emitted('nonceEntered')).toEqual(expected) }) }) }) diff --git a/webapp/components/EnterNonce/EnterNonce.vue b/webapp/components/EnterNonce/EnterNonce.vue new file mode 100644 index 000000000..d936544ad --- /dev/null +++ b/webapp/components/EnterNonce/EnterNonce.vue @@ -0,0 +1,66 @@ + + + diff --git a/webapp/components/PasswordReset/ChangePassword.spec.js b/webapp/components/PasswordReset/ChangePassword.spec.js index e93d5d00d..b5d85dea7 100644 --- a/webapp/components/PasswordReset/ChangePassword.spec.js +++ b/webapp/components/PasswordReset/ChangePassword.spec.js @@ -39,7 +39,7 @@ describe('ChangePassword ', () => { }) } - describe('given email and verification nonce', () => { + describe('given email and nonce', () => { beforeEach(() => { propsData.email = 'mail@example.org' propsData.nonce = '123456' @@ -66,7 +66,7 @@ describe('ChangePassword ', () => { describe('password reset successful', () => { it('displays success message', () => { - const expected = 'verify-nonce.form.change-password.success' + const expected = 'components.password-reset.change-password.success' expect(mocks.$t).toHaveBeenCalledWith(expected) }) diff --git a/webapp/components/PasswordReset/ChangePassword.vue b/webapp/components/PasswordReset/ChangePassword.vue index 3de4f048a..e45612171 100644 --- a/webapp/components/PasswordReset/ChangePassword.vue +++ b/webapp/components/PasswordReset/ChangePassword.vue @@ -1,54 +1,62 @@ - diff --git a/webapp/components/PasswordReset/VerifyNonce.vue b/webapp/components/PasswordReset/VerifyNonce.vue deleted file mode 100644 index 94ae13564..000000000 --- a/webapp/components/PasswordReset/VerifyNonce.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - diff --git a/webapp/components/Registration/CreateUserAccount.spec.js b/webapp/components/Registration/CreateUserAccount.spec.js index 6551211c0..67a14b3c5 100644 --- a/webapp/components/Registration/CreateUserAccount.spec.js +++ b/webapp/components/Registration/CreateUserAccount.spec.js @@ -8,6 +8,7 @@ const localVue = createLocalVue() localVue.use(Styleguide) config.stubs['sweetalert-icon'] = '' config.stubs['client-only'] = '' +config.stubs['nuxt-link'] = '' describe('CreateUserAccount', () => { let wrapper, Wrapper, mocks, propsData, stubs @@ -102,7 +103,9 @@ describe('CreateUserAccount', () => { it('displays success', async () => { await action() - expect(mocks.$t).toHaveBeenCalledWith('registration.create-user-account.success') + expect(mocks.$t).toHaveBeenCalledWith( + 'components.registration.create-user-account.success', + ) }) describe('after timeout', () => { @@ -130,7 +133,9 @@ describe('CreateUserAccount', () => { it('displays form errors', async () => { await action() - expect(wrapper.find('.backendErrors').text()).toContain('Invalid nonce') + expect(mocks.$t).toHaveBeenCalledWith( + 'components.registration.create-user-account.error', + ) }) }) }) diff --git a/webapp/components/Registration/CreateUserAccount.vue b/webapp/components/Registration/CreateUserAccount.vue index b5711873c..be01e0bb0 100644 --- a/webapp/components/Registration/CreateUserAccount.vue +++ b/webapp/components/Registration/CreateUserAccount.vue @@ -1,117 +1,111 @@ + diff --git a/webapp/pages/password-reset/change-password.vue b/webapp/pages/password-reset/change-password.vue index 7ab124782..3efdd001b 100644 --- a/webapp/pages/password-reset/change-password.vue +++ b/webapp/pages/password-reset/change-password.vue @@ -3,7 +3,11 @@ :email="email" :nonce="nonce" @passwordResetResponse="handlePasswordResetResponse" - /> + > + + {{ $t('site.back-to-login') }} + + diff --git a/webapp/pages/password-reset/request.vue b/webapp/pages/password-reset/request.vue index aa1b1ef05..29f8fde9d 100644 --- a/webapp/pages/password-reset/request.vue +++ b/webapp/pages/password-reset/request.vue @@ -1,5 +1,9 @@ diff --git a/webapp/pages/registration.vue b/webapp/pages/registration.vue index 7c7f03975..695570d26 100644 --- a/webapp/pages/registration.vue +++ b/webapp/pages/registration.vue @@ -1,9 +1,29 @@ diff --git a/webapp/pages/registration/signup.vue b/webapp/pages/registration/signup.vue new file mode 100644 index 000000000..721cb6268 --- /dev/null +++ b/webapp/pages/registration/signup.vue @@ -0,0 +1,34 @@ + + + diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue index 5795792f9..2d8ba7237 100644 --- a/webapp/pages/settings.vue +++ b/webapp/pages/settings.vue @@ -39,6 +39,10 @@ export default { name: this.$t('settings.blocked-users.name'), path: `/settings/blocked-users`, }, + { + name: this.$t('settings.embeds.name'), + path: `/settings/embeds`, + }, { name: this.$t('settings.deleteUserAccount.name'), path: `/settings/delete-account`, diff --git a/webapp/pages/settings/embeds.vue b/webapp/pages/settings/embeds.vue new file mode 100644 index 000000000..7b85295c0 --- /dev/null +++ b/webapp/pages/settings/embeds.vue @@ -0,0 +1,87 @@ + + + diff --git a/webapp/store/auth.js b/webapp/store/auth.js index 498477660..90c59a8f5 100644 --- a/webapp/store/auth.js +++ b/webapp/store/auth.js @@ -86,6 +86,7 @@ export const actions = { locationName contributionsCount commentedCount + allowEmbedIframes termsAndConditionsAgreedVersion socialMedia { id diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 65bd0b6d5..b6dca2ca6 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -438,10 +438,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79" - integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ== +"@babel/plugin-transform-block-scoping@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a" + integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.13" @@ -563,10 +563,10 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b" - integrity sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g== +"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf" + integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw== dependencies: regexpu-core "^4.6.0" @@ -679,10 +679,10 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.6.0" -"@babel/preset-env@^7.4.5", "@babel/preset-env@^7.6.2", "@babel/preset-env@~7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" - integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== +"@babel/preset-env@^7.4.5", "@babel/preset-env@^7.6.2", "@babel/preset-env@~7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271" + integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -700,7 +700,7 @@ "@babel/plugin-transform-arrow-functions" "^7.2.0" "@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.2" + "@babel/plugin-transform-block-scoping" "^7.6.3" "@babel/plugin-transform-classes" "^7.5.5" "@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-destructuring" "^7.6.0" @@ -715,7 +715,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.6.0" "@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3" "@babel/plugin-transform-new-target" "^7.4.4" "@babel/plugin-transform-object-super" "^7.5.5" "@babel/plugin-transform-parameters" "^7.4.4" @@ -728,7 +728,7 @@ "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" "@babel/plugin-transform-unicode-regex" "^7.6.2" - "@babel/types" "^7.6.0" + "@babel/types" "^7.6.3" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" diff --git a/yarn.lock b/yarn.lock index 8d561ef5e..231e6100e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1877,10 +1877,10 @@ cucumber@^4.2.1: util-arity "^1.0.2" verror "^1.9.0" -cypress-cucumber-preprocessor@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.16.1.tgz#2ac7e0e53396334c052aeed8b5e61e08616f73a2" - integrity sha512-m8Z5t9hSc10kv47qK0fV/JlCboCwQVxgTa+WhnCjOPB7YBnX/en4f0O8l27yaZbZyHan7JBoJvpfzINlaOKafg== +cypress-cucumber-preprocessor@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.16.2.tgz#79e0ce7e7afa781f752711f7284a3bf48aa99ab8" + integrity sha512-jDJuQnnzrOrO+4PRt+VKFkHxHO7DplJACXOMUHLLWcL7vjlRUkIG4+QWnOkn/Py3yOhv9Rmuug8Iil5+FV8wEw== dependencies: "@cypress/browserify-preprocessor" "^2.1.1" chai "^4.1.2"