obsidian-micropub/src/views/PublishViewModel.ts

246 lines
7.1 KiB
TypeScript

import { NetworkRequestFactoryInterface } from '@networking/NetworkRequestFactory'
import { NetworkClientInterface } from '@networking/NetworkClient'
import { PublishResponse } from '@networking/PublishResponse'
import { TagSuggestionDelegate, TagSuggestionViewModel } from '@views/TagSuggestionViewModel'
import { ViewModelFactoryInterface } from '@factories/ViewModelFactory'
import { Editor } from 'obsidian'
import { ServiceFactoryInterface } from '@base/factories/ServiceFactory'
import { FrontmatterService, FrontmatterServiceInterface } from '@base/services/FrontmatterService'
/*
* Publish View Delegate Interface, implemented by
* the object which needs to observe events from the view model.
*/
export interface PublishViewModelDelegate {
// Triggered when user clicks the delete button when the
// title property is reset.
publishDidClearTitle(): void
// triggered when user clicks the clear button when the
// date property is reset.
publishDidClearDate(): void
// Triggered when publishing a new post succeeds.
publishDidSucceed(response: PublishResponse): void
// Triggered when publishing a new post fails.
publishDidFail(error: Error): void
// Triggered when selecting a tag from the picker.
publishDidSelectTag(): void
// Triggered after checking whether the scheduled date
// is valid or not. It returns `true` for no date or for
// valid date, and false for invalid dates.
publishDidValidateDate(): void
}
/*
* This view model drives the content and interactions with the
* publish view.
*/
export class PublishViewModel implements TagSuggestionDelegate {
// Properties
public delegate?: PublishViewModelDelegate
private isValidDate: boolean
private isSubmitting: boolean
private titleWrappedValue: string
private content: string
private visibilityWrappedValue: string
private tagsWrappedValue: string
private selectedBlogIDWrappedValue: string
private scheduledDateWrappedValue: string
private networkClient: NetworkClientInterface
private networkRequestFactory: NetworkRequestFactoryInterface
private viewModelFactory: ViewModelFactoryInterface
private frontmatterService: FrontmatterServiceInterface
private editor: Editor
readonly blogs: Record<string, string>
// Life cycle
constructor(
title: string,
content: string,
tags: string,
visibility: string,
blogs: Record<string, string>,
selectedBlogID: string,
networkClient: NetworkClientInterface,
networkRequestFactory: NetworkRequestFactoryInterface,
viewModelFactory: ViewModelFactoryInterface,
frontmatterService: FrontmatterServiceInterface,
editor: Editor
) {
this.titleWrappedValue = title
this.content = content
this.tagsWrappedValue = tags
this.visibilityWrappedValue = visibility
this.blogs = blogs
this.selectedBlogIDWrappedValue = selectedBlogID
this.scheduledDateWrappedValue = ''
this.isValidDate = true
this.isSubmitting = false
this.networkClient = networkClient
this.networkRequestFactory = networkRequestFactory
this.viewModelFactory = viewModelFactory
this.frontmatterService = frontmatterService
this.editor = editor
}
// Public
public get title(): string {
return this.titleWrappedValue
}
public set title(value: string) {
this.titleWrappedValue = value
}
public get tags(): string {
return this.tagsWrappedValue
}
public set tags(value: string) {
this.tagsWrappedValue = value
}
public get visibility(): string {
return this.visibilityWrappedValue
}
public set visibility(value: string) {
this.visibilityWrappedValue = value
}
public get hasMultipleBlogs(): boolean {
return Object.keys(this.blogs).length > 2
}
public get selectedBlogID(): string {
return this.selectedBlogIDWrappedValue
}
public set selectedBlogID(value: string) {
this.selectedBlogIDWrappedValue = value
}
public get scheduledDate(): string {
return this.scheduledDateWrappedValue
}
public set scheduledDate(value: string) {
this.scheduledDateWrappedValue = value
}
public get showPublishingButton(): boolean {
return this.isValidDate && this.isSubmitting
}
public get invalidDateText(): string {
return this.isValidDate ? "" : "Invalid date format"
}
public async publishNote() {
if (!this.isValidScheduledDate()) {
this.isValidDate = false
this.isSubmitting = false
this.delegate?.publishDidValidateDate()
return
}
this.isValidDate = true
this.isSubmitting = true
this.delegate?.publishDidValidateDate()
try {
const request = this.networkRequestFactory.makePublishRequest(
this.title,
this.content,
this.tags,
this.visibility,
this.selectedBlogID,
this.formattedScheduledDate()
)
const response: PublishResponse = await this.networkClient.publish(
request
)
this.frontmatterService.updateFrontmatter(response, this.editor)
this.delegate?.publishDidSucceed(response)
} catch (error) {
this.delegate?.publishDidFail(error)
}
}
public clearTitle() {
this.title = ''
this.delegate?.publishDidClearTitle()
}
public suggestionsViewModel(): TagSuggestionViewModel {
const excluding = this.tags
.split(',')
.filter(value => value.length > 0)
.map(tag => tag.trim())
return this.viewModelFactory.makeTagSuggestionViewModel(
excluding,
this
)
}
public clearDate() {
this.scheduledDateWrappedValue = ''
this.isValidDate = true
this.delegate?.publishDidClearDate()
}
// Private
private isValidScheduledDate(): boolean {
const scheduledDate = new Date(this.scheduledDateWrappedValue)
const isInvalidDate = isNaN(scheduledDate.getTime())
if (this.scheduledDateWrappedValue.length > 0 && isInvalidDate) {
return false
}
return true
}
private formattedScheduledDate(): string {
const scheduledDate = new Date(this.scheduledDateWrappedValue.trim())
const isInvalidDate = isNaN(scheduledDate.getTime())
if (isInvalidDate) {
return ''
}
return scheduledDate.toISOString()
}
// TagSuggestionDelegate
public tagSuggestionDidSelectTag(category: string) {
const tags = this.tags
.split(',')
.filter(value => value.length > 0)
.map(tag => tag.trim())
tags.push(category)
const formattedTags = tags
.filter((tag, index) => index === tags.indexOf(tag))
.join()
this.tags = formattedTags
this.delegate?.publishDidSelectTag()
}
}