mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' into 1680-Direct_answer_on_Comment
This commit is contained in:
commit
9776efe760
@ -6,7 +6,6 @@ addons:
|
|||||||
- libgconf-2-4
|
- libgconf-2-4
|
||||||
snaps:
|
snaps:
|
||||||
- docker
|
- docker
|
||||||
- chromium
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- yarn global add wait-on
|
- yarn global add wait-on
|
||||||
|
|||||||
7
.versionrc.json
Normal file
7
.versionrc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"bumpFiles": [
|
||||||
|
"package.json",
|
||||||
|
"backend/package.json",
|
||||||
|
"webapp/package.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
154
CHANGELOG.md
154
CHANGELOG.md
@ -4,6 +4,106 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [v0.2.2](https://github.com/Human-Connection/Human-Connection/compare/v0.2.1...v0.2.2)
|
||||||
|
|
||||||
|
> 20 January 2020
|
||||||
|
|
||||||
|
- build(deps): bump metascraper-title from 5.10.3 to 5.10.5 in /backend [`#2835`](https://github.com/Human-Connection/Human-Connection/pull/2835)
|
||||||
|
- build(deps): bump metascraper-publisher in /backend [`#2836`](https://github.com/Human-Connection/Human-Connection/pull/2836)
|
||||||
|
- build(deps): bump metascraper-audio from 5.10.3 to 5.10.5 in /backend [`#2840`](https://github.com/Human-Connection/Human-Connection/pull/2840)
|
||||||
|
- build(deps): bump metascraper-author from 5.10.3 to 5.10.5 in /backend [`#2838`](https://github.com/Human-Connection/Human-Connection/pull/2838)
|
||||||
|
- build(deps): bump metascraper-url from 5.10.3 to 5.10.5 in /backend [`#2832`](https://github.com/Human-Connection/Human-Connection/pull/2832)
|
||||||
|
- build(deps): bump metascraper-lang from 5.10.3 to 5.10.5 in /backend [`#2831`](https://github.com/Human-Connection/Human-Connection/pull/2831)
|
||||||
|
- refactor(modules): Various import fixes [`#2802`](https://github.com/Human-Connection/Human-Connection/pull/2802)
|
||||||
|
- build(deps): bump metascraper-description from 5.10.3 to 5.10.5 in /backend [`#2839`](https://github.com/Human-Connection/Human-Connection/pull/2839)
|
||||||
|
- build(deps-dev): bump @storybook/addon-notes from 5.3.5 to 5.3.6 in /webapp [`#2834`](https://github.com/Human-Connection/Human-Connection/pull/2834)
|
||||||
|
- build(deps): bump metascraper-youtube from 5.10.3 to 5.10.5 in /backend [`#2833`](https://github.com/Human-Connection/Human-Connection/pull/2833)
|
||||||
|
- build(deps): bump metascraper from 5.10.3 to 5.10.5 in /backend [`#2830`](https://github.com/Human-Connection/Human-Connection/pull/2830)
|
||||||
|
- build(deps): bump metascraper-soundcloud from 5.10.3 to 5.10.5 in /backend [`#2829`](https://github.com/Human-Connection/Human-Connection/pull/2829)
|
||||||
|
- fix(translations): Remove duplicate and mistranslated item from code of conduct [`#2725`](https://github.com/Human-Connection/Human-Connection/pull/2725)
|
||||||
|
- build(deps-dev): bump @storybook/addon-a11y from 5.3.3 to 5.3.6 in /webapp [`#2820`](https://github.com/Human-Connection/Human-Connection/pull/2820)
|
||||||
|
- build(deps): bump metascraper from 5.10.2 to 5.10.3 in /backend [`#2808`](https://github.com/Human-Connection/Human-Connection/pull/2808)
|
||||||
|
- build(deps-dev): bump @storybook/vue from 5.3.3 to 5.3.6 in /webapp [`#2819`](https://github.com/Human-Connection/Human-Connection/pull/2819)
|
||||||
|
- build(deps): bump faker from `9fd8d7d` to `3b2fa4a` in /backend [`#2803`](https://github.com/Human-Connection/Human-Connection/pull/2803)
|
||||||
|
- build(deps-dev): bump faker from `9fd8d7d` to `3b2fa4a` [`#2804`](https://github.com/Human-Connection/Human-Connection/pull/2804)
|
||||||
|
- build(deps-dev): bump @storybook/addon-a11y in /webapp [`#2809`](https://github.com/Human-Connection/Human-Connection/pull/2809)
|
||||||
|
- build(deps): bump uuid from 3.3.3 to 3.4.0 in /backend [`#2810`](https://github.com/Human-Connection/Human-Connection/pull/2810)
|
||||||
|
- build(deps): bump metascraper-image from 5.9.5 to 5.10.3 in /backend [`#2811`](https://github.com/Human-Connection/Human-Connection/pull/2811)
|
||||||
|
- build(deps-dev): bump node-sass from 4.13.0 to 4.13.1 in /webapp [`#2812`](https://github.com/Human-Connection/Human-Connection/pull/2812)
|
||||||
|
- build(deps): bump metascraper-audio from 5.9.5 to 5.10.3 in /backend [`#2813`](https://github.com/Human-Connection/Human-Connection/pull/2813)
|
||||||
|
- build(deps): bump metascraper-soundcloud in /backend [`#2815`](https://github.com/Human-Connection/Human-Connection/pull/2815)
|
||||||
|
- build(deps-dev): bump @storybook/addon-notes in /webapp [`#2816`](https://github.com/Human-Connection/Human-Connection/pull/2816)
|
||||||
|
- build(deps-dev): bump @storybook/addon-actions from 5.3.3 to 5.3.5 in /webapp [`#2807`](https://github.com/Human-Connection/Human-Connection/pull/2807)
|
||||||
|
- build(deps): bump metascraper-description from 5.9.5 to 5.10.3 in /backend [`#2806`](https://github.com/Human-Connection/Human-Connection/pull/2806)
|
||||||
|
- build(deps): bump mustache from 3.2.1 to 4.0.0 in /backend [`#2805`](https://github.com/Human-Connection/Human-Connection/pull/2805)
|
||||||
|
- 🍰 feat(webapp): Display deployed version in footer [`#2728`](https://github.com/Human-Connection/Human-Connection/pull/2728)
|
||||||
|
- fix: cypress breaks locally in login step [`#2776`](https://github.com/Human-Connection/Human-Connection/pull/2776)
|
||||||
|
- build(deps-dev): bump @vue/test-utils from 1.0.0-beta.29 to 1.0.0-beta.30 in /webapp [`#2378`](https://github.com/Human-Connection/Human-Connection/pull/2378)
|
||||||
|
- build(deps): bump metascraper-youtube from 5.9.5 to 5.10.3 in /backend [`#2794`](https://github.com/Human-Connection/Human-Connection/pull/2794)
|
||||||
|
- build(deps): bump metascraper-video from 5.9.5 to 5.10.3 in /backend [`#2795`](https://github.com/Human-Connection/Human-Connection/pull/2795)
|
||||||
|
- build(deps): bump metascraper-logo from 5.9.5 to 5.10.3 in /backend [`#2796`](https://github.com/Human-Connection/Human-Connection/pull/2796)
|
||||||
|
- refactor(styleguide): improve emotion buttons and header responsiveness [`#2582`](https://github.com/Human-Connection/Human-Connection/pull/2582)
|
||||||
|
- build(deps): bump metascraper-url from 5.9.5 to 5.10.3 in /backend [`#2793`](https://github.com/Human-Connection/Human-Connection/pull/2793)
|
||||||
|
- build(deps): bump metascraper-author from 5.9.5 to 5.10.3 in /backend [`#2789`](https://github.com/Human-Connection/Human-Connection/pull/2789)
|
||||||
|
- build(deps): bump metascraper-lang from 5.9.5 to 5.10.3 in /backend [`#2790`](https://github.com/Human-Connection/Human-Connection/pull/2790)
|
||||||
|
- build(deps): bump metascraper-publisher from 5.9.5 to 5.10.3 in /backend [`#2792`](https://github.com/Human-Connection/Human-Connection/pull/2792)
|
||||||
|
- build(deps): bump metascraper-title from 5.9.5 to 5.10.3 in /backend [`#2791`](https://github.com/Human-Connection/Human-Connection/pull/2791)
|
||||||
|
- build(deps): bump @sentry/node from 5.11.0 to 5.11.1 in /backend [`#2788`](https://github.com/Human-Connection/Human-Connection/pull/2788)
|
||||||
|
- build(deps): bump metascraper-date from 5.9.5 to 5.10.3 in /backend [`#2787`](https://github.com/Human-Connection/Human-Connection/pull/2787)
|
||||||
|
- build(deps-dev): bump @babel/node from 7.8.0 to 7.8.3 in /backend [`#2754`](https://github.com/Human-Connection/Human-Connection/pull/2754)
|
||||||
|
- refactor(styleguide): migrate and redesign buttons [`#2562`](https://github.com/Human-Connection/Human-Connection/pull/2562)
|
||||||
|
- build(deps-dev): bump @babel/core from 7.8.0 to 7.8.3 in /backend [`#2760`](https://github.com/Human-Connection/Human-Connection/pull/2760)
|
||||||
|
- build(deps-dev): bump @storybook/addon-actions from 5.3.2 to 5.3.3 in /webapp [`#2782`](https://github.com/Human-Connection/Human-Connection/pull/2782)
|
||||||
|
- build(deps-dev): bump sass-loader from 8.0.0 to 8.0.2 in /webapp [`#2781`](https://github.com/Human-Connection/Human-Connection/pull/2781)
|
||||||
|
- build(deps-dev): bump @babel/plugin-syntax-dynamic-import from 7.8.0 to 7.8.3 in /webapp [`#2780`](https://github.com/Human-Connection/Human-Connection/pull/2780)
|
||||||
|
- build(deps-dev): bump @storybook/addon-a11y from 5.3.2 to 5.3.3 in /webapp [`#2779`](https://github.com/Human-Connection/Human-Connection/pull/2779)
|
||||||
|
- build(deps): bump metascraper from 5.9.5 to 5.10.2 in /backend [`#2778`](https://github.com/Human-Connection/Human-Connection/pull/2778)
|
||||||
|
- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.3 in /webapp [`#2767`](https://github.com/Human-Connection/Human-Connection/pull/2767)
|
||||||
|
- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.8.0 to 7.8.3 in /backend [`#2757`](https://github.com/Human-Connection/Human-Connection/pull/2757)
|
||||||
|
- build(deps-dev): bump @storybook/vue from 5.3.1 to 5.3.3 in /webapp [`#2772`](https://github.com/Human-Connection/Human-Connection/pull/2772)
|
||||||
|
- build(deps-dev): bump @babel/preset-env from 7.8.2 to 7.8.3 [`#2758`](https://github.com/Human-Connection/Human-Connection/pull/2758)
|
||||||
|
- build(deps-dev): bump eslint-plugin-import from 2.19.1 to 2.20.0 in /webapp [`#2748`](https://github.com/Human-Connection/Human-Connection/pull/2748)
|
||||||
|
- build(deps-dev): bump @storybook/addon-notes from 5.3.1 to 5.3.3 in /webapp [`#2771`](https://github.com/Human-Connection/Human-Connection/pull/2771)
|
||||||
|
- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.3 in /webapp [`#2769`](https://github.com/Human-Connection/Human-Connection/pull/2769)
|
||||||
|
- build(deps-dev): bump @babel/register from 7.8.0 to 7.8.3 [`#2764`](https://github.com/Human-Connection/Human-Connection/pull/2764)
|
||||||
|
- build(deps-dev): bump @babel/preset-env from 7.8.2 to 7.8.3 in /backend [`#2755`](https://github.com/Human-Connection/Human-Connection/pull/2755)
|
||||||
|
- build(deps-dev): bump eslint-plugin-jest from 23.3.0 to 23.6.0 in /webapp [`#2768`](https://github.com/Human-Connection/Human-Connection/pull/2768)
|
||||||
|
- build(deps-dev): bump @babel/cli from 7.8.0 to 7.8.3 in /backend [`#2763`](https://github.com/Human-Connection/Human-Connection/pull/2763)
|
||||||
|
- build(deps-dev): bump cypress-cucumber-preprocessor from 1.19.0 to 2.0.1 [`#2761`](https://github.com/Human-Connection/Human-Connection/pull/2761)
|
||||||
|
- build(deps-dev): bump @storybook/addon-a11y from 5.2.8 to 5.3.2 in /webapp [`#2759`](https://github.com/Human-Connection/Human-Connection/pull/2759)
|
||||||
|
- build(deps-dev): bump @babel/core from 7.8.0 to 7.8.3 [`#2756`](https://github.com/Human-Connection/Human-Connection/pull/2756)
|
||||||
|
- build(deps-dev): bump @babel/register from 7.8.0 to 7.8.3 in /backend [`#2753`](https://github.com/Human-Connection/Human-Connection/pull/2753)
|
||||||
|
- build(deps): [security] bump serialize-javascript from 2.1.0 to 2.1.2 in /webapp [`#2752`](https://github.com/Human-Connection/Human-Connection/pull/2752)
|
||||||
|
- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.0 in /backend [`#2743`](https://github.com/Human-Connection/Human-Connection/pull/2743)
|
||||||
|
- build(deps-dev): bump @storybook/addon-actions from 5.2.8 to 5.3.2 in /webapp [`#2751`](https://github.com/Human-Connection/Human-Connection/pull/2751)
|
||||||
|
- build(deps-dev): bump @babel/register from 7.7.7 to 7.8.0 in /backend [`#2735`](https://github.com/Human-Connection/Human-Connection/pull/2735)
|
||||||
|
- build(deps-dev): bump @babel/plugin-syntax-dynamic-import from 7.7.4 to 7.8.0 in /webapp [`#2746`](https://github.com/Human-Connection/Human-Connection/pull/2746)
|
||||||
|
- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.2 in /backend [`#2739`](https://github.com/Human-Connection/Human-Connection/pull/2739)
|
||||||
|
- build(deps-dev): bump @babel/cli from 7.7.7 to 7.8.0 in /backend [`#2744`](https://github.com/Human-Connection/Human-Connection/pull/2744)
|
||||||
|
- Issues marked as bounty never become stale [`#2726`](https://github.com/Human-Connection/Human-Connection/pull/2726)
|
||||||
|
- build(deps-dev): bump css-loader from 3.4.1 to 3.4.2 in /webapp [`#2747`](https://github.com/Human-Connection/Human-Connection/pull/2747)
|
||||||
|
- build(deps-dev): bump @storybook/addon-notes from 5.2.8 to 5.3.1 in /webapp [`#2742`](https://github.com/Human-Connection/Human-Connection/pull/2742)
|
||||||
|
- build(deps-dev): bump @babel/plugin-proposal-throw-expressions from 7.7.4 to 7.8.0 in /backend [`#2741`](https://github.com/Human-Connection/Human-Connection/pull/2741)
|
||||||
|
- build(deps-dev): bump eslint-plugin-import from 2.19.1 to 2.20.0 in /backend [`#2737`](https://github.com/Human-Connection/Human-Connection/pull/2737)
|
||||||
|
- build(deps-dev): bump @babel/preset-env from 7.7.7 to 7.8.2 [`#2732`](https://github.com/Human-Connection/Human-Connection/pull/2732)
|
||||||
|
- build(deps): bump @nuxtjs/axios from 5.9.2 to 5.9.3 in /webapp [`#2740`](https://github.com/Human-Connection/Human-Connection/pull/2740)
|
||||||
|
- build(deps-dev): bump @storybook/vue from 5.2.8 to 5.3.1 in /webapp [`#2738`](https://github.com/Human-Connection/Human-Connection/pull/2738)
|
||||||
|
- build(deps-dev): bump cypress from 3.8.1 to 3.8.2 [`#2734`](https://github.com/Human-Connection/Human-Connection/pull/2734)
|
||||||
|
- build(deps-dev): bump @babel/node from 7.7.7 to 7.8.0 in /backend [`#2733`](https://github.com/Human-Connection/Human-Connection/pull/2733)
|
||||||
|
- build(deps-dev): bump eslint-plugin-jest from 23.3.0 to 23.6.0 in /backend [`#2731`](https://github.com/Human-Connection/Human-Connection/pull/2731)
|
||||||
|
- build(deps-dev): bump @babel/core from 7.7.7 to 7.8.0 [`#2730`](https://github.com/Human-Connection/Human-Connection/pull/2730)
|
||||||
|
- build(deps-dev): bump @babel/register from 7.7.7 to 7.8.0 [`#2729`](https://github.com/Human-Connection/Human-Connection/pull/2729)
|
||||||
|
- build(deps): bump nuxt from 2.10.2 to 2.11.0 in /webapp [`#2552`](https://github.com/Human-Connection/Human-Connection/pull/2552)
|
||||||
|
- Update yarn.lock after dependabot update [`#2724`](https://github.com/Human-Connection/Human-Connection/pull/2724)
|
||||||
|
- build(deps): bump @nuxtjs/axios from 5.8.0 to 5.9.2 in /webapp [`#2657`](https://github.com/Human-Connection/Human-Connection/pull/2657)
|
||||||
|
- Update to version 0.2.1 [`#2722`](https://github.com/Human-Connection/Human-Connection/pull/2722)
|
||||||
|
- refactor(modules): Various import fixes [`#2773`](https://github.com/Human-Connection/Human-Connection/issues/2773) [`#2774`](https://github.com/Human-Connection/Human-Connection/issues/2774)
|
||||||
|
- feat(webapp): Display deployed version in footer [`#1831`](https://github.com/Human-Connection/Human-Connection/issues/1831)
|
||||||
|
- fix #2229 [`#2229`](https://github.com/Human-Connection/Human-Connection/issues/2229)
|
||||||
|
- build(deps-dev): bump @storybook/addon-actions in /webapp [`d0124bf`](https://github.com/Human-Connection/Human-Connection/commit/d0124bf2b4b4a641c9af76d6d2f7b5aa075ade90)
|
||||||
|
- refactor and use base-button in SearchableInput [`fcbe612`](https://github.com/Human-Connection/Human-Connection/commit/fcbe6125f35c0dd23e2ba1ae63f539f5ef5990ea)
|
||||||
|
- Update `vue-test-utils` and follow updated docs [`8c29ad9`](https://github.com/Human-Connection/Human-Connection/commit/8c29ad947b72fbaa173d070221cdf35b7ab6aaa5)
|
||||||
|
|
||||||
#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1)
|
#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1)
|
||||||
|
|
||||||
> 10 January 2020
|
> 10 January 2020
|
||||||
@ -107,16 +207,16 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- build(deps): bump metascraper-publisher from 5.8.7 to 5.8.12 in /backend [`#2592`](https://github.com/Human-Connection/Human-Connection/pull/2592)
|
- build(deps): bump metascraper-publisher from 5.8.7 to 5.8.12 in /backend [`#2592`](https://github.com/Human-Connection/Human-Connection/pull/2592)
|
||||||
- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /backend [`#2568`](https://github.com/Human-Connection/Human-Connection/pull/2568)
|
- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /backend [`#2568`](https://github.com/Human-Connection/Human-Connection/pull/2568)
|
||||||
- Fix imageAspectRatio set to null UpdatePost [`#2588`](https://github.com/Human-Connection/Human-Connection/pull/2588)
|
- Fix imageAspectRatio set to null UpdatePost [`#2588`](https://github.com/Human-Connection/Human-Connection/pull/2588)
|
||||||
- Update to version 0.2.0 [`#2584`](https://github.com/Human-Connection/Human-Connection/pull/2584)
|
|
||||||
- fixes #2659 [`#2659`](https://github.com/Human-Connection/Human-Connection/issues/2659)
|
- fixes #2659 [`#2659`](https://github.com/Human-Connection/Human-Connection/issues/2659)
|
||||||
- build(deps-dev): bump storybook-design-token in /webapp [`88d39c4`](https://github.com/Human-Connection/Human-Connection/commit/88d39c4a427cb86527b06201f3f5e96d53ac09a0)
|
- build(deps-dev): bump storybook-design-token in /webapp [`88d39c4`](https://github.com/Human-Connection/Human-Connection/commit/88d39c4a427cb86527b06201f3f5e96d53ac09a0)
|
||||||
|
- manage button states and color schemes with mixin [`1b9249c`](https://github.com/Human-Connection/Human-Connection/commit/1b9249c685e34eb2e94b31ee0ec22421c6aa6a73)
|
||||||
- Specs for Searches [`bc3aa51`](https://github.com/Human-Connection/Human-Connection/commit/bc3aa519d0e7a6e0242ecd37d611fd1a3df385d0)
|
- Specs for Searches [`bc3aa51`](https://github.com/Human-Connection/Human-Connection/commit/bc3aa519d0e7a6e0242ecd37d611fd1a3df385d0)
|
||||||
- build(deps): bump apollo-server-express in /backend [`84df7b5`](https://github.com/Human-Connection/Human-Connection/commit/84df7b5a0a4845ab44d19946d877aef79691d38e)
|
|
||||||
|
|
||||||
#### [v0.2.0](https://github.com/Human-Connection/Human-Connection/compare/v0.1.13...v0.2.0)
|
#### [v0.2.0](https://github.com/Human-Connection/Human-Connection/compare/v0.1.13...v0.2.0)
|
||||||
|
|
||||||
> 19 December 2019
|
> 19 December 2019
|
||||||
|
|
||||||
|
- Update to version 0.2.0 [`#2584`](https://github.com/Human-Connection/Human-Connection/pull/2584)
|
||||||
- build(deps): bump metascraper-image from 5.8.10 to 5.8.12 in /backend [`#2556`](https://github.com/Human-Connection/Human-Connection/pull/2556)
|
- build(deps): bump metascraper-image from 5.8.10 to 5.8.12 in /backend [`#2556`](https://github.com/Human-Connection/Human-Connection/pull/2556)
|
||||||
- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 [`#2569`](https://github.com/Human-Connection/Human-Connection/pull/2569)
|
- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 [`#2569`](https://github.com/Human-Connection/Human-Connection/pull/2569)
|
||||||
- build(deps-dev): bump @babel/cli from 7.7.5 to 7.7.7 in /backend [`#2576`](https://github.com/Human-Connection/Human-Connection/pull/2576)
|
- build(deps-dev): bump @babel/cli from 7.7.5 to 7.7.7 in /backend [`#2576`](https://github.com/Human-Connection/Human-Connection/pull/2576)
|
||||||
@ -203,7 +303,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Fix search by adding result id [`ebc5cf3`](https://github.com/Human-Connection/Human-Connection/commit/ebc5cf392d92acf3a9e22c8967d02ea2cf6fd7fb)
|
- Fix search by adding result id [`ebc5cf3`](https://github.com/Human-Connection/Human-Connection/commit/ebc5cf392d92acf3a9e22c8967d02ea2cf6fd7fb)
|
||||||
- Write test/refactor tests/resolvers/middleware [`d375ebe`](https://github.com/Human-Connection/Human-Connection/commit/d375ebe7d90e3251b17f59ffba8fb1470923ebe8)
|
- Write test/refactor tests/resolvers/middleware [`d375ebe`](https://github.com/Human-Connection/Human-Connection/commit/d375ebe7d90e3251b17f59ffba8fb1470923ebe8)
|
||||||
|
|
||||||
#### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.12)
|
#### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.11...v0.1.12)
|
||||||
|
|
||||||
> 10 December 2019
|
> 10 December 2019
|
||||||
|
|
||||||
@ -313,6 +413,16 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- 2329 normalize emails in login form [`#2330`](https://github.com/Human-Connection/Human-Connection/pull/2330)
|
- 2329 normalize emails in login form [`#2330`](https://github.com/Human-Connection/Human-Connection/pull/2330)
|
||||||
- Lokalise: Translations update [`#2327`](https://github.com/Human-Connection/Human-Connection/pull/2327)
|
- Lokalise: Translations update [`#2327`](https://github.com/Human-Connection/Human-Connection/pull/2327)
|
||||||
- Changed translation must change test :( [`#2310`](https://github.com/Human-Connection/Human-Connection/pull/2310)
|
- Changed translation must change test :( [`#2310`](https://github.com/Human-Connection/Human-Connection/pull/2310)
|
||||||
|
- Merge pull request #2443 from Human-Connection/2237-longer-comments [`#2237`](https://github.com/Human-Connection/Human-Connection/issues/2237)
|
||||||
|
- fix #2329: Normalize email on login in the backend [`#2329`](https://github.com/Human-Connection/Human-Connection/issues/2329)
|
||||||
|
- Lokalise: update of webapp/locales/ru.json [`3e52ee0`](https://github.com/Human-Connection/Human-Connection/commit/3e52ee090c88c357b796895370d126f8bb5529f0)
|
||||||
|
- Lokalise: update of webapp/locales/de.json [`d2b3396`](https://github.com/Human-Connection/Human-Connection/commit/d2b3396e9b44bac0e767ee970e083d1847426b26)
|
||||||
|
- Lokalise: update of webapp/locales/pt.json [`bcd9f0e`](https://github.com/Human-Connection/Human-Connection/commit/bcd9f0ec93cfab2661589d72a3b3f38455ec4d51)
|
||||||
|
|
||||||
|
#### [v0.1.11](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.11)
|
||||||
|
|
||||||
|
> 22 November 2019
|
||||||
|
|
||||||
- build(deps-dev): bump apollo-server-testing from 2.9.9 to 2.9.12 in /backend [`#2318`](https://github.com/Human-Connection/Human-Connection/pull/2318)
|
- build(deps-dev): bump apollo-server-testing from 2.9.9 to 2.9.12 in /backend [`#2318`](https://github.com/Human-Connection/Human-Connection/pull/2318)
|
||||||
- build(deps-dev): bump fuse.js from 3.4.5 to 3.4.6 in /webapp [`#2314`](https://github.com/Human-Connection/Human-Connection/pull/2314)
|
- build(deps-dev): bump fuse.js from 3.4.5 to 3.4.6 in /webapp [`#2314`](https://github.com/Human-Connection/Human-Connection/pull/2314)
|
||||||
- build(deps-dev): bump eslint-config-prettier from 6.6.0 to 6.7.0 in /webapp [`#2302`](https://github.com/Human-Connection/Human-Connection/pull/2302)
|
- build(deps-dev): bump eslint-config-prettier from 6.6.0 to 6.7.0 in /webapp [`#2302`](https://github.com/Human-Connection/Human-Connection/pull/2302)
|
||||||
@ -362,13 +472,11 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- build(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /webapp [`#2205`](https://github.com/Human-Connection/Human-Connection/pull/2205)
|
- build(deps-dev): bump eslint from 5.16.0 to 6.6.0 in /webapp [`#2205`](https://github.com/Human-Connection/Human-Connection/pull/2205)
|
||||||
- Add locale to undefined to null [`#2233`](https://github.com/Human-Connection/Human-Connection/pull/2233)
|
- Add locale to undefined to null [`#2233`](https://github.com/Human-Connection/Human-Connection/pull/2233)
|
||||||
- Update to version 0.1.10 [`#2231`](https://github.com/Human-Connection/Human-Connection/pull/2231)
|
- Update to version 0.1.10 [`#2231`](https://github.com/Human-Connection/Human-Connection/pull/2231)
|
||||||
- Merge pull request #2443 from Human-Connection/2237-longer-comments [`#2237`](https://github.com/Human-Connection/Human-Connection/issues/2237)
|
|
||||||
- fix #2329: Normalize email on login in the backend [`#2329`](https://github.com/Human-Connection/Human-Connection/issues/2329)
|
|
||||||
- Fix #2294 [`#2294`](https://github.com/Human-Connection/Human-Connection/issues/2294)
|
- Fix #2294 [`#2294`](https://github.com/Human-Connection/Human-Connection/issues/2294)
|
||||||
- Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042)
|
- Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042)
|
||||||
- Move components to components/features [`2357028`](https://github.com/Human-Connection/Human-Connection/commit/235702867d97b44dac37f8059f9194e23ba7f47d)
|
- Tell github-linguists to ignore snapshots [`978347b`](https://github.com/Human-Connection/Human-Connection/commit/978347ba7b5a6aa1bc915ada972ffffa2816d37c)
|
||||||
- Basic Search Is Working For Users And Posts [`72e4d0a`](https://github.com/Human-Connection/Human-Connection/commit/72e4d0abbcb9abab07f3fd12876453eb1de5da4c)
|
- Lokalise: update of webapp/locales/ru.json [`906e851`](https://github.com/Human-Connection/Human-Connection/commit/906e8518bf060134150187fb1574ac50ffd502f6)
|
||||||
- Add missing unit tests/refactor code [`b364065`](https://github.com/Human-Connection/Human-Connection/commit/b3640659bb608cc34edc6f2aca350f07dd2b9ce6)
|
- set up global localVue [`77f4810`](https://github.com/Human-Connection/Human-Connection/commit/77f4810ddc963386bc68d3e8a5e078ef4cf270b2)
|
||||||
|
|
||||||
#### [v0.1.10](https://github.com/Human-Connection/Human-Connection/compare/v0.1.9...v0.1.10)
|
#### [v0.1.10](https://github.com/Human-Connection/Human-Connection/compare/v0.1.9...v0.1.10)
|
||||||
|
|
||||||
@ -428,8 +536,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Update feature template [`#2116`](https://github.com/Human-Connection/Human-Connection/pull/2116)
|
- Update feature template [`#2116`](https://github.com/Human-Connection/Human-Connection/pull/2116)
|
||||||
- Update to version 0.1.9 [`#2114`](https://github.com/Human-Connection/Human-Connection/pull/2114)
|
- Update to version 0.1.9 [`#2114`](https://github.com/Human-Connection/Human-Connection/pull/2114)
|
||||||
- remove package-lock.json [`3cf3c31`](https://github.com/Human-Connection/Human-Connection/commit/3cf3c31808dc6ae59fb9c6ec33e9e178c5556438)
|
- remove package-lock.json [`3cf3c31`](https://github.com/Human-Connection/Human-Connection/commit/3cf3c31808dc6ae59fb9c6ec33e9e178c5556438)
|
||||||
- Extract AvatarMenu into its own component [`994a0b0`](https://github.com/Human-Connection/Human-Connection/commit/994a0b049d1803784d9c06383872f1c9e33095a0)
|
- add current file [`26c0d4d`](https://github.com/Human-Connection/Human-Connection/commit/26c0d4d83e4418a2378e05b66b6b47461f82735f)
|
||||||
- Add notifications page with Notifications in table [`7cdc12f`](https://github.com/Human-Connection/Human-Connection/commit/7cdc12f4b9943062e15a874dd39f8a50142b6c61)
|
- Finish portuguese translations [`15c671c`](https://github.com/Human-Connection/Human-Connection/commit/15c671c4a8aae86317896ca30601389504bce9e1)
|
||||||
|
|
||||||
#### [v0.1.9](https://github.com/Human-Connection/Human-Connection/compare/v0.1.8...v0.1.9)
|
#### [v0.1.9](https://github.com/Human-Connection/Human-Connection/compare/v0.1.8...v0.1.9)
|
||||||
|
|
||||||
@ -497,9 +605,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- first implementation [`aeae72f`](https://github.com/Human-Connection/Human-Connection/commit/aeae72f6918861aa2a4c64d0b32c847d9e857e93)
|
- first implementation [`aeae72f`](https://github.com/Human-Connection/Human-Connection/commit/aeae72f6918861aa2a4c64d0b32c847d9e857e93)
|
||||||
- build(deps-dev): bump eslint-plugin-jest in /backend [`6c1bd53`](https://github.com/Human-Connection/Human-Connection/commit/6c1bd535ac482eb0a05d21e227a476800717a19e)
|
- build(deps-dev): bump eslint-plugin-jest in /backend [`6c1bd53`](https://github.com/Human-Connection/Human-Connection/commit/6c1bd535ac482eb0a05d21e227a476800717a19e)
|
||||||
|
|
||||||
#### [v0.1.8](https://github.com/Human-Connection/Human-Connection/compare/0.1.7...v0.1.8)
|
#### [v0.1.8](https://github.com/Human-Connection/Human-Connection/compare/v0.1.7...v0.1.8)
|
||||||
|
|
||||||
> 25 October 2019
|
> 24 October 2019
|
||||||
|
|
||||||
- add FAQ _blank-href in Footer [`#2028`](https://github.com/Human-Connection/Human-Connection/pull/2028)
|
- add FAQ _blank-href in Footer [`#2028`](https://github.com/Human-Connection/Human-Connection/pull/2028)
|
||||||
- fix: Don't attempt to save locale if not authenticated [`#2025`](https://github.com/Human-Connection/Human-Connection/pull/2025)
|
- fix: Don't attempt to save locale if not authenticated [`#2025`](https://github.com/Human-Connection/Human-Connection/pull/2025)
|
||||||
@ -515,11 +623,11 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- build(deps-dev): bump @storybook/addon-a11y from 5.2.4 to 5.2.5 in /webapp [`#1989`](https://github.com/Human-Connection/Human-Connection/pull/1989)
|
- build(deps-dev): bump @storybook/addon-a11y from 5.2.4 to 5.2.5 in /webapp [`#1989`](https://github.com/Human-Connection/Human-Connection/pull/1989)
|
||||||
- build(deps-dev): bump @vue/cli-shared-utils from 4.0.4 to 4.0.5 in /webapp [`#2002`](https://github.com/Human-Connection/Human-Connection/pull/2002)
|
- build(deps-dev): bump @vue/cli-shared-utils from 4.0.4 to 4.0.5 in /webapp [`#2002`](https://github.com/Human-Connection/Human-Connection/pull/2002)
|
||||||
- Update to version 0.1.7 [`#2015`](https://github.com/Human-Connection/Human-Connection/pull/2015)
|
- Update to version 0.1.7 [`#2015`](https://github.com/Human-Connection/Human-Connection/pull/2015)
|
||||||
- Update to version 0.1.8 [`d45264b`](https://github.com/Human-Connection/Human-Connection/commit/d45264b3afa1557c2205e7ca1b77c778ee37ab5a)
|
|
||||||
- build(deps): bump @nuxtjs/apollo in /webapp [`26c21b5`](https://github.com/Human-Connection/Human-Connection/commit/26c21b5b76c96206d98ff6bbfdbd1ca973ffcd4f)
|
- build(deps): bump @nuxtjs/apollo in /webapp [`26c21b5`](https://github.com/Human-Connection/Human-Connection/commit/26c21b5b76c96206d98ff6bbfdbd1ca973ffcd4f)
|
||||||
- Finish redesign of moderators report list [`15d28aa`](https://github.com/Human-Connection/Human-Connection/commit/15d28aa8ef84788aa640aac67838380bfacf63b7)
|
- Finish redesign of moderators report list [`15d28aa`](https://github.com/Human-Connection/Human-Connection/commit/15d28aa8ef84788aa640aac67838380bfacf63b7)
|
||||||
|
- build(deps-dev): bump @storybook/addon-actions in /webapp [`7e95d37`](https://github.com/Human-Connection/Human-Connection/commit/7e95d376a311a5ede6351d577d30e25aea9cb65d)
|
||||||
|
|
||||||
#### [0.1.7](https://github.com/Human-Connection/Human-Connection/compare/0.1.6...0.1.7)
|
#### [v0.1.7](https://github.com/Human-Connection/Human-Connection/compare/v0.1.6...v0.1.7)
|
||||||
|
|
||||||
> 23 October 2019
|
> 23 October 2019
|
||||||
|
|
||||||
@ -535,7 +643,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- build(deps-dev): bump @vue/cli-shared-utils in /webapp [`a5d993c`](https://github.com/Human-Connection/Human-Connection/commit/a5d993c761b2f92c3f44b6f83592ea4c1d822606)
|
- build(deps-dev): bump @vue/cli-shared-utils in /webapp [`a5d993c`](https://github.com/Human-Connection/Human-Connection/commit/a5d993c761b2f92c3f44b6f83592ea4c1d822606)
|
||||||
- Fix block user workflow [`44e5437`](https://github.com/Human-Connection/Human-Connection/commit/44e54372c4148fafae1095d172d1a52a87b3b1b2)
|
- Fix block user workflow [`44e5437`](https://github.com/Human-Connection/Human-Connection/commit/44e54372c4148fafae1095d172d1a52a87b3b1b2)
|
||||||
|
|
||||||
#### [0.1.6](https://github.com/Human-Connection/Human-Connection/compare/0.1.5...0.1.6)
|
#### [v0.1.6](https://github.com/Human-Connection/Human-Connection/compare/v0.1.5...v0.1.6)
|
||||||
|
|
||||||
> 22 October 2019
|
> 22 October 2019
|
||||||
|
|
||||||
@ -569,7 +677,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Refactor tests for querying reported resources [`4e42017`](https://github.com/Human-Connection/Human-Connection/commit/4e42017afaa97fa87ec726a5bbd1605cca911375)
|
- Refactor tests for querying reported resources [`4e42017`](https://github.com/Human-Connection/Human-Connection/commit/4e42017afaa97fa87ec726a5bbd1605cca911375)
|
||||||
- Return pinnedAt date from pinPost resolver/clean up [`be0c804`](https://github.com/Human-Connection/Human-Connection/commit/be0c8044e87e211f2578df151d9d2d11795a135f)
|
- Return pinnedAt date from pinPost resolver/clean up [`be0c804`](https://github.com/Human-Connection/Human-Connection/commit/be0c8044e87e211f2578df151d9d2d11795a135f)
|
||||||
|
|
||||||
#### [0.1.5](https://github.com/Human-Connection/Human-Connection/compare/0.1.4...0.1.5)
|
#### [v0.1.5](https://github.com/Human-Connection/Human-Connection/compare/v0.1.4...v0.1.5)
|
||||||
|
|
||||||
> 17 October 2019
|
> 17 October 2019
|
||||||
|
|
||||||
@ -627,7 +735,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Start adding missing portuguese translation [`33eb000`](https://github.com/Human-Connection/Human-Connection/commit/33eb000ee33e5aa513083450f0a00abd7240efb0)
|
- Start adding missing portuguese translation [`33eb000`](https://github.com/Human-Connection/Human-Connection/commit/33eb000ee33e5aa513083450f0a00abd7240efb0)
|
||||||
- refactor: restructure translations and components [`bb5d581`](https://github.com/Human-Connection/Human-Connection/commit/bb5d581906b5e6e723966c3dc687c7f309356841)
|
- refactor: restructure translations and components [`bb5d581`](https://github.com/Human-Connection/Human-Connection/commit/bb5d581906b5e6e723966c3dc687c7f309356841)
|
||||||
|
|
||||||
#### [0.1.4](https://github.com/Human-Connection/Human-Connection/compare/0.1.3...0.1.4)
|
#### [v0.1.4](https://github.com/Human-Connection/Human-Connection/compare/v0.1.3...v0.1.4)
|
||||||
|
|
||||||
> 10 October 2019
|
> 10 October 2019
|
||||||
|
|
||||||
@ -665,7 +773,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Fix lint, update tests [`bced698`](https://github.com/Human-Connection/Human-Connection/commit/bced6983ea1f51736e989eab6a41166723a6a6ca)
|
- Fix lint, update tests [`bced698`](https://github.com/Human-Connection/Human-Connection/commit/bced6983ea1f51736e989eab6a41166723a6a6ca)
|
||||||
- add test embeds and links [`7cc139e`](https://github.com/Human-Connection/Human-Connection/commit/7cc139e879ac7ea912e82ea7eff14f7b67eddb4a)
|
- add test embeds and links [`7cc139e`](https://github.com/Human-Connection/Human-Connection/commit/7cc139e879ac7ea912e82ea7eff14f7b67eddb4a)
|
||||||
|
|
||||||
#### [0.1.3](https://github.com/Human-Connection/Human-Connection/compare/0.1.2...0.1.3)
|
#### [v0.1.3](https://github.com/Human-Connection/Human-Connection/compare/v0.1.2...v0.1.3)
|
||||||
|
|
||||||
> 4 October 2019
|
> 4 October 2019
|
||||||
|
|
||||||
@ -685,7 +793,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Set hasMore to false when returned Posts are equal to pageSize [`6f1c5e3`](https://github.com/Human-Connection/Human-Connection/commit/6f1c5e3efa3b77e72172592a0b5e4ea52158e642)
|
- Set hasMore to false when returned Posts are equal to pageSize [`6f1c5e3`](https://github.com/Human-Connection/Human-Connection/commit/6f1c5e3efa3b77e72172592a0b5e4ea52158e642)
|
||||||
- refactor: use named slot for additional text [`3912b21`](https://github.com/Human-Connection/Human-Connection/commit/3912b21ea2f24e2e25682060b7166d1511442e6e)
|
- refactor: use named slot for additional text [`3912b21`](https://github.com/Human-Connection/Human-Connection/commit/3912b21ea2f24e2e25682060b7166d1511442e6e)
|
||||||
|
|
||||||
#### [0.1.2](https://github.com/Human-Connection/Human-Connection/compare/0.1.1...0.1.2)
|
#### [v0.1.2](https://github.com/Human-Connection/Human-Connection/compare/v0.1.1...v0.1.2)
|
||||||
|
|
||||||
> 2 October 2019
|
> 2 October 2019
|
||||||
|
|
||||||
@ -744,7 +852,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- build(deps): bump @nuxtjs/apollo in /webapp [`4648080`](https://github.com/Human-Connection/Human-Connection/commit/4648080a74fa6df60d6bb9b34d1db5030a9d4124)
|
- build(deps): bump @nuxtjs/apollo in /webapp [`4648080`](https://github.com/Human-Connection/Human-Connection/commit/4648080a74fa6df60d6bb9b34d1db5030a9d4124)
|
||||||
- Write and refactor backend test which are supposed to fail at first [`6ad9dc2`](https://github.com/Human-Connection/Human-Connection/commit/6ad9dc27e937eb263914846c073172906aa661e1)
|
- Write and refactor backend test which are supposed to fail at first [`6ad9dc2`](https://github.com/Human-Connection/Human-Connection/commit/6ad9dc27e937eb263914846c073172906aa661e1)
|
||||||
|
|
||||||
#### [0.1.1](https://github.com/Human-Connection/Human-Connection/compare/0.1.0...0.1.1)
|
#### [v0.1.1](https://github.com/Human-Connection/Human-Connection/compare/v0.1.0...v0.1.1)
|
||||||
|
|
||||||
> 27 September 2019
|
> 27 September 2019
|
||||||
|
|
||||||
@ -818,7 +926,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Run with tag 0.1.0 [`c634ad2`](https://github.com/Human-Connection/Human-Connection/commit/c634ad264bd99dd1a87a86f870d7877aa751dc38)
|
- Run with tag 0.1.0 [`c634ad2`](https://github.com/Human-Connection/Human-Connection/commit/c634ad264bd99dd1a87a86f870d7877aa751dc38)
|
||||||
- zwischenspeichern [`e4c7c11`](https://github.com/Human-Connection/Human-Connection/commit/e4c7c1125da6f8fa259241b4d3838b1e7b1e24a2)
|
- zwischenspeichern [`e4c7c11`](https://github.com/Human-Connection/Human-Connection/commit/e4c7c1125da6f8fa259241b4d3838b1e7b1e24a2)
|
||||||
|
|
||||||
#### 0.1.0
|
#### v0.1.0
|
||||||
|
|
||||||
> 18 September 2019
|
> 18 September 2019
|
||||||
|
|
||||||
@ -1956,5 +2064,5 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Merge pull request #93 from Gerald1614/500_error_on_login [`#49`](https://github.com/Human-Connection/Human-Connection/issues/49)
|
- Merge pull request #93 from Gerald1614/500_error_on_login [`#49`](https://github.com/Human-Connection/Human-Connection/issues/49)
|
||||||
- Update schema.graphql [`#7`](https://github.com/Human-Connection/Human-Connection/issues/7)
|
- Update schema.graphql [`#7`](https://github.com/Human-Connection/Human-Connection/issues/7)
|
||||||
- Refactore the import and hashtags to all unicode characters [`0bc4c55`](https://github.com/Human-Connection/Human-Connection/commit/0bc4c558ae8f01d6d975b8ee1ea7f0f42b056d91)
|
- Refactore the import and hashtags to all unicode characters [`0bc4c55`](https://github.com/Human-Connection/Human-Connection/commit/0bc4c558ae8f01d6d975b8ee1ea7f0f42b056d91)
|
||||||
- Change strategy, only build docker image [`d6b7374`](https://github.com/Human-Connection/Human-Connection/commit/d6b7374ddbf497bdb5cbc935b88ae085c38b3237)
|
- Remove package-lock.json [`25bd96e`](https://github.com/Human-Connection/Human-Connection/commit/25bd96eedf6be5b7ea6e94c8433d044e13d62e70)
|
||||||
- Copy package.json from webapp/ [`f3a9996`](https://github.com/Human-Connection/Human-Connection/commit/f3a9996962e5dd8b2e365a032c1a5766fe666103)
|
- Remove Styleguide [`53ea934`](https://github.com/Human-Connection/Human-Connection/commit/53ea93492dcc7f861743cd50a4ddf7728c9d659b)
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
* [Volume Snapshots](deployment/volumes/volume-snapshots/README.md)
|
* [Volume Snapshots](deployment/volumes/volume-snapshots/README.md)
|
||||||
* [Reclaim Policy](deployment/volumes/reclaim-policy/README.md)
|
* [Reclaim Policy](deployment/volumes/reclaim-policy/README.md)
|
||||||
* [Velero](deployment/volumes/velero/README.md)
|
* [Velero](deployment/volumes/velero/README.md)
|
||||||
|
* [Metrics](deployment/monitoring/README.md)
|
||||||
* [Legacy Migration](deployment/legacy-migration/README.md)
|
* [Legacy Migration](deployment/legacy-migration/README.md)
|
||||||
* [Feature Specification](cypress/features.md)
|
* [Feature Specification](cypress/features.md)
|
||||||
* [Code of conduct](CODE_OF_CONDUCT.md)
|
* [Code of conduct](CODE_OF_CONDUCT.md)
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "human-connection-backend",
|
"name": "human-connection-backend",
|
||||||
"version": "0.0.1",
|
"version": "0.2.2",
|
||||||
"description": "GraphQL Backend for Human Connection",
|
"description": "GraphQL Backend for Human Connection",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "babel src/ -d dist/ --copy-files",
|
"build": "babel src/ -d dist/ --copy-files",
|
||||||
"start": "node dist/",
|
"start": "node dist/",
|
||||||
"dev": "nodemon --exec babel-node src/ -e js,gql",
|
"dev": "nodemon --exec babel-node src/ -e js,gql",
|
||||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,gql",
|
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
|
||||||
"lint": "eslint src --config .eslintrc.js",
|
"lint": "eslint src --config .eslintrc.js",
|
||||||
"test": "jest --forceExit --detectOpenHandles --runInBand",
|
"test": "jest --forceExit --detectOpenHandles --runInBand",
|
||||||
"db:reset": "babel-node src/seed/reset-db.js",
|
"db:reset": "babel-node src/seed/reset-db.js",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/joi": "^17.0.2",
|
"@hapi/joi": "^17.1.0",
|
||||||
"@sentry/node": "^5.11.1",
|
"@sentry/node": "^5.11.1",
|
||||||
"apollo-cache-inmemory": "~1.6.5",
|
"apollo-cache-inmemory": "~1.6.5",
|
||||||
"apollo-client": "~2.6.8",
|
"apollo-client": "~2.6.8",
|
||||||
@ -62,24 +62,24 @@
|
|||||||
"linkifyjs": "~2.1.8",
|
"linkifyjs": "~2.1.8",
|
||||||
"lodash": "~4.17.14",
|
"lodash": "~4.17.14",
|
||||||
"merge-graphql-schemas": "^1.7.6",
|
"merge-graphql-schemas": "^1.7.6",
|
||||||
"metascraper": "^5.10.2",
|
"metascraper": "^5.10.5",
|
||||||
"metascraper-audio": "^5.9.5",
|
"metascraper-audio": "^5.10.5",
|
||||||
"metascraper-author": "^5.9.5",
|
"metascraper-author": "^5.10.5",
|
||||||
"metascraper-clearbit-logo": "^5.3.0",
|
"metascraper-clearbit-logo": "^5.3.0",
|
||||||
"metascraper-date": "^5.10.3",
|
"metascraper-date": "^5.10.5",
|
||||||
"metascraper-description": "^5.9.5",
|
"metascraper-description": "^5.10.5",
|
||||||
"metascraper-image": "^5.9.5",
|
"metascraper-image": "^5.10.5",
|
||||||
"metascraper-lang": "^5.9.5",
|
"metascraper-lang": "^5.10.5",
|
||||||
"metascraper-lang-detector": "^4.10.2",
|
"metascraper-lang-detector": "^4.10.2",
|
||||||
"metascraper-logo": "^5.9.5",
|
"metascraper-logo": "^5.10.5",
|
||||||
"metascraper-publisher": "^5.10.3",
|
"metascraper-publisher": "^5.10.5",
|
||||||
"metascraper-soundcloud": "^5.9.5",
|
"metascraper-soundcloud": "^5.10.5",
|
||||||
"metascraper-title": "^5.10.3",
|
"metascraper-title": "^5.10.5",
|
||||||
"metascraper-url": "^5.9.5",
|
"metascraper-url": "^5.10.5",
|
||||||
"metascraper-video": "^5.9.5",
|
"metascraper-video": "^5.10.5",
|
||||||
"metascraper-youtube": "^5.9.5",
|
"metascraper-youtube": "^5.10.5",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"mustache": "^3.2.1",
|
"mustache": "^4.0.0",
|
||||||
"neo4j-driver": "^4.0.1",
|
"neo4j-driver": "^4.0.1",
|
||||||
"neo4j-graphql-js": "^2.11.5",
|
"neo4j-graphql-js": "^2.11.5",
|
||||||
"neode": "^0.3.7",
|
"neode": "^0.3.7",
|
||||||
@ -88,12 +88,12 @@
|
|||||||
"nodemailer-html-to-text": "^3.1.0",
|
"nodemailer-html-to-text": "^3.1.0",
|
||||||
"npm-run-all": "~4.1.5",
|
"npm-run-all": "~4.1.5",
|
||||||
"request": "~2.88.0",
|
"request": "~2.88.0",
|
||||||
"sanitize-html": "~1.20.1",
|
"sanitize-html": "~1.21.1",
|
||||||
"slug": "~2.1.0",
|
"slug": "~2.1.0",
|
||||||
"trunc-html": "~1.1.2",
|
"trunc-html": "~1.1.2",
|
||||||
"uuid": "~3.3.3",
|
"uuid": "~3.4.0",
|
||||||
"validator": "^12.1.0",
|
"validator": "^12.1.0",
|
||||||
"wait-on": "~3.3.0",
|
"wait-on": "~4.0.0",
|
||||||
"xregexp": "^4.2.4"
|
"xregexp": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -118,7 +118,7 @@
|
|||||||
"eslint-plugin-prettier": "~3.1.2",
|
"eslint-plugin-prettier": "~3.1.2",
|
||||||
"eslint-plugin-promise": "~4.2.1",
|
"eslint-plugin-promise": "~4.2.1",
|
||||||
"eslint-plugin-standard": "~4.0.1",
|
"eslint-plugin-standard": "~4.0.1",
|
||||||
"jest": "~24.9.0",
|
"jest": "~25.1.0",
|
||||||
"nodemon": "~2.0.2",
|
"nodemon": "~2.0.2",
|
||||||
"prettier": "~1.19.1",
|
"prettier": "~1.19.1",
|
||||||
"supertest": "~4.0.2"
|
"supertest": "~4.0.2"
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import path from 'path'
|
if (require.resolve) {
|
||||||
|
// are we in a nodejs environment?
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../../.env') })
|
dotenv.config({ path: require.resolve('../../.env') })
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
MAPBOX_TOKEN,
|
MAPBOX_TOKEN,
|
||||||
@ -27,6 +28,15 @@ export const requiredConfigs = {
|
|||||||
PRIVATE_KEY_PASSPHRASE,
|
PRIVATE_KEY_PASSPHRASE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (require.resolve) {
|
||||||
|
// are we in a nodejs environment?
|
||||||
|
Object.entries(requiredConfigs).map(entry => {
|
||||||
|
if (!entry[1]) {
|
||||||
|
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const smtpConfigs = {
|
export const smtpConfigs = {
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_PORT,
|
SMTP_PORT,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export default shield(
|
|||||||
Badge: allow,
|
Badge: allow,
|
||||||
PostsEmotionsCountByEmotion: allow,
|
PostsEmotionsCountByEmotion: allow,
|
||||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||||
blockedUsers: isAuthenticated,
|
mutedUsers: isAuthenticated,
|
||||||
notifications: isAuthenticated,
|
notifications: isAuthenticated,
|
||||||
Donations: isAuthenticated,
|
Donations: isAuthenticated,
|
||||||
},
|
},
|
||||||
@ -137,8 +137,8 @@ export default shield(
|
|||||||
resetPassword: allow,
|
resetPassword: allow,
|
||||||
AddPostEmotions: isAuthenticated,
|
AddPostEmotions: isAuthenticated,
|
||||||
RemovePostEmotions: isAuthenticated,
|
RemovePostEmotions: isAuthenticated,
|
||||||
block: isAuthenticated,
|
muteUser: isAuthenticated,
|
||||||
unblock: isAuthenticated,
|
unmuteUser: isAuthenticated,
|
||||||
markAsRead: isAuthenticated,
|
markAsRead: isAuthenticated,
|
||||||
AddEmailAddress: isAuthenticated,
|
AddEmailAddress: isAuthenticated,
|
||||||
VerifyEmailAddress: isAuthenticated,
|
VerifyEmailAddress: isAuthenticated,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, lowercase: true },
|
id: { type: 'string', primary: true, lowercase: true },
|
||||||
status: { type: 'string', valid: ['permanent', 'temporary'] },
|
status: { type: 'string', valid: ['permanent', 'temporary'] },
|
||||||
type: { type: 'string', valid: ['role', 'crowdfunding'] },
|
type: { type: 'string', valid: ['role', 'crowdfunding'] },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
name: { type: 'string', required: true, default: false },
|
name: { type: 'string', required: true, default: false },
|
||||||
slug: { type: 'string' },
|
slug: { type: 'string' },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
goal: { type: 'number' },
|
goal: { type: 'number' },
|
||||||
progress: { type: 'number' },
|
progress: { type: 'number' },
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
email: { type: 'string', primary: true, lowercase: true, email: true },
|
email: { type: 'string', primary: true, lowercase: true, email: true },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
verifiedAt: { type: 'string', isoDate: true },
|
verifiedAt: { type: 'string', isoDate: true },
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
token: { type: 'string', primary: true, token: true },
|
token: { type: 'string', primary: true, token: true },
|
||||||
generatedBy: {
|
generatedBy: {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true },
|
id: { type: 'string', primary: true },
|
||||||
lat: { type: 'number' },
|
lat: { type: 'number' },
|
||||||
lng: { type: 'number' },
|
lng: { type: 'number' },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
activityId: { type: 'string', allow: [null] },
|
activityId: { type: 'string', allow: [null] },
|
||||||
objectId: { type: 'string', allow: [null] },
|
objectId: { type: 'string', allow: [null] },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid },
|
id: { type: 'string', primary: true, default: uuid },
|
||||||
url: { type: 'string', uri: true, required: true },
|
url: { type: 'string', uri: true, required: true },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true },
|
id: { type: 'string', primary: true },
|
||||||
deleted: { type: 'boolean', default: false },
|
deleted: { type: 'boolean', default: false },
|
||||||
disabled: { type: 'boolean', default: false },
|
disabled: { type: 'boolean', default: false },
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
email: { type: 'string', primary: true, lowercase: true, email: true },
|
email: { type: 'string', primary: true, lowercase: true, email: true },
|
||||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||||
nonce: { type: 'string', token: true },
|
nonce: { type: 'string', token: true },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests
|
id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests
|
||||||
actorId: { type: 'string', allow: [null] },
|
actorId: { type: 'string', allow: [null] },
|
||||||
name: { type: 'string', disallow: [null], min: 3 },
|
name: { type: 'string', disallow: [null], min: 3 },
|
||||||
@ -78,6 +78,12 @@ module.exports = {
|
|||||||
target: 'User',
|
target: 'User',
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
},
|
},
|
||||||
|
muted: {
|
||||||
|
type: 'relationship',
|
||||||
|
relationship: 'MUTED',
|
||||||
|
target: 'User',
|
||||||
|
direction: 'out',
|
||||||
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationship: 'NOTIFIED',
|
relationship: 'NOTIFIED',
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
|
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
|
||||||
// module that is not browser-compatible. Node's `fs` module is server-side only
|
// module that is not browser-compatible. Node's `fs` module is server-side only
|
||||||
export default {
|
export default {
|
||||||
Badge: require('./Badge.js'),
|
Badge: require('./Badge.js').default,
|
||||||
User: require('./User.js'),
|
User: require('./User.js').default,
|
||||||
EmailAddress: require('./EmailAddress.js'),
|
EmailAddress: require('./EmailAddress.js').default,
|
||||||
UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js'),
|
UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js').default,
|
||||||
SocialMedia: require('./SocialMedia.js'),
|
SocialMedia: require('./SocialMedia.js').default,
|
||||||
Post: require('./Post.js'),
|
Post: require('./Post.js').default,
|
||||||
Comment: require('./Comment.js'),
|
Comment: require('./Comment.js').default,
|
||||||
Category: require('./Category.js'),
|
Category: require('./Category.js').default,
|
||||||
Tag: require('./Tag.js'),
|
Tag: require('./Tag.js').default,
|
||||||
Location: require('./Location.js'),
|
Location: require('./Location.js').default,
|
||||||
Donations: require('./Donations.js'),
|
Donations: require('./Donations.js').default,
|
||||||
Report: require('./Report.js'),
|
Report: require('./Report.js').default,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import { getBlockedUsers, getBlockedByUsers } from '../users.js'
|
|
||||||
import { mergeWith, isArray } from 'lodash'
|
|
||||||
|
|
||||||
export const filterForBlockedUsers = async (params, context) => {
|
|
||||||
if (!context.user) return params
|
|
||||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
|
||||||
getBlockedUsers(context),
|
|
||||||
getBlockedByUsers(context),
|
|
||||||
])
|
|
||||||
const blockedUsersIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)]
|
|
||||||
if (!blockedUsersIds.length) return params
|
|
||||||
|
|
||||||
params.filter = mergeWith(
|
|
||||||
params.filter,
|
|
||||||
{
|
|
||||||
author_not: { id_in: blockedUsersIds },
|
|
||||||
},
|
|
||||||
(objValue, srcValue) => {
|
|
||||||
if (isArray(objValue)) {
|
|
||||||
return objValue.concat(srcValue)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
22
backend/src/schema/resolvers/helpers/filterForMutedUsers.js
Normal file
22
backend/src/schema/resolvers/helpers/filterForMutedUsers.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { getMutedUsers } from '../users.js'
|
||||||
|
import { mergeWith, isArray } from 'lodash'
|
||||||
|
|
||||||
|
export const filterForMutedUsers = async (params, context) => {
|
||||||
|
if (!context.user) return params
|
||||||
|
const [mutedUsers] = await Promise.all([getMutedUsers(context)])
|
||||||
|
const mutedUsersIds = [...mutedUsers.map(user => user.id)]
|
||||||
|
if (!mutedUsersIds.length) return params
|
||||||
|
|
||||||
|
params.filter = mergeWith(
|
||||||
|
params.filter,
|
||||||
|
{
|
||||||
|
author_not: { id_in: mutedUsersIds },
|
||||||
|
},
|
||||||
|
(objValue, srcValue) => {
|
||||||
|
if (isArray(objValue)) {
|
||||||
|
return objValue.concat(srcValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import uuid from 'uuid/v4'
|
import uuid from 'uuid/v4'
|
||||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import fileUpload from './fileUpload'
|
|
||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
|
import fileUpload from './fileUpload'
|
||||||
import Resolver from './helpers/Resolver'
|
import Resolver from './helpers/Resolver'
|
||||||
import { filterForBlockedUsers } from './helpers/filterForBlockedUsers'
|
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
|
||||||
|
|
||||||
const maintainPinnedPosts = params => {
|
const maintainPinnedPosts = params => {
|
||||||
const pinnedPostFilter = { pinned: true }
|
const pinnedPostFilter = { pinned: true }
|
||||||
@ -19,16 +19,16 @@ const maintainPinnedPosts = params => {
|
|||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
Post: async (object, params, context, resolveInfo) => {
|
Post: async (object, params, context, resolveInfo) => {
|
||||||
params = await filterForBlockedUsers(params, context)
|
params = await filterForMutedUsers(params, context)
|
||||||
params = await maintainPinnedPosts(params)
|
params = await maintainPinnedPosts(params)
|
||||||
return neo4jgraphql(object, params, context, resolveInfo)
|
return neo4jgraphql(object, params, context, resolveInfo)
|
||||||
},
|
},
|
||||||
findPosts: async (object, params, context, resolveInfo) => {
|
findPosts: async (object, params, context, resolveInfo) => {
|
||||||
params = await filterForBlockedUsers(params, context)
|
params = await filterForMutedUsers(params, context)
|
||||||
return neo4jgraphql(object, params, context, resolveInfo)
|
return neo4jgraphql(object, params, context, resolveInfo)
|
||||||
},
|
},
|
||||||
profilePagePosts: async (object, params, context, resolveInfo) => {
|
profilePagePosts: async (object, params, context, resolveInfo) => {
|
||||||
params = await filterForBlockedUsers(params, context)
|
params = await filterForMutedUsers(params, context)
|
||||||
return neo4jgraphql(object, params, context, resolveInfo)
|
return neo4jgraphql(object, params, context, resolveInfo)
|
||||||
},
|
},
|
||||||
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => {
|
||||||
|
|||||||
@ -8,42 +8,26 @@ import createOrUpdateLocations from './users/location'
|
|||||||
|
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|
||||||
export const getBlockedUsers = async context => {
|
export const getMutedUsers = async context => {
|
||||||
const { neode } = context
|
const { neode } = context
|
||||||
const userModel = neode.model('User')
|
const userModel = neode.model('User')
|
||||||
let blockedUsers = neode
|
let mutedUsers = neode
|
||||||
.query()
|
.query()
|
||||||
.match('user', userModel)
|
.match('user', userModel)
|
||||||
.where('user.id', context.user.id)
|
.where('user.id', context.user.id)
|
||||||
.relationship(userModel.relationships().get('blocked'))
|
.relationship(userModel.relationships().get('muted'))
|
||||||
.to('blocked', userModel)
|
.to('muted', userModel)
|
||||||
.return('blocked')
|
.return('muted')
|
||||||
blockedUsers = await blockedUsers.execute()
|
mutedUsers = await mutedUsers.execute()
|
||||||
blockedUsers = blockedUsers.records.map(r => r.get('blocked').properties)
|
mutedUsers = mutedUsers.records.map(r => r.get('muted').properties)
|
||||||
return blockedUsers
|
return mutedUsers
|
||||||
}
|
|
||||||
|
|
||||||
export const getBlockedByUsers = async context => {
|
|
||||||
if (context.user.role === 'moderator' || context.user.role === 'admin') return []
|
|
||||||
const { neode } = context
|
|
||||||
const userModel = neode.model('User')
|
|
||||||
let blockedByUsers = neode
|
|
||||||
.query()
|
|
||||||
.match('user', userModel)
|
|
||||||
.relationship(userModel.relationships().get('blocked'))
|
|
||||||
.to('blocked', userModel)
|
|
||||||
.where('blocked.id', context.user.id)
|
|
||||||
.return('user')
|
|
||||||
blockedByUsers = await blockedByUsers.execute()
|
|
||||||
blockedByUsers = blockedByUsers.records.map(r => r.get('user').properties)
|
|
||||||
return blockedByUsers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
blockedUsers: async (object, args, context, resolveInfo) => {
|
mutedUsers: async (object, args, context, resolveInfo) => {
|
||||||
try {
|
try {
|
||||||
return getBlockedUsers(context)
|
return getMutedUsers(context)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UserInputError(e.message)
|
throw new UserInputError(e.message)
|
||||||
}
|
}
|
||||||
@ -72,6 +56,36 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
|
muteUser: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
const { user: currentUser } = context
|
||||||
|
if (currentUser.id === params.id) return null
|
||||||
|
await neode.cypher(
|
||||||
|
`
|
||||||
|
MATCH(u:User {id: $currentUser.id})-[previousRelationship:FOLLOWS]->(b:User {id: $params.id})
|
||||||
|
DELETE previousRelationship
|
||||||
|
`,
|
||||||
|
{ currentUser, params },
|
||||||
|
)
|
||||||
|
const [user, mutedUser] = await Promise.all([
|
||||||
|
neode.find('User', currentUser.id),
|
||||||
|
neode.find('User', params.id),
|
||||||
|
])
|
||||||
|
await user.relateTo(mutedUser, 'muted')
|
||||||
|
return mutedUser.toJson()
|
||||||
|
},
|
||||||
|
unmuteUser: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
const { user: currentUser } = context
|
||||||
|
if (currentUser.id === params.id) return null
|
||||||
|
await neode.cypher(
|
||||||
|
`
|
||||||
|
MATCH(u:User {id: $currentUser.id})-[previousRelationship:MUTED]->(b:User {id: $params.id})
|
||||||
|
DELETE previousRelationship
|
||||||
|
`,
|
||||||
|
{ currentUser, params },
|
||||||
|
)
|
||||||
|
const unmutedUser = await neode.find('User', params.id)
|
||||||
|
return unmutedUser.toJson()
|
||||||
|
},
|
||||||
block: async (object, args, context, resolveInfo) => {
|
block: async (object, args, context, resolveInfo) => {
|
||||||
const { user: currentUser } = context
|
const { user: currentUser } = context
|
||||||
if (currentUser.id === args.id) return null
|
if (currentUser.id === args.id) return null
|
||||||
@ -217,6 +231,8 @@ export default {
|
|||||||
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
isBlocked:
|
isBlocked:
|
||||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
|
isMuted:
|
||||||
|
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
contributionsCount:
|
contributionsCount:
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const factory = Factory()
|
|||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|
||||||
let currentUser
|
let currentUser
|
||||||
let blockedUser
|
let mutedUser
|
||||||
let authenticatedUser
|
let authenticatedUser
|
||||||
let server
|
let server
|
||||||
|
|
||||||
@ -33,15 +33,15 @@ afterEach(async () => {
|
|||||||
await factory.cleanDatabase()
|
await factory.cleanDatabase()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('blockedUsers', () => {
|
describe('mutedUsers', () => {
|
||||||
let blockedUserQuery
|
let mutedUserQuery
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
blockedUserQuery = gql`
|
mutedUserQuery = gql`
|
||||||
query {
|
query {
|
||||||
blockedUsers {
|
mutedUsers {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
isBlocked
|
isMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -49,34 +49,34 @@ describe('blockedUsers', () => {
|
|||||||
|
|
||||||
it('throws permission error', async () => {
|
it('throws permission error', async () => {
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
const result = await query({ query: blockedUserQuery })
|
const result = await query({ query: mutedUserQuery })
|
||||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('authenticated and given a blocked user', () => {
|
describe('authenticated and given a muted user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
currentUser = await neode.create('User', {
|
currentUser = await neode.create('User', {
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
id: 'u1',
|
id: 'u1',
|
||||||
})
|
})
|
||||||
blockedUser = await neode.create('User', {
|
mutedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Muted User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
await currentUser.relateTo(blockedUser, 'blocked')
|
await currentUser.relateTo(mutedUser, 'muted')
|
||||||
authenticatedUser = await currentUser.toJson()
|
authenticatedUser = await currentUser.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns a list of blocked users', async () => {
|
it('returns a list of muted users', async () => {
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(query({ query: blockedUserQuery })).resolves.toEqual(
|
await expect(query({ query: mutedUserQuery })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: {
|
data: {
|
||||||
blockedUsers: [
|
mutedUsers: [
|
||||||
{
|
{
|
||||||
name: 'Blocked User',
|
name: 'Muted User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
isBlocked: true,
|
isMuted: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -86,28 +86,28 @@ describe('blockedUsers', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('block', () => {
|
describe('muteUser', () => {
|
||||||
let blockAction
|
let muteAction
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
currentUser = undefined
|
currentUser = undefined
|
||||||
blockAction = variables => {
|
muteAction = variables => {
|
||||||
const { mutate } = createTestClient(server)
|
const { mutate } = createTestClient(server)
|
||||||
const blockMutation = gql`
|
const muteUserMutation = gql`
|
||||||
mutation($id: ID!) {
|
mutation($id: ID!) {
|
||||||
block(id: $id) {
|
muteUser(id: $id) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
isBlocked
|
isMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
return mutate({ mutation: blockMutation, variables })
|
return mutate({ mutation: muteUserMutation, variables })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws permission error', async () => {
|
it('throws permission error', async () => {
|
||||||
const result = await blockAction({ id: 'u2' })
|
const result = await muteAction({ id: 'u2' })
|
||||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -120,45 +120,47 @@ describe('block', () => {
|
|||||||
authenticatedUser = await currentUser.toJson()
|
authenticatedUser = await currentUser.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('block yourself', () => {
|
describe('mute yourself', () => {
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(blockAction({ id: 'u1' })).resolves.toEqual(
|
await expect(muteAction({ id: 'u1' })).resolves.toEqual(
|
||||||
expect.objectContaining({ data: { block: null } }),
|
expect.objectContaining({ data: { muteUser: null } }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('block not existing user', () => {
|
describe('mute not existing user', () => {
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(blockAction({ id: 'u2' })).resolves.toEqual(
|
await expect(muteAction({ id: 'u2' })).resolves.toEqual(
|
||||||
expect.objectContaining({ data: { block: null } }),
|
expect.objectContaining({ data: { muteUser: null } }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given a to-be-blocked user', () => {
|
describe('given a to-be-muted user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
blockedUser = await neode.create('User', {
|
mutedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Muted User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('blocks a user', async () => {
|
it('mutes a user', async () => {
|
||||||
await expect(blockAction({ id: 'u2' })).resolves.toEqual(
|
await expect(muteAction({ id: 'u2' })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { block: { id: 'u2', name: 'Blocked User', isBlocked: true } },
|
data: {
|
||||||
|
muteUser: { id: 'u2', name: 'Muted User', isMuted: true },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unfollows the user', async () => {
|
it('unfollows the user', async () => {
|
||||||
await currentUser.relateTo(blockedUser, 'following')
|
await currentUser.relateTo(mutedUser, 'following')
|
||||||
const queryUser = gql`
|
const queryUser = gql`
|
||||||
query {
|
query {
|
||||||
User(id: "u2") {
|
User(id: "u2") {
|
||||||
id
|
id
|
||||||
isBlocked
|
isMuted
|
||||||
followedByCurrentUser
|
followedByCurrentUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,18 +168,18 @@ describe('block', () => {
|
|||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(query({ query: queryUser })).resolves.toEqual(
|
await expect(query({ query: queryUser })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { User: [{ id: 'u2', isBlocked: false, followedByCurrentUser: true }] },
|
data: { User: [{ id: 'u2', isMuted: false, followedByCurrentUser: true }] },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
await blockAction({ id: 'u2' })
|
await muteAction({ id: 'u2' })
|
||||||
await expect(query({ query: queryUser })).resolves.toEqual(
|
await expect(query({ query: queryUser })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { User: [{ id: 'u2', isBlocked: true, followedByCurrentUser: false }] },
|
data: { User: [{ id: 'u2', isMuted: true, followedByCurrentUser: false }] },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given both the current user and the to-be-blocked user write a post', () => {
|
describe('given both the current user and the to-be-muted user write a post', () => {
|
||||||
let postQuery
|
let postQuery
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -187,11 +189,11 @@ describe('block', () => {
|
|||||||
})
|
})
|
||||||
const post2 = await neode.create('Post', {
|
const post2 = await neode.create('Post', {
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
title: 'A post written by the blocked user',
|
title: 'A post written by the muted user',
|
||||||
})
|
})
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
post1.relateTo(currentUser, 'author'),
|
post1.relateTo(currentUser, 'author'),
|
||||||
post2.relateTo(blockedUser, 'author'),
|
post2.relateTo(mutedUser, 'author'),
|
||||||
])
|
])
|
||||||
postQuery = gql`
|
postQuery = gql`
|
||||||
query {
|
query {
|
||||||
@ -223,9 +225,9 @@ describe('block', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
title: 'A post written by the blocked user',
|
title: 'A post written by the muted user',
|
||||||
author: {
|
author: {
|
||||||
name: 'Blocked User',
|
name: 'Muted User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -238,12 +240,12 @@ describe('block', () => {
|
|||||||
describe('from the perspective of the current user', () => {
|
describe('from the perspective of the current user', () => {
|
||||||
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
||||||
|
|
||||||
describe('but if the current user blocks the other user', () => {
|
describe('but if the current user mutes the other user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await currentUser.relateTo(blockedUser, 'blocked')
|
await currentUser.relateTo(mutedUser, 'muted')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("the blocked user's post won't show up in the newsfeed of the current user", async () => {
|
it("the muted user's post won't show up in the newsfeed of the current user", async () => {
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(query({ query: postQuery })).resolves.toEqual(
|
await expect(query({ query: postQuery })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@ -262,29 +264,34 @@ describe('block', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('from the perspective of the blocked user', () => {
|
describe('from the perspective of the muted user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
authenticatedUser = await blockedUser.toJson()
|
authenticatedUser = await mutedUser.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
it('both posts are in the newsfeed', bothPostsAreInTheNewsfeed)
|
||||||
describe('but if the current user blocks the other user', () => {
|
describe('but if the current user mutes the other user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await currentUser.relateTo(blockedUser, 'blocked')
|
await currentUser.relateTo(mutedUser, 'muted')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("the current user's post won't show up in the newsfeed of the blocked user", async () => {
|
it("the current user's post will show up in the newsfeed of the muted user", async () => {
|
||||||
const { query } = createTestClient(server)
|
const { query } = createTestClient(server)
|
||||||
await expect(query({ query: postQuery })).resolves.toEqual(
|
await expect(query({ query: postQuery })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: {
|
data: {
|
||||||
Post: [
|
Post: expect.arrayContaining([
|
||||||
{
|
{
|
||||||
id: 'p23',
|
id: 'p23',
|
||||||
title: 'A post written by the blocked user',
|
title: 'A post written by the muted user',
|
||||||
author: { name: 'Blocked User', id: 'u2' },
|
author: { name: 'Muted User', id: 'u2' },
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
|
id: 'p12',
|
||||||
|
title: 'A post written by the current user',
|
||||||
|
author: { name: 'Current User', id: 'u1' },
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -296,28 +303,28 @@ describe('block', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unblock', () => {
|
describe('unmuteUser', () => {
|
||||||
let unblockAction
|
let unmuteAction
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
currentUser = undefined
|
currentUser = undefined
|
||||||
unblockAction = variables => {
|
unmuteAction = variables => {
|
||||||
const { mutate } = createTestClient(server)
|
const { mutate } = createTestClient(server)
|
||||||
const unblockMutation = gql`
|
const unmuteUserMutation = gql`
|
||||||
mutation($id: ID!) {
|
mutation($id: ID!) {
|
||||||
unblock(id: $id) {
|
unmuteUser(id: $id) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
isBlocked
|
isMuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
return mutate({ mutation: unblockMutation, variables })
|
return mutate({ mutation: unmuteUserMutation, variables })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws permission error', async () => {
|
it('throws permission error', async () => {
|
||||||
const result = await unblockAction({ id: 'u2' })
|
const result = await unmuteAction({ id: 'u2' })
|
||||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -330,59 +337,69 @@ describe('unblock', () => {
|
|||||||
authenticatedUser = await currentUser.toJson()
|
authenticatedUser = await currentUser.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unblock yourself', () => {
|
describe('unmute yourself', () => {
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(unblockAction({ id: 'u1' })).resolves.toEqual(
|
await expect(unmuteAction({ id: 'u1' })).resolves.toEqual(
|
||||||
expect.objectContaining({ data: { unblock: null } }),
|
expect.objectContaining({ data: { unmuteUser: null } }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unblock not-existing user', () => {
|
describe('unmute not-existing user', () => {
|
||||||
it('returns null', async () => {
|
it('returns null', async () => {
|
||||||
await expect(unblockAction({ id: 'lksjdflksfdj' })).resolves.toEqual(
|
await expect(unmuteAction({ id: 'lksjdflksfdj' })).resolves.toEqual(
|
||||||
expect.objectContaining({ data: { unblock: null } }),
|
expect.objectContaining({ data: { unmuteUser: null } }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given another user', () => {
|
describe('given another user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
blockedUser = await neode.create('User', {
|
mutedUser = await neode.create('User', {
|
||||||
name: 'Blocked User',
|
name: 'Muted User',
|
||||||
id: 'u2',
|
id: 'u2',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unblocking a not yet blocked user', () => {
|
describe('unmuting a not yet muted user', () => {
|
||||||
it('does not hurt', async () => {
|
it('does not hurt', async () => {
|
||||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
await expect(unmuteAction({ id: 'u2' })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
data: {
|
||||||
|
unmuteUser: { id: 'u2', name: 'Muted User', isMuted: false },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given a blocked user', () => {
|
describe('given a muted user', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await currentUser.relateTo(blockedUser, 'blocked')
|
await currentUser.relateTo(mutedUser, 'muted')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('unblocks a user', async () => {
|
it('unmutes a user', async () => {
|
||||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
await expect(unmuteAction({ id: 'u2' })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
data: {
|
||||||
|
unmuteUser: { id: 'u2', name: 'Muted User', isMuted: false },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unblocking twice', () => {
|
describe('unmuting twice', () => {
|
||||||
it('has no effect', async () => {
|
it('has no effect', async () => {
|
||||||
await unblockAction({ id: 'u2' })
|
await unmuteAction({ id: 'u2' })
|
||||||
await expect(unblockAction({ id: 'u2' })).resolves.toEqual(
|
await expect(unmuteAction({ id: 'u2' })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { unblock: { id: 'u2', name: 'Blocked User', isBlocked: false } },
|
data: {
|
||||||
|
unmuteUser: {
|
||||||
|
id: 'u2',
|
||||||
|
name: 'Muted User',
|
||||||
|
isMuted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1,30 +1,5 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { mergeTypes } from 'merge-graphql-schemas'
|
import { mergeTypes, fileLoader } from 'merge-graphql-schemas'
|
||||||
|
|
||||||
const findGqlFiles = dir => {
|
|
||||||
var results = []
|
|
||||||
var list = fs.readdirSync(dir)
|
|
||||||
list.forEach(file => {
|
|
||||||
file = path.join(dir, file).toString('utf-8')
|
|
||||||
var stat = fs.statSync(file)
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
// Recurse into a subdirectory
|
|
||||||
results = results.concat(findGqlFiles(file))
|
|
||||||
} else {
|
|
||||||
if (path.extname(file) === '.gql') {
|
|
||||||
// Is a gql file
|
|
||||||
results.push(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeDefs = []
|
|
||||||
|
|
||||||
findGqlFiles(__dirname).forEach(file => {
|
|
||||||
typeDefs.push(fs.readFileSync(file).toString('utf-8'))
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const typeDefs = fileLoader(path.join(__dirname, './**/*.gql'))
|
||||||
export default mergeTypes(typeDefs, { all: true })
|
export default mergeTypes(typeDefs, { all: true })
|
||||||
|
|||||||
@ -75,6 +75,13 @@ type User {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
isMuted: Boolean! @cypher(
|
||||||
|
statement: """
|
||||||
|
MATCH (this)<-[:MUTED]-(user:User { id: $cypherParams.currentUserId})
|
||||||
|
RETURN COUNT(user) >= 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# contributions: [WrittenPost]!
|
# contributions: [WrittenPost]!
|
||||||
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||||
# @cypher(
|
# @cypher(
|
||||||
@ -160,6 +167,7 @@ type Query {
|
|||||||
filter: _UserFilter
|
filter: _UserFilter
|
||||||
): [User]
|
): [User]
|
||||||
|
|
||||||
|
mutedUsers: [User]
|
||||||
blockedUsers: [User]
|
blockedUsers: [User]
|
||||||
isLoggedIn: Boolean!
|
isLoggedIn: Boolean!
|
||||||
currentUser: User
|
currentUser: User
|
||||||
@ -197,7 +205,8 @@ type Mutation {
|
|||||||
|
|
||||||
DeleteUser(id: ID!, resource: [Deletable]): User
|
DeleteUser(id: ID!, resource: [Deletable]): User
|
||||||
|
|
||||||
|
muteUser(id: ID!): User
|
||||||
|
unmuteUser(id: ID!): User
|
||||||
block(id: ID!): User
|
block(id: ID!): User
|
||||||
unblock(id: ID!): User
|
unblock(id: ID!): User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,18 @@
|
|||||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||||
import createBadge from './badges.js'
|
|
||||||
import createUser from './users.js'
|
|
||||||
import createPost from './posts.js'
|
|
||||||
import createComment from './comments.js'
|
|
||||||
import createCategory from './categories.js'
|
|
||||||
import createTag from './tags.js'
|
|
||||||
import createSocialMedia from './socialMedia.js'
|
|
||||||
import createLocation from './locations.js'
|
|
||||||
import createEmailAddress from './emailAddresses.js'
|
|
||||||
import createDonations from './donations.js'
|
|
||||||
import createUnverifiedEmailAddresss from './unverifiedEmailAddresses.js'
|
|
||||||
import createReport from './reports.js'
|
|
||||||
|
|
||||||
const factories = {
|
const factories = {
|
||||||
Badge: createBadge,
|
Badge: require('./badges.js').default,
|
||||||
User: createUser,
|
User: require('./users.js').default,
|
||||||
Post: createPost,
|
Post: require('./posts.js').default,
|
||||||
Comment: createComment,
|
Comment: require('./comments.js').default,
|
||||||
Category: createCategory,
|
Category: require('./categories.js').default,
|
||||||
Tag: createTag,
|
Tag: require('./tags.js').default,
|
||||||
SocialMedia: createSocialMedia,
|
SocialMedia: require('./socialMedia.js').default,
|
||||||
Location: createLocation,
|
Location: require('./locations.js').default,
|
||||||
EmailAddress: createEmailAddress,
|
EmailAddress: require('./emailAddresses.js').default,
|
||||||
UnverifiedEmailAddress: createUnverifiedEmailAddresss,
|
UnverifiedEmailAddress: require('./unverifiedEmailAddresses.js').default,
|
||||||
Donations: createDonations,
|
Donations: require('./donations.js').default,
|
||||||
Report: createReport,
|
Report: require('./reports.js').default,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cleanDatabase = async (options = {}) => {
|
export const cleanDatabase = async (options = {}) => {
|
||||||
|
|||||||
@ -36,7 +36,6 @@ export default function create() {
|
|||||||
if (categoryIds)
|
if (categoryIds)
|
||||||
categories = await Promise.all(categoryIds.map(id => neodeInstance.find('Category', id)))
|
categories = await Promise.all(categoryIds.map(id => neodeInstance.find('Category', id)))
|
||||||
categories = categories || (await Promise.all([factoryInstance.create('Category')]))
|
categories = categories || (await Promise.all([factoryInstance.create('Category')]))
|
||||||
|
|
||||||
const { tagIds = [] } = args
|
const { tagIds = [] } = args
|
||||||
delete args.tags
|
delete args.tags
|
||||||
const tags = await Promise.all(
|
const tags = await Promise.all(
|
||||||
|
|||||||
@ -226,6 +226,10 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
|||||||
dewey.relateTo(huey, 'following'),
|
dewey.relateTo(huey, 'following'),
|
||||||
louie.relateTo(jennyRostock, 'following'),
|
louie.relateTo(jennyRostock, 'following'),
|
||||||
|
|
||||||
|
huey.relateTo(dagobert, 'muted'),
|
||||||
|
dewey.relateTo(dagobert, 'muted'),
|
||||||
|
louie.relateTo(dagobert, 'muted'),
|
||||||
|
|
||||||
dagobert.relateTo(huey, 'blocked'),
|
dagobert.relateTo(huey, 'blocked'),
|
||||||
dagobert.relateTo(dewey, 'blocked'),
|
dagobert.relateTo(dewey, 'blocked'),
|
||||||
dagobert.relateTo(louie, 'blocked'),
|
dagobert.relateTo(louie, 'blocked'),
|
||||||
|
|||||||
@ -1,21 +1,13 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import helmet from 'helmet'
|
import helmet from 'helmet'
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import CONFIG, { requiredConfigs } from './config'
|
import CONFIG from './config'
|
||||||
import middleware from './middleware'
|
import middleware from './middleware'
|
||||||
import { getNeode, getDriver } from './bootstrap/neo4j'
|
import { getNeode, getDriver } from './bootstrap/neo4j'
|
||||||
import decode from './jwt/decode'
|
import decode from './jwt/decode'
|
||||||
import schema from './schema'
|
import schema from './schema'
|
||||||
import webfinger from './activitypub/routes/webfinger'
|
import webfinger from './activitypub/routes/webfinger'
|
||||||
|
|
||||||
// check required configs and throw error
|
|
||||||
// TODO check this directly in config file - currently not possible due to testsetup
|
|
||||||
Object.entries(requiredConfigs).map(entry => {
|
|
||||||
if (!entry[1]) {
|
|
||||||
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const neode = getNeode()
|
const neode = getNeode()
|
||||||
|
|
||||||
|
|||||||
1830
backend/yarn.lock
1830
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -24,10 +24,10 @@ Then("my comment should be successfully created", () => {
|
|||||||
Then("I should see my comment", () => {
|
Then("I should see my comment", () => {
|
||||||
cy.get("div.comment p")
|
cy.get("div.comment p")
|
||||||
.should("contain", "Human Connection rocks")
|
.should("contain", "Human Connection rocks")
|
||||||
.get(".ds-avatar img")
|
.get(".user-avatar img")
|
||||||
.should("have.attr", "src")
|
.should("have.attr", "src")
|
||||||
.and("contain", narratorAvatar)
|
.and("contain", narratorAvatar)
|
||||||
.get("div p.ds-text span")
|
.get(".user-teaser > .info > .text")
|
||||||
.should("contain", "today at");
|
.should("contain", "today at");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -32,5 +32,5 @@ Then("I cannot upload a picture", () => {
|
|||||||
cy.get(".ds-card-content")
|
cy.get(".ds-card-content")
|
||||||
.children()
|
.children()
|
||||||
.should("not.have.id", "customdropzone")
|
.should("not.have.id", "customdropzone")
|
||||||
.should("have.class", "ds-avatar");
|
.should("have.class", "user-avatar");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { gql } from '../../../backend/src/helpers/jest'
|
|||||||
let lastReportTitle
|
let lastReportTitle
|
||||||
let davidIrvingPostTitle = 'The Truth about the Holocaust'
|
let davidIrvingPostTitle = 'The Truth about the Holocaust'
|
||||||
let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
|
let davidIrvingPostSlug = 'the-truth-about-the-holocaust'
|
||||||
let annoyingUserWhoBlockedModeratorTitle = 'Fake news'
|
let annoyingUserWhoMutedModeratorTitle = 'Fake news'
|
||||||
|
|
||||||
const savePostTitle = $post => {
|
const savePostTitle = $post => {
|
||||||
return $post
|
return $post
|
||||||
@ -63,7 +63,7 @@ When('I click on "Report User" from the content menu in the user info box', () =
|
|||||||
})
|
})
|
||||||
|
|
||||||
When('I click on the author', () => {
|
When('I click on the author', () => {
|
||||||
cy.get('.username')
|
cy.get('.user-teaser')
|
||||||
.click()
|
.click()
|
||||||
.url().should('include', '/profile/')
|
.url().should('include', '/profile/')
|
||||||
})
|
})
|
||||||
@ -147,9 +147,9 @@ Then('I see all the reported posts including the one from above', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Then('I see all the reported posts including from the user who blocked me', () => {
|
Then('I see all the reported posts including from the user who muted me', () => {
|
||||||
cy.get('table tbody').within(() => {
|
cy.get('table tbody').within(() => {
|
||||||
cy.contains('tr', annoyingUserWhoBlockedModeratorTitle)
|
cy.contains('tr', annoyingUserWhoMutedModeratorTitle)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -159,9 +159,9 @@ Then('each list item links to the post page', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Then('I can visit the post page', () => {
|
Then('I can visit the post page', () => {
|
||||||
cy.contains(annoyingUserWhoBlockedModeratorTitle).click()
|
cy.contains(annoyingUserWhoMutedModeratorTitle).click()
|
||||||
cy.location('pathname').should('contain', '/post')
|
cy.location('pathname').should('contain', '/post')
|
||||||
.get('h3').should('contain', annoyingUserWhoBlockedModeratorTitle)
|
.get('h3').should('contain', annoyingUserWhoMutedModeratorTitle)
|
||||||
})
|
})
|
||||||
|
|
||||||
When("they have a post someone has reported", () => {
|
When("they have a post someone has reported", () => {
|
||||||
|
|||||||
@ -87,7 +87,7 @@ Then(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Then("I select a user entry", () => {
|
Then("I select a user entry", () => {
|
||||||
cy.get(".searchable-input .userinfo")
|
cy.get(".searchable-input .user-teaser")
|
||||||
.first()
|
.first()
|
||||||
.trigger("click");
|
.trigger("click");
|
||||||
})
|
})
|
||||||
|
|||||||
@ -29,10 +29,20 @@ const narratorParams = {
|
|||||||
...termsAndConditionsAgreedVersion,
|
...termsAndConditionsAgreedVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const annoyingParams = {
|
||||||
|
email: "spammy-spammer@example.org",
|
||||||
|
password: "1234",
|
||||||
|
...termsAndConditionsAgreedVersion
|
||||||
|
};
|
||||||
|
|
||||||
Given("I am logged in", () => {
|
Given("I am logged in", () => {
|
||||||
cy.login(loginCredentials);
|
cy.login(loginCredentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Given("I am logged in as the muted user", () => {
|
||||||
|
cy.login({ email: annoyingParams.email, password: '1234' });
|
||||||
|
});
|
||||||
|
|
||||||
Given("we have a selection of categories", () => {
|
Given("we have a selection of categories", () => {
|
||||||
cy.createCategories("cat0", "just-for-fun");
|
cy.createCategories("cat0", "just-for-fun");
|
||||||
});
|
});
|
||||||
@ -227,7 +237,6 @@ Given("I previously created a post", () => {
|
|||||||
lastPost.authorId = narratorParams.id
|
lastPost.authorId = narratorParams.id
|
||||||
lastPost.title = "previously created post";
|
lastPost.title = "previously created post";
|
||||||
lastPost.content = "with some content";
|
lastPost.content = "with some content";
|
||||||
lastPost.categoryIds = ["cat0"];
|
|
||||||
cy.factory()
|
cy.factory()
|
||||||
.create("Post", lastPost);
|
.create("Post", lastPost);
|
||||||
});
|
});
|
||||||
@ -407,11 +416,6 @@ Then("there are no notifications in the top menu", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Given("there is an annoying user called {string}", name => {
|
Given("there is an annoying user called {string}", name => {
|
||||||
const annoyingParams = {
|
|
||||||
email: "spammy-spammer@example.org",
|
|
||||||
password: "1234",
|
|
||||||
...termsAndConditionsAgreedVersion
|
|
||||||
};
|
|
||||||
cy.factory().create("User", {
|
cy.factory().create("User", {
|
||||||
...annoyingParams,
|
...annoyingParams,
|
||||||
id: "annoying-user",
|
id: "annoying-user",
|
||||||
@ -420,17 +424,17 @@ Given("there is an annoying user called {string}", name => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Given("there is an annoying user who has blocked me", () => {
|
Given("there is an annoying user who has muted me", () => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first("User", {
|
.first("User", {
|
||||||
role: 'moderator'
|
role: 'moderator'
|
||||||
})
|
})
|
||||||
.then(blocked => {
|
.then(mutedUser => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first("User", {
|
.first("User", {
|
||||||
id: 'annoying-user'
|
id: 'annoying-user'
|
||||||
})
|
})
|
||||||
.relateTo(blocked, "blocked");
|
.relateTo(mutedUser, "muted");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -459,7 +463,7 @@ When(
|
|||||||
);
|
);
|
||||||
|
|
||||||
When("I navigate to my {string} settings page", settingsPage => {
|
When("I navigate to my {string} settings page", settingsPage => {
|
||||||
cy.get(".avatar-menu").click();
|
cy.get(".avatar-menu-trigger").click();
|
||||||
cy.get(".avatar-menu-popover")
|
cy.get(".avatar-menu-popover")
|
||||||
.find("a[href]")
|
.find("a[href]")
|
||||||
.contains("Settings")
|
.contains("Settings")
|
||||||
@ -514,17 +518,17 @@ Given("I wrote a post {string}", title => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
When("I block the user {string}", name => {
|
When("I mute the user {string}", name => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first("User", {
|
.first("User", {
|
||||||
name
|
name
|
||||||
})
|
})
|
||||||
.then(blocked => {
|
.then(mutedUser => {
|
||||||
cy.neode()
|
cy.neode()
|
||||||
.first("User", {
|
.first("User", {
|
||||||
name: narratorParams.name
|
name: narratorParams.name
|
||||||
})
|
})
|
||||||
.relateTo(blocked, "blocked");
|
.relateTo(mutedUser, "muted");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ Feature: Report and Moderate
|
|||||||
Given we have the following user accounts:
|
Given we have the following user accounts:
|
||||||
| id | name |
|
| id | name |
|
||||||
| u67 | David Irving |
|
| u67 | David Irving |
|
||||||
| annoying-user | I'm gonna block Moderators and Admins HA HA HA |
|
| annoying-user | I'm gonna mute Moderators and Admins HA HA HA |
|
||||||
|
|
||||||
Given we have the following posts in our database:
|
Given we have the following posts in our database:
|
||||||
| authorId | id | title | content |
|
| authorId | id | title | content |
|
||||||
@ -58,16 +58,16 @@ Feature: Report and Moderate
|
|||||||
Then I see all the reported posts including the one from above
|
Then I see all the reported posts including the one from above
|
||||||
And each list item links to the post page
|
And each list item links to the post page
|
||||||
|
|
||||||
Scenario: Review reported posts of a user who's blocked a moderator
|
Scenario: Review reported posts of a user who's muted a moderator
|
||||||
Given somebody reported the following posts:
|
Given somebody reported the following posts:
|
||||||
| submitterEmail | resourceId | reasonCategory | reasonDescription |
|
| submitterEmail | resourceId | reasonCategory | reasonDescription |
|
||||||
| p2.submitter@example.org | p2 | other | Offensive content |
|
| p2.submitter@example.org | p2 | other | Offensive content |
|
||||||
And my user account has the role "moderator"
|
And my user account has the role "moderator"
|
||||||
And there is an annoying user who has blocked me
|
And there is an annoying user who has muted me
|
||||||
And I am logged in
|
And I am logged in
|
||||||
When I click on the avatar menu in the top right corner
|
When I click on the avatar menu in the top right corner
|
||||||
And I click on "Moderation"
|
And I click on "Moderation"
|
||||||
Then I see all the reported posts including from the user who blocked me
|
Then I see all the reported posts including from the user who muted me
|
||||||
And I can visit the post page
|
And I can visit the post page
|
||||||
|
|
||||||
Scenario: Normal user can't see the moderation page
|
Scenario: Normal user can't see the moderation page
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
Feature: Block a User
|
|
||||||
As a user
|
|
||||||
I'd like to have a button to block another user
|
|
||||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
|
||||||
|
|
||||||
Background:
|
|
||||||
Given I have a user account
|
|
||||||
And there is an annoying user called "Spammy Spammer"
|
|
||||||
|
|
||||||
Scenario Outline: Blocked users cannot see each others posts
|
|
||||||
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
|
||||||
And I wrote a post "I hate spammers"
|
|
||||||
And I block the user "Spammy Spammer"
|
|
||||||
When I log in with:
|
|
||||||
| Email | Password |
|
|
||||||
| <email> | <password> |
|
|
||||||
Then I see only one post with the title "<expected_title>"
|
|
||||||
Examples:
|
|
||||||
| email | password | expected_title |
|
|
||||||
| peterpan@example.org | 1234 | I hate spammers |
|
|
||||||
| spammy-spammer@example.org | 1234 | Spam Spam Spam |
|
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
Feature: Block a User
|
Feature: Mute a User
|
||||||
As a user
|
As a user
|
||||||
I'd like to have a button to block another user
|
I'd like to have a button to mute another user
|
||||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
@ -8,36 +8,46 @@ Feature: Block a User
|
|||||||
And there is an annoying user called "Spammy Spammer"
|
And there is an annoying user called "Spammy Spammer"
|
||||||
And I am logged in
|
And I am logged in
|
||||||
|
|
||||||
Scenario: Block a user
|
Scenario: Mute a user
|
||||||
Given I am on the profile page of the annoying user
|
Given I am on the profile page of the annoying user
|
||||||
When I click on "Block user" from the content menu in the user info box
|
When I click on "Mute user" from the content menu in the user info box
|
||||||
And I navigate to my "Blocked users" settings page
|
And I navigate to my "Muted users" settings page
|
||||||
Then I can see the following table:
|
Then I can see the following table:
|
||||||
| Avatar | Name |
|
| Avatar | Name |
|
||||||
| | Spammy Spammer |
|
| | Spammy Spammer |
|
||||||
|
|
||||||
Scenario: Block a previously followed user
|
Scenario: Mute a previously followed user
|
||||||
Given I follow the user "Spammy Spammer"
|
Given I follow the user "Spammy Spammer"
|
||||||
And "Spammy Spammer" wrote a post "Spam Spam Spam"
|
And "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||||
When I visit the profile page of the annoying user
|
When I visit the profile page of the annoying user
|
||||||
And I click on "Block user" from the content menu in the user info box
|
And I click on "Mute user" from the content menu in the user info box
|
||||||
Then the list of posts of this user is empty
|
Then the list of posts of this user is empty
|
||||||
And nobody is following the user profile anymore
|
And nobody is following the user profile anymore
|
||||||
|
|
||||||
Scenario: Posts of blocked users are filtered from search results
|
Scenario: Posts of muted users are filtered from search results
|
||||||
Given we have the following posts in our database:
|
Given we have the following posts in our database:
|
||||||
| id | title | content |
|
| id | title | content |
|
||||||
| im-not-blocked | Post that should be seen | cause I'm not blocked |
|
| im-not-muted | Post that should be seen | cause I'm not muted |
|
||||||
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
Given "Spammy Spammer" wrote a post "Spam Spam Spam"
|
||||||
When I search for "Spam"
|
When I search for "Spam"
|
||||||
Then I should see the following posts in the select dropdown:
|
Then I should see the following posts in the select dropdown:
|
||||||
| title |
|
| title |
|
||||||
| Spam Spam Spam |
|
| Spam Spam Spam |
|
||||||
When I block the user "Spammy Spammer"
|
When I mute the user "Spammy Spammer"
|
||||||
And I refresh the page
|
And I refresh the page
|
||||||
And I search for "Spam"
|
And I search for "Spam"
|
||||||
Then the search has no results
|
Then the search has no results
|
||||||
But I search for "not blocked"
|
But I search for "not muted"
|
||||||
Then I should see the following posts in the select dropdown:
|
Then I should see the following posts in the select dropdown:
|
||||||
| title |
|
| title |
|
||||||
| Post that should be seen |
|
| Post that should be seen |
|
||||||
|
|
||||||
|
Scenario: Muted users can still see my posts
|
||||||
|
Given I previously created a post
|
||||||
|
And I mute the user "Spammy Spammer"
|
||||||
|
Given I log out
|
||||||
|
And I am logged in as the muted user
|
||||||
|
When I search for "previously created"
|
||||||
|
Then I should see the following posts in the select dropdown:
|
||||||
|
| title |
|
||||||
|
| previously created post |
|
||||||
@ -11,10 +11,19 @@
|
|||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
// the project's config changing)
|
// the project's config changing)
|
||||||
|
|
||||||
|
|
||||||
const cucumber = require('cypress-cucumber-preprocessor').default
|
const cucumber = require('cypress-cucumber-preprocessor').default
|
||||||
module.exports = on => {
|
const dotenv = require('dotenv')
|
||||||
|
|
||||||
|
module.exports = (on, config) => {
|
||||||
// (on, config) => {
|
// (on, config) => {
|
||||||
// `on` is used to hook into various events Cypress emits
|
// `on` is used to hook into various events Cypress emits
|
||||||
// `config` is the resolved Cypress config
|
// `config` is the resolved Cypress config
|
||||||
|
const { parsed } = dotenv.config({ path: require.resolve('../../backend/.env') })
|
||||||
|
config.env.NEO4J_URI = parsed.NEO4J_URI
|
||||||
|
config.env.NEO4J_USERNAME = parsed.NEO4J_USERNAME
|
||||||
|
config.env.NEO4J_PASSWORD = parsed.NEO4J_PASSWORD
|
||||||
|
|
||||||
on('file:preprocessor', cucumber())
|
on('file:preprocessor', cucumber())
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ Cypress.Commands.add("login", ({ email, password }) => {
|
|||||||
.as("submitButton")
|
.as("submitButton")
|
||||||
.click();
|
.click();
|
||||||
cy.get(".iziToast-message").should("contain", "You are logged in!");
|
cy.get(".iziToast-message").should("contain", "You are logged in!");
|
||||||
cy.get(".iziToast-close").click();
|
cy.location("pathname").should("eq", "/");
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("logout", (email, password) => {
|
Cypress.Commands.add("logout", (email, password) => {
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import Factory from '../../backend/src/seed/factories'
|
import Factory from '../../backend/src/seed/factories'
|
||||||
import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
|
import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||||
import neode from 'neode'
|
|
||||||
|
|
||||||
const neo4jDriver = getDriver()
|
const neo4jConfigs = {
|
||||||
const neodeInstance = getNeode()
|
uri: Cypress.env('NEO4J_URI'),
|
||||||
|
username: Cypress.env('NEO4J_USERNAME'),
|
||||||
|
password: Cypress.env('NEO4J_PASSWORD')
|
||||||
|
}
|
||||||
|
const neo4jDriver = getDriver(neo4jConfigs)
|
||||||
|
const neodeInstance = getNeode(neo4jConfigs)
|
||||||
const factoryOptions = { neo4jDriver, neodeInstance }
|
const factoryOptions = { neo4jDriver, neodeInstance }
|
||||||
const factory = Factory(factoryOptions)
|
const factory = Factory(factoryOptions)
|
||||||
|
|
||||||
|
|||||||
43
deployment/monitoring/README.md
Normal file
43
deployment/monitoring/README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Metrics
|
||||||
|
|
||||||
|
You can optionally setup [prometheus](https://prometheus.io/) and
|
||||||
|
[grafana](https://grafana.com/) for metrics.
|
||||||
|
|
||||||
|
We follow this tutorial [here](https://medium.com/@chris_linguine/how-to-monitor-your-kubernetes-cluster-with-prometheus-and-grafana-2d5704187fc8):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl proxy # proxy to your kubernetes dashboard
|
||||||
|
|
||||||
|
helm repo list
|
||||||
|
# If using helm v3, the stable repository is not set, so you need to manually add it.
|
||||||
|
helm repo add stable https://kubernetes-charts.storage.googleapis.com
|
||||||
|
# Create a monitoring namespace for your cluster
|
||||||
|
kubectl create namespace monitoring
|
||||||
|
helm --namespace monitoring install prometheus stable/prometheus
|
||||||
|
kubectl -n monitoring get pods # look for 'server'
|
||||||
|
kubectl port-forward -n monitoring <PROMETHEUS_SERVER_ID> 9090
|
||||||
|
# You can now see your prometheus server on: http://localhost:9090
|
||||||
|
|
||||||
|
# Make sure you are in folder `deployment/`
|
||||||
|
kubectl apply -f monitoring/grafana/config.yml
|
||||||
|
helm --namespace monitoring install grafana stable/grafana -f monitoring/grafana/values.yml
|
||||||
|
# Get the admin password for grafana from your kubernetes dashboard.
|
||||||
|
kubectl --namespace monitoring port-forward <POD_NAME> 3000
|
||||||
|
# You can now see your grafana dashboard on: http://localhost:3000
|
||||||
|
# Login with user 'admin' and the password you just looked up.
|
||||||
|
# In your dashboard import this dashboard:
|
||||||
|
# https://grafana.com/grafana/dashboards/1860
|
||||||
|
# Enter ID 180 and choose "Prometheus" as datasource.
|
||||||
|
# You got metrics!
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you should see something like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can set up a grafana dashboard, by visiting https://grafana.com/dashboards, finding one that is suitable and copying it's id.
|
||||||
|
You then go to the left hand menu in localhost, choose `Dashboard` > `Manage` > `Import`
|
||||||
|
Paste in the id, click `Load`, select `Prometheus` for the data source, and click `Import`
|
||||||
|
|
||||||
|
When you just installed prometheus and grafana, the data will not be available
|
||||||
|
immediately, so wait for a couple of minutes and reload.
|
||||||
16
deployment/monitoring/grafana/config.yml
Normal file
16
deployment/monitoring/grafana/config.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: prometheus-grafana-datasource
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
grafana_datasource: '1'
|
||||||
|
data:
|
||||||
|
datasource.yaml: |-
|
||||||
|
apiVersion: 1
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
orgId: 1
|
||||||
|
url: http://prometheus-server.monitoring.svc.cluster.local
|
||||||
BIN
deployment/monitoring/grafana/metrics.png
Normal file
BIN
deployment/monitoring/grafana/metrics.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
4
deployment/monitoring/grafana/values.yml
Normal file
4
deployment/monitoring/grafana/values.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
sidecar:
|
||||||
|
datasources:
|
||||||
|
enabled: true
|
||||||
|
label: grafana_datasource
|
||||||
17
package.json
17
package.json
@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "human-connection",
|
"name": "human-connection",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
|
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
|
||||||
"author": "Human Connection gGmbh",
|
"author": "Human Connection gGmbh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Human-Connection/Human-Connection.git"
|
||||||
|
},
|
||||||
"cypress-cucumber-preprocessor": {
|
"cypress-cucumber-preprocessor": {
|
||||||
"nonGlobalStepDefinitions": true
|
"nonGlobalStepDefinitions": true
|
||||||
},
|
},
|
||||||
@ -14,11 +18,11 @@
|
|||||||
"cypress:backend": "cd backend && yarn run dev",
|
"cypress:backend": "cd backend && yarn run dev",
|
||||||
"cypress:webapp": "cd webapp && yarn run dev",
|
"cypress:webapp": "cd webapp && yarn run dev",
|
||||||
"cypress:setup": "run-p cypress:backend cypress:webapp",
|
"cypress:setup": "run-p cypress:backend cypress:webapp",
|
||||||
"cypress:run": "cross-env cypress run --browser chromium",
|
"cypress:run": "cross-env cypress run",
|
||||||
"cypress:open": "cross-env cypress open --browser chromium",
|
"cypress:open": "cross-env cypress open",
|
||||||
"cucumber:setup": "cd backend && yarn run dev",
|
"cucumber:setup": "cd backend && yarn run dev",
|
||||||
"cucumber": "wait-on tcp:4000 && cucumber-js --require-module @babel/register --exit",
|
"cucumber": "wait-on tcp:4000 && cucumber-js --require-module @babel/register --exit",
|
||||||
"version": "auto-changelog -p"
|
"release": "standard-version"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.8.3",
|
"@babel/core": "^7.8.3",
|
||||||
@ -35,13 +39,14 @@
|
|||||||
"cypress-plugin-retries": "^1.5.2",
|
"cypress-plugin-retries": "^1.5.2",
|
||||||
"date-fns": "^2.9.0",
|
"date-fns": "^2.9.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"expect": "^24.9.0",
|
"expect": "^25.1.0",
|
||||||
"faker": "Marak/faker.js#master",
|
"faker": "Marak/faker.js#master",
|
||||||
"graphql-request": "^1.8.2",
|
"graphql-request": "^1.8.2",
|
||||||
"neo4j-driver": "^4.0.1",
|
"neo4j-driver": "^4.0.1",
|
||||||
"neode": "^0.3.7",
|
"neode": "^0.3.7",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"slug": "^2.1.0"
|
"slug": "^2.1.0",
|
||||||
|
"standard-version": "^7.1.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"set-value": "^2.0.1"
|
"set-value": "^2.0.1"
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
ROOT_DIR=$(dirname "$0")/..
|
ROOT_DIR=$(dirname "$0")/..
|
||||||
# BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)}
|
# BUILD_COMMIT=${TRAVIS_COMMIT:-$(git rev-parse HEAD)}
|
||||||
|
|
||||||
IFS='.' read -r major minor patch < $ROOT_DIR/VERSION
|
VERSION=$(jq -r '.version' $ROOT_DIR/package.json)
|
||||||
|
IFS='.' read -r major minor patch <<< $VERSION
|
||||||
apps=(nitro-web nitro-backend neo4j maintenance)
|
apps=(nitro-web nitro-backend neo4j maintenance)
|
||||||
tags=($major $major.$minor $major.$minor.$patch)
|
tags=($major $major.$minor $major.$minor.$patch)
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
ROOT_DIR=$(dirname "$0")/..
|
ROOT_DIR=$(dirname "$0")/..
|
||||||
RELEASE_DIR="${ROOT_DIR}/release"
|
RELEASE_DIR="${ROOT_DIR}/release"
|
||||||
|
|
||||||
VERSION=$(<$ROOT_DIR/VERSION)
|
VERSION=$(jq -r ".version" $ROOT_DIR/package.json)
|
||||||
|
|
||||||
# mkdir -p $RELEASE_DIR
|
# mkdir -p $RELEASE_DIR
|
||||||
|
|
||||||
@ -13,4 +13,4 @@ VERSION=$(<$ROOT_DIR/VERSION)
|
|||||||
# docker image save "humanconnection/${app}:latest" | gzip > "${RELEASE_DIR}/${app}.${VERSION}.tar.gz"
|
# docker image save "humanconnection/${app}:latest" | gzip > "${RELEASE_DIR}/${app}.${VERSION}.tar.gz"
|
||||||
# done
|
# done
|
||||||
|
|
||||||
ghr -soft "${VERSION}"
|
ghr -c "${VERSION}" "${VERSION}"
|
||||||
|
|||||||
@ -240,6 +240,13 @@ $size-height-xlarge: 60px;
|
|||||||
$size-height-footer: 64px;
|
$size-height-footer: 64px;
|
||||||
$size-tappable-square: 44px;
|
$size-tappable-square: 44px;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @tokens Size Width
|
||||||
|
* @presenter Spacing
|
||||||
|
*/
|
||||||
|
|
||||||
|
$size-width-paginate: 100px;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @tokens Size Avatar
|
* @tokens Size Avatar
|
||||||
* @presenter Spacing
|
* @presenter Spacing
|
||||||
@ -247,8 +254,7 @@ $size-tappable-square: 44px;
|
|||||||
|
|
||||||
$size-avatar-small: 34px;
|
$size-avatar-small: 34px;
|
||||||
$size-avatar-base: 44px;
|
$size-avatar-base: 44px;
|
||||||
$size-avatar-large: 64px;
|
$size-avatar-large: 114px;
|
||||||
$size-avatar-x-large: 114px;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @tokens Size Buttons
|
* @tokens Size Buttons
|
||||||
@ -259,7 +265,7 @@ $size-avatar-x-large: 114px;
|
|||||||
$size-button-small: 26px;
|
$size-button-small: 26px;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @tokens Size Buttons
|
* @tokens Size Icons
|
||||||
* @presenter Spacing
|
* @presenter Spacing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -175,3 +175,7 @@ hr {
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
font-size: $font-size-xx-small;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import Avatar from './Avatar.vue'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
describe('Avatar.vue', () => {
|
|
||||||
let propsData = {}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(Avatar, { propsData, localVue })
|
|
||||||
}
|
|
||||||
|
|
||||||
it('renders no image', () => {
|
|
||||||
expect(
|
|
||||||
Wrapper()
|
|
||||||
.find('img')
|
|
||||||
.exists(),
|
|
||||||
).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
// this is testing the style guide
|
|
||||||
it('renders an icon', () => {
|
|
||||||
expect(
|
|
||||||
Wrapper()
|
|
||||||
.find('.ds-icon')
|
|
||||||
.exists(),
|
|
||||||
).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given a user', () => {
|
|
||||||
describe('with a relative avatar url', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
propsData = {
|
|
||||||
user: {
|
|
||||||
avatar: '/avatar.jpg',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('adds a prefix to load the image from the uploads service', () => {
|
|
||||||
expect(
|
|
||||||
Wrapper()
|
|
||||||
.find('img')
|
|
||||||
.attributes('src'),
|
|
||||||
).toBe('/api/avatar.jpg')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with an absolute avatar url', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
propsData = {
|
|
||||||
user: {
|
|
||||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('keeps the avatar URL as is', () => {
|
|
||||||
// e.g. our seeds have absolute image URLs
|
|
||||||
expect(
|
|
||||||
Wrapper()
|
|
||||||
.find('img')
|
|
||||||
.attributes('src'),
|
|
||||||
).toBe('https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ds-avatar
|
|
||||||
:image="user && user.avatar | proxyApiUrl"
|
|
||||||
:name="userName"
|
|
||||||
class="avatar"
|
|
||||||
:size="size"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'HcAvatar',
|
|
||||||
props: {
|
|
||||||
user: { type: Object, default: null },
|
|
||||||
size: { type: String, default: 'small' },
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
userName() {
|
|
||||||
const { name } = this.user || {}
|
|
||||||
// The name is used to display the initials in case
|
|
||||||
// the image cannot be loaded.
|
|
||||||
return name
|
|
||||||
// If the name is undefined, then our styleguide will
|
|
||||||
// display an icon for the anonymous user.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -42,9 +42,9 @@ describe('AvatarMenu.vue', () => {
|
|||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the HcAvatar component', () => {
|
it('renders the UserAvatar component', () => {
|
||||||
wrapper.find('.avatar-menu-trigger').trigger('click')
|
wrapper.find('.avatar-menu-trigger').trigger('click')
|
||||||
expect(wrapper.find('.ds-avatar').exists()).toBe(true)
|
expect(wrapper.find('.user-avatar').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given a userName', () => {
|
describe('given a userName', () => {
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"
|
"
|
||||||
@click.prevent="toggleMenu"
|
@click.prevent="toggleMenu"
|
||||||
>
|
>
|
||||||
<hc-avatar :user="user" />
|
<user-avatar :user="user" />
|
||||||
<base-icon class="dropdown-arrow" name="angle-down" />
|
<base-icon class="dropdown-arrow" name="angle-down" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
@ -49,12 +49,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
HcAvatar,
|
UserAvatar,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
placement: { type: String, default: 'top-end' },
|
placement: { type: String, default: 'top-end' },
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CategoriesSelect from './CategoriesSelect'
|
import CategoriesSelect from './CategoriesSelect'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -55,8 +56,9 @@ describe('CategoriesSelect.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('toggleCategory', () => {
|
describe('toggleCategory', () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
wrapper.vm.categories = categories
|
wrapper.vm.categories = categories
|
||||||
|
await Vue.nextTick()
|
||||||
democracyAndPolitics = wrapper.findAll('button').at(0)
|
democracyAndPolitics = wrapper.findAll('button').at(0)
|
||||||
democracyAndPolitics.trigger('click')
|
democracyAndPolitics.trigger('click')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -12,13 +12,13 @@
|
|||||||
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
||||||
<ds-card :id="anchor" :class="{ 'comment--target': isTarget }">
|
<ds-card :id="anchor" :class="{ 'comment--target': isTarget }">
|
||||||
<ds-space margin-bottom="small" margin-top="small">
|
<ds-space margin-bottom="small" margin-top="small">
|
||||||
<hc-user :user="author" :date-time="comment.createdAt">
|
<user-teaser :user="author" :date-time="comment.createdAt">
|
||||||
<template v-slot:dateTime>
|
<template v-slot:dateTime>
|
||||||
<ds-text v-if="comment.createdAt !== comment.updatedAt">
|
<ds-text v-if="comment.createdAt !== comment.updatedAt">
|
||||||
({{ $t('comment.edited') }})
|
({{ $t('comment.edited') }})
|
||||||
</ds-text>
|
</ds-text>
|
||||||
</template>
|
</template>
|
||||||
</hc-user>
|
</user-teaser>
|
||||||
<client-only>
|
<client-only>
|
||||||
<content-menu
|
<content-menu
|
||||||
v-show="!openEditCommentMenu"
|
v-show="!openEditCommentMenu"
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { COMMENT_MAX_UNTRUNCATED_LENGTH, COMMENT_TRUNCATE_TO_LENGTH } from '~/constants/comment'
|
import { COMMENT_MAX_UNTRUNCATED_LENGTH, COMMENT_TRUNCATE_TO_LENGTH } from '~/constants/comment'
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||||
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
||||||
@ -92,7 +92,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
HcUser,
|
UserTeaser,
|
||||||
ContentMenu,
|
ContentMenu,
|
||||||
ContentViewer,
|
ContentViewer,
|
||||||
HcCommentForm,
|
HcCommentForm,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CommentForm from './CommentForm'
|
import CommentForm from './CommentForm'
|
||||||
|
import Vue from 'vue'
|
||||||
import MutationObserver from 'mutation-observer'
|
import MutationObserver from 'mutation-observer'
|
||||||
|
|
||||||
global.MutationObserver = MutationObserver
|
global.MutationObserver = MutationObserver
|
||||||
@ -74,6 +74,7 @@ describe('CommentForm.vue', () => {
|
|||||||
|
|
||||||
it('calls `clear` method when the cancel button is clicked', async () => {
|
it('calls `clear` method when the cancel button is clicked', async () => {
|
||||||
wrapper.vm.updateEditorContent('ok')
|
wrapper.vm.updateEditorContent('ok')
|
||||||
|
await Vue.nextTick()
|
||||||
await wrapper.find('[data-test="cancel-button"]').trigger('submit')
|
await wrapper.find('[data-test="cancel-button"]').trigger('submit')
|
||||||
expect(cancelMethodSpy).toHaveBeenCalledTimes(1)
|
expect(cancelMethodSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -407,49 +407,49 @@ describe('ContentMenu.vue', () => {
|
|||||||
).toBe('/settings')
|
).toBe('/settings')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can block other users', () => {
|
it('can mute other users', () => {
|
||||||
const wrapper = openContentMenu({
|
const wrapper = openContentMenu({
|
||||||
isOwner: false,
|
isOwner: false,
|
||||||
resourceType: 'user',
|
resourceType: 'user',
|
||||||
resource: {
|
resource: {
|
||||||
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
||||||
isBlocked: false,
|
isMuted: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
wrapper
|
wrapper
|
||||||
.findAll('.ds-menu-item')
|
.findAll('.ds-menu-item')
|
||||||
.filter(item => item.text() === 'settings.blocked-users.block')
|
.filter(item => item.text() === 'settings.muted-users.mute')
|
||||||
.at(0)
|
.at(0)
|
||||||
.trigger('click')
|
.trigger('click')
|
||||||
expect(wrapper.emitted('block')).toEqual([
|
expect(wrapper.emitted('mute')).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
||||||
isBlocked: false,
|
isMuted: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can unblock blocked users', () => {
|
it('can unmute muted users', () => {
|
||||||
const wrapper = openContentMenu({
|
const wrapper = openContentMenu({
|
||||||
isOwner: false,
|
isOwner: false,
|
||||||
resourceType: 'user',
|
resourceType: 'user',
|
||||||
resource: {
|
resource: {
|
||||||
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
||||||
isBlocked: true,
|
isMuted: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
wrapper
|
wrapper
|
||||||
.findAll('.ds-menu-item')
|
.findAll('.ds-menu-item')
|
||||||
.filter(item => item.text() === 'settings.blocked-users.unblock')
|
.filter(item => item.text() === 'settings.muted-users.unmute')
|
||||||
.at(0)
|
.at(0)
|
||||||
.trigger('click')
|
.trigger('click')
|
||||||
expect(wrapper.emitted('unblock')).toEqual([
|
expect(wrapper.emitted('unmute')).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
||||||
isBlocked: true,
|
isMuted: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|||||||
@ -155,19 +155,19 @@ export default {
|
|||||||
icon: 'edit',
|
icon: 'edit',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (this.resource.isBlocked) {
|
if (this.resource.isMuted) {
|
||||||
routes.push({
|
routes.push({
|
||||||
label: this.$t(`settings.blocked-users.unblock`),
|
label: this.$t(`settings.muted-users.unmute`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('unblock', this.resource)
|
this.$emit('unmute', this.resource)
|
||||||
},
|
},
|
||||||
icon: 'user-plus',
|
icon: 'user-plus',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes.push({
|
routes.push({
|
||||||
label: this.$t(`settings.blocked-users.block`),
|
label: this.$t(`settings.muted-users.mute`),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.$emit('block', this.resource)
|
this.$emit('mute', this.resource)
|
||||||
},
|
},
|
||||||
icon: 'user-times',
|
icon: 'user-times',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { config, mount } from '@vue/test-utils'
|
import { config, mount } from '@vue/test-utils'
|
||||||
import ContributionForm from './ContributionForm.vue'
|
import ContributionForm from './ContributionForm.vue'
|
||||||
|
|
||||||
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import PostMutations from '~/graphql/PostMutations.js'
|
import PostMutations from '~/graphql/PostMutations.js'
|
||||||
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||||
@ -147,31 +148,31 @@ describe('ContributionForm.vue', () => {
|
|||||||
dataPrivacyButton.trigger('click')
|
dataPrivacyButton.trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be empty', async () => {
|
it('title cannot be empty', async () => {
|
||||||
postTitleInput.setValue('')
|
postTitleInput.setValue('')
|
||||||
wrapper.find('form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be too long', async () => {
|
it('title cannot be too long', async () => {
|
||||||
postTitleInput.setValue(postTitleTooLong)
|
postTitleInput.setValue(postTitleTooLong)
|
||||||
wrapper.find('form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('title should not be too short', async () => {
|
it('title cannot be too short', async () => {
|
||||||
postTitleInput.setValue(postTitleTooShort)
|
postTitleInput.setValue(postTitleTooShort)
|
||||||
wrapper.find('form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('content should not be empty', async () => {
|
it('content cannot be empty', async () => {
|
||||||
await wrapper.vm.updateEditorContent('')
|
await wrapper.vm.updateEditorContent('')
|
||||||
await wrapper.find('form').trigger('submit')
|
await wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have at least one category', async () => {
|
it('has at least one category', async () => {
|
||||||
dataPrivacyButton = await wrapper
|
dataPrivacyButton = await wrapper
|
||||||
.find(CategoriesSelect)
|
.find(CategoriesSelect)
|
||||||
.find('[data-test="category-buttons-cat12"]')
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
@ -180,8 +181,9 @@ describe('ContributionForm.vue', () => {
|
|||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have not have more than three categories', async () => {
|
it('has no more than three categories', async () => {
|
||||||
wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27']
|
wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27']
|
||||||
|
await Vue.nextTick()
|
||||||
wrapper.find('form').trigger('submit')
|
wrapper.find('form').trigger('submit')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@ -209,10 +211,12 @@ describe('ContributionForm.vue', () => {
|
|||||||
wrapper.find(CategoriesSelect).setData({ categories })
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
||||||
englishLanguage.trigger('click')
|
englishLanguage.trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
dataPrivacyButton = await wrapper
|
dataPrivacyButton = await wrapper
|
||||||
.find(CategoriesSelect)
|
.find(CategoriesSelect)
|
||||||
.find('[data-test="category-buttons-cat12"]')
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
dataPrivacyButton.trigger('click')
|
dataPrivacyButton.trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates a post with valid title, content, and at least one category', async () => {
|
it('creates a post with valid title, content, and at least one category', async () => {
|
||||||
@ -278,10 +282,12 @@ describe('ContributionForm.vue', () => {
|
|||||||
wrapper.find(CategoriesSelect).setData({ categories })
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
englishLanguage = wrapper.findAll('li').filter(language => language.text() === 'English')
|
||||||
englishLanguage.trigger('click')
|
englishLanguage.trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
dataPrivacyButton = await wrapper
|
dataPrivacyButton = await wrapper
|
||||||
.find(CategoriesSelect)
|
.find(CategoriesSelect)
|
||||||
.find('[data-test="category-buttons-cat12"]')
|
.find('[data-test="category-buttons-cat12"]')
|
||||||
dataPrivacyButton.trigger('click')
|
dataPrivacyButton.trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows an error toaster when apollo mutation rejects', async () => {
|
it('shows an error toaster when apollo mutation rejects', async () => {
|
||||||
@ -370,6 +376,7 @@ describe('ContributionForm.vue', () => {
|
|||||||
it('supports updating categories', async () => {
|
it('supports updating categories', async () => {
|
||||||
expectedParams.variables.categoryIds.push('cat3')
|
expectedParams.variables.categoryIds.push('cat3')
|
||||||
wrapper.find(CategoriesSelect).setData({ categories })
|
wrapper.find(CategoriesSelect).setData({ categories })
|
||||||
|
await Vue.nextTick()
|
||||||
const healthWellbeingButton = await wrapper
|
const healthWellbeingButton = await wrapper
|
||||||
.find(CategoriesSelect)
|
.find(CategoriesSelect)
|
||||||
.find('[data-test="category-buttons-cat3"]')
|
.find('[data-test="category-buttons-cat3"]')
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<ds-space />
|
<ds-space />
|
||||||
<client-only>
|
<client-only>
|
||||||
<hc-user :user="currentUser" :trunc="35" />
|
<user-teaser :user="currentUser" />
|
||||||
</client-only>
|
</client-only>
|
||||||
<ds-space />
|
<ds-space />
|
||||||
<ds-input
|
<ds-input
|
||||||
@ -122,14 +122,14 @@ import locales from '~/locales'
|
|||||||
import PostMutations from '~/graphql/PostMutations.js'
|
import PostMutations from '~/graphql/PostMutations.js'
|
||||||
import HcCategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
import HcCategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||||
import HcTeaserImage from '~/components/TeaserImage/TeaserImage'
|
import HcTeaserImage from '~/components/TeaserImage/TeaserImage'
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HcEditor,
|
HcEditor,
|
||||||
HcCategoriesSelect,
|
HcCategoriesSelect,
|
||||||
HcTeaserImage,
|
HcTeaserImage,
|
||||||
HcUser,
|
UserTeaser,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contribution: { type: Object, default: () => {} },
|
contribution: { type: Object, default: () => {} },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import DeleteData from './DeleteData.vue'
|
import DeleteData from './DeleteData.vue'
|
||||||
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -81,7 +81,7 @@ describe('DeleteData.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not call the delete user mutation if deleteEnabled is false', () => {
|
it('does not call the delete user mutation if deleteEnabled is false', () => {
|
||||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||||
deleteAccountBtn.trigger('click')
|
deleteAccountBtn.trigger('click')
|
||||||
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@ -90,7 +90,7 @@ describe('DeleteData.vue', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||||
enableDeletionInput.setValue(deleteAccountName)
|
enableDeletionInput.setValue(deleteAccountName)
|
||||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if deleteEnabled is true and only deletes user by default', () => {
|
it('if deleteEnabled is true and only deletes user by default', () => {
|
||||||
@ -168,7 +168,8 @@ describe('DeleteData.vue', () => {
|
|||||||
it('shows an error toaster when the mutation rejects', async () => {
|
it('shows an error toaster when the mutation rejects', async () => {
|
||||||
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
enableDeletionInput = wrapper.find('.enable-deletion-input input')
|
||||||
enableDeletionInput.setValue(deleteAccountName)
|
enableDeletionInput.setValue(deleteAccountName)
|
||||||
deleteAccountBtn = wrapper.find('.ds-button-danger')
|
await Vue.nextTick()
|
||||||
|
deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
|
||||||
await deleteAccountBtn.trigger('click')
|
await deleteAccountBtn.trigger('click')
|
||||||
// second submission causes mutation to reject
|
// second submission causes mutation to reject
|
||||||
await deleteAccountBtn.trigger('click')
|
await deleteAccountBtn.trigger('click')
|
||||||
|
|||||||
@ -55,22 +55,19 @@
|
|||||||
<ds-space margin-bottom="xx-small" />
|
<ds-space margin-bottom="xx-small" />
|
||||||
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'large' }">
|
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'large' }">
|
||||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1.75 }">
|
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1.75 }">
|
||||||
<ds-input
|
<ds-input v-model="enableDeletionValue" class="enable-deletion-input" />
|
||||||
v-model="enableDeletionValue"
|
|
||||||
@input="enableDeletion"
|
|
||||||
class="enable-deletion-input"
|
|
||||||
/>
|
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
||||||
<ds-button
|
<base-button
|
||||||
icon="trash"
|
icon="trash"
|
||||||
danger
|
danger
|
||||||
filled
|
filled
|
||||||
:disabled="!deleteEnabled"
|
:disabled="!deleteEnabled"
|
||||||
|
data-test="delete-button"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
{{ $t('settings.deleteUserAccount.name') }}
|
{{ $t('settings.deleteUserAccount.name') }}
|
||||||
</ds-button>
|
</base-button>
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
</ds-flex>
|
</ds-flex>
|
||||||
</ds-container>
|
</ds-container>
|
||||||
@ -88,7 +85,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
deleteContributions: false,
|
deleteContributions: false,
|
||||||
deleteComments: false,
|
deleteComments: false,
|
||||||
deleteEnabled: false,
|
|
||||||
enableDeletionValue: null,
|
enableDeletionValue: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -96,16 +92,14 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentUser: 'auth/user',
|
currentUser: 'auth/user',
|
||||||
}),
|
}),
|
||||||
|
deleteEnabled() {
|
||||||
|
return this.enableDeletionValue === this.currentUser.name
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
logout: 'auth/logout',
|
logout: 'auth/logout',
|
||||||
}),
|
}),
|
||||||
enableDeletion() {
|
|
||||||
if (this.enableDeletionValue === this.currentUser.name) {
|
|
||||||
this.deleteEnabled = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
const resourceArgs = []
|
const resourceArgs = []
|
||||||
if (this.deleteContributions) {
|
if (this.deleteContributions) {
|
||||||
|
|||||||
@ -65,10 +65,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-arrow {
|
|
||||||
font-size: $font-size-xx-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -328,85 +328,4 @@ li > p {
|
|||||||
margin: 0 0 $space-x-small;
|
margin: 0 0 $space-x-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror[contenteditable='false'] {
|
|
||||||
.embed-close-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-container {
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
margin: $space-small auto;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: $border-radius-base;
|
|
||||||
border: 1px solid $color-neutral-70;
|
|
||||||
background-color: $color-neutral-90;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: $space-small 0 0 $space-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
margin: 0 0 0 $space-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-preview-image {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-height: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-preview-image--clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-html {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
padding: $space-large;
|
|
||||||
background-color: $color-neutral-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-buttons {
|
|
||||||
button {
|
|
||||||
margin-right: $space-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-checkbox {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
input {
|
|
||||||
margin-right: $space-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: $space-x-small;
|
|
||||||
right: $space-x-small;
|
|
||||||
background-color: rgba(250, 249, 250, 0.6);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -65,13 +65,13 @@ describe('EmbedComponent.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows the description', () => {
|
it('shows the description', () => {
|
||||||
expect(wrapper.find('.embed-content p').text()).toBe(
|
expect(wrapper.find('.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...',
|
'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', () => {
|
it('shows preview Images for link', () => {
|
||||||
expect(wrapper.find('.embed-preview-image').exists()).toBe(true)
|
expect(wrapper.find('.preview').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ describe('EmbedComponent.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('show the desciption', () => {
|
it('show the desciption', () => {
|
||||||
expect(wrapper.find('.embed-content p').text()).toBe(
|
expect(wrapper.find('.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...',
|
'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...',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -121,12 +121,12 @@ describe('EmbedComponent.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows a simple link when a user closes the embed preview', () => {
|
it('shows a simple link when a user closes the embed preview', () => {
|
||||||
wrapper.find('.embed-close-button').trigger('click')
|
wrapper.find('.close-button').trigger('click')
|
||||||
expect(wrapper.vm.showLinkOnly).toBe(true)
|
expect(wrapper.vm.showLinkOnly).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens the data privacy overlay when a user clicks on the preview image', () => {
|
it('opens the data privacy overlay when a user clicks on the preview image', () => {
|
||||||
wrapper.find('.embed-preview-image--clickable').trigger('click')
|
wrapper.find('.preview.--clickable').trigger('click')
|
||||||
expect(wrapper.vm.showOverlay).toBe(true)
|
expect(wrapper.vm.showOverlay).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -135,19 +135,19 @@ describe('EmbedComponent.vue', () => {
|
|||||||
wrapper.setData({ showOverlay: true })
|
wrapper.setData({ showOverlay: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('when user agress', () => {
|
it('when user agrees', () => {
|
||||||
wrapper.find('.ds-button-primary').trigger('click')
|
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||||
expect(wrapper.vm.showEmbed).toBe(true)
|
expect(wrapper.vm.showEmbed).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not show iframe when user clicks to cancel', () => {
|
it('does not show iframe when user clicks to cancel', () => {
|
||||||
wrapper.find('.ds-button-ghost').trigger('click')
|
wrapper.find('[data-test="cancel-button"]').trigger('click')
|
||||||
expect(wrapper.vm.showEmbed).toBe(false)
|
expect(wrapper.vm.showEmbed).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("doesn't set permanently", () => {
|
describe("doesn't set permanently", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper.find('.ds-button-primary').trigger('click')
|
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("if user doesn't give consent", () => {
|
it("if user doesn't give consent", () => {
|
||||||
@ -162,7 +162,7 @@ describe('EmbedComponent.vue', () => {
|
|||||||
describe('sets permanently', () => {
|
describe('sets permanently', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper.find('input[type=checkbox]').trigger('click')
|
wrapper.find('input[type=checkbox]').trigger('click')
|
||||||
wrapper.find('.ds-button-primary').trigger('click')
|
wrapper.find('[data-test="play-now-button"]').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('changes setting permanetly when user requests', () => {
|
it('changes setting permanetly when user requests', () => {
|
||||||
@ -194,7 +194,7 @@ describe('EmbedComponent.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not display image to click', () => {
|
it('does not display image to click', () => {
|
||||||
expect(wrapper.find('.embed-preview-image--clickable').exists()).toBe(false)
|
expect(wrapper.find('.preview.--clickable').exists()).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
<a v-if="showLinkOnly" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
|
<a v-if="showLinkOnly" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
|
||||||
{{ dataEmbedUrl }}
|
{{ dataEmbedUrl }}
|
||||||
</a>
|
</a>
|
||||||
<ds-container v-else width="small" class="embed-container">
|
<ds-container v-else width="small" class="embed-component">
|
||||||
<section class="embed-content">
|
<section class="content">
|
||||||
<div v-if="showEmbed" v-html="embedHtml" class="embed-html" />
|
<div v-if="showEmbed" v-html="embedHtml" class="html" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<img
|
<img
|
||||||
v-if="embedHtml && embedImage"
|
v-if="embedHtml && embedImage"
|
||||||
:src="embedImage"
|
:src="embedImage"
|
||||||
class="embed-preview-image embed-preview-image--clickable"
|
class="preview --clickable"
|
||||||
@click.prevent="openOverlay()"
|
@click.prevent="openOverlay()"
|
||||||
/>
|
/>
|
||||||
<img v-else-if="embedImage" :src="embedImage" class="embed-preview-image" />
|
<img v-else-if="embedImage" :src="embedImage" class="preview" />
|
||||||
</template>
|
</template>
|
||||||
<h4 v-if="embedTitle">{{ embedTitle }}</h4>
|
<h4 v-if="embedTitle">{{ embedTitle }}</h4>
|
||||||
<p v-if="embedDescription">{{ embedDescription }}</p>
|
<p v-if="embedDescription">{{ embedDescription }}</p>
|
||||||
@ -20,25 +20,27 @@
|
|||||||
{{ dataEmbedUrl }}
|
{{ dataEmbedUrl }}
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
<aside v-if="showOverlay" class="embed-overlay">
|
<aside v-if="showOverlay" class="overlay">
|
||||||
<h3>{{ $t('editor.embed.data_privacy_warning') }}</h3>
|
<h3>{{ $t('editor.embed.data_privacy_warning') }}</h3>
|
||||||
<ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text>
|
<ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text>
|
||||||
<div class="embed-buttons">
|
<div class="buttons">
|
||||||
<ds-button primary @click.prevent="allowEmbed()">
|
<base-button primary @click="allowEmbed()" data-test="play-now-button">
|
||||||
{{ $t('editor.embed.play_now') }}
|
{{ $t('editor.embed.play_now') }}
|
||||||
</ds-button>
|
</base-button>
|
||||||
<ds-button ghost @click.prevent="closeOverlay()">{{ $t('actions.cancel') }}</ds-button>
|
<base-button @click="closeOverlay()" data-test="cancel-button">
|
||||||
|
{{ $t('actions.cancel') }}
|
||||||
|
</base-button>
|
||||||
</div>
|
</div>
|
||||||
<label class="embed-checkbox">
|
<label class="checkbox">
|
||||||
<input type="checkbox" v-model="checkedAlwaysAllowEmbeds" />
|
<input type="checkbox" v-model="checkedAlwaysAllowEmbeds" />
|
||||||
<span>{{ $t('editor.embed.always_allow') }}</span>
|
<span>{{ $t('editor.embed.always_allow') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</aside>
|
</aside>
|
||||||
<ds-button
|
<base-button
|
||||||
icon="close"
|
icon="close"
|
||||||
ghost
|
|
||||||
size="small"
|
size="small"
|
||||||
class="embed-close-button"
|
circle
|
||||||
|
class="close-button"
|
||||||
@click.prevent="removeEmbed()"
|
@click.prevent="removeEmbed()"
|
||||||
/>
|
/>
|
||||||
</ds-container>
|
</ds-container>
|
||||||
@ -151,3 +153,86 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.embed-component {
|
||||||
|
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;
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: $space-small 0 0 $space-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 0 $space-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview.--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
padding: $space-large;
|
||||||
|
background-color: $color-neutral-100;
|
||||||
|
|
||||||
|
> .buttons {
|
||||||
|
.base-button {
|
||||||
|
margin-right: $space-small;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .checkbox {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-right: $space-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: $space-x-small;
|
||||||
|
right: $space-x-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror[contenteditable='false'] {
|
||||||
|
.close-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
61
webapp/components/EmotionButton/EmotionButton.vue
Normal file
61
webapp/components/EmotionButton/EmotionButton.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div class="emotion-button">
|
||||||
|
<base-button :id="emotion" circle ghost @click="$emit('toggleEmotion', emotion)">
|
||||||
|
<img class="image" :src="emojiPath" />
|
||||||
|
</base-button>
|
||||||
|
<label class="label" :for="emotion">{{ $t(`contribution.emotions-label.${emotion}`) }}</label>
|
||||||
|
<p v-if="emotionCount !== null" class="count">{{ emotionCount }}x</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'EmotionButton',
|
||||||
|
props: {
|
||||||
|
emojiPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
emotion: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
emotionCount: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.emotion-button {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .base-button {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
padding: $space-xxx-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .label {
|
||||||
|
margin-top: $space-x-small;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .count {
|
||||||
|
margin: $space-x-small 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
max-width: $size-button-base;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,27 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<ds-flex :gutter="{ lg: 'large' }" class="emotions-flex">
|
<div class="emotions-button-group">
|
||||||
<div v-for="emotion in Object.keys(PostsEmotionsCountByEmotion)" :key="emotion">
|
<emotion-button
|
||||||
<ds-flex-item :width="{ lg: '100%' }">
|
v-for="emotion in Object.keys(PostsEmotionsCountByEmotion)"
|
||||||
<hc-emotions-button
|
:key="emotion"
|
||||||
@toggleEmotion="toggleEmotion"
|
:emojiPath="iconPath(emotion)"
|
||||||
:PostsEmotionsCountByEmotion="PostsEmotionsCountByEmotion"
|
|
||||||
:iconPath="iconPath(emotion)"
|
|
||||||
:emotion="emotion"
|
:emotion="emotion"
|
||||||
|
:emotionCount="PostsEmotionsCountByEmotion[emotion]"
|
||||||
|
@toggleEmotion="toggleEmotion"
|
||||||
/>
|
/>
|
||||||
</ds-flex-item>
|
|
||||||
</div>
|
</div>
|
||||||
</ds-flex>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import HcEmotionsButton from '~/components/EmotionsButton/EmotionsButton'
|
import EmotionButton from '~/components/EmotionButton/EmotionButton'
|
||||||
import { PostsEmotionsByCurrentUser } from '~/graphql/PostQuery.js'
|
import { PostsEmotionsByCurrentUser } from '~/graphql/PostQuery.js'
|
||||||
import PostMutations from '~/graphql/PostMutations.js'
|
import PostMutations from '~/graphql/PostMutations.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HcEmotionsButton,
|
EmotionButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
post: { type: Object, default: () => {} },
|
post: { type: Object, default: () => {} },
|
||||||
@ -113,3 +112,9 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.emotions-button-group {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<ds-button size="large" ghost @click="toggleEmotion(emotion)" class="emotions-buttons">
|
|
||||||
<img :src="iconPath" width="40" />
|
|
||||||
</ds-button>
|
|
||||||
<ds-space margin-bottom="xx-small" />
|
|
||||||
<div class="emotions-mobile-space">
|
|
||||||
<p class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</p>
|
|
||||||
<p style="display: inline" :key="PostsEmotionsCountByEmotion[emotion]">
|
|
||||||
{{ PostsEmotionsCountByEmotion[emotion] }}x
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
iconPath: { type: String, default: null },
|
|
||||||
PostsEmotionsCountByEmotion: { type: Object, default: () => {} },
|
|
||||||
emotion: { type: String, default: null },
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleEmotion(emotion) {
|
|
||||||
this.$emit('toggleEmotion', emotion)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.emotions-flex {
|
|
||||||
justify-content: space-evenly;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotions-label {
|
|
||||||
font-size: $font-size-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotions-buttons {
|
|
||||||
&:hover {
|
|
||||||
background-color: $background-color-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 960px) {
|
|
||||||
.emotions-mobile-space {
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -150,7 +150,7 @@ describe('FilterPosts.vue', () => {
|
|||||||
describe('click on an "emotions-buttons" button', () => {
|
describe('click on an "emotions-buttons" button', () => {
|
||||||
it('calls TOGGLE_EMOTION when clicked', () => {
|
it('calls TOGGLE_EMOTION when clicked', () => {
|
||||||
const wrapper = openFilterPosts()
|
const wrapper = openFilterPosts()
|
||||||
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
|
happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
|
||||||
happyEmotionButton.trigger('click')
|
happyEmotionButton.trigger('click')
|
||||||
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
|
expect(mutations['posts/TOGGLE_EMOTION']).toHaveBeenCalledWith({}, 'happy')
|
||||||
})
|
})
|
||||||
@ -158,7 +158,7 @@ describe('FilterPosts.vue', () => {
|
|||||||
it('sets the attribute `src` to colorized image', () => {
|
it('sets the attribute `src` to colorized image', () => {
|
||||||
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
|
getters['posts/filteredByEmotions'] = jest.fn(() => ['happy'])
|
||||||
const wrapper = openFilterPosts()
|
const wrapper = openFilterPosts()
|
||||||
happyEmotionButton = wrapper.findAll('button.emotions-buttons').at(1)
|
happyEmotionButton = wrapper.findAll('.emotion-button .base-button').at(1)
|
||||||
const happyEmotionButtonImage = happyEmotionButton.find('img')
|
const happyEmotionButtonImage = happyEmotionButton.find('img')
|
||||||
expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
|
expect(happyEmotionButtonImage.attributes().src).toEqual('/img/svg/emoji/happy_color.svg')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,14 +6,9 @@
|
|||||||
</ds-flex>
|
</ds-flex>
|
||||||
<ds-flex :gutter="{ lg: 'large' }">
|
<ds-flex :gutter="{ lg: 'large' }">
|
||||||
<ds-flex-item
|
<ds-flex-item
|
||||||
:width="{ base: '100%', sm: '100%', md: '100%', lg: '10%' }"
|
:width="{ base: '100%', sm: '100%', md: '10%', lg: '10%' }"
|
||||||
class="categories-menu-item"
|
class="follow-filter"
|
||||||
>
|
>
|
||||||
<ds-flex>
|
|
||||||
<ds-flex-item width="10%" />
|
|
||||||
<ds-space margin-bottom="xx-small" />
|
|
||||||
<ds-flex-item width="100%">
|
|
||||||
<div class="follow-filter-button">
|
|
||||||
<base-button
|
<base-button
|
||||||
data-test="filter-by-followed"
|
data-test="filter-by-followed"
|
||||||
icon="user-plus"
|
icon="user-plus"
|
||||||
@ -26,34 +21,27 @@
|
|||||||
delay: { show: 500 },
|
delay: { show: 500 },
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<ds-space margin-bottom="x-small" />
|
|
||||||
<ds-flex-item>
|
|
||||||
<label class="follow-label">{{ $t('filter-posts.followers.label') }}</label>
|
<label class="follow-label">{{ $t('filter-posts.followers.label') }}</label>
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
<ds-space />
|
<emotion-button
|
||||||
</div>
|
v-for="emotion in emotionsArray"
|
||||||
</ds-flex-item>
|
:key="emotion"
|
||||||
</ds-flex>
|
:emojiPath="iconPath(emotion)"
|
||||||
</ds-flex-item>
|
:emotion="emotion"
|
||||||
<div v-for="emotion in emotionsArray" :key="emotion">
|
@toggleEmotion="toogleFilteredByEmotions(emotion)"
|
||||||
<ds-flex-item :width="{ lg: '100%' }">
|
/>
|
||||||
<base-button @click="toogleFilteredByEmotions(emotion)" class="emotions-buttons" circle>
|
|
||||||
<img :src="iconPath(emotion)" width="40" />
|
|
||||||
</base-button>
|
|
||||||
<ds-space margin-bottom="x-small" />
|
|
||||||
<ds-flex-item class="emotions-mobile-space text-center">
|
|
||||||
<label class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</label>
|
|
||||||
</ds-flex-item>
|
|
||||||
</ds-flex-item>
|
|
||||||
</div>
|
|
||||||
<ds-space margin-bottom="large" />
|
<ds-space margin-bottom="large" />
|
||||||
</ds-flex>
|
</ds-flex>
|
||||||
</ds-space>
|
</ds-space>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations } from 'vuex'
|
import { mapGetters, mapMutations } from 'vuex'
|
||||||
|
import EmotionButton from '~/components/EmotionButton/EmotionButton'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
EmotionButton,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
user: { type: Object, required: true },
|
user: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
@ -91,13 +79,22 @@ export default {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.follow-filter.ds-flex-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: $space-base;
|
||||||
|
|
||||||
|
> .follow-label {
|
||||||
|
margin-top: $space-x-small;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 960px) {
|
@media only screen and (max-width: 960px) {
|
||||||
#filter-posts-header {
|
#filter-posts-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.follow-filter-button {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ds-space class="load-more" margin-top="large" style="text-align: center">
|
|
||||||
<ds-button :loading="loading" icon="arrow-down" ghost @click="$emit('click')">
|
|
||||||
{{ $t('actions.loadMore') }}
|
|
||||||
</ds-button>
|
|
||||||
</ds-space>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
loading: { type: Boolean, default: false },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Vue from 'vue'
|
||||||
import MasonryGrid from './MasonryGrid'
|
import MasonryGrid from './MasonryGrid'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -13,29 +13,29 @@ describe('MasonryGrid', () => {
|
|||||||
masonryGridItem = wrapper.vm.$children[0]
|
masonryGridItem = wrapper.vm.$children[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds the "reset-grid-height" class when itemsCalculating is more than 0', () => {
|
it('adds the "reset-grid-height" class when itemsCalculating is more than 0', async () => {
|
||||||
wrapper.setData({ itemsCalculating: 1 })
|
wrapper.setData({ itemsCalculating: 1 })
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.classes()).toContain('reset-grid-height')
|
expect(wrapper.classes()).toContain('reset-grid-height')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('removes the "reset-grid-height" class when itemsCalculating is 0', () => {
|
it('removes the "reset-grid-height" class when itemsCalculating is 0', async () => {
|
||||||
wrapper.setData({ itemsCalculating: 0 })
|
wrapper.setData({ itemsCalculating: 0 })
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.classes()).not.toContain('reset-grid-height')
|
expect(wrapper.classes()).not.toContain('reset-grid-height')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds 1 to itemsCalculating when a child emits "calculating-item-height"', () => {
|
it('adds 1 to itemsCalculating when a child emits "calculating-item-height"', async () => {
|
||||||
wrapper.setData({ itemsCalculating: 0 })
|
wrapper.setData({ itemsCalculating: 0 })
|
||||||
masonryGridItem.$emit('calculating-item-height')
|
masonryGridItem.$emit('calculating-item-height')
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.vm.itemsCalculating).toBe(1)
|
expect(wrapper.vm.itemsCalculating).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('subtracts 1 from itemsCalculating when a child emits "finished-calculating-item-height"', () => {
|
it('subtracts 1 from itemsCalculating when a child emits "finished-calculating-item-height"', async () => {
|
||||||
wrapper.setData({ itemsCalculating: 2 })
|
wrapper.setData({ itemsCalculating: 2 })
|
||||||
masonryGridItem.$emit('finished-calculating-item-height')
|
masonryGridItem.$emit('finished-calculating-item-height')
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.vm.itemsCalculating).toBe(1)
|
expect(wrapper.vm.itemsCalculating).toBe(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import DisableModal from './Modal/DisableModal.vue'
|
|||||||
import ReportModal from './Modal/ReportModal.vue'
|
import ReportModal from './Modal/ReportModal.vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import { getters, mutations } from '../store/modal'
|
import { getters, mutations } from '../store/modal'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -89,8 +90,9 @@ describe('Modal.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('child component emits close', () => {
|
describe('child component emits close', () => {
|
||||||
it('turns empty', () => {
|
it('turns empty', async () => {
|
||||||
wrapper.find(DisableModal).vm.$emit('close')
|
wrapper.find(DisableModal).vm.$emit('close')
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.contains(DisableModal)).toBe(false)
|
expect(wrapper.contains(DisableModal)).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { config, shallowMount, mount } from '@vue/test-utils'
|
import { config, shallowMount, mount } from '@vue/test-utils'
|
||||||
import ReportModal from './ReportModal.vue'
|
import ReportModal from './ReportModal.vue'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -151,9 +152,11 @@ describe('ReportModal.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('click confirm button', () => {
|
describe('click confirm button', () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
wrapper.find('.ds-radio-option-label').trigger('click')
|
wrapper.find('.ds-radio-option-label').trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
wrapper.find('button.confirm').trigger('click')
|
wrapper.find('button.confirm').trigger('click')
|
||||||
|
await Vue.nextTick()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls report mutation', () => {
|
it('calls report mutation', () => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<ds-space :class="{ read: notification.read, notification: true }" margin-bottom="x-small">
|
<ds-space :class="{ read: notification.read, notification: true }" margin-bottom="x-small">
|
||||||
<client-only>
|
<client-only>
|
||||||
<ds-space margin-bottom="x-small">
|
<ds-space margin-bottom="x-small">
|
||||||
<hc-user :user="from.author" :date-time="from.createdAt" :trunc="35" />
|
<user-teaser :user="from.author" :date-time="from.createdAt" />
|
||||||
</ds-space>
|
</ds-space>
|
||||||
<ds-text class="reason-text-for-test" color="soft">
|
<ds-text class="reason-text-for-test" color="soft">
|
||||||
{{ $t(`notifications.reason.${notification.reason}`) }}
|
{{ $t(`notifications.reason.${notification.reason}`) }}
|
||||||
@ -35,12 +35,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Notification',
|
name: 'Notification',
|
||||||
components: {
|
components: {
|
||||||
HcUser,
|
UserTeaser,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
notification: {
|
notification: {
|
||||||
|
|||||||
@ -89,8 +89,8 @@ describe('NotificationsTable.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders the author', () => {
|
it('renders the author', () => {
|
||||||
const username = firstRowNotification.find('.username')
|
const userinfo = firstRowNotification.find('.user-teaser > .info')
|
||||||
expect(username.text()).toEqual(postNotification.from.author.name)
|
expect(userinfo.text()).toContain(postNotification.from.author.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the reason for the notification', () => {
|
it('renders the reason for the notification', () => {
|
||||||
@ -122,8 +122,8 @@ describe('NotificationsTable.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders the author', () => {
|
it('renders the author', () => {
|
||||||
const username = secondRowNotification.find('.username')
|
const userinfo = secondRowNotification.find('.user-teaser > .info')
|
||||||
expect(username.text()).toEqual(commentNotification.from.author.name)
|
expect(userinfo.text()).toContain(commentNotification.from.author.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the reason for the notification', () => {
|
it('renders the reason for the notification', () => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { action } from '@storybook/addon-actions'
|
|||||||
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
|
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
|
||||||
import helpers from '~/storybook/helpers'
|
import helpers from '~/storybook/helpers'
|
||||||
import { post } from '~/components/PostCard/PostCard.story.js'
|
import { post } from '~/components/PostCard/PostCard.story.js'
|
||||||
import { user } from '~/components/User/User.story.js'
|
import { user } from '~/components/UserTeaser/UserTeaser.story.js'
|
||||||
|
|
||||||
helpers.init()
|
helpers.init()
|
||||||
export const notifications = [
|
export const notifications = [
|
||||||
|
|||||||
@ -15,10 +15,9 @@
|
|||||||
<template #user="scope">
|
<template #user="scope">
|
||||||
<ds-space margin-bottom="base">
|
<ds-space margin-bottom="base">
|
||||||
<client-only>
|
<client-only>
|
||||||
<hc-user
|
<user-teaser
|
||||||
:user="scope.row.from.author"
|
:user="scope.row.from.author"
|
||||||
:date-time="scope.row.from.createdAt"
|
:date-time="scope.row.from.createdAt"
|
||||||
:trunc="35"
|
|
||||||
:class="{ 'notification-status': scope.row.read }"
|
:class="{ 'notification-status': scope.row.read }"
|
||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
@ -50,12 +49,12 @@
|
|||||||
<hc-empty v-else icon="alert" :message="$t('notifications.empty')" />
|
<hc-empty v-else icon="alert" :message="$t('notifications.empty')" />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import HcEmpty from '~/components/Empty/Empty'
|
import HcEmpty from '~/components/Empty/Empty'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HcUser,
|
UserTeaser,
|
||||||
HcEmpty,
|
HcEmpty,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@ -18,12 +18,23 @@
|
|||||||
{{ $t('site.faq') }}
|
{{ $t('site.faq') }}
|
||||||
</a>
|
</a>
|
||||||
<span>-</span>
|
<span>-</span>
|
||||||
<a href="https://github.com/Human-Connection/Human-Connection/releases" target="_blank">
|
<a
|
||||||
{{ $t('site.changelog') }}
|
href="https://github.com/Human-Connection/Human-Connection/blob/master/CHANGELOG.md"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ version }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return { version: `v${process.env.release}` }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ds-footer {
|
.ds-footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ds-flex direction="row-reverse">
|
|
||||||
<ds-flex-item width="50px">
|
|
||||||
<ds-button @click="next" :disabled="!hasNext" icon="arrow-right" primary />
|
|
||||||
</ds-flex-item>
|
|
||||||
<ds-flex-item width="50px">
|
|
||||||
<ds-button @click="back" :disabled="!hasPrevious" icon="arrow-left" primary />
|
|
||||||
</ds-flex-item>
|
|
||||||
</ds-flex>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
hasNext: { type: Boolean, default: false },
|
|
||||||
hasPrevious: { type: Boolean, default: false },
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
back() {
|
|
||||||
this.$emit('back')
|
|
||||||
},
|
|
||||||
next() {
|
|
||||||
this.$emit('next')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -20,14 +20,14 @@
|
|||||||
<!-- Username, Image & Date of Post -->
|
<!-- Username, Image & Date of Post -->
|
||||||
<div class="user-wrapper">
|
<div class="user-wrapper">
|
||||||
<client-only>
|
<client-only>
|
||||||
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
|
<user-teaser :user="post.author" :date-time="post.createdAt" />
|
||||||
</client-only>
|
</client-only>
|
||||||
<hc-ribbon v-if="isPinned" class="ribbon--pinned" :text="$t('post.pinned')" />
|
<hc-ribbon v-if="isPinned" class="ribbon--pinned" :text="$t('post.pinned')" />
|
||||||
<hc-ribbon v-else :text="$t('post.name')" />
|
<hc-ribbon v-else :text="$t('post.name')" />
|
||||||
</div>
|
</div>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<!-- Post Title -->
|
<!-- Post Title -->
|
||||||
<ds-heading tag="h3" no-margin class="hyphenate-text">{{ post.title }}</ds-heading>
|
<ds-heading tag="h3" class="hyphenate-text post-title">{{ post.title }}</ds-heading>
|
||||||
<ds-space margin-bottom="small" />
|
<ds-space margin-bottom="small" />
|
||||||
<!-- Post Content Excerpt -->
|
<!-- Post Content Excerpt -->
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
@ -78,7 +78,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||||
import HcCategory from '~/components/Category'
|
import HcCategory from '~/components/Category'
|
||||||
import HcRibbon from '~/components/Ribbon'
|
import HcRibbon from '~/components/Ribbon'
|
||||||
@ -89,7 +89,7 @@ import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostH
|
|||||||
export default {
|
export default {
|
||||||
name: 'HcPostCard',
|
name: 'HcPostCard',
|
||||||
components: {
|
components: {
|
||||||
HcUser,
|
UserTeaser,
|
||||||
HcCategory,
|
HcCategory,
|
||||||
HcRibbon,
|
HcRibbon,
|
||||||
ContentMenu,
|
ContentMenu,
|
||||||
@ -186,7 +186,11 @@ export default {
|
|||||||
height: 75px;
|
height: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* workaround to avoid jumping layout when hc-user is rendered */
|
.post-title {
|
||||||
|
margin-top: $space-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* workaround to avoid jumping layout when user-teaser is rendered */
|
||||||
.user-wrapper {
|
.user-wrapper {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { config, mount } from '@vue/test-utils'
|
import { config, mount } from '@vue/test-utils'
|
||||||
|
import Vue from 'vue'
|
||||||
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
import { VERSION } from '~/constants/terms-and-conditions-version.js'
|
||||||
import CreateUserAccount from './CreateUserAccount'
|
import CreateUserAccount from './CreateUserAccount'
|
||||||
import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
import { SignupVerificationMutation } from '~/graphql/Registration.js'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
|
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
|
||||||
@ -110,6 +110,7 @@ describe('CreateUserAccount', () => {
|
|||||||
|
|
||||||
it('displays success', async () => {
|
it('displays success', async () => {
|
||||||
await action()
|
await action()
|
||||||
|
await Vue.nextTick()
|
||||||
expect(mocks.$t).toHaveBeenCalledWith(
|
expect(mocks.$t).toHaveBeenCalledWith(
|
||||||
'components.registration.create-user-account.success',
|
'components.registration.create-user-account.success',
|
||||||
)
|
)
|
||||||
@ -140,6 +141,7 @@ describe('CreateUserAccount', () => {
|
|||||||
|
|
||||||
it('displays form errors', async () => {
|
it('displays form errors', async () => {
|
||||||
await action()
|
await action()
|
||||||
|
await Vue.nextTick()
|
||||||
expect(mocks.$t).toHaveBeenCalledWith(
|
expect(mocks.$t).toHaveBeenCalledWith(
|
||||||
'components.registration.create-user-account.error',
|
'components.registration.create-user-account.error',
|
||||||
)
|
)
|
||||||
|
|||||||
@ -70,14 +70,6 @@ export default {
|
|||||||
showCropper: false,
|
showCropper: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
error() {
|
|
||||||
const that = this
|
|
||||||
setTimeout(function() {
|
|
||||||
that.error = false
|
|
||||||
}, 2000)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
template() {
|
template() {
|
||||||
return `<div class="dz-preview dz-file-preview">
|
return `<div class="dz-preview dz-file-preview">
|
||||||
@ -90,6 +82,9 @@ export default {
|
|||||||
verror(file, message) {
|
verror(file, message) {
|
||||||
this.error = true
|
this.error = true
|
||||||
this.$toast.error(file.status, message)
|
this.$toast.error(file.status, message)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.error = false
|
||||||
|
}, 2000)
|
||||||
},
|
},
|
||||||
transformImage(file) {
|
transformImage(file) {
|
||||||
this.file = file
|
this.file = file
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import Vue from 'vue'
|
||||||
import Upload from '.'
|
import Upload from '.'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -57,8 +58,9 @@ describe('Upload', () => {
|
|||||||
expect(mocks.$toast.error).toHaveBeenCalledWith(fileError.status, message)
|
expect(mocks.$toast.error).toHaveBeenCalledWith(fileError.status, message)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('changes error status from false to true to false', () => {
|
it('changes error status from false to true to false', async () => {
|
||||||
wrapper.vm.verror(fileError, message)
|
wrapper.vm.verror(fileError, message)
|
||||||
|
await Vue.nextTick()
|
||||||
expect(wrapper.vm.error).toEqual(true)
|
expect(wrapper.vm.error).toEqual(true)
|
||||||
jest.runAllTimers()
|
jest.runAllTimers()
|
||||||
expect(wrapper.vm.error).toEqual(false)
|
expect(wrapper.vm.error).toEqual(false)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||||
import User from './User.vue'
|
import UserTeaser from './UserTeaser.vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -7,7 +7,7 @@ const filter = jest.fn(str => str)
|
|||||||
|
|
||||||
localVue.filter('truncate', filter)
|
localVue.filter('truncate', filter)
|
||||||
|
|
||||||
describe('User', () => {
|
describe('UserTeaser', () => {
|
||||||
let propsData
|
let propsData
|
||||||
let mocks
|
let mocks
|
||||||
let stubs
|
let stubs
|
||||||
@ -35,7 +35,7 @@ describe('User', () => {
|
|||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
getters,
|
getters,
|
||||||
})
|
})
|
||||||
return mount(User, { store, propsData, mocks, stubs, localVue })
|
return mount(UserTeaser, { store, propsData, mocks, stubs, localVue })
|
||||||
}
|
}
|
||||||
|
|
||||||
it('renders anonymous user', () => {
|
it('renders anonymous user', () => {
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { storiesOf } from '@storybook/vue'
|
import { storiesOf } from '@storybook/vue'
|
||||||
import { withA11y } from '@storybook/addon-a11y'
|
import { withA11y } from '@storybook/addon-a11y'
|
||||||
import User from '~/components/User/User.vue'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser.vue'
|
||||||
import helpers from '~/storybook/helpers'
|
import helpers from '~/storybook/helpers'
|
||||||
|
|
||||||
helpers.init()
|
helpers.init()
|
||||||
@ -48,30 +48,38 @@ export const user = {
|
|||||||
],
|
],
|
||||||
followedByCount: 0,
|
followedByCount: 0,
|
||||||
followedByCurrentUser: false,
|
followedByCurrentUser: false,
|
||||||
isBlocked: false,
|
isMuted: false,
|
||||||
followedBy: [],
|
followedBy: [],
|
||||||
socialMedia: [],
|
socialMedia: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
storiesOf('User', module)
|
storiesOf('UserTeaser', module)
|
||||||
.addDecorator(withA11y)
|
.addDecorator(withA11y)
|
||||||
.addDecorator(helpers.layout)
|
.addDecorator(helpers.layout)
|
||||||
.add('available', () => ({
|
.add('user only', () => ({
|
||||||
components: { User },
|
components: { UserTeaser },
|
||||||
store: helpers.store,
|
store: helpers.store,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
template: '<user :user="user" :trunc="35" :date-time="new Date()" />',
|
template: '<user-teaser :user="user" />',
|
||||||
|
}))
|
||||||
|
.add('with Date', () => ({
|
||||||
|
components: { UserTeaser },
|
||||||
|
store: helpers.store,
|
||||||
|
data: () => ({
|
||||||
|
user,
|
||||||
|
}),
|
||||||
|
template: '<user-teaser :user="user" :date-time="new Date()" />',
|
||||||
}))
|
}))
|
||||||
.add('has edited something', () => ({
|
.add('has edited something', () => ({
|
||||||
components: { User },
|
components: { UserTeaser },
|
||||||
store: helpers.store,
|
store: helpers.store,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user,
|
user,
|
||||||
}),
|
}),
|
||||||
template: `
|
template: `
|
||||||
<user :user="user" :trunc="35" :date-time="new Date()">
|
<user-teaser :user="user" :date-time="new Date()">
|
||||||
<template v-slot:dateTime>
|
<template v-slot:dateTime>
|
||||||
- HEY! I'm edited
|
- HEY! I'm edited
|
||||||
</template>
|
</template>
|
||||||
@ -79,10 +87,10 @@ storiesOf('User', module)
|
|||||||
`,
|
`,
|
||||||
}))
|
}))
|
||||||
.add('anonymous', () => ({
|
.add('anonymous', () => ({
|
||||||
components: { User },
|
components: { UserTeaser },
|
||||||
store: helpers.store,
|
store: helpers.store,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user: null,
|
user: null,
|
||||||
}),
|
}),
|
||||||
template: '<user :user="user" :trunc="35" :date-time="new Date()" />',
|
template: '<user-teaser :user="user" :date-time="new Date()" />',
|
||||||
}))
|
}))
|
||||||
@ -1,32 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="user" v-if="displayAnonymous">
|
<div class="user-teaser" v-if="displayAnonymous">
|
||||||
<hc-avatar v-if="showAvatar" class="avatar" />
|
<user-avatar v-if="showAvatar" />
|
||||||
<div>
|
<span class="info anonymous">{{ $t('profile.userAnonym') }}</span>
|
||||||
<b class="username">{{ $t('profile.userAnonym') }}</b>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<dropdown
|
||||||
<dropdown v-else :class="{ 'disabled-content': user.disabled }" placement="top-start" offset="0">
|
v-else
|
||||||
<template slot="default" slot-scope="{ openMenu, closeMenu, isOpen }">
|
:class="[{ 'disabled-content': user.disabled }]"
|
||||||
<nuxt-link :to="userLink" :class="['user', isOpen && 'active']">
|
placement="top-start"
|
||||||
<div @mouseover="showPopover ? openMenu(true) : () => {}" @mouseleave="closeMenu(true)">
|
offset="0"
|
||||||
<hc-avatar v-if="showAvatar" class="avatar" :user="user" />
|
>
|
||||||
<div>
|
<template #default="{ openMenu, closeMenu, isOpen }">
|
||||||
<ds-text class="userinfo">
|
<nuxt-link
|
||||||
<b>{{ userSlug }}</b>
|
:to="userLink"
|
||||||
</ds-text>
|
:class="['user-teaser', isOpen && 'active']"
|
||||||
</div>
|
@mouseover.native="showPopover ? openMenu(true) : () => {}"
|
||||||
<ds-text class="username" align="left" size="small" color="soft">
|
@mouseleave.native="closeMenu(true)"
|
||||||
{{ userName | truncate(18) }}
|
>
|
||||||
<template v-if="dateTime">
|
<user-avatar v-if="showAvatar" :user="user" size="small" />
|
||||||
|
<div class="info">
|
||||||
|
<span class="text">
|
||||||
|
<span class="slug">{{ userSlug }}</span>
|
||||||
|
<span v-if="dateTime">{{ userName }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="dateTime" class="text">
|
||||||
<base-icon name="clock" />
|
<base-icon name="clock" />
|
||||||
<hc-relative-date-time :date-time="dateTime" />
|
<hc-relative-date-time :date-time="dateTime" />
|
||||||
<slot name="dateTime"></slot>
|
<slot name="dateTime"></slot>
|
||||||
</template>
|
</span>
|
||||||
</ds-text>
|
<span v-else class="text">{{ userName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<template slot="popover" v-if="showPopover">
|
<template #popover v-if="showPopover">
|
||||||
<div style="min-width: 250px">
|
<div style="min-width: 250px">
|
||||||
<hc-badges v-if="user.badges && user.badges.length" :badges="user.badges" />
|
<hc-badges v-if="user.badges && user.badges.length" :badges="user.badges" />
|
||||||
<ds-text
|
<ds-text
|
||||||
@ -77,7 +82,6 @@
|
|||||||
/>
|
/>
|
||||||
</ds-flex-item>
|
</ds-flex-item>
|
||||||
</ds-flex>
|
</ds-flex>
|
||||||
<!--<ds-space margin-bottom="x-small" />-->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</dropdown>
|
</dropdown>
|
||||||
@ -89,22 +93,21 @@ import { mapGetters } from 'vuex'
|
|||||||
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
||||||
import HcFollowButton from '~/components/FollowButton'
|
import HcFollowButton from '~/components/FollowButton'
|
||||||
import HcBadges from '~/components/Badges'
|
import HcBadges from '~/components/Badges'
|
||||||
import HcAvatar from '~/components/Avatar/Avatar.vue'
|
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||||
import Dropdown from '~/components/Dropdown'
|
import Dropdown from '~/components/Dropdown'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HcUser',
|
name: 'UserTeaser',
|
||||||
components: {
|
components: {
|
||||||
HcRelativeDateTime,
|
HcRelativeDateTime,
|
||||||
HcFollowButton,
|
HcFollowButton,
|
||||||
HcAvatar,
|
UserAvatar,
|
||||||
HcBadges,
|
HcBadges,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
user: { type: Object, default: null },
|
user: { type: Object, default: null },
|
||||||
showAvatar: { type: Boolean, default: true },
|
showAvatar: { type: Boolean, default: true },
|
||||||
trunc: { type: Number, default: 18 }, // "-1" is no trunc
|
|
||||||
dateTime: { type: [Date, String], default: null },
|
dateTime: { type: [Date, String], default: null },
|
||||||
showPopover: { type: Boolean, default: true },
|
showPopover: { type: Boolean, default: true },
|
||||||
},
|
},
|
||||||
@ -147,38 +150,51 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss">
|
||||||
.avatar {
|
.trigger {
|
||||||
float: left;
|
max-width: 100%;
|
||||||
margin-right: 4px;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.userinfo {
|
.user-teaser {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-wrap: nowrap;
|
||||||
|
z-index: $z-index-post-card-link;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .user-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: $space-xx-small;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
color: $text-color-soft;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
|
||||||
|
&.anonymous {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slug {
|
||||||
|
color: $color-primary;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
> .ds-text {
|
> .ds-text {
|
||||||
display: flex;
|
display: inline;
|
||||||
align-items: center;
|
|
||||||
margin-left: $space-xx-small;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.active {
|
|
||||||
z-index: 999;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-slug {
|
|
||||||
margin-bottom: $space-xx-small;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,39 +1,35 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
|
||||||
import Paginate from './Paginate'
|
import PaginationButtons from './PaginationButtons'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
describe('Paginate.vue', () => {
|
describe('PaginationButtons.vue', () => {
|
||||||
let propsData, wrapper, nextButton, backButton
|
let propsData = {}
|
||||||
|
let wrapper
|
||||||
beforeEach(() => {
|
let nextButton
|
||||||
propsData = {}
|
let backButton
|
||||||
})
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(Paginate, { propsData, localVue })
|
return mount(PaginationButtons, { propsData, localVue })
|
||||||
}
|
}
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
describe('next button', () => {
|
describe('next button', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData.hasNext = true
|
propsData.hasNext = true
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
nextButton = wrapper.findAll('.ds-button').at(0)
|
nextButton = wrapper.find('[data-test="next-button"]')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('is disabled by default', () => {
|
it('is disabled by default', () => {
|
||||||
propsData = {}
|
propsData = {}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
nextButton = wrapper.findAll('.ds-button').at(0)
|
nextButton = wrapper.find('[data-test="next-button"]')
|
||||||
expect(nextButton.attributes().disabled).toEqual('disabled')
|
expect(nextButton.attributes().disabled).toEqual('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('is not disabled if hasNext is true', () => {
|
it('is enabled if hasNext is true', () => {
|
||||||
expect(nextButton.attributes().disabled).toBeUndefined()
|
expect(nextButton.attributes().disabled).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -47,17 +43,17 @@ describe('Paginate.vue', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
propsData.hasPrevious = true
|
propsData.hasPrevious = true
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
backButton = wrapper.findAll('.ds-button').at(1)
|
backButton = wrapper.find('[data-test="previous-button"]')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('is disabled by default', () => {
|
it('is disabled by default', () => {
|
||||||
propsData = {}
|
propsData = {}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
backButton = wrapper.findAll('.ds-button').at(1)
|
backButton = wrapper.find('[data-test="previous-button"]')
|
||||||
expect(backButton.attributes().disabled).toEqual('disabled')
|
expect(backButton.attributes().disabled).toEqual('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('is not disabled if hasPrevious is true', () => {
|
it('is enabled if hasPrevious is true', () => {
|
||||||
expect(backButton.attributes().disabled).toBeUndefined()
|
expect(backButton.attributes().disabled).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1,16 +1,16 @@
|
|||||||
import { storiesOf } from '@storybook/vue'
|
import { storiesOf } from '@storybook/vue'
|
||||||
import { withA11y } from '@storybook/addon-a11y'
|
import { withA11y } from '@storybook/addon-a11y'
|
||||||
import { action } from '@storybook/addon-actions'
|
import { action } from '@storybook/addon-actions'
|
||||||
import Paginate from '~/components/Paginate/Paginate'
|
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||||
import helpers from '~/storybook/helpers'
|
import helpers from '~/storybook/helpers'
|
||||||
|
|
||||||
helpers.init()
|
helpers.init()
|
||||||
|
|
||||||
storiesOf('Paginate', module)
|
storiesOf('PaginationButtons', module)
|
||||||
.addDecorator(withA11y)
|
.addDecorator(withA11y)
|
||||||
.addDecorator(helpers.layout)
|
.addDecorator(helpers.layout)
|
||||||
.add('basic pagination', () => ({
|
.add('basic pagination', () => ({
|
||||||
components: { Paginate },
|
components: { PaginationButtons },
|
||||||
data: () => ({
|
data: () => ({
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
hasPrevious: false,
|
hasPrevious: false,
|
||||||
@ -19,7 +19,7 @@ storiesOf('Paginate', module)
|
|||||||
back: action('back'),
|
back: action('back'),
|
||||||
next: action('next'),
|
next: action('next'),
|
||||||
},
|
},
|
||||||
template: `<paginate
|
template: `<pagination-buttons
|
||||||
:hasNext="hasNext"
|
:hasNext="hasNext"
|
||||||
:hasPrevious="hasPrevious"
|
:hasPrevious="hasPrevious"
|
||||||
@back="back"
|
@back="back"
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pagination-buttons">
|
||||||
|
<base-button
|
||||||
|
@click="$emit('back')"
|
||||||
|
:disabled="!hasPrevious"
|
||||||
|
icon="arrow-left"
|
||||||
|
circle
|
||||||
|
data-test="previous-button"
|
||||||
|
/>
|
||||||
|
<base-button
|
||||||
|
@click="$emit('next')"
|
||||||
|
:disabled="!hasNext"
|
||||||
|
icon="arrow-right"
|
||||||
|
circle
|
||||||
|
data-test="next-button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
hasNext: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
hasPrevious: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.pagination-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: $size-width-paginate;
|
||||||
|
margin: $space-x-small auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
99
webapp/components/_new/generic/UserAvatar/UserAvatar.spec.js
Normal file
99
webapp/components/_new/generic/UserAvatar/UserAvatar.spec.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import UserAvatar from './UserAvatar.vue'
|
||||||
|
import BaseIcon from '~/components/_new/generic/BaseIcon/BaseIcon'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
describe('UserAvatar.vue', () => {
|
||||||
|
let propsData, wrapper
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(UserAvatar, { propsData, localVue })
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders no image', () => {
|
||||||
|
expect(wrapper.find('img').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders an icon', () => {
|
||||||
|
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('given a user', () => {
|
||||||
|
describe('with no image', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {
|
||||||
|
user: {
|
||||||
|
name: 'Matt Rider',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('no user name', () => {
|
||||||
|
it('renders an icon', () => {
|
||||||
|
propsData = { user: { name: null } }
|
||||||
|
wrapper = Wrapper()
|
||||||
|
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("user name is 'Anonymous'", () => {
|
||||||
|
it('renders an icon', () => {
|
||||||
|
propsData = { user: { name: 'Anonymous' } }
|
||||||
|
wrapper = Wrapper()
|
||||||
|
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays user initials', () => {
|
||||||
|
expect(wrapper.find('.initials').text()).toEqual('MR')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays no more than 3 initials', () => {
|
||||||
|
propsData = { user: { name: 'Ana Paula Nunes Marques' } }
|
||||||
|
wrapper = Wrapper()
|
||||||
|
expect(wrapper.find('.initials').text()).toEqual('APN')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with a relative avatar url', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {
|
||||||
|
user: {
|
||||||
|
name: 'Not Anonymous',
|
||||||
|
avatar: '/avatar.jpg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds a prefix to load the image from the uploads service', () => {
|
||||||
|
expect(wrapper.find('.image').attributes('src')).toBe('/api/avatar.jpg')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with an absolute avatar url', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
propsData = {
|
||||||
|
user: {
|
||||||
|
name: 'Not Anonymous',
|
||||||
|
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the avatar URL as is', () => {
|
||||||
|
// e.g. our seeds have absolute image URLs
|
||||||
|
expect(wrapper.find('.image').attributes('src')).toBe(
|
||||||
|
'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import { storiesOf } from '@storybook/vue'
|
||||||
|
import { withA11y } from '@storybook/addon-a11y'
|
||||||
|
import StoryRouter from 'storybook-vue-router'
|
||||||
|
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||||
|
import helpers from '~/storybook/helpers'
|
||||||
|
import { user } from '~/components/UserTeaser/UserTeaser.story.js'
|
||||||
|
|
||||||
|
helpers.init()
|
||||||
|
const anonymousUser = {
|
||||||
|
...user,
|
||||||
|
name: 'Anonymous',
|
||||||
|
avatar: null,
|
||||||
|
}
|
||||||
|
const userWithoutAvatar = {
|
||||||
|
...user,
|
||||||
|
avatar: null,
|
||||||
|
name: 'Ana Paula Nunes Marques',
|
||||||
|
}
|
||||||
|
storiesOf('UserAvatar', module)
|
||||||
|
.addDecorator(withA11y)
|
||||||
|
.addDecorator(helpers.layout)
|
||||||
|
.addDecorator(StoryRouter())
|
||||||
|
.add('with image', () => ({
|
||||||
|
components: { UserAvatar },
|
||||||
|
data: () => ({
|
||||||
|
user,
|
||||||
|
}),
|
||||||
|
template: '<user-avatar :user="user" />',
|
||||||
|
}))
|
||||||
|
.add('without image, anonymous user', () => ({
|
||||||
|
components: { UserAvatar },
|
||||||
|
data: () => ({
|
||||||
|
user: anonymousUser,
|
||||||
|
}),
|
||||||
|
template: '<user-avatar :user="user" />',
|
||||||
|
}))
|
||||||
|
.add('without image, user initials', () => ({
|
||||||
|
components: { UserAvatar },
|
||||||
|
data: () => ({
|
||||||
|
user: userWithoutAvatar,
|
||||||
|
}),
|
||||||
|
template: '<user-avatar :user="user" />',
|
||||||
|
}))
|
||||||
|
.add('small', () => ({
|
||||||
|
components: { UserAvatar },
|
||||||
|
data: () => ({
|
||||||
|
user,
|
||||||
|
}),
|
||||||
|
template: '<user-avatar :user="user" size="small"/>',
|
||||||
|
}))
|
||||||
|
.add('large', () => ({
|
||||||
|
components: { UserAvatar },
|
||||||
|
data: () => ({
|
||||||
|
user,
|
||||||
|
}),
|
||||||
|
template: '<user-avatar :user="user" size="large"/>',
|
||||||
|
}))
|
||||||
84
webapp/components/_new/generic/UserAvatar/UserAvatar.vue
Normal file
84
webapp/components/_new/generic/UserAvatar/UserAvatar.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="['user-avatar', size && `--${this.size}`]">
|
||||||
|
<span class="initials">{{ userInitials }}</span>
|
||||||
|
<base-icon v-if="isAnonymous" name="eye-slash" />
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
:src="user.avatar | proxyApiUrl"
|
||||||
|
class="image"
|
||||||
|
@error="event.target.style.display = 'none'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'UserAvatar',
|
||||||
|
props: {
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
validator: value => {
|
||||||
|
return value.match(/(small|large)/)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAnonymous() {
|
||||||
|
return !this.user || !this.user.name || this.user.name.toLowerCase() === 'anonymous'
|
||||||
|
},
|
||||||
|
userInitials() {
|
||||||
|
if (this.isAnonymous) return ''
|
||||||
|
|
||||||
|
return this.user.name
|
||||||
|
.match(/\b\w/g)
|
||||||
|
.join('')
|
||||||
|
.substring(0, 3)
|
||||||
|
.toUpperCase()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.user-avatar {
|
||||||
|
position: relative;
|
||||||
|
height: $size-avatar-base;
|
||||||
|
width: $size-avatar-base;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: $color-primary-dark;
|
||||||
|
color: $text-color-primary-inverse;
|
||||||
|
|
||||||
|
&.--small {
|
||||||
|
width: $size-avatar-small;
|
||||||
|
height: $size-avatar-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.--large {
|
||||||
|
width: $size-avatar-large;
|
||||||
|
height: $size-avatar-large;
|
||||||
|
font-size: $font-size-xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .initials,
|
||||||
|
> .base-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .image {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -7,11 +7,10 @@
|
|||||||
condensed
|
condensed
|
||||||
>
|
>
|
||||||
<template #submitter="scope">
|
<template #submitter="scope">
|
||||||
<hc-user
|
<user-teaser
|
||||||
:user="scope.row.submitter"
|
:user="scope.row.submitter"
|
||||||
:showAvatar="false"
|
:showAvatar="false"
|
||||||
:showPopover="false"
|
:showPopover="false"
|
||||||
:trunc="30"
|
|
||||||
data-test="filing-user"
|
data-test="filing-user"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -29,12 +28,12 @@
|
|||||||
</ds-table>
|
</ds-table>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import HcUser from '~/components/User/User'
|
import UserTeaser from '~/components/UserTeaser/UserTeaser'
|
||||||
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HcUser,
|
UserTeaser,
|
||||||
HcRelativeDateTime,
|
HcRelativeDateTime,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user