panter/mykonote

View on GitHub
client/src/NoteEdit.svelte

Summary

Maintainability
Test Coverage
{#if note}
  <NoteActionBar
    {showList}
    {isSynced}
    on:showListClicked={handleShowListClicked}
    on:newClicked={handleNewNoteClicked} />
  <div class="flex one two-900">
    <div class="full third-900 fourth-1200">
      <NoteList
        activeNoteUid={note.uid}
        {isSynced}
        {showList}
        {listNeedsUpdate}
        {collection}
        on:noteClick={handleNoteClick}
        on:deleteNote={handleDeleteNoteClick}
        on:archiveNote={handleArchiveNoteClick} />
    </div>
    <div class="full two-third-900 three-fourth-1200 padding-left-xl">
      <NoteForm
        {note}
        on:change={handleEditChange}
        showForm={!showList} />
    </div>
  </div>

  <Dialog
    title='Archive'
    text='Are you sure you want to archive this note?'
    show={showArchiveDialog}
    on:confirm={handleArchiveDialogConfirmed} />

  <Dialog
    title='Delete'
    text='Are you sure you want to delete this note?'
    show={showDeleteDialog}
    on:confirm={handleDeleteDialogConfirmed} />
{/if}

<script>
  import { onMount, beforeUpdate, onDestroy, tick } from 'svelte'
  import { querystring } from 'svelte-spa-router'
  import { ajax } from './ajax'
  import { show } from './flash'
  import SyncStorage from './SyncStorage'
  import { setEdit, setNew, setBrowserTitle } from './pushState'
  import { searchTerm, notes } from './stores'
  import { resize } from './image'
  import { getBlob, getJson } from './cache'
  import Note from './Note'
  import EventHive from './EventHive'
  import AutoSave from './AutoSave'
  import NoteForm from './NoteForm.svelte'
  import NoteList from './NoteList.svelte'
  import NoteActionBar from './NoteActionBar.svelte'
  import Dialog from './Dialog.svelte'
  import './keyboard'

  export let params = {}
  export let collection

  let note
  let showList
  let autoSave
  let isSynced
  let listNeedsUpdate
  let noteNewSubscription
  let searchEnteredSubscription
  let searchClearedSubscription
  let showDeleteDialog
  let handleDeleteDialogConfirmed
  let showArchiveDialog
  let handleArchiveDialogConfirmed

  $: { $querystring; initializefromShareTarget() }
  $: { params; initializefromParam() }

  const initializefromShareTarget = async () => {
    if ($querystring.includes('share-target')) {
      note = new Note()

      const blob = await getBlob('shared-image')
      const json = await getJson('shared-data')
      if (blob || json) {
        if (blob) {
          const { dataUrl, width, height } = await resize(blob)

          show('notice', `The image was scaled to ${width} × ${height} px`)

          note.content = `<img src="${dataUrl}" />`
        } else {
          note.title = json.title
          note.content = [json.text, json.url].filter(x => x).join('<br/>')
        }

        showList = false
        setEdit(note)
        autoSave.setChange(note)
      }
    }
  }

  const initializefromParam = async () => {
    if (params.id && note?.uid !== params.id) {
      // 1. try sync storage
      note = Note.fromAttributes(SyncStorage.getJson({ uid: params.id }))
      if (!note) {
        // 2. try svelte store
        note = $notes.find(note => note.uid === params.id)
        if (!note) {
          // 3. try server
          await initStateFromNoteId(params.id)
        }
      }
      showList = false
    }
    else if (!params.id) {
      note = new Note()
    }
  }

  onMount(async () => {
    autoSave = new AutoSave(handleServerSync)
    autoSave.startPolling()

    noteNewSubscription = EventHive.subscribe('note.new', (data) => {
      setNewNote(() => note = new Note())
    })

    await tick()
  })

  beforeUpdate(async () => {
    if (note === undefined) {
      note = new Note()
      showList = true
    }

    setBrowserTitle(note)
  })

  onDestroy(() => {
    noteNewSubscription.remove()
    autoSave.stopPolling()
  })

  $: if ($searchTerm !== undefined) showList = true

  const initStateFromNoteId = async (id) => {
    showList = false

    try {
      const data = await ajax(`/api/notes/${id}`)
      note = Note.fromAttributes(data.note)
    } catch (error) {
      setNew()
      show('alert',
        'While trying to load the note the internet broke down (or something ' +
          'else failed, maybe the note could not be found)'
      )
    }
  }

  const handleNoteClick = (event) => {
    event.preventDefault()

    showList = false
    setEdit(event.detail)
  }

  const handleDeleteNoteClick = (e) => {
    const clickedNote = e.detail

    const handler = (event) => {
      showDeleteDialog = false

      if (event.detail) {
        isSynced = false
        deleteNote(clickedNote)
      }
    }
    showDeleteDialog = true
    handleDeleteDialogConfirmed = handler
  }

  const handleArchiveNoteClick = (e) => {
    const clickedNote = e.detail

    const handler = (event) => {
      showArchiveDialog = false

      if (event.detail) {
        clickedNote.setArchived()
        autoSave.setChange(clickedNote)
        setNewNote(() => showList = true)
      }
    }
    showArchiveDialog = true
    handleArchiveDialogConfirmed = handler
  }

  const handleNewNoteClicked = () => {
    setNewNote(() => note = new Note())
  }

  const handleShowListClicked = () => {
    setNewNote(() => showList = true)
  }

  const handleEditChange = (event) => {
    const updatedNote = event.detail
    setEdit(updatedNote)

    autoSave.setChange(updatedNote)
  }

  const handleServerSync = (data) => {
    isSynced = data.isSynced
    listNeedsUpdate = true
    // when a note has been synced we need to set the serverUpdatedAt timestamp
    // for the conflict detection to work
    // (only do this if the current note is the synced note)
    if (data.note && note.uid === data.note.uid) {
      note.serverUpdatedAt = data.note.serverUpdatedAt
    }
  }

  const setNewNote = (callback) => {
    showList = false
    if (callback) callback()
    setNew()
  }

  const deleteNote = (affectedNote) => {
    ajax(`/api/notes/${affectedNote.uid}`, 'DELETE')
      .then((data) => {
        SyncStorage.remove(affectedNote)

        if (note.uid === affectedNote.uid) {
          setNewNote()
        }

        isSynced = true
        listNeedsUpdate = true
      })
      .catch((error) => {
        let message = 'Oh my, the note could not be deleted.'
        if (!window.navigator.onLine) {
          message += "<br>Please check your internet connection (Apologies, deleting in offline mode is not yet suppported)."
        }
        show('alert', message)
        console.error('note.uid: ', note.uid, 'error: ', error.toString())

        isSynced = true
        listNeedsUpdate = false
      })
  }
</script>