author import { Injectable , Self , Inject } from '@angular/core' ; import { BehaviorSubject , combineLatest , Subscription } from 'rxjs' ; import { Upload , Client } from '../../../../services/api' ; import { Router } from '@angular/router' ; import { distinctUntilChanged , tap , take } from 'rxjs/operators' ; import { SiteService } from '../../../../common/services/site.service' ; import { Captcha } from '../../../captcha/captcha.component' ; import { FormToastService } from '../../../../common/services/form-toast.service' ; import { ComposerService , MonetizationSubjectValue , DEFAULT_ACCESS_ID_VALUE , } from '../../../composer/services/composer.service' ; import { BlogPreloadService } from './blog-preload.service' ; export interface MetaData { title : string ; description : string ; author : string ; } export interface BlogResponse { guid ? : string ; route ? : string ; slug ? : string ; status : string ; message ? : string ; } export interface BlogEditEntity { guid : string ; title : string ; description : string ; editor_version : number ; published : number ; nsfw : number [ ] ; tags : string [ ] ; file : any ; fileKey : string ; captcha : Captcha ; paywall ? : boolean ; wire_threshold : MonetizationSubjectValue ; time_created : number ; access_id : number ; custom_meta ? : { title : string ; description : string ; author : string ; } ; slug ? : string ; license ? : string ; } export const DEFAULT_BLOG_EDITOR_VERSION_VALUE = 2 ; @ Injectable ( ) export class BlogsEditService { readonly error$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly title$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly author$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly banner$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly guid$ : BehaviorSubject < string > = new BehaviorSubject < string > ( 'new' ) ; readonly published$ : BehaviorSubject < number > = new BehaviorSubject < number > ( 0 ) ; readonly license$ : BehaviorSubject < string > = this . composerService . license$ ; readonly content$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly nsfw$ : BehaviorSubject < number [ ] > = new BehaviorSubject < number [ ] > ( [ ] ) ; readonly tags$ : BehaviorSubject < string [ ] > = this . composerService . tags$ ; readonly urlSlug$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly accessId$ : BehaviorSubject < string > = this . composerService . accessId$ ; readonly timeCreated$ : BehaviorSubject < number > = new BehaviorSubject < number > ( 0 ) ; readonly metaDescription$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly metaTitle$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly captcha$ : BehaviorSubject < Captcha > = new BehaviorSubject < Captcha > ( null ) ; readonly canPost$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; readonly inProgress$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; readonly bannerFile$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly schedule$ : BehaviorSubject < number > = new BehaviorSubject < number > ( null ) ; readonly savedContent$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly attachmentError$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; readonly monetize$ : BehaviorSubject < MonetizationSubjectValue > = this . composerService . monetization$ ; readonly editorVersion$ : BehaviorSubject < number > = new BehaviorSubject ( DEFAULT_BLOG_EDITOR_VERSION_VALUE ) ; private contentSubscription : Subscription ; private accessIdSubscription : Subscription ; constructor ( protected upload , protected router , protected client , protected site , private toaster , private preload , @ Self ( ) @ Inject ( ComposerService ) private composerService ) { this . contentSubscription = combineLatest ( [ this . content$ , this . preload . message$ , ] ) . pipe ( distinctUntilChanged ( ) , tap ( ( [ content , preloadMessage ] ) => { if ( preloadMessage ) { this . content$ . next ( preloadMessage ) ; this . preload . clear ( ) ; } this . canPost$ . next ( content . length > 0 ) ; } ) ) . subscribe ( ) ; } param activity public async load ( guid ) { this . inProgress$ . next ( true ) ; this . canPost$ . next ( false ) ; const response = await this . client . get ( 'api/v1/blog/' + guid , { } ) ; if ( response . blog ) { const blog = response . blog ; this . urlSlug$ . next ( blog . slug ) ; this . title$ . next ( blog . title ) ; this . content$ . next ( blog . description ) ; this . author$ . next ( blog . custom_meta . author ) ; this . nsfw$ . next ( blog . nsfw ) ; this . error$ . next ( '' ) ; this . banner$ . next ( this . site . baseUrl + 'fs/v1/banners/' + guid + '/' + blog . time_updated ) ; this . bannerFile$ . next ( '' ) ; this . canPost$ . next ( true ) ; this . guid$ . next ( guid ) ; this . published$ . next ( blog . published ) ; this . timeCreated$ . next ( blog . time_created ) ; this . accessId$ . next ( blog . access_id ) ; this . schedule$ . next ( blog . time_created ) ; this . savedContent$ . next ( blog . description ) ; this . license$ . next ( blog . license ) ; this . monetize$ . next ( blog . wire_threshold ) ; this . tags$ . next ( blog . tags ) ; this . metaDescription$ . next ( blog . custom_meta . description ) ; this . metaTitle$ . next ( blog . custom_meta . title ) ; this . editorVersion$ . next ( blog . editor_version ) ; } this . inProgress$ . next ( false ) ; this . canPost$ . next ( true ) ; } /**
     * Runs when dependant component is destroyed
     */ ngOnDestroy ( ) { this . tearDown ( ) ; if ( this . contentSubscription ) { this . contentSubscription . unsubscribe ( ) ; } if ( this . accessIdSubscription ) { this . accessIdSubscription . unsubscribe ( ) ; } } /**
     * Destroys the service and its state
     */ tearDown ( ) { this . reset ( ) ; } /**
     * Resets composer data and state
     */ reset ( ) { this . urlSlug$ . next ( '' ) ; this . title$ . next ( '' ) ; this . content$ . next ( '' ) ; this . author$ . next ( '' ) ; this . content$ . next ( '' ) ; this . error$ . next ( '' ) ; this . banner$ . next ( '' ) ; this . bannerFile$ . next ( '' ) ; this . canPost$ . next ( true ) ; this . guid$ . next ( '' ) ; this . published$ . next ( 0 ) ; this . timeCreated$ . next ( 0 ) ; this . accessId$ . next ( DEFAULT_ACCESS_ID_VALUE ) ; this . schedule$ . next ( null ) ; this . savedContent$ . next ( '' ) ; this . tags$ . next ( [ ] ) ; this . nsfw$ . next ( [ ] ) ; this . metaDescription$ . next ( '' ) ; this . metaTitle$ . next ( '' ) ; this . editorVersion$ . next ( DEFAULT_BLOG_EDITOR_VERSION_VALUE ) ; } param { boolean } returns { Promise < void > } async save ( draft = false ) { if ( ! this . bannerFile$ . getValue ( ) && ! this . banner$ . getValue ( ) ) { this . error$ . next ( 'You must upload a banner' ) ; return ; } if ( ! this . title$ . getValue ( ) ) { this . error$ . next ( 'Your blog must have a title' ) ; return ; } this . published$ . next ( draft ? 0 : 1 ) ; const blog = await this . buildBlog ( ) ; this . canPost$ . next ( false ) ; this . inProgress$ . next ( true ) ; try { const response = await this . upload . post ( 'api/v1/blog/' + blog . guid , [ blog . file ] , blog ) ; if ( response . status !== 'success' ) { this . canPost$ . next ( true ) ; this . inProgress$ . next ( false ) ; this . error$ . next ( response . message || 'An unknown error has occurred' ) ; return response ; } if ( ! draft ) { this . tearDown ( ) ; await this . router . navigate ( response . route ? [ '/' + response . route ] : [ '/blog/view' , response . guid ] ) ; return ; } this . guid$ . next ( response . guid ) ; this . savedContent$ . next ( this . content$ . getValue ( ) ) ; this . toaster . success ( 'Your draft has been successfully saved.' ) ; this . inProgress$ . next ( false ) ; this . canPost$ . next ( true ) ; return response ; } catch ( e ) { console . error ( e ) ; this . error$ . next ( e ) ; this . canPost$ . next ( true ) ; this . inProgress$ . next ( false ) ; return { status : '500' , message : e , } ; } } param { boolean } returns { Promise < void > } private async setNextPublishState ( draft ) { this . accessIdSubscription = this . accessId$ . pipe ( take ( 1 ) , tap ( id => { this . published$ . next ( draft ? 0 : 1 ) ; if ( draft ) { this . accessId$ . next ( '0' ) ; return ; } if ( id === '0' ) { this . accessId$ . next ( '2' ) ; return ; } return ; } ) ) . subscribe ( ) ; } returns { Promise < any > } public async saveDraft ( ) { return this . save ( true ) ; } returns { Promise < Blog > } async buildBlog ( ) { return { file : this . bannerFile$ . getValue ( ) , guid : this . guid$ . getValue ( ) , title : this . title$ . getValue ( ) , description : this . content$ . getValue ( ) , access_id : Number ( this . accessId$ . getValue ( ) ) , license : this . license$ . getValue ( ) , fileKey : 'header' , nsfw : this . nsfw$ . getValue ( ) , paywall : Boolean ( this . monetize$ . getValue ( ) ) , wire_threshold : this . monetize$ . getValue ( ) , published : this . published$ . getValue ( ) , custom_meta : { title : this . metaTitle$ . getValue ( ) , description : this . metaDescription$ . getValue ( ) , author : this . author$ . getValue ( ) , } , slug : this . urlSlug$ . getValue ( ) , tags : this . tags$ . getValue ( ) , time_created : this . schedule$ . getValue ( ) , editor_version : DEFAULT_BLOG_EDITOR_VERSION_VALUE , captcha : this . captcha$ . getValue ( ) , } ; } param banner addBanner ( banner ) { if ( banner . length === 0 ) { return ; } if ( banner . type . match ( /image\/*/ ) === null ) { this . error$ . next ( 'Banners must be an image' ) ; return ; } try { this . canPost$ . next ( true ) ; this . bannerFile$ . next ( banner ) ; const reader = new FileReader ( ) ; reader . readAsDataURL ( banner ) ; reader . onload = _event => { this . banner$ . next ( reader . result . toString ( ) ) ; } ; } catch ( e ) { this . error$ . next ( 'Error uploading banner' ) ; } } returns { boolean } public hasContent ( ) { return this . content$ . getValue ( ) !== this . savedContent$ . getValue ( ) ; } param { string } public pushTag ( tag ) { const current = this . tags$ . getValue ( ) ; if ( current . indexOf ( tag ) < 0 && current . length < 5 ) { tag = tag . replace ( /\W/g , '' ) ; if ( tag . length > 0 ) { current . push ( tag ) ; this . tags$ . next ( current ) ; } } } param { string } tag public removeTag ( tag ) { this . tags$ . next ( this . tags$ . getValue ( ) . filter ( t => t !== tag ) ) ; } param { number } public toggleNSFW ( value ) { let current = this . nsfw$ . getValue ( ) ; if ( current . indexOf ( value ) > - 1 ) { current = current . filter ( t => t !== value ) ; this . nsfw$ . next ( current ) ; return ; } current . push ( value ) ; this . nsfw$ . next ( current ) ; } returns { boolean } public isContentSaved ( ) { return this . savedContent$ . getValue ( ) === this . content$ . getValue ( ) ; } }