Merge branch 'master' of github.com:Human-Connection/Human-Connection into neo4j_data_migrations

This commit is contained in:
mattwr18 2020-01-22 15:23:11 +01:00
commit 8accc62f18
71 changed files with 3018 additions and 1794 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -1,6 +1,6 @@
{ {
"name": "human-connection-backend", "name": "human-connection-backend",
"version": "0.2.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": {
@ -37,7 +37,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",
@ -68,20 +68,20 @@
"lodash": "~4.17.14", "lodash": "~4.17.14",
"merge-graphql-schemas": "^1.7.6", "merge-graphql-schemas": "^1.7.6",
"metascraper": "^5.10.5", "metascraper": "^5.10.5",
"metascraper-audio": "^5.10.3", "metascraper-audio": "^5.10.5",
"metascraper-author": "^5.10.3", "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.10.5", "metascraper-description": "^5.10.5",
"metascraper-image": "^5.10.3", "metascraper-image": "^5.10.5",
"metascraper-lang": "^5.10.5", "metascraper-lang": "^5.10.5",
"metascraper-lang-detector": "^4.10.2", "metascraper-lang-detector": "^4.10.2",
"metascraper-logo": "^5.10.3", "metascraper-logo": "^5.10.5",
"metascraper-publisher": "^5.10.3", "metascraper-publisher": "^5.10.5",
"metascraper-soundcloud": "^5.10.5", "metascraper-soundcloud": "^5.10.5",
"metascraper-title": "^5.10.3", "metascraper-title": "^5.10.5",
"metascraper-url": "^5.10.3", "metascraper-url": "^5.10.5",
"metascraper-video": "^5.10.3", "metascraper-video": "^5.10.5",
"metascraper-youtube": "^5.10.5", "metascraper-youtube": "^5.10.5",
"migrate": "^1.6.2", "migrate": "^1.6.2",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
@ -94,12 +94,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.4.0", "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": {
@ -112,7 +112,7 @@
"apollo-server-testing": "~2.9.16", "apollo-server-testing": "~2.9.16",
"babel-core": "~7.0.0-0", "babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.3", "babel-eslint": "~10.0.3",
"babel-jest": "~24.9.0", "babel-jest": "~25.1.0",
"chai": "~4.2.0", "chai": "~4.2.0",
"cucumber": "~6.0.5", "cucumber": "~6.0.5",
"eslint": "~6.8.0", "eslint": "~6.8.0",
@ -124,7 +124,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"

View File

@ -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'),

View File

@ -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(

View File

@ -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,

View File

@ -78,6 +78,12 @@ export default {
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',

View File

@ -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
}

View 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
}

View File

@ -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) => {

View File

@ -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:

View File

@ -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,
},
},
}), }),
) )
}) })

View File

@ -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
} }

File diff suppressed because it is too large Load Diff

View File

@ -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");
}); });

View File

@ -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");
}); });

View File

@ -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", () => {

View File

@ -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");
}) })

View File

@ -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");
}); });
}); });

View File

@ -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

View File

@ -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 |

View File

@ -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 |

View File

@ -60,6 +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.location("pathname").should("eq", "/");
}); });
Cypress.Commands.add("logout", (email, password) => { Cypress.Commands.add("logout", (email, password) => {

View 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:
![Grafana dashboard](./grafana/metrics.png)
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.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@ -0,0 +1,4 @@
sidecar:
datasources:
enabled: true
label: grafana_datasource

View File

@ -1,6 +1,6 @@
{ {
"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",
@ -18,8 +18,8 @@
"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",
"release": "standard-version" "release": "standard-version"
@ -39,7 +39,7 @@
"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",

View File

@ -254,8 +254,7 @@ $size-width-paginate: 100px;
$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

View File

@ -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')
})
})
})
})

View File

@ -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>

View File

@ -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', () => {

View File

@ -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' },

View File

@ -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"
@ -61,7 +61,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'
@ -82,7 +82,7 @@ export default {
} }
}, },
components: { components: {
HcUser, UserTeaser,
ContentMenu, ContentMenu,
ContentViewer, ContentViewer,
HcCommentForm, HcCommentForm,

View File

@ -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,
}, },
], ],
]) ])

View File

@ -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',
}) })

View File

@ -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: () => {} },

View File

@ -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: {

View File

@ -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', () => {

View File

@ -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 = [

View File

@ -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: {

View File

@ -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;
} }

View File

@ -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', () => {

View File

@ -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()" />',
})) }))

View File

@ -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>

View 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',
)
})
})
})
})

View File

@ -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"/>',
}))

View 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>

View File

