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' import SimpleExporter from '../SimpleEditorExporter' import _ from 'lodash' class NotesEditor extends ProseEditor { didMount () { const provider = this.getProvider() provider.config.notesEditorContext = this.getChildContext() this.editorSession.onUpdate('document', this.findNote, this) this.on('noteSelected', this.scrollTo, this) this.on('notes:inserted', this.createNote, this) this.on('notes:deleted', this.removeNote, this) } getInitialState () { return { trackChangesView: this.props.trackChangesView } } render ($$) { 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, { comments: this.props.comments, fragment: this.props.fragment, update: this.props.update, user: this.props.user }).addClass('sc-comments-pane') const editorWithComments = $$(SplitPane, { sizeA: '80%', splitType: 'vertical' }) .append( editor, commentsPane ) const contentPanel = $$(ScrollPane, { name: 'notesEditorContentPanel', scrollbarPosition: 'right' }) .append(editorWithComments, $$(Overlay)) .attr('id', 'notes-editor-content-panel') .ref('notesEditorContentPanel') el.append( $$(SplitPane, { splitType: 'horizontal' }) .append(toolbar, contentPanel) ) return el } _renderNotesToolbar ($$) { let commandStates = this.commandManager.getCommandStates() return $$('div').addClass('se-toolbar-wrapper').append( $$(Toolbar, { commandStates: commandStates, toolGroups: ['annotations'] }).ref('notes_toolbar') ) } _renderEditor ($$) { return $$(ContainerEditor, { book: this.props.book, comments: this.props.comments, containerId: this.props.containerId, configurator: this.props.configurator, editorSession: this.editorSession, disabled: this.props.disabled, history: this.props.history, fragment: this.props.fragment, spellcheck: 'native', trackChanges: this.props.trackChanges, trackChangesView: this.props.trackChangesView, user: this.props.user }).ref('notes_body') } scrollTo (nodeId) { const nodes = this.getIsolatedNodes() const note = _.find(nodes, function (c) { return c.parentNoteId === nodeId }) this.refs.notesEditorContentPanel.scrollTo(note.id) } createNodeData (note) { return { 'type': 'isolated-note', 'content': note['note-content'], 'parentNoteId': note.id } } createNote (noteId) { const provider = this.getProvider() const notes = provider.computeEntries() const surface = this.getSurface() const container = surface.getContainer() const path = container.getContentPath() 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) tx.update(path, { type: 'insert', pos: findIndex, value: blockNode.id }) }) this.scrollTo(note.id) } removeNote (noteId) { const existingNodes = this.getIsolatedNodes() const surface = this.getSurface() const container = surface.getContainer() const path = container.getContentPath() 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) { tx.update(path, { type: 'delete', pos: findIndex }) tx.delete(note.id) }) } saveNote (isolatedNote) { // If isloated note has no content and you keep pressing backspace, // it gets deleted. Set selection to null to prevent that const selection = this.editorSession.getSelection() if (selection.start && selection.start.offset === 0 && selection.end.offset === 0) { this.editorSession.setSelection(null) this.context.editorSession.setSelection(null) } 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'] tx.set(path, convertedNode.innerHTML) }) } 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' }) if (!entries) return _.forEach(entries, function (node, key) { if (node.path[0] === noteId) { this.editorSession.transaction(function (tx) { tx.delete(node.id) }) } }.bind(this)) } getProvider () { return this.context.notesProvider } getSurface () { const containerId = this.props.containerId return this.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 } } } export default NotesEditor