diff options
Diffstat (limited to 'client/src/app')
-rw-r--r-- | client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | 55 | ||||
-rw-r--r-- | client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | 6 | ||||
-rw-r--r-- | client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | 6 | ||||
-rw-r--r-- | client/src/app/app.component.html | 10 | ||||
-rw-r--r-- | client/src/app/app.component.scss | 38 | ||||
-rw-r--r-- | client/src/app/app.component.ts | 71 | ||||
-rw-r--r-- | client/src/app/app.module.ts | 6 | ||||
-rw-r--r-- | client/src/app/core/server/server.service.ts | 17 | ||||
-rw-r--r-- | client/src/app/shared/bulk/bulk.service.ts | 24 | ||||
-rw-r--r-- | client/src/app/shared/locale/oc.ts | 104 | ||||
-rw-r--r-- | client/src/app/shared/moderation/user-moderation-dropdown.component.ts | 38 | ||||
-rw-r--r-- | client/src/app/shared/shared.module.ts | 145 |
12 files changed, 426 insertions, 94 deletions
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 5703d5a2e..4ee573696 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 | |||
@@ -276,6 +276,58 @@ | |||
276 | </div> | 276 | </div> |
277 | </div> | 277 | </div> |
278 | 278 | ||
279 | <div class="form-row mt-4"> <!-- broadcast grid --> | ||
280 | <div class="form-group col-12 col-lg-4 col-xl-3"> | ||
281 | <div i18n class="inner-form-title">BROADCAST MESSAGE</div> | ||
282 | <div i18n class="inner-for-description"> | ||
283 | Display a message on your instance | ||
284 | </div> | ||
285 | </div> | ||
286 | |||
287 | <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> | ||
288 | |||
289 | <ng-container formGroupName="broadcastMessage"> | ||
290 | |||
291 | <div class="form-group"> | ||
292 | <my-peertube-checkbox | ||
293 | inputName="broadcastMessageEnabled" formControlName="enabled" | ||
294 | i18n-labelText labelText="Enable broadcast message" | ||
295 | ></my-peertube-checkbox> | ||
296 | </div> | ||
297 | |||
298 | <div class="form-group"> | ||
299 | <my-peertube-checkbox | ||
300 | inputName="broadcastMessageDismissable" formControlName="dismissable" | ||
301 | i18n-labelText labelText="Allow users to dismiss the broadcast message " | ||
302 | ></my-peertube-checkbox> | ||
303 | </div> | ||
304 | |||
305 | <div class="form-group"> | ||
306 | <label i18n for="broadcastMessageLevel">Broadcast message level</label> | ||
307 | <div class="peertube-select-container"> | ||
308 | <select id="broadcastMessageLevel" formControlName="level" class="form-control"> | ||
309 | <option value="info">info</option> | ||
310 | <option value="warning">warning</option> | ||
311 | <option value="error">error</option> | ||
312 | </select> | ||
313 | </div> | ||
314 | <div *ngIf="formErrors.broadcastMessage.level" class="form-error">{{ formErrors.broadcastMessage.level }}</div> | ||
315 | </div> | ||
316 | |||
317 | <div class="form-group"> | ||
318 | <label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help> | ||
319 | <my-markdown-textarea | ||
320 | name="broadcastMessageMessage" formControlName="message" textareaMaxWidth="500px" | ||
321 | [classes]="{ 'input-error': formErrors['broadcastMessage.message'] }" | ||
322 | ></my-markdown-textarea> | ||
323 | <div *ngIf="formErrors.broadcastMessage.message" class="form-error">{{ formErrors.broadcastMessage.message }}</div> | ||
324 | </div> | ||
325 | |||
326 | </ng-container> | ||
327 | |||
328 | </div> | ||
329 | </div> | ||
330 | |||
279 | <div class="form-row mt-4"> <!-- new users grid --> | 331 | <div class="form-row mt-4"> <!-- new users grid --> |
280 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 332 | <div class="form-group col-12 col-lg-4 col-xl-3"> |
281 | <div i18n class="inner-form-title">NEW USERS</div> | 333 | <div i18n class="inner-form-title">NEW USERS</div> |
@@ -801,8 +853,9 @@ | |||
801 | <div class="form-row mt-4"> <!-- submit placement block --> | 853 | <div class="form-row mt-4"> <!-- submit placement block --> |
802 | <div class="col-md-7 col-xl-5"></div> | 854 | <div class="col-md-7 col-xl-5"></div> |
803 | <div class="col-md-5 col-xl-5"> | 855 | <div class="col-md-5 col-xl-5"> |
856 | <span class="form-error submit-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span> | ||
857 | |||
804 | <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> | 858 | <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> |
805 | <span class="form-error" i18n *ngIf="!form.valid">It seems like the configuration is invalid. Please search for potential errors in the different tabs.</span> | ||
806 | </div> | 859 | </div> |
807 | </div> | 860 | </div> |
808 | </form> | 861 | </form> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index 9ee960ad6..2bfa92da4 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -76,4 +76,8 @@ ngb-tabset:not(.previews) ::ng-deep { | |||
76 | .nav-link { | 76 | .nav-link { |
77 | font-size: 105%; | 77 | font-size: 105%; |
78 | } | 78 | } |
79 | } \ No newline at end of file | 79 | } |
80 | |||
81 | .submit-error { | ||
82 | margin-bottom: 20px; | ||
83 | } | ||
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 cea314cea..6d59494c8 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 | |||
@@ -215,6 +215,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A | |||
215 | indexUrl: this.customConfigValidatorsService.INDEX_URL | 215 | indexUrl: this.customConfigValidatorsService.INDEX_URL |
216 | } | 216 | } |
217 | } | 217 | } |
218 | }, | ||
219 | broadcastMessage: { | ||
220 | enabled: null, | ||
221 | level: null, | ||
222 | dismissable: null, | ||
223 | message: null | ||
218 | } | 224 | } |
219 | } | 225 | } |
220 | 226 | ||
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index b0d2e5050..b243c129b 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -25,6 +25,16 @@ | |||
25 | <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }"> | 25 | <div id="content" tabindex="-1" class="main-col" [ngClass]="{ expanded: menu.isMenuDisplayed === false }"> |
26 | 26 | ||
27 | <div class="main-row"> | 27 | <div class="main-row"> |
28 | |||
29 | <div *ngIf="broadcastMessage" class="broadcast-message alert" [ngClass]="broadcastMessage.class"> | ||
30 | <div [innerHTML]="broadcastMessage.message"></div> | ||
31 | |||
32 | <my-global-icon | ||
33 | *ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()" | ||
34 | iconName="cross" role="button" title="Close this message" i18n-title | ||
35 | ></my-global-icon> | ||
36 | </div> | ||
37 | |||
28 | <router-outlet></router-outlet> | 38 | <router-outlet></router-outlet> |
29 | </div> | 39 | </div> |
30 | </div> | 40 | </div> |
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 0c33dc4a1..27fd69c8d 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -1,5 +1,7 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import '~bootstrap/scss/functions'; | ||
4 | @import '~bootstrap/scss/variables'; | ||
3 | 5 | ||
4 | .peertube-container { | 6 | .peertube-container { |
5 | padding-bottom: 20px; | 7 | padding-bottom: 20px; |
@@ -88,3 +90,39 @@ | |||
88 | flex: 1; | 90 | flex: 1; |
89 | } | 91 | } |
90 | } | 92 | } |
93 | |||
94 | .broadcast-message { | ||
95 | min-height: 50px; | ||
96 | text-align: center; | ||
97 | margin-bottom: 0; | ||
98 | border-radius: 0; | ||
99 | display: grid; | ||
100 | grid-template-columns: 1fr 30px; | ||
101 | column-gap: 10px; | ||
102 | |||
103 | my-global-icon { | ||
104 | justify-self: center; | ||
105 | align-self: center; | ||
106 | cursor: pointer; | ||
107 | |||
108 | width: 20px; | ||
109 | } | ||
110 | |||
111 | @each $color, $value in $theme-colors { | ||
112 | &.alert-#{$color} { | ||
113 | my-global-icon { | ||
114 | @include apply-svg-color(theme-color-level($color, $alert-color-level)); | ||
115 | } | ||
116 | } | ||
117 | } | ||
118 | |||
119 | ::ng-deep { | ||
120 | p { | ||
121 | font-size: 16px; | ||
122 | } | ||
123 | |||
124 | p:last-child { | ||
125 | margin-bottom: 0; | ||
126 | } | ||
127 | } | ||
128 | } | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 12c0efd8a..a464e90fa 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -4,7 +4,7 @@ import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular | |||
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { filter, map, pairwise } from 'rxjs/operators' | 7 | import { filter, map, pairwise, first } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { PlatformLocation, ViewportScroller } from '@angular/common' | 10 | import { PlatformLocation, ViewportScroller } from '@angular/common' |
@@ -19,6 +19,10 @@ import { ServerConfig, UserRole } from '@shared/models' | |||
19 | import { User } from '@app/shared' | 19 | import { User } from '@app/shared' |
20 | import { InstanceService } from '@app/shared/instance/instance.service' | 20 | import { InstanceService } from '@app/shared/instance/instance.service' |
21 | import { MenuService } from './core/menu/menu.service' | 21 | import { MenuService } from './core/menu/menu.service' |
22 | import { BroadcastMessageLevel } from '@shared/models/server' | ||
23 | import { MarkdownService } from './shared/renderer' | ||
24 | import { concat } from 'rxjs' | ||
25 | import { peertubeLocalStorage } from './shared/misc/peertube-web-storage' | ||
22 | 26 | ||
23 | @Component({ | 27 | @Component({ |
24 | selector: 'my-app', | 28 | selector: 'my-app', |
@@ -26,11 +30,14 @@ import { MenuService } from './core/menu/menu.service' | |||
26 | styleUrls: [ './app.component.scss' ] | 30 | styleUrls: [ './app.component.scss' ] |
27 | }) | 31 | }) |
28 | export class AppComponent implements OnInit, AfterViewInit { | 32 | export class AppComponent implements OnInit, AfterViewInit { |
33 | private static BROADCAST_MESSAGE_KEY = 'app-broadcast-message-dismissed' | ||
34 | |||
29 | @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent | 35 | @ViewChild('welcomeModal') welcomeModal: WelcomeModalComponent |
30 | @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent | 36 | @ViewChild('instanceConfigWarningModal') instanceConfigWarningModal: InstanceConfigWarningModalComponent |
31 | @ViewChild('customModal') customModal: CustomModalComponent | 37 | @ViewChild('customModal') customModal: CustomModalComponent |
32 | 38 | ||
33 | customCSS: SafeHtml | 39 | customCSS: SafeHtml |
40 | broadcastMessage: { message: string, dismissable: boolean, class: string } | null = null | ||
34 | 41 | ||
35 | private serverConfig: ServerConfig | 42 | private serverConfig: ServerConfig |
36 | 43 | ||
@@ -50,6 +57,7 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
50 | private hooks: HooksService, | 57 | private hooks: HooksService, |
51 | private location: PlatformLocation, | 58 | private location: PlatformLocation, |
52 | private modalService: NgbModal, | 59 | private modalService: NgbModal, |
60 | private markdownService: MarkdownService, | ||
53 | public menu: MenuService | 61 | public menu: MenuService |
54 | ) { } | 62 | ) { } |
55 | 63 | ||
@@ -81,6 +89,7 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
81 | this.initRouteEvents() | 89 | this.initRouteEvents() |
82 | this.injectJS() | 90 | this.injectJS() |
83 | this.injectCSS() | 91 | this.injectCSS() |
92 | this.injectBroadcastMessage() | ||
84 | 93 | ||
85 | this.initHotkeys() | 94 | this.initHotkeys() |
86 | 95 | ||
@@ -97,6 +106,12 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
97 | return this.authService.isLoggedIn() | 106 | return this.authService.isLoggedIn() |
98 | } | 107 | } |
99 | 108 | ||
109 | hideBroadcastMessage () { | ||
110 | peertubeLocalStorage.setItem(AppComponent.BROADCAST_MESSAGE_KEY, this.serverConfig.broadcastMessage.message) | ||
111 | |||
112 | this.broadcastMessage = null | ||
113 | } | ||
114 | |||
100 | private initRouteEvents () { | 115 | private initRouteEvents () { |
101 | let resetScroll = true | 116 | let resetScroll = true |
102 | const eventsObs = this.router.events | 117 | const eventsObs = this.router.events |
@@ -165,6 +180,36 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
165 | ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page | 180 | ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page |
166 | } | 181 | } |
167 | 182 | ||
183 | private injectBroadcastMessage () { | ||
184 | concat( | ||
185 | this.serverService.getConfig().pipe(first()), | ||
186 | this.serverService.configReloaded | ||
187 | ).subscribe(async config => { | ||
188 | this.broadcastMessage = null | ||
189 | |||
190 | const messageConfig = config.broadcastMessage | ||
191 | |||
192 | if (messageConfig.enabled) { | ||
193 | // Already dismissed this message? | ||
194 | if (messageConfig.dismissable && localStorage.getItem(AppComponent.BROADCAST_MESSAGE_KEY) === messageConfig.message) { | ||
195 | return | ||
196 | } | ||
197 | |||
198 | const classes: { [id in BroadcastMessageLevel]: string } = { | ||
199 | info: 'alert-info', | ||
200 | warning: 'alert-warning', | ||
201 | error: 'alert-danger' | ||
202 | } | ||
203 | |||
204 | this.broadcastMessage = { | ||
205 | message: await this.markdownService.completeMarkdownToHTML(messageConfig.message), | ||
206 | dismissable: messageConfig.dismissable, | ||
207 | class: classes[messageConfig.level] | ||
208 | } | ||
209 | } | ||
210 | }) | ||
211 | } | ||
212 | |||
168 | private injectJS () { | 213 | private injectJS () { |
169 | // Inject JS | 214 | // Inject JS |
170 | this.serverService.getConfig() | 215 | this.serverService.getConfig() |
@@ -182,17 +227,19 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
182 | 227 | ||
183 | private injectCSS () { | 228 | private injectCSS () { |
184 | // Inject CSS if modified (admin config settings) | 229 | // Inject CSS if modified (admin config settings) |
185 | this.serverService.configReloaded | 230 | concat( |
186 | .subscribe(() => { | 231 | this.serverService.getConfig().pipe(first()), |
187 | const headStyle = document.querySelector('style.custom-css-style') | 232 | this.serverService.configReloaded |
188 | if (headStyle) headStyle.parentNode.removeChild(headStyle) | 233 | ).subscribe(config => { |
189 | 234 | const headStyle = document.querySelector('style.custom-css-style') | |
190 | // We test customCSS if the admin removed the css | 235 | if (headStyle) headStyle.parentNode.removeChild(headStyle) |
191 | if (this.customCSS || this.serverConfig.instance.customizations.css) { | 236 | |
192 | const styleTag = '<style>' + this.serverConfig.instance.customizations.css + '</style>' | 237 | // We test customCSS if the admin removed the css |
193 | this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) | 238 | if (this.customCSS || config.instance.customizations.css) { |
194 | } | 239 | const styleTag = '<style>' + config.instance.customizations.css + '</style>' |
195 | }) | 240 | this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) |
241 | } | ||
242 | }) | ||
196 | } | 243 | } |
197 | 244 | ||
198 | private async loadPlugins () { | 245 | private async loadPlugins () { |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 6e74cd394..e61346dac 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -2,7 +2,6 @@ import { LOCALE_ID, NgModule, TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular | |||
2 | import { BrowserModule } from '@angular/platform-browser' | 2 | import { BrowserModule } from '@angular/platform-browser' |
3 | import { ServerService } from '@app/core' | 3 | import { ServerService } from '@app/core' |
4 | import { ResetPasswordModule } from '@app/reset-password' | 4 | import { ResetPasswordModule } from '@app/reset-password' |
5 | |||
6 | import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' | 5 | import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' |
7 | import 'focus-visible' | 6 | import 'focus-visible' |
8 | 7 | ||
@@ -18,9 +17,12 @@ import { SearchModule } from '@app/search' | |||
18 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | 17 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' |
19 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | 18 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' |
20 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' | 19 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '@shared/models' |
21 | import { APP_BASE_HREF } from '@angular/common' | 20 | import { APP_BASE_HREF, registerLocaleData } from '@angular/common' |
22 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | 21 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' |
23 | import { CustomModalComponent } from '@app/modal/custom-modal.component' | 22 | import { CustomModalComponent } from '@app/modal/custom-modal.component' |
23 | import localeOc from '@app/shared/locale/oc' | ||
24 | |||
25 | registerLocaleData(localeOc, 'oc') | ||
24 | 26 | ||
25 | @NgModule({ | 27 | @NgModule({ |
26 | bootstrap: [ AppComponent ], | 28 | bootstrap: [ AppComponent ], |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index eac8f85e4..fdfbe4c02 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -21,7 +21,7 @@ export class ServerService { | |||
21 | 21 | ||
22 | private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' | 22 | private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' |
23 | 23 | ||
24 | configReloaded = new Subject<void>() | 24 | configReloaded = new Subject<ServerConfig>() |
25 | 25 | ||
26 | private localeObservable: Observable<any> | 26 | private localeObservable: Observable<any> |
27 | private videoLicensesObservable: Observable<VideoConstant<number>[]> | 27 | private videoLicensesObservable: Observable<VideoConstant<number>[]> |
@@ -139,6 +139,12 @@ export class ServerService { | |||
139 | indexUrl: 'https://instances.joinpeertube.org' | 139 | indexUrl: 'https://instances.joinpeertube.org' |
140 | } | 140 | } |
141 | } | 141 | } |
142 | }, | ||
143 | broadcastMessage: { | ||
144 | enabled: false, | ||
145 | message: '', | ||
146 | level: 'info', | ||
147 | dismissable: false | ||
142 | } | 148 | } |
143 | } | 149 | } |
144 | 150 | ||
@@ -162,6 +168,11 @@ export class ServerService { | |||
162 | resetConfig () { | 168 | resetConfig () { |
163 | this.configLoaded = false | 169 | this.configLoaded = false |
164 | this.configReset = true | 170 | this.configReset = true |
171 | |||
172 | // Notify config update | ||
173 | this.getConfig().subscribe(() => { | ||
174 | // empty, to fire a reset config event | ||
175 | }) | ||
165 | } | 176 | } |
166 | 177 | ||
167 | getConfig () { | 178 | getConfig () { |
@@ -175,9 +186,9 @@ export class ServerService { | |||
175 | this.config = config | 186 | this.config = config |
176 | this.configLoaded = true | 187 | this.configLoaded = true |
177 | }), | 188 | }), |
178 | tap(() => { | 189 | tap(config => { |
179 | if (this.configReset) { | 190 | if (this.configReset) { |
180 | this.configReloaded.next() | 191 | this.configReloaded.next(config) |
181 | this.configReset = false | 192 | this.configReset = false |
182 | } | 193 | } |
183 | }), | 194 | }), |
diff --git a/client/src/app/shared/bulk/bulk.service.ts b/client/src/app/shared/bulk/bulk.service.ts new file mode 100644 index 000000000..b00db31ec --- /dev/null +++ b/client/src/app/shared/bulk/bulk.service.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { HttpClient } from '@angular/common/http' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { environment } from '../../../environments/environment' | ||
4 | import { RestExtractor, RestService } from '../rest' | ||
5 | import { BulkRemoveCommentsOfBody } from '../../../../../shared' | ||
6 | import { catchError } from 'rxjs/operators' | ||
7 | |||
8 | @Injectable() | ||
9 | export class BulkService { | ||
10 | static BASE_BULK_URL = environment.apiUrl + '/api/v1/bulk' | ||
11 | |||
12 | constructor ( | ||
13 | private authHttp: HttpClient, | ||
14 | private restExtractor: RestExtractor, | ||
15 | private restService: RestService | ||
16 | ) { } | ||
17 | |||
18 | removeCommentsOf (body: BulkRemoveCommentsOfBody) { | ||
19 | const url = BulkService.BASE_BULK_URL + '/remove-comments-of' | ||
20 | |||
21 | return this.authHttp.post(url, body) | ||
22 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
23 | } | ||
24 | } | ||
diff --git a/client/src/app/shared/locale/oc.ts b/client/src/app/shared/locale/oc.ts new file mode 100644 index 000000000..d3b2e8407 --- /dev/null +++ b/client/src/app/shared/locale/oc.ts | |||
@@ -0,0 +1,104 @@ | |||
1 | |||
2 | // This code is not generated | ||
3 | // See angular/tools/gulp-tasks/cldr/extract.js | ||
4 | |||
5 | const u: any = undefined | ||
6 | |||
7 | function plural (n: number): number { | ||
8 | const i = Math.floor(Math.abs(n)) | ||
9 | if (i === 0 || i === 1) return 1 | ||
10 | return 5 | ||
11 | } | ||
12 | |||
13 | export default [ | ||
14 | 'oc', | ||
15 | [['a. m.', 'p. m.'], u, u], | ||
16 | u, | ||
17 | [ | ||
18 | ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'], | ||
19 | ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], | ||
20 | ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'] | ||
21 | ], | ||
22 | u, | ||
23 | [ | ||
24 | ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], | ||
25 | [ | ||
26 | 'de gen.', 'de febr.', 'de març', 'd’abr.', 'de mai', 'de junh', 'de jul.', 'd’ag.', | ||
27 | 'de set.', 'd’oct.', 'de nov.', 'de dec.' | ||
28 | ], | ||
29 | [ | ||
30 | 'de genièr', 'de febrièr', 'de març', 'd’abril', 'de mai', 'de junh', 'de julhet', | ||
31 | 'd’agòst', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre' | ||
32 | ] | ||
33 | ], | ||
34 | [ | ||
35 | ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], | ||
36 | [ | ||
37 | 'gen.', 'febr.', 'març', 'abr.', 'mai', 'junh', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', | ||
38 | 'dec.' | ||
39 | ], | ||
40 | [ | ||
41 | 'genièr', 'febrièr', 'març', 'abril', 'mai', 'junh', 'julhet', 'agòst', 'setembre', 'octòbre', | ||
42 | 'novembre', 'decembre' | ||
43 | ] | ||
44 | ], | ||
45 | [['aC', 'dC'], u, ['abans Jèsus-Crist', 'aprèp Jèsus-Crist']], | ||
46 | 1, | ||
47 | [6, 0], | ||
48 | ['d/M/yy', 'd MMM y', 'd MMMM \'de\' y', 'EEEE, d MMMM \'de\' y'], | ||
49 | ['H:mm', 'H:mm:ss', 'H:mm:ss z', 'H:mm:ss zzzz'], | ||
50 | ['{1} {0}', '{1}, {0}', '{1} \'a\' \'les\' {0}', u], | ||
51 | [',', '.', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'], | ||
52 | ['#,##0.###', '#,##0%', '#,##0.00 ¤', '#E0'], | ||
53 | 'EUR', | ||
54 | '€', | ||
55 | 'euro', | ||
56 | { | ||
57 | 'ARS': ['$AR', '$'], | ||
58 | 'AUD': ['$AU', '$'], | ||
59 | 'BEF': ['FB'], | ||
60 | 'BMD': ['$BM', '$'], | ||
61 | 'BND': ['$BN', '$'], | ||
62 | 'BZD': ['$BZ', '$'], | ||
63 | 'CAD': ['$CA', '$'], | ||
64 | 'CLP': ['$CL', '$'], | ||
65 | 'CNY': [u, 'Â¥'], | ||
66 | 'COP': ['$CO', '$'], | ||
67 | 'CYP': ['£CY'], | ||
68 | 'EGP': [u, '£E'], | ||
69 | 'FJD': ['$FJ', '$'], | ||
70 | 'FKP': ['£FK', '£'], | ||
71 | 'FRF': ['F'], | ||
72 | 'GBP': ['£GB', '£'], | ||
73 | 'GIP': ['£GI', '£'], | ||
74 | 'HKD': [u, '$'], | ||
75 | 'IEP': ['£IE'], | ||
76 | 'ILP': ['£IL'], | ||
77 | 'ITL': ['₤IT'], | ||
78 | 'JPY': [u, 'Â¥'], | ||
79 | 'KMF': [u, 'FC'], | ||
80 | 'LBP': ['£LB', '£L'], | ||
81 | 'MTP': ['£MT'], | ||
82 | 'MXN': ['$MX', '$'], | ||
83 | 'NAD': ['$NA', '$'], | ||
84 | 'NIO': [u, '$C'], | ||
85 | 'NZD': ['$NZ', '$'], | ||
86 | 'RHD': ['$RH'], | ||
87 | 'RON': [u, 'L'], | ||
88 | 'RWF': [u, 'FR'], | ||
89 | 'SBD': ['$SB', '$'], | ||
90 | 'SGD': ['$SG', '$'], | ||
91 | 'SRD': ['$SR', '$'], | ||
92 | 'TOP': [u, '$T'], | ||
93 | 'TTD': ['$TT', '$'], | ||
94 | 'TWD': [u, 'NT$'], | ||
95 | 'USD': ['$US', '$'], | ||
96 | 'UYU': ['$UY', '$'], | ||
97 | 'WST': ['$WS'], | ||
98 | 'XCD': [u, '$'], | ||
99 | 'XPF': ['FCFP'], | ||
100 | 'ZMW': [u, 'Kw'] | ||
101 | }, | ||
102 | 'ltr', | ||
103 | plural | ||
104 | ] | ||
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index f8ad7ce13..82f39050e 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts | |||
@@ -7,7 +7,8 @@ import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' | |||
7 | import { User, UserRight } from '../../../../../shared/models/users' | 7 | import { User, UserRight } from '../../../../../shared/models/users' |
8 | import { Account } from '@app/shared/account/account.model' | 8 | import { Account } from '@app/shared/account/account.model' |
9 | import { BlocklistService } from '@app/shared/blocklist' | 9 | import { BlocklistService } from '@app/shared/blocklist' |
10 | import { ServerConfig } from '@shared/models' | 10 | import { ServerConfig, BulkRemoveCommentsOfBody } from '@shared/models' |
11 | import { BulkService } from '../bulk/bulk.service' | ||
11 | 12 | ||
12 | @Component({ | 13 | @Component({ |
13 | selector: 'my-user-moderation-dropdown', | 14 | selector: 'my-user-moderation-dropdown', |
@@ -38,6 +39,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
38 | private serverService: ServerService, | 39 | private serverService: ServerService, |
39 | private userService: UserService, | 40 | private userService: UserService, |
40 | private blocklistService: BlocklistService, | 41 | private blocklistService: BlocklistService, |
42 | private bulkService: BulkService, | ||
41 | private i18n: I18n | 43 | private i18n: I18n |
42 | ) { } | 44 | ) { } |
43 | 45 | ||
@@ -229,6 +231,21 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
229 | ) | 231 | ) |
230 | } | 232 | } |
231 | 233 | ||
234 | async bulkRemoveCommentsOf (body: BulkRemoveCommentsOfBody) { | ||
235 | const message = this.i18n('Are you sure you want to remove all the comments of this account?') | ||
236 | const res = await this.confirmService.confirm(message, this.i18n('Delete account comments')) | ||
237 | if (res === false) return | ||
238 | |||
239 | this.bulkService.removeCommentsOf(body) | ||
240 | .subscribe( | ||
241 | () => { | ||
242 | this.notifier.success(this.i18n('Will remove comments of this account (may take several minutes).')) | ||
243 | }, | ||
244 | |||
245 | err => this.notifier.error(err.message) | ||
246 | ) | ||
247 | } | ||
248 | |||
232 | getRouterUserEditLink (user: User) { | 249 | getRouterUserEditLink (user: User) { |
233 | return [ '/admin', 'users', 'update', user.id ] | 250 | return [ '/admin', 'users', 'update', user.id ] |
234 | } | 251 | } |
@@ -300,12 +317,17 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
300 | description: this.i18n('Show back content from that instance for you.'), | 317 | description: this.i18n('Show back content from that instance for you.'), |
301 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, | 318 | isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true, |
302 | handler: ({ account }) => this.unblockServerByUser(account.host) | 319 | handler: ({ account }) => this.unblockServerByUser(account.host) |
320 | }, | ||
321 | { | ||
322 | label: this.i18n('Remove comments from your videos'), | ||
323 | description: this.i18n('Remove comments of this account from your videos.'), | ||
324 | handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'my-videos' }) | ||
303 | } | 325 | } |
304 | ]) | 326 | ]) |
305 | 327 | ||
306 | let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] | 328 | let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] |
307 | 329 | ||
308 | // Instance actions | 330 | // Instance actions on account blocklists |
309 | if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { | 331 | if (authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { |
310 | instanceActions = instanceActions.concat([ | 332 | instanceActions = instanceActions.concat([ |
311 | { | 333 | { |
@@ -323,7 +345,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
323 | ]) | 345 | ]) |
324 | } | 346 | } |
325 | 347 | ||
326 | // Instance actions | 348 | // Instance actions on server blocklists |
327 | if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { | 349 | if (authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { |
328 | instanceActions = instanceActions.concat([ | 350 | instanceActions = instanceActions.concat([ |
329 | { | 351 | { |
@@ -341,6 +363,16 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
341 | ]) | 363 | ]) |
342 | } | 364 | } |
343 | 365 | ||
366 | if (authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) { | ||
367 | instanceActions = instanceActions.concat([ | ||
368 | { | ||
369 | label: this.i18n('Remove comments from your instance'), | ||
370 | description: this.i18n('Remove comments of this account from your instance.'), | ||
371 | handler: ({ account }) => this.bulkRemoveCommentsOf({ accountName: account.nameWithHost, scope: 'instance' }) | ||
372 | } | ||
373 | ]) | ||
374 | } | ||
375 | |||
344 | if (instanceActions.length !== 0) { | 376 | if (instanceActions.length !== 0) { |
345 | this.userActions.push(instanceActions) | 377 | this.userActions.push(instanceActions) |
346 | } | 378 | } |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 01735c187..813f76672 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -1,32 +1,30 @@ | |||
1 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | ||
2 | import { SharedModule as PrimeSharedModule } from 'primeng/api' | ||
3 | import { InputMaskModule } from 'primeng/inputmask' | ||
4 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
5 | import { MultiSelectModule } from 'primeng/multiselect' | ||
6 | import { ClipboardModule } from '@angular/cdk/clipboard' | ||
1 | import { CommonModule } from '@angular/common' | 7 | import { CommonModule } from '@angular/common' |
2 | import { HttpClientModule } from '@angular/common/http' | 8 | import { HttpClientModule } from '@angular/common/http' |
3 | import { NgModule } from '@angular/core' | 9 | import { NgModule } from '@angular/core' |
4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | 10 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
5 | import { RouterModule } from '@angular/router' | 11 | import { RouterModule } from '@angular/router' |
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | 12 | import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' |
7 | import { HelpComponent } from '@app/shared/misc/help.component' | 13 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' |
8 | import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' | 14 | import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' |
9 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 15 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' |
10 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | ||
11 | import { SharedModule as PrimeSharedModule } from 'primeng/api' | ||
12 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | ||
13 | import { ButtonComponent } from './buttons/button.component' | ||
14 | import { DeleteButtonComponent } from './buttons/delete-button.component' | ||
15 | import { EditButtonComponent } from './buttons/edit-button.component' | ||
16 | import { LoaderComponent } from './misc/loader.component' | ||
17 | import { RestExtractor, RestService } from './rest' | ||
18 | import { UserService } from './users' | ||
19 | import { VideoAbuseService } from './video-abuse' | ||
20 | import { VideoBlacklistService } from './video-blacklist' | ||
21 | import { VideoOwnershipService } from './video-ownership' | ||
22 | import { VideoMiniatureComponent } from './video/video-miniature.component' | ||
23 | import { FeedComponent } from './video/feed.component' | ||
24 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' | ||
25 | import { VideoService } from './video/video.service' | ||
26 | import { AccountService } from '@app/shared/account/account.service' | 16 | import { AccountService } from '@app/shared/account/account.service' |
27 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 17 | import { FromNowPipe } from '@app/shared/angular/from-now.pipe' |
28 | import { I18n } from '@ngx-translate/i18n-polyfill' | 18 | import { HighlightPipe } from '@app/shared/angular/highlight.pipe' |
29 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 19 | import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' |
20 | import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' | ||
21 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
22 | import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' | ||
23 | import { BlocklistService } from '@app/shared/blocklist' | ||
24 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' | ||
25 | import { AvatarComponent } from '@app/shared/channel/avatar.component' | ||
26 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' | ||
27 | import { DateToggleComponent } from '@app/shared/date/date-toggle.component' | ||
30 | import { | 28 | import { |
31 | CustomConfigValidatorsService, | 29 | CustomConfigValidatorsService, |
32 | InstanceValidatorsService, | 30 | InstanceValidatorsService, |
@@ -44,70 +42,72 @@ import { | |||
44 | VideoPlaylistValidatorsService, | 42 | VideoPlaylistValidatorsService, |
45 | VideoValidatorsService | 43 | VideoValidatorsService |
46 | } from '@app/shared/forms' | 44 | } from '@app/shared/forms' |
47 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' | 45 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
48 | import { InputMaskModule } from 'primeng/inputmask' | ||
49 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
50 | import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' | ||
51 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' | 46 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' |
52 | import { VideoCaptionService } from '@app/shared/video-caption' | 47 | import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' |
48 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | ||
53 | import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' | 49 | import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' |
54 | import { VideoImportService } from '@app/shared/video-import/video-import.service' | 50 | import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' |
55 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' | 51 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' |
56 | import { | 52 | import { GlobalIconComponent } from '@app/shared/images/global-icon.component' |
57 | NgbCollapseModule, | 53 | import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' |
58 | NgbDropdownModule, | 54 | import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' |
59 | NgbModalModule, | 55 | import { FollowService } from '@app/shared/instance/follow.service' |
60 | NgbPopoverModule, | ||
61 | NgbNavModule, | ||
62 | NgbTooltipModule | ||
63 | } from '@ng-bootstrap/ng-bootstrap' | ||
64 | import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' | ||
65 | import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' | 56 | import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' |
66 | import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' | 57 | import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' |
67 | import { OverviewService } from '@app/shared/overview' | 58 | import { InstanceService } from '@app/shared/instance/instance.service' |
59 | import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' | ||
60 | import { HelpComponent } from '@app/shared/misc/help.component' | ||
61 | import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' | ||
62 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
63 | import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' | ||
64 | import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' | ||
68 | import { UserBanModalComponent } from '@app/shared/moderation' | 65 | import { UserBanModalComponent } from '@app/shared/moderation' |
69 | import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' | 66 | import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' |
70 | import { BlocklistService } from '@app/shared/blocklist' | 67 | import { OverviewService } from '@app/shared/overview' |
71 | import { AvatarComponent } from '@app/shared/channel/avatar.component' | 68 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' |
72 | import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' | 69 | import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' |
73 | import { UserHistoryService } from '@app/shared/users/user-history.service' | 70 | import { UserHistoryService } from '@app/shared/users/user-history.service' |
74 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | 71 | import { UserNotificationService } from '@app/shared/users/user-notification.service' |
75 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' | 72 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' |
76 | import { InstanceService } from '@app/shared/instance/instance.service' | 73 | import { VideoCaptionService } from '@app/shared/video-caption' |
77 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' | 74 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
78 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' | 75 | import { VideoImportService } from '@app/shared/video-import/video-import.service' |
79 | import { DateToggleComponent } from '@app/shared/date/date-toggle.component' | ||
80 | import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' | ||
81 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
82 | import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' | ||
83 | import { GlobalIconComponent } from '@app/shared/images/global-icon.component' | ||
84 | import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' | ||
85 | import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' | 76 | import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' |
86 | import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' | ||
87 | import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' | 77 | import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' |
88 | import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' | 78 | import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' |
89 | import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' | 79 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' |
90 | import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' | 80 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
91 | import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' | ||
92 | import { FromNowPipe } from '@app/shared/angular/from-now.pipe' | ||
93 | import { HighlightPipe } from '@app/shared/angular/highlight.pipe' | ||
94 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
95 | import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component' | ||
96 | import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' | 81 | import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklist.component' |
97 | import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' | 82 | import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' |
98 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | 83 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' |
99 | import { FollowService } from '@app/shared/instance/follow.service' | ||
100 | import { MultiSelectModule } from 'primeng/multiselect' | ||
101 | import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' | ||
102 | import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' | ||
103 | import { RedundancyService } from '@app/shared/video/redundancy.service' | 84 | import { RedundancyService } from '@app/shared/video/redundancy.service' |
104 | import { ClipboardModule } from '@angular/cdk/clipboard' | 85 | import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component' |
105 | import { InputSwitchModule } from 'primeng/inputswitch' | 86 | import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' |
106 | 87 | import { | |
107 | import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' | 88 | NgbCollapseModule, |
108 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | 89 | NgbDropdownModule, |
109 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' | 90 | NgbModalModule, |
110 | import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' | 91 | NgbNavModule, |
92 | NgbPopoverModule, | ||
93 | NgbTooltipModule | ||
94 | } from '@ng-bootstrap/ng-bootstrap' | ||
95 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
96 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | ||
97 | import { BulkService } from './bulk/bulk.service' | ||
98 | import { ButtonComponent } from './buttons/button.component' | ||
99 | import { DeleteButtonComponent } from './buttons/delete-button.component' | ||
100 | import { EditButtonComponent } from './buttons/edit-button.component' | ||
101 | import { LoaderComponent } from './misc/loader.component' | ||
102 | import { RestExtractor, RestService } from './rest' | ||
103 | import { UserService } from './users' | ||
104 | import { VideoAbuseService } from './video-abuse' | ||
105 | import { VideoBlacklistService } from './video-blacklist' | ||
106 | import { VideoOwnershipService } from './video-ownership' | ||
107 | import { FeedComponent } from './video/feed.component' | ||
108 | import { VideoMiniatureComponent } from './video/video-miniature.component' | ||
109 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' | ||
110 | import { VideoService } from './video/video.service' | ||
111 | 111 | ||
112 | @NgModule({ | 112 | @NgModule({ |
113 | imports: [ | 113 | imports: [ |
@@ -313,6 +313,7 @@ import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-d | |||
313 | BlocklistService, | 313 | BlocklistService, |
314 | UserHistoryService, | 314 | UserHistoryService, |
315 | InstanceService, | 315 | InstanceService, |
316 | BulkService, | ||
316 | 317 | ||
317 | MarkdownService, | 318 | MarkdownService, |
318 | LinkifierService, | 319 | LinkifierService, |