From: Chocobozzz Date: Wed, 6 Feb 2019 11:26:58 +0000 (+0100) Subject: Merge branch 'release/v1.2.0' X-Git-Tag: v1.3.0-rc.2~11^2~1 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=73471b1a52f242e86364ffb077ea6cadb3b07ae2;hp=c22419dd265c0c7185bf4197a1cb286eb3d8ebc0;p=github%2FChocobozzz%2FPeerTube.git Merge branch 'release/v1.2.0' --- diff --git a/.travis.yml b/.travis.yml index 3a73e4fc0..d252ae625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,12 +48,12 @@ matrix: - env: TEST_SUITE=jest script: - - travis_retry npm run travis -- "$TEST_SUITE" + - NODE_PENDING_JOB_WAIT=1000 travis_retry npm run travis -- "$TEST_SUITE" after_failure: - - cat test1/logs/all-logs.log - - cat test2/logs/all-logs.log - - cat test3/logs/all-logs.log - - cat test4/logs/all-logs.log - - cat test5/logs/all-logs.log - - cat test6/logs/all-logs.log + - cat test1/logs/peertube.log + - cat test2/logs/peertube.log + - cat test3/logs/peertube.log + - cat test4/logs/peertube.log + - cat test5/logs/peertube.log + - cat test6/logs/peertube.log diff --git a/CHANGELOG.md b/CHANGELOG.md index cce6e7402..13bec7535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,101 @@ # Changelog +## v1.2.0 + +### BREAKING CHANGES + + * **Docker:** `PEERTUBE_TRUST_PROXY` env variable is now an array ([LecygneNoir](https://github.com/LecygneNoir)) + * **Docker:** Check you have all the storage fields in your `/config/production.yaml` file: https://github.com/Chocobozzz/PeerTube/blob/develop/support/docker/production/config/production.yaml#L34 + * **nginx:** Add redundancy endpoint in static file. **You should add it in your nginx configuration: https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/production.md#nginx** + * **nginx:** Add socket io endpoint. **You should add it in your nginx configuration: https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/production.md#nginx** + * Moderators can manage users now (add/delete/update/block) + * Add `tmp` and `redundancy` directories in configuration file. **You should configure them in your production.yaml** + +### Maintenance + + * Check free storage before upgrading in upgrade script ([@Nutomic](https://github.com/nutomic)) + * Explain that PeerTube must be stopped in prune storage script + * Add some security directives in the systemd unit configuration file ([@rigelk](https://github.com/rigelk) & [@mkoppmann](https://github.com/mkoppmann)) + * Update FreeBSD startup script ([@gegeweb](https://github.com/gegeweb)) + +### Docker + + * Patch docker entrypoint to speed up the chown at startup ([LecygneNoir](https://github.com/LecygneNoir)) + +### Features + + * Add Russian, Polish and Italian languages + * Add user notifications: + * Notification types: + * Comment on my video + * New video from my subscriptions + * New video abuses (for moderators) + * Blacklist/Unblacklist on my video + * Video import finished (error or success) + * Pending video published (after transcoding or a scheduled update) + * My account or one of my channel has a new follower + * Someone (except muted accounts) mentioned me in comments + * A user registered on the instance (for moderators) + * Notification actions: + * Add a web notification + * Send an english email + * Add contact form in about page (**enabled by default**) + * Add ability to unfederate a local video in blacklist modal (**checkbox checked by default**) + * Support additional video extensions if transcoding is enabled (**enabled by default**) + * Redirect to the last url on login + * Add ability to automatically set the video caption in URL. Example: https://peertube2.cpy.re/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d?subtitle=ru + * Automatically enable the last selected caption when watching a video + * Add ability to disable, clear and list user videos history + * Add a button to help to translate peertube + * Add text in the report modal to explain to whom the report will be sent + * Open my account menu entries on hover + * Explain what features are enabled on the instance in the about page + * Add an error message in the forgot password modal if the instance email system is not configured + * Add sitemap + * Add well known url to change password ([@rigelk](https://github.com/rigelk)) + * Remove 8GB video upload limit on client side. There may still be such limit depending on the reverse proxy configuration ([@scanlime](https://github.com/scanlime)) + * Add CSP ([@rigelk](https://github.com/rigelk) & [@Nutomic](https://github.com/nutomic)) + * Update title and description HTML tags when rendering video HTML page + * Add webfinger support for remote follows ([@acid-chicken](https://github.com/acid-chicken)) + * Add tooltip to explain how the trending algorithm works ([@auberanger](https://github.com/auberanger)) + * Warn users when they want to delete a channel because they will not be able to create another channel with the same name + * Warn users when they leave the video upload/update (on page refresh/tab close) + * Set max user name, user display name, channel name and channel display name lengths to 50 characters ([@McFlat](https://github.com/mcflat)) + * Increase video abuse length to 3000 characters + * Add totalLocalVideoFilesSize in the stats endpoint + +## Bug fixes + + * Fix the addition of captions to a video + * Fix federation of some videos + * Fix NSFW blur on search + * Add error message when trying to upload .ass subtitles + * Fix default homepage in the progressive web application + * Don't crash on queue error + * Fix EXDEV errors if you have multiple mount points + * Fix broken audio in transcoding with some videos + * Fix crash on getVideoFileStream issue + * Fix followers search + * Remove trailing `/` in CLI import script ([@HesioZ](https://github.com/HesioZ/)) + * Use origin video url in canonical tag + * Fix captions in HTTP fallback + * Automatically refresh remote actors to fix deleted remote actors that are still displayed on some instances + * Add missing translations in video embed page + * Fix some styling issues in dark mode + * Fix transcoding issues with some videos + * Fix Mac OS mkv/avi upload + * Fix menu overflow on mobile + * Fix ownership button icons ([@joshmorel](https://github.com/joshmorel)) + + ## v1.1.0 ***Since v1.0.1*** +### BREAKING CHANGES + + * **Docker:** `PEERTUBE_TRUST_PROXY` env variable is now an array ([LecygneNoir](https://github.com/LecygneNoir)) + ### Maintenance * Improve REST API documentation: https://docs.joinpeertube.org/api.html ([@rigelk](https://github.com/rigelk)) @@ -26,7 +118,6 @@ * Add postfix image * Redirect HTTP -> HTTPS * Disable Træfik web UI - * Add ability to set an array in `PEERTUBE_TRUST_PROXY` ([LecygneNoir](https://github.com/LecygneNoir)) ### Features diff --git a/CREDITS.md b/CREDITS.md index ad5125227..509f9800d 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -6,25 +6,27 @@ * [Nutomic](https://github.com/Nutomic) * [Jorropo](https://github.com/Jorropo) * [BO41](https://github.com/BO41) + * [joshmorel](https://github.com/joshmorel) + * [buoyantair](https://github.com/buoyantair) * [bnjbvr](https://github.com/bnjbvr) * [DavidLibeau](https://github.com/DavidLibeau) * [jankeromnes](https://github.com/jankeromnes) - * [joshmorel](https://github.com/joshmorel) * [JohnXLivingston](https://github.com/JohnXLivingston) * [kaiyou](https://github.com/kaiyou) + * [McFlat](https://github.com/McFlat) * [DimitriGilbert](https://github.com/DimitriGilbert) * [floSoX](https://github.com/floSoX) * [Green-Star](https://github.com/Green-Star) + * [thomaskuntzz](https://github.com/thomaskuntzz) * [rezonant](https://github.com/rezonant) * [ldidry](https://github.com/ldidry) - * [McFlat](https://github.com/McFlat) * [okhin](https://github.com/okhin) * [daftaupe](https://github.com/daftaupe) - * [thomaskuntzz](https://github.com/thomaskuntzz) * [LecygneNoir](https://github.com/LecygneNoir) * [fflorent](https://github.com/fflorent) * [dedesite](https://github.com/dedesite) * [Nautigsam](https://github.com/Nautigsam) + * [scanlime](https://github.com/scanlime) * [tcitworld](https://github.com/tcitworld) * [am97](https://github.com/am97) * [dadall](https://github.com/dadall) @@ -35,7 +37,6 @@ * [jocelynj](https://github.com/jocelynj) * [lucas-dclrcq](https://github.com/lucas-dclrcq) * [lucaspontoexe](https://github.com/lucaspontoexe) - * [scanlime](https://github.com/scanlime) * [flyingrub](https://github.com/flyingrub) * [SerCom-KC](https://github.com/SerCom-KC) * [valvin1](https://github.com/valvin1) @@ -43,6 +44,7 @@ * [sticmac](https://github.com/sticmac) * [barbeque](https://github.com/barbeque) * [luzpaz](https://github.com/luzpaz) + * [acid-chicken](https://github.com/acid-chicken) * [louistio](https://github.com/louistio) * [qsypoq](https://github.com/qsypoq) * [daker](https://github.com/daker) @@ -65,6 +67,7 @@ * [grizio](https://github.com/grizio) * [Glandos](https://github.com/Glandos) * [lanodan](https://github.com/lanodan) + * [HesioZ](https://github.com/HesioZ) * [jagannathBhat](https://github.com/jagannathBhat) * [jlebras](https://github.com/jlebras) * [alcalyn](https://github.com/alcalyn) @@ -73,7 +76,9 @@ * [zapashcanon](https://github.com/zapashcanon) * [mart-e](https://github.com/mart-e) * [0mp](https://github.com/0mp) + * [mkoppmann](https://github.com/mkoppmann) * [1000i100](https://github.com/1000i100) + * [roipoussiere](https://github.com/roipoussiere) * [zeograd](https://github.com/zeograd) * [PhieF](https://github.com/PhieF) * [Quenty31](https://github.com/Quenty31) @@ -125,11 +130,14 @@ * [h3zjp](https://trad.framasoft.org/zanata/profile/view/h3zjp) * [jfblanc](https://trad.framasoft.org/zanata/profile/view/jfblanc) * [jhertel](https://trad.framasoft.org/zanata/profile/view/jhertel) + * [jmf](https://trad.framasoft.org/zanata/profile/view/jmf) * [jorropo](https://trad.framasoft.org/zanata/profile/view/jorropo) + * [kairozen](https://trad.framasoft.org/zanata/profile/view/kairozen) * [kedemferre](https://trad.framasoft.org/zanata/profile/view/kedemferre) * [kousha](https://trad.framasoft.org/zanata/profile/view/kousha) * [krkk](https://trad.framasoft.org/zanata/profile/view/krkk) * [landrok](https://trad.framasoft.org/zanata/profile/view/landrok) + * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48) * [m4sk1n](https://trad.framasoft.org/zanata/profile/view/m4sk1n) * [matograine](https://trad.framasoft.org/zanata/profile/view/matograine) * [medow](https://trad.framasoft.org/zanata/profile/view/medow) @@ -167,6 +175,10 @@ * [xinayder](https://trad.framasoft.org/zanata/profile/view/xinayder) * [xosem](https://trad.framasoft.org/zanata/profile/view/xosem) * [zveryok](https://trad.framasoft.org/zanata/profile/view/zveryok) + * [aditoo](https://trad.framasoft.org/zanata/profile/view/aditoo) + * [autom](https://trad.framasoft.org/zanata/profile/view/autom) + * [curupira](https://trad.framasoft.org/zanata/profile/view/curupira) + * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48) # Design diff --git a/FAQ.md b/FAQ.md index 1e586161c..e335868f8 100644 --- a/FAQ.md +++ b/FAQ.md @@ -13,6 +13,8 @@ - [Will an index of all the videos of servers you follow be too large for small servers?](#will-an-index-of-all-the-videos-of-servers-you-follow-be-too-large-for-small-servers) - [Which container formats can I use for the videos I want to upload?](#which-container-formats-can-i-use-for-the-videos-i-want-to-upload) - [I want to change my domain name, how can I do that?](#i-want-to-change-my-domain-name-how-can-i-do-that) +- [Why do we have to put our Twitter username in PeerTube configuration?](#why-do-we-have-to-put-our-twitter-username-in-peertube-configuration) +- [How video views are calculated?](#how-video-views-are-calculated) - [Should I have a big server to run PeerTube?](#should-i-have-a-big-server-to-run-peertube) - [Can I seed videos with my classic BitTorrent client (Transmission, rTorrent...)?](#can-i-seed-videos-with-my-classic-bittorrent-client-transmission-rtorrent) - [Why host on GitHub and Framagit?](#why-host-on-github-and-framagit) @@ -89,6 +91,18 @@ WEBM, MP4 or OGV videos. You can't. You'll need to reinstall an instance and reupload your videos. +## Why do we have to put our Twitter username in PeerTube configuration? + +You don't have to: we set a default value if you don't have a Twitter account. +We need this information because Twitter requires an account for links share/videos embed on their platform. + + +## How video views are calculated? + +Your web browser sends a view to the server after 30 seconds of playback. Then, the IP cannot send another view in the next hour. +Views are buffered, so don't panic if the view counter stays the same after you watched a video. + + ## Should I have a big server to run PeerTube? Not really. For instance, the demonstration server [https://peertube.cpy.re](https://peertube.cpy.re) has 2 vCore and 2GB of RAM and consumes on average: diff --git a/README.md b/README.md index a3669353b..a9b4eb54a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ Be part of a network of multiple small federated, interoperable video hosting providers. Follow video creators and create videos. No vendor lock-in. All on a platform that is community-owned and ad-free.

