"root": "src",
"outDir": "dist",
"assets": [
- {
- "glob": "**/*",
- "input": "./assets/images",
- "output": "./client/assets/images",
- "allowOutsideOutDir": false
- },
+ "./assets/images",
"./manifest.json"
],
"deployUrl": "client/",
component: EditCustomConfigComponent,
data: {
meta: {
- title: 'Following list'
+ title: 'Edit custom configuration'
}
}
}
import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'
import { ConfigService } from '@app/+admin/config/shared/config.service'
+import { ConfirmService } from '@app/core'
import { ServerService } from '@app/core/server/server.service'
import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared'
import {
userVideoQuota: USER_VIDEO_QUOTA.MESSAGES
}
+ private oldCustomJavascript: string
+ private oldCustomCSS: string
+
constructor (
private formBuilder: FormBuilder,
private router: Router,
private notificationsService: NotificationsService,
private configService: ConfigService,
- private serverService: ServerService
+ private serverService: ServerService,
+ private confirmService: ConfirmService
) {
super()
}
res => {
this.customConfig = res
+ this.oldCustomCSS = this.customConfig.instance.customizations.css
+ this.oldCustomJavascript = this.customConfig.instance.customizations.javascript
+
this.updateForm()
},
return this.form.value['signupEnabled'] === true
}
- formValidated () {
+ async formValidated () {
+ const newCustomizationJavascript = this.form.value['customizationJavascript']
+ const newCustomizationCSS = this.form.value['customizationCSS']
+
+ const customizations = []
+ if (newCustomizationJavascript && newCustomizationJavascript !== this.oldCustomJavascript) customizations.push('JavaScript')
+ if (newCustomizationCSS && newCustomizationCSS !== this.oldCustomCSS) customizations.push('CSS')
+
+ if (customizations.length !== 0) {
+ const customizationsText = customizations.join('/')
+
+ const message = `You set custom ${customizationsText}. ` +
+ 'This could lead to security issues or bugs if you do not understand it. ' +
+ 'Are you sure you want to update the configuration?'
+ const label = `Please type "I understand the ${customizationsText} I set" to confirm.`
+ const expectedInputValue = `I understand the ${customizationsText} I set`
+
+ const confirmRes = await this.confirmService.confirmWithInput(message, label, expectedInputValue)
+ if (confirmRes === false) return
+ }
+
const data = {
instance: {
name: this.form.value['instanceName'],
}
}
- addFollowing () {
+ async addFollowing () {
this.error = ''
const hosts = this.getNotEmptyHosts()
}
const confirmMessage = 'If you confirm, you will send a follow request to:<br /> - ' + hosts.join('<br /> - ')
- this.confirmService.confirm(confirmMessage, 'Follow new server(s)').subscribe(
- res => {
- if (res === false) return
+ const res = await this.confirmService.confirm(confirmMessage, 'Follow new server(s)')
+ if (res === false) return
- this.followService.follow(hosts).subscribe(
- status => {
- this.notificationsService.success('Success', 'Follow request(s) sent!')
+ this.followService.follow(hosts).subscribe(
+ () => {
+ this.notificationsService.success('Success', 'Follow request(s) sent!')
- setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500)
- },
+ setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500)
+ },
- err => this.notificationsService.error('Error', err.message)
- )
- }
+ err => this.notificationsService.error('Error', err.message)
)
}
super()
}
- removeFollowing (follow: AccountFollow) {
- this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow').subscribe(
- res => {
- if (res === false) return
+ async removeFollowing (follow: AccountFollow) {
+ const res = await this.confirmService.confirm(`Do you really want to unfollow ${follow.following.host}?`, 'Unfollow')
+ if (res === false) return
- this.followService.unfollow(follow).subscribe(
- () => {
- this.notificationsService.success('Success', `You are not following ${follow.following.host} anymore.`)
- this.loadData()
- },
+ this.followService.unfollow(follow).subscribe(
+ () => {
+ this.notificationsService.success('Success', `You are not following ${follow.following.host} anymore.`)
+ this.loadData()
+ },
- err => this.notificationsService.error('Error', err.message)
- )
- }
+ err => this.notificationsService.error('Error', err.message)
)
}
import { Component } from '@angular/core'
-import { SortMeta } from 'primeng/components/common/sortmeta'
import { NotificationsService } from 'angular2-notifications'
+import { SortMeta } from 'primeng/components/common/sortmeta'
import { ConfirmService } from '../../../core'
-import { RestTable, RestPagination, User } from '../../../shared'
+import { RestPagination, RestTable, User } from '../../../shared'
import { UserService } from '../shared'
@Component({
super()
}
- removeUser (user: User) {
+ async removeUser (user: User) {
if (user.username === 'root') {
this.notificationsService.error('Error', 'You cannot delete root.')
return
}
- this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe(
- res => {
- if (res === false) return
+ const res = await this.confirmService.confirm('Do you really want to delete this user?', 'Delete')
+ if (res === false) return
- this.userService.removeUser(user).subscribe(
- () => {
- this.notificationsService.success('Success', `User ${user.username} deleted.`)
- this.loadData()
- },
+ this.userService.removeUser(user).subscribe(
+ () => {
+ this.notificationsService.success('Success', `User ${user.username} deleted.`)
+ this.loadData()
+ },
- err => this.notificationsService.error('Error', err.message)
- )
- }
+ err => this.notificationsService.error('Error', err.message)
)
}
this.loadData()
}
- removeVideoFromBlacklist (entry: BlacklistedVideo) {
+ async removeVideoFromBlacklist (entry: BlacklistedVideo) {
const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the video list.'
- this.confirmService.confirm(confirmMessage, 'Remove').subscribe(
- res => {
- if (res === false) return
+ const res = await this.confirmService.confirm(confirmMessage, 'Remove')
+ if (res === false) return
- this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe(
- status => {
- this.notificationsService.success('Success', `Video ${entry.name} removed from the blacklist.`)
- this.loadData()
- },
+ this.videoBlacklistService.removeVideoFromBlacklist(entry.videoId).subscribe(
+ () => {
+ this.notificationsService.success('Success', `Video ${entry.name} removed from the blacklist.`)
+ this.loadData()
+ },
- err => this.notificationsService.error('Error', err.message)
- )
- }
+ err => this.notificationsService.error('Error', err.message)
)
}
return this.videoService.getMyVideos(newPagination, this.sort)
}
- deleteSelectedVideos () {
+ async deleteSelectedVideos () {
const toDeleteVideosIds = Object.keys(this.checkedVideos)
.filter(k => this.checkedVideos[k] === true)
.map(k => parseInt(k, 10))
- this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete').subscribe(
- res => {
- if (res === false) return
-
- const observables: Observable<any>[] = []
- for (const videoId of toDeleteVideosIds) {
- const o = this.videoService
- .removeVideo(videoId)
- .do(() => this.spliceVideosById(videoId))
-
- observables.push(o)
- }
-
- Observable.from(observables)
- .concatAll()
- .subscribe(
- res => {
- this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
- this.buildVideoPages()
- },
-
- err => this.notificationsService.error('Error', err.message)
- )
- }
- )
+ const res = await this.confirmService.confirm(`Do you really want to delete ${toDeleteVideosIds.length} videos?`, 'Delete')
+ if (res === false) return
+
+ const observables: Observable<any>[] = []
+ for (const videoId of toDeleteVideosIds) {
+ const o = this.videoService
+ .removeVideo(videoId)
+ .do(() => this.spliceVideosById(videoId))
+
+ observables.push(o)
+ }
+
+ Observable.from(observables)
+ .concatAll()
+ .subscribe(
+ res => {
+ this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
+ this.buildVideoPages()
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
}
- deleteVideo (video: Video) {
- this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete').subscribe(
- res => {
- if (res === false) return
-
- this.videoService.removeVideo(video.id)
- .subscribe(
- status => {
- this.notificationsService.success('Success', `Video ${video.name} deleted.`)
- this.spliceVideosById(video.id)
- this.buildVideoPages()
- },
-
- error => this.notificationsService.error('Error', error.message)
- )
- }
- )
+ async deleteVideo (video: Video) {
+ const res = await this.confirmService.confirm(`Do you really want to delete ${video.name}?`, 'Delete')
+ if (res === false) return
+
+ this.videoService.removeVideo(video.id)
+ .subscribe(
+ status => {
+ this.notificationsService.success('Success', `Video ${video.name} deleted.`)
+ this.spliceVideosById(video.id)
+ this.buildVideoPages()
+ },
+
+ error => this.notificationsService.error('Error', error.message)
+ )
}
private spliceVideosById (id: number) {
<div class="modal-body" >
<div [innerHtml]="message"></div>
+ <div *ngIf="inputLabel && expectedInputValue" class="form-group">
+ <label for="confirmInput">{{ inputLabel }}</label>
+ <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" />
+ </div>
+
<div class="form-group inputs">
<span class="action-button action-button-cancel" (click)="cancel()">
Cancel
</span>
<input
- type="submit" value="Confirm" class="action-button-submit"
+ type="submit" value="Confirm" class="action-button-submit" [disabled]="isConfirmationDisabled()"
(click)="confirm()"
>
</div>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+.button {
+ padding: 0 13px;
+}
+
+input[type=text] {
+ @include peertube-input-text(100%);
+ display: block;
+}
+
+.form-group {
+ margin: 20px 0;
+}
+
+
import { ConfirmService } from './confirm.service'
-export interface ConfigChangedEvent {
- columns: { [id: string]: { isDisplayed: boolean } }
- config: { resultsPerPage: number }
-}
-
@Component({
selector: 'my-confirm',
templateUrl: './confirm.component.html',
- styles: [ '.button { padding: 0 13px; }' ]
+ styleUrls: [ './confirm.component.scss' ]
})
export class ConfirmComponent implements OnInit {
@ViewChild('confirmModal') confirmModal: ModalDirective
title = ''
message = ''
+ expectedInputValue = ''
+ inputLabel = ''
+
+ inputValue = ''
constructor (private confirmService: ConfirmService) {
// Empty
}
this.confirmService.showConfirm.subscribe(
- ({ title, message }) => {
+ ({ title, message, expectedInputValue, inputLabel }) => {
this.title = title
this.message = message
+ this.inputLabel = inputLabel
+ this.expectedInputValue = expectedInputValue
+
this.showModal()
}
)
this.hideModal()
}
+ isConfirmationDisabled () {
+ // No input validation
+ if (!this.inputLabel || !this.expectedInputValue) return false
+
+ return this.expectedInputValue !== this.inputValue
+ }
+
showModal () {
this.confirmModal.show()
}
import { Injectable } from '@angular/core'
import { Subject } from 'rxjs/Subject'
import 'rxjs/add/operator/first'
+import 'rxjs/add/operator/toPromise'
@Injectable()
export class ConfirmService {
- showConfirm = new Subject<{ title, message }>()
+ showConfirm = new Subject<{ title: string, message: string, inputLabel?: string, expectedInputValue?: string }>()
confirmResponse = new Subject<boolean>()
- confirm (message = '', title = '') {
+ confirm (message: string, title = '') {
this.showConfirm.next({ title, message })
- return this.confirmResponse.asObservable().first()
+ return this.confirmResponse.asObservable().first().toPromise()
+ }
+
+ confirmWithInput (message: string, inputLabel: string, expectedInputValue: string, title = '') {
+ this.showConfirm.next({ title, message, inputLabel, expectedInputValue })
+
+ return this.confirmResponse.asObservable().first().toPromise()
}
}
import { CommonModule } from '@angular/common'
import { NgModule, Optional, SkipSelf } from '@angular/core'
+import { FormsModule } from '@angular/forms'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouterModule } from '@angular/router'
import { LoadingBarModule } from '@ngx-loading-bar/core'
imports: [
CommonModule,
RouterModule,
+ FormsModule,
BrowserAnimationsModule,
ModalModule,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
- ): Observable<boolean> | boolean {
+ ) {
const result = component.canDeactivate()
const text = result.text || 'All unsaved data will be lost, are you sure you want to leave this page?'
this.viewReplies(commentTree.comment.id)
}
- onWantedToDelete (commentToDelete: VideoComment) {
+ async onWantedToDelete (commentToDelete: VideoComment) {
let message = 'Do you really want to delete this comment?'
if (commentToDelete.totalReplies !== 0) message += `${commentToDelete.totalReplies} would be deleted too.`
- this.confirmService.confirm(message, 'Delete').subscribe(
- res => {
- if (res === false) return
-
- this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id)
- .subscribe(
- () => {
- // Delete the comment in the tree
- if (commentToDelete.inReplyToCommentId) {
- const thread = this.threadComments[commentToDelete.threadId]
- if (!thread) {
- console.error(`Cannot find thread ${commentToDelete.threadId} of the comment to delete ${commentToDelete.id}`)
- return
- }
-
- this.deleteLocalCommentThread(thread, commentToDelete)
- return
- }
-
- // Delete the thread
- this.comments = this.comments.filter(c => c.id !== commentToDelete.id)
- this.componentPagination.totalItems--
- },
-
- err => this.notificationsService.error('Error', err.message)
- )
- }
- )
+ const res = await this.confirmService.confirm(message, 'Delete')
+ if (res === false) return
+
+ this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id)
+ .subscribe(
+ () => {
+ // Delete the comment in the tree
+ if (commentToDelete.inReplyToCommentId) {
+ const thread = this.threadComments[commentToDelete.threadId]
+ if (!thread) {
+ console.error(`Cannot find thread ${commentToDelete.threadId} of the comment to delete ${commentToDelete.id}`)
+ return
+ }
+
+ this.deleteLocalCommentThread(thread, commentToDelete)
+ return
+ }
+
+ // Delete the thread
+ this.comments = this.comments.filter(c => c.id !== commentToDelete.id)
+ this.componentPagination.totalItems--
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
}
isUserLoggedIn () {
}
}
- blacklistVideo (event: Event) {
+ async blacklistVideo (event: Event) {
event.preventDefault()
- this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist').subscribe(
- res => {
- if (res === false) return
+ const res = await this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist')
+ if (res === false) return
- this.videoBlacklistService.blacklistVideo(this.video.id)
- .subscribe(
- status => {
- this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
- this.router.navigate(['/videos/list'])
- },
+ this.videoBlacklistService.blacklistVideo(this.video.id)
+ .subscribe(
+ status => {
+ this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
+ this.router.navigate(['/videos/list'])
+ },
- error => this.notificationsService.error('Error', error.message)
- )
- }
- )
+ error => this.notificationsService.error('Error', error.message)
+ )
}
showMoreDescription () {
return this.video.isRemovableBy(this.authService.getUser())
}
- removeVideo (event: Event) {
+ async removeVideo (event: Event) {
event.preventDefault()
- this.confirmService.confirm('Do you really want to delete this video?', 'Delete')
- .subscribe(
- res => {
- if (res === false) return
+ const res = await this.confirmService.confirm('Do you really want to delete this video?', 'Delete')
+ if (res === false) return
- this.videoService.removeVideo(this.video.id)
- .subscribe(
- status => {
- this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
+ this.videoService.removeVideo(this.video.id)
+ .subscribe(
+ status => {
+ this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
- // Go back to the video-list.
- this.router.navigate([ '/videos/list' ])
- },
+ // Go back to the video-list.
+ this.router.navigate([ '/videos/list' ])
+ },
- error => this.notificationsService.error('Error', error.message)
- )
- }
+ error => this.notificationsService.error('Error', error.message)
)
}
const clientsRouter = express.Router()
const distPath = join(root(), 'client', 'dist')
-const assetsImagesPath = join(root(), 'client', 'dist', 'client', 'assets', 'images')
+const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
const indexPath = join(distPath, 'index.html')