Skip to content
Snippets Groups Projects
NotesEditor.js 7.23 KiB
Newer Older
import {
  ProseEditor,
  Toolbar
} from 'substance'

import ContainerEditor from '../ContainerEditor'
import Comments from '../panes/Comments/CommentBoxList'
import CommentsProvider from '../panes/Comments/CommentsProvider'
import TrackChangesProvider from '../elements/track_change/TrackChangesProvider'
chris's avatar
chris committed
import SimpleExporter from '../SimpleEditorExporter'
chris's avatar
chris committed
import _ from 'lodash'
chris's avatar
chris committed
class NotesEditor extends ProseEditor {
    const provider = this.getProvider()
chris's avatar
chris committed
    provider.config.notesEditorContext = this.getChildContext()
chris's avatar
chris committed
    this.editorSession.onUpdate('document', this.findNote, this)
    this.on('noteSelected', this.scrollTo, this)
chris's avatar
chris committed
    this.on('notes:inserted', this.createNote, this)
    this.on('notes:deleted', this.removeNote, this)
chris's avatar
chris committed
    const el = $$('div').addClass('sc-notes-editor')
    let toolbar = this._renderNotesToolbar($$)
    let editor = this._renderEditor($$)
    let SplitPane = this.componentRegistry.get('split-pane')
    let ScrollPane = this.componentRegistry.get('scroll-pane')
    let Overlay = this.componentRegistry.get('overlay')
    var commentsPane = $$(Comments, {
chris's avatar
chris committed
      comments: this.props.comments,
      fragment: this.props.fragment,
      update: this.props.update,
      user: this.props.user
    }).addClass('sc-comments-pane')

    const editorWithComments = $$(SplitPane, { sizeA: '100%', splitType: 'vertical' })
      .append(
        editor,
        commentsPane
      )

    const contentPanel = $$(ScrollPane, {
chris's avatar
chris committed
      name: 'notesEditorContentPanel',
      scrollbarPosition: 'right'
    })
      .append(editorWithComments, $$(Overlay))
chris's avatar
chris committed
      .attr('id', 'notes-editor-content-panel')
      .ref('notesEditorContentPanel')

    el.append(
      $$(SplitPane, { splitType: 'horizontal' })
        .append(toolbar, contentPanel)
    )

    return el
  }