+

+ Developed with ❤ by Framasoft +

+ +

+ + Framasoft logo + +

+

Client @@ -123,7 +133,7 @@ You can also join the cheerful bunch that makes our community: * Chat: * IRC : **[#peertube on chat.freenode.net:6697](https://kiwiirc.com/client/irc.freenode.net/#peertube)** - * Matrix (bridged on the IRC channel) : **[#peertube:matrix.org](https://matrix.to/#/#peertube:matrix.org)** + * Matrix (bridged on IRC and [Discord](https://discord.gg/wj8DDUT)) : **[#peertube:matrix.org](https://matrix.to/#/#peertube:matrix.org)** * Forum: * Framacolibri: [https://framacolibri.org/c/peertube](https://framacolibri.org/c/peertube) diff --git a/client/package.json b/client/package.json index 3761a641a..31fc77887 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "peertube-client", - "version": "1.1.0", + "version": "1.2.0", "private": true, "licence": "GPLv3", "author": { @@ -63,26 +63,26 @@ "setupTestFrameworkScriptFile": "/src/setupJest.ts" }, "devDependencies": { - "@angular-devkit/build-angular": "~0.10.0", - "@angular/animations": "~7.0.2", - "@angular/cli": "~7.0.4", - "@angular/common": "~7.0.2", - "@angular/compiler": "~7.0.2", - "@angular/compiler-cli": "~7.0.2", - "@angular/core": "~7.0.2", - "@angular/forms": "~7.0.2", - "@angular/http": "~7.0.2", - "@angular/language-service": "~7.0.2", - "@angular/platform-browser": "~7.0.2", - "@angular/platform-browser-dynamic": "~7.0.2", - "@angular/router": "~7.0.2", - "@angular/service-worker": "~7.0.2", + "@angular-devkit/build-angular": "~0.11.1", + "@angular/animations": "~7.1.1", + "@angular/cli": "~7.1.1", + "@angular/common": "~7.1.1", + "@angular/compiler": "~7.1.1", + "@angular/compiler-cli": "~7.1.1", + "@angular/core": "~7.1.1", + "@angular/forms": "~7.1.1", + "@angular/http": "~7.1.1", + "@angular/language-service": "~7.1.1", + "@angular/platform-browser": "~7.1.1", + "@angular/platform-browser-dynamic": "~7.1.1", + "@angular/router": "~7.1.1", + "@angular/service-worker": "~7.1.1", "@angularclass/hmr": "^2.1.3", "@neos21/bootstrap3-glyphicons": "^1.0.1", "@ng-bootstrap/ng-bootstrap": "^4.0.0", - "@ngx-loading-bar/core": "^2.2.0", - "@ngx-loading-bar/http-client": "^2.2.0", - "@ngx-loading-bar/router": "^2.2.0", + "@ngx-loading-bar/core": "^3.0.0", + "@ngx-loading-bar/http-client": "^3.0.0", + "@ngx-loading-bar/router": "^3.0.0", "@ngx-meta/core": "^6.0.0-rc.1", "@ngx-translate/i18n-polyfill": "^1.0.0", "@types/core-js": "^2.5.0", @@ -94,10 +94,10 @@ "@types/markdown-it": "^0.0.5", "@types/node": "^10.9.2", "@types/sanitize-html": "1.18.0", + "@types/socket.io-client": "^1.4.32", "@types/video.js": "^7.2.5", "@types/webtorrent": "^0.98.4", "angular2-hotkeys": "^2.1.2", - "angular2-notifications": "^1.0.2", "awesome-typescript-loader": "5.2.1", "bootstrap": "^4.1.3", "buffer": "^5.1.0", @@ -132,7 +132,7 @@ "node-sass": "^4.9.3", "npm-font-source-sans-pro": "^1.0.2", "path-browserify": "^1.0.0", - "primeng": "^6.1.2", + "primeng": "^7.0.0", "process": "^0.11.10", "protractor": "^5.3.2", "purify-css": "^1.2.5", @@ -142,6 +142,7 @@ "sanitize-html": "^1.18.4", "sass-loader": "^7.1.0", "sass-resources-loader": "^2.0.0", + "socket.io-client": "^2.2.0", "stream-browserify": "^2.0.1", "stream-http": "^3.0.0", "terser-webpack-plugin": "^1.1.0", @@ -153,7 +154,6 @@ "videojs-contextmenu-ui": "^5.0.0", "videojs-dock": "^2.0.2", "videojs-hotkeys": "^0.2.21", - "webpack": "^4.17.1", "webpack-bundle-analyzer": "^3.0.2", "webpack-cli": "^3.0.8", "webtorrent": "https://github.com/webtorrent/webtorrent#e9b209c7970816fc29e0cc871157a4918d66001d", diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 5970cac01..8c700752e 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html @@ -1,39 +1,52 @@ -

- About {{ instanceName }} instance -
+
+
+
+
About {{ instanceName }} instance
-
-
{{ shortDescription }}
-
+
Contact administrator
+
-
-
Description
+
+
{{ shortDescription }}
+
-
-
+
+
Description
-
-
Terms
+
+
-
-
+
+
Terms
- -
- User registration is allowed and +
-
- User registration is currently not allowed. +
+ +
-
\ No newline at end of file +
+ + diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss index b451e85aa..75cf57322 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss @@ -2,9 +2,19 @@ @import '_mixins'; .about-instance-title { - font-size: 20px; - font-weight: bold; - margin-bottom: 15px; + display: flex; + justify-content: space-between; + + & > div { + font-size: 20px; + font-weight: bold; + margin-bottom: 15px; + } + + & > .contact-admin { + @include peertube-button; + @include orange-button; + } } .section-title { diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index 354f52ce7..a1b30fa8c 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts @@ -1,23 +1,26 @@ -import { Component, OnInit } from '@angular/core' -import { ServerService } from '@app/core' -import { MarkdownService } from '@app/videos/shared' -import { NotificationsService } from 'angular2-notifications' +import { Component, OnInit, ViewChild } from '@angular/core' +import { Notifier, ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' +import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' +import { InstanceService } from '@app/shared/instance/instance.service' +import { MarkdownService } from '@app/shared/renderer' @Component({ selector: 'my-about-instance', templateUrl: './about-instance.component.html', styleUrls: [ './about-instance.component.scss' ] }) - export class AboutInstanceComponent implements OnInit { + @ViewChild('contactAdminModal') contactAdminModal: ContactAdminModalComponent + shortDescription = '' descriptionHTML = '' termsHTML = '' constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private serverService: ServerService, + private instanceService: InstanceService, private markdownService: MarkdownService, private i18n: I18n ) {} @@ -34,8 +37,12 @@ export class AboutInstanceComponent implements OnInit { return this.serverService.getConfig().signup.allowed } + get isContactFormEnabled () { + return this.serverService.getConfig().email.enabled && this.serverService.getConfig().contactForm.enabled + } + ngOnInit () { - this.serverService.getAbout() + this.instanceService.getAbout() .subscribe( res => { this.shortDescription = res.instance.shortDescription @@ -43,8 +50,12 @@ export class AboutInstanceComponent implements OnInit { this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms) }, - err => this.notificationsService.error(this.i18n('Error getting about from server'), err) + () => this.notifier.error(this.i18n('Cannot get about information from server')) ) } + openContactModal () { + return this.contactAdminModal.show() + } + } diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html new file mode 100644 index 000000000..b2cbd0873 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.html @@ -0,0 +1,50 @@ + + + + + diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss new file mode 100644 index 000000000..260d77888 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.scss @@ -0,0 +1,11 @@ +@import 'variables'; +@import 'mixins'; + +input[type=text] { + @include peertube-input-text(340px); + display: block; +} + +textarea { + @include peertube-textarea(100%, 200px); +} diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts new file mode 100644 index 000000000..7d79c2215 --- /dev/null +++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts @@ -0,0 +1,77 @@ +import { Component, OnInit, ViewChild } from '@angular/core' +import { Notifier, ServerService } from '@app/core' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { FormReactive, InstanceValidatorsService } from '@app/shared' +import { InstanceService } from '@app/shared/instance/instance.service' + +@Component({ + selector: 'my-contact-admin-modal', + templateUrl: './contact-admin-modal.component.html', + styleUrls: [ './contact-admin-modal.component.scss' ] +}) +export class ContactAdminModalComponent extends FormReactive implements OnInit { + @ViewChild('modal') modal: NgbModal + + error: string + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private instanceValidatorsService: InstanceValidatorsService, + private instanceService: InstanceService, + private serverService: ServerService, + private notifier: Notifier, + private i18n: I18n + ) { + super() + } + + get instanceName () { + return this.serverService.getConfig().instance.name + } + + ngOnInit () { + this.buildForm({ + fromName: this.instanceValidatorsService.FROM_NAME, + fromEmail: this.instanceValidatorsService.FROM_EMAIL, + body: this.instanceValidatorsService.BODY + }) + } + + show () { + this.openedModal = this.modalService.open(this.modal, { keyboard: false }) + } + + hide () { + this.form.reset() + this.error = undefined + + this.openedModal.close() + this.openedModal = null + } + + sendForm () { + const fromName = this.form.value['fromName'] + const fromEmail = this.form.value[ 'fromEmail' ] + const body = this.form.value[ 'body' ] + + this.instanceService.contactAdministrator(fromEmail, fromName, body) + .subscribe( + () => { + this.notifier.success(this.i18n('Your message has been sent.')) + this.hide() + }, + + err => { + this.error = err.status === 403 + ? this.i18n('You already sent this form recently') + : err.message + } + ) + } +} diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts index ff6e8ef41..9c6b29740 100644 --- a/client/src/app/+about/about.module.ts +++ b/client/src/app/+about/about.module.ts @@ -5,6 +5,7 @@ import { AboutComponent } from './about.component' import { SharedModule } from '../shared' import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' +import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' @NgModule({ imports: [ @@ -15,7 +16,8 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub declarations: [ AboutComponent, AboutInstanceComponent, - AboutPeertubeComponent + AboutPeertubeComponent, + ContactAdminModalComponent ], exports: [ diff --git a/client/src/app/+accounts/account-about/account-about.component.ts b/client/src/app/+accounts/account-about/account-about.component.ts index 6f3e6caa0..13890a0ee 100644 --- a/client/src/app/+accounts/account-about/account-about.component.ts +++ b/client/src/app/+accounts/account-about/account-about.component.ts @@ -1,9 +1,9 @@ -import { Component, OnInit, OnDestroy } from '@angular/core' +import { Component, OnDestroy, OnInit } from '@angular/core' import { Account } from '@app/shared/account/account.model' import { AccountService } from '@app/shared/account/account.service' import { I18n } from '@ngx-translate/i18n-polyfill' import { Subscription } from 'rxjs' -import { MarkdownService } from '@app/videos/shared' +import { MarkdownService } from '@app/shared/renderer' @Component({ selector: 'my-account-about', diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index e5c1f58b0..13b634a01 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts @@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { Location } from '@angular/common' import { immutableAssign } from '@app/shared/misc/utils' -import { NotificationsService } from 'angular2-notifications' import { AuthService } from '../../core/auth' import { ConfirmService } from '../../core/confirm' import { AbstractVideoList } from '../../shared/video/abstract-video-list' @@ -13,6 +12,7 @@ import { tap } from 'rxjs/operators' import { I18n } from '@ngx-translate/i18n-polyfill' import { Subscription } from 'rxjs' import { ScreenService } from '@app/shared/misc/screen.service' +import { Notifier } from '@app/core' @Component({ selector: 'my-account-videos', @@ -35,7 +35,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, protected router: Router, protected route: ActivatedRoute, protected authService: AuthService, - protected notificationsService: NotificationsService, + protected notifier: Notifier, protected confirmService: ConfirmService, protected location: Location, protected screenService: ScreenService, diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index e19927d6b..e8339b78b 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts @@ -5,10 +5,9 @@ import { Account } from '@app/shared/account/account.model' import { RestExtractor, UserService } from '@app/shared' import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' import { Subscription } from 'rxjs' -import { NotificationsService } from 'angular2-notifications' +import { AuthService, Notifier, RedirectService } from '@app/core' import { User, UserRight } from '../../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' -import { AuthService, RedirectService } from '@app/core' @Component({ templateUrl: './accounts.component.html', @@ -24,11 +23,10 @@ export class AccountsComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private userService: UserService, private accountService: AccountService, - private notificationsService: NotificationsService, + private notifier: Notifier, private restExtractor: RestExtractor, private redirectService: RedirectService, - private authService: AuthService, - private i18n: I18n + private authService: AuthService ) {} ngOnInit () { @@ -43,7 +41,7 @@ export class AccountsComponent implements OnInit, OnDestroy { .subscribe( account => this.account = account, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -69,7 +67,7 @@ export class AccountsComponent implements OnInit, OnDestroy { .subscribe( user => this.user = user, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index fd4d3d9c9..52eb00d93 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -7,169 +7,169 @@
Instance
-
- - -
- {{ formErrors.instanceName }} + +
+ + +
{{ formErrors.instance.name }}
-
-
- - -
- {{ formErrors.instanceShortDescription }} +
+ + +
{{ formErrors.instance.shortDescription }}
-
-
- - -
- {{ formErrors.instanceDescription }} +
+ + +
{{ formErrors.instance.description }}
-
-
- - -
- {{ formErrors.instanceTerms }} +
+ + +
{{ formErrors.instance.terms }}
-
-
- -
- +
+ +
+ +
+
{{ formErrors.instance.defaultClientRoute }}
-
- {{ formErrors.instanceDefaultClientRoute }} + +
+ + + +
+ +
+
{{ formErrors.instance.defaultNSFWPolicy }}
-
+ -
- - +
Signup
-
- + +
+
-
- {{ formErrors.instanceDefaultNSFWPolicy }} + +
+
-
-
Signup
+
+ + +
{{ formErrors.signup.limit }}
+
+
-
- -
+
Users
-
- -
+ +
+ +
+ +
+
{{ formErrors.user.videoQuota }}
+
-
- - -
- {{ formErrors.signupLimit }} +
+ +
+ +
+
{{ formErrors.user.videoQuotaDaily }}
-
+
Import
-
- -
+ + -
- -
+
+ +
+ +
+ +
+ +
+
Administrator
-
+
-
- {{ formErrors.adminEmail }} -
+
{{ formErrors.admin.email }}
-
Users
- -
- -
- -
-
- {{ formErrors.userVideoQuota }} -
+
+
-
- -
- -
-
- {{ formErrors.userVideoQuotaDaily }} -
-
@@ -177,30 +177,35 @@
Twitter
-
- - - -
- {{ formErrors.servicesTwitterUsername }} -
-
+ + + +
+ + + +
{{ formErrors.services.twitter.username }}
+
+ +
+ +
+ +
+
-
- -
@@ -209,37 +214,48 @@
Transcoding
-
- -
+ +
+ +
- + -
- -
- +
+
-
- {{ formErrors.transcodingThreads }} + +
+ +
+ +
+
{{ formErrors.transcoding.threads }}
-
-
- -
- + +
+ +
+
+ + +
Cache @@ -250,74 +266,73 @@ >
-
- - -
- {{ formErrors.cachePreviewsSize }} + +
+ + +
{{ formErrors.cache.previews.size }}
-
-
- - -
- {{ formErrors.cacheCaptionsSize }} +
+ + +
{{ formErrors.cache.captions.size }}
-
+
Customizations
-
- - - -
- {{ formErrors.customizationJavascript }} -
-
+ + +
+ + + +
{{ formErrors.instance.customizations.javascript }}
+
+ +
+ + + +
{{ formErrors.instance.customizations.css }}
+
+
+
-
- - - -
- {{ formErrors.customizationCSS }} -
-
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index f48b6fc1a..654a076b0 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core' import { ConfigService } from '@app/+admin/config/shared/config.service' import { ServerService } from '@app/core/server/server.service' import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' import { I18n } from '@ngx-translate/i18n-polyfill' import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' @@ -18,14 +18,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { resolutions: string[] = [] transcodingThreadOptions: { label: string, value: number }[] = [] - private oldCustomJavascript: string - private oldCustomCSS: string - constructor ( protected formValidatorService: FormValidatorService, private customConfigValidatorsService: CustomConfigValidatorsService, private userValidatorsService: UserValidatorsService, - private notificationsService: NotificationsService, + private notifier: Notifier, private configService: ConfigService, private serverService: ServerService, private i18n: I18n @@ -58,40 +55,78 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { } getResolutionKey (resolution: string) { - return 'transcodingResolution' + resolution + return 'transcoding.resolutions.' + resolution } ngOnInit () { - const formGroupData: { [key: string]: any } = { - instanceName: this.customConfigValidatorsService.INSTANCE_NAME, - instanceShortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, - instanceDescription: null, - instanceTerms: null, - instanceDefaultClientRoute: null, - instanceDefaultNSFWPolicy: null, - servicesTwitterUsername: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, - servicesTwitterWhitelisted: null, - cachePreviewsSize: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE, - cacheCaptionsSize: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE, - signupEnabled: null, - signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT, - signupRequiresEmailVerification: null, - importVideosHttpEnabled: null, - importVideosTorrentEnabled: null, - adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, - userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, - userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, - transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, - transcodingEnabled: null, - customizationJavascript: null, - customizationCSS: null + const formGroupData: { [key in keyof CustomConfig ]: any } = { + instance: { + name: this.customConfigValidatorsService.INSTANCE_NAME, + shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, + description: null, + terms: null, + defaultClientRoute: null, + defaultNSFWPolicy: null, + customizations: { + javascript: null, + css: null + } + }, + services: { + twitter: { + username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, + whitelisted: null + } + }, + cache: { + previews: { + size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE + }, + captions: { + size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE + } + }, + signup: { + enabled: null, + limit: this.customConfigValidatorsService.SIGNUP_LIMIT, + requiresEmailVerification: null + }, + import: { + videos: { + http: { + enabled: null + }, + torrent: { + enabled: null + } + } + }, + admin: { + email: this.customConfigValidatorsService.ADMIN_EMAIL + }, + contactForm: { + enabled: null + }, + user: { + videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, + videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY + }, + transcoding: { + enabled: null, + threads: this.customConfigValidatorsService.TRANSCODING_THREADS, + allowAdditionalExtensions: null, + resolutions: {} + } } - const defaultValues: BuildFormDefaultValues = {} + const defaultValues = { + transcoding: { + resolutions: {} + } + } for (const resolution of this.resolutions) { - const key = this.getResolutionKey(resolution) - defaultValues[key] = 'false' - formGroupData[key] = null + defaultValues.transcoding.resolutions[resolution] = 'false' + formGroupData.transcoding.resolutions[resolution] = null } this.buildForm(formGroupData) @@ -101,90 +136,25 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { res => { this.customConfig = res - this.oldCustomCSS = this.customConfig.instance.customizations.css - this.oldCustomJavascript = this.customConfig.instance.customizations.javascript - this.updateForm() // Force form validation this.forceCheck() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } isTranscodingEnabled () { - return this.form.value['transcodingEnabled'] === true + return this.form.value['transcoding']['enabled'] === true } isSignupEnabled () { - return this.form.value['signupEnabled'] === true + return this.form.value['signup']['enabled'] === true } async formValidated () { - const data: CustomConfig = { - instance: { - name: this.form.value['instanceName'], - shortDescription: this.form.value['instanceShortDescription'], - description: this.form.value['instanceDescription'], - terms: this.form.value['instanceTerms'], - defaultClientRoute: this.form.value['instanceDefaultClientRoute'], - defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'], - customizations: { - javascript: this.form.value['customizationJavascript'], - css: this.form.value['customizationCSS'] - } - }, - services: { - twitter: { - username: this.form.value['servicesTwitterUsername'], - whitelisted: this.form.value['servicesTwitterWhitelisted'] - } - }, - cache: { - previews: { - size: this.form.value['cachePreviewsSize'] - }, - captions: { - size: this.form.value['cacheCaptionsSize'] - } - }, - signup: { - enabled: this.form.value['signupEnabled'], - limit: this.form.value['signupLimit'], - requiresEmailVerification: this.form.value['signupRequiresEmailVerification'] - }, - admin: { - email: this.form.value['adminEmail'] - }, - user: { - videoQuota: this.form.value['userVideoQuota'], - videoQuotaDaily: this.form.value['userVideoQuotaDaily'] - }, - transcoding: { - enabled: this.form.value['transcodingEnabled'], - threads: this.form.value['transcodingThreads'], - resolutions: { - '240p': this.form.value[this.getResolutionKey('240p')], - '360p': this.form.value[this.getResolutionKey('360p')], - '480p': this.form.value[this.getResolutionKey('480p')], - '720p': this.form.value[this.getResolutionKey('720p')], - '1080p': this.form.value[this.getResolutionKey('1080p')] - } - }, - import: { - videos: { - http: { - enabled: this.form.value['importVideosHttpEnabled'] - }, - torrent: { - enabled: this.form.value['importVideosTorrentEnabled'] - } - } - } - } - - this.configService.updateCustomConfig(data) + this.configService.updateCustomConfig(this.form.value) .subscribe( res => { this.customConfig = res @@ -194,45 +164,15 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { this.updateForm() - this.notificationsService.success(this.i18n('Success'), this.i18n('Configuration updated.')) + this.notifier.success(this.i18n('Configuration updated.')) }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } private updateForm () { - const data: { [key: string]: any } = { - instanceName: this.customConfig.instance.name, - instanceShortDescription: this.customConfig.instance.shortDescription, - instanceDescription: this.customConfig.instance.description, - instanceTerms: this.customConfig.instance.terms, - instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, - instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy, - servicesTwitterUsername: this.customConfig.services.twitter.username, - servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, - cachePreviewsSize: this.customConfig.cache.previews.size, - cacheCaptionsSize: this.customConfig.cache.captions.size, - signupEnabled: this.customConfig.signup.enabled, - signupLimit: this.customConfig.signup.limit, - signupRequiresEmailVerification: this.customConfig.signup.requiresEmailVerification, - adminEmail: this.customConfig.admin.email, - userVideoQuota: this.customConfig.user.videoQuota, - userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, - transcodingThreads: this.customConfig.transcoding.threads, - transcodingEnabled: this.customConfig.transcoding.enabled, - customizationJavascript: this.customConfig.instance.customizations.javascript, - customizationCSS: this.customConfig.instance.customizations.css, - importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, - importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled - } - - for (const resolution of this.resolutions) { - const key = this.getResolutionKey(resolution) - data[key] = this.customConfig.transcoding.resolutions[resolution] - } - - this.form.patchValue(data) + this.form.patchValue(this.customConfig) } } diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index 4a25b7ff3..9a8848bfb 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { SortMeta } from 'primeng/primeng' import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' import { RestPagination, RestTable } from '../../../shared' @@ -20,7 +20,7 @@ export class FollowersListComponent extends RestTable implements OnInit { pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private followService: FollowService, private i18n: I18n ) { @@ -32,14 +32,14 @@ export class FollowersListComponent extends RestTable implements OnInit { } protected loadData () { - this.followService.getFollowers(this.pagination, this.sort) + this.followService.getFollowers(this.pagination, this.sort, this.search) .subscribe( resultList => { this.followers = resultList.data this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts index bd9cc022b..2bb249746 100644 --- a/client/src/app/+admin/follows/following-add/following-add.component.ts +++ b/client/src/app/+admin/follows/following-add/following-add.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core' import { Router } from '@angular/router' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { ConfirmService } from '../../../core' import { validateHost } from '../../../shared' import { FollowService } from '../shared' @@ -18,7 +18,7 @@ export class FollowingAddComponent { constructor ( private router: Router, - private notificationsService: NotificationsService, + private notifier: Notifier, private confirmService: ConfirmService, private followService: FollowService, private i18n: I18n @@ -64,12 +64,12 @@ export class FollowingAddComponent { this.followService.follow(hosts).subscribe( () => { - this.notificationsService.success(this.i18n('Success'), this.i18n('Follow request(s) sent!')) + this.notifier.success(this.i18n('Follow request(s) sent!')) setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index 9b7029f75..4517a721e 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { SortMeta } from 'primeng/primeng' import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' import { ConfirmService } from '../../../core/confirm/confirm.service' @@ -20,7 +20,7 @@ export class FollowingListComponent extends RestTable implements OnInit { pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private confirmService: ConfirmService, private followService: FollowService, private i18n: I18n @@ -41,14 +41,11 @@ export class FollowingListComponent extends RestTable implements OnInit { this.followService.unfollow(follow).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('You are not following {{host}} anymore.', { host: follow.following.host }) - ) + this.notifier.success(this.i18n('You are not following {{host}} anymore.', { host: follow.following.host })) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -60,7 +57,7 @@ export class FollowingListComponent extends RestTable implements OnInit { this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts index 6d77a0eb4..fa1da26bf 100644 --- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts +++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' @@ -13,24 +13,21 @@ export class RedundancyCheckboxComponent { @Input() host: string constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private redundancyService: RedundancyService, private i18n: I18n ) { } updateRedundancyState () { this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed) - .subscribe( - () => { - const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') + .subscribe( + () => { + const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled') - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel }) - ) - }, + this.notifier.success(this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel })) + }, - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + err => this.notifier.error(err.message) + ) } } diff --git a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts index 44778ab56..b265e1dd6 100644 --- a/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts +++ b/client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core' import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { SortMeta } from 'primeng/primeng' import { Job } from '../../../../../../shared/index' import { JobState } from '../../../../../../shared/models' @@ -25,7 +25,7 @@ export class JobsListComponent extends RestTable implements OnInit { pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private jobsService: JobService, private i18n: I18n ) { @@ -53,7 +53,7 @@ export class JobsListComponent extends RestTable implements OnInit { this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts index 3f243aee4..032bf745a 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { RestPagination, RestTable } from '@app/shared' import { SortMeta } from 'primeng/components/common/sortmeta' -import { BlocklistService, AccountBlock } from '@app/shared/blocklist' +import { AccountBlock, BlocklistService } from '@app/shared/blocklist' @Component({ selector: 'my-instance-account-blocklist', @@ -18,7 +18,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private blocklistService: BlocklistService, private i18n: I18n ) { @@ -35,8 +35,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn this.blocklistService.unblockAccountByInstance(blockedAccount) .subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), + this.notifier.success( this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost }) ) @@ -53,7 +52,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts index 130009dc7..db3dfcd1c 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { RestPagination, RestTable } from '@app/shared' import { SortMeta } from 'primeng/components/common/sortmeta' @@ -19,7 +19,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private blocklistService: BlocklistService, private i18n: I18n ) { @@ -36,10 +36,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni this.blocklistService.unblockServerByInstance(host) .subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Instance {{host}} unmuted by your instance.', { host }) - ) + this.notifier.success(this.i18n('Instance {{host}} unmuted by your instance.', { host })) this.loadData() } @@ -54,7 +51,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index 02ccfc8ca..13b019c5b 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss @@ -10,6 +10,7 @@ font-weight: $font-semibold; min-width: 200px; display: inline-block; + vertical-align: top; } .moderation-expanded-text { diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html index 3a8424f68..303a788d2 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html @@ -1,7 +1,8 @@
-
+
This comment can only be seen by you or the other moderators.
- Cancel + Cancel
- \ No newline at end of file + diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts index 34ab384d1..f915978ee 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' @@ -22,7 +22,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI constructor ( protected formValidatorService: FormValidatorService, private modalService: NgbModal, - private notificationsService: NotificationsService, + private notifier: Notifier, private videoAbuseService: VideoAbuseService, private videoAbuseValidatorsService: VideoAbuseValidatorsService, private i18n: I18n @@ -45,29 +45,26 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI }) } - hideModerationCommentModal () { + hide () { this.abuseToComment = undefined this.openedModal.close() this.form.reset() } async banUser () { - const moderationComment: string = this.form.value['moderationComment'] + const moderationComment: string = this.form.value[ 'moderationComment' ] this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) - .subscribe( - () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Comment updated.') - ) + .subscribe( + () => { + this.notifier.success(this.i18n('Comment updated.')) - this.commentUpdated.emit(moderationComment) - this.hideModerationCommentModal() - }, + this.commentUpdated.emit(moderationComment) + this.hide() + }, - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + err => this.notifier.error(err.message) + ) } } diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 0374b70ef..05b549de6 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html @@ -41,7 +41,7 @@ - + @@ -51,15 +51,15 @@
Reason: - {{ videoAbuse.reason }} +
Moderation comment: - {{ videoAbuse.moderationComment }} +
- \ No newline at end of file + diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 7a219c846..00c871659 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChild } from '@angular/core' import { Account } from '../../../shared/account/account.model' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { SortMeta } from 'primeng/components/common/sortmeta' import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' @@ -9,6 +9,7 @@ import { DropdownAction } from '../../../shared/buttons/action-dropdown.componen import { ConfirmService } from '../../../core/index' import { ModerationCommentModalComponent } from './moderation-comment-modal.component' import { Video } from '../../../shared/video/video.model' +import { MarkdownService } from '@app/shared/renderer' @Component({ selector: 'my-video-abuse-list', @@ -27,10 +28,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { videoAbuseActions: DropdownAction[] = [] constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private videoAbuseService: VideoAbuseService, private confirmService: ConfirmService, - private i18n: I18n + private i18n: I18n, + private markdownRenderer: MarkdownService ) { super() @@ -90,14 +92,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Abuse deleted.') - ) + this.notifier.success(this.i18n('Abuse deleted.')) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -106,11 +105,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { .subscribe( () => this.loadData(), - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } + toHtml (text: string) { + return this.markdownRenderer.textMarkdownToHTML(text) + } + protected loadData () { return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) .subscribe( @@ -119,7 +122,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html index ff4543b97..247f441c1 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html @@ -7,6 +7,7 @@ Video name Sensitive + Unfederated Date @@ -26,20 +27,21 @@ - {{ videoBlacklist.video.nsfw }} + {{ booleanToText(videoBlacklist.video.nsfw) }} + {{ booleanToText(videoBlacklist.unfederated) }} {{ videoBlacklist.createdAt }} - + - + Blacklist reason: - {{ videoBlacklist.reason }} + diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index e491edaca..b27bbbfef 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts @@ -1,12 +1,13 @@ import { Component, OnInit } from '@angular/core' import { SortMeta } from 'primeng/components/common/sortmeta' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { ConfirmService } from '../../../core' import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared' import { VideoBlacklist } from '../../../../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' import { Video } from '../../../shared/video/video.model' +import { MarkdownService } from '@app/shared/renderer' @Component({ selector: 'my-video-blacklist-list', @@ -23,9 +24,10 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { videoBlacklistActions: DropdownAction[] = [] constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private confirmService: ConfirmService, private videoBlacklistService: VideoBlacklistService, + private markdownRenderer: MarkdownService, private i18n: I18n ) { super() @@ -46,6 +48,16 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { return Video.buildClientUrl(videoBlacklist.video.uuid) } + booleanToText (value: boolean) { + if (value === true) return this.i18n('yes') + + return this.i18n('no') + } + + toHtml (text: string) { + return this.markdownRenderer.textMarkdownToHTML(text) + } + async removeVideoFromBlacklist (entry: VideoBlacklist) { const confirmMessage = this.i18n( 'Do you really want to remove this video from the blacklist? It will be available again in the videos list.' @@ -56,14 +68,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name }) - ) + this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -75,7 +84,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts index dd8e4efd5..137ecfcbd 100644 --- a/client/src/app/+admin/users/user-edit/user-create.component.ts +++ b/client/src/app/+admin/users/user-edit/user-create.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' -import { NotificationsService } from 'angular2-notifications' -import { ServerService } from '../../../core' +import { Notifier, ServerService } from '@app/core' import { UserCreate, UserRole } from '../../../../../../shared' import { UserEdit } from './user-edit' import { I18n } from '@ngx-translate/i18n-polyfill' @@ -24,7 +23,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { protected configService: ConfigService, private userValidatorsService: UserValidatorsService, private router: Router, - private notificationsService: NotificationsService, + private notifier: Notifier, private userService: UserService, private i18n: I18n ) { @@ -60,10 +59,7 @@ export class UserCreateComponent extends UserEdit implements OnInit { this.userService.addUser(userCreate).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('User {{username}} created.', { username: userCreate.username }) - ) + this.notifier.success(this.i18n('User {{username}} created.', { username: userCreate.username })) this.router.navigate([ '/admin/users/list' ]) }, diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts index cd3885a99..61e641823 100644 --- a/client/src/app/+admin/users/user-edit/user-update.component.ts +++ b/client/src/app/+admin/users/user-edit/user-update.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { Subscription } from 'rxjs' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { ServerService } from '../../../core' import { UserEdit } from './user-edit' import { User, UserUpdate } from '../../../../../../shared' @@ -30,7 +30,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { private userValidatorsService: UserValidatorsService, private route: ActivatedRoute, private router: Router, - private notificationsService: NotificationsService, + private notifier: Notifier, private userService: UserService, private i18n: I18n ) { @@ -73,10 +73,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { this.userService.updateUser(this.userId, userUpdate).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('User {{username}} updated.', { username: this.username }) - ) + this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username })) this.router.navigate([ '/admin/users/list' ]) }, diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 556ab3c5d..69a4616a3 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html @@ -2,7 +2,7 @@
Users list
- + Create user
@@ -65,7 +65,9 @@ (banned) + {{ user.email }} + ? {{ user.email }} @@ -76,6 +78,7 @@ + {{ user.videoQuotaUsed }} / {{ user.videoQuota }} {{ user.roleLabel }} {{ user.createdAt }} diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index f235769f0..5274be01c 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss @@ -2,7 +2,7 @@ @import '_mixins'; .add-button { - @include create-button('../../../../assets/images/global/add.svg'); + @include create-button; } tr.banned { @@ -23,4 +23,4 @@ tr.banned { input { @include peertube-input-text(250px); } -} \ No newline at end of file +} diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index fb085c133..66ab796f9 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { SortMeta } from 'primeng/components/common/sortmeta' import { ConfirmService, ServerService } from '../../../core' import { RestPagination, RestTable, UserService } from '../../../shared' @@ -26,7 +26,7 @@ export class UserListComponent extends RestTable implements OnInit { bulkUserActions: DropdownAction[] = [] constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private confirmService: ConfirmService, private serverService: ServerService, private userService: UserService, @@ -68,7 +68,7 @@ export class UserListComponent extends RestTable implements OnInit { openBanUserModal (users: User[]) { for (const user of users) { if (user.username === 'root') { - this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.')) + this.notifier.error(this.i18n('You cannot ban root.')) return } } @@ -91,18 +91,18 @@ export class UserListComponent extends RestTable implements OnInit { () => { const message = this.i18n('{{num}} users unbanned.', { num: users.length }) - this.notificationsService.success(this.i18n('Success'), message) + this.notifier.success(message) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } async removeUsers (users: User[]) { for (const user of users) { if (user.username === 'root') { - this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) + this.notifier.error(this.i18n('You cannot delete root.')) return } } @@ -113,28 +113,22 @@ export class UserListComponent extends RestTable implements OnInit { this.userService.removeUser(users).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('{{num}} users deleted.', { num: users.length }) - ) + this.notifier.success(this.i18n('{{num}} users deleted.', { num: users.length })) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } async setEmailsAsVerified (users: User[]) { this.userService.updateUsers(users, { emailVerified: true }).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('{{num}} users email set as verified.', { num: users.length }) - ) + this.notifier.success(this.i18n('{{num}} users email set as verified.', { num: users.length })) this.loadData() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -146,13 +140,13 @@ export class UserListComponent extends RestTable implements OnInit { this.selectedUsers = [] this.userService.getUsers(this.pagination, this.sort, this.search) - .subscribe( - resultList => { - this.users = resultList.data - this.totalRecords = resultList.total - }, - - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) + .subscribe( + resultList => { + this.users = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notifier.error(err.message) + ) } } diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts index fbad28410..e3025dec4 100644 --- a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts +++ b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { RestPagination, RestTable } from '@app/shared' import { SortMeta } from 'primeng/components/common/sortmeta' -import { BlocklistService, AccountBlock } from '@app/shared/blocklist' +import { AccountBlock, BlocklistService } from '@app/shared/blocklist' @Component({ selector: 'my-account-blocklist', @@ -18,7 +18,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private blocklistService: BlocklistService, private i18n: I18n ) { @@ -35,10 +35,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { this.blocklistService.unblockAccountByUser(blockedAccount) .subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost }) - ) + this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })) this.loadData() } @@ -53,7 +50,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit { this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts index b411d6926..4c5cc28b8 100644 --- a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts +++ b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { RestPagination, RestTable } from '@app/shared' import { SortMeta } from 'primeng/components/common/sortmeta' @@ -19,7 +19,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private blocklistService: BlocklistService, private i18n: I18n ) { @@ -36,10 +36,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn this.blocklistService.unblockServerByUser(host) .subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Instance {{host}} unmuted.', { host }) - ) + this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host })) this.loadData() } @@ -54,7 +51,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html new file mode 100644 index 000000000..d42af37d4 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.html @@ -0,0 +1,27 @@ +
+
+ + +
+ +
+ +
+
+ + +
You don't have videos history yet.
+ +
+
+
+ + +
+ {{ video.name }} + {{ video.views | myNumberFormatter }} views + +
+
+
+
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.scss b/client/src/app/+my-account/my-account-history/my-account-history.component.scss new file mode 100644 index 000000000..e7c6863f1 --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.scss @@ -0,0 +1,99 @@ +@import '_variables'; +@import '_mixins'; + +.no-history { + display: flex; + justify-content: center; + margin-top: 50px; + font-weight: $font-semibold; + font-size: 16px; +} + +.top-buttons { + margin-bottom: 20px; + display: flex; + + .history-switch { + display: flex; + flex-grow: 1; + + label { + margin: 0 0 0 5px; + } + } + + .delete-history { + font-size: 15px; + + button { + @include peertube-button; + @include grey-button; + } + } +} + +.video { + @include row-blocks; + + my-video-thumbnail { + margin-right: 10px; + } + + .video-info { + flex-grow: 1; + + .video-info-name { + @include disable-default-a-behaviour; + + color: var(--mainForegroundColor); + display: block; + width: fit-content; + font-size: 18px; + font-weight: $font-semibold; + } + + .video-info-date-views { + font-size: 14px; + } + + .video-info-account { + @include disable-default-a-behaviour; + + display: block; + width: fit-content; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 14px; + color: $grey-foreground-color; + + &:hover { + color: $grey-foreground-hover-color; + } + } + } +} + +@media screen and (max-width: $small-view) { + .video { + flex-direction: column; + height: auto; + text-align: center; + + .video-info-name { + margin: auto; + } + + input[type=checkbox] { + display: none; + } + + my-video-thumbnail { + margin-right: 0; + } + + .video-buttons { + margin-top: 10px; + } + } +} diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts new file mode 100644 index 000000000..394091bad --- /dev/null +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts @@ -0,0 +1,107 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { Location } from '@angular/common' +import { immutableAssign } from '@app/shared/misc/utils' +import { ComponentPagination } from '@app/shared/rest/component-pagination.model' +import { AuthService } from '../../core/auth' +import { ConfirmService } from '../../core/confirm' +import { AbstractVideoList } from '../../shared/video/abstract-video-list' +import { VideoService } from '../../shared/video/video.service' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { ScreenService } from '@app/shared/misc/screen.service' +import { UserHistoryService } from '@app/shared/users/user-history.service' +import { UserService } from '@app/shared' +import { Notifier } from '@app/core' + +@Component({ + selector: 'my-account-history', + templateUrl: './my-account-history.component.html', + styleUrls: [ './my-account-history.component.scss' ] +}) +export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy { + titlePage: string + currentRoute = '/my-account/history/videos' + pagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 5, + totalItems: null + } + videosHistoryEnabled: boolean + + protected baseVideoWidth = -1 + protected baseVideoHeight = 155 + + constructor ( + protected router: Router, + protected route: ActivatedRoute, + protected authService: AuthService, + protected userService: UserService, + protected notifier: Notifier, + protected location: Location, + protected screenService: ScreenService, + protected i18n: I18n, + private confirmService: ConfirmService, + private videoService: VideoService, + private userHistoryService: UserHistoryService + ) { + super() + + this.titlePage = this.i18n('My videos history') + } + + ngOnInit () { + super.ngOnInit() + + this.videosHistoryEnabled = this.authService.getUser().videosHistoryEnabled + } + + ngOnDestroy () { + super.ngOnDestroy() + } + + getVideosObservable (page: number) { + const newPagination = immutableAssign(this.pagination, { currentPage: page }) + + return this.userHistoryService.getUserVideosHistory(newPagination) + } + + generateSyndicationList () { + throw new Error('Method not implemented.') + } + + onVideosHistoryChange () { + this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled }) + .subscribe( + () => { + const message = this.videosHistoryEnabled === true ? + this.i18n('Videos history is enabled') : + this.i18n('Videos history is disabled') + + this.notifier.success(message) + + this.authService.refreshUserInformation() + }, + + err => this.notifier.error(err.message) + ) + } + + async deleteHistory () { + const title = this.i18n('Delete videos history') + const message = this.i18n('Are you sure you want to delete all your videos history?') + + const res = await this.confirmService.confirm(message, title) + if (res !== true) return + + this.userHistoryService.deleteUserVideosHistory() + .subscribe( + () => { + this.notifier.success(this.i18n('Videos history deleted')) + + this.reloadVideos() + }, + + err => this.notifier.error(err.message) + ) + } +} diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html new file mode 100644 index 000000000..d518b22ec --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html @@ -0,0 +1,13 @@ +
+ + + Notification preferences + + + +
+ + diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss new file mode 100644 index 000000000..43d1f82ab --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss @@ -0,0 +1,25 @@ +@import '_variables'; +@import '_mixins'; + +.header { + display: flex; + justify-content: space-between; + font-size: 15px; + margin-bottom: 20px; + + a { + @include peertube-button-link; + @include grey-button; + @include button-with-icon(18px, 3px, -1px); + } + + button { + @include peertube-button; + @include grey-button; + @include button-with-icon(20px, 3px, -1px); + } +} + +my-user-notifications { + font-size: 15px; +} diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts new file mode 100644 index 000000000..3e197088d --- /dev/null +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts @@ -0,0 +1,14 @@ +import { Component, ViewChild } from '@angular/core' +import { UserNotificationsComponent } from '@app/shared' + +@Component({ + templateUrl: './my-account-notifications.component.html', + styleUrls: [ './my-account-notifications.component.scss' ] +}) +export class MyAccountNotificationsComponent { + @ViewChild('userNotification') userNotification: UserNotificationsComponent + + markAllAsRead () { + this.userNotification.markAllAsRead() + } +} diff --git a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html index fd7d7d23b..674a4e8a2 100644 --- a/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html +++ b/client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html @@ -1,7 +1,8 @@ - - - - + + + + + @@ -16,4 +17,4 @@ - \ No newline at end of file + diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index 62053d97b..f4b954e54 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { BytesPipe } from 'ngx-pipes' import { AuthService } from '../../core' import { User } from '../../shared' @@ -19,7 +19,7 @@ export class MyAccountSettingsComponent implements OnInit { constructor ( private userService: UserService, private authService: AuthService, - private notificationsService: NotificationsService, + private notifier: Notifier, private i18n: I18n ) {} @@ -48,12 +48,12 @@ export class MyAccountSettingsComponent implements OnInit { this.userService.changeAvatar(formData) .subscribe( data => { - this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) + this.notifier.success(this.i18n('Avatar changed.')) this.user.updateAccountAvatar(data.avatar) }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 6c9a7ce75..b8f80bc1a 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { UserUpdateMe } from '../../../../../../shared' import { AuthService } from '../../../core' import { FormReactive, User, UserService } from '../../../shared' @@ -19,7 +19,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI constructor ( protected formValidatorService: FormValidatorService, private authService: AuthService, - private notificationsService: NotificationsService, + private notifier: Notifier, private userService: UserService, private i18n: I18n ) { @@ -54,12 +54,12 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI this.userService.updateMyProfile(details).subscribe( () => { - this.notificationsService.success(this.i18n('Success'), this.i18n('Information updated.')) + this.notifier.success(this.i18n('Information updated.')) this.authService.refreshUserInformation() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts index 9517a3705..9d2dccdf0 100644 --- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' import { I18n } from '@ngx-translate/i18n-polyfill' import { UserSubscriptionService } from '@app/shared/user-subscription' @@ -21,7 +21,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { constructor ( private userSubscriptionService: UserSubscriptionService, - private notificationsService: NotificationsService, + private notifier: Notifier, private i18n: I18n ) {} @@ -37,7 +37,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { this.pagination.totalItems = res.total }, - error => this.notificationsService.error(this.i18n('Error'), error.message) + error => this.notifier.error(error.message) ) } diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts index 81608d837..a68f79b47 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' -import { NotificationsService } from 'angular2-notifications' +import { AuthService, Notifier } from '@app/core' import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' import { VideoChannelCreate } from '../../../../../shared/models/videos' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' -import { AuthService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' @@ -21,7 +20,7 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE protected formValidatorService: FormValidatorService, private authService: AuthService, private videoChannelValidatorsService: VideoChannelValidatorsService, - private notificationsService: NotificationsService, + private notifier: Notifier, private router: Router, private videoChannelService: VideoChannelService, private i18n: I18n @@ -56,8 +55,8 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe( () => { this.authService.refreshUserInformation() - this.notificationsService.success( - this.i18n('Success'), + + this.notifier.success( this.i18n('Video channel {{videoChannelName}} created.', { videoChannelName: videoChannelCreate.displayName }) ) this.router.navigate([ '/my-account', 'video-channels' ]) diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts index 5d43956f2..da4fb645a 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts @@ -1,12 +1,11 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' -import { NotificationsService } from 'angular2-notifications' +import { AuthService, Notifier, ServerService } from '@app/core' import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit' import { VideoChannelUpdate } from '../../../../../shared/models/videos' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { Subscription } from 'rxjs' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' -import { AuthService, ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service' @@ -26,7 +25,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE protected formValidatorService: FormValidatorService, private authService: AuthService, private videoChannelValidatorsService: VideoChannelValidatorsService, - private notificationsService: NotificationsService, + private notifier: Notifier, private router: Router, private route: ActivatedRoute, private videoChannelService: VideoChannelService, @@ -79,10 +78,11 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe( () => { this.authService.refreshUserInformation() - this.notificationsService.success( - this.i18n('Success'), + + this.notifier.success( this.i18n('Video channel {{videoChannelName}} updated.', { videoChannelName: videoChannelUpdate.displayName }) ) + this.router.navigate([ '/my-account', 'video-channels' ]) }, @@ -94,12 +94,12 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData) .subscribe( data => { - this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.')) + this.notifier.success(this.i18n('Avatar changed.')) this.videoChannelToUpdate.updateAvatar(data.avatar) }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index df74b19b6..51db2e75d 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html @@ -1,6 +1,6 @@ diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss index 472cbb723..77fce138b 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss @@ -2,7 +2,7 @@ @import '_mixins'; .create-button { - @include create-button('../../../assets/images/global/add.svg'); + @include create-button; } /deep/ .action-button { diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts index 6d1098865..da2c5bcd3 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { AuthService } from '../../core/auth' import { ConfirmService } from '../../core/confirm' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' @@ -20,7 +20,7 @@ export class MyAccountVideoChannelsComponent implements OnInit { constructor ( private authService: AuthService, - private notificationsService: NotificationsService, + private notifier: Notifier, private confirmService: ConfirmService, private videoChannelService: VideoChannelService, private i18n: I18n @@ -35,10 +35,14 @@ export class MyAccountVideoChannelsComponent implements OnInit { async deleteVideoChannel (videoChannel: VideoChannel) { const res = await this.confirmService.confirmWithInput( this.i18n( - 'Do you really want to delete {{videoChannelName}}? It will delete all videos uploaded in this channel too.', - { videoChannelName: videoChannel.displayName } + 'Do you really want to delete {{channelDisplayName}}? It will delete all videos uploaded in this channel, ' + + 'and you will not be able to create another channel with the same name ({{channelName}})!', + { channelDisplayName: videoChannel.displayName, channelName: videoChannel.name } + ), + this.i18n( + 'Please type the display name of the video channel ({{displayName}}) to confirm', + { displayName: videoChannel.displayName } ), - this.i18n('Please type the name of the video channel to confirm'), videoChannel.displayName, this.i18n('Delete') ) @@ -46,15 +50,14 @@ export class MyAccountVideoChannelsComponent implements OnInit { this.videoChannelService.removeVideoChannel(videoChannel) .subscribe( - status => { + () => { this.loadVideoChannels() - this.notificationsService.success( - this.i18n('Success'), + this.notifier.success( this.i18n('Video channel {{videoChannelName}} deleted.', { videoChannelName: videoChannel.displayName }) ) }, - error => this.notificationsService.error(this.i18n('Error'), error.message) + error => this.notifier.error(error.message) ) } diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts index 5b920c98d..21a10c8ff 100644 --- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts +++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core' import { RestPagination, RestTable } from '@app/shared' import { SortMeta } from 'primeng/components/common/sortmeta' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { VideoImport, VideoImportState } from '../../../../../shared/models/videos' import { VideoImportService } from '@app/shared/video-import' @@ -19,7 +19,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private notificationsService: NotificationsService, + private notifier: Notifier, private videoImportService: VideoImportService, private i18n: I18n ) { @@ -58,7 +58,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit this.totalRecords = resultList.total }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } } diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html index a6911e4bf..69748ef37 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html @@ -32,7 +32,7 @@ - + Delete
@@ -45,7 +45,7 @@
@@ -53,4 +53,4 @@
- \ No newline at end of file + diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss index 2db81a3fe..39d0cf2f7 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.scss @@ -23,14 +23,11 @@ .action-button-delete-selection { @include peertube-button; @include orange-button; - } - - .icon.icon-delete-white { - @include icon(21px); + @include button-with-icon(21px); - position: relative; - top: -2px; - background-image: url('../../../assets/images/global/delete-white.svg'); + my-global-icon { + @include apply-svg-color(#fff); + } } } } @@ -97,7 +94,7 @@ } } -@media screen and (max-width: 800px) { +@media screen and (max-width: $small-view) { .video { flex-direction: column; height: auto; diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts index 2d88ac760..41608f796 100644 --- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts +++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts @@ -5,7 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router' import { Location } from '@angular/common' import { immutableAssign } from '@app/shared/misc/utils' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core' import { AuthService } from '../../core/auth' import { ConfirmService } from '../../core/confirm' import { AbstractVideoList } from '../../shared/video/abstract-video-list' @@ -40,7 +40,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni protected router: Router, protected route: ActivatedRoute, protected authService: AuthService, - protected notificationsService: NotificationsService, + protected notifier: Notifier, protected location: Location, protected screenService: ScreenService, protected i18n: I18n, @@ -102,16 +102,13 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni .pipe(concatAll()) .subscribe( res => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }) - ) + this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })) this.abortSelectionMode() this.reloadVideos() }, - err => this.notificationsService.error(this.i18n('Error'), err.message) + err => this.notifier.error(err.message) ) } @@ -124,15 +121,12 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni this.videoService.removeVideo(video.id) .subscribe( - status => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Video {{videoName}} deleted.', { videoName: video.name }) - ) + () => { + this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name })) this.reloadVideos() }, - error => this.notificationsService.error(this.i18n('Error'), error.message) + error => this.notifier.error(error.message) ) } diff --git a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html index 7c0df850d..22f127904 100644 --- a/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html +++ b/client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html @@ -1,7 +1,8 @@
+ - + + + +
+
+

{{ message.summary }}

+

{{ message.detail }}

+
+ + + + +
+
+
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index b51a81eb1..881f3ff31 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -91,8 +91,3 @@ footer { height: $footer-height; justify-content: center; } - -simple-notifications { - position: relative; - z-index: 1500; -} diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index dc4d0bf6a..7583fdee8 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -15,19 +15,6 @@ import { fromEvent } from 'rxjs' styleUrls: [ './app.component.scss' ] }) export class AppComponent implements OnInit { - notificationOptions = { - timeOut: 5000, - lastOnBottom: true, - clickToClose: true, - maxLength: 0, - maxStack: 7, - showProgressBar: false, - pauseOnHover: false, - preventDuplicates: false, - preventLastDuplicates: 'visible', - rtl: false - } - isMenuDisplayed = true isMenuChangedByUser = false diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 371199442..0bbc2e08b 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -12,13 +12,12 @@ import { AppComponent } from './app.component' import { CoreModule } from './core' import { HeaderComponent } from './header' import { LoginModule } from './login' -import { MenuComponent } from './menu' +import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu' import { SharedModule } from './shared' import { SignupModule } from './signup' import { VideosModule } from './videos' import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' -import { LanguageChooserComponent } from '@app/menu/language-chooser.component' import { SearchModule } from '@app/search' export function metaFactory (serverService: ServerService): MetaLoader { @@ -40,6 +39,7 @@ export function metaFactory (serverService: ServerService): MetaLoader { MenuComponent, LanguageChooserComponent, + AvatarNotificationComponent, HeaderComponent ], imports: [ diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index acd13d9c5..abb11fdc2 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts @@ -1,8 +1,9 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' import { UserRight } from '../../../../../shared/models/users/user-right.enum' +import { User as ServerUserModel } from '../../../../../shared/models/users/user.model' // Do not use the barrel (dependency loop) import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' -import { User, UserConstructorHash } from '../../shared/users/user.model' +import { User } from '../../shared/users/user.model' import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' export type TokenOptions = { @@ -70,6 +71,7 @@ export class AuthUser extends User { ID: 'id', ROLE: 'role', EMAIL: 'email', + VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', USERNAME: 'username', NSFW_POLICY: 'nsfw_policy', WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', @@ -89,7 +91,8 @@ export class AuthUser extends User { role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType, webTorrentEnabled: peertubeLocalStorage.getItem(this.KEYS.WEBTORRENT_ENABLED) === 'true', - autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true' + autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true', + videosHistoryEnabled: peertubeLocalStorage.getItem(this.KEYS.VIDEOS_HISTORY_ENABLED) === 'true' }, Tokens.load() ) @@ -104,12 +107,13 @@ export class AuthUser extends User { peertubeLocalStorage.removeItem(this.KEYS.ROLE) peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY) peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED) + peertubeLocalStorage.removeItem(this.KEYS.VIDEOS_HISTORY_ENABLED) peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO) peertubeLocalStorage.removeItem(this.KEYS.EMAIL) Tokens.flush() } - constructor (userHash: UserConstructorHash, hashTokens: TokenOptions) { + constructor (userHash: Partial, hashTokens: TokenOptions) { super(userHash) this.tokens = new Tokens(hashTokens) } diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 443772c9e..eaa822e0f 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -3,18 +3,18 @@ import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { Router } from '@angular/router' -import { NotificationsService } from 'angular2-notifications' +import { Notifier } from '@app/core/notification/notifier.service' import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' import { User } from '../../../../../shared/models/users' import { UserLogin } from '../../../../../shared/models/users/user-login.model' import { environment } from '../../../environments/environment' -import { RestExtractor } from '../../shared/rest' +import { RestExtractor } from '../../shared/rest/rest-extractor.service' import { AuthStatus } from './auth-status.model' import { AuthUser } from './auth-user.model' import { objectToUrlEncoded } from '@app/shared/misc/utils' import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' import { I18n } from '@ngx-translate/i18n-polyfill' -import { HotkeysService, Hotkey } from 'angular2-hotkeys' +import { Hotkey, HotkeysService } from 'angular2-hotkeys' interface UserLoginWithUsername extends UserLogin { access_token: string @@ -38,7 +38,6 @@ export class AuthService { loginChangedSource: Observable userInformationLoaded = new ReplaySubject(1) hotkeys: Hotkey[] - redirectUrl: string private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID) private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET) @@ -48,7 +47,7 @@ export class AuthService { constructor ( private http: HttpClient, - private notificationsService: NotificationsService, + private notifier: Notifier, private hotkeysService: HotkeysService, private restExtractor: RestExtractor, private router: Router, @@ -106,9 +105,8 @@ export class AuthService { ) } - // We put a bigger timeout - // This is an important message - this.notificationsService.error(this.i18n('Error'), errorMessage, { timeOut: 7000 }) + // We put a bigger timeout: this is an important message + this.notifier.error(errorMessage, this.i18n('Error'), 7000) } ) } @@ -178,8 +176,6 @@ export class AuthService { this.setStatus(AuthStatus.LoggedOut) this.hotkeysService.remove(this.hotkeys) - - this.redirectUrl = null } refreshAccessToken () { diff --git a/client/src/app/core/auth/index.ts b/client/src/app/core/auth/index.ts index bc7bfec0e..8e5caa7ed 100644 --- a/client/src/app/core/auth/index.ts +++ b/client/src/app/core/auth/index.ts @@ -1,4 +1,3 @@ export * from './auth-status.model' export * from './auth-user.model' export * from './auth.service' -export * from '../routing/login-guard.service' diff --git a/client/src/app/core/confirm/index.ts b/client/src/app/core/confirm/index.ts index 44aabfc13..aca591e1a 100644 --- a/client/src/app/core/confirm/index.ts +++ b/client/src/app/core/confirm/index.ts @@ -1,2 +1 @@ -export * from './confirm.component' export * from './confirm.service' diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index df2ec696d..4ef3b1e73 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -7,16 +7,18 @@ import { LoadingBarModule } from '@ngx-loading-bar/core' import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' import { LoadingBarRouterModule } from '@ngx-loading-bar/router' -import { SimpleNotificationsModule } from 'angular2-notifications' - import { AuthService } from './auth' -import { ConfirmComponent, ConfirmService } from './confirm' +import { ConfirmService } from './confirm' import { throwIfAlreadyLoaded } from './module-import-guard' import { LoginGuard, RedirectService, UserRightGuard } from './routing' import { ServerService } from './server' import { ThemeService } from './theme' import { HotkeyModule } from 'angular2-hotkeys' -import { CheatSheetComponent } from '@app/core/hotkeys' +import { CheatSheetComponent } from './hotkeys' +import { ToastModule } from 'primeng/toast' +import { Notifier } from './notification' +import { MessageService } from 'primeng/api' +import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' @NgModule({ imports: [ @@ -25,11 +27,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys' FormsModule, BrowserAnimationsModule, - SimpleNotificationsModule.forRoot(), - LoadingBarHttpClientModule, LoadingBarRouterModule, - LoadingBarModule.forRoot(), + LoadingBarModule, + ToastModule, HotkeyModule.forRoot({ cheatSheetCloseEsc: true @@ -37,16 +38,15 @@ import { CheatSheetComponent } from '@app/core/hotkeys' ], declarations: [ - ConfirmComponent, CheatSheetComponent ], exports: [ - SimpleNotificationsModule, LoadingBarHttpClientModule, LoadingBarModule, - ConfirmComponent, + ToastModule, + CheatSheetComponent ], @@ -57,7 +57,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys' ThemeService, LoginGuard, UserRightGuard, - RedirectService + RedirectService, + Notifier, + MessageService, + UserNotificationSocket ] }) export class CoreModule { diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts index 524589d74..f664aff41 100644 --- a/client/src/app/core/index.ts +++ b/client/src/app/core/index.ts @@ -2,6 +2,7 @@ export * from './auth' export * from './confirm' export * from './routing' export * from './server' +export * from './notification' export * from './theme' export * from './core.module' diff --git a/client/src/app/core/notification/index.ts b/client/src/app/core/notification/index.ts new file mode 100644 index 000000000..3e8d9ea65 --- /dev/null +++ b/client/src/app/core/notification/index.ts @@ -0,0 +1,2 @@ +export * from './notifier.service' +export * from './user-notification-socket.service' diff --git a/client/src/app/core/notification/notifier.service.ts b/client/src/app/core/notification/notifier.service.ts new file mode 100644 index 000000000..9833c65a0 --- /dev/null +++ b/client/src/app/core/notification/notifier.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core' +import { MessageService } from 'primeng/api' +import { I18n } from '@ngx-translate/i18n-polyfill' + +@Injectable() +export class Notifier { + readonly TIMEOUT = 5000 + + constructor ( + private i18n: I18n, + private messageService: MessageService) { + } + + info (text: string, title?: string, timeout?: number) { + if (!title) title = this.i18n('Info') + + return this.notify('info', text, title, timeout) + } + + error (text: string, title?: string, timeout?: number) { + if (!title) title = this.i18n('Error') + + return this.notify('error', text, title, timeout) + } + + success (text: string, title?: string, timeout?: number) { + if (!title) title = this.i18n('Success') + + return this.notify('success', text, title, timeout) + } + + private notify (severity: 'success' | 'info' | 'warn' | 'error', text: string, title: string, timeout?: number) { + this.messageService.add({ + severity, + summary: title, + detail: text, + closable: true, + life: timeout || this.TIMEOUT + }) + } +} diff --git a/client/src/app/core/notification/user-notification-socket.service.ts b/client/src/app/core/notification/user-notification-socket.service.ts new file mode 100644 index 000000000..f367d9ae4 --- /dev/null +++ b/client/src/app/core/notification/user-notification-socket.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core' +import { environment } from '../../../environments/environment' +import { UserNotification as UserNotificationServer } from '../../../../../shared' +import { Subject } from 'rxjs' +import * as io from 'socket.io-client' +import { AuthService } from '../auth' + +export type NotificationEvent = 'new' | 'read' | 'read-all' + +@Injectable() +export class UserNotificationSocket { + private notificationSubject = new Subject<{ type: NotificationEvent, notification?: UserNotificationServer }>() + + private socket: SocketIOClient.Socket + + constructor ( + private auth: AuthService + ) {} + + dispatch (type: NotificationEvent, notification?: UserNotificationServer) { + this.notificationSubject.next({ type, notification }) + } + + getMyNotificationsSocket () { + const socket = this.getSocket() + + socket.on('new-notification', (n: UserNotificationServer) => this.dispatch('new', n)) + + return this.notificationSubject.asObservable() + } + + private getSocket () { + if (this.socket) return this.socket + + this.socket = io(environment.apiUrl + '/user-notifications', { + query: { accessToken: this.auth.getAccessToken() } + }) + + return this.socket + } +} diff --git a/client/src/app/core/routing/login-guard.service.ts b/client/src/app/core/routing/login-guard.service.ts index 40ff8f505..7b1c37ee8 100644 --- a/client/src/app/core/routing/login-guard.service.ts +++ b/client/src/app/core/routing/login-guard.service.ts @@ -1,11 +1,5 @@ import { Injectable } from '@angular/core' -import { - ActivatedRouteSnapshot, - CanActivateChild, - RouterStateSnapshot, - CanActivate, - Router -} from '@angular/router' +import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router' import { AuthService } from '../auth/auth.service' @@ -20,8 +14,6 @@ export class LoginGuard implements CanActivate, CanActivateChild { canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (this.auth.isLoggedIn() === true) return true - this.auth.redirectUrl = state.url - this.router.navigate([ '/login' ]) return false } diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts index 1881be117..e1db4097b 100644 --- a/client/src/app/core/routing/redirect.service.ts +++ b/client/src/app/core/routing/redirect.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { Router } from '@angular/router' +import { NavigationEnd, Router } from '@angular/router' import { ServerService } from '../server' @Injectable() @@ -8,6 +8,9 @@ export class RedirectService { static INIT_DEFAULT_ROUTE = '/videos/trending' static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE + private previousUrl: string + private currentUrl: string + constructor ( private router: Router, private serverService: ServerService @@ -18,6 +21,7 @@ export class RedirectService { RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute } + // Load default route this.serverService.configLoaded .subscribe(() => { const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute @@ -26,6 +30,21 @@ export class RedirectService { RedirectService.DEFAULT_ROUTE = defaultRouteConfig } }) + + // Track previous url + this.currentUrl = this.router.url + router.events.subscribe(event => { + if (event instanceof NavigationEnd) { + this.previousUrl = this.currentUrl + this.currentUrl = event.url + } + }) + } + + redirectToPreviousRoute () { + if (this.previousUrl) return this.router.navigateByUrl(this.previousUrl) + + return this.redirectToHomepage() } redirectToHomepage (skipLocationChange = false) { diff --git a/client/src/app/core/routing/user-right-guard.service.ts b/client/src/app/core/routing/user-right-guard.service.ts index 65d029977..50c3d8c19 100644 --- a/client/src/app/core/routing/user-right-guard.service.ts +++ b/client/src/app/core/routing/user-right-guard.service.ts @@ -7,7 +7,7 @@ import { Router } from '@angular/router' -import { AuthService } from '../auth' +import { AuthService } from '../auth/auth.service' @Injectable() export class UserRightGuard implements CanActivate, CanActivateChild { diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index da8bd26db..4ae72427b 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -13,6 +13,7 @@ import { sortBy } from '@app/shared/misc/utils' @Injectable() export class ServerService { + private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server/' private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/' private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' @@ -37,6 +38,12 @@ export class ServerService { css: '' } }, + email: { + enabled: false + }, + contactForm: { + enabled: false + }, serverVersion: 'Unknown', signup: { allowed: false, @@ -80,6 +87,11 @@ export class ServerService { enabled: false } } + }, + trending: { + videos: { + intervalDays: 0 + } } } private videoCategories: Array> = [] @@ -141,10 +153,6 @@ export class ServerService { return this.videoPrivacies } - getAbout () { - return this.http.get(ServerService.BASE_CONFIG_URL + '/about') - } - private loadVideoAttributeEnum ( attributeName: 'categories' | 'licences' | 'languages' | 'privacies', hashToPopulate: VideoConstant[], diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html index c23e0c55d..46a87c79c 100644 --- a/client/src/app/header/header.component.html +++ b/client/src/app/header/header.component.html @@ -5,6 +5,6 @@ - + Upload diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index 2f9820665..cea415d9b 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss @@ -6,6 +6,7 @@ padding-left: 10px; margin-right: 15px; padding-right: 40px; // For the search icon + font-size: 14px; &::placeholder { color: var(--inputPlaceholderColor); @@ -40,6 +41,7 @@ .upload-button { @include peertube-button-link; @include orange-button; + @include button-with-icon(22px, 3px, -1px); margin-right: 25px; @@ -47,15 +49,6 @@ margin-right: 0; } - .icon.icon-upload { - @include icon(22px); - - background-image: url('../../assets/images/header/upload-white.svg'); - height: 24px; - vertical-align: middle; - margin-right: 6px; - } - @media screen and (max-width: 600px) { margin-right: 10px; padding: 0 10px; diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 93dbed525..4efe3fb22 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html @@ -55,11 +55,17 @@