From 2539932e16129992a2c0889b4ff527c265a8e2c7 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 27 May 2021 15:59:55 +0200 Subject: Instance homepage support (#4007) * Prepare homepage parsers * Add ability to update instance hompage * Add ability to set homepage as landing page * Add homepage preview in admin * Dynamically update left menu for homepage * Inject home content in homepage * Add videos list and channel miniature custom markup * Remove unused elements in markup service --- .../channel-miniature-markup.component.html | 8 ++ .../channel-miniature-markup.component.scss | 9 ++ .../channel-miniature-markup.component.ts | 26 ++++ .../shared-custom-markup/custom-markup.service.ts | 136 +++++++++++++++++++++ .../dynamic-element.service.ts | 57 +++++++++ .../shared-custom-markup/embed-markup.component.ts | 22 ++++ .../src/app/shared/shared-custom-markup/index.ts | 3 + .../playlist-miniature-markup.component.html | 2 + .../playlist-miniature-markup.component.scss | 7 ++ .../playlist-miniature-markup.component.ts | 38 ++++++ .../shared-custom-markup.module.ts | 49 ++++++++ .../video-miniature-markup.component.html | 6 + .../video-miniature-markup.component.scss | 7 ++ .../video-miniature-markup.component.ts | 44 +++++++ .../videos-list-markup.component.html | 13 ++ .../videos-list-markup.component.scss | 9 ++ .../videos-list-markup.component.ts | 60 +++++++++ .../shared-forms/markdown-textarea.component.html | 1 + .../shared-forms/markdown-textarea.component.ts | 45 +++++-- .../shared/shared-icons/global-icon.component.ts | 1 + .../shared-main/custom-page/custom-page.service.ts | 38 ++++++ .../app/shared/shared-main/custom-page/index.ts | 1 + .../app/shared/shared-main/shared-main.module.ts | 5 +- 23 files changed, 579 insertions(+), 8 deletions(-) create mode 100644 client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html create mode 100644 client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss create mode 100644 client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts create mode 100644 client/src/app/shared/shared-custom-markup/custom-markup.service.ts create mode 100644 client/src/app/shared/shared-custom-markup/dynamic-element.service.ts create mode 100644 client/src/app/shared/shared-custom-markup/embed-markup.component.ts create mode 100644 client/src/app/shared/shared-custom-markup/index.ts create mode 100644 client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html create mode 100644 client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss create mode 100644 client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts create mode 100644 client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts create mode 100644 client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html create mode 100644 client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss create mode 100644 client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts create mode 100644 client/src/app/shared/shared-custom-markup/videos-list-markup.component.html create mode 100644 client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss create mode 100644 client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts create mode 100644 client/src/app/shared/shared-main/custom-page/custom-page.service.ts create mode 100644 client/src/app/shared/shared-main/custom-page/index.ts (limited to 'client/src/app/shared') diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html new file mode 100644 index 000000000..da81006b9 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.html @@ -0,0 +1,8 @@ +
+ + +
{{ channel.displayName }}
+
{{ channel.name }}
+ +
{{ channel.description }}
+
diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss new file mode 100644 index 000000000..85018afe2 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.scss @@ -0,0 +1,9 @@ +@import '_variables'; +@import '_mixins'; + +.channel { + border-radius: 15px; + padding: 10px; + width: min-content; + border: 1px solid pvar(--mainColor); +} diff --git a/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts new file mode 100644 index 000000000..97bb5567e --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/channel-miniature-markup.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, OnInit } from '@angular/core' +import { VideoChannel, VideoChannelService } from '../shared-main' + +/* + * Markup component that creates a channel miniature only +*/ + +@Component({ + selector: 'my-channel-miniature-markup', + templateUrl: 'channel-miniature-markup.component.html', + styleUrls: [ 'channel-miniature-markup.component.scss' ] +}) +export class ChannelMiniatureMarkupComponent implements OnInit { + @Input() name: string + + channel: VideoChannel + + constructor ( + private channelService: VideoChannelService + ) { } + + ngOnInit () { + this.channelService.getVideoChannel(this.name) + .subscribe(channel => this.channel = channel) + } +} 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 new file mode 100644 index 000000000..ffaf15710 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/custom-markup.service.ts @@ -0,0 +1,136 @@ +import { ComponentRef, Injectable } from '@angular/core' +import { MarkdownService } from '@app/core' +import { + ChannelMiniatureMarkupData, + EmbedMarkupData, + PlaylistMiniatureMarkupData, + VideoMiniatureMarkupData, + VideosListMarkupData +} from '@shared/models' +import { ChannelMiniatureMarkupComponent } from './channel-miniature-markup.component' +import { DynamicElementService } from './dynamic-element.service' +import { EmbedMarkupComponent } from './embed-markup.component' +import { PlaylistMiniatureMarkupComponent } from './playlist-miniature-markup.component' +import { VideoMiniatureMarkupComponent } from './video-miniature-markup.component' +import { VideosListMarkupComponent } from './videos-list-markup.component' + +type BuilderFunction = (el: HTMLElement) => ComponentRef + +@Injectable() +export class CustomMarkupService { + private builders: { [ selector: string ]: BuilderFunction } = { + 'peertube-video-embed': el => this.embedBuilder(el, 'video'), + 'peertube-playlist-embed': el => this.embedBuilder(el, 'playlist'), + 'peertube-video-miniature': el => this.videoMiniatureBuilder(el), + 'peertube-playlist-miniature': el => this.playlistMiniatureBuilder(el), + 'peertube-channel-miniature': el => this.channelMiniatureBuilder(el), + 'peertube-videos-list': el => this.videosListBuilder(el) + } + + constructor ( + private dynamicElementService: DynamicElementService, + private markdown: MarkdownService + ) { } + + async buildElement (text: string) { + const html = await this.markdown.customPageMarkdownToHTML(text, this.getSupportedTags()) + + const rootElement = document.createElement('div') + rootElement.innerHTML = html + + for (const selector of this.getSupportedTags()) { + rootElement.querySelectorAll(selector) + .forEach((e: HTMLElement) => { + try { + const component = this.execBuilder(selector, e) + + this.dynamicElementService.injectElement(e, component) + } catch (err) { + console.error('Cannot inject component %s.', selector, err) + } + }) + } + + return rootElement + } + + private getSupportedTags () { + return Object.keys(this.builders) + } + + private execBuilder (selector: string, el: HTMLElement) { + return this.builders[selector](el) + } + + private embedBuilder (el: HTMLElement, type: 'video' | 'playlist') { + const data = el.dataset as EmbedMarkupData + const component = this.dynamicElementService.createElement(EmbedMarkupComponent) + + this.dynamicElementService.setModel(component, { uuid: data.uuid, type }) + + return component + } + + private videoMiniatureBuilder (el: HTMLElement) { + const data = el.dataset as VideoMiniatureMarkupData + const component = this.dynamicElementService.createElement(VideoMiniatureMarkupComponent) + + this.dynamicElementService.setModel(component, { uuid: data.uuid }) + + return component + } + + private playlistMiniatureBuilder (el: HTMLElement) { + const data = el.dataset as PlaylistMiniatureMarkupData + const component = this.dynamicElementService.createElement(PlaylistMiniatureMarkupComponent) + + this.dynamicElementService.setModel(component, { uuid: data.uuid }) + + return component + } + + private channelMiniatureBuilder (el: HTMLElement) { + const data = el.dataset as ChannelMiniatureMarkupData + const component = this.dynamicElementService.createElement(ChannelMiniatureMarkupComponent) + + this.dynamicElementService.setModel(component, { name: data.name }) + + return component + } + + private videosListBuilder (el: HTMLElement) { + const data = el.dataset as VideosListMarkupData + const component = this.dynamicElementService.createElement(VideosListMarkupComponent) + + const model = { + title: data.title, + description: data.description, + sort: data.sort, + categoryOneOf: this.buildArrayNumber(data.categoryOneOf), + languageOneOf: this.buildArrayString(data.languageOneOf), + count: this.buildNumber(data.count) || 10 + } + + this.dynamicElementService.setModel(component, model) + + return component + } + + private buildNumber (value: string) { + if (!value) return undefined + + return parseInt(value, 10) + } + + private buildArrayNumber (value: string) { + if (!value) return undefined + + return value.split(',').map(v => parseInt(v, 10)) + } + + private buildArrayString (value: string) { + if (!value) return undefined + + return value.split(',') + } +} diff --git a/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts b/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts new file mode 100644 index 000000000..e967e30ac --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/dynamic-element.service.ts @@ -0,0 +1,57 @@ +import { + ApplicationRef, + ComponentFactoryResolver, + ComponentRef, + EmbeddedViewRef, + Injectable, + Injector, + OnChanges, + SimpleChange, + SimpleChanges, + Type +} from '@angular/core' + +@Injectable() +export class DynamicElementService { + + constructor ( + private injector: Injector, + private applicationRef: ApplicationRef, + private componentFactoryResolver: ComponentFactoryResolver + ) { } + + createElement (ofComponent: Type) { + const div = document.createElement('div') + + const component = this.componentFactoryResolver.resolveComponentFactory(ofComponent) + .create(this.injector, [], div) + + return component + } + + injectElement (wrapper: HTMLElement, componentRef: ComponentRef) { + const hostView = componentRef.hostView as EmbeddedViewRef + + this.applicationRef.attachView(hostView) + wrapper.appendChild(hostView.rootNodes[0]) + } + + setModel (componentRef: ComponentRef, attributes: Partial) { + const changes: SimpleChanges = {} + + for (const key of Object.keys(attributes)) { + const previousValue = componentRef.instance[key] + const newValue = attributes[key] + + componentRef.instance[key] = newValue + changes[key] = new SimpleChange(previousValue, newValue, previousValue === undefined) + } + + const component = componentRef.instance + if (typeof (component as unknown as OnChanges).ngOnChanges === 'function') { + (component as unknown as OnChanges).ngOnChanges(changes) + } + + componentRef.changeDetectorRef.detectChanges() + } +} diff --git a/client/src/app/shared/shared-custom-markup/embed-markup.component.ts b/client/src/app/shared/shared-custom-markup/embed-markup.component.ts new file mode 100644 index 000000000..a854d89f6 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/embed-markup.component.ts @@ -0,0 +1,22 @@ +import { buildPlaylistLink, buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' +import { environment } from 'src/environments/environment' +import { Component, ElementRef, Input, OnInit } from '@angular/core' + +@Component({ + selector: 'my-embed-markup', + template: '' +}) +export class EmbedMarkupComponent implements OnInit { + @Input() uuid: string + @Input() type: 'video' | 'playlist' = 'video' + + constructor (private el: ElementRef) { } + + ngOnInit () { + const link = this.type === 'video' + ? buildVideoLink({ baseUrl: `${environment.originServerUrl}/videos/embed/${this.uuid}` }) + : buildPlaylistLink({ baseUrl: `${environment.originServerUrl}/video-playlists/embed/${this.uuid}` }) + + this.el.nativeElement.innerHTML = buildVideoOrPlaylistEmbed(link, this.uuid) + } +} diff --git a/client/src/app/shared/shared-custom-markup/index.ts b/client/src/app/shared/shared-custom-markup/index.ts new file mode 100644 index 000000000..14bde3ea9 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/index.ts @@ -0,0 +1,3 @@ +export * from './custom-markup.service' +export * from './dynamic-element.service' +export * from './shared-custom-markup.module' diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html new file mode 100644 index 000000000..4e1d1a13f --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.html @@ -0,0 +1,2 @@ + + diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss new file mode 100644 index 000000000..281cef726 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.scss @@ -0,0 +1,7 @@ +@import '_variables'; +@import '_mixins'; + +my-video-playlist-miniature { + display: inline-block; + width: $video-thumbnail-width; +} diff --git a/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts new file mode 100644 index 000000000..7aee450f1 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/playlist-miniature-markup.component.ts @@ -0,0 +1,38 @@ +import { Component, Input, OnInit } from '@angular/core' +import { MiniatureDisplayOptions } from '../shared-video-miniature' +import { VideoPlaylist, VideoPlaylistService } from '../shared-video-playlist' + +/* + * Markup component that creates a playlist miniature only +*/ + +@Component({ + selector: 'my-playlist-miniature-markup', + templateUrl: 'playlist-miniature-markup.component.html', + styleUrls: [ 'playlist-miniature-markup.component.scss' ] +}) +export class PlaylistMiniatureMarkupComponent implements OnInit { + @Input() uuid: string + + playlist: VideoPlaylist + + displayOptions: MiniatureDisplayOptions = { + date: true, + views: true, + by: true, + avatar: false, + privacyLabel: false, + privacyText: false, + state: false, + blacklistInfo: false + } + + constructor ( + private playlistService: VideoPlaylistService + ) { } + + ngOnInit () { + this.playlistService.getVideoPlaylist(this.uuid) + .subscribe(playlist => this.playlist = playlist) + } +} diff --git a/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts b/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts new file mode 100644 index 000000000..4bbb71588 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/shared-custom-markup.module.ts @@ -0,0 +1,49 @@ + +import { CommonModule } from '@angular/common' +import { NgModule } from '@angular/core' +import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' +import { SharedGlobalIconModule } from '../shared-icons' +import { SharedMainModule } from '../shared-main' +import { SharedVideoMiniatureModule } from '../shared-video-miniature' +import { SharedVideoPlaylistModule } from '../shared-video-playlist' +import { ChannelMiniatureMarkupComponent } from './channel-miniature-markup.component' +import { CustomMarkupService } from './custom-markup.service' +import { DynamicElementService } from './dynamic-element.service' +import { EmbedMarkupComponent } from './embed-markup.component' +import { PlaylistMiniatureMarkupComponent } from './playlist-miniature-markup.component' +import { VideoMiniatureMarkupComponent } from './video-miniature-markup.component' +import { VideosListMarkupComponent } from './videos-list-markup.component' + +@NgModule({ + imports: [ + CommonModule, + + SharedMainModule, + SharedGlobalIconModule, + SharedVideoMiniatureModule, + SharedVideoPlaylistModule, + SharedActorImageModule + ], + + declarations: [ + VideoMiniatureMarkupComponent, + PlaylistMiniatureMarkupComponent, + ChannelMiniatureMarkupComponent, + EmbedMarkupComponent, + VideosListMarkupComponent + ], + + exports: [ + VideoMiniatureMarkupComponent, + PlaylistMiniatureMarkupComponent, + ChannelMiniatureMarkupComponent, + VideosListMarkupComponent, + EmbedMarkupComponent + ], + + providers: [ + CustomMarkupService, + DynamicElementService + ] +}) +export class SharedCustomMarkupModule { } diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html new file mode 100644 index 000000000..9b4930b6d --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.html @@ -0,0 +1,6 @@ + + diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss new file mode 100644 index 000000000..81e265f29 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.scss @@ -0,0 +1,7 @@ +@import '_variables'; +@import '_mixins'; + +my-video-miniature { + display: inline-block; + width: $video-thumbnail-width; +} diff --git a/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts new file mode 100644 index 000000000..79add0c3b --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/video-miniature-markup.component.ts @@ -0,0 +1,44 @@ +import { Component, Input, OnInit } from '@angular/core' +import { AuthService } from '@app/core' +import { Video, VideoService } from '../shared-main' +import { MiniatureDisplayOptions } from '../shared-video-miniature' + +/* + * Markup component that creates a video miniature only +*/ + +@Component({ + selector: 'my-video-miniature-markup', + templateUrl: 'video-miniature-markup.component.html', + styleUrls: [ 'video-miniature-markup.component.scss' ] +}) +export class VideoMiniatureMarkupComponent implements OnInit { + @Input() uuid: string + + video: Video + + displayOptions: MiniatureDisplayOptions = { + date: true, + views: true, + by: true, + avatar: false, + privacyLabel: false, + privacyText: false, + state: false, + blacklistInfo: false + } + + constructor ( + private auth: AuthService, + private videoService: VideoService + ) { } + + getUser () { + return this.auth.getUser() + } + + ngOnInit () { + this.videoService.getVideo({ videoId: this.uuid }) + .subscribe(video => this.video = video) + } +} diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html new file mode 100644 index 000000000..501f35e04 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.html @@ -0,0 +1,13 @@ +
+

{{ title }}

+
{{ description }}
+ +
+ + +
+
diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss new file mode 100644 index 000000000..dcd931090 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.scss @@ -0,0 +1,9 @@ +@import '_variables'; +@import '_mixins'; + +my-video-miniature { + margin-right: 15px; + display: inline-block; + min-width: $video-thumbnail-width; + max-width: $video-thumbnail-width; +} diff --git a/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts new file mode 100644 index 000000000..cc25d0a51 --- /dev/null +++ b/client/src/app/shared/shared-custom-markup/videos-list-markup.component.ts @@ -0,0 +1,60 @@ +import { Component, Input, OnInit } from '@angular/core' +import { AuthService } from '@app/core' +import { VideoSortField } from '@shared/models' +import { Video, VideoService } from '../shared-main' +import { MiniatureDisplayOptions } from '../shared-video-miniature' + +/* + * Markup component list videos depending on criterias +*/ + +@Component({ + selector: 'my-videos-list-markup', + templateUrl: 'videos-list-markup.component.html', + styleUrls: [ 'videos-list-markup.component.scss' ] +}) +export class VideosListMarkupComponent implements OnInit { + @Input() title: string + @Input() description: string + @Input() sort = '-publishedAt' + @Input() categoryOneOf: number[] + @Input() languageOneOf: string[] + @Input() count = 10 + + videos: Video[] + + displayOptions: MiniatureDisplayOptions = { + date: true, + views: true, + by: true, + avatar: false, + privacyLabel: false, + privacyText: false, + state: false, + blacklistInfo: false + } + + constructor ( + private auth: AuthService, + private videoService: VideoService + ) { } + + getUser () { + return this.auth.getUser() + } + + ngOnInit () { + const options = { + videoPagination: { + currentPage: 1, + itemsPerPage: this.count + }, + categoryOneOf: this.categoryOneOf, + languageOneOf: this.languageOneOf, + sort: this.sort as VideoSortField + } + + this.videoService.getVideos(options) + .subscribe(({ data }) => this.videos = data) + } +} diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.html b/client/src/app/shared/shared-forms/markdown-textarea.component.html index 513b543cd..6e70e2f37 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.html +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.html @@ -19,6 +19,7 @@ Complete preview +
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts index 9b3ab9cf3..a233a4205 100644 --- a/client/src/app/shared/shared-forms/markdown-textarea.component.ts +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts @@ -1,9 +1,10 @@ -import { ViewportScroller } from '@angular/common' import truncate from 'lodash-es/truncate' import { Subject } from 'rxjs' import { debounceTime, distinctUntilChanged } from 'rxjs/operators' +import { ViewportScroller } from '@angular/common' import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core' import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' +import { SafeHtml } from '@angular/platform-browser' import { MarkdownService, ScreenService } from '@app/core' @Component({ @@ -21,18 +22,27 @@ import { MarkdownService, ScreenService } from '@app/core' export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { @Input() content = '' + @Input() classes: string[] | { [klass: string]: any[] | any } = [] + @Input() textareaMaxWidth = '100%' @Input() textareaHeight = '150px' + @Input() truncate: number + @Input() markdownType: 'text' | 'enhanced' = 'text' + @Input() customMarkdownRenderer?: (text: string) => Promise + @Input() markdownVideo = false + @Input() name = 'description' @ViewChild('textarea') textareaElement: ElementRef + @ViewChild('previewElement') previewElement: ElementRef + + truncatedPreviewHTML: SafeHtml | string = '' + previewHTML: SafeHtml | string = '' - truncatedPreviewHTML = '' - previewHTML = '' isMaximized = false maximizeInText = $localize`Maximize editor` @@ -115,10 +125,31 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { } private async markdownRender (text: string) { - const html = this.markdownType === 'text' ? - await this.markdownService.textMarkdownToHTML(text) : - await this.markdownService.enhancedMarkdownToHTML(text) + let html: string + + if (this.customMarkdownRenderer) { + const result = await this.customMarkdownRenderer(text) + + if (result instanceof HTMLElement) { + html = '' + + const wrapperElement = this.previewElement.nativeElement as HTMLElement + wrapperElement.innerHTML = '' + wrapperElement.appendChild(result) + return + } + + html = result + } else if (this.markdownType === 'text') { + html = await this.markdownService.textMarkdownToHTML(text) + } else { + html = await this.markdownService.enhancedMarkdownToHTML(text) + } + + if (this.markdownVideo) { + html = this.markdownService.processVideoTimestamps(html) + } - return this.markdownVideo ? this.markdownService.processVideoTimestamps(html) : html + return html } } diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index 3af517927..a4dd72db6 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts @@ -72,6 +72,7 @@ const icons = { 'repeat': require('!!raw-loader?!../../../assets/images/feather/repeat.svg').default, 'message-circle': require('!!raw-loader?!../../../assets/images/feather/message-circle.svg').default, 'codesandbox': require('!!raw-loader?!../../../assets/images/feather/codesandbox.svg').default, + 'octagon': require('!!raw-loader?!../../../assets/images/feather/octagon.svg').default, 'award': require('!!raw-loader?!../../../assets/images/feather/award.svg').default } diff --git a/client/src/app/shared/shared-main/custom-page/custom-page.service.ts b/client/src/app/shared/shared-main/custom-page/custom-page.service.ts new file mode 100644 index 000000000..e5c2b3cd4 --- /dev/null +++ b/client/src/app/shared/shared-main/custom-page/custom-page.service.ts @@ -0,0 +1,38 @@ +import { of } from 'rxjs' +import { catchError, map } from 'rxjs/operators' +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { RestExtractor } from '@app/core' +import { CustomPage } from '@shared/models' +import { environment } from '../../../../environments/environment' + +@Injectable() +export class CustomPageService { + static BASE_INSTANCE_HOMEPAGE_URL = environment.apiUrl + '/api/v1/custom-pages/homepage/instance' + + constructor ( + private authHttp: HttpClient, + private restExtractor: RestExtractor + ) { } + + getInstanceHomepage () { + return this.authHttp.get(CustomPageService.BASE_INSTANCE_HOMEPAGE_URL) + .pipe( + catchError(err => { + if (err.status === 404) { + return of({ content: '' }) + } + + this.restExtractor.handleError(err) + }) + ) + } + + updateInstanceHomepage (content: string) { + return this.authHttp.put(CustomPageService.BASE_INSTANCE_HOMEPAGE_URL, { content }) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } +} diff --git a/client/src/app/shared/shared-main/custom-page/index.ts b/client/src/app/shared/shared-main/custom-page/index.ts new file mode 100644 index 000000000..7269ece95 --- /dev/null +++ b/client/src/app/shared/shared-main/custom-page/index.ts @@ -0,0 +1 @@ +export * from './custom-page.service' diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index 772198cb2..f9b6085cf 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts @@ -29,6 +29,7 @@ import { } from './angular' import { AUTH_INTERCEPTOR_PROVIDER } from './auth' import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditButtonComponent } from './buttons' +import { CustomPageService } from './custom-page' import { DateToggleComponent } from './date' import { FeedComponent } from './feeds' import { LoaderComponent, SmallLoaderComponent } from './loaders' @@ -171,7 +172,9 @@ import { VideoChannelService } from './video-channel' VideoCaptionService, - VideoChannelService + VideoChannelService, + + CustomPageService ] }) export class SharedMainModule { } -- cgit v1.2.3