Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Editor.js 8.02 KiB
import { includes, some } from 'lodash'

import {
  ProseEditor,
  Toolbar
  // TOCProvider,
} from 'substance'

import Comments from './panes/Comments/CommentBoxList'
import CommentsProvider from './panes/Comments/CommentsProvider'
import ContainerEditor from './ContainerEditor'

import Notes from './panes/Notes/Notes'
import NotesProvider from './panes/Notes/NotesProvider'

// import TableOfContents from './panes/TableOfContents/TableOfContents'
import TrackChangesProvider from './elements/track_change/TrackChangesProvider'

import ModalWarning from './elements/modal_warning/ModalWarning'

class Editor extends ProseEditor {
  constructor (parent, props) {
    super(parent, props)

    this.handleActions({
      // TODO -- clean them up like changesNotSaved
      'trackChangesUpdate': function () { this.updateTrackChange() },
      'trackChangesViewToggle': function () { this.trackChangesViewToggle() },
      'changesNotSaved': this.changesNotSaved,
      'closeModal': this.closeModal
    })
  }

  trackChangesViewToggle () {
    this.extendState({
      trackChangesView: !this.state.trackChangesView
    })
  }

  updateTrackChange () {
    // TODO -- clean up this.props and this.refs
    this.extendProps({ trackChanges: !this.props.trackChanges })
    this.props.updateTrackChangesStatus(!this.props.trackChanges)
    this.refs.toolbar.extendProps({trackChanges: this.props.trackChanges})
  }

  willUpdateState () {}

  didMount () {
    this.extendState({ editorReady: true })
    this.on('noteSelected', this.scrollTo, this)
  }

  render ($$) {
    // const { trackChangesView } = this.state
    // const canToggleTrackChanges = this.canToggleTrackChanges()

    const el = $$('div').addClass('sc-prose-editor')

    // left side: editor and toolbar
    let toolbar = this._renderToolbar($$)
    let toolbarLeft = this._renderToolbarLeft($$)
    let editor = this._renderEditor($$)

    let SplitPane = this.componentRegistry.get('split-pane')
    let ScrollPane = this.componentRegistry.get('scroll-pane')
    let Overlay = this.componentRegistry.get('overlay')

    // TODO -- unnecessary // posssibly breaks book builder dnd
    let ContextMenu = this.componentRegistry.get('context-menu') // new what does it do?
    // let Dropzones = this.componentRegistry.get('dropzones') // new what does it do?

    const notesEditorProps = {
      book: this.props.book,
      comments: this.props.fragment.comments,
      containerId: 'notes',
      history: this.props.history,
      disabled: this.props.disabled,
      fragment: this.props.fragment,
      trackChanges: this.props.trackChanges,
      trackChangesView: this.state.trackChangesView,
      update: this.props.update,
      user: this.props.user
    }
    const footerNotes = $$(Notes, notesEditorProps).ref('footer-notes')

    // const props = {
    //   book: this.props.book,
    //   fragment: this.props.fragment,
    //   history: this.props.history
    // }

    // const toc = $$(TableOfContents, props)

    var commentsPane = this.state.editorReady
    ? $$(Comments, {
      // TODO -- should probably remove the || {}
      comments: this.props.fragment.comments || {},
      fragment: this.props.fragment,
      update: this.props.update,
      user: this.props.user
    }).addClass('sc-comments-pane')
    : $$('div')

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

    const sideNav = $$('div').addClass('sidenav').append(toolbarLeft)

    const contentPanel = $$(ScrollPane, {
      name: 'contentPanel',
      scrollbarPosition: 'right'
    })
    .append(editorWithComments,
      $$(Overlay),
      $$(ContextMenu),
      sideNav
    )
    .attr('id', 'content-panel')
    .ref('contentPanel')

    const contentPanelWithSplitPane = $$(SplitPane, { sizeA: '95%', splitType: 'vertical' })
      .append(
        contentPanel,
        $$('div')
      )

    const ToolbarWithEditor = $$(SplitPane, { splitType: 'horizontal' })
      .append(
        toolbar,
        contentPanelWithSplitPane
      )

    el.append(
      $$(SplitPane, { sizeA: '85%', splitType: 'horizontal' })
        .append(
          ToolbarWithEditor,
          footerNotes
        )
    )

    const modal = $$(ModalWarning, {
      width: 'medium',
      textAlign: 'center'
    })

    if (this.state.changesNotSaved) {
      return el.append(modal)
    }

    return el
  }

  // TODO -- leverage ProseEditor's this._renderToolbar maybe?
  _renderToolbar ($$) {
    let viewMode = this.props.disabled ? $$('span')
        .addClass('view-mode')
        .append('Editor is in Read-Only mode')
        : ''

    let commandStates = this.commandManager.getCommandStates()

    return $$('div')
      .addClass('se-toolbar-wrapper')
      .append(
        $$(Toolbar, {
          commandStates: commandStates,
          trackChanges: this.props.trackChanges,
          trackChangesView: this.state.trackChangesView,
          toolGroups: [
            'document',
            'annotations',
            'track-change-enable',
            'track-change-toggle-view'
          ]
        }).ref('toolbar')
      )
      .append(viewMode)
  }

  _renderToolbarLeft ($$) {
    let commandStates = this.commandManager.getCommandStates()

    return $$('div')
      .addClass('se-toolbar-wrapper')
      .append(
        $$(Toolbar, {
          commandStates: commandStates,
          trackChanges: this.props.trackChanges,
          trackChangesView: this.state.trackChangesView,
          toolGroups: ['text', 'default']
        }
      )
      .ref('toolbarLeft')
    )
  }

  _renderEditor ($$) {
    const configurator = this.props.configurator
    const editing = this.props.disabled ? 'selection' : 'full'
    return $$(ContainerEditor, {
      disabled: this.props.disabled,
      editing: editing,
      editorSession: this.editorSession,
      commands: configurator.getSurfaceCommandNames(),
      containerId: 'body',
      spellcheck: 'native',
      textTypes: configurator.getTextTypes(),
      trackChanges: this.props.trackChanges,
      updateTrackChangesStatus: this.props.updateTrackChangesStatus,
      history: this.props.history,
      book: this.props.book
    }).ref('body')
  }

  getInitialState () {
    return {
      changesNotSaved: false,
      editorReady: false,
      trackChangesView: true
    }
  }

  canToggleTrackChanges () {
    const { user } = this.props
    const accepted = ['admin', 'production-editor', 'copy-editor']
    return some(accepted, (role) => includes(user.roles, role))
  }

  scrollTo (nodeId) {
    this.refs.contentPanel.scrollTo(nodeId)
  }

  // Modal funcionality

  changesNotSaved () {
    this.extendState({ changesNotSaved: true })
  }

  closeModal () {
    this.extendState({ changesNotSaved: false })
  }

  getChildContext () {
    const oldContext = super.getChildContext()
    const doc = this.doc

    // toc provider
    // const tocProvider = new TOCProvider(doc, {
    //   containerId: this.props.containerId
    // })

    // notes provider
    const notesProvider = new NotesProvider(doc, {
      notesEditorContext: ''
    })

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

    // TODO -- do I need all of these?
    // track changes provider
    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,
      notesProvider,
      commentsProvider,
      trackChangesProvider
    }
  }

  dispose () {
    this.editorSession.dragManager.dispose()
  }
}

export default Editor