import { DefaultDOMElement, FontAwesomeIcon as Icon, Tool } from 'substance' class CommentBubble extends Tool { render ($$) { if (!this.canCreateComment()) return $$('div') const title = 'Create a new comment' const iconPlus = $$(Icon, { icon: 'fa-plus' }) .addClass('sc-comment-icon-plus') const iconBubble = $$(Icon, { icon: 'fa-comment-o' }) .addClass('sc-comment-icon-bubble') const bubble = $$('div') .attr('title', title) .addClass('sc-overlay-bubble') .addClass('sc-overlay-bubble-hidden') .on('click', this.createComment) .append(iconBubble) .append(iconPlus) return bubble } // reset bubble position on window resize // only works on the horizontal level, as the vertical position gets // calculated relative to the overlay container, which gets positioned // wrong on resize (substance bug -- TODO) didMount () { this.context.editorSession.onUpdate('', this.position, this) this.position() DefaultDOMElement.getBrowserWindow().on('resize', this.didUpdate, this) } didUpdate () { this.position() } dispose () { DefaultDOMElement.getBrowserWindow().off(this) } position () { if (this.el.getChildCount() === 0) return this.setBubblePosition() } setBubblePosition () { // without this check, the editor will break on first load const surface = this.getSurface() if (!surface) return setTimeout(() => { // read comment below let documentElement = document.querySelector('.se-content') let overlayContainer = document.querySelector('.sc-overlay') let fix = 15 if (parseInt(overlayContainer.offsetLeft) === 0) { const minEditorContentPanelChildren = document.getElementById('notes-editor-content-panel').children const temp = minEditorContentPanelChildren[0].children documentElement = temp[0] const children = temp[0].children overlayContainer = children[1] } const documentElementWidth = documentElement.offsetWidth / 1.85 const overlayContainerLeft = overlayContainer.offsetLeft const left = documentElementWidth - overlayContainerLeft - fix // unhide it first, as the bubble has no height otherwise this.el.removeClass('sc-overlay-bubble-hidden') let wsel = window.getSelection() let wrange = wsel.getRangeAt(0) const hints = wrange.getBoundingClientRect() const selectionHeight = hints.height const bubbleHeight = this.el.getHeight() const cheat = 3 const moveUp = (selectionHeight / 2) + (bubbleHeight / 2) + cheat const top = '-' + moveUp + 'px' this.el.css('left', left) this.el.css('top', top) }) /* There is a race condition with overlayContainer's position. If it gets rendered fast enough, this is fine. Otherwise, the overlayContainerLeft variable won't have been updated by substance for us to get the correct value. There is no event letting us know that this has been updated, and it's probably not worth creating a listener. */ } getCommentState () { const { commandManager } = this.context const commandStates = commandManager.getCommandStates() return commandStates.comment } getEditorSession () { return this.context.editorSession } getMode () { const commentState = this.getCommentState() return commentState.mode } getProvider () { return this.context.commentsProvider } getSelection () { const editorSession = this.getEditorSession() return editorSession.getSelection() } // TODO -- get from provider getSurface () { const surfaceManager = this.context.surfaceManager return surfaceManager.getFocusedSurface() } isSelectionLargerThanComments () { const provider = this.getProvider() return provider.isSelectionLargerThanComments() } canCreateComment () { const mode = this.getMode() if (mode === 'create') return true if (!this.isSelectionLargerThanComments()) return false return true } // TODO -- move to provider createComment () { if (!this.canCreateComment()) return const provider = this.getProvider() const selection = this.getSelection() const surface = this.getSurface() const newNode = { selection: selection, type: 'comment', path: selection.path, start: selection.start, end: selection.end } surface.editorSession.transaction((tx, args) => { const annotation = tx.create(newNode) provider.focusTextArea(annotation.id) }) } } export default CommentBubble