mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
test: specs not ok, lint is ok
This commit is contained in:
commit
21fa211e0c
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@ -6,6 +6,7 @@ daysUntilClose: 30
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- bounty
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -8,5 +8,4 @@
|
||||
}
|
||||
],
|
||||
"editor.formatOnSave": false,
|
||||
"eslint.autoFixOnSave": true
|
||||
}
|
||||
|
||||
178
CHANGELOG.md
178
CHANGELOG.md
@ -4,6 +4,176 @@ 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).
|
||||
|
||||
#### [v0.2.1](https://github.com/Human-Connection/Human-Connection/compare/v0.2.0...v0.2.1)
|
||||
|
||||
> 10 January 2020
|
||||
|
||||
- 🍰 Search For Users [`#2262`](https://github.com/Human-Connection/Human-Connection/pull/2262)
|
||||
- Use node LTS in production [`#2713`](https://github.com/Human-Connection/Human-Connection/pull/2713)
|
||||
- build(deps): bump apollo-server from 2.9.15 to 2.9.16 in /backend [`#2718`](https://github.com/Human-Connection/Human-Connection/pull/2718)
|
||||
- build(deps): bump neo4j-graphql-js from 2.11.4 to 2.11.5 in /backend [`#2715`](https://github.com/Human-Connection/Human-Connection/pull/2715)
|
||||
- build(deps-dev): bump apollo-server-testing from 2.9.15 to 2.9.16 in /backend [`#2720`](https://github.com/Human-Connection/Human-Connection/pull/2720)
|
||||
- build(deps): bump @hapi/joi from 17.0.0 to 17.0.2 in /backend [`#2719`](https://github.com/Human-Connection/Human-Connection/pull/2719)
|
||||
- build(deps): bump apollo-server-express from 2.9.15 to 2.9.16 in /backend [`#2717`](https://github.com/Human-Connection/Human-Connection/pull/2717)
|
||||
- build(deps): bump metascraper-url from 5.8.13 to 5.9.5 in /backend [`#2716`](https://github.com/Human-Connection/Human-Connection/pull/2716)
|
||||
- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /backend [`#2706`](https://github.com/Human-Connection/Human-Connection/pull/2706)
|
||||
- build(deps): bump metascraper-lang from 5.8.13 to 5.9.5 in /backend [`#2703`](https://github.com/Human-Connection/Human-Connection/pull/2703)
|
||||
- build(deps): bump date-fns from 2.8.1 to 2.9.0 in /webapp [`#2711`](https://github.com/Human-Connection/Human-Connection/pull/2711)
|
||||
- build(deps): bump metascraper-logo from 5.8.13 to 5.9.5 in /backend [`#2697`](https://github.com/Human-Connection/Human-Connection/pull/2697)
|
||||
- build(deps): bump metascraper-title from 5.8.13 to 5.9.5 in /backend [`#2694`](https://github.com/Human-Connection/Human-Connection/pull/2694)
|
||||
- build(deps): bump metascraper-description from 5.8.15 to 5.9.5 in /backend [`#2690`](https://github.com/Human-Connection/Human-Connection/pull/2690)
|
||||
- build(deps): bump node from 13.5.0-alpine to 13.6.0-alpine in /webapp [`#2708`](https://github.com/Human-Connection/Human-Connection/pull/2708)
|
||||
- build(deps): bump @sentry/node from 5.10.2 to 5.11.0 in /backend [`#2709`](https://github.com/Human-Connection/Human-Connection/pull/2709)
|
||||
- build(deps): bump metascraper-audio from 5.8.13 to 5.9.5 in /backend [`#2707`](https://github.com/Human-Connection/Human-Connection/pull/2707)
|
||||
- build(deps): bump metascraper-image from 5.9.4 to 5.9.5 in /backend [`#2705`](https://github.com/Human-Connection/Human-Connection/pull/2705)
|
||||
- build(deps): bump metascraper-youtube from 5.8.13 to 5.9.5 in /backend [`#2704`](https://github.com/Human-Connection/Human-Connection/pull/2704)
|
||||
- build(deps-dev): bump date-fns from 2.8.1 to 2.9.0 [`#2702`](https://github.com/Human-Connection/Human-Connection/pull/2702)
|
||||
- build(deps): bump metascraper-soundcloud from 5.9.0 to 5.9.5 in /backend [`#2693`](https://github.com/Human-Connection/Human-Connection/pull/2693)
|
||||
- build(deps): bump metascraper-date from 5.8.13 to 5.9.5 in /backend [`#2698`](https://github.com/Human-Connection/Human-Connection/pull/2698)
|
||||
- build(deps): bump neo4j-graphql-js from 2.11.3 to 2.11.4 in /backend [`#2696`](https://github.com/Human-Connection/Human-Connection/pull/2696)
|
||||
- build(deps): bump metascraper-video from 5.8.13 to 5.9.5 in /backend [`#2695`](https://github.com/Human-Connection/Human-Connection/pull/2695)
|
||||
- build(deps): bump metascraper-publisher from 5.8.13 to 5.9.5 in /backend [`#2692`](https://github.com/Human-Connection/Human-Connection/pull/2692)
|
||||
- build(deps): bump metascraper-author from 5.8.13 to 5.9.5 in /backend [`#2691`](https://github.com/Human-Connection/Human-Connection/pull/2691)
|
||||
- build(deps): bump metascraper from 5.9.4 to 5.9.5 in /backend [`#2689`](https://github.com/Human-Connection/Human-Connection/pull/2689)
|
||||
- Changes Text For SignUp [`#2678`](https://github.com/Human-Connection/Human-Connection/pull/2678)
|
||||
- Update de.json [`#2655`](https://github.com/Human-Connection/Human-Connection/pull/2655)
|
||||
- build(deps): bump neode from 0.3.6 to 0.3.7 in /backend [`#2682`](https://github.com/Human-Connection/Human-Connection/pull/2682)
|
||||
- Update neo4j-driver [`#2546`](https://github.com/Human-Connection/Human-Connection/pull/2546)
|
||||
- build(deps): bump merge-graphql-schemas from 1.7.5 to 1.7.6 in /backend [`#2681`](https://github.com/Human-Connection/Human-Connection/pull/2681)
|
||||
- build(deps): bump neo4j-graphql-js from 2.11.2 to 2.11.3 in /backend [`#2680`](https://github.com/Human-Connection/Human-Connection/pull/2680)
|
||||
- build(deps-dev): bump neode from 0.3.6 to 0.3.7 [`#2679`](https://github.com/Human-Connection/Human-Connection/pull/2679)
|
||||
- Parse xss before extracting mentions/hashtags [`#2674`](https://github.com/Human-Connection/Human-Connection/pull/2674)
|
||||
- build(deps): bump metascraper-logo from 5.8.12 to 5.8.13 in /backend [`#2672`](https://github.com/Human-Connection/Human-Connection/pull/2672)
|
||||
- build(deps): bump metascraper from 5.9.0 to 5.9.4 in /backend [`#2668`](https://github.com/Human-Connection/Human-Connection/pull/2668)
|
||||
- build(deps-dev): bump eslint-plugin-jest from 23.2.0 to 23.3.0 in /webapp [`#2671`](https://github.com/Human-Connection/Human-Connection/pull/2671)
|
||||
- build(deps-dev): bump css-loader from 3.4.0 to 3.4.1 in /webapp [`#2669`](https://github.com/Human-Connection/Human-Connection/pull/2669)
|
||||
- build(deps): bump metascraper-image from 5.8.13 to 5.9.4 in /backend [`#2670`](https://github.com/Human-Connection/Human-Connection/pull/2670)
|
||||
- build(deps-dev): bump apollo-server-testing from 2.9.14 to 2.9.15 in /backend [`#2667`](https://github.com/Human-Connection/Human-Connection/pull/2667)
|
||||
- build(deps): bump neo4j-graphql-js from 2.11.0 to 2.11.2 in /backend [`#2666`](https://github.com/Human-Connection/Human-Connection/pull/2666)
|
||||
- build(deps): bump metascraper-title from 5.8.12 to 5.8.13 in /backend [`#2665`](https://github.com/Human-Connection/Human-Connection/pull/2665)
|
||||
- build(deps): bump @hapi/joi from 16.1.8 to 17.0.0 in /backend [`#2664`](https://github.com/Human-Connection/Human-Connection/pull/2664)
|
||||
- build(deps-dev): bump cypress-file-upload from 3.5.1 to 3.5.3 [`#2663`](https://github.com/Human-Connection/Human-Connection/pull/2663)
|
||||
- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.3.0 in /backend [`#2662`](https://github.com/Human-Connection/Human-Connection/pull/2662)
|
||||
- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /webapp [`#2632`](https://github.com/Human-Connection/Human-Connection/pull/2632)
|
||||
- build(deps-dev): bump slug from 2.0.0 to 2.1.0 [`#2647`](https://github.com/Human-Connection/Human-Connection/pull/2647)
|
||||
- build(deps): bump merge-graphql-schemas from 1.7.3 to 1.7.5 in /backend [`#2648`](https://github.com/Human-Connection/Human-Connection/pull/2648)
|
||||
- build(deps): bump metascraper-url from 5.8.12 to 5.8.13 in /backend [`#2637`](https://github.com/Human-Connection/Human-Connection/pull/2637)
|
||||
- build(deps): bump metascraper-publisher from 5.8.12 to 5.8.13 in /backend [`#2636`](https://github.com/Human-Connection/Human-Connection/pull/2636)
|
||||
- build(deps-dev): bump eslint-plugin-jest from 23.1.1 to 23.2.0 in /webapp [`#2642`](https://github.com/Human-Connection/Human-Connection/pull/2642)
|
||||
- build(deps): bump graphql-shield from 7.0.5 to 7.0.7 in /backend [`#2649`](https://github.com/Human-Connection/Human-Connection/pull/2649)
|
||||
- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /backend [`#2650`](https://github.com/Human-Connection/Human-Connection/pull/2650)
|
||||
- build(deps-dev): bump eslint-config-prettier from 6.7.0 to 6.9.0 in /backend [`#2651`](https://github.com/Human-Connection/Human-Connection/pull/2651)
|
||||
- build(deps): bump slug from 2.0.0 to 2.1.0 in /backend [`#2652`](https://github.com/Human-Connection/Human-Connection/pull/2652)
|
||||
- build(deps): bump metascraper from 5.8.12 to 5.9.0 in /backend [`#2654`](https://github.com/Human-Connection/Human-Connection/pull/2654)
|
||||
- build(deps): bump metascraper-description from 5.8.12 to 5.8.15 in /backend [`#2653`](https://github.com/Human-Connection/Human-Connection/pull/2653)
|
||||
- build(deps): bump metascraper-author from 5.8.12 to 5.8.13 in /backend [`#2616`](https://github.com/Human-Connection/Human-Connection/pull/2616)
|
||||
- build(deps): bump metascraper-lang from 5.8.12 to 5.8.13 in /backend [`#2618`](https://github.com/Human-Connection/Human-Connection/pull/2618)
|
||||
- build(deps): bump apollo-server from 2.9.13 to 2.9.15 in /backend [`#2634`](https://github.com/Human-Connection/Human-Connection/pull/2634)
|
||||
- build(deps): bump metascraper-soundcloud from 5.8.15 to 5.9.0 in /backend [`#2638`](https://github.com/Human-Connection/Human-Connection/pull/2638)
|
||||
- build(deps): bump metascraper-video from 5.8.12 to 5.8.13 in /backend [`#2639`](https://github.com/Human-Connection/Human-Connection/pull/2639)
|
||||
- build(deps): bump mustache from 3.2.0 to 3.2.1 in /backend [`#2640`](https://github.com/Human-Connection/Human-Connection/pull/2640)
|
||||
- build(deps): bump slug from 1.1.0 to 2.0.0 in /backend [`#2641`](https://github.com/Human-Connection/Human-Connection/pull/2641)
|
||||
- build(deps-dev): bump @vue/cli-shared-utils from 4.1.1 to 4.1.2 in /webapp [`#2643`](https://github.com/Human-Connection/Human-Connection/pull/2643)
|
||||
- build(deps-dev): bump eslint-plugin-vue from 6.1.1 to 6.1.2 in /webapp [`#2644`](https://github.com/Human-Connection/Human-Connection/pull/2644)
|
||||
- build(deps-dev): bump eslint-plugin-node from 10.0.0 to 11.0.0 in /webapp [`#2645`](https://github.com/Human-Connection/Human-Connection/pull/2645)
|
||||
- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 in /webapp [`#2579`](https://github.com/Human-Connection/Human-Connection/pull/2579)
|
||||
- build(deps): bump metascraper-soundcloud from 5.8.12 to 5.8.15 in /backend [`#2630`](https://github.com/Human-Connection/Human-Connection/pull/2630)
|
||||
- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /webapp [`#2617`](https://github.com/Human-Connection/Human-Connection/pull/2617)
|
||||
- build(deps): bump metascraper-date from 5.8.12 to 5.8.13 in /backend [`#2615`](https://github.com/Human-Connection/Human-Connection/pull/2615)
|
||||
- build(deps): bump metascraper-image from 5.8.12 to 5.8.13 in /backend [`#2614`](https://github.com/Human-Connection/Human-Connection/pull/2614)
|
||||
- build(deps): bump metascraper-youtube from 5.8.12 to 5.8.13 in /backend [`#2612`](https://github.com/Human-Connection/Human-Connection/pull/2612)
|
||||
- build(deps): bump metascraper-audio from 5.8.12 to 5.8.13 in /backend [`#2610`](https://github.com/Human-Connection/Human-Connection/pull/2610)
|
||||
- 🍰 Added Language Tag For Posts [`#2627`](https://github.com/Human-Connection/Human-Connection/pull/2627)
|
||||
- build(deps-dev): bump cypress-plugin-retries from 1.5.0 to 1.5.2 [`#2609`](https://github.com/Human-Connection/Human-Connection/pull/2609)
|
||||
- build(deps-dev): bump eslint from 6.7.2 to 6.8.0 in /backend [`#2613`](https://github.com/Human-Connection/Human-Connection/pull/2613)
|
||||
- remove accidently created ru.json in wrong place [`#2606`](https://github.com/Human-Connection/Human-Connection/pull/2606)
|
||||
- build(deps): bump neo4j from 3.5.13-enterprise to 3.5.14-enterprise in /neo4j [`#2620`](https://github.com/Human-Connection/Human-Connection/pull/2620)
|
||||
- Fixes 2603 [`#2619`](https://github.com/Human-Connection/Human-Connection/pull/2619)
|
||||
- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /webapp [`#2581`](https://github.com/Human-Connection/Human-Connection/pull/2581)
|
||||
- build(deps-dev): bump slug from 1.1.0 to 2.0.0 [`#2621`](https://github.com/Human-Connection/Human-Connection/pull/2621)
|
||||
- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /webapp [`#2624`](https://github.com/Human-Connection/Human-Connection/pull/2624)
|
||||
- build(deps): [security] bump handlebars from 4.1.2 to 4.5.3 in /backend [`#2625`](https://github.com/Human-Connection/Human-Connection/pull/2625)
|
||||
- build(deps-dev): bump cypress from 3.8.0 to 3.8.1 [`#2626`](https://github.com/Human-Connection/Human-Connection/pull/2626)
|
||||
- build(deps-dev): bump eslint-plugin-vue from 6.0.1 to 6.1.1 in /webapp [`#2633`](https://github.com/Human-Connection/Human-Connection/pull/2633)
|
||||
- build(deps-dev): bump @babel/register from 7.7.4 to 7.7.7 [`#2571`](https://github.com/Human-Connection/Human-Connection/pull/2571)
|
||||
- build(deps): bump neo4j-graphql-js from 2.10.2 to 2.11.0 in /backend [`#2600`](https://github.com/Human-Connection/Human-Connection/pull/2600)
|
||||
- build(deps-dev): bump @babel/core from 7.7.5 to 7.7.7 in /backend [`#2590`](https://github.com/Human-Connection/Human-Connection/pull/2590)
|
||||
- build(deps): bump metascraper-url from 5.8.7 to 5.8.12 in /backend [`#2599`](https://github.com/Human-Connection/Human-Connection/pull/2599)
|
||||
- build(deps): bump metascraper-lang from 5.8.10 to 5.8.12 in /backend [`#2598`](https://github.com/Human-Connection/Human-Connection/pull/2598)
|
||||
- build(deps): bump metascraper-audio from 5.8.10 to 5.8.12 in /backend [`#2596`](https://github.com/Human-Connection/Human-Connection/pull/2596)
|
||||
- build(deps): bump node from 13.4.0-alpine to 13.5.0-alpine in /webapp [`#2595`](https://github.com/Human-Connection/Human-Connection/pull/2595)
|
||||
- build(deps-dev): bump storybook-design-token from 0.4.1 to 0.5.0 in /webapp [`#2594`](https://github.com/Human-Connection/Human-Connection/pull/2594)
|
||||
- build(deps): bump graphql-shield from 7.0.4 to 7.0.5 in /backend [`#2593`](https://github.com/Human-Connection/Human-Connection/pull/2593)
|
||||
- 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)
|
||||
- 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)
|
||||
- build(deps-dev): bump storybook-design-token in /webapp [`88d39c4`](https://github.com/Human-Connection/Human-Connection/commit/88d39c4a427cb86527b06201f3f5e96d53ac09a0)
|
||||
- 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)
|
||||
|
||||
> 19 December 2019
|
||||
|
||||
- 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/cli from 7.7.5 to 7.7.7 in /backend [`#2576`](https://github.com/Human-Connection/Human-Connection/pull/2576)
|
||||
- Lokalise: Translations update [`#2563`](https://github.com/Human-Connection/Human-Connection/pull/2563)
|
||||
- build(deps-dev): bump style-resources-loader from 1.3.2 to 1.3.3 in /webapp [`#2580`](https://github.com/Human-Connection/Human-Connection/pull/2580)
|
||||
- build(deps): bump node from 13.3.0-alpine to 13.4.0-alpine in /webapp [`#2577`](https://github.com/Human-Connection/Human-Connection/pull/2577)
|
||||
- build(deps): bump metascraper-title from 5.8.10 to 5.8.12 in /backend [`#2575`](https://github.com/Human-Connection/Human-Connection/pull/2575)
|
||||
- build(deps-dev): bump apollo-server-testing from 2.9.13 to 2.9.14 in /backend [`#2574`](https://github.com/Human-Connection/Human-Connection/pull/2574)
|
||||
- build(deps): bump mustache from 3.1.0 to 3.2.0 in /backend [`#2572`](https://github.com/Human-Connection/Human-Connection/pull/2572)
|
||||
- Blur Images [`#2351`](https://github.com/Human-Connection/Human-Connection/pull/2351)
|
||||
- build(deps-dev): bump @babel/node from 7.7.4 to 7.7.7 in /backend [`#2570`](https://github.com/Human-Connection/Human-Connection/pull/2570)
|
||||
- build(deps-dev): bump @babel/preset-env from 7.7.6 to 7.7.7 [`#2567`](https://github.com/Human-Connection/Human-Connection/pull/2567)
|
||||
- build(deps): bump metascraper-description from 5.8.10 to 5.8.12 in /backend [`#2566`](https://github.com/Human-Connection/Human-Connection/pull/2566)
|
||||
- Add back layout changes/update db_manipulation [`#2544`](https://github.com/Human-Connection/Human-Connection/pull/2544)
|
||||
- build(deps): bump metascraper-soundcloud from 5.8.10 to 5.8.12 in /backend [`#2560`](https://github.com/Human-Connection/Human-Connection/pull/2560)
|
||||
- build(deps): bump metascraper-author from 5.8.7 to 5.8.12 in /backend [`#2559`](https://github.com/Human-Connection/Human-Connection/pull/2559)
|
||||
- build(deps): bump metascraper from 5.8.9 to 5.8.12 in /backend [`#2558`](https://github.com/Human-Connection/Human-Connection/pull/2558)
|
||||
- build(deps): bump metascraper-youtube from 5.8.9 to 5.8.12 in /backend [`#2547`](https://github.com/Human-Connection/Human-Connection/pull/2547)
|
||||
- build(deps): bump metascraper-video from 5.8.10 to 5.8.12 in /backend [`#2557`](https://github.com/Human-Connection/Human-Connection/pull/2557)
|
||||
- build(deps): bump metascraper-date from 5.8.7 to 5.8.12 in /backend [`#2555`](https://github.com/Human-Connection/Human-Connection/pull/2555)
|
||||
- build(deps): bump metascraper-logo from 5.8.10 to 5.8.12 in /backend [`#2554`](https://github.com/Human-Connection/Human-Connection/pull/2554)
|
||||
- build(deps): bump apollo-server-express from 2.9.13 to 2.9.14 in /backend [`#2551`](https://github.com/Human-Connection/Human-Connection/pull/2551)
|
||||
- build(deps-dev): bump css-loader from 3.3.2 to 3.4.0 in /webapp [`#2550`](https://github.com/Human-Connection/Human-Connection/pull/2550)
|
||||
- build(deps-dev): bump cypress-cucumber-preprocessor from 1.18.0 to 1.19.0 [`#2548`](https://github.com/Human-Connection/Human-Connection/pull/2548)
|
||||
- Lokalise: Translations update [`#2545`](https://github.com/Human-Connection/Human-Connection/pull/2545)
|
||||
- build(deps): bump metascraper-youtube from 5.8.9 to 5.8.10 in /backend [`#2522`](https://github.com/Human-Connection/Human-Connection/pull/2522)
|
||||
- build(deps): bump metascraper-title from 5.8.7 to 5.8.10 in /backend [`#2525`](https://github.com/Human-Connection/Human-Connection/pull/2525)
|
||||
- build(deps): bump metascraper-lang from 5.8.9 to 5.8.10 in /backend [`#2531`](https://github.com/Human-Connection/Human-Connection/pull/2531)
|
||||
- build(deps): bump tiptap-extensions from 1.28.5 to 1.28.6 in /webapp [`#2535`](https://github.com/Human-Connection/Human-Connection/pull/2535)
|
||||
- Fix maintenance service/LocaleSwitch import [`#2542`](https://github.com/Human-Connection/Human-Connection/pull/2542)
|
||||
- build(deps): bump apollo-client from 2.6.4 to 2.6.8 in /webapp [`#2523`](https://github.com/Human-Connection/Human-Connection/pull/2523)
|
||||
- build(deps): bump stack-utils from 1.0.2 to 2.0.1 in /webapp [`#2521`](https://github.com/Human-Connection/Human-Connection/pull/2521)
|
||||
- build(deps): bump metascraper-soundcloud from 5.8.9 to 5.8.10 in /backend [`#2520`](https://github.com/Human-Connection/Human-Connection/pull/2520)
|
||||
- Update neode [`#2539`](https://github.com/Human-Connection/Human-Connection/pull/2539)
|
||||
- build(deps-dev): bump eslint-plugin-prettier from 3.1.1 to 3.1.2 in /webapp [`#2519`](https://github.com/Human-Connection/Human-Connection/pull/2519)
|
||||
- build(deps): bump apollo-cache-inmemory from 1.6.3 to 1.6.5 in /webapp [`#2527`](https://github.com/Human-Connection/Human-Connection/pull/2527)
|
||||
- build(deps): bump neo4j-graphql-js from 2.10.1 to 2.10.2 in /backend [`#2530`](https://github.com/Human-Connection/Human-Connection/pull/2530)
|
||||
- build(deps): bump metascraper-image from 5.8.7 to 5.8.10 in /backend [`#2532`](https://github.com/Human-Connection/Human-Connection/pull/2532)
|
||||
- build(deps): bump apollo-cache-inmemory from 1.6.3 to 1.6.5 in /backend [`#2534`](https://github.com/Human-Connection/Human-Connection/pull/2534)
|
||||
- build(deps): bump metascraper-video from 5.8.9 to 5.8.10 in /backend [`#2536`](https://github.com/Human-Connection/Human-Connection/pull/2536)
|
||||
- build(deps): bump tiptap from 1.26.5 to 1.26.6 in /webapp [`#2537`](https://github.com/Human-Connection/Human-Connection/pull/2537)
|
||||
- build(deps-dev): bump vue-loader from 15.7.2 to 15.8.3 in /webapp [`#2538`](https://github.com/Human-Connection/Human-Connection/pull/2538)
|
||||
- Refactor: content menu [`#2512`](https://github.com/Human-Connection/Human-Connection/pull/2512)
|
||||
- build(deps): bump metascraper-audio from 5.8.7 to 5.8.10 in /backend [`#2524`](https://github.com/Human-Connection/Human-Connection/pull/2524)
|
||||
- build(deps): bump metascraper-description from 5.8.7 to 5.8.10 in /backend [`#2518`](https://github.com/Human-Connection/Human-Connection/pull/2518)
|
||||
- build(deps-dev): bump eslint-plugin-prettier from 3.1.1 to 3.1.2 in /backend [`#2517`](https://github.com/Human-Connection/Human-Connection/pull/2517)
|
||||
- build(deps): bump apollo-client from 2.6.4 to 2.6.8 in /backend [`#2516`](https://github.com/Human-Connection/Human-Connection/pull/2516)
|
||||
- build(deps): bump metascraper-logo from 5.8.7 to 5.8.10 in /backend [`#2515`](https://github.com/Human-Connection/Human-Connection/pull/2515)
|
||||
- Fix duplicate fragment `user` issue [`#2511`](https://github.com/Human-Connection/Human-Connection/pull/2511)
|
||||
- fix: editor not visible in server-side-rendering [`#2513`](https://github.com/Human-Connection/Human-Connection/pull/2513)
|
||||
- Update it.json [`#2507`](https://github.com/Human-Connection/Human-Connection/pull/2507)
|
||||
- Fix: User.name is not non-nullable [`#2510`](https://github.com/Human-Connection/Human-Connection/pull/2510)
|
||||
- Update to version 0.1.13 [`#2506`](https://github.com/Human-Connection/Human-Connection/pull/2506)
|
||||
- Lokalise: update of webapp/locales/ru.json [`b70ff73`](https://github.com/Human-Connection/Human-Connection/commit/b70ff73bba98d28494c55ed12161288b1efa1516)
|
||||
- Separate concerns in components [`d74d207`](https://github.com/Human-Connection/Human-Connection/commit/d74d2072ba41af6170d79d7dc2e24f9ebab15771)
|
||||
- Fix failing component tests [`b79c292`](https://github.com/Human-Connection/Human-Connection/commit/b79c292ef4f76b307131566c24d849c2e35c8089)
|
||||
|
||||
#### [v0.1.13](https://github.com/Human-Connection/Human-Connection/compare/v0.1.12...v0.1.13)
|
||||
|
||||
> 13 December 2019
|
||||
@ -30,8 +200,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- build(deps): bump cookie-universal-nuxt from 2.0.19 to 2.1.0 in /webapp [`#2490`](https://github.com/Human-Connection/Human-Connection/pull/2490)
|
||||
- Update to version 0.1.12 [`#2483`](https://github.com/Human-Connection/Human-Connection/pull/2483)
|
||||
- Lokalise: update of locale/ru.json [`60b3035`](https://github.com/Human-Connection/Human-Connection/commit/60b3035a3d475cb481130c6fe94f2901711a4053)
|
||||
- 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)
|
||||
- Fix this annoying bug with a tested helper [`e24d803`](https://github.com/Human-Connection/Human-Connection/commit/e24d8035b13040dc29f5f9cb033de8c1a401ac34)
|
||||
|
||||
#### [v0.1.12](https://github.com/Human-Connection/Human-Connection/compare/v0.1.10...v0.1.12)
|
||||
|
||||
@ -196,9 +366,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
- 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)
|
||||
- Merge pull request #2078 from Human-Connection/fix-2042-back-link [`#2042`](https://github.com/Human-Connection/Human-Connection/issues/2042)
|
||||
- Tell github-linguists to ignore snapshots [`978347b`](https://github.com/Human-Connection/Human-Connection/commit/978347ba7b5a6aa1bc915ada972ffffa2816d37c)
|
||||
- Lokalise: update of webapp/locales/ru.json [`906e851`](https://github.com/Human-Connection/Human-Connection/commit/906e8518bf060134150187fb1574ac50ffd502f6)
|
||||
- Lokalise: update of webapp/locales/ru.json [`3e52ee0`](https://github.com/Human-Connection/Human-Connection/commit/3e52ee090c88c357b796895370d126f8bb5529f0)
|
||||
- Move components to components/features [`2357028`](https://github.com/Human-Connection/Human-Connection/commit/235702867d97b44dac37f8059f9194e23ba7f47d)
|
||||
- Basic Search Is Working For Users And Posts [`72e4d0a`](https://github.com/Human-Connection/Human-Connection/commit/72e4d0abbcb9abab07f3fd12876453eb1de5da4c)
|
||||
- Add missing unit tests/refactor code [`b364065`](https://github.com/Human-Connection/Human-Connection/commit/b3640659bb608cc34edc6f2aca350f07dd2b9ce6)
|
||||
|
||||
#### [v0.1.10](https://github.com/Human-Connection/Human-Connection/compare/v0.1.9...v0.1.10)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:12.12.0-alpine as base
|
||||
FROM node:lts-alpine as base
|
||||
LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
@ -32,20 +32,20 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^16.1.8",
|
||||
"@sentry/node": "^5.10.2",
|
||||
"@hapi/joi": "^17.0.2",
|
||||
"@sentry/node": "^5.11.1",
|
||||
"apollo-cache-inmemory": "~1.6.5",
|
||||
"apollo-client": "~2.6.8",
|
||||
"apollo-link-context": "~1.0.19",
|
||||
"apollo-link-http": "~1.5.16",
|
||||
"apollo-server": "~2.9.13",
|
||||
"apollo-server-express": "^2.9.14",
|
||||
"apollo-server": "~2.9.16",
|
||||
"apollo-server-express": "^2.9.16",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~6.0.3",
|
||||
"date-fns": "2.8.1",
|
||||
"date-fns": "2.9.0",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.2.0",
|
||||
"express": "^4.17.1",
|
||||
@ -55,41 +55,41 @@
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-shield": "~7.0.4",
|
||||
"graphql-shield": "~7.0.7",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"helmet": "~3.21.2",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.3",
|
||||
"metascraper": "^5.8.12",
|
||||
"metascraper-audio": "^5.8.10",
|
||||
"metascraper-author": "^5.8.12",
|
||||
"merge-graphql-schemas": "^1.7.6",
|
||||
"metascraper": "^5.10.2",
|
||||
"metascraper-audio": "^5.9.5",
|
||||
"metascraper-author": "^5.9.5",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
"metascraper-date": "^5.8.12",
|
||||
"metascraper-description": "^5.8.10",
|
||||
"metascraper-image": "^5.8.10",
|
||||
"metascraper-lang": "^5.8.10",
|
||||
"metascraper-date": "^5.10.3",
|
||||
"metascraper-description": "^5.9.5",
|
||||
"metascraper-image": "^5.9.5",
|
||||
"metascraper-lang": "^5.9.5",
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.8.12",
|
||||
"metascraper-publisher": "^5.8.7",
|
||||
"metascraper-soundcloud": "^5.8.12",
|
||||
"metascraper-title": "^5.8.10",
|
||||
"metascraper-url": "^5.8.7",
|
||||
"metascraper-video": "^5.8.12",
|
||||
"metascraper-youtube": "^5.8.12",
|
||||
"metascraper-logo": "^5.9.5",
|
||||
"metascraper-publisher": "^5.10.3",
|
||||
"metascraper-soundcloud": "^5.9.5",
|
||||
"metascraper-title": "^5.10.3",
|
||||
"metascraper-url": "^5.9.5",
|
||||
"metascraper-video": "^5.9.5",
|
||||
"metascraper-youtube": "^5.9.5",
|
||||
"minimatch": "^3.0.4",
|
||||
"mustache": "^3.1.0",
|
||||
"neo4j-driver": "~1.7.6",
|
||||
"neo4j-graphql-js": "^2.10.2",
|
||||
"neode": "^0.3.6",
|
||||
"mustache": "^3.2.1",
|
||||
"neo4j-driver": "^4.0.1",
|
||||
"neo4j-graphql-js": "^2.11.5",
|
||||
"neode": "^0.3.7",
|
||||
"node-fetch": "~2.6.0",
|
||||
"nodemailer": "^6.4.2",
|
||||
"nodemailer-html-to-text": "^3.1.0",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.0",
|
||||
"sanitize-html": "~1.20.1",
|
||||
"slug": "~1.1.0",
|
||||
"slug": "~2.1.0",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~3.3.3",
|
||||
"validator": "^12.1.0",
|
||||
@ -97,24 +97,24 @@
|
||||
"xregexp": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.7.5",
|
||||
"@babel/core": "~7.7.5",
|
||||
"@babel/node": "~7.7.4",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.7.4",
|
||||
"@babel/preset-env": "~7.7.6",
|
||||
"@babel/register": "~7.7.0",
|
||||
"apollo-server-testing": "~2.9.13",
|
||||
"@babel/cli": "~7.8.3",
|
||||
"@babel/core": "~7.8.3",
|
||||
"@babel/node": "~7.8.3",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
|
||||
"@babel/preset-env": "~7.8.3",
|
||||
"@babel/register": "~7.8.3",
|
||||
"apollo-server-testing": "~2.9.16",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.3",
|
||||
"babel-jest": "~24.9.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~6.0.5",
|
||||
"eslint": "~6.7.2",
|
||||
"eslint-config-prettier": "~6.7.0",
|
||||
"eslint": "~6.8.0",
|
||||
"eslint-config-prettier": "~6.9.0",
|
||||
"eslint-config-standard": "~14.1.0",
|
||||
"eslint-plugin-import": "~2.19.1",
|
||||
"eslint-plugin-jest": "~23.1.1",
|
||||
"eslint-plugin-node": "~10.0.0",
|
||||
"eslint-plugin-import": "~2.20.0",
|
||||
"eslint-plugin-jest": "~23.6.0",
|
||||
"eslint-plugin-node": "~11.0.0",
|
||||
"eslint-plugin-prettier": "~3.1.2",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
"eslint-plugin-standard": "~4.0.1",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { v1 as neo4j } from 'neo4j-driver'
|
||||
import neo4j from 'neo4j-driver'
|
||||
import CONFIG from './../config'
|
||||
import Neode from 'neode'
|
||||
import models from '../models'
|
||||
|
||||
@ -7,7 +7,6 @@ import sluggify from './sluggifyMiddleware'
|
||||
import excerpt from './excerptMiddleware'
|
||||
import xss from './xssMiddleware'
|
||||
import permissions from './permissionsMiddleware'
|
||||
import user from './user/userMiddleware'
|
||||
import includedFields from './includedFieldsMiddleware'
|
||||
import orderBy from './orderByMiddleware'
|
||||
import validation from './validation/validationMiddleware'
|
||||
@ -18,25 +17,25 @@ import sentry from './sentryMiddleware'
|
||||
|
||||
export default schema => {
|
||||
const middlewares = {
|
||||
permissions,
|
||||
sentry,
|
||||
permissions,
|
||||
xss,
|
||||
activityPub,
|
||||
validation,
|
||||
sluggify,
|
||||
excerpt,
|
||||
email,
|
||||
notifications,
|
||||
hashtags,
|
||||
xss,
|
||||
softDelete,
|
||||
user,
|
||||
includedFields,
|
||||
orderBy,
|
||||
email,
|
||||
}
|
||||
|
||||
let order = [
|
||||
'sentry',
|
||||
'permissions',
|
||||
'xss',
|
||||
// 'activityPub', disabled temporarily
|
||||
'validation',
|
||||
'sluggify',
|
||||
@ -44,9 +43,7 @@ export default schema => {
|
||||
'email',
|
||||
'notifications',
|
||||
'hashtags',
|
||||
'xss',
|
||||
'softDelete',
|
||||
'user',
|
||||
'includedFields',
|
||||
'orderBy',
|
||||
]
|
||||
|
||||
@ -85,6 +85,8 @@ export default shield(
|
||||
Query: {
|
||||
'*': deny,
|
||||
findPosts: allow,
|
||||
findUsers: allow,
|
||||
findResources: allow,
|
||||
embed: allow,
|
||||
Category: allow,
|
||||
Tag: allow,
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import createOrUpdateLocations from '../nodes/locations'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
SignupVerification: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
await createOrUpdateLocations(result.id, args.locationName, context.driver)
|
||||
return result
|
||||
},
|
||||
UpdateUser: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
||||
return result
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -39,5 +39,6 @@ module.exports = {
|
||||
default: () => new Date().toISOString(),
|
||||
},
|
||||
language: { type: 'string', allow: [null] },
|
||||
imageBlurred: { type: 'boolean', default: false },
|
||||
imageAspectRatio: { type: 'float', default: 1.0 },
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ const debugCypher = Debug('human-connection:neo4j:cypher')
|
||||
const debugStats = Debug('human-connection:neo4j:stats')
|
||||
|
||||
export default function log(response) {
|
||||
const { statement, counters, resultConsumedAfter, resultAvailableAfter } = response.summary
|
||||
const { text, parameters } = statement
|
||||
const { counters, resultConsumedAfter, resultAvailableAfter, query } = response.summary
|
||||
const { text, parameters } = query
|
||||
debugCypher('%s', text)
|
||||
debugCypher('%o', parameters)
|
||||
debugStats('%o', counters)
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
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
|
||||
}
|
||||
@ -1,33 +1,10 @@
|
||||
import uuid from 'uuid/v4'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import { isEmpty } from 'lodash'
|
||||
import fileUpload from './fileUpload'
|
||||
import { getBlockedUsers, getBlockedByUsers } from './users.js'
|
||||
import { mergeWith, isArray, isEmpty } from 'lodash'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const filterForBlockedUsers = async (params, context) => {
|
||||
if (!context.user) return params
|
||||
const [blockedUsers, blockedByUsers] = await Promise.all([
|
||||
getBlockedUsers(context),
|
||||
getBlockedByUsers(context),
|
||||
])
|
||||
const badIds = [...blockedByUsers.map(b => b.id), ...blockedUsers.map(b => b.id)]
|
||||
if (!badIds.length) return params
|
||||
|
||||
params.filter = mergeWith(
|
||||
params.filter,
|
||||
{
|
||||
author_not: { id_in: badIds },
|
||||
},
|
||||
(objValue, srcValue) => {
|
||||
if (isArray(objValue)) {
|
||||
return objValue.concat(srcValue)
|
||||
}
|
||||
},
|
||||
)
|
||||
return params
|
||||
}
|
||||
import { filterForBlockedUsers } from './helpers/filterForBlockedUsers'
|
||||
|
||||
const maintainPinnedPosts = params => {
|
||||
const pinnedPostFilter = { pinned: true }
|
||||
@ -341,6 +318,7 @@ export default {
|
||||
'language',
|
||||
'pinnedAt',
|
||||
'pinned',
|
||||
'imageBlurred',
|
||||
'imageAspectRatio',
|
||||
],
|
||||
hasMany: {
|
||||
|
||||
@ -5,6 +5,7 @@ import encryptPassword from '../../helpers/encryptPassword'
|
||||
import generateNonce from './helpers/generateNonce'
|
||||
import existingEmailAddress from './helpers/existingEmailAddress'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
import createOrUpdateLocations from './users/location'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -22,7 +23,9 @@ export default {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
SignupVerification: async (_parent, args) => {
|
||||
SignupVerification: async (_parent, args, context) => {
|
||||
const { driver } = context
|
||||
const session = driver.session()
|
||||
const { termsAndConditionsAgreedVersion } = args
|
||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||
@ -51,11 +54,14 @@ export default {
|
||||
emailAddress.relateTo(user, 'belongsTo'),
|
||||
emailAddress.update({ verifiedAt: new Date().toISOString() }),
|
||||
])
|
||||
await createOrUpdateLocations(args.id, args.locationName, session)
|
||||
return user.toJson()
|
||||
} catch (e) {
|
||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
throw new UserInputError('User with this slug already exists!')
|
||||
throw new UserInputError(e.message)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
74
backend/src/schema/resolvers/searches.js
Normal file
74
backend/src/schema/resolvers/searches.js
Normal file
@ -0,0 +1,74 @@
|
||||
import log from './helpers/databaseLogger'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
findResources: async (_parent, args, context, _resolveInfo) => {
|
||||
const { query, limit } = args
|
||||
const { id: thisUserId } = context.user
|
||||
// see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description
|
||||
const myQuery = query
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[[@#:*~\\$|^\]?/"'(){}+?!,.-;]/g, '')
|
||||
.split(' ')
|
||||
.map(s => (s.toLowerCase().match(/^(not|and|or)$/) ? '"' + s + '"' : s + '*'))
|
||||
.join(' ')
|
||||
const postCypher = `
|
||||
CALL db.index.fulltext.queryNodes('post_fulltext_search', $query)
|
||||
YIELD node as resource, score
|
||||
MATCH (resource)<-[:WROTE]-(author:User)
|
||||
WHERE score >= 0.5
|
||||
AND NOT (
|
||||
author.deleted = true OR author.disabled = true
|
||||
OR resource.deleted = true OR resource.disabled = true
|
||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(author)
|
||||
)
|
||||
WITH resource, author,
|
||||
[(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
|
||||
[(resource)<-[:SHOUTED]-(user:User) | user] as shouter
|
||||
RETURN resource {
|
||||
.*,
|
||||
__typename: labels(resource)[0],
|
||||
author: properties(author),
|
||||
commentsCount: toString(size(comments)),
|
||||
shoutedCount: toString(size(shouter))
|
||||
}
|
||||
LIMIT $limit
|
||||
`
|
||||
|
||||
const userCypher = `
|
||||
CALL db.index.fulltext.queryNodes('user_fulltext_search', $query)
|
||||
YIELD node as resource, score
|
||||
MATCH (resource)
|
||||
WHERE score >= 0.5
|
||||
AND NOT (resource.deleted = true OR resource.disabled = true
|
||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(resource))
|
||||
RETURN resource {.*, __typename: labels(resource)[0]}
|
||||
LIMIT $limit
|
||||
`
|
||||
|
||||
const session = context.driver.session()
|
||||
const searchResultPromise = session.readTransaction(async transaction => {
|
||||
const postTransactionResponse = transaction.run(postCypher, {
|
||||
query: myQuery,
|
||||
limit,
|
||||
thisUserId,
|
||||
})
|
||||
const userTransactionResponse = transaction.run(userCypher, {
|
||||
query: myQuery,
|
||||
limit,
|
||||
thisUserId,
|
||||
})
|
||||
return Promise.all([postTransactionResponse, userTransactionResponse])
|
||||
})
|
||||
|
||||
try {
|
||||
const [postResults, userResults] = await searchResultPromise
|
||||
log(postResults)
|
||||
log(userResults)
|
||||
return [...postResults.records, ...userResults.records].map(r => r.get('resource'))
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import log from './helpers/databaseLogger'
|
||||
import createOrUpdateLocations from './users/location'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -127,6 +128,7 @@ export default {
|
||||
})
|
||||
try {
|
||||
const [user] = await writeTxResultPromise
|
||||
await createOrUpdateLocations(params.id, params.locationName, session)
|
||||
return user
|
||||
} catch (error) {
|
||||
throw new UserInputError(error.message)
|
||||
|
||||
@ -2,8 +2,8 @@ import request from 'request'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import isEmpty from 'lodash/isEmpty'
|
||||
import Debug from 'debug'
|
||||
import asyncForEach from '../../helpers/asyncForEach'
|
||||
import CONFIG from './../../config'
|
||||
import asyncForEach from '../../../helpers/asyncForEach'
|
||||
import CONFIG from '../../../config'
|
||||
|
||||
const debug = Debug('human-connection:location')
|
||||
|
||||
@ -57,16 +57,12 @@ const createLocation = async (session, mapboxData) => {
|
||||
}
|
||||
mutation += ' RETURN l.id'
|
||||
|
||||
try {
|
||||
await session.writeTransaction(transaction => {
|
||||
return transaction.run(mutation, data)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
const createOrUpdateLocations = async (userId, locationName, session) => {
|
||||
if (isEmpty(locationName)) {
|
||||
return
|
||||
}
|
||||
@ -99,7 +95,6 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
throw new UserInputError('locationName is invalid')
|
||||
}
|
||||
|
||||
const session = driver.session()
|
||||
if (data.place_type.length > 1) {
|
||||
data.id = 'region.' + data.id.split('.')[1]
|
||||
}
|
||||
@ -110,7 +105,6 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
if (data.context) {
|
||||
await asyncForEach(data.context, async ctx => {
|
||||
await createLocation(session, ctx)
|
||||
try {
|
||||
await session.writeTransaction(transaction => {
|
||||
return transaction.run(
|
||||
`
|
||||
@ -125,13 +119,9 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
)
|
||||
})
|
||||
parent = ctx
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
// delete all current locations from user and add new location
|
||||
try {
|
||||
await session.writeTransaction(transaction => {
|
||||
return transaction.run(
|
||||
`
|
||||
@ -145,9 +135,6 @@ const createOrUpdateLocations = async (userId, locationName, driver) => {
|
||||
{ userId: userId, locationId: data.id },
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export default createOrUpdateLocations
|
||||
@ -1,8 +1,8 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import Factory from '../../../seed/factories'
|
||||
import { getNeode, getDriver } from '../../../bootstrap/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import createServer from '../../../server'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
@ -1,23 +1,3 @@
|
||||
type Query {
|
||||
isLoggedIn: Boolean!
|
||||
# Get the currently logged in User based on the given JWT Token
|
||||
currentUser: User
|
||||
findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('full_text_search', $query)
|
||||
YIELD node as post, score
|
||||
MATCH (post)<-[:WROTE]-(user:User)
|
||||
WHERE score >= 0.2
|
||||
AND NOT user.deleted = true AND NOT user.disabled = true
|
||||
AND NOT post.deleted = true AND NOT post.disabled = true
|
||||
AND NOT user.id in COALESCE($filter.author_not.id_in, [])
|
||||
RETURN post
|
||||
LIMIT $limit
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
# Get a JWT Token for the given Email and password
|
||||
login(email: String!, password: String!): String!
|
||||
|
||||
@ -82,6 +82,7 @@ input _PostFilter {
|
||||
emotions_none: _PostEMOTEDFilter
|
||||
emotions_single: _PostEMOTEDFilter
|
||||
emotions_every: _PostEMOTEDFilter
|
||||
imageBlurred: Boolean
|
||||
}
|
||||
|
||||
enum _PostOrdering {
|
||||
@ -127,6 +128,7 @@ type Post {
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
imageBlurred: Boolean
|
||||
pinnedAt: String @cypher(
|
||||
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
|
||||
)
|
||||
@ -140,7 +142,6 @@ type Post {
|
||||
LIMIT 10
|
||||
"""
|
||||
)
|
||||
|
||||
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
@ -183,6 +184,7 @@ type Mutation {
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
contentExcerpt: String
|
||||
imageBlurred: Boolean
|
||||
imageAspectRatio: Float
|
||||
): Post
|
||||
UpdatePost(
|
||||
@ -196,6 +198,7 @@ type Mutation {
|
||||
visibility: Visibility
|
||||
language: String
|
||||
categoryIds: [ID]
|
||||
imageBlurred: Boolean
|
||||
imageAspectRatio: Float
|
||||
): Post
|
||||
DeletePost(id: ID!): Post
|
||||
@ -217,6 +220,7 @@ type Query {
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
language: String
|
||||
imageBlurred: Boolean
|
||||
first: Int
|
||||
offset: Int
|
||||
orderBy: [_PostOrdering]
|
||||
@ -226,4 +230,18 @@ type Query {
|
||||
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
|
||||
PostsEmotionsByCurrentUser(postId: ID!): [String]
|
||||
profilePagePosts(filter: _PostFilter, first: Int, offset: Int, orderBy: [_PostOrdering]): [Post]
|
||||
findPosts(query: String!, limit: Int = 10, filter: _PostFilter): [Post]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('post_fulltext_search', $query)
|
||||
YIELD node as post, score
|
||||
MATCH (post)<-[:WROTE]-(user:User)
|
||||
WHERE score >= 0.2
|
||||
AND NOT user.deleted = true AND NOT user.disabled = true
|
||||
AND NOT post.deleted = true AND NOT post.disabled = true
|
||||
AND NOT user.id in COALESCE($filter.author_not.id_in, [])
|
||||
RETURN post
|
||||
LIMIT $limit
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
5
backend/src/schema/types/type/Search.gql
Normal file
5
backend/src/schema/types/type/Search.gql
Normal file
@ -0,0 +1,5 @@
|
||||
union SearchResult = Post | User
|
||||
|
||||
type Query {
|
||||
findResources(query: String!, limit: Int = 5): [SearchResult]!
|
||||
}
|
||||
@ -161,7 +161,20 @@ type Query {
|
||||
): [User]
|
||||
|
||||
blockedUsers: [User]
|
||||
isLoggedIn: Boolean!
|
||||
currentUser: User
|
||||
findUsers(query: String!,limit: Int = 10, filter: _UserFilter): [User]!
|
||||
@cypher(
|
||||
statement: """
|
||||
CALL db.index.fulltext.queryNodes('user_fulltext_search', $query)
|
||||
YIELD node as post, score
|
||||
MATCH (user)
|
||||
WHERE score >= 0.2
|
||||
AND NOT user.deleted = true AND NOT user.disabled = true
|
||||
RETURN user
|
||||
LIMIT $limit
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@ -179,7 +192,6 @@ type Mutation {
|
||||
termsAndConditionsAgreedAt: String
|
||||
allowEmbedIframes: Boolean
|
||||
showShoutsPublicly: Boolean
|
||||
|
||||
locale: String
|
||||
): User
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ export default function create() {
|
||||
visibility: 'public',
|
||||
deleted: false,
|
||||
categoryIds: [],
|
||||
imageBlurred: false,
|
||||
imageAspectRatio: 1.333,
|
||||
}
|
||||
args = {
|
||||
|
||||
@ -352,6 +352,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
language: sample(languages),
|
||||
image: faker.image.unsplash.food(300, 169),
|
||||
categoryIds: ['cat16'],
|
||||
imageBlurred: true,
|
||||
imageAspectRatio: 300 / 169,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
@ -398,6 +399,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
author: dewey,
|
||||
id: 'p10',
|
||||
categoryIds: ['cat10'],
|
||||
imageBlurred: true,
|
||||
}),
|
||||
factory.create('Post', {
|
||||
author: louie,
|
||||
@ -444,6 +446,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
$title: String!
|
||||
$content: String!
|
||||
$categoryIds: [ID]
|
||||
$imageBlurred: Boolean
|
||||
$imageAspectRatio: Float
|
||||
) {
|
||||
CreatePost(
|
||||
@ -451,6 +454,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
title: $title
|
||||
content: $content
|
||||
categoryIds: $categoryIds
|
||||
imageBlurred: $imageBlurred
|
||||
imageAspectRatio: $imageAspectRatio
|
||||
) {
|
||||
id
|
||||
|
||||
2108
backend/yarn.lock
2108
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@ Given('I am logged in with a {string} role', role => {
|
||||
|
||||
When('I click on "Report Post" from the content menu of the post', () => {
|
||||
cy.contains('.ds-card', davidIrvingPostTitle)
|
||||
.find('.content-menu-trigger')
|
||||
.find('.content-menu .base-button')
|
||||
.click({force: true})
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
@ -54,7 +54,7 @@ When('I click on "Report Post" from the content menu of the post', () => {
|
||||
|
||||
When('I click on "Report User" from the content menu in the user info box', () => {
|
||||
cy.contains('.ds-card', davidIrvingPostTitle)
|
||||
.get('.user-content-menu .content-menu-trigger')
|
||||
.get('.user-content-menu .base-button')
|
||||
.click({ force: true })
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
import { When, Then } from "cypress-cucumber-preprocessor/steps";
|
||||
When("I search for {string}", value => {
|
||||
cy.get("#nav-search")
|
||||
cy.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type(value);
|
||||
});
|
||||
|
||||
Then("I should have one post in the select dropdown", () => {
|
||||
cy.get(".input .ds-select-dropdown").should($li => {
|
||||
Then("I should have one item in the select dropdown", () => {
|
||||
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||
expect($li).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
Then("the search has no results", () => {
|
||||
cy.get(".input .ds-select-dropdown").should($li => {
|
||||
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||
expect($li).to.have.length(1);
|
||||
});
|
||||
cy.get(".ds-select-dropdown").should("contain", 'Nothing found');
|
||||
cy.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type("{esc}");
|
||||
});
|
||||
|
||||
Then("I should see the following posts in the select dropdown:", table => {
|
||||
@ -24,26 +27,33 @@ Then("I should see the following posts in the select dropdown:", table => {
|
||||
});
|
||||
});
|
||||
|
||||
Then("I should see the following users in the select dropdown:", table => {
|
||||
cy.get(".ds-heading").should("contain", "Users");
|
||||
table.hashes().forEach(({ slug }) => {
|
||||
cy.get(".ds-select-dropdown").should("contain", slug);
|
||||
});
|
||||
});
|
||||
|
||||
When("I type {string} and press Enter", value => {
|
||||
cy.get("#nav-search")
|
||||
cy.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type(value)
|
||||
.type("{enter}", { force: true });
|
||||
});
|
||||
|
||||
When("I type {string} and press escape", value => {
|
||||
cy.get("#nav-search")
|
||||
cy.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type(value)
|
||||
.type("{esc}");
|
||||
});
|
||||
|
||||
Then("the search field should clear", () => {
|
||||
cy.get("#nav-search").should("have.text", "");
|
||||
cy.get(".searchable-input .ds-select-search").should("have.text", "");
|
||||
});
|
||||
|
||||
When("I select an entry", () => {
|
||||
cy.get(".input .ds-select-dropdown ul li")
|
||||
When("I select a post entry", () => {
|
||||
cy.get(".searchable-input .search-post")
|
||||
.first()
|
||||
.trigger("click");
|
||||
});
|
||||
@ -75,3 +85,13 @@ Then(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Then("I select a user entry", () => {
|
||||
cy.get(".searchable-input .userinfo")
|
||||
.first()
|
||||
.trigger("click");
|
||||
})
|
||||
|
||||
Then("I should be on the user's profile", () => {
|
||||
cy.location("pathname").should("eq", "/profile/user-for-search/search-for-me")
|
||||
})
|
||||
|
||||
@ -119,7 +119,7 @@ Then('they should be able to see my social media links', () => {
|
||||
})
|
||||
|
||||
When('I delete a social media link', () => {
|
||||
cy.get("a[name='delete']")
|
||||
cy.get(".base-button[title='Delete']")
|
||||
.click()
|
||||
})
|
||||
|
||||
@ -129,7 +129,7 @@ Then('it gets deleted successfully', () => {
|
||||
})
|
||||
|
||||
When('I start editing a social media link', () => {
|
||||
cy.get("a[name='edit']")
|
||||
cy.get(".base-button[title='Edit']")
|
||||
.click()
|
||||
})
|
||||
|
||||
|
||||
@ -243,7 +243,7 @@ When("I type in the following text:", text => {
|
||||
});
|
||||
|
||||
Then("I select a category", () => {
|
||||
cy.get("span")
|
||||
cy.get(".base-button")
|
||||
.contains("Just for Fun")
|
||||
.click();
|
||||
});
|
||||
@ -449,7 +449,7 @@ When("I ", name => {
|
||||
When(
|
||||
"I click on {string} from the content menu in the user info box",
|
||||
button => {
|
||||
cy.get(".user-content-menu .content-menu-trigger").click();
|
||||
cy.get(".user-content-menu .base-button").click();
|
||||
cy.get(".popover .ds-menu-item-link")
|
||||
.contains(button)
|
||||
.click({
|
||||
|
||||
@ -9,18 +9,23 @@ Feature: Search
|
||||
| id | title | content |
|
||||
| p1 | 101 Essays that will change the way you think | 101 Essays, of course! |
|
||||
| p2 | No searched for content | will be found in this post, I guarantee |
|
||||
And we have the following user accounts:
|
||||
| slug | name | id |
|
||||
| search-for-me | Search for me | user-for-search |
|
||||
| not-to-be-found | Not to be found | just-an-id |
|
||||
|
||||
Given I am logged in
|
||||
|
||||
Scenario: Search for specific words
|
||||
When I search for "Essays"
|
||||
Then I should have one post in the select dropdown
|
||||
Then I should have one item in the select dropdown
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| 101 Essays that will change the way you think |
|
||||
|
||||
Scenario: Press enter starts search
|
||||
When I type "Essa" and press Enter
|
||||
Then I should have one post in the select dropdown
|
||||
When I type "Es" and press Enter
|
||||
Then I should have one item in the select dropdown
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| 101 Essays that will change the way you think |
|
||||
@ -31,11 +36,20 @@ Feature: Search
|
||||
|
||||
Scenario: Select entry goes to post
|
||||
When I search for "Essays"
|
||||
And I select an entry
|
||||
And I select a post entry
|
||||
Then I should be on the post's page
|
||||
|
||||
Scenario: Select dropdown content
|
||||
When I search for "Essays"
|
||||
Then I should have one post in the select dropdown
|
||||
Then I should have one item in the select dropdown
|
||||
Then I should see posts with the searched-for term in the select dropdown
|
||||
And I should not see posts without the searched-for term in the select dropdown
|
||||
|
||||
Scenario: Search for users
|
||||
Given I search for "Search"
|
||||
Then I should have one item in the select dropdown
|
||||
And I should see the following users in the select dropdown:
|
||||
| slug |
|
||||
| search-for-me |
|
||||
And I select a user entry
|
||||
Then I should be on the user's profile
|
||||
814
locale/ru.json
814
locale/ru.json
@ -1,814 +0,0 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "Отменить",
|
||||
"create": "Создать",
|
||||
"delete": "Удалить",
|
||||
"edit": "Редактировать",
|
||||
"loading": "загрузка",
|
||||
"loadMore": "Загрузить ещё",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"admin": {
|
||||
"categories": {
|
||||
"categoryName": "Имя",
|
||||
"name": "Категории",
|
||||
"postCount": "Посты"
|
||||
},
|
||||
"dashboard": {
|
||||
"comments": "Комментарии",
|
||||
"follows": "Подписки",
|
||||
"invites": "Приглашения",
|
||||
"name": "Панель управления",
|
||||
"notifications": "Уведомления",
|
||||
"organizations": "Организации",
|
||||
"posts": "Посты",
|
||||
"projects": "Проекты",
|
||||
"shouts": "Выкрики",
|
||||
"users": "Пользователи"
|
||||
},
|
||||
"donations": {
|
||||
"goal": "Необходимы ежемесячные пожертвования",
|
||||
"name": "Информация о пожертвованиях",
|
||||
"progress": "Пожертвования собраны",
|
||||
"successfulUpdate": "Информация о пожертвованиях успешно обновлена!"
|
||||
},
|
||||
"hashtags": {
|
||||
"name": "Хэштеги",
|
||||
"nameOfHashtag": "Имя",
|
||||
"number": "№",
|
||||
"tagCount": "Посты",
|
||||
"tagCountUnique": "Пользователи"
|
||||
},
|
||||
"invites": {
|
||||
"description": "Приглашения — это замечательный способ завести друзей в своей сети ...",
|
||||
"name": "Пригласить пользователей",
|
||||
"title": "Пригласить людей"
|
||||
},
|
||||
"name": "Администрирование",
|
||||
"notifications": {
|
||||
"name": "Уведомления"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Организации"
|
||||
},
|
||||
"pages": {
|
||||
"name": "Страницы"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Настройки"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Теги",
|
||||
"tagCount": "Посты",
|
||||
"tagCountUnique": "Пользователи"
|
||||
},
|
||||
"users": {
|
||||
"empty": "Пользователи не найдены",
|
||||
"form": {
|
||||
"placeholder": "Электронная почта, имя или описание"
|
||||
},
|
||||
"name": "Пользователи",
|
||||
"table": {
|
||||
"columns": {
|
||||
"createdAt": "Дата создания",
|
||||
"email": "Эл. почта",
|
||||
"name": "Имя",
|
||||
"number": "№",
|
||||
"role": "Роль",
|
||||
"slug": "Slug"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"code-of-conduct": {
|
||||
"consequences": {
|
||||
"description": "Если участник сообщества проявляет неприемлемое поведение, ответственные операторы, модераторы и администраторы сети могут принять соответствующие меры, включая, но не ограничиваясь:",
|
||||
"list": {
|
||||
"0": "Просьба о немедленном прекращении неприемлемого поведения",
|
||||
"1": "Блокирование или удаление комментариев",
|
||||
"2": "Временное исключение из соответствующего поста или другого контента",
|
||||
"3": "Блокирование или удаление контента",
|
||||
"4": "Временный запрет на добавление контента",
|
||||
"5": "Временное исключение из сети",
|
||||
"6": "Окончательное исключение из сети",
|
||||
"7": "Передача сведений о нарушениях немецкого законодательства.",
|
||||
"8": "Пропаганда или поощрение такого поведения."
|
||||
},
|
||||
"title": "Последствия неприемлемого поведения"
|
||||
},
|
||||
"expected-behaviour": {
|
||||
"description": "Мы ожидаем и требуем от всех членов сообщества предерживаться следующих правил поведения:",
|
||||
"list": {
|
||||
"0": "Будьте внимательны и уважительны к тому, что пишете и делаете.",
|
||||
"1": "Пытайтесь сотрудничать, прежде чем возникнет конфликт.",
|
||||
"2": "Воздерживайтесь от поведения и высказываний, унижающих достоинство, дискриминационного или преследующего характера.",
|
||||
"3": "Будьте внимательны к своему окружению и другим участникам. Информируйте лидеров сообщества об опасных ситуациях, когда кто-либо попал в беду или нарушает настоящий Кодекс поведения, даже если они кажутся незначительными."
|
||||
},
|
||||
"title": "Ожидаемое поведение"
|
||||
},
|
||||
"get-help": "Если вы стали жертвой или свидетелем неприемлемого поведения или у вас возникли какие-либо другие проблемы, пожалуйста, как можно скорее сообщите об этом организатору сообщества и укажите ссылку на соответствующий контент:",
|
||||
"preamble": {
|
||||
"description": "Human Connection - это некоммерческая социальная сеть знаний и действий следующего поколения. Создана людьми – для людей. С открытым исходным кодом, справедливая и прозрачная. Для позитивных локальных и глобальных изменений во всех сферах жизни. Мы полностью перестраиваем публичный обмен знаниями, идеями и проектами. Функции Human Connection объединяют людей – офлайн и онлайн – так что мы можем сделать мир лучше.",
|
||||
"title": "Преамбула"
|
||||
},
|
||||
"purpose": {
|
||||
"description": "С помощью этих правил поведения мы регулируем основные принципы поведения в нашей социальной сети. При этом Устав ООН по правам человека является нашей ориентацией и лежит в основе нашего понимания ценностей. Правила поведения служат руководящими принципами для личного выступления и общения друг с другом. Любой, кто является активным пользователем в сети Human Connection, публикует сообщения, комментирует или контактирует с другими пользователями, в том числе за пределами сети, признает эти правила поведения обязательными.",
|
||||
"title": "Цель"
|
||||
},
|
||||
"subheader": "социальной сети \"Human Connection gGmbH\"",
|
||||
"unacceptable-behaviour": {
|
||||
"description": "В нашем сообществе неприемлемо следующее поведение:",
|
||||
"list": {
|
||||
"0": "Дискриминационные посты, комментарии, высказывания или оскорбления, в частности, касающиеся пола, сексуальной ориентации, расы, религии, политической или мировоззренческой ориентации, или инвалидности.",
|
||||
"1": "Публикация или ссылка на явно порнографические материалы.",
|
||||
"2": "Прославление или умаление жестоких, или бесчеловечных актов насилия.",
|
||||
"3": "Публикация персональных данных других лиц без их согласия или угрозы (\"Доксинг\").",
|
||||
"4": "Преднамеренное запугивание или преследование.",
|
||||
"5": "Рекламировать продукты и услуги с коммерческим намерением.",
|
||||
"6": "Преступное поведение или нарушение немецкого права.",
|
||||
"7": "Одобрение или поощрение недопустимого поведения."
|
||||
},
|
||||
"title": "Недопустимое поведение"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
"content": {
|
||||
"unavailable-placeholder": "...этот комментарий больше не доступен"
|
||||
},
|
||||
"delete": "Удалить комментарий",
|
||||
"edit": "Редактировать комментарий",
|
||||
"edited": "Изменен",
|
||||
"menu": {
|
||||
"delete": "Удалить комментарий",
|
||||
"edit": "Редактировать комментарий"
|
||||
},
|
||||
"show": {
|
||||
"less": "показать меньше",
|
||||
"more": "показать больше"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"category": "Категория ::: Категории ::: Категории",
|
||||
"comment": "Комментарий::: Комментарии::: Комментарии",
|
||||
"letsTalk": "Давай поговорим",
|
||||
"loading": "загрузка",
|
||||
"loadMore": "Загрузить ещё",
|
||||
"moreInfo": "Больше информации",
|
||||
"name": "Имя",
|
||||
"organization": "Организация ::: Организации ::: Организации",
|
||||
"post": "Пост ::: Посты ::: Посты",
|
||||
"project": "Проект ::: Проекты ::: Проекты",
|
||||
"reportContent": "Отчет",
|
||||
"shout": "Выкрик ::: Выкрики ::: Выкрики",
|
||||
"tag": "Тег ::: Теги ::: Теги",
|
||||
"takeAction": "Принять меры",
|
||||
"user": "Пользователь ::: Пользователи ::: Пользователи",
|
||||
"validations": {
|
||||
"categories": "Выберите от одной то трех категорий",
|
||||
"email": "должен быть корректный адрес электронной почты",
|
||||
"url": "должен быть корректный URL"
|
||||
},
|
||||
"versus": "Против"
|
||||
},
|
||||
"components": {
|
||||
"enter-nonce": {
|
||||
"form": {
|
||||
"description": "Откройте папку \\\"Входящие\\\" и введите код из сообщения.",
|
||||
"next": "Продолжить",
|
||||
"nonce": "Введите код",
|
||||
"validations": {
|
||||
"length": "длина должна быть 6 символов"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password-reset": {
|
||||
"change-password": {
|
||||
"error": "Смена пароля не удалась. Может быть, код безопасности был неправильным?",
|
||||
"help": "В случае возникновения проблем, не стесняйся обращаться за помощью, отправив нам письмо по адресу:",
|
||||
"success": "Смена пароля прошла успешно!"
|
||||
},
|
||||
"request": {
|
||||
"form": {
|
||||
"description": "На указанный адрес электронной почты будет отправлено сообщение с инструкциями для сброса пароля.",
|
||||
"submit": "Отправить запрос",
|
||||
"submitted": "На адрес <b>{email}<\/b>было отправлено электронное письмо с дальнейшими инструкциями"
|
||||
},
|
||||
"title": "Сбросить пароль"
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"create-user-account": {
|
||||
"error": "Не удалось создать учетную запись!",
|
||||
"help": "Может быть, подтверждение было недействительным? В случае возникновения проблем, не стесняйтесь обращаться за помощью, отправив нам письмо по электронной почте:",
|
||||
"success": "Учетная запись успешно создана!",
|
||||
"title": "Создать учетную запись"
|
||||
},
|
||||
"signup": {
|
||||
"form": {
|
||||
"data-privacy": "Я прочитал и понял <a href=\"https:\/\/human-connection.org\/datenschutz\/\" target=\"_blank\"><ds-text bold color=\"primary\" >Заявление о конфиденциальности<\/ds-text><\/a>",
|
||||
"description": "Для начала работы введите свой адрес электронной почты:",
|
||||
"errors": {
|
||||
"email-exists": "Уже есть учетная запись пользователя с этим адресом электронной почты!",
|
||||
"invalid-invitation-token": "Похоже, что приглашение уже было использовано. Ссылку из приглашения можно использовать только один раз."
|
||||
},
|
||||
"invitation-code": "Код приглашения: <b>{code}<\/b>",
|
||||
"minimum-age": "Мне 18 лет или более",
|
||||
"no-commercial": "У меня нет коммерческих намерений, и я не представляю коммерческое предприятие или организацию.",
|
||||
"no-political": "Я не от имени какой-либо партии или политической организации в сети.",
|
||||
"submit": "Создать учетную запись",
|
||||
"success": "Письмо со ссылкой для завершения регистрации было отправлено на <b> {email} <\/b>",
|
||||
"terms-and-condition": "Принимаю <a href=\"\/terms-and-conditions\"><ds-text bold color=\"primary\" >Условия и положения<\/ds-text><\/a>."
|
||||
},
|
||||
"title": "Присоединяйся к Human Connection!",
|
||||
"unavailable": "К сожалению, публичная регистрация пользователей на этом сервере сейчас недоступна."
|
||||
}
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
"categories": {
|
||||
"infoSelectedNoOfMaxCategories": "Выбрано {chosen} из {max} категорий"
|
||||
},
|
||||
"category": {
|
||||
"name": {
|
||||
"animal-protection": "Защита животных",
|
||||
"art-culture-sport": "Искусство, культура и спорт",
|
||||
"consumption-sustainability": "Потребление и стабильность",
|
||||
"cooperation-development": "Сотрудничество и развитие",
|
||||
"democracy-politics": "Демократия и политика",
|
||||
"economy-finances": "Экономика и финансы",
|
||||
"education-sciences": "Образование и наука",
|
||||
"energy-technology": "Энергия и технологии",
|
||||
"environment-nature": "Окружающая среда и природа",
|
||||
"freedom-of-speech": "Свобода слова",
|
||||
"global-peace-nonviolence": "Глобальный мир и борьба с насилием",
|
||||
"happiness-values": "Счастье и ценности",
|
||||
"health-wellbeing": "Здоровье и благополучие",
|
||||
"human-rights-justice": "Права человека и справедливость",
|
||||
"it-internet-data-privacy": "ИТ, интернет и конфиденциальность",
|
||||
"just-for-fun": "Просто для удовольствия"
|
||||
}
|
||||
},
|
||||
"delete": "Удалить",
|
||||
"edit": "Редактировать",
|
||||
"emotions-label": {
|
||||
"angry": "Возмутительно",
|
||||
"cry": "Плачу",
|
||||
"funny": "Смешно",
|
||||
"happy": "Счастлив",
|
||||
"surprised": "Удивлен"
|
||||
},
|
||||
"filterALL": "Просмотреть все посты",
|
||||
"filterFollow": "Показать сообщения пользователей, на которых я подписан",
|
||||
"languageSelectLabel": "Язык",
|
||||
"languageSelectText": "Выберите язык",
|
||||
"newPost": "Создать пост",
|
||||
"success": "Сохранено!",
|
||||
"teaserImage": {
|
||||
"cropperConfirm": "Подтвердить"
|
||||
},
|
||||
"title": "Заголовок"
|
||||
},
|
||||
"delete": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"message": "Вы уверены, что хотите удалить комментарий \"<b>{name}<\/b>\"?",
|
||||
"success": "Комментарий успешно удален!",
|
||||
"title": "Удалить комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"message": "Вы уверены, что хотите удалить пост \"<b>{name}<\/b>\"?",
|
||||
"success": "Пост успешно удален!",
|
||||
"title": "Удалить пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Удалить"
|
||||
},
|
||||
"disable": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"message": "Вы действительно хотите отключить комментарий от «<b>{name}<\/b>»?",
|
||||
"title": "Отключить комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"message": "Вы действительно хотите отключить пост «<b>{name}<\/b>»?",
|
||||
"title": "Отключить пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Отключить",
|
||||
"success": "Успешно отключен",
|
||||
"user": {
|
||||
"message": "Вы действительно хотите отключить пользователя «<b>{name}<\/b>»?",
|
||||
"title": "Отключить пользователя",
|
||||
"type": "Пользователь"
|
||||
}
|
||||
},
|
||||
"donations": {
|
||||
"amount-of-total": "{amount} из {total} € собрано",
|
||||
"donate-now": "Пожертвуйте сейчас",
|
||||
"donations-for": "Пожертвования для"
|
||||
},
|
||||
"editor": {
|
||||
"embed": {
|
||||
"always_allow": "Всегда отображать содержимое сторонних производителей (эту настройку можно изменить в любое время).",
|
||||
"data_privacy_info": "Ваши данные еще не были переданы третьим лицам. Если вы воспроизведёте это видео, следующий провайдер, вероятно, зарегистрирует ваши данные пользователя:",
|
||||
"data_privacy_warning": "Предупреждение о конфиденциальности данных!",
|
||||
"play_now": "Смотреть сейчас"
|
||||
},
|
||||
"hashtag": {
|
||||
"addHashtag": "Новый хэштег",
|
||||
"addLetter": "Введите букву",
|
||||
"noHashtagsFound": "Хэштеги не найдены"
|
||||
},
|
||||
"mention": {
|
||||
"noUsersFound": "Пользователи не найдены"
|
||||
},
|
||||
"placeholder": "Поделитесь своими вдохновляющими мыслями ..."
|
||||
},
|
||||
"filter-menu": {
|
||||
"clearSearch": "Очистить поиск",
|
||||
"hashtag-search": "Поиск по #{hashtag}",
|
||||
"title": "Ваш фильтр пузыря"
|
||||
},
|
||||
"filter-posts": {
|
||||
"categories": {
|
||||
"all": "Все",
|
||||
"header": "Категории"
|
||||
},
|
||||
"followers": {
|
||||
"label": "Мои подписки"
|
||||
},
|
||||
"general": {
|
||||
"header": "Другие фильтры"
|
||||
},
|
||||
"language": {
|
||||
"all": "Все",
|
||||
"header": "Языки"
|
||||
}
|
||||
},
|
||||
"followButton": {
|
||||
"follow": "Подписаться",
|
||||
"following": "Вы подписаны"
|
||||
},
|
||||
"index": {
|
||||
"change-filter-settings": "Измените настройки фильтра, чтобы получить больше результатов.",
|
||||
"no-results": "Посты не найдены."
|
||||
},
|
||||
"login": {
|
||||
"copy": "Авторизуйтесь, если у вас уже есть учетная запись Human Connection.",
|
||||
"email": "Электронная почта",
|
||||
"failure": "Неверный адрес электронной почты или пароль.",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"hello": "Здравствуйте",
|
||||
"login": "Вход",
|
||||
"logout": "Выйти",
|
||||
"moreInfo": "Что такое Human Connection?",
|
||||
"moreInfoHint": "на страницу проекта",
|
||||
"moreInfoURL": "https:\/\/human-connection.org\/en\/",
|
||||
"no-account": "У вас нет аккаунта?",
|
||||
"password": "Пароль",
|
||||
"register": "Зарегистрируйтесь",
|
||||
"success": "Вы вошли в систему!"
|
||||
},
|
||||
"maintenance": {
|
||||
"explanation": "В данный момент мы проводим плановое техническое обслуживание, пожалуйста, повторите попытку позже.",
|
||||
"questions": "Любые вопросы или сообщения о проблемах отправляйте на электронную почту",
|
||||
"title": "Human Connection на техническом обслуживании"
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Модерация",
|
||||
"reports": {
|
||||
"author": "Автор",
|
||||
"content": "Содержа́ние",
|
||||
"decideButton": "Подтвердить",
|
||||
"decided": "Решил",
|
||||
"decideModal": {
|
||||
"cancel": "Отменить",
|
||||
"Comment": {
|
||||
"disable": {
|
||||
"message": "Вы действительно хотите, чтобы комментарий \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить комментарий"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы действительно хотите, чтобы комментарий \"<b>{name}<\/b>\" остановиться и <b>включен<\/b>?",
|
||||
"title": "Окончательно включить комментарий"
|
||||
}
|
||||
},
|
||||
"Post": {
|
||||
"disable": {
|
||||
"message": "Вы действительно хотите, чтобы пост \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить пост"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы действительно хотите, чтобы пост \"<b>{name}<\/b>\" остановиться и <b>включен<\/b>?",
|
||||
"title": "Окончательно включить пост"
|
||||
}
|
||||
},
|
||||
"submit": "Подтвердить решение",
|
||||
"User": {
|
||||
"disable": {
|
||||
"message": "Вы действительно хотите, чтобы пользователь \"<b>{name}<\/b>\" остановиться и <b>отключен<\/b>?",
|
||||
"title": "Окончательно отключить пользователя"
|
||||
},
|
||||
"enable": {
|
||||
"message": "Вы уверены, что хотите поделиться пользователем \"<b>{name}<\/b>\"?",
|
||||
"title": "Окончательно включить пост"
|
||||
}
|
||||
}
|
||||
},
|
||||
"decision": "Решение",
|
||||
"DecisionSuccess": "Решил успешно!",
|
||||
"disabled": "Отключен",
|
||||
"disabledAt": "Отключено на",
|
||||
"disabledBy": "Отключил(а)",
|
||||
"empty": "Поздравляю, модерировать нечего.",
|
||||
"enabled": "Включен",
|
||||
"enabledAt": "Включено на",
|
||||
"enabledBy": "Включено с",
|
||||
"filterLabel": {
|
||||
"all": "Все",
|
||||
"closed": "Закрыто",
|
||||
"reviewed": "Рассмотренный",
|
||||
"unreviewed": "Нерассмотренный"
|
||||
},
|
||||
"moreDetails": "Посмотреть подробности",
|
||||
"name": "Отчеты",
|
||||
"noDecision": "Нет решения!",
|
||||
"numberOfUsers": "{count} пользователи",
|
||||
"previousDecision": "Предыдущее решение:",
|
||||
"reasonCategory": "Категория",
|
||||
"reasonDescription": "Описание",
|
||||
"reportedOn": "Дата",
|
||||
"reporter": "Сообщил(а)",
|
||||
"status": "Текущее состояние",
|
||||
"submitter": "Сообщил(а)"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"comment": "Комментарий",
|
||||
"content": "Контент",
|
||||
"empty": "Извините, на данный момент у вас нет уведомлений.",
|
||||
"filterLabel": {
|
||||
"all": "Все",
|
||||
"read": "Прочитанные",
|
||||
"unread": "Непрочитанные"
|
||||
},
|
||||
"pageLink": "Все уведомления",
|
||||
"post": "Пост",
|
||||
"reason": {
|
||||
"commented_on_post": "Комментарий к посту...",
|
||||
"mentioned_in_comment": "Упоминание в комментарии....",
|
||||
"mentioned_in_post": "Упоминание в посте...."
|
||||
},
|
||||
"title": "Уведомления",
|
||||
"user": "Пользователь"
|
||||
},
|
||||
"post": {
|
||||
"comment": {
|
||||
"submit": "Комментировать",
|
||||
"submitted": "Комментарий отправлен",
|
||||
"updated": "Изменения сохраненные"
|
||||
},
|
||||
"edited": "Изменен",
|
||||
"menu": {
|
||||
"delete": "Удалить пост",
|
||||
"edit": "Редактировать пост",
|
||||
"pin": "Закрепить пост",
|
||||
"pinnedSuccessfully": "Пост больше не закреплен!",
|
||||
"unpin": "Открепить пост",
|
||||
"unpinnedSuccessfully": "Пост успешно не закреплено!"
|
||||
},
|
||||
"moreInfo": {
|
||||
"description": "Здесь содержится дополнительная информация по теме.",
|
||||
"name": "Дополнительная информация",
|
||||
"title": "Дополнительная информация",
|
||||
"titleOfCategoriesSection": "Категории",
|
||||
"titleOfHashtagsSection": "Хэштеги",
|
||||
"titleOfRelatedContributionsSection": "Похожие посты"
|
||||
},
|
||||
"name": "Пост",
|
||||
"pinned": "Объявление",
|
||||
"takeAction": {
|
||||
"name": "Действовать"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"commented": "Прокомментированные",
|
||||
"follow": "Подписаться",
|
||||
"followers": "Подписчики",
|
||||
"following": "Подписки",
|
||||
"invites": {
|
||||
"description": "Введите адрес электронной почты для приглашения.",
|
||||
"emailPlaceholder": "Электронная почта для приглашения",
|
||||
"title": "Пригласите кого-нибудь в Human Connection!"
|
||||
},
|
||||
"memberSince": "Участник с",
|
||||
"name": "Мой профиль",
|
||||
"network": {
|
||||
"andMore": "и ещё {number} человек... ::: и ещё {number} человека... ::: и ещё {number} человек...",
|
||||
"followedBy": "ваши подписчики:",
|
||||
"followedByNobody": "у вас нет подписчиков.",
|
||||
"following": "подписан на:",
|
||||
"followingNobody": "ни на кого не подписан.",
|
||||
"title": "Сеть"
|
||||
},
|
||||
"shouted": "С выкриками",
|
||||
"socialMedia": "Где еще я могу найти",
|
||||
"userAnonym": "Анонимный"
|
||||
},
|
||||
"quotes": {
|
||||
"african": {
|
||||
"author": "Африканская пословица",
|
||||
"quote": "Много маленьких людей делают много маленьких вещей во многих маленьких местах, что может изменить мир до неузнаваемости."
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"error": "Вы уже сообщили о комментарии!",
|
||||
"message": "Вы уверены, что хотите показать комментарий \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы уверены, что хотите показать пост \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать пост",
|
||||
"type": "Пост"
|
||||
},
|
||||
"submit": "Показать",
|
||||
"success": "Успешно показан!",
|
||||
"user": {
|
||||
"error": "Вы уже сообщили о пользователе!",
|
||||
"message": "Вы уверены, что хотите показать пользователя \"<b>{name}<\/b>\"?",
|
||||
"title": "Показать пользователя",
|
||||
"type": "Пользователь"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"cancel": "Отменить",
|
||||
"comment": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы действительно хотите сообщить о посте \"<b> {name} <\/b>\"?",
|
||||
"title": "Пожаловаться на комментарий",
|
||||
"type": "Комментарий"
|
||||
},
|
||||
"contribution": {
|
||||
"error": "Вы уже сообщили о посте!",
|
||||
"message": "Вы действительно хотите сообщить о посте \"<b>{name}<\/b>\"?",
|
||||
"title": "Пожаловаться на пост",
|
||||
"type": "Пожаловаться на пост"
|
||||
},
|
||||
"reason": {
|
||||
"category": {
|
||||
"invalid": "Пожалуйста, выберите подходящую категорию",
|
||||
"label": "Выберите категорию:",
|
||||
"options": {
|
||||
"advert_products_services_commercial": "Реклама продуктов и услуг с коммерческим намерением.",
|
||||
"criminal_behavior_violation_german_law": "Уголовное поведение или нарушении немецкого права.",
|
||||
"discrimination_etc": "Дискриминационные посты, комментарии, заявления или оскорбления.",
|
||||
"doxing": "Публикация персональных данных других лиц без их согласия или угроза публикации (\"Доксинг\").",
|
||||
"glorific_trivia_of_cruel_inhuman_acts": "Прославление или умаление жестоких, или бесчеловечных актов насилия.",
|
||||
"intentional_intimidation_stalking_persecution": "Преднамеренное запугивание или преследование.",
|
||||
"other": "Другое ...",
|
||||
"pornographic_content_links": "Публикация или ссылка на явно порнографический материал."
|
||||
},
|
||||
"placeholder": "Категория ..."
|
||||
},
|
||||
"description": {
|
||||
"label": "Пожалуйста, объясните, почему хотите об этом сообщить?",
|
||||
"placeholder": "Дополнительная информация ..."
|
||||
}
|
||||
},
|
||||
"submit": "Отправить",
|
||||
"success": "Спасибо за сообщение!",
|
||||
"user": {
|
||||
"error": "Вы уже сообщили о пользователе!",
|
||||
"message": "Вы действительно хотите сообщить о пользователе \"<b>{name}<\/b>\"?",
|
||||
"title": "Пожаловаться на пользователя",
|
||||
"type": "Пользователь"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"failed": "Ничего не найдено",
|
||||
"hint": "Что вы хотите найти?",
|
||||
"placeholder": "Поиск"
|
||||
},
|
||||
"settings": {
|
||||
"blocked-users": {
|
||||
"block": "Блокировать",
|
||||
"columns": {
|
||||
"name": "Имя",
|
||||
"slug": "Псевдоним",
|
||||
"unblock": "Разблокировать"
|
||||
},
|
||||
"empty": "Вы пока никого не блокировали.",
|
||||
"explanation": {
|
||||
"closing": "На данный момент этого должно быть достаточно, чтобы заблокированные пользователи больше вас не беспокоили.",
|
||||
"intro": "Если блокируете другого пользователя, происходит следующее:",
|
||||
"notifications": "Заблокированные пользователи больше не будут получать уведомления об упоминаниях в ваших постах.",
|
||||
"search": "Посты заблокированных пользователей не отображаются в результатах поиска.",
|
||||
"their-perspective": "И наоборот — заблокированный пользователь больше не видит ваши посты в своей ленте.",
|
||||
"your-perspective": "Посты заблокированного пользователя не отображаются в персональной ленте."
|
||||
},
|
||||
"how-to": "Вы можете блокировать других пользователей на странице их профиля с помощью меню профиля.",
|
||||
"name": "Заблокированные пользователи",
|
||||
"unblock": "Разблокировать пользователей",
|
||||
"unblocked": "{name} - снова разблокирован"
|
||||
},
|
||||
"data": {
|
||||
"labelBio": "О себе",
|
||||
"labelCity": "Город или регион",
|
||||
"labelName": "Имя",
|
||||
"labelSlug": "Уникальное имя пользователя",
|
||||
"name": "Персональные данные",
|
||||
"namePlaceholder": "Маша Медведева",
|
||||
"success": "Персональные данные были успешно обновлены!"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Удалить аккаунт"
|
||||
},
|
||||
"deleteUserAccount": {
|
||||
"accountDescription": "Обратите внимание, что ваши посты и комментарии важны для сообщества. Если вы все равно хотите их удалить, то вы должны отметить соответствующие опции ниже.",
|
||||
"accountWarning": "Вы <b>НЕ СМОЖЕТЕ<\/b> восстановить свой аккаунт, посты или комментарии после удаления.",
|
||||
"commentedCount": "Удалить мои комментарии: {count}",
|
||||
"contributionsCount": "Удалить мои посты: {count}",
|
||||
"name": "Удалить данные",
|
||||
"pleaseConfirm": "<b class='is-danger'>Разрушительное действие!<\/b> Введите <b>{confirm}<\/b> для подтверждения.",
|
||||
"success": "Аккаунт успешно удален!"
|
||||
},
|
||||
"download": {
|
||||
"name": "Скачать данные"
|
||||
},
|
||||
"email": {
|
||||
"change-successful": "Адрес электронной почты был успешно изменен.",
|
||||
"labelEmail": "Адрес электронной почты",
|
||||
"labelNewEmail": "Новый адрес электронной почты",
|
||||
"labelNonce": "Введите свой код",
|
||||
"name": "Электронная почта",
|
||||
"submitted": "Электронное письмо с подтверждением отправлено на <b>{email}<\/b>.",
|
||||
"success": "Новый адрес электронной почты был зарегистрирован.",
|
||||
"validation": {
|
||||
"same-email": "Это текущий адрес электронной почты."
|
||||
},
|
||||
"verification-error": {
|
||||
"explanation": "Причины могут быть разными:",
|
||||
"message": "Адрес электронной почты не может быть изменен.",
|
||||
"reason": {
|
||||
"invalid-nonce": "Правильно ли указан код подтверждения?",
|
||||
"no-email-request": "Вы уверены, что отправляли запрос на изменение своего адреса электронной почты?"
|
||||
},
|
||||
"support": "Если проблема сохраняется, пожалуйста, свяжитесь с нами по электронной почте"
|
||||
}
|
||||
},
|
||||
"embeds": {
|
||||
"info-description": "Вот список сторонних провайдеров, чей контент может отображаться в форме вставок кода, например, в виде встроенных видео:",
|
||||
"name": "Сторонний контент",
|
||||
"status": {
|
||||
"change": {
|
||||
"allow": "Конечно.",
|
||||
"deny": "Нет, не надо",
|
||||
"question": "Вы хотите, чтобы вставки кода сторонних провайдеров всегда отображались?"
|
||||
},
|
||||
"description": "Значение по умолчанию -",
|
||||
"disabled": {
|
||||
"off": "сначала не отображать вставки кода сторонних провайдеров",
|
||||
"on": "сразу отображать вставки кода сторонних провайдеров"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Приглашения"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Языки"
|
||||
},
|
||||
"name": "Настройки",
|
||||
"organizations": {
|
||||
"name": "Мои организации"
|
||||
},
|
||||
"privacy": {
|
||||
"make-shouts-public": "Публиковать в моем публичном профиле статьи в которых я участвовал",
|
||||
"name": "Конфиденциальность",
|
||||
"success-update": "Настройки приватности сохранены"
|
||||
},
|
||||
"security": {
|
||||
"change-password": {
|
||||
"button": "Изменить пароль",
|
||||
"label-new-password": "Новый пароль",
|
||||
"label-new-password-confirm": "Подтверждение пароля",
|
||||
"label-old-password": "Старый пароль",
|
||||
"message-new-password-confirm-required": "Требуется подтверждение пароля",
|
||||
"message-new-password-missmatch": "Пароли не совпадают",
|
||||
"message-new-password-required": "Требуется новый пароль",
|
||||
"message-old-password-required": "Требуется свой старый пароль",
|
||||
"passwordSecurity": "Безопасность пароля",
|
||||
"passwordStrength0": "Очень небезопасный",
|
||||
"passwordStrength1": "Небезопасный",
|
||||
"passwordStrength2": "Посредственный",
|
||||
"passwordStrength3": "Надежный",
|
||||
"passwordStrength4": "Очень надежный",
|
||||
"success": "Пароль успешно изменен!"
|
||||
},
|
||||
"name": "Безопасность"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Социальные Медиа",
|
||||
"placeholder": "Ссылка на профиль социальной сети",
|
||||
"requireUnique": "Ссылка уже существует",
|
||||
"submit": "Добавить ссылку",
|
||||
"successAdd": "Добавлены социальные меди. Профиль обновлен!",
|
||||
"successDelete": "Социальные Меди удалены. Профиль обновлен!"
|
||||
},
|
||||
"validation": {
|
||||
"slug": {
|
||||
"alreadyTaken": "Это имя пользователя уже занято.",
|
||||
"regex": "Допускаются только строчные буквы, цифры, подчеркивания или дефисы."
|
||||
}
|
||||
}
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "выкрикнули"
|
||||
},
|
||||
"site": {
|
||||
"back-to-login": "Вернуться на страницу входа",
|
||||
"bank": "банковский счет",
|
||||
"changelog": "Изменения",
|
||||
"code-of-conduct": "Кодекс поведения",
|
||||
"contact": "Контакт",
|
||||
"data-privacy": "Конфиденциальность",
|
||||
"director": "Управляющий директор",
|
||||
"error-occurred": "Произошла ошибка.",
|
||||
"faq": "ЧаВо (FAQ)",
|
||||
"germany": "Германия",
|
||||
"imprint": "Импрессум",
|
||||
"made": "Сделано с ❤",
|
||||
"register": "Регистрационный номер",
|
||||
"responsible": "ответственный за содержание этой страницы (§ 55 Abs. 2 RStV)",
|
||||
"taxident": "UST-ID. в соответствии с §27a Закона о налоге с продаж Германии:",
|
||||
"termsAndConditions": "Условия и положения",
|
||||
"thanks": "Спасибо!",
|
||||
"tribunal": "Суд регистрации"
|
||||
},
|
||||
"store": {
|
||||
"posts": {
|
||||
"orderBy": {
|
||||
"newest": {
|
||||
"label": "Сначала новые"
|
||||
},
|
||||
"oldest": {
|
||||
"label": "Сначала старые"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"termsAndConditions": {
|
||||
"addition": {
|
||||
"description": "<a href=\"https:\/\/human-connection.org\/events\/\" target=\"_blank\" > https:\/\/human-connection.org\/events\/ <\/a>",
|
||||
"title": "Кроме того, мы регулярно проводим мероприятия, где вы также можете\\nподелиться своими впечатлениями и задать вопросы. Информацию о текущих событиях можно найти здесь:"
|
||||
},
|
||||
"agree": "Я согласен(на)!",
|
||||
"code-of-conduct": {
|
||||
"description": "Наш кодекс поведения служит руководством для личного поведения и взаимодействия друг с другом. Каждый пользователь социальной сети Human Connection, который пишет статьи, комментирует или вступает в контакт с другими пользователями, даже за пределами сети, признает эти правила поведения обязательными. <a href=\"https:\/\/alpha.human-connection.org\/code-of-conduct\" target=\"_blank\"> https:\/\/alpha.human-connection.org\/code-of-conduct<\/a>",
|
||||
"title": "Кодекс поведения"
|
||||
},
|
||||
"errors-and-feedback": {
|
||||
"description": "Мы прилагаем все усилия для обеспечения безопасности и доступности нашей сети и данных. Каждый новый выпуск программного обеспечения проходит как автоматическое, так и ручное тестирование. Однако могут возникнуть непредвиденные ошибки. Поэтому мы благодарны за любые обнаруженные ошибки. Вы можете сообщить о любых обнаруженных ошибках, отправив электронное письмо в службу поддержки по адресу support@human-connection.org",
|
||||
"title": "Ошибки и обратная связь"
|
||||
},
|
||||
"help-and-questions": {
|
||||
"description": "Для справки и вопросов мы собрали для вас исчерпывающую подборку часто задаваемых вопросов и ответов (FAQ). Вы можете найти их здесь: <a href=\"https:\/\/support.human-connection.org\/kb\/\" target=\"_blank\" > https:\/\/support.human-connection.org\/kb\/ <\/a>",
|
||||
"title": "Помощь и вопросы"
|
||||
},
|
||||
"moderation": {
|
||||
"description": "Пока наши финансовые возможности не позволяют нам реализовать полноценную систему модерации, поэтому мы осуществляем упрощенную модерацию собственными силами и с помощью волонтёров. Мы специально обучаем этих модераторов, поэтому только они принимают соответствующие решения. Модераторы действуют анонимно. Вы можете сообщать нам о постах, комментариях и пользователях (например, если они предоставляют информацию в своем профиле или имеют изображения, которые нарушают настоящие Условия использования). При обращении вы можете указать причину и дать краткое пояснение. Мы рассмотрим обращение и применим санкции в случае необходимости, например, путем блокировки постов, комментариев или пользователей. К сожалению, в настоящее время ни вы ни пострадавший пользователь не получите от нас обратной связи, но мы планируем ряд улучшений в этом направлении. Несмотря на это, мы оставляем за собой право на применение санкций по причинам, которые не могут быть или ещё не указаны в нашем Кодексе поведения или настоящих Условиях использования.",
|
||||
"title": "Модерация"
|
||||
},
|
||||
"newTermsAndConditions": "Новые условия и положения",
|
||||
"no-commercial-use": {
|
||||
"description": "Использование Human Connection сети не допускается в коммерческих целях. Это включает, но не ограничивается рекламой продуктов с коммерческими целями, размещением партнерских ссылок, прямым привлечением пожертвований или предоставлением финансовой поддержки для целей, которые не признаются благотворительными для целей налогообложения.",
|
||||
"title": "Нет коммерческого использования"
|
||||
},
|
||||
"privacy-statement": {
|
||||
"description": "Наша сеть — это социальная сеть знаний и действий. Поэтому для нас особенно важно, чтобы как можно больше контента было общедоступным. В процессе развития нашей сети будет добавлено больше возможностей для управления видимостью личных данных. Об этих новых функциях мы сообщим дополнительно. В противном случае вы должны думать о том, какие личные данные вы раскрываете о себе (или других). Это особенно актуально для содержания постов и комментариев, поскольку они имеют в основном общедоступный характер. Позже появятся возможности ограничения видимости вашего профиля. Часть условий использования — это наша политика конфиденциальности, которая информирует вас об обработке персональных данных в нашей сети: <a href=\"https:\/\/human-connection.org\/datenschutz\/#netzwerk\" target=\"_blank\">https:\/\/human-connection.org\/datenschutz\/#netzwerk<\/a> или <a href=\"https:\/\/human-connection.org\/datenschutz\/\" target=\"_blank\">https:\/\/human-connection.org\/datenschutz<\/a>. Наше заявление о конфиденциальности корректируется в соответствии с законодательством и характеристиками нашей сети и является действительной в настоящей версии.",
|
||||
"title": "Заявление о конфиденциальности"
|
||||
},
|
||||
"terms-of-service": {
|
||||
"description": "Следующие условия использования являются основой для использования нашей сети. При регистрации вы должны принять их, а мы при необходимости сообщим вам об изменениях. Сеть Human Connection работает в Германии и поэтому регулируется немецким законодательством. Место юрисдикции - Kirchheim \/ Teck. Подробности в выходных данных: <a href=\"https:\/\/human-connection.org\/en\/imprint\" target=\"_blank\" >https:\/\/human-connection.org\/en\/imprint<\/a>.",
|
||||
"title": "Условия обслуживания"
|
||||
},
|
||||
"termsAndConditionsConfirmed": "Я прочитал(а) и подтверждаю <a href=\"\/terms-and-conditions\" target=\"_blank\">Условия и положения<\/a>.",
|
||||
"termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
|
||||
"termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!",
|
||||
"use-and-license": {
|
||||
"description": "Если размещаемый в сети контент защищен правами на интеллектуальную собственность, вы предоставляете нам неисключительную, передаваемую, сублицензируемую и всемирную лицензию на использование этого контента для публикации в нашей сети. Эта лицензия заканчивается, как только вы удаляете свой контент или учетную запись. Помните, что другие пользователи могут продолжать делиться вашим контентом, и мы не можем его удалить.",
|
||||
"title": "Использование и лицензия"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Успешная загрузка!"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
FROM neo4j:3.5.13-enterprise
|
||||
FROM neo4j:3.5.14-enterprise
|
||||
LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
ARG BUILD_COMMIT
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Setting up database constraints and indexes will probably fail because of authentication errors."
|
||||
@ -21,7 +20,8 @@ CALL db.indexes();
|
||||
' | cypher-shell
|
||||
|
||||
echo '
|
||||
CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]);
|
||||
CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"]);
|
||||
CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"]);
|
||||
CREATE CONSTRAINT ON (p:Post) ASSERT p.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Comment) ASSERT c.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Category) ASSERT c.id IS UNIQUE;
|
||||
|
||||
24
package.json
24
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "human-connection",
|
||||
"version": "0.1.13",
|
||||
"version": "0.2.1",
|
||||
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
|
||||
"author": "Human Connection gGmbh",
|
||||
"license": "MIT",
|
||||
@ -21,27 +21,27 @@
|
||||
"version": "auto-changelog -p"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@babel/register": "^7.7.4",
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/register": "^7.8.3",
|
||||
"auto-changelog": "^1.16.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"codecov": "^3.6.1",
|
||||
"cross-env": "^6.0.3",
|
||||
"cucumber": "^6.0.5",
|
||||
"cypress": "^3.8.0",
|
||||
"cypress-cucumber-preprocessor": "^1.19.0",
|
||||
"cypress-file-upload": "^3.5.1",
|
||||
"cypress-plugin-retries": "^1.5.0",
|
||||
"date-fns": "^2.8.1",
|
||||
"cypress": "^3.8.2",
|
||||
"cypress-cucumber-preprocessor": "^2.0.1",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"cypress-plugin-retries": "^1.5.2",
|
||||
"date-fns": "^2.9.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"expect": "^24.9.0",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql-request": "^1.8.2",
|
||||
"neo4j-driver": "^1.7.6",
|
||||
"neode": "^0.3.6",
|
||||
"neo4j-driver": "^4.0.1",
|
||||
"neode": "^0.3.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"slug": "^1.1.0"
|
||||
"slug": "^2.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"set-value": "^2.0.1"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:13.3.0-alpine as base
|
||||
FROM node:lts-alpine as base
|
||||
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:13.3.0-alpine as build
|
||||
FROM node:lts-alpine as build
|
||||
LABEL Description="Maintenance page of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
5
webapp/assets/_new/icons/svgs/level-down.svg
Executable file
5
webapp/assets/_new/icons/svgs/level-down.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>level-down</title>
|
||||
<path d="M5 5h17v19.063l4.281-4.281 1.438 1.438-6 6-0.719 0.688-0.719-0.688-6-6 1.438-1.438 4.281 4.281v-17.063h-15v-2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 293 B |
65
webapp/assets/_new/styles/mixins/buttonStates.scss
Normal file
65
webapp/assets/_new/styles/mixins/buttonStates.scss
Normal file
@ -0,0 +1,65 @@
|
||||
@mixin buttonStates($color-scheme: primary, $filled: false) {
|
||||
$main-color: $color-primary;
|
||||
$active-color: $color-primary-dark;
|
||||
$hover-color: $color-primary-light;
|
||||
|
||||
@if $color-scheme == danger {
|
||||
$main-color: $color-danger;
|
||||
$active-color: $color-danger-dark;
|
||||
$hover-color: $color-danger-light;
|
||||
}
|
||||
|
||||
color: $main-color;
|
||||
border-color: $main-color;
|
||||
background-color: transparent;
|
||||
transition: background-color $duration-short;
|
||||
|
||||
&:focus {
|
||||
outline: $border-size-base dashed $main-color;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
&:hover {
|
||||
color: $color-neutral-100;
|
||||
border-color: $main-color;
|
||||
background-color: $main-color;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: $color-neutral-100;
|
||||
border-color: $active-color;
|
||||
background-color: $active-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $color-neutral-60;
|
||||
border-color: $color-neutral-60;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@if $filled {
|
||||
color: $color-neutral-100;
|
||||
border-color: $main-color;
|
||||
background-color: $main-color;
|
||||
|
||||
&:enabled {
|
||||
&:hover {
|
||||
border-color: $hover-color;
|
||||
background-color: $hover-color;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: $color-neutral-100;
|
||||
border-color: $active-color;
|
||||
background-color: $active-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $color-neutral-100;
|
||||
background-color: $color-neutral-60;
|
||||
border-color: $color-neutral-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
webapp/assets/_new/styles/resets.scss
Normal file
11
webapp/assets/_new/styles/resets.scss
Normal file
@ -0,0 +1,11 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
$color-primary: rgb(23, 181, 63);
|
||||
$color-primary-light: rgb(96, 214, 98);
|
||||
$color-primary-dark: rgb(25, 122, 49);
|
||||
$color-primary-active: rgb(25, 194, 67);
|
||||
$color-primary-inverse: rgb(241, 253, 244);
|
||||
$color-secondary: rgb(0, 142, 230);
|
||||
@ -13,6 +15,8 @@
|
||||
$color-success-active: rgb(26, 203, 71);
|
||||
$color-success-inverse: rgb(241, 253, 244);
|
||||
$color-danger: rgb(219, 57, 36);
|
||||
$color-danger-light: rgb(242, 97, 65);
|
||||
$color-danger-dark: rgb(158, 43, 28);
|
||||
$color-danger-active: rgb(224, 81, 62);
|
||||
$color-danger-inverse: rgb(253, 243, 242);
|
||||
$color-warning: rgb(230, 121, 25);
|
||||
@ -246,6 +250,21 @@ $size-avatar-base: 44px;
|
||||
$size-avatar-large: 64px;
|
||||
$size-avatar-x-large: 114px;
|
||||
|
||||
/**
|
||||
* @tokens Size Buttons
|
||||
* @presenter Spacing
|
||||
*/
|
||||
|
||||
$size-button-base: 36px;
|
||||
$size-button-small: 26px;
|
||||
|
||||
/**
|
||||
* @tokens Size Buttons
|
||||
* @presenter Spacing
|
||||
*/
|
||||
|
||||
$size-icon-base: 16px;
|
||||
|
||||
/**
|
||||
* @tokens Shadow
|
||||
* @presenter Shadow
|
||||
@ -291,6 +310,7 @@ $z-index-page-submenu: 2500;
|
||||
$z-index-page-header: 2000;
|
||||
$z-index-page-sidebar: 1500;
|
||||
$z-index-sticky: 100;
|
||||
$z-index-post-card-link: 5;
|
||||
|
||||
/**
|
||||
* @tokens Media Query
|
||||
|
||||
@ -3,16 +3,16 @@
|
||||
<ds-flex :gutter="{ base: 'xx-small', md: 'small', lg: 'xx-small' }">
|
||||
<div v-for="category in categories" :key="category.id">
|
||||
<ds-flex-item>
|
||||
<ds-button
|
||||
size="small"
|
||||
<base-button
|
||||
:data-test="categoryButtonsId(category.id)"
|
||||
@click.prevent="toggleCategory(category.id)"
|
||||
:primary="isActive(category.id)"
|
||||
@click="toggleCategory(category.id)"
|
||||
:filled="isActive(category.id)"
|
||||
:disabled="isDisabled(category.id)"
|
||||
:icon="category.icon"
|
||||
size="small"
|
||||
>
|
||||
<base-icon :name="category.icon" />
|
||||
{{ $t(`contribution.category.name.${category.slug}`) }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</ds-flex-item>
|
||||
</div>
|
||||
</ds-flex>
|
||||
|
||||
@ -19,6 +19,9 @@ export default {
|
||||
.category-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&.language {
|
||||
float: right;
|
||||
}
|
||||
|
||||
> .base-icon {
|
||||
margin-right: $space-xx-small;
|
||||
|
||||
@ -54,14 +54,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<ds-space margin-bottom="small" />
|
||||
<ds-button
|
||||
<base-button
|
||||
:title="this.$t('post.comment.reply')"
|
||||
icon="level-down"
|
||||
@click.prevent="reply"
|
||||
v-scroll-to="'.editor'"
|
||||
circle
|
||||
class="reply-button"
|
||||
size="small"
|
||||
></ds-button>
|
||||
></base-button>
|
||||
</ds-card>
|
||||
</div>
|
||||
</template>
|
||||
@ -75,6 +76,7 @@ import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
||||
import CommentMutations from '~/graphql/CommentMutations'
|
||||
import scrollToAnchor from '~/mixins/scrollToAnchor.js'
|
||||
import BaseButton from '~/components/_new/generic/BaseButton/BaseButton'
|
||||
|
||||
export default {
|
||||
mixins: [scrollToAnchor],
|
||||
@ -94,6 +96,7 @@ export default {
|
||||
ContentMenu,
|
||||
ContentViewer,
|
||||
HcCommentForm,
|
||||
BaseButton,
|
||||
},
|
||||
props: {
|
||||
routeHash: { type: String, default: () => '' },
|
||||
|
||||
@ -74,7 +74,7 @@ describe('CommentForm.vue', () => {
|
||||
|
||||
it('calls `clear` method when the cancel button is clicked', async () => {
|
||||
wrapper.vm.updateEditorContent('ok')
|
||||
await wrapper.find('.cancelBtn').trigger('submit')
|
||||
await wrapper.find('[data-test="cancel-button"]').trigger('submit')
|
||||
expect(cancelMethodSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@ -162,13 +162,13 @@ describe('CommentForm.vue', () => {
|
||||
describe('cancel button is clicked', () => {
|
||||
it('calls `closeEditWindow` method', async () => {
|
||||
wrapper.vm.updateEditorContent('ok')
|
||||
await wrapper.find('.cancelBtn').trigger('submit')
|
||||
await wrapper.find('[data-test="cancel-button"]').trigger('submit')
|
||||
expect(closeMethodSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('emits `showEditCommentMenu` event', async () => {
|
||||
wrapper.vm.updateEditorContent('ok')
|
||||
await wrapper.find('.cancelBtn').trigger('submit')
|
||||
await wrapper.find('[data-test="cancel-button"]').trigger('submit')
|
||||
expect(wrapper.emitted('showEditCommentMenu')).toEqual([[false]])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,28 +1,22 @@
|
||||
<template>
|
||||
<ds-form v-model="form" @submit="handleSubmit">
|
||||
<ds-form v-model="form" @submit="handleSubmit" class="comment-form">
|
||||
<template slot-scope="{ errors }">
|
||||
<ds-card>
|
||||
<!-- with client-only the content is not shown -->
|
||||
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
|
||||
<ds-space />
|
||||
<ds-flex :gutter="{ base: 'small', md: 'small', sm: 'x-large', xs: 'x-large' }">
|
||||
<ds-flex-item :width="{ base: '0%', md: '50%', sm: '0%', xs: '0%' }" />
|
||||
<ds-flex-item :width="{ base: '40%', md: '20%', sm: '30%', xs: '30%' }">
|
||||
<ds-button
|
||||
<div class="buttons">
|
||||
<base-button
|
||||
:disabled="disabled && !update"
|
||||
ghost
|
||||
class="cancelBtn"
|
||||
@click.prevent="handleCancel"
|
||||
@click="handleCancel"
|
||||
data-test="cancel-button"
|
||||
danger
|
||||
>
|
||||
{{ $t('actions.cancel') }}
|
||||
</ds-button>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '40%', md: '20%', sm: '40%', xs: '40%' }">
|
||||
<ds-button type="submit" :loading="loading" :disabled="disabled || errors" primary>
|
||||
</base-button>
|
||||
<base-button type="submit" :loading="loading" :disabled="disabled || errors" filled>
|
||||
{{ $t('post.comment.submit') }}
|
||||
</ds-button>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</base-button>
|
||||
</div>
|
||||
</ds-card>
|
||||
</template>
|
||||
</ds-form>
|
||||
@ -149,3 +143,17 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.comment-form {
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: $space-small 0;
|
||||
|
||||
> .base-button {
|
||||
margin-left: $space-x-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -63,12 +63,7 @@ describe('CommentList.vue', () => {
|
||||
|
||||
it('displays a comments counter', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('span.ds-tag').text()).toEqual('1')
|
||||
})
|
||||
|
||||
it('displays a comments counter', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('span.ds-tag').text()).toEqual('1')
|
||||
expect(wrapper.find('.count').text()).toEqual('1')
|
||||
})
|
||||
|
||||
describe('scrollToAnchor mixin', () => {
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div id="comments">
|
||||
<h3 style="margin-top: -10px;">
|
||||
<counter-icon icon="comments" :count="post.comments.length">
|
||||
<div id="comments" class="comment-list">
|
||||
<h3 class="title">
|
||||
<counter-icon icon="comments" :count="post.comments.length" />
|
||||
{{ $t('common.comment', null, 0) }}
|
||||
</counter-icon>
|
||||
</h3>
|
||||
<ds-space margin-bottom="large" />
|
||||
<div v-if="post.comments && post.comments.length" id="comments" class="comments">
|
||||
@ -55,3 +54,15 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.comment-list {
|
||||
> .title {
|
||||
margin-top: 0;
|
||||
|
||||
> .counter-icon {
|
||||
margin-right: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -46,7 +46,7 @@ describe('ContentMenu.vue', () => {
|
||||
store,
|
||||
localVue,
|
||||
})
|
||||
menuToggle = wrapper.find('.content-menu-trigger')
|
||||
menuToggle = wrapper.find('[data-test="content-menu-button"]')
|
||||
menuToggle.trigger('click')
|
||||
return wrapper
|
||||
}
|
||||
|
||||
@ -2,9 +2,14 @@
|
||||
<dropdown class="content-menu" :placement="placement" offset="5">
|
||||
<template slot="default" slot-scope="{ toggleMenu }">
|
||||
<slot name="button" :toggleMenu="toggleMenu">
|
||||
<ds-button class="content-menu-trigger" size="small" ghost @click.prevent="toggleMenu">
|
||||
<base-icon name="ellipsis-v" />
|
||||
</ds-button>
|
||||
<base-button
|
||||
data-test="content-menu-button"
|
||||
icon="ellipsis-v"
|
||||
size="small"
|
||||
circle
|
||||
ghost
|
||||
@click="toggleMenu"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<div slot="popover" slot-scope="{ toggleMenu }" class="content-menu-popover">
|
||||
|
||||
@ -200,6 +200,7 @@ describe('ContributionForm.vue', () => {
|
||||
imageUpload: null,
|
||||
imageAspectRatio: null,
|
||||
image: null,
|
||||
imageBlurred: false,
|
||||
},
|
||||
}
|
||||
postTitleInput = wrapper.find('.ds-input')
|
||||
@ -257,7 +258,7 @@ describe('ContributionForm.vue', () => {
|
||||
|
||||
describe('cancel', () => {
|
||||
it('calls $router.back() when cancel button clicked', () => {
|
||||
cancelBtn = wrapper.find('.cancel-button')
|
||||
cancelBtn = wrapper.find('[data-test="cancel-button"]')
|
||||
cancelBtn.trigger('click')
|
||||
expect(mocks.$router.back).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@ -307,6 +308,7 @@ describe('ContributionForm.vue', () => {
|
||||
name: 'Democracy & Politics',
|
||||
},
|
||||
],
|
||||
imageAspectRatio: 1,
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -353,7 +355,7 @@ describe('ContributionForm.vue', () => {
|
||||
categoryIds: ['cat12'],
|
||||
image,
|
||||
imageUpload: null,
|
||||
imageAspectRatio: null,
|
||||
imageAspectRatio: 1,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
<hc-teaser-image
|
||||
:contribution="contribution"
|
||||
@addTeaserImage="addTeaserImage"
|
||||
:class="{ '--blur-image': form.blurImage }"
|
||||
@addImageAspectRatio="addImageAspectRatio"
|
||||
>
|
||||
<img
|
||||
@ -18,7 +19,23 @@
|
||||
:src="contribution.image | proxyApiUrl"
|
||||
/>
|
||||
</hc-teaser-image>
|
||||
|
||||
<ds-card>
|
||||
<div class="blur-toggle">
|
||||
<label for="blur-img">{{ $t('contribution.inappropriatePicture') }}</label>
|
||||
<input type="checkbox" id="blur-img" v-model="form.blurImage" />
|
||||
<p>
|
||||
<a
|
||||
href="https://support.human-connection.org/kb/faq.php?id=113"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>
|
||||
{{ $t('contribution.inappropriatePictureText') }}
|
||||
<ds-icon name="question-circle" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ds-space />
|
||||
<client-only>
|
||||
<hc-user :user="currentUser" :trunc="35" />
|
||||
@ -80,19 +97,15 @@
|
||||
<ds-icon name="warning"></ds-icon>
|
||||
</ds-chip>
|
||||
</ds-text>
|
||||
|
||||
<ds-space />
|
||||
<div slot="footer" style="text-align: right">
|
||||
<ds-button
|
||||
class="cancel-button"
|
||||
:disabled="loading"
|
||||
ghost
|
||||
@click.prevent="$router.back()"
|
||||
>
|
||||
<base-button data-test="cancel-button" :disabled="loading" @click="$router.back()" danger>
|
||||
{{ $t('actions.cancel') }}
|
||||
</ds-button>
|
||||
<ds-button type="submit" icon="check" :loading="loading" :disabled="errors" primary>
|
||||
</base-button>
|
||||
<base-button type="submit" icon="check" :loading="loading" :disabled="errors" filled>
|
||||
{{ $t('actions.save') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</div>
|
||||
<ds-space margin-bottom="large" />
|
||||
</ds-card>
|
||||
@ -134,7 +147,9 @@ export default {
|
||||
image: null,
|
||||
language: null,
|
||||
categoryIds: [],
|
||||
blurImage: false,
|
||||
}
|
||||
|
||||
let id = null
|
||||
let slug = null
|
||||
const form = { ...formDefaults }
|
||||
@ -149,7 +164,10 @@ export default {
|
||||
? languageOptions.find(o => this.contribution.language === o.value)
|
||||
: null
|
||||
form.categoryIds = this.categoryIds(this.contribution.categories)
|
||||
form.imageAspectRatio = this.contribution.imageAspectRatio
|
||||
form.blurImage = this.contribution.imageBlurred
|
||||
}
|
||||
|
||||
return {
|
||||
form,
|
||||
formSchema: {
|
||||
@ -167,6 +185,7 @@ export default {
|
||||
},
|
||||
},
|
||||
language: { required: true },
|
||||
blurImage: { required: false },
|
||||
},
|
||||
languageOptions,
|
||||
id,
|
||||
@ -175,6 +194,7 @@ export default {
|
||||
users: [],
|
||||
contentMin: 3,
|
||||
hashtags: [],
|
||||
elem: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -195,6 +215,7 @@ export default {
|
||||
teaserImage,
|
||||
imageAspectRatio,
|
||||
categoryIds,
|
||||
blurImage,
|
||||
} = this.form
|
||||
this.loading = true
|
||||
this.$apollo
|
||||
@ -208,6 +229,7 @@ export default {
|
||||
language,
|
||||
image,
|
||||
imageUpload: teaserImage,
|
||||
imageBlurred: blurImage,
|
||||
imageAspectRatio,
|
||||
},
|
||||
})
|
||||
@ -273,12 +295,24 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.smallTag {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
left: 90%;
|
||||
<style lang="scss">
|
||||
.contribution-form {
|
||||
.ds-card-image.--blur-image img {
|
||||
filter: blur(32px);
|
||||
}
|
||||
|
||||
.blur-toggle {
|
||||
text-align: right;
|
||||
|
||||
> .link {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-chip {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
margin-top: $space-x-small;
|
||||
margin-bottom: $space-xx-small;
|
||||
@ -291,10 +325,5 @@ export default {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contribution-form {
|
||||
.ds-chip {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -62,7 +62,13 @@
|
||||
/>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: '100%', md: '100%', lg: 1 }">
|
||||
<ds-button icon="trash" danger :disabled="!deleteEnabled" @click="handleSubmit">
|
||||
<ds-button
|
||||
icon="trash"
|
||||
danger
|
||||
filled
|
||||
:disabled="!deleteEnabled"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ $t('settings.deleteUserAccount.name') }}
|
||||
</ds-button>
|
||||
</ds-flex-item>
|
||||
|
||||
@ -32,12 +32,12 @@ describe('DonationInfo.vue', () => {
|
||||
it('displays a call to action button', () => {
|
||||
expect(
|
||||
Wrapper()
|
||||
.find('.ds-button')
|
||||
.find('.base-button')
|
||||
.text(),
|
||||
).toBe('donations.donate-now')
|
||||
})
|
||||
|
||||
it('creates a title from the current month and a translation string', () => {
|
||||
it.skip('creates a title from the current month and a translation string', () => {
|
||||
mocks.$t = jest.fn(() => 'Spenden für')
|
||||
expect(Wrapper().vm.title).toBe('Spenden für Dezember')
|
||||
})
|
||||
@ -49,7 +49,7 @@ describe('DonationInfo.vue', () => {
|
||||
})
|
||||
|
||||
describe('given german locale', () => {
|
||||
it('creates a label from the given amounts and a translation string', () => {
|
||||
it.skip('creates a label from the given amounts and a translation string', () => {
|
||||
expect(mocks.$t).toBeCalledWith(
|
||||
'donations.amount-of-total',
|
||||
expect.objectContaining({
|
||||
@ -65,7 +65,7 @@ describe('DonationInfo.vue', () => {
|
||||
mocks.$i18n.locale = () => 'en'
|
||||
})
|
||||
|
||||
it('creates a label from the given amounts and a translation string', () => {
|
||||
it.skip('creates a label from the given amounts and a translation string', () => {
|
||||
expect(mocks.$t).toBeCalledWith(
|
||||
'donations.amount-of-total',
|
||||
expect.objectContaining({
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="donation-info">
|
||||
<progress-bar :title="title" :label="label" :goal="goal" :progress="progress" />
|
||||
<a target="_blank" href="https://human-connection.org/spenden/">
|
||||
<ds-button primary>{{ $t('donations.donate-now') }}</ds-button>
|
||||
<base-button filled>{{ $t('donations.donate-now') }}</base-button>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ds-button size="small" :ghost="!isActive" @click.prevent="onClick" :icon="icon">
|
||||
<base-button size="small" circle :ghost="!isActive" @click="onClick" :icon="icon">
|
||||
<span v-if="label">{{ label }}</span>
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ds-space margin-top="large" margin-bottom="xxx-small">
|
||||
<ds-form
|
||||
class="enter-nonce"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmitVerify"
|
||||
@ -14,17 +14,14 @@
|
||||
id="nonce"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text>
|
||||
{{ $t('components.enter-nonce.form.description') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
|
||||
<base-button :disabled="disabled" filled name="submit" type="submit">
|
||||
{{ $t('components.enter-nonce.form.next') }}
|
||||
</ds-button>
|
||||
</ds-form>
|
||||
</base-button>
|
||||
<slot></slot>
|
||||
</ds-space>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -64,3 +61,11 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.enter-nonce {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: $space-large 0 $space-xxx-small 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -39,7 +39,7 @@ describe('FilterMenu.vue', () => {
|
||||
|
||||
describe('click "clear-search-button" button', () => {
|
||||
it('emits clearSearch', () => {
|
||||
wrapper.find({ name: 'clear-search-button' }).trigger('click')
|
||||
wrapper.find('[name="clear-search-button"]').trigger('click')
|
||||
expect(wrapper.emitted().clearSearch).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
@ -6,15 +6,16 @@
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<div class="filter-menu-buttons">
|
||||
<ds-button
|
||||
<base-button
|
||||
name="clear-search-button"
|
||||
icon="close"
|
||||
circle
|
||||
@click="clearSearch"
|
||||
v-tooltip="{
|
||||
content: this.$t('filter-menu.clearSearch'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="clear-search-button"
|
||||
icon="close"
|
||||
@click="clearSearch"
|
||||
/>
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
|
||||
@ -12,10 +12,11 @@
|
||||
<ds-flex>
|
||||
<ds-flex-item width="10%" />
|
||||
<ds-flex-item width="100%">
|
||||
<ds-button
|
||||
<base-button
|
||||
circle
|
||||
icon="check"
|
||||
@click.stop.prevent="resetCategories"
|
||||
:primary="!filteredCategoryIds.length"
|
||||
@click="resetCategories"
|
||||
:filled="!filteredCategoryIds.length"
|
||||
/>
|
||||
<ds-flex-item>
|
||||
<label class="category-labels">{{ $t('filter-posts.categories.all') }}</label>
|
||||
@ -37,10 +38,11 @@
|
||||
<ds-flex v-for="category in chunk[index - 1]" :key="category.id" class="categories-menu">
|
||||
<ds-flex class="categories-menu">
|
||||
<ds-flex-item width="100%" class="categories-menu-item">
|
||||
<ds-button
|
||||
<base-button
|
||||
circle
|
||||
:icon="category.icon"
|
||||
:primary="filteredCategoryIds.includes(category.id)"
|
||||
@click.stop.prevent="toggleCategory(category.id)"
|
||||
:filled="filteredCategoryIds.includes(category.id)"
|
||||
@click="toggleCategory(category.id)"
|
||||
/>
|
||||
<ds-space margin-bottom="small" />
|
||||
</ds-flex-item>
|
||||
|
||||
@ -92,7 +92,7 @@ describe('FilterPosts.vue', () => {
|
||||
it('starts with all categories button active', () => {
|
||||
const wrapper = openFilterPosts()
|
||||
allCategoriesButton = wrapper.findAll('button').at(1)
|
||||
expect(allCategoriesButton.attributes().class).toContain('ds-button-primary')
|
||||
expect(allCategoriesButton.attributes().class).toContain('--filled')
|
||||
})
|
||||
|
||||
it('calls TOGGLE_CATEGORY when clicked', () => {
|
||||
@ -111,35 +111,35 @@ describe('FilterPosts.vue', () => {
|
||||
expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en')
|
||||
})
|
||||
|
||||
it('sets category button attribute `primary` when corresponding category is filtered', () => {
|
||||
it('sets category button attribute `filled` when corresponding category is filtered', () => {
|
||||
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
|
||||
const wrapper = openFilterPosts()
|
||||
democracyAndPoliticsButton = wrapper.findAll('button').at(4)
|
||||
expect(democracyAndPoliticsButton.attributes().class).toContain('ds-button-primary')
|
||||
expect(democracyAndPoliticsButton.attributes().class).toContain('--filled')
|
||||
})
|
||||
|
||||
it('sets language button attribute `primary` when corresponding language is filtered', () => {
|
||||
it('sets language button attribute `filled` when corresponding language is filtered', () => {
|
||||
getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es'])
|
||||
const wrapper = openFilterPosts()
|
||||
spanishButton = wrapper
|
||||
.findAll('button.language-buttons')
|
||||
.at(languages.findIndex(l => l.code === 'es'))
|
||||
expect(spanishButton.attributes().class).toContain('ds-button-primary')
|
||||
expect(spanishButton.attributes().class).toContain('--filled')
|
||||
})
|
||||
|
||||
it('sets "filter-by-followed-authors-only" button attribute `primary`', () => {
|
||||
it('sets "filter-by-followed" button attribute `filled`', () => {
|
||||
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
|
||||
const wrapper = openFilterPosts()
|
||||
expect(
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).classes('ds-button-primary'),
|
||||
).toBe(true)
|
||||
expect(wrapper.find('.base-button[data-test="filter-by-followed"]').classes('--filled')).toBe(
|
||||
true,
|
||||
)
|
||||
})
|
||||
|
||||
describe('click "filter-by-followed-authors-only" button', () => {
|
||||
describe('click "filter-by-followed" button', () => {
|
||||
let wrapper
|
||||
beforeEach(() => {
|
||||
wrapper = openFilterPosts()
|
||||
wrapper.find({ name: 'filter-by-followed-authors-only' }).trigger('click')
|
||||
wrapper.find('.base-button[data-test="filter-by-followed"]').trigger('click')
|
||||
})
|
||||
|
||||
it('calls TOGGLE_FILTER_BY_FOLLOWED', () => {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<dropdown ref="menu" :placement="placement" :offset="offset">
|
||||
<ds-button
|
||||
<base-button
|
||||
slot="default"
|
||||
icon="filter"
|
||||
:primary="filterActive"
|
||||
:filled="filterActive"
|
||||
:ghost="!filterActive"
|
||||
slot-scope="{ toggleMenu }"
|
||||
@click.prevent="toggleMenu()"
|
||||
>
|
||||
<base-icon class="dropdown-arrow" name="angle-down" />
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<template slot="popover">
|
||||
<ds-container>
|
||||
<categories-filter-menu-items :chunk="chunk" />
|
||||
|
||||
@ -13,17 +13,18 @@
|
||||
<ds-flex-item width="10%" />
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
<ds-flex-item width="100%">
|
||||
<div class="follow-button">
|
||||
<ds-button
|
||||
<div class="follow-filter-button">
|
||||
<base-button
|
||||
data-test="filter-by-followed"
|
||||
icon="user-plus"
|
||||
circle
|
||||
:filled="filteredByUsersFollowed"
|
||||
@click="toggleFilteredByFollowed(user.id)"
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="filter-by-followed-authors-only"
|
||||
icon="user-plus"
|
||||
:primary="filteredByUsersFollowed"
|
||||
@click="toggleFilteredByFollowed(user.id)"
|
||||
/>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<ds-flex-item>
|
||||
@ -36,14 +37,9 @@
|
||||
</ds-flex-item>
|
||||
<div v-for="emotion in emotionsArray" :key="emotion">
|
||||
<ds-flex-item :width="{ lg: '100%' }">
|
||||
<ds-button
|
||||
size="large"
|
||||
ghost
|
||||
@click="toogleFilteredByEmotions(emotion)"
|
||||
class="emotions-buttons"
|
||||
>
|
||||
<base-button @click="toogleFilteredByEmotions(emotion)" class="emotions-buttons" circle>
|
||||
<img :src="iconPath(emotion)" width="40" />
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<ds-flex-item class="emotions-mobile-space text-center">
|
||||
<label class="emotions-label">{{ $t(`contribution.emotions-label.${emotion}`) }}</label>
|
||||
@ -99,7 +95,7 @@ export default {
|
||||
#filter-posts-header {
|
||||
text-align: center;
|
||||
}
|
||||
.follow-button {
|
||||
.follow-filter-button {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,11 @@
|
||||
<ds-flex>
|
||||
<ds-flex-item width="10%" />
|
||||
<ds-flex-item width="100%">
|
||||
<ds-button
|
||||
<base-button
|
||||
icon="check"
|
||||
@click.stop.prevent="resetLanguages"
|
||||
:primary="!filteredLanguageCodes.length"
|
||||
circle
|
||||
:filled="!filteredLanguageCodes.length"
|
||||
@click="resetLanguages"
|
||||
/>
|
||||
<ds-flex-item>
|
||||
<label class="language-labels">{{ $t('filter-posts.language.all') }}</label>
|
||||
@ -32,13 +33,14 @@
|
||||
<ds-flex v-for="language in locales" :key="language.code" class="languages-menu">
|
||||
<ds-flex class="languages-menu">
|
||||
<ds-flex-item width="100%" class="language-menu-item">
|
||||
<ds-button
|
||||
<base-button
|
||||
class="language-buttons"
|
||||
:primary="filteredLanguageCodes.includes(language.code)"
|
||||
@click.stop.prevent="toggleLanguage(language.code)"
|
||||
circle
|
||||
:filled="filteredLanguageCodes.includes(language.code)"
|
||||
@click="toggleLanguage(language.code)"
|
||||
>
|
||||
{{ language.code.toUpperCase() }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<ds-space margin-bottom="small" />
|
||||
</ds-flex-item>
|
||||
<ds-flex>
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<ds-button
|
||||
<base-button
|
||||
class="follow-button"
|
||||
:disabled="disabled || !followId"
|
||||
:loading="loading"
|
||||
:icon="icon"
|
||||
:primary="isFollowed && !hovered"
|
||||
:filled="isFollowed && !hovered"
|
||||
:danger="isFollowed && hovered"
|
||||
fullwidth
|
||||
@mouseenter.native="onHover"
|
||||
@mouseleave.native="hovered = false"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
{{ label }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -83,3 +83,10 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.follow-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -51,11 +51,10 @@ describe('LoginForm', () => {
|
||||
|
||||
it('dispatches login with form data', () => {
|
||||
fillIn(Wrapper())
|
||||
expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
{ email: 'email@example.org', password: '1234' },
|
||||
undefined,
|
||||
)
|
||||
expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(expect.any(Object), {
|
||||
email: 'email@example.org',
|
||||
password: '1234',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -50,16 +50,9 @@
|
||||
<ds-space margin-bottom="large">
|
||||
<nuxt-link to="/password-reset/request">{{ $t('login.forgotPassword') }}</nuxt-link>
|
||||
</ds-space>
|
||||
<ds-button
|
||||
:loading="pending"
|
||||
primary
|
||||
fullwidth
|
||||
name="submit"
|
||||
type="submit"
|
||||
icon="sign-in"
|
||||
>
|
||||
<base-button :loading="pending" filled name="submit" type="submit" icon="sign-in">
|
||||
{{ $t('login.login') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<ds-space margin-top="large" margin-bottom="x-small">
|
||||
{{ $t('login.no-account') }}
|
||||
<nuxt-link to="/registration/signup">{{ $t('login.register') }}</nuxt-link>
|
||||
@ -113,6 +106,11 @@ export default {
|
||||
}
|
||||
.login-card {
|
||||
position: relative;
|
||||
|
||||
.base-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.login-locale-switch {
|
||||
position: absolute;
|
||||
|
||||
@ -10,11 +10,16 @@
|
||||
<p v-html="message" />
|
||||
|
||||
<template slot="footer">
|
||||
<ds-button class="cancel" :icon="modalData.buttons.cancel.icon" @click="cancel">
|
||||
<base-button
|
||||
class="cancel"
|
||||
:danger="!modalData.buttons.confirm.danger"
|
||||
:icon="modalData.buttons.cancel.icon"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ $t(modalData.buttons.cancel.textIdent) }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
|
||||
<ds-button
|
||||
<base-button
|
||||
:danger="modalData.buttons.confirm.danger"
|
||||
class="confirm"
|
||||
:icon="modalData.buttons.confirm.icon"
|
||||
@ -22,7 +27,7 @@
|
||||
@click="confirm"
|
||||
>
|
||||
{{ $t(modalData.buttons.confirm.textIdent) }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
</template>
|
||||
|
||||
@ -4,11 +4,10 @@
|
||||
<p v-html="message" />
|
||||
|
||||
<template slot="footer">
|
||||
<ds-button class="cancel" @click="cancel">{{ $t('disable.cancel') }}</ds-button>
|
||||
|
||||
<ds-button danger class="confirm" icon="exclamation-circle" @click="confirm">
|
||||
<base-button class="cancel" @click="cancel">{{ $t('disable.cancel') }}</base-button>
|
||||
<base-button danger filled class="confirm" icon="exclamation-circle" @click="confirm">
|
||||
{{ $t('disable.submit') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
</template>
|
||||
|
||||
@ -29,12 +29,13 @@
|
||||
</small>
|
||||
<ds-space />
|
||||
<template #footer>
|
||||
<ds-button class="cancel" icon="close" @click="cancel">
|
||||
<base-button class="cancel" icon="close" @click="cancel">
|
||||
{{ $t('report.cancel') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
|
||||
<ds-button
|
||||
<base-button
|
||||
danger
|
||||
filled
|
||||
class="confirm"
|
||||
icon="exclamation-circle"
|
||||
:disabled="!form.reasonCategory"
|
||||
@ -42,7 +43,7 @@
|
||||
@click="confirm"
|
||||
>
|
||||
{{ $t('report.submit') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
</template>
|
||||
@ -161,7 +162,7 @@ export default {
|
||||
.ds-modal {
|
||||
max-width: 600px !important;
|
||||
}
|
||||
.ds-radio-option:not(.ds-button) {
|
||||
.ds-radio-option {
|
||||
width: 100% !important;
|
||||
}
|
||||
.ds-radio-option-label {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { config, shallowMount } from '@vue/test-utils'
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import NotificationMenu from './NotificationMenu'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
localVue.filter('truncate', string => string)
|
||||
|
||||
config.stubs.dropdown = '<span class="dropdown"><slot /></span>'
|
||||
config.stubs.dropdown = '<span class="dropdown"><slot :toggleMenu="() => null" /></span>'
|
||||
|
||||
describe('NotificationMenu.vue', () => {
|
||||
let wrapper
|
||||
@ -22,9 +22,9 @@ describe('NotificationMenu.vue', () => {
|
||||
}
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return shallowMount(NotificationMenu, {
|
||||
return mount(NotificationMenu, {
|
||||
data,
|
||||
mocks,
|
||||
localVue,
|
||||
@ -33,7 +33,7 @@ describe('NotificationMenu.vue', () => {
|
||||
|
||||
it('counter displays 0', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').text()).toEqual('0')
|
||||
expect(wrapper.find('.count').text()).toEqual('0')
|
||||
})
|
||||
|
||||
it('no dropdown is rendered', () => {
|
||||
@ -67,12 +67,12 @@ describe('NotificationMenu.vue', () => {
|
||||
|
||||
it('counter displays 0', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').text()).toEqual('0')
|
||||
expect(wrapper.find('.count').text()).toEqual('0')
|
||||
})
|
||||
|
||||
it('button is not primary', () => {
|
||||
it('counter is not colored', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').props('primary')).toBe(false)
|
||||
expect(wrapper.find('.count').classes()).toContain('--inactive')
|
||||
})
|
||||
})
|
||||
|
||||
@ -130,12 +130,12 @@ describe('NotificationMenu.vue', () => {
|
||||
|
||||
it('displays the number of unread notifications', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').text()).toEqual('2')
|
||||
expect(wrapper.find('.count').text()).toEqual('2')
|
||||
})
|
||||
|
||||
it('renders primary button', () => {
|
||||
it('renders the counter in red', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').props('primary')).toBe(true)
|
||||
expect(wrapper.find('.count').classes()).toContain('--danger')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<ds-button v-if="!notifications.length" class="notifications-menu" disabled icon="bell">
|
||||
{{ unreadNotificationsCount }}
|
||||
</ds-button>
|
||||
<base-button v-if="!notifications.length" class="notifications-menu" disabled ghost circle>
|
||||
<counter-icon icon="bell" :count="unreadNotificationsCount" danger />
|
||||
</base-button>
|
||||
<dropdown v-else class="notifications-menu" offset="8" :placement="placement">
|
||||
<template slot="default" slot-scope="{ toggleMenu }">
|
||||
<ds-button :primary="!!unreadNotificationsCount" icon="bell" @click.prevent="toggleMenu">
|
||||
{{ unreadNotificationsCount }}
|
||||
</ds-button>
|
||||
<base-button @click="toggleMenu" ghost circle>
|
||||
<counter-icon icon="bell" :count="unreadNotificationsCount" danger />
|
||||
</base-button>
|
||||
</template>
|
||||
<template slot="popover">
|
||||
<div class="notifications-menu-popover">
|
||||
@ -22,17 +22,20 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { NOTIFICATIONS_POLL_INTERVAL } from '~/constants/notifications'
|
||||
import { notificationQuery, markAsReadMutation } from '~/graphql/User'
|
||||
import NotificationList from '../NotificationList/NotificationList'
|
||||
import unionBy from 'lodash/unionBy'
|
||||
|
||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import NotificationList from '../NotificationList/NotificationList'
|
||||
|
||||
export default {
|
||||
name: 'NotificationMenu',
|
||||
components: {
|
||||
NotificationList,
|
||||
CounterIcon,
|
||||
Dropdown,
|
||||
NotificationList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -90,8 +93,13 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
.notifications-menu {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.base-button {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.notifications-menu-popover {
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
/>
|
||||
<password-strength :password="formData.password" />
|
||||
<ds-space margin-top="base">
|
||||
<ds-button :loading="loading" :disabled="errors" primary>
|
||||
<base-button :loading="loading" :disabled="errors" filled type="submit">
|
||||
{{ $t('settings.security.change-password.button') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</ds-space>
|
||||
</template>
|
||||
</ds-form>
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
/>
|
||||
<password-strength :password="formData.password" />
|
||||
<ds-space margin-top="base" margin-bottom="xxx-small">
|
||||
<ds-button :loading="$apollo.loading" :disabled="errors" primary>
|
||||
<base-button :loading="$apollo.loading" :disabled="errors" filled type="submit">
|
||||
{{ $t('settings.security.change-password.button') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</ds-space>
|
||||
</template>
|
||||
</ds-form>
|
||||
|
||||
@ -20,17 +20,16 @@
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text align="left">{{ $t('components.password-reset.request.form.description') }}</ds-text>
|
||||
</ds-space>
|
||||
<ds-button
|
||||
<base-button
|
||||
:disabled="disabled"
|
||||
:loading="$apollo.loading"
|
||||
primary
|
||||
fullwidth
|
||||
filled
|
||||
name="submit"
|
||||
type="submit"
|
||||
icon="envelope"
|
||||
>
|
||||
{{ $t('components.password-reset.request.form.submit') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
<div v-else>
|
||||
|
||||
@ -2,7 +2,12 @@
|
||||
<ds-card
|
||||
:lang="post.language"
|
||||
:image="post.image | proxyApiUrl"
|
||||
:class="{ 'post-card': true, 'disabled-content': post.disabled, 'post--pinned': isPinned }"
|
||||
:class="{
|
||||
'post-card': true,
|
||||
'disabled-content': post.disabled,
|
||||
'--pinned': isPinned,
|
||||
'--blur-image': post.imageBlurred,
|
||||
}"
|
||||
>
|
||||
<!-- Post Link Target -->
|
||||
<nuxt-link
|
||||
@ -151,24 +156,27 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.post-card {
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ds-card-image img {
|
||||
&.--pinned {
|
||||
border: 1px solid $color-warning;
|
||||
}
|
||||
|
||||
&.--blur-image > .ds-card-image img {
|
||||
filter: blur(22px);
|
||||
}
|
||||
|
||||
> .ds-card-image img {
|
||||
width: 100%;
|
||||
max-height: 2000px;
|
||||
object-fit: contain;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
-o-object-position: center;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
|
||||
> .ds-card-content {
|
||||
flex-grow: 0;
|
||||
}
|
||||
@ -184,6 +192,8 @@ export default {
|
||||
}
|
||||
|
||||
.content-menu {
|
||||
position: relative;
|
||||
z-index: $z-index-post-card-link;
|
||||
display: inline-block;
|
||||
margin-left: $space-xx-small;
|
||||
margin-right: -$space-x-small;
|
||||
@ -200,8 +210,4 @@ export default {
|
||||
text-indent: -999999px;
|
||||
}
|
||||
}
|
||||
|
||||
.post--pinned {
|
||||
border: 1px solid $color-warning;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -102,10 +102,11 @@
|
||||
v-html="$t('components.registration.signup.form.no-political')"
|
||||
></label>
|
||||
</ds-text>
|
||||
<ds-button
|
||||
<base-button
|
||||
style="float: right;"
|
||||
icon="check"
|
||||
type="submit"
|
||||
filled
|
||||
:loading="$apollo.loading"
|
||||
:disabled="
|
||||
errors ||
|
||||
@ -115,10 +116,9 @@
|
||||
!noCommercial ||
|
||||
!noPolitical
|
||||
"
|
||||
primary
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-form>
|
||||
</div>
|
||||
|
||||
@ -30,17 +30,16 @@
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<ds-button
|
||||
<base-button
|
||||
:disabled="disabled"
|
||||
:loading="$apollo.loading"
|
||||
primary
|
||||
fullwidth
|
||||
filled
|
||||
name="submit"
|
||||
type="submit"
|
||||
icon="envelope"
|
||||
>
|
||||
{{ $t('components.registration.signup.form.submit') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
<slot></slot>
|
||||
</ds-form>
|
||||
</ds-space>
|
||||
|
||||
@ -4,11 +4,10 @@
|
||||
<p v-html="message" />
|
||||
|
||||
<template slot="footer">
|
||||
<ds-button class="cancel" @click="cancel">{{ $t('release.cancel') }}</ds-button>
|
||||
|
||||
<ds-button danger class="confirm" icon="exclamation-circle" @click="confirm">
|
||||
<base-button class="cancel" @click="cancel">{{ $t('release.cancel') }}</base-button>
|
||||
<base-button danger filled class="confirm" icon="exclamation-circle" @click="confirm">
|
||||
{{ $t('release.submit') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</template>
|
||||
</ds-modal>
|
||||
</template>
|
||||
|
||||
@ -1,140 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import SearchInput from './SearchInput.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
localVue.filter('truncate', () => 'truncated string')
|
||||
localVue.filter('dateTime', () => Date.now)
|
||||
|
||||
describe('SearchInput.vue', () => {
|
||||
let mocks
|
||||
let propsData
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
mocks = {
|
||||
$t: () => {},
|
||||
}
|
||||
return mount(SearchInput, { mocks, localVue, propsData })
|
||||
}
|
||||
|
||||
it('renders', () => {
|
||||
expect(Wrapper().is('div')).toBe(true)
|
||||
})
|
||||
|
||||
it('has id "nav-search"', () => {
|
||||
expect(Wrapper().contains('#nav-search')).toBe(true)
|
||||
})
|
||||
|
||||
it('defaults to an empty value', () => {
|
||||
expect(Wrapper().vm.value).toBe('')
|
||||
})
|
||||
|
||||
it('defaults to id "nav-search"', () => {
|
||||
expect(Wrapper().vm.id).toBe('nav-search')
|
||||
})
|
||||
|
||||
it('default to a 300 millisecond delay from the time the user stops typing to when the search starts', () => {
|
||||
expect(Wrapper().vm.delay).toEqual(300)
|
||||
})
|
||||
|
||||
it('defaults to an empty array as results', () => {
|
||||
expect(Wrapper().vm.results).toEqual([])
|
||||
})
|
||||
|
||||
it('defaults to pending false, as in the search is not pending', () => {
|
||||
expect(Wrapper().vm.pending).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts values as a string', () => {
|
||||
propsData = { value: 'abc' }
|
||||
const wrapper = Wrapper()
|
||||
expect(wrapper.vm.value).toEqual('abc')
|
||||
})
|
||||
|
||||
describe('testing custom functions', () => {
|
||||
let select
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
select = wrapper.find('.ds-select')
|
||||
select.trigger('focus')
|
||||
select.element.value = 'abcd'
|
||||
})
|
||||
|
||||
it('opens the select dropdown when focused on', () => {
|
||||
expect(wrapper.vm.isOpen).toBe(true)
|
||||
})
|
||||
|
||||
it('opens the select dropdown and blurs after focused on', () => {
|
||||
select.trigger('blur')
|
||||
expect(wrapper.vm.isOpen).toBe(false)
|
||||
})
|
||||
|
||||
it('is clearable', () => {
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.esc')
|
||||
expect(wrapper.emitted().clear.length).toBe(1)
|
||||
})
|
||||
|
||||
it('changes the unprocessedSearchInput as the value changes', () => {
|
||||
select.trigger('input')
|
||||
expect(wrapper.vm.unprocessedSearchInput).toBe('abcd')
|
||||
})
|
||||
|
||||
it('searches for the term when enter is pressed', async () => {
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.enter')
|
||||
await expect(wrapper.emitted().search[0]).toEqual(['abcd'])
|
||||
})
|
||||
|
||||
it('calls onDelete when the delete key is pressed', () => {
|
||||
const spy = jest.spyOn(wrapper.vm, 'onDelete')
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.delete')
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('calls query when a user starts a search by pressing enter', () => {
|
||||
const spy = jest.spyOn(wrapper.vm, 'query')
|
||||
select.trigger('input')
|
||||
select.trigger('keyup.enter')
|
||||
expect(spy).toHaveBeenCalledWith('abcd')
|
||||
})
|
||||
|
||||
it('calls onSelect when a user selects an item in the search dropdown menu', async () => {
|
||||
// searched for term in the browser, copied the results from Vuex in Vue dev tools
|
||||
propsData = {
|
||||
results: [
|
||||
{
|
||||
__typename: 'Post',
|
||||
author: {
|
||||
__typename: 'User',
|
||||
id: 'u5',
|
||||
name: 'Trick',
|
||||
slug: 'trick',
|
||||
},
|
||||
commentsCount: 0,
|
||||
createdAt: '2019-03-13T11:00:20.835Z',
|
||||
id: 'p10',
|
||||
label: 'Eos aut illo omnis quis eaque et iure aut.',
|
||||
shoutedCount: 0,
|
||||
slug: 'eos-aut-illo-omnis-quis-eaque-et-iure-aut',
|
||||
value: 'Eos aut illo omnis quis eaque et iure aut.',
|
||||
},
|
||||
],
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
select.trigger('input')
|
||||
const results = wrapper.find('.ds-select-option')
|
||||
results.trigger('click')
|
||||
await expect(wrapper.emitted().select[0]).toEqual(propsData.results)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,268 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="search"
|
||||
aria-label="search"
|
||||
role="search"
|
||||
:class="{
|
||||
'is-active': isActive,
|
||||
'is-open': isOpen,
|
||||
}"
|
||||
>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<a v-if="isActive" class="search-clear-btn" @click="clear"> </a>
|
||||
<ds-select
|
||||
:id="id"
|
||||
ref="input"
|
||||
v-model="searchValue"
|
||||
class="input"
|
||||
name="search"
|
||||
type="search"
|
||||
icon="search"
|
||||
label-prop="id"
|
||||
:no-options-available="emptyText"
|
||||
:icon-right="isActive ? 'close' : null"
|
||||
:filter="item => item"
|
||||
:options="results"
|
||||
:auto-reset-search="!searchValue"
|
||||
:placeholder="$t('search.placeholder')"
|
||||
:loading="pending"
|
||||
@keyup.enter.native="onEnter"
|
||||
@focus.capture.native="onFocus"
|
||||
@blur.capture.native="onBlur"
|
||||
@keyup.delete.native="onDelete"
|
||||
@keyup.esc.native="clear"
|
||||
@input.exact="onSelect"
|
||||
@input.native="handleInput"
|
||||
@click.capture.native="isOpen = true"
|
||||
>
|
||||
<template slot="option" slot-scope="{ option }">
|
||||
<ds-flex>
|
||||
<ds-flex-item class="search-option-label">
|
||||
<ds-text>{{ option.label | truncate(70) }}</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item class="search-option-meta" width="280px">
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<ds-text size="small" color="softer" class="search-meta">
|
||||
<span style="text-align: right;">
|
||||
<b>{{ option.commentsCount }}</b>
|
||||
<base-icon name="comments" />
|
||||
</span>
|
||||
<span style="width: 36px; display: inline-block; text-align: right;">
|
||||
<b>{{ option.shoutedCount }}</b>
|
||||
<base-icon name="bullhorn" />
|
||||
</span>
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-text size="small" color="softer" align="right">
|
||||
{{ option.author.name | truncate(32) }} -
|
||||
{{ option.createdAt | dateTime('dd.MM.yyyy') }}
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</template>
|
||||
</ds-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'SearchInput',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: 'nav-search',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
results: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
pending: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchProcess: null,
|
||||
isOpen: false,
|
||||
lastSearchTerm: '',
|
||||
unprocessedSearchInput: '',
|
||||
searchValue: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// #: Unused at the moment?
|
||||
isActive() {
|
||||
return !isEmpty(this.lastSearchTerm)
|
||||
},
|
||||
emptyText() {
|
||||
return this.isActive && !this.pending ? this.$t('search.failed') : this.$t('search.hint')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async query(value) {
|
||||
if (isEmpty(value) || value.length < 3) {
|
||||
this.clear()
|
||||
return
|
||||
}
|
||||
this.$emit('search', value)
|
||||
},
|
||||
handleInput(e) {
|
||||
clearTimeout(this.searchProcess)
|
||||
const value = e.target ? e.target.value.trim() : ''
|
||||
this.isOpen = true
|
||||
this.unprocessedSearchInput = value
|
||||
this.searchProcess = setTimeout(() => {
|
||||
this.lastSearchTerm = value
|
||||
this.query(value)
|
||||
}, this.delay)
|
||||
},
|
||||
onSelect(item) {
|
||||
this.isOpen = false
|
||||
this.$emit('select', item)
|
||||
this.$nextTick(() => {
|
||||
this.searchValue = this.lastSearchTerm
|
||||
})
|
||||
},
|
||||
onFocus(e) {
|
||||
clearTimeout(this.searchProcess)
|
||||
this.isOpen = true
|
||||
},
|
||||
onBlur(e) {
|
||||
this.searchValue = this.lastSearchTerm
|
||||
// this.$nextTick(() => {
|
||||
// this.searchValue = this.lastSearchTerm
|
||||
// })
|
||||
this.isOpen = false
|
||||
clearTimeout(this.searchProcess)
|
||||
},
|
||||
onDelete(e) {
|
||||
clearTimeout(this.searchProcess)
|
||||
const value = e.target ? e.target.value.trim() : ''
|
||||
if (isEmpty(value)) {
|
||||
this.clear()
|
||||
} else {
|
||||
this.handleInput(e)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* TODO: on enter we should go to a dedicated seach page!?
|
||||
*/
|
||||
onEnter(e) {
|
||||
// this.isOpen = false
|
||||
clearTimeout(this.searchProcess)
|
||||
if (!this.pending) {
|
||||
// this.lastSearchTerm = this.unprocessedSearchInput
|
||||
this.query(this.unprocessedSearchInput)
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.$emit('clear')
|
||||
clearTimeout(this.searchProcess)
|
||||
this.isOpen = false
|
||||
this.unprocessedSearchInput = ''
|
||||
this.lastSearchTerm = ''
|
||||
this.searchValue = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.search {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
$padding-left: $space-x-small;
|
||||
|
||||
.search-option-label {
|
||||
align-self: center;
|
||||
padding-left: $padding-left;
|
||||
}
|
||||
|
||||
.search-option-meta {
|
||||
align-self: center;
|
||||
|
||||
.ds-flex {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
&,
|
||||
.ds-select-dropdown {
|
||||
transition: box-shadow 100ms;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
.ds-select-dropdown {
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-select-dropdown-message {
|
||||
opacity: 0.5;
|
||||
padding-left: $padding-left;
|
||||
}
|
||||
|
||||
.search-clear-btn {
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-meta {
|
||||
float: right;
|
||||
padding-top: 2px;
|
||||
white-space: nowrap;
|
||||
word-wrap: none;
|
||||
|
||||
.base-icon {
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-select {
|
||||
z-index: $z-index-dropdown + 1;
|
||||
}
|
||||
|
||||
.ds-select-option-hover {
|
||||
.ds-text-size-small,
|
||||
.ds-text-size-small-x {
|
||||
color: $text-color-soft;
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<ds-space margin="xx-small" class="text-align-center">
|
||||
<ds-button
|
||||
<base-button
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:ghost="!shouted"
|
||||
:primary="shouted"
|
||||
size="x-large"
|
||||
:filled="shouted"
|
||||
icon="bullhorn"
|
||||
circle
|
||||
@click="toggle"
|
||||
/>
|
||||
<ds-space margin-bottom="xx-small" />
|
||||
|
||||
@ -9,10 +9,18 @@
|
||||
@vdropzone-thumbnail="transformImage"
|
||||
>
|
||||
<div class="crop-overlay" ref="cropperOverlay" v-show="showCropper">
|
||||
<ds-button @click.stop.prevent="cropImage" class="crop-confirm" primary>
|
||||
<base-button @click="cropImage" class="crop-confirm" filled>
|
||||
{{ $t('contribution.teaserImage.cropperConfirm') }}
|
||||
</ds-button>
|
||||
<ds-button @click="cancelCrop" class="crop-cancel" icon="close"></ds-button>
|
||||
</base-button>
|
||||
<base-button
|
||||
class="crop-cancel"
|
||||
icon="close"
|
||||
size="small"
|
||||
circle
|
||||
danger
|
||||
filled
|
||||
@click="cancelCrop"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:class="{
|
||||
@ -140,7 +148,7 @@ export default {
|
||||
<style lang="scss">
|
||||
#postdropzone {
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
min-height: 400px;
|
||||
background-color: $background-color-softest;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import BaseButton from './BaseButton.vue'
|
||||
|
||||
storiesOf('Generic/BaseButton', module)
|
||||
.addDecorator(helpers.layout)
|
||||
|
||||
.add('default', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button>Click me</base-button>
|
||||
<base-button disabled>Disabled</base-button>
|
||||
<base-button loading>Loading</base-button>
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('icon', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button icon="edit">With Text</base-button>
|
||||
<base-button icon="bullhorn" />
|
||||
<base-button icon="trash" disabled />
|
||||
<base-button icon="trash" loading />
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('circle', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button circle icon="eye" />
|
||||
<base-button circle>EN</base-button>
|
||||
<base-button circle disabled icon="eye-slash" />
|
||||
<base-button circle loading icon="eye-slash" />
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('danger', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button danger>Danger</base-button>
|
||||
<base-button danger disabled>Disabled</base-button>
|
||||
<base-button danger loading>Loading</base-button>
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('filled', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button filled>Filled</base-button>
|
||||
<base-button filled danger>Filled Danger</base-button>
|
||||
<base-button filled disabled>Disabled</base-button>
|
||||
<base-button filled loading>Loading</base-button>
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('small', () => ({
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button size="small">Small</base-button>
|
||||
<base-button size="small" circle>S</base-button>
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('ghost', () => ({
|
||||
// TODO: add documentation --> ghost button should only be used for very special occasions
|
||||
// e.g. for the ContentMenu + for the EditorMenuBarButtons
|
||||
components: { BaseButton },
|
||||
template: `
|
||||
<div>
|
||||
<base-button size="small" icon="ellipsis-v" circle ghost />
|
||||
</div>
|
||||
`,
|
||||
}))
|
||||
144
webapp/components/_new/generic/BaseButton/BaseButton.vue
Normal file
144
webapp/components/_new/generic/BaseButton/BaseButton.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<button
|
||||
:class="buttonClass"
|
||||
:disabled="loading"
|
||||
:type="type"
|
||||
@click.capture="event => $emit('click', event)"
|
||||
>
|
||||
<base-icon v-if="icon" :name="icon" />
|
||||
<loading-spinner v-if="loading" />
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingSpinner from '~/components/_new/generic/LoadingSpinner/LoadingSpinner'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
},
|
||||
props: {
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
danger: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
filled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ghost: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'regular',
|
||||
validator(value) {
|
||||
return value.match(/(small|regular)/)
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
validator(value) {
|
||||
return value.match(/(button|submit)/)
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
buttonClass() {
|
||||
let buttonClass = 'base-button'
|
||||
|
||||
if (this.$slots.default === undefined) buttonClass += ' --icon-only'
|
||||
if (this.circle) buttonClass += ' --circle'
|
||||
if (this.danger) buttonClass += ' --danger'
|
||||
if (this.loading) buttonClass += ' --loading'
|
||||
if (this.size === 'small') buttonClass += ' --small'
|
||||
|
||||
if (this.filled) buttonClass += ' --filled'
|
||||
else if (this.ghost) buttonClass += ' --ghost'
|
||||
|
||||
return buttonClass
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~/assets/_new/styles/mixins/buttonStates.scss';
|
||||
|
||||
.base-button {
|
||||
@include buttonStates;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $size-button-base;
|
||||
padding: 0 $space-x-small;
|
||||
vertical-align: bottom;
|
||||
border: $border-size-base solid;
|
||||
border-radius: $border-radius-x-large;
|
||||
overflow: hidden;
|
||||
font-weight: $font-weight-bold;
|
||||
cursor: pointer;
|
||||
|
||||
&.--danger {
|
||||
@include buttonStates($color-scheme: danger);
|
||||
}
|
||||
|
||||
&.--filled {
|
||||
@include buttonStates($filled: true);
|
||||
}
|
||||
|
||||
&.--danger.--filled {
|
||||
@include buttonStates($color-scheme: danger, $filled: true);
|
||||
}
|
||||
|
||||
&.--circle {
|
||||
width: $size-button-base;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.--ghost {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.--small {
|
||||
height: $size-button-small;
|
||||
font-size: $font-size-small;
|
||||
|
||||
&.--circle {
|
||||
width: $size-button-small;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.--icon-only) > .base-icon {
|
||||
margin-right: $space-xx-small;
|
||||
}
|
||||
|
||||
&:disabled.--loading {
|
||||
color: $color-neutral-80;
|
||||
}
|
||||
|
||||
> .loading-spinner {
|
||||
position: absolute;
|
||||
height: $size-button-small;
|
||||
color: $color-neutral-60;
|
||||
}
|
||||
|
||||
&.--filled > .loading-spinner {
|
||||
color: $color-neutral-100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -29,7 +29,7 @@ const iconStyles = `
|
||||
font-size: 20px;
|
||||
`
|
||||
|
||||
storiesOf('BaseIcon', module)
|
||||
storiesOf('Generic/BaseIcon', module)
|
||||
.addDecorator(helpers.layout)
|
||||
|
||||
.add('pure icon', () => ({
|
||||
|
||||
@ -37,6 +37,7 @@ export default {
|
||||
<style lang="scss">
|
||||
.base-icon {
|
||||
display: inline-flex;
|
||||
vertical-align: bottom;
|
||||
|
||||
> .svg {
|
||||
height: 1.2em;
|
||||
|
||||
@ -5,33 +5,41 @@ import BaseIcon from '../BaseIcon/BaseIcon'
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('CounterIcon.vue', () => {
|
||||
let propsData, wrapper, tag
|
||||
let propsData, wrapper, count
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(CounterIcon, { propsData, localVue })
|
||||
}
|
||||
|
||||
describe('given a valid icon name and count', () => {
|
||||
describe('given a valid icon name and count below 100', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { icon: 'comments', count: 1 }
|
||||
propsData = { icon: 'comments', count: 42 }
|
||||
wrapper = Wrapper()
|
||||
tag = wrapper.find('.ds-tag')
|
||||
count = wrapper.find('.count')
|
||||
})
|
||||
|
||||
it('renders BaseIcon', () => {
|
||||
it('renders the icon', () => {
|
||||
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders the count', () => {
|
||||
expect(tag.text()).toEqual('1')
|
||||
expect(count.text()).toEqual('42')
|
||||
})
|
||||
})
|
||||
|
||||
it('uses a round tag', () => {
|
||||
expect(tag.classes()).toContain('ds-tag-round')
|
||||
describe('given a valid icon name and count above 100', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { icon: 'comments', count: 750 }
|
||||
wrapper = Wrapper()
|
||||
count = wrapper.find('.count')
|
||||
})
|
||||
|
||||
it('uses a primary button', () => {
|
||||
expect(tag.classes()).toContain('ds-tag-primary')
|
||||
it('renders the icon', () => {
|
||||
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders the capped count with a plus', () => {
|
||||
expect(count.text()).toEqual('99+')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,18 +2,33 @@ import { storiesOf } from '@storybook/vue'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import CounterIcon from './CounterIcon.vue'
|
||||
|
||||
storiesOf('CounterIcon', module)
|
||||
storiesOf('Generic/CounterIcon', module)
|
||||
.addDecorator(helpers.layout)
|
||||
.add('flag icon with button in slot position', () => ({
|
||||
|
||||
.add('default', () => ({
|
||||
components: { CounterIcon },
|
||||
data() {
|
||||
return { icon: 'flag', count: 3 }
|
||||
},
|
||||
template: `
|
||||
<counter-icon icon="pizza" :count="count">
|
||||
<ds-button ghost primary>
|
||||
Report Details
|
||||
</ds-button>
|
||||
</counter-icon>
|
||||
<counter-icon icon="flag" :count="3" />
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('high count', () => ({
|
||||
components: { CounterIcon },
|
||||
template: `
|
||||
<counter-icon icon="comments" :count="150" />
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('danger', () => ({
|
||||
components: { CounterIcon },
|
||||
template: `
|
||||
<counter-icon icon="bell" :count="42" danger />
|
||||
`,
|
||||
}))
|
||||
|
||||
.add('count is 0', () => ({
|
||||
components: { CounterIcon },
|
||||
template: `
|
||||
<counter-icon icon="bell" :count="0" />
|
||||
`,
|
||||
}))
|
||||
|
||||
@ -1,29 +1,63 @@
|
||||
<template>
|
||||
<span>
|
||||
<span class="counter-icon">
|
||||
<base-icon :name="icon" />
|
||||
<ds-tag
|
||||
style="margin-top: -4px; margin-left: -12px; position: absolute;"
|
||||
color="primary"
|
||||
size="small"
|
||||
round
|
||||
>
|
||||
{{ count }}
|
||||
</ds-tag>
|
||||
<span class="counter-icon-text">
|
||||
<slot />
|
||||
</span>
|
||||
<span :class="counterClass">{{ cappedCount }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
icon: { type: String, required: true },
|
||||
count: { type: Number, required: true },
|
||||
danger: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
cappedCount() {
|
||||
return this.count <= 99 ? this.count : '99+'
|
||||
},
|
||||
counterClass() {
|
||||
let counterClass = 'count'
|
||||
if (this.danger) counterClass += ' --danger'
|
||||
if (this.count === 0) counterClass += ' --inactive'
|
||||
|
||||
return counterClass
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.counter-icon-text {
|
||||
margin-left: $space-xx-small;
|
||||
|
||||
<style lang="scss">
|
||||
.counter-icon {
|
||||
position: relative;
|
||||
|
||||
> .count {
|
||||
position: absolute;
|
||||
top: -$space-xx-small;
|
||||
right: 0;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $size-icon-base;
|
||||
min-width: $size-icon-base;
|
||||
padding: 3px; // magic number to center count
|
||||
border-radius: 50%;
|
||||
transform: translateX(50%);
|
||||
|
||||
color: $color-neutral-100;
|
||||
background-color: $color-primary;
|
||||
font-size: 10px; // magic number to center count
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
|
||||
&.--danger {
|
||||
background-color: $color-danger;
|
||||
}
|
||||
|
||||
&.--inactive {
|
||||
background-color: $color-neutral-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { storiesOf } from '@storybook/vue'
|
||||
import helpers from '~/storybook/helpers'
|
||||
import LoadingSpinner from './LoadingSpinner.vue'
|
||||
|
||||
storiesOf('Generic/LoadingSpinner', module)
|
||||
.addDecorator(helpers.layout)
|
||||
|
||||
.add('default', () => ({
|
||||
components: { LoadingSpinner },
|
||||
template: '<loading-spinner />',
|
||||
}))
|
||||
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 50 50" class="loading-spinner">
|
||||
<circle cx="25" cy="25" r="20" class="circle" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LoadingSpinner',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loading-spinner {
|
||||
height: $size-button-base;
|
||||
overflow: hidden;
|
||||
stroke: currentColor;
|
||||
animation: rotate 16s linear infinite;
|
||||
|
||||
> .circle {
|
||||
fill: none;
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
animation: dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 150;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -35;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -124;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(2160deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -40,7 +40,7 @@ describe('ReportRow', () => {
|
||||
beforeEach(() => {
|
||||
propsData = { ...propsData, report: reports[1] }
|
||||
wrapper = Wrapper()
|
||||
confirmButton = wrapper.find('.ds-button-danger')
|
||||
confirmButton = wrapper.find('.base-button.--danger')
|
||||
})
|
||||
|
||||
it('renders a confirm button', () => {
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
<span class="user-count">
|
||||
{{ $t('moderation.reports.numberOfUsers', { count: report.filed.length }) }}
|
||||
</span>
|
||||
<ds-button size="small" @click="showFiledReports = !showFiledReports">
|
||||
<base-button size="small" @click="showFiledReports = !showFiledReports">
|
||||
{{ $t('moderation.reports.moreDetails') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</td>
|
||||
|
||||
<!-- Content Column -->
|
||||
@ -61,16 +61,17 @@
|
||||
<span v-if="report.closed" class="title">
|
||||
{{ $t('moderation.reports.decided') }}
|
||||
</span>
|
||||
<ds-button
|
||||
<base-button
|
||||
v-else
|
||||
danger
|
||||
filled
|
||||
data-test="confirm"
|
||||
size="small"
|
||||
:icon="statusIconName"
|
||||
@click="$emit('confirm-report')"
|
||||
>
|
||||
{{ $t('moderation.reports.decideButton') }}
|
||||
</ds-button>
|
||||
</base-button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
64
webapp/components/features/SearchField/SearchField.spec.js
Normal file
64
webapp/components/features/SearchField/SearchField.spec.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import SearchField from './SearchField.vue'
|
||||
import SearchableInput from '~/components/generic/SearchableInput/SearchableInput'
|
||||
import { searchResults } from '~/components/generic/SearchableInput/SearchableInput.story'
|
||||
const localVue = global.localVue
|
||||
|
||||
localVue.filter('truncate', () => 'truncated string')
|
||||
localVue.filter('dateTime', () => Date.now)
|
||||
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||
|
||||
describe('SearchField.vue', () => {
|
||||
let mocks, wrapper, getters
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$apollo: {
|
||||
query: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(string => string),
|
||||
}
|
||||
getters = { 'auth/isModerator': () => false }
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return mount(SearchField, { mocks, localVue, store })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
describe('Emitted events', () => {
|
||||
let searchableInputComponent
|
||||
beforeEach(() => {
|
||||
searchableInputComponent = wrapper.find(SearchableInput)
|
||||
})
|
||||
|
||||
describe('query event', () => {
|
||||
it('calls an apollo query', () => {
|
||||
searchableInputComponent.vm.$emit('query', 'abcd')
|
||||
expect(mocks.$apollo.query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ variables: { query: 'abcd' } }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearSearch event', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.setData({ searchResults, pending: true })
|
||||
searchableInputComponent.vm.$emit('clearSearch')
|
||||
})
|
||||
|
||||
it('clears searchResults', () => {
|
||||
expect(wrapper.vm.searchResults).toEqual([])
|
||||
})
|
||||
|
||||
it('set pending to false', () => {
|
||||
expect(wrapper.vm.pending).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
51
webapp/components/features/SearchField/SearchField.vue
Normal file
51
webapp/components/features/SearchField/SearchField.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<searchable-input
|
||||
data-test="search-field"
|
||||
:loading="pending"
|
||||
:options="searchResults"
|
||||
@query="query"
|
||||
@clearSearch="clear"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { findResourcesQuery } from '~/graphql/Search.js'
|
||||
import SearchableInput from '~/components/generic/SearchableInput/SearchableInput.vue'
|
||||
|
||||
export default {
|
||||
name: 'SearchField',
|
||||
components: {
|
||||
SearchableInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pending: false,
|
||||
searchResults: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async query(value) {
|
||||
this.pending = true
|
||||
try {
|
||||
const {
|
||||
data: { findResources },
|
||||
} = await this.$apollo.query({
|
||||
query: findResourcesQuery,
|
||||
variables: {
|
||||
query: value,
|
||||
},
|
||||
})
|
||||
this.searchResults = findResources
|
||||
} catch (error) {
|
||||
this.searchResults = []
|
||||
} finally {
|
||||
this.pending = false
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.pending = false
|
||||
this.searchResults = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,27 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import SearchHeading from './SearchHeading.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('SearchHeading.vue', () => {
|
||||
let mocks, wrapper, propsData
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(string => string),
|
||||
}
|
||||
propsData = {
|
||||
resourceType: 'Post',
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(SearchHeading, { mocks, localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
it('renders heading', () => {
|
||||
expect(wrapper.text()).toMatch('search.heading.Post')
|
||||
})
|
||||
})
|
||||
})
|
||||
26
webapp/components/generic/SearchHeading/SearchHeading.vue
Normal file
26
webapp/components/generic/SearchHeading/SearchHeading.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ds-flex-item class="search-heading">
|
||||
<ds-heading soft size="h5">
|
||||
{{ $t(`search.heading.${resourceType}`) }}
|
||||
</ds-heading>
|
||||
</ds-flex-item>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'SearchHeading',
|
||||
props: {
|
||||
resourceType: { type: String, required: true },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.search-heading {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
background-color: white;
|
||||
margin: -8px;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
64
webapp/components/generic/SearchPost/SearchPost.spec.js
Normal file
64
webapp/components/generic/SearchPost/SearchPost.spec.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import SearchPost from './SearchPost.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
localVue.filter('dateTime', d => d)
|
||||
|
||||
describe('SearchPost.vue', () => {
|
||||
let mocks, wrapper, propsData
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(string => string),
|
||||
}
|
||||
propsData = {
|
||||
option: {
|
||||
title: 'Post Title',
|
||||
commentsCount: 3,
|
||||
shoutedCount: 6,
|
||||
createdAt: '23.08.2019',
|
||||
author: {
|
||||
name: 'Post Author',
|
||||
},
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
return shallowMount(SearchPost, { mocks, localVue, propsData })
|
||||
}
|
||||
|
||||
describe('shallowMount', () => {
|
||||
it('renders post title', () => {
|
||||
expect(wrapper.find('.search-option-label').text()).toMatch('Post Title')
|
||||
})
|
||||
|
||||
it('renders post commentsCount', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('.search-post-meta')
|
||||
.findAll('span')
|
||||
.filter(item => item.text() === '3')
|
||||
.exists(),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('renders post shoutedCount', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('.search-post-meta')
|
||||
.findAll('span')
|
||||
.filter(item => item.text() === '6')
|
||||
.exists(),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('renders post author', () => {
|
||||
expect(wrapper.find('.search-post-author').text()).toContain('Post Author')
|
||||
})
|
||||
|
||||
it('renders post createdAt', () => {
|
||||
expect(wrapper.find('.search-post-author').text()).toContain('23.08.2019')
|
||||
})
|
||||
})
|
||||
})
|
||||
71
webapp/components/generic/SearchPost/SearchPost.vue
Normal file
71
webapp/components/generic/SearchPost/SearchPost.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<ds-flex class="search-post">
|
||||
<ds-flex-item class="search-option-label">
|
||||
<ds-text>{{ option.title | truncate(70) }}</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item class="search-option-meta" width="280px">
|
||||
<ds-flex>
|
||||
<ds-flex-item>
|
||||
<ds-text size="small" color="softer" class="search-post-meta">
|
||||
<span class="comments-count">
|
||||
{{ option.commentsCount }}
|
||||
<base-icon name="comments" />
|
||||
</span>
|
||||
<span class="shouted-count">
|
||||
{{ option.shoutedCount }}
|
||||
<base-icon name="bullhorn" />
|
||||
</span>
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-text size="small" color="softer" align="right" class="search-post-author">
|
||||
{{ option.author.name | truncate(32) }} -
|
||||
{{ option.createdAt | dateTime('dd.MM.yyyy') }}
|
||||
</ds-text>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'SearchPost',
|
||||
props: {
|
||||
option: { type: Object, required: true },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.search-post {
|
||||
width: 100%;
|
||||
}
|
||||
.search-option-label {
|
||||
align-self: center;
|
||||
padding-left: $space-x-small;
|
||||
}
|
||||
.search-option-meta {
|
||||
align-self: center;
|
||||
.ds-flex {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.search-post-meta {
|
||||
float: right;
|
||||
padding-top: 2px;
|
||||
white-space: nowrap;
|
||||
word-wrap: none;
|
||||
.base-icon {
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
.shouted-count {
|
||||
width: 36px;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
.comments-count {
|
||||
text-align: right;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user