diff --git a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.html b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.html index 6bf2294a3..685efc9ab 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.html +++ b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.html @@ -1 +1 @@ -
+
diff --git a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts index 3d49c6768..4e802b14d 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts +++ b/client/src/app/shared/shared-custom-markup/custom-markup-container.component.ts @@ -10,6 +10,8 @@ export class CustomMarkupContainerComponent implements OnChanges { @Input() content: string + displayed = false + constructor ( private customMarkupService: CustomMarkupService ) { } @@ -19,8 +21,13 @@ export class CustomMarkupContainerComponent implements OnChanges { } private async buildElement () { - const element = await this.customMarkupService.buildElement(this.content) - this.contentWrapper.nativeElement.appendChild(element) - } + if (!this.content) return + const { rootElement, componentsLoaded } = await this.customMarkupService.buildElement(this.content) + this.contentWrapper.nativeElement.appendChild(rootElement) + + await componentsLoaded + + this.displayed = true + } } diff --git a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts index cb1110593..15da94709 100644 --- a/client/src/app/shared/shared-custom-markup/custom-markup.service.ts +++ b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts @@ -1,3 +1,4 @@ +import { first } from 'rxjs/operators' import { ComponentRef, Injectable } from '@angular/core' import { MarkdownService } from '@app/core' import { @@ -19,8 +20,9 @@ import { VideoMiniatureMarkupComponent, VideosListMarkupComponent } from './peertube-custom-tags' +import { CustomMarkupComponent } from './peertube-custom-tags/shared' -type AngularBuilderFunction = (el: HTMLElement) => ComponentRef +type AngularBuilderFunction = (el: HTMLElement) => ComponentRef type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement @Injectable() @@ -45,7 +47,10 @@ export class CustomMarkupService { private dynamicElementService: DynamicElementService, private markdown: MarkdownService ) { - this.customMarkdownRenderer = async (text: string) => this.buildElement(text) + this.customMarkdownRenderer = (text: string) => { + return this.buildElement(text) + .then(({ rootElement }) => rootElement) + } } getCustomMarkdownRenderer () { @@ -60,23 +65,30 @@ export class CustomMarkupService { for (const selector of Object.keys(this.htmlBuilders)) { rootElement.querySelectorAll(selector) - .forEach((e: HTMLElement) => { - try { - const element = this.execHTMLBuilder(selector, e) - // Insert as first child - e.insertBefore(element, e.firstChild) - } catch (err) { - console.error('Cannot inject component %s.', selector, err) - } - }) + .forEach((e: HTMLElement) => { + try { + const element = this.execHTMLBuilder(selector, e) + // Insert as first child + e.insertBefore(element, e.firstChild) + } catch (err) { + console.error('Cannot inject component %s.', selector, err) + } + }) } + const loadedPromises: Promise[] = [] + for (const selector of Object.keys(this.angularBuilders)) { rootElement.querySelectorAll(selector) .forEach((e: HTMLElement) => { try { const component = this.execAngularBuilder(selector, e) + if (component.instance.loaded) { + const p = component.instance.loaded.pipe(first()).toPromise() + loadedPromises.push(p) + } + this.dynamicElementService.injectElement(e, component) } catch (err) { console.error('Cannot inject component %s.', selector, err) @@ -84,7 +96,7 @@ export class CustomMarkupService { }) } - return rootElement + return { rootElement, componentsLoaded: Promise.all(loadedPromises) } } private getSupportedTags () { diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/button-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/button-markup.component.ts index 987b37d19..1af060548 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/button-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/button-markup.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core' import { VideoChannel } from '../../shared-main' +import { CustomMarkupComponent } from './shared' /* * Markup component that creates a button @@ -10,13 +11,14 @@ import { VideoChannel } from '../../shared-main' templateUrl: 'button-markup.component.html', styleUrls: [ 'button-markup.component.scss' ] }) -export class ButtonMarkupComponent { +export class ButtonMarkupComponent implements CustomMarkupComponent { @Input() theme: 'primary' | 'secondary' @Input() href: string @Input() label: string @Input() blankTarget?: boolean channel: VideoChannel + loaded: undefined getTarget () { if (this.blankTarget === true) return '_blank' diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts index 87caec8a5..a91debbef 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/channel-miniature-markup.component.ts @@ -1,8 +1,9 @@ import { map, switchMap } from 'rxjs/operators' -import { Component, Input, OnInit } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { MarkdownService, UserService } from '@app/core' import { Video, VideoSortField } from '@shared/models/videos' import { VideoChannel, VideoChannelService, VideoService } from '../../shared-main' +import { CustomMarkupComponent } from './shared' /* * Markup component that creates a channel miniature only @@ -13,11 +14,13 @@ import { VideoChannel, VideoChannelService, VideoService } from '../../shared-ma templateUrl: 'channel-miniature-markup.component.html', styleUrls: [ 'channel-miniature-markup.component.scss' ] }) -export class ChannelMiniatureMarkupComponent implements OnInit { +export class ChannelMiniatureMarkupComponent implements CustomMarkupComponent, OnInit { @Input() name: string @Input() displayLatestVideo: boolean @Input() displayDescription: boolean + @Output() loaded = new EventEmitter() + channel: VideoChannel descriptionHTML: string totalVideos: number @@ -61,9 +64,13 @@ export class ChannelMiniatureMarkupComponent implements OnInit { map(user => user.nsfwPolicy), switchMap(nsfwPolicy => this.videoService.getVideoChannelVideos({ ...videoOptions, nsfwPolicy })) ) - .subscribe(({ total, data }) => { - this.totalVideos = total - this.video = data[0] + .subscribe({ + next: ({ total, data }) => { + this.totalVideos = total + this.video = data[0] + }, + + complete: () => this.loaded.emit(true) }) } } diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts index a854d89f6..4462903db 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/embed-markup.component.ts @@ -1,15 +1,18 @@ import { buildPlaylistLink, buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' import { environment } from 'src/environments/environment' import { Component, ElementRef, Input, OnInit } from '@angular/core' +import { CustomMarkupComponent } from './shared' @Component({ selector: 'my-embed-markup', template: '' }) -export class EmbedMarkupComponent implements OnInit { +export class EmbedMarkupComponent implements CustomMarkupComponent, OnInit { @Input() uuid: string @Input() type: 'video' | 'playlist' = 'video' + loaded: undefined + constructor (private el: ElementRef) { } ngOnInit () { diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts index eddc3636e..42a42d711 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/playlist-miniature-markup.component.ts @@ -1,6 +1,7 @@ -import { Component, Input, OnInit } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { MiniatureDisplayOptions } from '../../shared-video-miniature' import { VideoPlaylist, VideoPlaylistService } from '../../shared-video-playlist' +import { CustomMarkupComponent } from './shared' /* * Markup component that creates a playlist miniature only @@ -11,9 +12,11 @@ import { VideoPlaylist, VideoPlaylistService } from '../../shared-video-playlist templateUrl: 'playlist-miniature-markup.component.html', styleUrls: [ 'playlist-miniature-markup.component.scss' ] }) -export class PlaylistMiniatureMarkupComponent implements OnInit { +export class PlaylistMiniatureMarkupComponent implements CustomMarkupComponent, OnInit { @Input() uuid: string + @Output() loaded = new EventEmitter() + playlist: VideoPlaylist displayOptions: MiniatureDisplayOptions = { @@ -33,6 +36,10 @@ export class PlaylistMiniatureMarkupComponent implements OnInit { ngOnInit () { this.playlistService.getVideoPlaylist(this.uuid) - .subscribe(playlist => this.playlist = playlist) + .subscribe({ + next: playlist => this.playlist = playlist, + + complete: () => this.loaded.emit(true) + }) } } diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/custom-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/custom-markup.component.ts new file mode 100644 index 000000000..adfd48c55 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/custom-markup.component.ts @@ -0,0 +1,5 @@ +import { EventEmitter } from '@angular/core' + +export interface CustomMarkupComponent { + loaded: EventEmitter | undefined +} diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/index.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/index.ts new file mode 100644 index 000000000..a6a7bbd92 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/shared/index.ts @@ -0,0 +1 @@ +export * from './custom-markup.component' diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts index dfb4c497f..6ee5123e0 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/video-miniature-markup.component.ts @@ -1,7 +1,8 @@ -import { Component, Input, OnInit } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { AuthService } from '@app/core' import { Video, VideoService } from '../../shared-main' import { MiniatureDisplayOptions } from '../../shared-video-miniature' +import { CustomMarkupComponent } from './shared' /* * Markup component that creates a video miniature only @@ -12,10 +13,12 @@ import { MiniatureDisplayOptions } from '../../shared-video-miniature' templateUrl: 'video-miniature-markup.component.html', styleUrls: [ 'video-miniature-markup.component.scss' ] }) -export class VideoMiniatureMarkupComponent implements OnInit { +export class VideoMiniatureMarkupComponent implements CustomMarkupComponent, OnInit { @Input() uuid: string @Input() onlyDisplayTitle: boolean + @Output() loaded = new EventEmitter() + video: Video displayOptions: MiniatureDisplayOptions = { @@ -46,6 +49,10 @@ export class VideoMiniatureMarkupComponent implements OnInit { } this.videoService.getVideo({ videoId: this.uuid }) - .subscribe(video => this.video = video) + .subscribe({ + next: video => this.video = video, + + complete: () => this.loaded.emit(true) + }) } } diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts index d4402dd9f..02738022e 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/videos-list-markup.component.ts @@ -1,8 +1,9 @@ -import { Component, Input, OnInit } from '@angular/core' +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { AuthService } from '@app/core' import { VideoFilter, VideoSortField } from '@shared/models' import { Video, VideoService } from '../../shared-main' import { MiniatureDisplayOptions } from '../../shared-video-miniature' +import { CustomMarkupComponent } from './shared' /* * Markup component list videos depending on criterias @@ -13,7 +14,7 @@ import { MiniatureDisplayOptions } from '../../shared-video-miniature' templateUrl: 'videos-list-markup.component.html', styleUrls: [ 'videos-list-markup.component.scss' ] }) -export class VideosListMarkupComponent implements OnInit { +export class VideosListMarkupComponent implements CustomMarkupComponent, OnInit { @Input() sort: string @Input() categoryOneOf: number[] @Input() languageOneOf: string[] @@ -22,6 +23,8 @@ export class VideosListMarkupComponent implements OnInit { @Input() filter: VideoFilter @Input() maxRows: number + @Output() loaded = new EventEmitter() + videos: Video[] displayOptions: MiniatureDisplayOptions = { @@ -73,6 +76,10 @@ export class VideosListMarkupComponent implements OnInit { } this.videoService.getVideos(options) - .subscribe(({ data }) => this.videos = data) + .subscribe({ + next: ({ data }) => this.videos = data, + + complete: () => this.loaded.emit(true) + }) } }