import { createAnnotation, DefaultDOMElement, FontAwesomeIcon as Icon, Tool } from 'substance' class CommentBubble extends Tool { render ($$) { const mode = this.getMode() const title = 'Create a new comment' if (mode === 'delete') return $$('div') // console.log('bubble commands', commands.mode) const icon = $$(Icon, { icon: 'fa-comment' }) .addClass('sc-comment-icon') const bubble = $$('div') .attr('title', title) .addClass('sc-overlay-bubble') .addClass('sc-overlay-bubble-hidden') .on('click', this.createComment) .append(icon) 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.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 const documentElement = document.querySelector('.se-content') const overlayContainer = document.querySelector('.sc-overlay-container') setTimeout(() => { // read comment below const documentElementWidth = documentElement.offsetWidth const overlayContainerLeft = overlayContainer.offsetLeft const left = documentElementWidth - overlayContainerLeft - 15 // unhide it first, as the bubble has no height otherwise this.el.removeClass('sc-overlay-bubble-hidden') const hints = surface.getBoundingRectangleForSelection() 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 } getDocumentSession () { return this.context.documentSession } getMode () { const commentState = this.getCommentState() return commentState.mode } getProvider () { return this.context.commentsProvider } getSelection () { const documentSession = this.getDocumentSession() return documentSession.getSelection() } getSurface () { const surfaceManager = this.context.surfaceManager return surfaceManager.getFocusedSurface() } // TODO -- move to provider? createComment () { const mode = this.getMode() const provider = this.getProvider() const selection = this.getSelection() const surface = this.getSurface() if (mode === 'delete') return const newNode = { selection: selection, node: { type: 'comment' } } surface.transaction((tx, args) => { const annotation = createAnnotation(tx, newNode) provider.focusTextArea(annotation.node.id) }) } } export default CommentBubble