diff --git a/.gitignore b/.gitignore
index fbdc98903..7c4393cb7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,3 +80,4 @@ static/uploads
cypress/videos
cypress/screenshots/
+cypress.env.json
diff --git a/.travis.yml b/.travis.yml
index f5cb1b4ef..2b1608b01 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,22 +18,23 @@ before_install:
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
+ - cp cypress.env.template.json cypress.env.json
install:
- docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT --target production -t humanconnection/nitro-web .
- docker-compose -f docker-compose.yml -f docker-compose.travis.yml up -d
- git clone https://github.com/Human-Connection/Nitro-Backend.git ../Nitro-Backend
- git -C "../Nitro-Backend" checkout $BACKEND_BRANCH || git -C "../Nitro-Backend" checkout master
- - docker-compose -f ../Nitro-Backend/docker-compose.yml -f ../Nitro-Backend/docker-compose.travis.yml up -d
+ - cd ../Nitro-Backend && yarn install && cd -
+ - docker-compose -f ../Nitro-Backend/docker-compose.yml -f ../Nitro-Backend/docker-compose.cypress.yml up -d
- yarn global add cypress wait-on
- yarn add cypress-cucumber-preprocessor
script:
- docker-compose exec -e NODE_ENV=test webapp yarn run lint
- docker-compose exec -e NODE_ENV=test webapp yarn run test
- - docker-compose -f ../Nitro-Backend/docker-compose.yml exec backend yarn run db:seed
- - wait-on http://localhost:3000
- - cypress run --record --key $CYPRESS_TOKEN
+ - wait-on http://localhost:7474 && docker-compose -f ../Nitro-Backend/docker-compose.yml exec neo4j migrate
+ - wait-on http://localhost:3000 && cypress run --record --key $CYPRESS_TOKEN
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
diff --git a/README.md b/README.md
index f7d296f87..45f6e9010 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,9 @@ $ yarn install
Copy:
```
cp .env.template .env
+cp cypress.env.template.json cypress.env.json
```
-Configure the file `.env` according to your needs and your local setup.
+Configure the files according to your needs and your local setup.
### Development
``` bash
diff --git a/assets/styles/main.scss b/assets/styles/main.scss
index d22f3ec08..662b8bb0d 100644
--- a/assets/styles/main.scss
+++ b/assets/styles/main.scss
@@ -78,45 +78,6 @@ blockquote {
}
}
-.hc-editor-content {
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- &:not(:first-child) {
- margin-top: 2rem;
- }
- }
- p {
- &:not(:last-child) {
- margin-top: 0;
- margin-bottom: 0;
- }
- }
- dl,
- ol,
- ul,
- blockquote,
- pre,
- table {
- &:not(:first-child) {
- margin-top: 15px;
- }
- }
- *:first-child {
- margin-top: 0;
- }
- *:last-child {
- margin-bottom: 0;
- }
- // avoid double breaks
- br + p {
- margin-top: 0;
- }
-}
-
hr {
border: 0;
width: 100%;
diff --git a/components/ContentMenu.vue b/components/ContentMenu.vue
index 9ab85cb12..5e614c476 100644
--- a/components/ContentMenu.vue
+++ b/components/ContentMenu.vue
@@ -8,14 +8,19 @@
slot="default"
slot-scope="{toggleMenu}"
>
-
+
+
{
+ console.log('EDIT COMMENT')
+ },
+ icon: 'edit'
+ })
+ }
+
+ if (!this.isOwner) {
+ routes.push({
name: this.$t(`report.${this.context}.title`),
callback: this.openReportDialog,
icon: 'flag'
- }
- ]
- if (this.isModerator) {
+ })
+ }
+
+ if (!this.isOwner && this.isModerator) {
routes.push({
name: this.$t(`disable.${this.context}.title`),
- callback: this.openDisableDialog,
+ callback: () => {},
icon: 'eye-slash'
})
}
+
+ if (this.isOwner && this.context === 'user') {
+ routes.push({
+ name: this.$t(`settings.data.name`),
+ // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+ callback: () => this.$router.push('/settings'),
+ icon: 'edit'
+ })
+ }
return routes
},
isModerator() {
diff --git a/components/ContributionForm.vue b/components/ContributionForm.vue
new file mode 100644
index 000000000..cf7c28ece
--- /dev/null
+++ b/components/ContributionForm.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+ {{ $t('actions.cancel') }}
+
+
+ {{ $t('actions.save') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Editor/Editor.vue b/components/Editor/Editor.vue
new file mode 100644
index 000000000..b59ca376d
--- /dev/null
+++ b/components/Editor/Editor.vue
@@ -0,0 +1,366 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Editor/plugins/eventHandler.js b/components/Editor/plugins/eventHandler.js
new file mode 100644
index 000000000..6086421b8
--- /dev/null
+++ b/components/Editor/plugins/eventHandler.js
@@ -0,0 +1,83 @@
+import { Extension, Plugin } from 'tiptap'
+// import { Slice, Fragment } from 'prosemirror-model'
+
+export default class EventHandler extends Extension {
+ get name() {
+ return 'event_handler'
+ }
+ get plugins() {
+ return [
+ new Plugin({
+ props: {
+ transformPastedText(text) {
+ // console.log('#### transformPastedText', text)
+ return text.trim()
+ },
+ transformPastedHTML(html) {
+ html = html
+ // remove all tags with "space only"
+ .replace(/<[a-z-]+>[\s]+<\/[a-z-]+>/gim, '')
+ // remove all iframes
+ .replace(
+ /(
@@ -86,13 +87,16 @@ export default {
computed: {
excerpt() {
// remove all links from excerpt to prevent issues with the serounding link
- let excerpt = this.post.contentExcerpt.replace(/(.+)<\/a>/gim, '')
+ let excerpt = this.post.contentExcerpt.replace(/(.+)<\/a>/gim, '$1')
// do not display content that is only linebreaks
if (excerpt.replace(/
/gim, '').trim() === '') {
excerpt = ''
}
return excerpt
+ },
+ isAuthor() {
+ return this.$store.getters['auth/user'].id === this.post.author.id
}
},
methods: {
diff --git a/cypress.env.template.json b/cypress.env.template.json
new file mode 100644
index 000000000..bd03f6381
--- /dev/null
+++ b/cypress.env.template.json
@@ -0,0 +1,6 @@
+{
+ "SEED_SERVER_HOST": "http://localhost:4001",
+ "NEO4J_URI": "bolt://localhost:7687",
+ "NEO4J_USERNAME": "neo4j",
+ "NEO4J_PASSWORD": "letmein"
+}
diff --git a/cypress/integration/01.Login.feature b/cypress/integration/01.Login.feature
index 72adc8553..3837f7042 100644
--- a/cypress/integration/01.Login.feature
+++ b/cypress/integration/01.Login.feature
@@ -4,11 +4,7 @@ Feature: Authentication
In order to attribute posts and other contributions to their authors
Background:
- Given my account has the following details:
- | name | email | password | type
- | Peter Lustig | admin@example.org | 1234 | Admin
- | Bob der Bausmeister | moderator@example.org | 1234 | Moderator
- | Jenny Rostock" | user@example.org | 1234 | User
+ Given I have a user account
Scenario: Log in
When I visit the "/login" page
diff --git a/cypress/integration/03.TagsAndCategories.feature b/cypress/integration/03.TagsAndCategories.feature
index f9509e858..8f27a36cf 100644
--- a/cypress/integration/03.TagsAndCategories.feature
+++ b/cypress/integration/03.TagsAndCategories.feature
@@ -14,28 +14,27 @@ Feature: Tags and Categories
looking at the popularity of a tag.
Background:
- Given we have a selection of tags and categories as well as posts
- And my user account has the role "administrator"
- Given I am logged in
+ Given my user account has the role "admin"
+ And we have a selection of tags and categories as well as posts
+ And I am logged in
Scenario: See an overview of categories
When I navigate to the administration dashboard
And I click on "Categories"
Then I can see a list of categories ordered by post count:
- | Icon | Name | Post Count |
- | | Just For Fun | 5 |
- | | Happyness & Values | 2 |
- | | Health & Wellbeing | 1 |
+ | Icon | Name | Posts |
+ | | Just For Fun | 2 |
+ | | Happyness & Values | 1 |
+ | | Health & Wellbeing | 0 |
Scenario: See an overview of tags
When I navigate to the administration dashboard
And I click on "Tags"
- Then I can see a list of tags ordered by user and post count:
- | # | Name | Nutzer | Beiträge |
- | 1 | Naturschutz | 2 | 2 |
- | 2 | Freiheit | 2 | 2 |
- | 3 | Umwelt | 1 | 1 |
- | 4 | Demokratie | 1 | 1 |
+ Then I can see a list of tags ordered by user count:
+ | # | Name | Users | Posts |
+ | 1 | Democracy | 2 | 3 |
+ | 2 | Ecology | 1 | 1 |
+ | 3 | Nature | 1 | 2 |
diff --git a/cypress/integration/04.AboutMeAndLocation.feature b/cypress/integration/04.AboutMeAndLocation.feature
index 8601f7c80..2a512bf3f 100644
--- a/cypress/integration/04.AboutMeAndLocation.feature
+++ b/cypress/integration/04.AboutMeAndLocation.feature
@@ -7,21 +7,18 @@ Feature: About me and location
to search for users by location.
Background:
- Given I am logged in
+ Given I have a user account
+ And I am logged in
And I am on the "settings" page
Scenario: Change username
When I save "Hansi" as my new name
Then I can see my new name "Hansi" when I click on my profile picture in the top right
-
- Scenario: Keep changes after refresh
- When I changed my username to "Hansi" previously
- And I refresh the page
- Then my new username is still there
+ And when I refresh the page
+ Then the name "Hansi" is still there
Scenario Outline: I set my location to ""
When I save "" as my location
- And my username is "Peter Lustig"
When people visit my profile page
Then they can see the location in the info box below my avatar
@@ -36,10 +33,5 @@ Feature: About me and location
"""
Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
"""
- And my username is "Peter Lustig"
When people visit my profile page
Then they can see the text in the info box below my avatar
-
-
-
-
diff --git a/cypress/integration/05.ReportContent.feature b/cypress/integration/05.ReportContent.feature
index fbfdfe8de..76ebb4fcd 100644
--- a/cypress/integration/05.ReportContent.feature
+++ b/cypress/integration/05.ReportContent.feature
@@ -9,13 +9,13 @@ Feature: Report and Moderate
Background:
Given we have the following posts in our database:
- | Author | Title | Content | Slug |
- | David Irving | The Truth about the Holocaust | It never existed! | the-truth-about-the-holocaust |
+ | Author | id | title | content |
+ | David Irving | p1 | The Truth about the Holocaust | It never existed! |
Scenario Outline: Report a post from various pages
Given I am logged in with a "user" role
- And I see David Irving's post on the
- When I click on "Report Contribution" from the triple dot menu of the post
+ When I see David Irving's post on the
+ And I click on "Report Contribution" from the triple dot menu of the post
And I confirm the reporting dialog because it is a criminal act under German law:
"""
Do you really want to report the contribution "The Truth about the Holocaust"?
@@ -45,8 +45,8 @@ Feature: Report and Moderate
Scenario: Review reported content
Given somebody reported the following posts:
- | Slug |
- | the-truth-about-the-holocaust |
+ | id |
+ | p1 |
And I am logged in with a "moderator" role
When I click on the avatar menu in the top right corner
And I click on "Moderation"
diff --git a/cypress/integration/06.WritePost.feature b/cypress/integration/06.WritePost.feature
new file mode 100644
index 000000000..0193e44bf
--- /dev/null
+++ b/cypress/integration/06.WritePost.feature
@@ -0,0 +1,25 @@
+Feature: Create a post
+ As a user
+ I would like to create a post
+ To say something to everyone in the community
+
+ Background:
+ Given I have a user account
+ And I am logged in
+ And I am on the "landing" page
+
+ Scenario: Create a post
+ When I click on the big plus icon in the bottom right corner to create post
+ And I choose "My first post" as the title of the post
+ And I type in the following text:
+ """
+ Human Connection is a free and open-source social network
+ for active citizenship.
+ """
+ And I click on "Save"
+ Then I get redirected to "/post/my-first-post/"
+ And the post was saved successfully
+
+ Scenario: See a post on the landing page
+ Given I previously created a post
+ Then the post shows up on the landing page at position 1
diff --git a/cypress/integration/common/admin.js b/cypress/integration/common/admin.js
index 9162667b4..03cbe5fca 100644
--- a/cypress/integration/common/admin.js
+++ b/cypress/integration/common/admin.js
@@ -2,18 +2,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'
/* global cy */
-const lastColumnIsSortedInDescendingOrder = () => {
- cy.get('tbody')
- .find('tr td:last-child')
- .then(lastColumn => {
- cy.wrap(lastColumn)
- const values = lastColumn
- .map((i, td) => parseInt(td.textContent))
- .toArray()
- const orderedDescending = values.slice(0).sort((a, b) => b - a)
- return cy.wrap(values).should('deep.eq', orderedDescending)
- })
-}
When('I navigate to the administration dashboard', () => {
cy.get('.avatar-menu').click()
@@ -23,17 +11,27 @@ When('I navigate to the administration dashboard', () => {
})
Then('I can see a list of categories ordered by post count:', table => {
- // TODO: match the table in the feature with the html table
cy.get('thead')
.find('tr th')
.should('have.length', 3)
- lastColumnIsSortedInDescendingOrder()
+ table.hashes().forEach(({Name, Posts}, index) => {
+ cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
+ .should('contain', Name)
+ cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
+ .should('contain', Posts)
+ })
})
-Then('I can see a list of tags ordered by user and post count:', table => {
- // TODO: match the table in the feature with the html table
+Then('I can see a list of tags ordered by user count:', table => {
cy.get('thead')
.find('tr th')
.should('have.length', 4)
- lastColumnIsSortedInDescendingOrder()
+ table.hashes().forEach(({Name, Users, Posts}, index) => {
+ cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
+ .should('contain', Name)
+ cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
+ .should('contain', Users)
+ cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(4)`)
+ .should('contain', Posts)
+ })
})
diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js
index 3c546f0f5..3f2895dd9 100644
--- a/cypress/integration/common/report.js
+++ b/cypress/integration/common/report.js
@@ -3,9 +3,9 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
/* global cy */
let lastReportTitle
-let dummyReportedPostTitle = 'Hacker, Freaks und Funktionäre'
-let dummyReportedPostSlug = 'hacker-freaks-und-funktionareder-ccc'
-let dummyAuthorName = 'Jenny Rostock'
+let davidIrvingPostTitle = 'The Truth about the Holocaust'
+let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
+let davidIrvingName = 'David Irving'
const savePostTitle = $post => {
return $post
@@ -23,21 +23,27 @@ Given("I see David Irving's post on the landing page", page => {
})
Given("I see David Irving's post on the post page", page => {
- cy.visit(`/post/${dummyReportedPostSlug}`)
- cy.contains(dummyReportedPostTitle) // wait
+ cy.visit(`/post/${davidIrvingPostSlug}`)
+ cy.contains(davidIrvingPostTitle) // wait
})
Given('I am logged in with a {string} role', role => {
- cy.loginAs(role)
+ cy.factory().create('User', {
+ email: `${role}@example.org`,
+ password: '1234',
+ role
+ })
+ cy.login({
+ email: `${role}@example.org`,
+ password: '1234'
+ })
})
When(
'I click on "Report Contribution" from the triple dot menu of the post',
() => {
- //TODO: match the created post title, not a dummy post title
- cy.contains('.ds-card', dummyReportedPostTitle)
+ cy.contains('.ds-card', davidIrvingPostTitle)
.find('.content-menu-trigger')
- .first()
.click()
cy.get('.popover .ds-menu-item-link')
@@ -49,8 +55,7 @@ When(
When(
'I click on "Report User" from the triple dot menu in the user info box',
() => {
- //TODO: match the created post author, not a dummy author
- cy.contains('.ds-card', dummyAuthorName)
+ cy.contains('.ds-card', davidIrvingName)
.find('.content-menu-trigger')
.first()
.click()
@@ -106,12 +111,8 @@ Then(`I can't see the moderation menu item`, () => {
.should('not.exist')
})
-When(/^I confirm the reporting dialog .*:$/, () => {
- //TODO: take message from method argument
- //TODO: match the right post
- const message = 'Do you really want to report the'
+When(/^I confirm the reporting dialog .*:$/, (message) => {
cy.contains(message) // wait for element to become visible
- //TODO: cy.get('.ds-modal').contains(dummyReportedPostTitle)
cy.get('.ds-modal').within(() => {
cy.get('button')
.contains('Send Report')
@@ -120,22 +121,28 @@ When(/^I confirm the reporting dialog .*:$/, () => {
})
Given('somebody reported the following posts:', table => {
- table.hashes().forEach(row => {
- //TODO: calll factory here
- // const options = Object.assign({}, row, { reported: true })
- //create('post', options)
+ table.hashes().forEach(({ id }) => {
+ const reporter = {
+ email: `reporter${id}@example.org`,
+ password: '1234'
+ }
+ cy.factory()
+ .create('User', reporter)
+ .authenticateAs(reporter)
+ .create('Report', {
+ description: "I don't like this post",
+ resource: { id, type: 'contribution' }
+ })
})
})
Then('I see all the reported posts including the one from above', () => {
- //TODO: match the right post
cy.get('table tbody').within(() => {
- cy.contains('tr', dummyReportedPostTitle)
+ cy.contains('tr', davidIrvingPostTitle)
})
})
Then('each list item links to the post page', () => {
- //TODO: match the right post
- cy.contains(dummyReportedPostTitle).click()
+ cy.contains(davidIrvingPostTitle).click()
cy.location('pathname').should('contain', '/post')
})
diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js
index b7b283e2b..3aa6022a8 100644
--- a/cypress/integration/common/settings.js
+++ b/cypress/integration/common/settings.js
@@ -4,7 +4,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'
let aboutMeText
let myLocation
-let myName
const matchNameInUserMenu = name => {
cy.get('.avatar-menu').click() // open
@@ -12,18 +11,13 @@ const matchNameInUserMenu = name => {
cy.get('.avatar-menu').click() // close again
}
-const setUserName = name => {
+When('I save {string} as my new name', name => {
cy.get('input[id=name]')
.clear()
.type(name)
cy.get('[type=submit]')
.click()
.not('[disabled]')
- myName = name
-}
-
-When('I save {string} as my new name', name => {
- setUserName(name)
})
When('I save {string} as my location', location => {
@@ -47,31 +41,20 @@ When('I have the following self-description:', text => {
aboutMeText = text
})
-When('my username is {string}', name => {
- if (myName !== name) {
- setUserName(name)
- }
- matchNameInUserMenu(name)
-})
-
When('people visit my profile page', url => {
- cy.visitMyProfile()
+ cy.openPage('/profile/peter-pan')
})
When('they can see the text in the info box below my avatar', () => {
cy.contains(aboutMeText)
})
-When('I changed my username to {string} previously', name => {
- myName = name
-})
-
Then('they can see the location in the info box below my avatar', () => {
- matchNameInUserMenu(myName)
+ cy.contains(myLocation)
})
-Then('my new username is still there', () => {
- matchNameInUserMenu(myName)
+Then('the name {string} is still there', name => {
+ matchNameInUserMenu(name)
})
Then(
diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
index cd4280578..c17c2729b 100644
--- a/cypress/integration/common/steps.js
+++ b/cypress/integration/common/steps.js
@@ -1,26 +1,87 @@
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
import { getLangByName } from '../../support/helpers'
-import users from '../../fixtures/users.json'
/* global cy */
+let lastPost = {}
+
+const loginCredentials = {
+ email: 'peterpan@example.org',
+ password: '1234'
+}
+const narratorParams = {
+ name: 'Peter Pan',
+ ...loginCredentials
+}
+
Given('I am logged in', () => {
- cy.loginAs('admin')
-})
-Given('I am logged in as {string}', userType => {
- cy.loginAs(userType)
+ cy.login(loginCredentials)
})
Given('we have a selection of tags and categories as well as posts', () => {
- // TODO: use db factories instead of seed data
+ cy.factory()
+ .authenticateAs(loginCredentials)
+ .create('Category', {
+ id: 'cat1',
+ name: 'Just For Fun',
+ slug: 'justforfun',
+ icon: 'smile'
+ })
+ .create('Category', {
+ id: 'cat2',
+ name: 'Happyness & Values',
+ slug: 'happyness-values',
+ icon: 'heart-o'
+ })
+ .create('Category', {
+ id: 'cat3',
+ name: 'Health & Wellbeing',
+ slug: 'health-wellbeing',
+ icon: 'medkit'
+ })
+ .create('Tag', { id: 't1', name: 'Ecology' })
+ .create('Tag', { id: 't2', name: 'Nature' })
+ .create('Tag', { id: 't3', name: 'Democracy' })
+
+ const someAuthor = {
+ id: 'authorId',
+ email: 'author@example.org',
+ password: '1234'
+ }
+ cy.factory()
+ .create('User', someAuthor)
+ .authenticateAs(someAuthor)
+ .create('Post', { id: 'p0' })
+ .create('Post', { id: 'p1' })
+ cy.factory()
+ .authenticateAs(loginCredentials)
+ .create('Post', { id: 'p2' })
+ .relate('Post', 'Categories', { from: 'p0', to: 'cat1' })
+ .relate('Post', 'Categories', { from: 'p1', to: 'cat2' })
+ .relate('Post', 'Categories', { from: 'p2', to: 'cat1' })
+ .relate('Post', 'Tags', { from: 'p0', to: 't1' })
+ .relate('Post', 'Tags', { from: 'p0', to: 't2' })
+ .relate('Post', 'Tags', { from: 'p0', to: 't3' })
+ .relate('Post', 'Tags', { from: 'p1', to: 't2' })
+ .relate('Post', 'Tags', { from: 'p1', to: 't3' })
+ .relate('Post', 'Tags', { from: 'p2', to: 't3' })
})
-Given('my account has the following details:', table => {
- // TODO: use db factories instead of seed data
+Given('we have the following user accounts:', table => {
+ table.hashes().forEach(params => {
+ cy.factory().create('User', params)
+ })
+})
+
+Given('I have a user account', () => {
+ cy.factory().create('User', narratorParams)
})
Given('my user account has the role {string}', role => {
- // TODO: use db factories instead of seed data
+ cy.factory().create('User', {
+ role,
+ ...loginCredentials
+ })
})
When('I log out', cy.logout)
@@ -34,10 +95,10 @@ Given('I am on the {string} page', page => {
})
When('I fill in my email and password combination and click submit', () => {
- cy.login('admin@example.org', 1234)
+ cy.login(loginCredentials)
})
-When('I refresh the page', () => {
+When(/(?:when )?I refresh the page/, () => {
cy.reload()
})
@@ -49,7 +110,7 @@ When('I log out through the menu in the top right corner', () => {
})
Then('I can see my name {string} in the dropdown menu', () => {
- cy.get('.avatar-menu-popover').should('contain', users.admin.name)
+ cy.get('.avatar-menu-popover').should('contain', narratorParams.name)
})
Then('I see the login screen again', () => {
@@ -63,7 +124,7 @@ Then('I can click on my profile picture in the top right corner', () => {
Then('I am still logged in', () => {
cy.get('.avatar-menu').click()
- cy.get('.avatar-menu-popover').contains(users.admin.name)
+ cy.get('.avatar-menu-popover').contains(narratorParams.name)
})
When('I select {string} in the language menu', name => {
@@ -90,9 +151,18 @@ When('I press {string}', label => {
})
Given('we have the following posts in our database:', table => {
- table.hashes().forEach(row => {
- //TODO: calll factory here
- //create('post', row)
+ table.hashes().forEach(({ Author, id, title, content }) => {
+ cy.factory()
+ .create('User', {
+ name: Author,
+ email: `${Author}@example.org`,
+ password: '1234'
+ })
+ .authenticateAs({
+ email: `${Author}@example.org`,
+ password: '1234'
+ })
+ .create('Post', { id, title, content })
})
})
@@ -103,3 +173,42 @@ Then('I see a success message:', message => {
When('I click on the avatar menu in the top right corner', () => {
cy.get('.avatar-menu').click()
})
+
+When(
+ 'I click on the big plus icon in the bottom right corner to create post',
+ () => {
+ cy.get('.post-add-button').click()
+ }
+)
+
+Given('I previously created a post', () => {
+ cy.factory()
+ .authenticateAs(loginCredentials)
+ .create('Post', lastPost)
+})
+
+When('I choose {string} as the title of the post', title => {
+ lastPost.title = title.replace('\n', ' ')
+ cy.get('input[name="title"]').type(lastPost.title)
+})
+
+When('I type in the following text:', text => {
+ lastPost.content = text.replace('\n', ' ')
+ cy.get('.ProseMirror').type(lastPost.content)
+})
+
+Then('the post shows up on the landing page at position {int}', index => {
+ cy.openPage('landing')
+ const selector = `:nth-child(${index}) > .ds-card > .ds-card-content`
+ cy.get(selector).should('contain', lastPost.title)
+ cy.get(selector).should('contain', lastPost.content)
+})
+
+Then('I get redirected to {string}', route => {
+ cy.location('pathname').should('contain', route)
+})
+
+Then('the post was saved successfully', () => {
+ cy.get('.ds-card-header > .ds-heading').should('contain', lastPost.title)
+ cy.get('.content').should('contain', lastPost.content)
+})
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index ebbd6acd1..5b4c2055b 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -35,14 +35,7 @@ Cypress.Commands.add('switchLanguage', (name, force) => {
}
})
-Cypress.Commands.add('visitMyProfile', () => {
- cy.get('.avatar-menu').click()
- cy.get('.avatar-menu-popover')
- .find('a[href^="/profile/"]')
- .click()
-})
-
-Cypress.Commands.add('login', (email, password) => {
+Cypress.Commands.add('login', ({ email, password }) => {
cy.visit(`/login`)
cy.get('input[name=email]')
.trigger('focus')
@@ -56,11 +49,6 @@ Cypress.Commands.add('login', (email, password) => {
cy.location('pathname').should('eq', '/') // we're in!
})
-Cypress.Commands.add('loginAs', role => {
- role = role || 'admin'
- cy.login(users[role].email, users[role].password)
-})
-
Cypress.Commands.add('logout', (email, password) => {
cy.visit(`/logout`)
cy.location('pathname').should('contain', '/login') // we're out
diff --git a/cypress/support/factories.js b/cypress/support/factories.js
new file mode 100644
index 000000000..95355f414
--- /dev/null
+++ b/cypress/support/factories.js
@@ -0,0 +1,43 @@
+// TODO: find a better way how to import the factories
+import Factory from '../../../Nitro-Backend/src/seed/factories'
+import { getDriver } from '../../../Nitro-Backend/src/bootstrap/neo4j'
+
+const neo4jDriver = getDriver({
+ uri: Cypress.env('NEO4J_URI'),
+ username: Cypress.env('NEO4J_USERNAME'),
+ password: Cypress.env('NEO4J_PASSWORD')
+})
+const factory = Factory({ neo4jDriver })
+const seedServerHost = Cypress.env('SEED_SERVER_HOST')
+
+beforeEach(async () => {
+ await factory.cleanDatabase({ seedServerHost, neo4jDriver })
+})
+
+Cypress.Commands.add('factory', () => {
+ return Factory({seedServerHost})
+})
+
+Cypress.Commands.add(
+ 'create',
+ { prevSubject: true },
+ (factory, node, properties) => {
+ return factory.create(node, properties)
+ }
+)
+
+Cypress.Commands.add(
+ 'relate',
+ { prevSubject: true },
+ (factory, node, relationship, properties) => {
+ return factory.relate(node, relationship, properties)
+ }
+)
+
+Cypress.Commands.add(
+ 'authenticateAs',
+ { prevSubject: true },
+ (factory, loginCredentials) => {
+ return factory.authenticateAs(loginCredentials)
+ }
+)
diff --git a/cypress/support/index.js b/cypress/support/index.js
index d68db96df..3519487bf 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -15,6 +15,7 @@
// Import commands.js using ES2015 syntax:
import './commands'
+import './factories'
// Alternatively you can use CommonJS syntax:
// require('./commands')
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
index e740d8180..6edc22f25 100644
--- a/docker-compose.override.yml
+++ b/docker-compose.override.yml
@@ -8,7 +8,9 @@ services:
volumes:
- .:/nitro-web
- node_modules:/nitro-web/node_modules
+ - nuxt:/nitro-web/.nuxt
command: yarn run dev
volumes:
node_modules:
+ nuxt:
diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml
index ced9719a2..8bd536a7b 100644
--- a/docker-compose.travis.yml
+++ b/docker-compose.travis.yml
@@ -5,3 +5,5 @@ services:
build:
context: .
target: build-and-test
+ environment:
+ - BACKEND_URL=http://backend:4123
diff --git a/graphql/PostMutations.js b/graphql/PostMutations.js
new file mode 100644
index 000000000..d92445744
--- /dev/null
+++ b/graphql/PostMutations.js
@@ -0,0 +1,28 @@
+import gql from 'graphql-tag'
+
+export default app => {
+ return {
+ CreatePost: gql(`
+ mutation($title: String!, $content: String!) {
+ CreatePost(title: $title, content: $content) {
+ id
+ title
+ slug
+ content
+ contentExcerpt
+ }
+ }
+ `),
+ UpdatePost: gql(`
+ mutation($id: ID!, $title: String!, $content: String!) {
+ UpdatePost(id: $id, title: $title, content: $content) {
+ id
+ title
+ slug
+ content
+ contentExcerpt
+ }
+ }
+ `)
+ }
+}
diff --git a/locales/de.json b/locales/de.json
index b5d5be9c5..b3e6289ac 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -5,7 +5,8 @@
"create": "Erstellen",
"save": "Speichern",
"edit": "Bearbeiten",
- "delete": "Löschen"
+ "delete": "Löschen",
+ "cancel": "Abbrechen"
},
"login": {
"copy": "Wenn Du bereits ein Konto bei Human Connection hast, melde Dich bitte hier an.",
@@ -100,6 +101,14 @@
"reporter": "gemeldet von"
}
},
+ "contribution": {
+ "edit": "Beitrag bearbeiten",
+ "delete": "Beitrag löschen"
+ },
+ "comment": {
+ "edit": "Kommentar bearbeiten",
+ "delete": "Kommentar löschen"
+ },
"disable": {
"user": {
"title": "Nutzer sperren",
diff --git a/locales/en.json b/locales/en.json
index 023e34835..cfe634675 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -5,7 +5,8 @@
"create": "Create",
"save": "Save",
"edit": "Edit",
- "delete": "Delete"
+ "delete": "Delete",
+ "cancel": "Cancel"
},
"login": {
"copy": "If you already have a human-connection account, login here.",
@@ -100,6 +101,14 @@
"reporter": "reported by"
}
},
+ "contribution": {
+ "edit": "Edit Contribution",
+ "delete": "Delete Contribution"
+ },
+ "comment": {
+ "edit": "Edit Comment",
+ "delete": "Delete Comment"
+ },
"disable": {
"user": {
"title": "Disable User",
diff --git a/package.json b/package.json
index 4f7554378..f6508d6ec 100644
--- a/package.json
+++ b/package.json
@@ -46,18 +46,22 @@
"express": "~4.16.4",
"graphql": "~14.1.1",
"jsonwebtoken": "~8.5.0",
+ "linkify-it": "~2.1.0",
"nuxt": "~2.4.3",
"nuxt-env": "~0.1.0",
"portal-vue": "~1.5.1",
"@human-connection/styleguide": "~0.5.2",
"v-tooltip": "~2.0.0-rc.33",
"vue-count-to": "~1.0.13",
+ "string-hash": "^1.1.3",
+ "tiptap": "^1.13.0",
+ "tiptap-extensions": "^1.13.0",
"vue-izitoast": "1.1.2",
"vue-sweetalert-icons": "~3.2.0",
"vuex-i18n": "~1.11.0"
},
"devDependencies": {
- "@babel/core": "~7.3.3",
+ "@babel/core": "~7.3.4",
"@babel/preset-env": "~7.3.1",
"@vue/cli-shared-utils": "~3.4.0",
"@vue/eslint-config-prettier": "~4.0.1",
diff --git a/pages/index.vue b/pages/index.vue
index ec0f3f31e..7bc85b3a4 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -13,6 +13,16 @@
+
+
+
+
+
diff --git a/pages/post/_slug/index.vue b/pages/post/_slug/index.vue
index 200a9f472..68655a31f 100644
--- a/pages/post/_slug/index.vue
+++ b/pages/post/_slug/index.vue
@@ -12,6 +12,7 @@
context="contribution"
:item-id="post.id"
:name="post.title"
+ :is-owner="isAuthor(post.author.id)"
/>
@@ -97,6 +98,7 @@
style="float-right"
:item-id="comment.id"
:name="comment.author.name"
+ :is-owner="isAuthor(comment.author.id)"
/>
@@ -160,6 +162,11 @@ export default {
this.title = this.post.title
}
},
+ methods: {
+ isAuthor(id) {
+ return this.$store.getters['auth/user'].id === id
+ }
+ },
apollo: {
Post: {
query() {
diff --git a/pages/post/_slug/more-info.vue b/pages/post/_slug/more-info.vue
index 05135b891..847975757 100644
--- a/pages/post/_slug/more-info.vue
+++ b/pages/post/_slug/more-info.vue
@@ -38,7 +38,7 @@
@@ -70,7 +70,7 @@ export default {
},
computed: {
post() {
- return this.Post ? this.Post[0] : {}
+ return this.Post ? this.Post[0] || {} : {}
}
},
apollo: {
diff --git a/pages/post/create.vue b/pages/post/create.vue
new file mode 100644
index 000000000..72cf65174
--- /dev/null
+++ b/pages/post/create.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/post/edit/_id.vue b/pages/post/edit/_id.vue
new file mode 100644
index 000000000..156bcfd23
--- /dev/null
+++ b/pages/post/edit/_id.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/profile/_slug.vue b/pages/profile/_slug.vue
index ad99402c7..98d3b9f48 100644
--- a/pages/profile/_slug.vue
+++ b/pages/profile/_slug.vue
@@ -23,6 +23,7 @@
context="user"
:item-id="user.id"
:name="user.name"
+ :is-owner="myProfile"
/>
@@ -251,6 +252,17 @@
+
+
+
+
diff --git a/styleguide/src/system/icons/svg/italic.svg b/styleguide/src/system/icons/svg/italic.svg
new file mode 100755
index 000000000..81d29483c
--- /dev/null
+++ b/styleguide/src/system/icons/svg/italic.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/styleguide/src/system/icons/svg/list-ol.svg b/styleguide/src/system/icons/svg/list-ol.svg
new file mode 100755
index 000000000..f3fb101ac
--- /dev/null
+++ b/styleguide/src/system/icons/svg/list-ol.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/styleguide/src/system/icons/svg/list-ul.svg b/styleguide/src/system/icons/svg/list-ul.svg
new file mode 100755
index 000000000..d565a8064
--- /dev/null
+++ b/styleguide/src/system/icons/svg/list-ul.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/styleguide/src/system/icons/svg/paragraph.svg b/styleguide/src/system/icons/svg/paragraph.svg
new file mode 100755
index 000000000..26365f984
--- /dev/null
+++ b/styleguide/src/system/icons/svg/paragraph.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/styleguide/src/system/icons/svg/quote-right.svg b/styleguide/src/system/icons/svg/quote-right.svg
new file mode 100755
index 000000000..4e6469624
--- /dev/null
+++ b/styleguide/src/system/icons/svg/quote-right.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/yarn.lock b/yarn.lock
index b88b8c927..996793d56 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -41,18 +41,18 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@~7.3.3":
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.3.tgz#d090d157b7c5060d05a05acaebc048bd2b037947"
- integrity sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ==
+"@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@~7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b"
+ integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.3.3"
+ "@babel/generator" "^7.3.4"
"@babel/helpers" "^7.2.0"
- "@babel/parser" "^7.3.3"
+ "@babel/parser" "^7.3.4"
"@babel/template" "^7.2.2"
- "@babel/traverse" "^7.2.2"
- "@babel/types" "^7.3.3"
+ "@babel/traverse" "^7.3.4"
+ "@babel/types" "^7.3.4"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
@@ -61,12 +61,12 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0", "@babel/generator@^7.2.2", "@babel/generator@^7.3.3":
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e"
- integrity sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==
+"@babel/generator@^7.0.0", "@babel/generator@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e"
+ integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==
dependencies:
- "@babel/types" "^7.3.3"
+ "@babel/types" "^7.3.4"
jsesc "^2.5.1"
lodash "^4.17.11"
source-map "^0.5.0"
@@ -264,10 +264,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.3.3":
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.3.tgz#092d450db02bdb6ccb1ca8ffd47d8774a91aef87"
- integrity sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
+ integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
"@babel/plugin-proposal-async-generator-functions@^7.1.0", "@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -816,25 +816,25 @@
"@babel/parser" "^7.2.2"
"@babel/types" "^7.2.2"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
- integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.3", "@babel/traverse@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
+ integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.2.2"
+ "@babel/generator" "^7.3.4"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.2.3"
- "@babel/types" "^7.2.2"
+ "@babel/parser" "^7.3.4"
+ "@babel/types" "^7.3.4"
debug "^4.1.0"
globals "^11.1.0"
- lodash "^4.17.10"
+ lodash "^4.17.11"
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.3.tgz#6c44d1cdac2a7625b624216657d5bc6c107ab436"
- integrity sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4":
+ version "7.3.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed"
+ integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==
dependencies:
esutils "^2.0.2"
lodash "^4.17.11"
@@ -4930,6 +4930,13 @@ fastparse@^1.1.1:
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==
+fault@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
+ integrity sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==
+ dependencies:
+ format "^0.2.2"
+
fb-watchman@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
@@ -5142,6 +5149,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+format@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -5608,6 +5620,11 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
+highlight.js@~9.13.0:
+ version "9.13.1"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
+ integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
+
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -7118,6 +7135,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+linkify-it@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
+ integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
+ dependencies:
+ uc.micro "^1.0.1"
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -7335,6 +7359,14 @@ lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+lowlight@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
+ integrity sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==
+ dependencies:
+ fault "^1.0.2"
+ highlight.js "~9.13.0"
+
lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -8222,6 +8254,11 @@ ora@^3.0.0:
strip-ansi "^5.0.0"
wcwidth "^1.0.1"
+orderedmap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.0.0.tgz#d90fc2ba1ed085190907d601dec6e6a53f8d41ba"
+ integrity sha1-2Q/Cuh7QhRkJB9YB3sbmpT+NQbo=
+
os-browserify@^0.3.0, os-browserify@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
@@ -9317,6 +9354,114 @@ prompts@^2.0.1:
kleur "^3.0.2"
sisteransi "^1.0.0"
+prosemirror-commands@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz#e5a2ba821e29ea7065c88277fe2c3d7f6b0b9d37"
+ integrity sha512-IR8yMSdw7XlKuF68tydAak1J9P/lLD5ohsrL7pzoLsJAJAQU7mVPDXtGbQrrm0mesddFjcc1zNo/cJQN3lRYnA==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-dropcursor@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.1.tgz#c60ed1ed6c58804a06a75db06a0d993b087b7622"
+ integrity sha512-GeUyMO/tOEf8MXrP7Xb7UIMrfK86OGh0fnyBrHfhav4VjY9cw65mNoqHy87CklE5711AhCP5Qzfp8RL/hVKusg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+ prosemirror-view "^1.1.0"
+
+prosemirror-gapcursor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.0.3.tgz#acc6537fc5a35e9b38966f91a199a382dfc715c4"
+ integrity sha512-X+hJhr42PcHWiSWL+lI5f/UeOhXCxlBFb8M6O8aG1hssmaRrW7sS2/Fjg5jFV+pTdS1REFkmm1occh01FMdDIQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-history@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.0.3.tgz#5fb8591adfc272afaaf0b41bec64ee7d9522a118"
+ integrity sha512-IfFGbhafSx+R3aq7nLJGkXeu2iaUiP8mkU3aRu2uQcIIjU8Fq7RJfuvhIOJ2RNUoSyqF/ANkdTjnZ74F5eHs1Q==
+ dependencies:
+ prosemirror-state "^1.2.2"
+ prosemirror-transform "^1.0.0"
+ rope-sequence "^1.2.0"
+
+prosemirror-inputrules@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.0.1.tgz#f63305fd966379f218e82ca76a2a9b328b66dc7b"
+ integrity sha512-UHy22NmwxS5WIMQYkzraDttQAF8mpP82FfbJsmKFfx6jwkR/SZa+ZhbkLY0zKQ5fBdJN7euj36JG/B5iAlrpxA==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz#03ef32b828e3a859dfb570eb84928bf2e5330bc2"
+ integrity sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ w3c-keyname "^1.1.8"
+
+prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.0.tgz#1fde0fd7cb2f9ead0be1581ad9f04593002a65aa"
+ integrity sha512-/6ul6guiqyAl5I+0qbnL7SlmuX0DEfYqjvzeLUVEnb7nwF/vmKZuWqbjEG2tqi/9SSudvd3UxQTBDHvxy9hQwA==
+ dependencies:
+ orderedmap "^1.0.0"
+
+prosemirror-schema-list@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.0.2.tgz#8381fb0c1eaf439d848059f62e2fac517033c2ef"
+ integrity sha512-IJ4DEpUEymfO+NNA4DAgCMF39XiQqpmCoPYY3SXa1jYcVgObGpGfJlSjZYVFEpimoLI7/mLoOLDhCtpGCRhTfg==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-state@^1.0.0, prosemirror-state@^1.2.1, prosemirror-state@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.2.2.tgz#8df26d95fd6fd327c0f9984a760e84d863204154"
+ integrity sha512-j8aC/kf9BJSCQau485I/9pj39XQoce+TqH5xzekT7WWFARTsRYFLJtiXBcCKakv1VSeev+sC3bJP0pLfz7Ft8g==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-tables@^0.7.10, prosemirror-tables@^0.7.9:
+ version "0.7.10"
+ resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.7.10.tgz#4b0f623422b4b8f84cdc9c559f8a87579846b3ba"
+ integrity sha512-VIu7UGS9keYEHs0Y6AEOTGbNE9QI2rL1OKng4vV6yoTshW/lYcb+s3hGXI12i+WLMjDVm7ujhfdWrpKpvFZOkQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz#28cfdf1f9ee514edc40466be7b7db39eed545fdf"
+ integrity sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ==
+ dependencies:
+ prosemirror-model "^1.0.0"
+
+prosemirror-utils@^0.7.5:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.6.tgz#c462ddfbf2452e56e4b25d1f02b34caccddb0f33"
+ integrity sha512-vzsCBTiJ56R3nRDpIJnKOJzsZP7KFO8BkXk7zvQgQiXpml2o/djPCRhuyaFc7VTqSHlLPQHVI1feTLAwHp+prQ==
+
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.7.1:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.7.1.tgz#f0ea75faa6d7bd25ea22897dd5bae35708c59a28"
+ integrity sha512-UFY/h4i5H1Yen8u2ZTve0WL+nh/y1qU3geC3SrWl5yIKSgGbvllD5vr5LxmeRgVsY8hb+oDXRHk5KvLwqmu7Lg==
+ dependencies:
+ prosemirror-model "^1.1.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -9889,6 +10034,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
+rope-sequence@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.2.2.tgz#49c4e5c2f54a48e990b050926771e2871bcb31ce"
+ integrity sha1-ScTlwvVKSOmQsFCSZ3HihxvLMc4=
+
rsvp@^3.3.3:
version "3.6.2"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
@@ -10508,6 +10658,11 @@ string-argv@0.0.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY=
+string-hash@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
+ integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=
+
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -10890,6 +11045,57 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
+tiptap-commands@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.6.0.tgz#567b7b218bd7d1c1214534a2007acbb7b8d06688"
+ integrity sha512-9HO8UYJz1qGyqsHn0+PifmndlRTInqfcb7vjNDvqWQA2P7r8koJqrP8CYMR0DXQHlys1druJnaBaOzLa1d5PQQ==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-schema-list "^1.0.2"
+ prosemirror-state "^1.2.2"
+ tiptap-utils "^1.2.0"
+
+tiptap-extensions@^1.13.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.13.0.tgz#1c78223d68f4c321909c5448764d2b5a32bad7f3"
+ integrity sha512-56y5uXAnkdZ/9MmTuk2fmbglUwmfECVOz2DZh/5OI5nsPclVNk+OFjNFEbZ91rb3fC4NOtFdfNQVKyqrBe9pNA==
+ dependencies:
+ lowlight "^1.11.0"
+ prosemirror-history "^1.0.3"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.10"
+ prosemirror-utils "^0.7.5"
+ prosemirror-view "^1.7.1"
+ tiptap "^1.13.0"
+ tiptap-commands "^1.6.0"
+
+tiptap-utils@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.2.0.tgz#e1e566af1212acff6b2e3c4ca6edc21ebf306440"
+ integrity sha512-p8Q0UfNhYHXqMDSwvCc6x0Vm95AYgM/f1V+8oNu9FI0aRWwXpTwIJj+1CAGO1mb6NFUSxn9HcZaUvEcBKR5WzQ==
+ dependencies:
+ prosemirror-model "^1.7.0"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.9"
+ prosemirror-utils "^0.7.5"
+
+tiptap@^1.13.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.13.0.tgz#52b086fc8d4df7534123d31571366dd90ee62a23"
+ integrity sha512-kwzgtOY5PnbSfzMyNPFchI/Cyi1O3kFNiRP7K4p6t0zcNCXy9EaWgaM/U2bKArI0/HxG/GSPM6RTTCCOF3I6EA==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-dropcursor "^1.1.1"
+ prosemirror-gapcursor "^1.0.3"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-keymap "^1.0.1"
+ prosemirror-model "^1.7.0"
+ prosemirror-state "^1.2.1"
+ prosemirror-view "^1.7.1"
+ tiptap-commands "^1.6.0"
+ tiptap-utils "^1.2.0"
+
title-case@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"
@@ -11100,6 +11306,11 @@ ua-parser-js@^0.7.19:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
+uc.micro@^1.0.1:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
+ integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
+
uglify-js@3.4.x, uglify-js@^3.1.4:
version "3.4.9"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
@@ -11581,6 +11792,11 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
+w3c-keyname@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
+ integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
+
walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"