@ -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: {

View File

@ -2,7 +2,7 @@ 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 { 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'
import helpers from '~/storybook/helpers' import helpers from '~/storybook/helpers'
import ReportList from './ReportList' import ReportList from './ReportList'
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter' import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'

View File

@ -19,7 +19,7 @@
<!-- Content Column --> <!-- Content Column -->
<td class="ds-table-col" data-test="report-content"> <td class="ds-table-col" data-test="report-content">
<client-only v-if="isUser"> <client-only v-if="isUser">
<hc-user :user="report.resource" :showAvatar="false" :trunc="30" :showPopover="false" /> <user-teaser :user="report.resource" :showAvatar="false" :showPopover="false" />
</client-only> </client-only>
<nuxt-link v-else class="title" :to="linkTarget"> <nuxt-link v-else class="title" :to="linkTarget">
{{ linkText | truncate(50) }} {{ linkText | truncate(50) }}
@ -29,12 +29,7 @@
<!-- Author Column --> <!-- Author Column -->
<td class="ds-table-col" data-test="report-author"> <td class="ds-table-col" data-test="report-author">
<client-only v-if="!isUser"> <client-only v-if="!isUser">
<hc-user <user-teaser :user="report.resource.author" :showAvatar="false" :showPopover="false" />
:user="report.resource.author"
:showAvatar="false"
:trunc="30"
:showPopover="false"
/>
</client-only> </client-only>
<span v-else></span> <span v-else></span>
</td> </td>
@ -46,10 +41,9 @@
{{ statusText }} {{ statusText }}
</span> </span>
<client-only v-if="isReviewed"> <client-only v-if="isReviewed">
<hc-user <user-teaser
:user="moderatorOfLatestReview" :user="moderatorOfLatestReview"
:showAvatar="false" :showAvatar="false"
:trunc="30"
:date-time="report.updatedAt" :date-time="report.updatedAt"
:showPopover="false" :showPopover="false"
/> />
@ -85,12 +79,12 @@
<script> <script>
import FiledReportsTable from '~/components/features/FiledReportsTable/FiledReportsTable' import FiledReportsTable from '~/components/features/FiledReportsTable/FiledReportsTable'
import HcUser from '~/components/User/User' import UserTeaser from '~/components/UserTeaser/UserTeaser'
export default { export default {
components: { components: {
FiledReportsTable, FiledReportsTable,
HcUser, UserTeaser,
}, },
props: { props: {
report: { report: {

View File

@ -97,8 +97,8 @@ describe('SearchableInput.vue', () => {
it("pushes to user's profile", async () => { it("pushes to user's profile", async () => {
select.element.value = 'Bob' select.element.value = 'Bob'
select.trigger('input') select.trigger('input')
const users = wrapper.findAll('.userinfo') const users = wrapper.findAll('.slug')
const bob = users.filter(item => item.text() === '@bob-der-baumeister') const bob = users.filter(item => item.text().match(/@bob-der-baumeister/))
bob.trigger('click') bob.trigger('click')
await Vue.nextTick() await Vue.nextTick()
expect(mocks.$router.push).toHaveBeenCalledWith({ expect(mocks.$router.push).toHaveBeenCalledWith({

View File

@ -27,7 +27,7 @@
v-if="option.__typename === 'User'" v-if="option.__typename === 'User'"
:class="{ 'option-with-heading': isFirstOfType(option) }" :class="{ 'option-with-heading': isFirstOfType(option) }"
> >
<hc-user :user="option" :showPopover="false" /> <user-teaser :user="option" :showPopover="false" />
</p> </p>
<p <p
v-if="option.__typename === 'Post'" v-if="option.__typename === 'Post'"
@ -45,14 +45,14 @@
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import SearchHeading from '~/components/generic/SearchHeading/SearchHeading.vue' import SearchHeading from '~/components/generic/SearchHeading/SearchHeading.vue'
import SearchPost from '~/components/generic/SearchPost/SearchPost.vue' import SearchPost from '~/components/generic/SearchPost/SearchPost.vue'
import HcUser from '~/components/User/User.vue' import UserTeaser from '~/components/UserTeaser/UserTeaser.vue'
export default { export default {
name: 'SearchableInput', name: 'SearchableInput',
components: { components: {
SearchHeading, SearchHeading,
SearchPost, SearchPost,
HcUser, UserTeaser,
}, },
props: { props: {
id: { type: String }, id: { type: String },

View File

@ -23,7 +23,7 @@ export default i18n => {
locationName locationName
createdAt createdAt
followedByCurrentUser followedByCurrentUser
isBlocked isMuted
following(first: 7) { following(first: 7) {
...user ...user
...userCounts ...userCounts

View File

@ -1,9 +1,9 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const BlockedUsers = () => { export const mutedUsers = () => {
return gql` return gql`
{ {
blockedUsers { mutedUsers {
id id
name name
slug slug
@ -16,26 +16,26 @@ export const BlockedUsers = () => {
` `
} }
export const Block = () => { export const muteUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation($id: ID!) {
block(id: $id) { muteUser(id: $id) {
id id
name name
isBlocked isMuted
followedByCurrentUser followedByCurrentUser
} }
} }
` `
} }
export const Unblock = () => { export const unmuteUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation($id: ID!) {
unblock(id: $id) { unmuteUser(id: $id) {
id id
name name
isBlocked isMuted
followedByCurrentUser followedByCurrentUser
} }
} }

View File

@ -76,9 +76,6 @@
"download": { "download": {
"name": "Daten herunterladen" "name": "Daten herunterladen"
}, },
"delete": {
"name": "Konto löschen"
},
"organizations": { "organizations": {
"name": "Meine Organisationen" "name": "Meine Organisationen"
}, },
@ -145,26 +142,23 @@
"successAdd": "Social-Media hinzugefügt. Profil aktualisiert!", "successAdd": "Social-Media hinzugefügt. Profil aktualisiert!",
"successDelete": "Social-Media gelöscht. Profil aktualisiert!" "successDelete": "Social-Media gelöscht. Profil aktualisiert!"
}, },
"blocked-users": { "muted-users": {
"name": "Blockierte Benutzer", "name": "Stummgeschaltete Benutzer",
"explanation": { "explanation": {
"intro": "Wenn ein anderer Benutzer von dir blockiert wurde, dann passiert folgendes:", "intro": "Wenn ein anderer Benutzer von dir stummgeschaltet wurde, dann passiert folgendes:",
"your-perspective": "In deiner Beitragsübersicht tauchen keine Beiträge der blockierten Person mehr auf.", "your-perspective": "In deiner Beitragsübersicht tauchen keine Beiträge der stummgeschalteten Person mehr auf.",
"their-perspective": "Umgekehrt das gleiche: Die blockierte Person sieht deine Beiträge auch nicht mehr in ihrer Übersicht.", "search": "Die Beiträge von stummgeschalteten Personen verschwinden aus deinen Suchergebnissen."
"search": "Die Beiträge von blockierten Personen verschwinden aus deinen Suchergebnissen.",
"notifications": "Von dir blockierte Personen erhalten keine Benachrichtigungen mehr, wenn sie in deinen Beiträgen erwähnt werden.",
"closing": "Das sollte fürs Erste genügen, damit blockierte Benutzer dich nicht mehr länger belästigen können."
}, },
"columns": { "columns": {
"name": "Name", "name": "Name",
"slug": "Alias", "slug": "Alias",
"unblock": "Entsperren" "unmute": "Entsperren"
}, },
"empty": "Bislang hast du niemanden blockiert.", "empty": "Bislang hast du niemanden stummgeschaltet.",
"how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü blockieren.", "how-to": "Du kannst andere Benutzer auf deren Profilseite über das Inhaltsmenü stummschalten.",
"block": "Nutzer blockieren", "mute": "Stumm schalten",
"unblock": "Nutzer entblocken", "unmute": "Stummschaltung aufheben",
"unblocked": "{name} ist wieder entsperrt" "unmuted": "{name} ist nicht mehr stummgeschaltet"
}, },
"privacy": { "privacy": {
"name": "Privatsphäre", "name": "Privatsphäre",
@ -217,11 +211,6 @@
"categoryName": "Name", "categoryName": "Name",
"postCount": "Beiträge" "postCount": "Beiträge"
}, },
"tags": {
"name": "Schlagworte",
"tagCountUnique": "Benutzer",
"tagCount": "Beiträge"
},
"settings": { "settings": {
"name": "Einstellungen" "name": "Einstellungen"
}, },
@ -268,7 +257,7 @@
}, },
"comment": { "comment": {
"submit": "Kommentiere", "submit": "Kommentiere",
"submitted": "Kommentar Gesendet", "submitted": "Kommentar gesendet",
"updated": "Änderungen gespeichert" "updated": "Änderungen gespeichert"
}, },
"edited": "bearbeitet" "edited": "bearbeitet"
@ -444,8 +433,6 @@
} }
}, },
"contribution": { "contribution": {
"edit": "Beitrag bearbeiten",
"delete": "Beitrag löschen",
"title": "Titel", "title": "Titel",
"newPost": "Erstelle einen neuen Beitrag", "newPost": "Erstelle einen neuen Beitrag",
"filterFollow": "Beiträge filtern von Usern denen ich folge", "filterFollow": "Beiträge filtern von Usern denen ich folge",
@ -490,8 +477,6 @@
"inappropriatePictureText" : "Wann soll ein Foto versteckt werden" "inappropriatePictureText" : "Wann soll ein Foto versteckt werden"
}, },
"comment": { "comment": {
"edit": "Kommentar bearbeiten",
"delete": "Kommentar löschen",
"content": { "content": {
"unavailable-placeholder": "… dieser Kommentar ist nicht mehr verfügbar" "unavailable-placeholder": "… dieser Kommentar ist nicht mehr verfügbar"
}, },

View File

@ -312,26 +312,23 @@
"successAdd": "Added social media. Updated user profile!", "successAdd": "Added social media. Updated user profile!",
"successDelete": "Deleted social media. Updated user profile!" "successDelete": "Deleted social media. Updated user profile!"
}, },
"blocked-users": { "muted-users": {
"name": "Blocked users", "name": "Muted users",
"explanation": { "explanation": {
"intro": "If another user has been blocked by you, this is what happens:", "intro": "If another user has been muted by you, this is what happens:",
"your-perspective": "The blocked person's posts will no longer appear in your news feed.", "your-perspective": "The muted person's posts will no longer appear in your news feed.",
"their-perspective": "Vice versa: The blocked person will also no longer see your posts in their news feed.", "search": "Posts of muted people disappear from your search results."
"search": "Posts of blocked people disappear from your search results.",
"notifications": "Blocked users will no longer receive notifications if they are mentioned in your posts.",
"closing": "This should be sufficient for now so that blocked users can no longer bother you."
}, },
"columns": { "columns": {
"name": "Name", "name": "Name",
"slug": "Slug", "slug": "Slug",
"unblock": "Unblock" "unmute": "Unmute"
}, },
"empty": "So far, you have not blocked anybody.", "empty": "So far, you have not muted anybody.",
"how-to": "You can block other users on their profile page via the content menu.", "how-to": "You can mute other users on their profile page via the content menu.",
"block": "Block user", "mute": "Mute user",
"unblock": "Unblock user", "unmute": "Unmute user",
"unblocked": "{name} is unblocked again" "unmuted": "{name} is unmuted again"
} }
}, },
"admin": { "admin": {

View File

@ -145,26 +145,23 @@
"successAdd": "Social-Media agregó. Perfil actualizado!", "successAdd": "Social-Media agregó. Perfil actualizado!",
"successDelete": "Social-Media borrado. Perfil actualizado!" "successDelete": "Social-Media borrado. Perfil actualizado!"
}, },
"blocked-users": { "muted-users": {
"name": "Usuarios bloqueados", "name": "Usuarios bloqueados",
"explanation": { "explanation": {
"intro": "Si otro usuario ha sido bloqueado por usted, esto es lo que sucede:", "intro": "Si otro usuario ha sido bloqueado por usted, esto es lo que sucede:",
"your-perspective": "Las contribuciones de la persona bloqueada no aparecerán más en su canal de noticias.", "your-perspective": "Las contribuciones de la persona bloqueada no aparecerán más en su canal de noticias.",
"their-perspective": "Viceversa: la persona bloqueada tampoco verá más sus contribuciones en sus noticias.", "search": "Las contribuciones de personas bloqueadas desaparecen de los resultados de búsqueda."
"search": "Las contribuciones de personas bloqueadas desaparecen de los resultados de búsqueda.",
"notifications": "Los usuarios bloqueados no recibirán más notificaciones si se mencionan en sus contribuciones.",
"closing": "Esto debería ser suficiente por ahora para que los usuarios bloqueados no puedan molestarle más."
}, },
"columns": { "columns": {
"name": "Nombre", "name": "Nombre",
"slug": "Alias", "slug": "Alias",
"unblock": "Desbloquear" "unmute": "Desbloquear"
}, },
"empty": "Hasta ahora, no ha bloqueado a nadie.", "empty": "Hasta ahora, no ha bloqueado a nadie.",
"how-to": "Puede bloquear a otros usuarios en la página de perfil de aquellos a través del menú de contenido.", "how-to": "Puede bloquear a otros usuarios en la página de perfil de aquellos a través del menú de contenido.",
"block": "Bloquear usuario", "mute": "Bloquear usuario",
"unblock": "Desbloquear usuario", "unmute": "Desbloquear usuario",
"unblocked": "{name} está desbloqueado nuevamente" "unmuted": "{name} está desbloqueado nuevamente"
}, },
"privacy": { "privacy": {
"name": "Privacidad", "name": "Privacidad",

View File

@ -145,26 +145,23 @@
"successAdd": "Les médias sociaux ont été ajoutés. Profil mis à jour !", "successAdd": "Les médias sociaux ont été ajoutés. Profil mis à jour !",
"successDelete": "Médias sociaux supprimé. Profil mis à jour !" "successDelete": "Médias sociaux supprimé. Profil mis à jour !"
}, },
"blocked-users": { "muted-users": {
"name": "Utilisateurs bloqués", "name": "Utilisateurs bloqués",
"explanation": { "explanation": {
"intro": "Si vous avez bloqué un autre utilisateur, voici ce qui se passe:", "intro": "Si vous avez bloqué un autre utilisateur, voici ce qui se passe:",
"your-perspective": "Les postes de la personne bloquée n'apparaîtront dans votre fil d'actualités.", "your-perspective": "Les postes de la personne bloquée n'apparaîtront dans votre fil d'actualités.",
"their-perspective": "Vice versa: la personne bloquée ne verra plus non plus vos postes dans son fil d'actualités.", "search": "Les postes des personnes bloquées disparaissent de vos résultats de recherche."
"search": "Les postes des personnes bloquées disparaissent de vos résultats de recherche.",
"notifications": "Les utilisateurs bloqués ne recevront plus de notifications s'ils sont mentionnés dans vos postes.",
"closing": "Ceci devrait être suffisant pour le moment afin que les utilisateurs bloqués ne puissent plus vous déranger."
}, },
"columns": { "columns": {
"name": "Nom", "name": "Nom",
"slug": "Slug", "slug": "Slug",
"unblock": "" "unmute": ""
}, },
"empty": "Jusqu'à présent, vous n'avez bloqué personne.", "empty": "Jusqu'à présent, vous n'avez bloqué personne.",
"how-to": "Vous pouvez bloquer d'autres utilisateurs sur leur page de profil via le menu de contenu.", "how-to": "Vous pouvez bloquer d'autres utilisateurs sur leur page de profil via le menu de contenu.",
"block": "Bloquer l'utilisateur", "mute": "Bloquer l'utilisateur",
"unblock": "Débloquer l'utilisateur", "unmute": "Débloquer l'utilisateur",
"unblocked": "{name} est à nouveau débloqué" "unmuted": "{name} est à nouveau débloqué"
}, },
"privacy": { "privacy": {
"name": "", "name": "",

View File

@ -145,26 +145,23 @@
"successAdd": "Social media aggiunti. \nProfilo utente aggiornato ", "successAdd": "Social media aggiunti. \nProfilo utente aggiornato ",
"successDelete": "Social media cancellati. Profilo utente aggiornato!" "successDelete": "Social media cancellati. Profilo utente aggiornato!"
}, },
"blocked-users": { "muted-users": {
"name": "", "name": null,
"explanation": { "explanation": {
"intro": "", "intro": null,
"your-perspective": "", "your-perspective": null,
"their-perspective": "", "search": null
"search": "",
"notifications": "",
"closing": ""
}, },
"columns": { "columns": {
"name": "", "name": null,
"slug": "", "slug": null,
"unblock": "" "unmute": null
}, },
"empty": "", "empty": null,
"how-to": "", "how-to": null,
"block": "", "mute": null,
"unblock": "", "unmute": null,
"unblocked": "" "unmuted": null
}, },
"privacy": { "privacy": {
"name": "", "name": "",

View File

@ -145,26 +145,23 @@
"successAdd": "Mídias sociais adicionadas. Perfil de usuário atualizado!", "successAdd": "Mídias sociais adicionadas. Perfil de usuário atualizado!",
"successDelete": "Mídias sociais removidas. Perfil de usuário atualizado!" "successDelete": "Mídias sociais removidas. Perfil de usuário atualizado!"
}, },
"blocked-users": { "muted-users": {
"name": "Usuários bloqueados", "name": "Usuários bloqueados",
"explanation": { "explanation": {
"intro": "Se outro usuário foi bloqueado por você, isto é o que acontece:", "intro": "Se outro usuário foi bloqueado por você, isto é o que acontece:",
"your-perspective": "As mensagens da pessoa bloqueada não aparecerão mais no seu feed de notícias.", "your-perspective": "As mensagens da pessoa bloqueada não aparecerão mais no seu feed de notícias.",
"their-perspective": "Vice versa: A pessoa bloqueada também não verá mais suas mensagens em seu feed de notícias.", "search": "Publicações de pessoas bloqueadas desaparecem dos resultados da sua pesquisa."
"search": "Publicações de pessoas bloqueadas desaparecem dos resultados da sua pesquisa.",
"notifications": "Usuários bloqueados não receberão mais notificações se forem mencionados em suas mensagens.",
"closing": "Isso deve ser suficiente por enquanto para que os usuários bloqueados não possam mais incomodá-lo."
}, },
"columns": { "columns": {
"name": "Nome", "name": "Nome",
"slug": "Slug", "slug": "Slug",
"unblock": "Desbloquear" "unmute": "Desbloquear"
}, },
"empty": "Até agora, você não bloqueou ninguém.", "empty": "Até agora, você não bloqueou ninguém.",
"how-to": "Você pode bloquear outros usuários em suas páginas de perfil através do menu de conteúdo.", "how-to": "Você pode bloquear outros usuários em suas páginas de perfil através do menu de conteúdo.",
"block": "Bloquear usuário", "mute": "Bloquear usuário",
"unblock": "Desbloquear usuário", "unmute": "Desbloquear usuário",
"unblocked": "{name} está desbloqueado novamente" "unmuted": "{name} está desbloqueado novamente"
}, },
"privacy": { "privacy": {
"name": "Privacidade", "name": "Privacidade",

View File

@ -595,26 +595,23 @@
"placeholder": "Поиск" "placeholder": "Поиск"
}, },
"settings": { "settings": {
"blocked-users": { "muted-users": {
"block": "Блокировать", "mute": "Блокировать",
"columns": { "columns": {
"name": "Имя", "name": "Имя",
"slug": "Псевдоним", "slug": "Псевдоним",
"unblock": "Разблокировать" "unmute": "Разблокировать"
}, },
"empty": "Вы пока никого не блокировали.", "empty": "Вы пока никого не блокировали.",
"explanation": { "explanation": {
"closing": "На данный момент этого должно быть достаточно, чтобы заблокированные пользователи больше вас не беспокоили.",
"intro": "Если блокируете другого пользователя, происходит следующее:", "intro": "Если блокируете другого пользователя, происходит следующее:",
"notifications": "Заблокированные пользователи больше не будут получать уведомления об упоминаниях в ваших постах.",
"search": "Посты заблокированных пользователей не отображаются в результатах поиска.", "search": "Посты заблокированных пользователей не отображаются в результатах поиска.",
"their-perspective": "И наоборот — заблокированный пользователь больше не видит ваши посты в своей ленте.",
"your-perspective": "Посты заблокированного пользователя не отображаются в персональной ленте." "your-perspective": "Посты заблокированного пользователя не отображаются в персональной ленте."
}, },
"how-to": "Вы можете блокировать других пользователей на странице их профиля с помощью меню профиля.", "how-to": "Вы можете блокировать других пользователей на странице их профиля с помощью меню профиля.",
"name": "Заблокированные пользователи", "name": "Заблокированные пользователи",
"unblock": "Разблокировать пользователей", "unmute": "Разблокировать пользователей",
"unblocked": "{name} - снова разблокирован" "unmuted": "{name} - снова разблокирован"
}, },
"data": { "data": {
"labelBio": "О себе", "labelBio": "О себе",

View File

@ -1,6 +1,6 @@
{ {
"name": "human-connection-webapp", "name": "human-connection-webapp",
"version": "0.2.1", "version": "0.2.2",
"description": "Human Connection Frontend", "description": "Human Connection Frontend",
"authors": [ "authors": [
"Grzegorz Leoniec (appinteractive)", "Grzegorz Leoniec (appinteractive)",
@ -84,7 +84,7 @@
"tiptap": "~1.26.6", "tiptap": "~1.26.6",
"tiptap-extensions": "~1.28.6", "tiptap-extensions": "~1.28.6",
"trunc-html": "^1.1.2", "trunc-html": "^1.1.2",
"v-tooltip": "~2.0.2", "v-tooltip": "~2.0.3",
"validator": "^12.1.0", "validator": "^12.1.0",
"vue-count-to": "~1.0.13", "vue-count-to": "~1.0.13",
"vue-infinite-loading": "^2.4.4", "vue-infinite-loading": "^2.4.4",
@ -99,18 +99,18 @@
"@babel/core": "~7.8.3", "@babel/core": "~7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "~7.8.3", "@babel/preset-env": "~7.8.3",
"@storybook/addon-a11y": "^5.3.6", "@storybook/addon-a11y": "^5.3.8",
"@storybook/addon-actions": "^5.3.5", "@storybook/addon-actions": "^5.3.8",
"@storybook/addon-notes": "^5.3.6", "@storybook/addon-notes": "^5.3.8",
"@storybook/vue": "~5.3.6", "@storybook/vue": "~5.3.7",
"@vue/cli-shared-utils": "~4.1.2", "@vue/cli-shared-utils": "~4.1.2",
"@vue/eslint-config-prettier": "~6.0.0", "@vue/eslint-config-prettier": "~6.0.0",
"@vue/server-test-utils": "~1.0.0-beta.30", "@vue/server-test-utils": "~1.0.0-beta.31",
"@vue/test-utils": "~1.0.0-beta.30", "@vue/test-utils": "~1.0.0-beta.31",
"async-validator": "^3.2.3", "async-validator": "^3.2.3",
"babel-core": "~7.0.0-bridge.0", "babel-core": "~7.0.0-bridge.0",
"babel-eslint": "~10.0.3", "babel-eslint": "~10.0.3",
"babel-jest": "~24.9.0", "babel-jest": "~25.1.0",
"babel-loader": "~8.0.6", "babel-loader": "~8.0.6",
"babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-require-context-hook": "^1.0.0",
"babel-preset-vue": "~2.0.2", "babel-preset-vue": "~2.0.2",

View File

@ -19,11 +19,11 @@
@click="blurred = !blurred" @click="blurred = !blurred"
/> />
</aside> </aside>
<hc-user :user="post.author" :date-time="post.createdAt"> <user-teaser :user="post.author" :date-time="post.createdAt">
<template v-slot:dateTime> <template v-slot:dateTime>
<ds-text v-if="post.createdAt !== post.updatedAt">({{ $t('post.edited') }})</ds-text> <ds-text v-if="post.createdAt !== post.updatedAt">({{ $t('post.edited') }})</ds-text>
</template> </template>
</hc-user> </user-teaser>
<client-only> <client-only>
<content-menu <content-menu
placement="bottom-end" placement="bottom-end"
@ -101,7 +101,7 @@ import ContentViewer from '~/components/Editor/ContentViewer'
import HcCategory from '~/components/Category' import HcCategory from '~/components/Category'
import HcHashtag from '~/components/Hashtag/Hashtag' import HcHashtag from '~/components/Hashtag/Hashtag'
import ContentMenu from '~/components/ContentMenu/ContentMenu' import ContentMenu from '~/components/ContentMenu/ContentMenu'
import HcUser from '~/components/User/User' import UserTeaser from '~/components/UserTeaser/UserTeaser'
import HcShoutButton from '~/components/ShoutButton.vue' import HcShoutButton from '~/components/ShoutButton.vue'
import HcCommentForm from '~/components/CommentForm/CommentForm' import HcCommentForm from '~/components/CommentForm/CommentForm'
import HcCommentList from '~/components/CommentList/CommentList' import HcCommentList from '~/components/CommentList/CommentList'
@ -119,7 +119,7 @@ export default {
components: { components: {
HcCategory, HcCategory,
HcHashtag, HcHashtag,
HcUser, UserTeaser,
HcShoutButton, HcShoutButton,
ContentMenu, ContentMenu,
HcCommentForm, HcCommentForm,

View File

@ -11,9 +11,9 @@
style="position: relative; height: auto;" style="position: relative; height: auto;"
> >
<hc-upload v-if="myProfile" :user="user"> <hc-upload v-if="myProfile" :user="user">
<hc-avatar :user="user" class="profile-avatar" size="x-large"></hc-avatar> <user-avatar :user="user" class="profile-avatar" size="large"></user-avatar>
</hc-upload> </hc-upload>
<hc-avatar v-else :user="user" class="profile-avatar" size="x-large" /> <user-avatar v-else :user="user" class="profile-avatar" size="large" />
<!-- Menu --> <!-- Menu -->
<client-only> <client-only>
<content-menu <content-menu
@ -22,8 +22,8 @@
:resource="user" :resource="user"
:is-owner="myProfile" :is-owner="myProfile"
class="user-content-menu" class="user-content-menu"
@block="block" @mute="muteUser"
@unblock="unblock" @unmute="unmuteUser"
/> />
</client-only> </client-only>
<ds-space margin="small"> <ds-space margin="small">
@ -67,14 +67,14 @@
<ds-space margin="small"> <ds-space margin="small">
<template v-if="!myProfile"> <template v-if="!myProfile">
<hc-follow-button <hc-follow-button
v-if="!user.isBlocked" v-if="!user.isMuted"
:follow-id="user.id" :follow-id="user.id"
:is-followed="user.followedByCurrentUser" :is-followed="user.followedByCurrentUser"
@optimistic="optimisticFollow" @optimistic="optimisticFollow"
@update="updateFollow" @update="updateFollow"
/> />
<base-button v-else @click="unblock(user)" class="unblock-user-button"> <base-button v-else @click="unmuteUser(user)" class="unblock-user-button">
{{ $t('settings.blocked-users.unblock') }} {{ $t('settings.muted-users.unmute') }}
</base-button> </base-button>
</template> </template>
</ds-space> </ds-space>
@ -99,7 +99,7 @@
<ds-space v-for="follow in uniq(user.following)" :key="follow.id" margin="x-small"> <ds-space v-for="follow in uniq(user.following)" :key="follow.id" margin="x-small">
<!-- TODO: find better solution for rendering errors --> <!-- TODO: find better solution for rendering errors -->
<client-only> <client-only>
<user :user="follow" :trunc="15" /> <user-teaser :user="follow" />
</client-only> </client-only>
</ds-space> </ds-space>
<ds-space v-if="user.followingCount - user.following.length" margin="small"> <ds-space v-if="user.followingCount - user.following.length" margin="small">
@ -129,7 +129,7 @@
<ds-space v-for="follow in uniq(user.followedBy)" :key="follow.id" margin="x-small"> <ds-space v-for="follow in uniq(user.followedBy)" :key="follow.id" margin="x-small">
<!-- TODO: find better solution for rendering errors --> <!-- TODO: find better solution for rendering errors -->
<client-only> <client-only>
<user :user="follow" :trunc="15" /> <user-teaser :user="follow" />
</client-only> </client-only>
</ds-space> </ds-space>
<ds-space v-if="user.followedByCount - user.followedBy.length" margin="small"> <ds-space v-if="user.followedByCount - user.followedBy.length" margin="small">
@ -157,7 +157,7 @@
<template> <template>
<ds-space v-for="link in socialMediaLinks" :key="link.username" margin="x-small"> <ds-space v-for="link in socialMediaLinks" :key="link.username" margin="x-small">
<a :href="link.url" target="_blank"> <a :href="link.url" target="_blank">
<ds-avatar :image="link.favicon" /> <user-avatar :image="link.favicon" />
{{ link.username }} {{ link.username }}
</a> </a>
</ds-space> </ds-space>
@ -271,7 +271,7 @@
<script> <script>
import uniqBy from 'lodash/uniqBy' import uniqBy from 'lodash/uniqBy'
import User from '~/components/User/User' import UserTeaser from '~/components/UserTeaser/UserTeaser'
import HcPostCard from '~/components/PostCard/PostCard.vue' import HcPostCard from '~/components/PostCard/PostCard.vue'
import HcFollowButton from '~/components/FollowButton.vue' import HcFollowButton from '~/components/FollowButton.vue'
import HcCountTo from '~/components/CountTo.vue' import HcCountTo from '~/components/CountTo.vue'
@ -279,12 +279,12 @@ import HcBadges from '~/components/Badges.vue'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
import ContentMenu from '~/components/ContentMenu/ContentMenu' import ContentMenu from '~/components/ContentMenu/ContentMenu'
import HcUpload from '~/components/Upload' import HcUpload from '~/components/Upload'
import HcAvatar from '~/components/Avatar/Avatar.vue' import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue' import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue' import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
import { profilePagePosts } from '~/graphql/PostQuery' import { profilePagePosts } from '~/graphql/PostQuery'
import UserQuery from '~/graphql/User' import UserQuery from '~/graphql/User'
import { Block, Unblock } from '~/graphql/settings/BlockedUsers' import { muteUser, unmuteUser } from '~/graphql/settings/MutedUsers'
import PostMutations from '~/graphql/PostMutations' import PostMutations from '~/graphql/PostMutations'
import UpdateQuery from '~/components/utils/UpdateQuery' import UpdateQuery from '~/components/utils/UpdateQuery'
@ -297,15 +297,14 @@ const tabToFilterMapping = ({ tab, id }) => {
} }
export default { export default {
name: 'HcUserProfile',
components: { components: {
User, UserTeaser,
HcPostCard, HcPostCard,
HcFollowButton, HcFollowButton,
HcCountTo, HcCountTo,
HcBadges, HcBadges,
HcEmpty, HcEmpty,
HcAvatar, UserAvatar,
ContentMenu, ContentMenu,
HcUpload, HcUpload,
MasonryGrid, MasonryGrid,
@ -396,17 +395,27 @@ export default {
this.posts = [] this.posts = []
this.hasMore = true this.hasMore = true
}, },
async block(user) { async muteUser(user) {
await this.$apollo.mutate({ mutation: Block(), variables: { id: user.id } }) try {
await this.$apollo.mutate({ mutation: muteUser(), variables: { id: user.id } })
} catch (error) {
this.$toast.error(error.message)
} finally {
this.$apollo.queries.User.refetch() this.$apollo.queries.User.refetch()
this.resetPostList() this.resetPostList()
this.$apollo.queries.profilePagePosts.refetch() this.$apollo.queries.profilePagePosts.refetch()
}
}, },
async unblock(user) { async unmuteUser(user) {
await this.$apollo.mutate({ mutation: Unblock(), variables: { id: user.id } }) try {
this.$apollo.mutate({ mutation: unmuteUser(), variables: { id: user.id } })
} catch (error) {
this.$toast.error(error.message)
} finally {
this.$apollo.queries.User.refetch() this.$apollo.queries.User.refetch()
this.resetPostList() this.resetPostList()
this.$apollo.queries.profilePagePosts.refetch() this.$apollo.queries.profilePagePosts.refetch()
}
}, },
pinPost(post) { pinPost(post) {
this.$apollo this.$apollo
@ -515,11 +524,9 @@ export default {
} }
} }
} }
.profile-avatar.ds-avatar { .profile-avatar.user-avatar {
display: block;
margin: auto; margin: auto;
margin-top: -60px; margin-top: -60px;
border: #fff 5px solid;
} }
.page-name-profile-id-slug { .page-name-profile-id-slug {
.ds-flex-item:first-child .content-menu { .ds-flex-item:first-child .content-menu {

View File

@ -40,8 +40,8 @@ export default {
path: `/settings/my-social-media`, path: `/settings/my-social-media`,
}, },
{ {
name: this.$t('settings.blocked-users.name'), name: this.$t('settings.muted-users.name'),
path: `/settings/blocked-users`, path: `/settings/muted-users`,
}, },
{ {
name: this.$t('settings.embeds.name'), name: this.$t('settings.embeds.name'),

View File

@ -1,8 +1,8 @@
import { config, mount, createLocalVue } from '@vue/test-utils' import { config, mount, createLocalVue } from '@vue/test-utils'
import BlockedUsers from './blocked-users.vue' import MutedUsers from './muted-users.vue'
import Styleguide from '@human-connection/styleguide' import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters' import Filters from '~/plugins/vue-filters'
import { Unblock } from '~/graphql/settings/BlockedUsers' import { unmuteUser } from '~/graphql/settings/MutedUsers'
const localVue = createLocalVue() const localVue = createLocalVue()
@ -11,7 +11,7 @@ localVue.use(Filters)
config.stubs['nuxt-link'] = '<span><slot /></span>' config.stubs['nuxt-link'] = '<span><slot /></span>'
describe('blocked-users.vue', () => { describe('muted-users.vue', () => {
let wrapper let wrapper
let mocks let mocks
@ -21,7 +21,7 @@ describe('blocked-users.vue', () => {
$apollo: { $apollo: {
mutate: jest.fn(), mutate: jest.fn(),
queries: { queries: {
blockedUsers: { mutedUsers: {
refetch: jest.fn(), refetch: jest.fn(),
}, },
}, },
@ -35,7 +35,7 @@ describe('blocked-users.vue', () => {
describe('mount', () => { describe('mount', () => {
const Wrapper = () => { const Wrapper = () => {
return mount(BlockedUsers, { mocks, localVue }) return mount(MutedUsers, { mocks, localVue })
} }
beforeEach(() => { beforeEach(() => {
@ -48,18 +48,18 @@ describe('blocked-users.vue', () => {
describe('given a list of blocked users', () => { describe('given a list of blocked users', () => {
beforeEach(() => { beforeEach(() => {
const blockedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }] const mutedUsers = [{ id: 'u1', name: 'John Doe', slug: 'john-doe', avatar: '' }]
wrapper.setData({ blockedUsers }) wrapper.setData({ mutedUsers })
}) })
describe('click unblock', () => { describe('click unmute', () => {
beforeEach(() => { beforeEach(() => {
wrapper.find('button').trigger('click') wrapper.find('button').trigger('click')
}) })
it('calls unblock mutation with given user', () => { it('calls unmute mutation with given user', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ expect(mocks.$apollo.mutate).toHaveBeenCalledWith({
mutation: Unblock(), mutation: unmuteUser(),
variables: { id: 'u1' }, variables: { id: 'u1' },
}) })
}) })

View File

@ -1,31 +1,22 @@
<template> <template>
<div> <div>
<ds-space> <ds-space>
<ds-card :header="$t('settings.blocked-users.name')"> <ds-card :header="$t('settings.muted-users.name')">
<ds-text> <ds-text>
{{ $t('settings.blocked-users.explanation.intro') }} {{ $t('settings.muted-users.explanation.intro') }}
</ds-text> </ds-text>
<ds-list> <ds-list>
<ds-list-item> <ds-list-item>
{{ $t('settings.blocked-users.explanation.your-perspective') }} {{ $t('settings.muted-users.explanation.your-perspective') }}
</ds-list-item> </ds-list-item>
<ds-list-item> <ds-list-item>
{{ $t('settings.blocked-users.explanation.their-perspective') }} {{ $t('settings.muted-users.explanation.search') }}
</ds-list-item>
<ds-list-item>
{{ $t('settings.blocked-users.explanation.search') }}
</ds-list-item>
<ds-list-item>
{{ $t('settings.blocked-users.explanation.notifications') }}
</ds-list-item> </ds-list-item>
</ds-list> </ds-list>
<ds-text>
{{ $t('settings.blocked-users.explanation.closing') }}
</ds-text>
</ds-card> </ds-card>
</ds-space> </ds-space>
<ds-card v-if="blockedUsers && blockedUsers.length"> <ds-card v-if="mutedUsers && mutedUsers.length">
<ds-table :data="blockedUsers" :fields="fields" condensed> <ds-table :data="mutedUsers" :fields="fields" condensed>
<template slot="avatar" slot-scope="scope"> <template slot="avatar" slot-scope="scope">
<nuxt-link <nuxt-link
:to="{ :to="{
@ -33,7 +24,7 @@
params: { id: scope.row.id, slug: scope.row.slug }, params: { id: scope.row.id, slug: scope.row.slug },
}" }"
> >
<hc-avatar :user="scope.row" size="small" /> <user-avatar :user="scope.row" size="small" />
</nuxt-link> </nuxt-link>
</template> </template>
<template slot="name" slot-scope="scope"> <template slot="name" slot-scope="scope">
@ -57,20 +48,20 @@
</nuxt-link> </nuxt-link>
</template> </template>
<template slot="unblock" slot-scope="scope"> <template slot="unmuteUser" slot-scope="scope">
<base-button circle size="small" @click="unblock(scope)" icon="user-plus" /> <base-button circle size="small" @click="unmuteUser(scope)" icon="user-plus" />
</template> </template>
</ds-table> </ds-table>
</ds-card> </ds-card>
<ds-card v-else> <ds-card v-else>
<ds-space> <ds-space>
<ds-placeholder> <ds-placeholder>
{{ $t('settings.blocked-users.empty') }} {{ $t('settings.muted-users.empty') }}
</ds-placeholder> </ds-placeholder>
</ds-space> </ds-space>
<ds-space> <ds-space>
<ds-text align="center"> <ds-text align="center">
{{ $t('settings.blocked-users.how-to') }} {{ $t('settings.muted-users.how-to') }}
</ds-text> </ds-text>
</ds-space> </ds-space>
</ds-card> </ds-card>
@ -78,37 +69,40 @@
</template> </template>
<script> <script>
import { BlockedUsers, Unblock } from '~/graphql/settings/BlockedUsers' import { mutedUsers, unmuteUser } from '~/graphql/settings/MutedUsers'
import HcAvatar from '~/components/Avatar/Avatar.vue' import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
export default { export default {
components: { components: {
HcAvatar, UserAvatar,
}, },
data() { data() {
return { return {
blockedUsers: [], mutedUsers: [],
} }
}, },
computed: { computed: {
fields() { fields() {
return { return {
avatar: '', avatar: '',
name: this.$t('settings.blocked-users.columns.name'), name: this.$t('settings.muted-users.columns.name'),
slug: this.$t('settings.blocked-users.columns.slug'), slug: this.$t('settings.muted-users.columns.slug'),
unblock: this.$t('settings.blocked-users.columns.unblock'), unmuteUser: this.$t('settings.muted-users.columns.unmute'),
} }
}, },
}, },
apollo: { apollo: {
blockedUsers: { query: BlockedUsers, fetchPolicy: 'cache-and-network' }, mutedUsers: { query: mutedUsers, fetchPolicy: 'cache-and-network' },
}, },
methods: { methods: {
async unblock(user) { async unmuteUser(user) {
await this.$apollo.mutate({ mutation: Unblock(), variables: { id: user.row.id } }) await this.$apollo.mutate({
this.$apollo.queries.blockedUsers.refetch() mutation: unmuteUser(),
variables: { id: user.row.id },
})
this.$apollo.queries.mutedUsers.refetch()
const { name } = user.row const { name } = user.row
this.$toast.success(this.$t('settings.blocked-users.unblocked', { name })) this.$toast.success(this.$t('settings.muted-users.unmuted', { name }))
}, },
}, },
} }

File diff suppressed because it is too large Load Diff

656
yarn.lock

File diff suppressed because it is too large Load Diff