Untitled

 avatar
unknown
typescript
14 days ago
5.9 kB
6
Indexable
export class PostFormModel {
  postPartsAtom = reatomArray<AtomMut<PostPart>>([])
    .pipe(withReset())
    .pipe(withInit(() => [createPartAtom(generateTextPart()) as AtomMut<PostPart>]))
    .pipe(withSetInterceptor(normalizeParts))

  focusedTextPart = atom<FocusedTextPartValue>(null).pipe(withReset())
  activeSidebar = atom<ActiveSidebarInfo | null>(null).pipe(withReset())
  scheduledDatetime = atom<Date | null>(null).pipe(withReset())

  constructor(post?: CommunityPost) {
    if (post) {
      const segments = splitKeep(post.content, /(\[image]|\[poll]|\[book]|\[bookmark])/g)
      const parts: PostPart[] = []

      let imageIdx = 0
      for (const segment of segments) {
        if (segment === '[book]') {
          parts.push(generateBookPart(post.book_meta))
        } else if (segment === '[bookmark]') {
          parts.push(generateBookmarkPart(post.bookmark_meta))
        } else if (segment === '[poll]') {
          parts.push(generatePollPart(new PollFormModel({
            title: post.poll_meta.title,
            multipleChoices: post.poll_meta.multiple,
            choices: post.poll_meta.options.map(opt => opt.option),
          })))
        } else if (segment === '[image]') {
          // parts.push(atom(generateImagesPart(post.images[imageIdx])))
          imageIdx++
        } else {
          parts.push(generateTextPart(segment.trim().replaceAll('\r\n', '\n')))
        }
      }

      this.postPartsAtom.change(ctx, parts.map(part => createPartAtom(part)))
    }
  }

  resetAll = action((ctx) => {
    this.postPartsAtom.reset(ctx)
    this.focusedTextPart.reset(ctx)
    this.activeSidebar.reset(ctx)
    this.scheduledDatetime.reset(ctx)
  })

  setFocusedTextAtom = action((ctx, part: AtomMut<TextPart>, ref: HTMLTextAreaElement | null) => {
    this.focusedTextPart(ctx, [part, ref])
  })

  unsetFocusedTextAtom = action((ctx) => {
    this.focusedTextPart(ctx, null)
  })

  insertMedia = action((ctx, part: PostPart, focusedText: FocusedTextPartValue) => {
    const newAtoms = [createPartAtom(part)]
    const parts = ctx.get(this.postPartsAtom)

    if (focusedText) {
      const [focusedTextAtom, focusedTextRef] = focusedText
      const focusedTextAtomIdx = parts.findIndex(item => item === focusedTextAtom)
      const focusPosition = focusedTextRef?.selectionStart

      if (focusedTextAtomIdx > -1 && focusPosition !== undefined) {
        const textValue = ctx.get(focusedTextAtom)
        const [before, after] = splitInPosition(textValue.content, focusPosition)

        before.trim().length && newAtoms.unshift(createPartAtom(generateTextPart(before.trim())) as AtomMut<PostPart>)
        after.trim().length && newAtoms.push(createPartAtom(generateTextPart(after.trim())) as AtomMut<PostPart>)
      }

      this.postPartsAtom.change(ctx, [
        ...parts.slice(0, focusedTextAtomIdx),
        ...newAtoms,
        ...parts.slice(focusedTextAtomIdx + 1),
      ])
      return
    }

    this.postPartsAtom.change(ctx, [...parts, ...newAtoms])
  })

  insertPart = action((ctx, {atom, i}: { atom: AtomMut<PostPart>, i: number }) => {
    this.postPartsAtom.change(ctx, v => [...v.slice(0, i), atom, ...v.slice(i)])
  })

  addPart = action((ctx, part: PostPart) => {
    const newAtoms = [createPartAtom(part)]

    if (part.type !== 'text') {
      newAtoms.push(createPartAtom(generateTextPart('')) as AtomMut<PostPart>)
    }

    this.postPartsAtom.change(ctx, v => [...v, ...newAtoms])
  })

  removePart = action((ctx, id: string) => {
    const partsValue = ctx.get(this.postPartsAtom)
    const removingI = partsValue.findIndex(atom => ctx.get(atom).id === id)
    this.postPartsAtom.change(ctx, [...partsValue.slice(0, removingI), ...partsValue.slice(removingI + 1)])
  })

  rearrangeParts = action(ctx => {
    this.postPartsAtom.change(ctx, v => v)
  })

  getSerialised() {
    const totalImages: File[] = []
    let totalText = ''
    let poll: PollForm | undefined
    let bookId: Book["id"] | undefined
    let bookmarkId: Bookmark["id"] | undefined

    const partsValue = ctx.get(this.postPartsAtom)

    for (const partAtom of partsValue) {
      const partValue = ctx.get(partAtom)

      switch (partValue.type) {
        case 'text':
          totalText += partValue.content
          break
        case 'images':
          totalText += partValue.images.map(() => '[image]').join('')
          totalImages.push(...partValue.images.map(item => item.file))
          break
        case 'book':
          totalText += '[book]'
          bookId = partValue.book.id
          break
        case 'audio_bookmark':
          totalText += '[bookmark]'
          bookmarkId = partValue.bookmark.id
          break
        case 'poll':
          poll = partValue.poll.toObject()
          break
      }
    }

    return {
      totalText,
      totalImages,
      bookId,
      bookmarkId,
      poll,
      publicationDatetime: ctx.get(this.scheduledDatetime),
    }
  }

  symbolsCount = atom(ctx => ctx.spy(this.postPartsAtom)
    .reduce((count, partAtom) => {
      const part = ctx.spy(partAtom)

      return part.type === 'text'
        ? count + part.content?.length
        : count
    }, 0))

  books = atom(ctx => ctx.spy(this.postPartsAtom)
    .map(partAtom => ctx.get(partAtom))
    .filter(part => part?.type === 'book'))

  bookmarks = atom(ctx => ctx.spy(this.postPartsAtom)
    .map(partAtom => ctx.get(partAtom))
    .filter(part => part?.type === 'audio_bookmark'))

  isEmpty = atom(ctx => ctx.spy(this.postPartsAtom)
    .map(partAtom => ctx.spy(partAtom))
    .find(part => {
      if (part.type === 'text') return part.content.trim().length
      if (part.type === 'images') return part.images.length
      return true
    }) === undefined)

  lastPostPart = atom<PostPart | undefined>(ctx => {
    const lastAtom = ctx.spy(this.postPartsAtom).at(-1)
    return lastAtom ? ctx.spy(lastAtom) : undefined
  })
}
Editor is loading...
Leave a Comment