chris's avatar
chris committed
  _renderNotesToolbar ($$) {
    let commandStates = this.commandManager.getCommandStates()
    return $$('div').addClass('se-toolbar-wrapper').append(
      $$(Toolbar, {
        commandStates: commandStates,
chris's avatar
chris committed
        toolGroups: ['annotations']
chris's avatar
chris committed
      }).ref('notes_toolbar')
    )
  }

  _renderEditor ($$) {
    return $$(ContainerEditor, {
      book: this.props.book,
      comments: this.props.comments,
      containerId: this.props.containerId,
chris's avatar
chris committed
      configurator: this.props.configurator,
      editorSession: this.editorSession,
      disabled: this.props.disabled,
      history: this.props.history,
      fragment: this.props.fragment,
chris's avatar
chris committed
      spellcheck: 'native',
      trackChanges: this.props.trackChanges,
      user: this.props.user
chris's avatar
chris committed
    }).ref('notes_body')
chris's avatar
chris committed
    const nodes = this.getIsolatedNodes()
    const note = _.find(nodes, function (c) {
      return c.parentNoteId === nodeId
    })

chris's avatar
chris committed
    this.refs.notesEditorContentPanel.scrollTo(note.id)
chris's avatar
chris committed
  createNodeData (note) {
    return {
      'type': 'isolated-note',
chris's avatar
chris committed
      'content': note['note-content'],
chris's avatar
chris committed
      'parentNoteId': note.id
chris's avatar
chris committed
  createNote (noteId) {
    const provider = this.getProvider()
    const notes = provider.computeEntries()
    const surface = this.getSurface()
    const container = surface.getContainer()
chris's avatar
chris committed
    const path = container.getContentPath()
chris's avatar
chris committed
    let note = _.find(notes, function (c) {
      return c.id === noteId
    })

    let findIndex = _.findIndex(notes, ['id', note.id])
    let nodeData = this.createNodeData(note)

    this.editorSession.transaction(function (tx) {
      let blockNode = tx.create(nodeData)
chris's avatar
chris committed
      tx.update(path, { type: 'insert', pos: findIndex, value: blockNode.id })
chris's avatar
chris committed
    })

    const existingNodes = this.getIsolatedNodes()
    let isolatedNote = _.find(existingNodes, function (c) {
      return c.parentNoteId === noteId
    })
    this.saveNote(isolatedNote)
chris's avatar
chris committed
  removeNote (noteId) {
    const existingNodes = this.getIsolatedNodes()
    const surface = this.getSurface()
    const container = surface.getContainer()
chris's avatar
chris committed
    const path = container.getContentPath()
chris's avatar
chris committed

    let note = _.find(existingNodes, function (c) {
      return c.parentNoteId === noteId
    })

    // if Isolated note is focused cannot be deleted. Remove focus
    // before deleting callout
    this.editorSession.setSelection(null)

    // Delete comments from the isolated note before
    // removing the note
    this.removeCommentsFirst(note.id)

    let findIndex = _.findIndex(existingNodes, ['parentNoteId', noteId])

    this.editorSession.transaction(function (tx) {
chris's avatar
chris committed
      tx.update(path, { type: 'delete', pos: findIndex })
chris's avatar
chris committed
      tx.delete(note.id)
    })
chris's avatar
chris committed
  saveNote (isolatedNote) {
    // If isloated note has no content and you keep pressing backspace,
    // it gets deleted. Set selection to null to prevent that
chris's avatar
chris committed
    const selection = this.editorSession.getSelection()
chris's avatar
chris committed
    if (selection.start && selection.start.offset === 0 && selection.end.offset === 0) {
chris's avatar
chris committed
      this.editorSession.setSelection(null)
    }

chris's avatar
chris committed
    const exporter = new SimpleExporter(this.props.configurator.config)
    const convertedNode = exporter.convertNode(isolatedNote)
    this.context.editorSession.transaction(function (tx, args) {
      const path = [isolatedNote.parentNoteId, 'note-content']
chris's avatar
chris committed
      tx.set(path, convertedNode.outerHTML)
    })
  }

  findNote () {
    const selection = this.editorSession.getSelection()
    if (!selection.end) return

    const isolatedNoteId = selection.end.path[0]
    const isolatedNote = this.editorSession.document.get(isolatedNoteId)
    return this.saveNote(isolatedNote)
  }

  getIsolatedNodes () {
    const doc = this.editorSession.document
    const nodes = doc.getNodes()
    const entries = _.pickBy(nodes, function (value, key) {
      return value.type === 'isolated-note'
    })
    return _.values(entries)
  }

  removeCommentsFirst (noteId) {
    const doc = this.editorSession.document
    const nodes = doc.getNodes()

    const entries = _.pickBy(nodes, function (value, key) {
      return value.type === 'comment'
chris's avatar
chris committed
    })
chris's avatar
chris committed

    if (!entries) return

    _.forEach(entries, function (node, key) {
      if (node.path[0] === noteId) {
        this.editorSession.transaction(function (tx) {
          tx.delete(node.id)
        })
      }
    }.bind(this))
chris's avatar
chris committed
  }

  getProvider () {
    return this.context.notesProvider
  }

  getSurface () {
    const containerId = this.props.containerId
    const provider = this.getProvider()
chris's avatar
chris committed
    return provider.config.notesEditorContext.surfaceManager.getSurface(containerId)
  getChildContext () {
    const oldContext = super.getChildContext()
    const doc = this.doc

    // comments provider
    const commentsProvider = new CommentsProvider(doc, {
      commandManager: this.commandManager,
      comments: this.props.fragment.comments,
      containerId: this.props.containerId,
      controller: this,
      editorSession: this.editorSession,
      fragment: this.props.fragment,
      surfaceManager: this.surfaceManager,
      update: this.props.update
    })

    const trackChangesProvider = new TrackChangesProvider(doc, {
      commandManager: this.commandManager,
      containerId: this.props.containerId,
      controller: this,
      editorSession: this.editorSession,
      surfaceManager: this.surfaceManager,
      user: this.props.user
    })

    // attach all to context
    return {
      ...oldContext,
      commentsProvider,
      trackChangesProvider
chris's avatar
chris committed
export default NotesEditor