diff --git a/app/components/Navigation/Navigation.jsx b/app/components/Navigation/Navigation.jsx index 013e3aad55b467238e8e965e874fc5e6da0fab70..a189d45c7a97ba67b3c71e7b497642bf327eb5b3 100644 --- a/app/components/Navigation/Navigation.jsx +++ b/app/components/Navigation/Navigation.jsx @@ -6,10 +6,22 @@ import { Navbar, Nav, NavItem, NavbarBrand } from 'react-bootstrap' import Authorize from 'pubsweet-client/src/helpers/Authorize' import NavbarUser from 'pubsweet-component-navigation/NavbarUser' +// TODO -- break into smaller components export default class Navigation extends React.Component { constructor (props) { super(props) this.logout = this.logout.bind(this) + + // rewrite cleaner + // should the manage component maybe pass the location prop? + browserHistory.listen(function (event) { + this.collectionId = '' + this.inEditor = event.pathname.match(/fragments/g) + if (this.inEditor) { + let pathnameSplited = event.pathname.split('/') + this.collectionId = pathnameSplited[2] + } + }.bind(this)) } logout () { @@ -31,6 +43,15 @@ export default class Navigation extends React.Component { ) } + let BackToBooks + if (this.inEditor) { + BackToBooks = ( + <LinkContainer to={'/books/' + this.collectionId + '/book-builder'}> + <NavItem>Back To book</NavItem> + </LinkContainer> + ) + } + // TODO -- fix object properties underneath return ( <Navbar fluid> @@ -59,6 +80,9 @@ export default class Navigation extends React.Component { <NavItem>Teams</NavItem> </LinkContainer> </Authorize> + + {BackToBooks} + </Nav> { logoutButtonIfAuthenticated } diff --git a/app/components/SimpleEditor/ContainerEditor.js b/app/components/SimpleEditor/ContainerEditor.js index ad3241bda9012258f818ad6c8c47554a0fb0bcd6..123021f66d69a3692f69eb0b0a555b439feb6e0e 100644 --- a/app/components/SimpleEditor/ContainerEditor.js +++ b/app/components/SimpleEditor/ContainerEditor.js @@ -1,8 +1,6 @@ import { each, keys, includes } from 'lodash' import { ContainerEditor as SubstanceContainerEditor, - // deleteCharacter, - // deleteSelection, keys as keyboardKeys, uuid } from 'substance' @@ -35,11 +33,14 @@ class ContainerEditor extends SubstanceContainerEditor { didMount () { super.didMount() - if (this.isEmpty()) this.createText() - - // TODO -- why this and not this.focus ? - this.el.focus() + // TODO -- why are we checking for disabled as well? + if (this.isEmpty() && !this.props.disabled) { + this.createText() + // TODO -- why this and not this.focus ? + this.el.focus() + } + // Check is this is working properly as the isReadOnlyMode is unstable if (this.isReadOnlyMode()) { this.editorSession.onUpdate('', this.disableToolbar, this) this.addTargetToLinks() @@ -48,9 +49,8 @@ class ContainerEditor extends SubstanceContainerEditor { // TODO -- this.props.history is deprecated and gives a warning if (this.props.history) { this.props.history.listenBefore((location, callback) => { - const commandStates = this.getCommandStates() - - if (commandStates['save'].disabled === false) { + // TODO -- body is hardcoded here + if (this.props.containerId === 'body' && this.editorSession.hasUnsavedChanges()) { const editor = this.getEditor() editor.send('changesNotSaved') @@ -63,26 +63,32 @@ class ContainerEditor extends SubstanceContainerEditor { }) } - window.history.pushState(null, null, document.URL) + // window.history.pushState(null, null, document.URL) window.addEventListener('popstate', this.controlBackButton) } // TODO -- review // messes up browser history controlBackButton () { - const commandStates = this.getCommandStates() + // TODO -- why are we pushing this url? + // it is not necessary that that's where the user wants to go const url = '/books/' + this.props.book.id + '/book-builder' window.removeEventListener('popstate', this.controlBackButton) - if (commandStates['save'].disabled === false) { + // TODO -- body is hardcoded here + if (this.props.containerId === 'body' && this.editorSession.hasUnsavedChanges()) { const editor = this.getEditor() window.history.pushState(null, null, document.URL) editor.send('changesNotSaved') - editor.emit('send:route', {location: url, back: true}) + + editor.emit('send:route', { + back: true, + location: url + }) } else { - this.props.history.goBack() + this.props.history.push(url) } } @@ -201,8 +207,8 @@ class ContainerEditor extends SubstanceContainerEditor { newSel = tx.createSelection({ type: 'property', // TODO -- both id's ?? - containerId: 'body', - surfaceId: 'body', + containerId: this.props.containerId, + surfaceId: this.props.containerId, path: [ node.id, 'content' ], startOffset: 0, endOffset: 0 @@ -219,7 +225,7 @@ class ContainerEditor extends SubstanceContainerEditor { const commandStates = this.getCommandStates() each(keys(commandStates), key => { - const allowed = ['comment', 'note', 'save', 'undo', 'redo', 'track-change-toggle-view'] + const allowed = ['comment', 'save', 'undo', 'redo', 'track-change-toggle-view'] if (!includes(allowed, key)) commandStates[key].disabled = true }) } diff --git a/app/components/SimpleEditor/Editor.js b/app/components/SimpleEditor/Editor.js index 523bb0ab71757035c67a41652e2e51f529124e9a..cfbc452fa1d7d55ba819eace4b1e177318aae84d 100644 --- a/app/components/SimpleEditor/Editor.js +++ b/app/components/SimpleEditor/Editor.js @@ -1,8 +1,7 @@ -import { includes, some } from 'lodash' +import { includes, some, isEmpty } from 'lodash' import { ProseEditor, - TOCProvider, Toolbar } from 'substance' @@ -13,7 +12,6 @@ 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' @@ -23,13 +21,9 @@ class Editor extends ProseEditor { super(parent, props) this.handleActions({ - 'showComments': function () { this.toggleCommentsArea(true) }, - 'hideComments': function () { this.toggleCommentsArea(false) }, - // TODO -- clean them up like changesNotSaved 'trackChangesUpdate': function () { this.updateTrackChange() }, 'trackChangesViewToggle': function () { this.trackChangesViewToggle() }, - // 'changesNotSaved': function () { this.changesNotSaved() } 'changesNotSaved': this.changesNotSaved, 'closeModal': this.closeModal }) @@ -52,8 +46,12 @@ class Editor extends ProseEditor { didMount () { this.extendState({ editorReady: true }) + + // TODO -- if not needed, delete + // this.on('noteSelected', this.scrollTo, this) } + // TODO - break this all up into smaller pieces render ($$) { // const { trackChangesView } = this.state // const canToggleTrackChanges = this.canToggleTrackChanges() @@ -73,21 +71,18 @@ class Editor extends ProseEditor { let ContextMenu = this.componentRegistry.get('context-menu') // new what does it do? // let Dropzones = this.componentRegistry.get('dropzones') // new what does it do? - const footerNotes = $$(Notes) - - const props = { + const footerNotes = $$(Notes, { book: this.props.book, + comments: this.props.fragment.comments, + containerId: 'notes', + history: this.props.history, + disabled: this.props.disabled, fragment: this.props.fragment, - history: this.props.history - } - - const toc = $$(TableOfContents, props) - - var editorWithFooter = $$('div') - .append( - editor, - footerNotes - ) + trackChanges: this.props.trackChanges, + trackChangesView: this.state.trackChangesView, + update: this.props.update, + user: this.props.user + }).ref('footer-notes') var commentsPane = this.state.editorReady ? $$(Comments, { @@ -99,39 +94,43 @@ class Editor extends ProseEditor { }).addClass('sc-comments-pane') : $$('div') - var editorWithComments = $$(SplitPane, { sizeA: '100%', splitType: 'vertical' }) + const editorWithComments = $$(SplitPane, { sizeA: '80%', splitType: 'vertical' }) .append( - editorWithFooter, + editor, commentsPane ) - let sideNav = $$('div').addClass('sidenav').append(toolbarLeft) + const sideNav = $$('div').addClass('sidenav').append(toolbarLeft) - var contentPanel = $$(ScrollPane, { + const contentPanel = $$(ScrollPane, { name: 'contentPanel', - // contextMenu: 'custom', scrollbarPosition: 'right' }) .append(editorWithComments, $$(Overlay), $$(ContextMenu), - // $$(Dropzones) sideNav ) - .attr('id', 'content-panel') + .attr('id', 'content-panel-' + this.props.containerId) .ref('contentPanel') - const contentPanelWithSplitPane = $$(SplitPane, { sizeA: '75%', splitType: 'vertical' }) + const contentPanelWithSplitPane = $$(SplitPane, { sizeA: '95%', splitType: 'vertical' }) .append( contentPanel, - toc + $$('div') + ) + + const ToolbarWithEditor = $$(SplitPane, { splitType: 'horizontal' }) + .append( + toolbar, + contentPanelWithSplitPane ) el.append( - $$(SplitPane, { splitType: 'horizontal' }) + $$(SplitPane, { sizeA: '85%', splitType: 'horizontal' }) .append( - toolbar, - contentPanelWithSplitPane + ToolbarWithEditor, + footerNotes ) ) @@ -164,10 +163,8 @@ class Editor extends ProseEditor { trackChanges: this.props.trackChanges, trackChangesView: this.state.trackChangesView, toolGroups: [ - // 'text', 'document', 'annotations', - 'default', 'track-change-enable', 'track-change-toggle-view' ] @@ -186,7 +183,7 @@ class Editor extends ProseEditor { commandStates: commandStates, trackChanges: this.props.trackChanges, trackChangesView: this.state.trackChangesView, - toolGroups: ['text'] + toolGroups: ['text', 'default'] } ) .ref('toolbarLeft') @@ -196,8 +193,11 @@ class Editor extends ProseEditor { _renderEditor ($$) { const configurator = this.props.configurator const editing = this.props.disabled ? 'selection' : 'full' + const disabled = (isEmpty(this.props.fragment.source) && editing === 'selection') + return $$(ContainerEditor, { editing: editing, + disabled: disabled, editorSession: this.editorSession, commands: configurator.getSurfaceCommandNames(), containerId: 'body', @@ -238,33 +238,19 @@ class Editor extends ProseEditor { this.extendState({ changesNotSaved: false }) } - toggleCommentsArea (show) { - var self = this - - setTimeout(function () { - var el = self.refs.contentPanel - - if (show && !el.hasClass('sc-has-comments')) { - el.addClass('sc-has-comments') - } - - if (!show && el.hasClass('sc-has-comments')) { - el.removeClass('sc-has-comments') - } - }) - } - getChildContext () { const oldContext = super.getChildContext() const doc = this.doc // toc provider - const tocProvider = new TOCProvider(doc, { - containerId: 'body' - }) + // const tocProvider = new TOCProvider(doc, { + // containerId: this.props.containerId + // }) // notes provider - const notesProvider = new NotesProvider(doc) + const notesProvider = new NotesProvider(doc, { + containerId: this.props.containerId + }) // comments provider const commentsProvider = new CommentsProvider(doc, { @@ -275,7 +261,6 @@ class Editor extends ProseEditor { editorSession: this.editorSession, fragment: this.props.fragment, surfaceManager: this.surfaceManager, - toggleCommentsArea: this.toggleCommentsArea, update: this.props.update }) @@ -293,7 +278,6 @@ class Editor extends ProseEditor { // attach all to context return { ...oldContext, - tocProvider, notesProvider, commentsProvider, trackChangesProvider diff --git a/app/components/SimpleEditor/SimpleEditor.scss b/app/components/SimpleEditor/SimpleEditor.scss index f363e65f295d17b1b7a4b87d8bbea2c1bd744f4e..a9115c48c49e4a1deba080ffdc348379e04c1217 100644 --- a/app/components/SimpleEditor/SimpleEditor.scss +++ b/app/components/SimpleEditor/SimpleEditor.scss @@ -5,39 +5,26 @@ $fa-font-path: '~font-awesome/fonts'; @import '~substance/substance.css'; @import './elements/elements'; @import './panes/panes'; -@import './miniEditor/miniEditor'; +@import './notesEditor/notesEditor'; +@import './css/toolBar'; +@import './css/sideNav'; +@import './css/scrollBar'; +@import './css/customFonts'; // grays -$border: #ccc; -$dark-gray: #999; $light-gray: #ddd; -$primary: #eee; $ultra-light-gray: #fafafa; $black: #000; $shadow: rgba(0, 0, 0, .05); -$teal: #46b9ba; -$toolbar-active-bg: rgba(204, 204, 204, .75); -$transparent-black: rgba(0, 0, 0, .75); + +// $transparent-black: rgba(0, 0, 0, .75); +$content: #4a4a4a; $white: #fff; -// track changes -$inactive-grey: #404040; -$active-blue: #4a90e2; .editor-wrapper { height: 90vh; position: relative; - - // move to a new file toolbar.scss ?? - .view-mode { - font-size: 14px; - position: absolute; - right: 0; - text-align: center; - top: 10px; - width: 20%; - z-index: 9999; - } } .sc-prose-editor { @@ -47,224 +34,24 @@ $active-blue: #4a90e2; right: 0; top: 0; - .se-toolbar-wrapper .sc-toolbar { - background-color: $primary; - border: 1px solid $border; - border-right: 0; - float: left; - max-width: 1920px; - padding-left: 0; - vertical-align: middle; - width: 100%; - - @-moz-document url-prefix() { - .sc-tool-group > .sc-switch-text-type { - margin: 0; - position: relative; - top: 8px; - } - } - .sm-target-text { - // bottom: 5px; - height: 100%; - padding: 0; - position: relative; - - .sc-switch-text-type { - bottom: 5px; - margin-left: 1px; - width: 150px; - - button { - color: $transparent-black; - height: inherit; - position: relative; - top: 5px; - } - - .se-toggle { - background: transparent; - border: 0; - font-family: 'Fira Sans'; - font-size: 14px; - outline: none; - } - - .se-options { - background: transparent; - border: 0; - box-shadow: none; - top: 27px; - } - - .se-option { - background-color: #E8E8E8; - border: 0; - font-family: 'Fira Sans'; - } - } - } // end dropdown - - .sm-target-track-change-enable { - button { - background-color: $inactive-grey; - border-radius: 0; - color: $white; - line-height: 0; - // cursor: pointer; - padding: 0 19px; - position: relative; - } - - button::after { - content: 'Record Changes'; - } - - .track-changes-active { - background-color: $active-blue; - color: $white; - padding: 0 10px; - - &:after { - content: 'Recording Changes'; - } - } - } - - .sm-target-track-change-toggle-view { - cursor: pointer; - - button { - background-color: $inactive-grey; - border-radius: 0; - color: $white; - cursor: pointer; - padding: 0 19px; - position: relative; - line-height: 0; - } - - button::after { - content: 'Changes View Off'; - } - - .track-changes-view-active { - background-color: #228b46; - color: $white; - padding: 0 19px - } - - .track-changes-view-active::after { - content: 'Changes View On'; - } - } - - .sm-target-document { - border-left: 1px solid $border; - } - - .sm-target-track-change-enable { - padding: 0 9px; - } - - .sm-target-document, - .sm-target-annotations, - .sm-target-insert, - .sm-target-default { - border-right: 1px solid $border; - padding: 0px 9px; - - .sc-button { - background: transparent; - border: 0; - border-radius: 0; - font-size: 14px; - outline: none; - padding: 0 12px; - - &:hover { - background: $toolbar-active-bg; - - &:disabled { - background: transparent; - } - } - } - - .sm-active { - background: $toolbar-active-bg; - color: $black; - margin: 0; - outline: none; - padding: 0; - - &:after { - bottom: 8px; - color: $black; - content: 'x'; - font-size: 8px; - left: 21px; - position: absolute; - } - } + .sc-container-editor { + max-width: 50%; } - } // End sc-toolbar - .se-scrollable { - background-color: $primary; + background-color: $white; border-right: 1px solid $light-gray; margin-top: 1px; - - .sidenav .sc-toolbar ul li.se-option.active { - background-color: #d8d8d8; - } - - .sidenav { - background-color: #e8e8e8; - // box-shadow: 0 0 12px $dark-gray; - box-shadow: 2px 0px 1px -1px #ccc; - height: 100%; - left: 0; - margin-top: 149px; - overflow-x: hidden; - position: fixed; - top: 0; - width: 128px; - z-index: 1; - - .sc-toolbar { - background: transparent; - border: 0; - - ul { - margin: 0; - padding: 0; - - li.se-option { - padding: 3px; - } - } - - .sc-switch-text-type { - bottom: 0px; - margin: 0; - padding: 0; - width: 140px; - } - } - } } div.se-content { background-color: $white; - box-shadow: 0 0 8px $dark-gray; - color: $transparent-black; - font-family: 'Fira Sans'; + color: $content; + font-family: Vollkorn-Regular; line-height: 38px; - margin: 1.5% 17.8% 7%; + margin: 0% 0% 0% 20%; min-height: 100vh; - padding: 3% 4% 1%; + padding: 1% 0%; transition: .3s; word-wrap: break-word; @@ -313,23 +100,9 @@ $active-blue: #4a90e2; } } // end sc-content - .sc-has-comments { - div.se-content { - margin: 1.5% 23.6% 5% 12%; - transition: .3s; - } - } - .se-context-section { background-color: $ultra-light-gray; border-left: 1px solid $light-gray; box-shadow: inset 0 0 10px $shadow; } - - .sc-comments-pane { - display: flex; - flex-direction: column; - flex-wrap: wrap; - margin-left: 8%; - } } diff --git a/app/components/SimpleEditor/css/customFonts.scss b/app/components/SimpleEditor/css/customFonts.scss new file mode 100644 index 0000000000000000000000000000000000000000..63c78f5c306a0b8906713195b61c908c016b352a --- /dev/null +++ b/app/components/SimpleEditor/css/customFonts.scss @@ -0,0 +1,15 @@ + +@font-face { + font-family: 'Vollkorn-Regular'; + src: url('./css/fonts/Vollkorn-Regular.ttf'); +} + +@font-face { + font-family: 'Vollkorn-Italic'; + src: url('./css/fonts/Vollkorn-Italic.ttf'); +} + +@font-face { + font-family: 'FiraSansCondensed-Bold'; + src: url('./css/fonts/FiraSansCondensed-Bold-webfont.ttf'); +} diff --git a/app/components/SimpleEditor/css/fonts/FiraSansCondensed-Bold-webfont.ttf b/app/components/SimpleEditor/css/fonts/FiraSansCondensed-Bold-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/components/SimpleEditor/css/fonts/OFL.txt b/app/components/SimpleEditor/css/fonts/OFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..6152f23c5e2d94f9a7d26fc2869c834880fe7b58 --- /dev/null +++ b/app/components/SimpleEditor/css/fonts/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2010, Friedrich Althausen (post@grafikfritze.de) +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/components/SimpleEditor/css/fonts/Vollkorn-Bold.ttf b/app/components/SimpleEditor/css/fonts/Vollkorn-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9d4730f1d6a9f894b741413d109e8fe146b5d40e Binary files /dev/null and b/app/components/SimpleEditor/css/fonts/Vollkorn-Bold.ttf differ diff --git a/app/components/SimpleEditor/css/fonts/Vollkorn-BoldItalic.ttf b/app/components/SimpleEditor/css/fonts/Vollkorn-BoldItalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f70355904d616b5978813275158c255e242d27bf Binary files /dev/null and b/app/components/SimpleEditor/css/fonts/Vollkorn-BoldItalic.ttf differ diff --git a/app/components/SimpleEditor/css/fonts/Vollkorn-Italic.ttf b/app/components/SimpleEditor/css/fonts/Vollkorn-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d55c2eec3be818e7c4f24320e4530bfbb8f480f9 Binary files /dev/null and b/app/components/SimpleEditor/css/fonts/Vollkorn-Italic.ttf differ diff --git a/app/components/SimpleEditor/css/fonts/Vollkorn-Regular.ttf b/app/components/SimpleEditor/css/fonts/Vollkorn-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a913a1b524a1e4f21f6cfc84f125c4def01ae2fa Binary files /dev/null and b/app/components/SimpleEditor/css/fonts/Vollkorn-Regular.ttf differ diff --git a/app/components/SimpleEditor/css/scrollBar.scss b/app/components/SimpleEditor/css/scrollBar.scss new file mode 100644 index 0000000000000000000000000000000000000000..7681fd79229405923e2b67592bb3e50b37d6025c --- /dev/null +++ b/app/components/SimpleEditor/css/scrollBar.scss @@ -0,0 +1,19 @@ + +$scrollbar-bg: #f5f5f5; +$scrollbar-thumb: rgba(0, 0, 0, .2); +$scrollbar-track: rgba(0, 0, 0, .3); + +::-webkit-scrollbar-track { + background-color: $scrollbar-bg; + border-radius: 10px; + box-shadow: inset 0 0 6px $scrollbar-track; + } + +::-webkit-scrollbar { + background-color: $scrollbar-bg; + width: 4px; +} + +::-webkit-scrollbar-thumb { + background: $scrollbar-thumb; +} diff --git a/app/components/SimpleEditor/css/sideNav.scss b/app/components/SimpleEditor/css/sideNav.scss new file mode 100644 index 0000000000000000000000000000000000000000..4e85793c6a8d129394f8b3c81ede92a43a9ed01d --- /dev/null +++ b/app/components/SimpleEditor/css/sideNav.scss @@ -0,0 +1,113 @@ +$black: #000; +$heading: #979797; +$option: #4a4a4a; +$toolbar-bg: #fafafa; + + +.sidenav { + height: 60%; + left: 3%; + margin-top: 197px; + overflow-x: hidden; + position: fixed; + top: 0; + user-select: none; + width: 9%; + z-index: 100; + + .sc-toolbar { + background-color: $toolbar-bg !important; + border: 0; + width: 100%; + + .sc-tool-group.sm-target-text.sm-layout-horizontal { + width: 100%; + } + + ul { + margin: 0; + padding: 0; + + li.se-option { + padding: 3px; + } + } + + .sc-switch-text-type { + bottom: 0; + font-size: 14px; + margin: 0; + padding: 0; + width: 100%; + } + + .sm-target-default { + width: 71%; +// + &:before { + border-bottom: 1px solid $heading; + color: $heading; + content: 'List Types'; + display: block; + font: 'FiraSans-Bold'; + font-family: 'FiraSans-Bold'; + font-size: 15px; + font-weight: 900; + letter-spacing: .6px; + margin-top: -5px; + text-align: left; + } + } + + } + + .sc-toolbar ul li.heading { + border-bottom: 1px solid $heading; + color: $heading; + font-family: 'FiraSans-Bold'; + font-size: 15px; + letter-spacing: .6px; + text-align: left; + width: 71%; + } + + .sc-toolbar ul li.se-option { + color: $option; + cursor: pointer; + font-size: 14px; + font-weight: normal; + } + + .sc-toolbar ul li.se-option.active { + color: $black; + font-size: 14px; + font-weight: 900; + } + + .sc-insert-list-tool { + left: -14px; + .fa { + color: $option; + cursor: pointer; + font: inherit; + font-size: 14px; + font-weight: normal; + } + } + + .fa-list-ul::before { + content: 'Unordered list'; + } + + .fa-list-ol::before { + content: 'Ordered list'; + } + + .fa-quora::before { + content: 'Q & A'; + } + + .fa-bars::before { + content: 'Unstyled list'; + } +} diff --git a/app/components/SimpleEditor/css/toolBar.scss b/app/components/SimpleEditor/css/toolBar.scss new file mode 100644 index 0000000000000000000000000000000000000000..f94ee6b48517f8df5a839b11327ef08f4e8e6863 --- /dev/null +++ b/app/components/SimpleEditor/css/toolBar.scss @@ -0,0 +1,140 @@ +$white: #fff; +$black: #000; +// track changes +$inactive-grey: #404040; +$active-blue: #4990e2; +$toolbar-active-bg: rgba(204, 204, 204, .75); + +.sc-prose-editor { + .se-toolbar-wrapper { + border-bottom: 0; + + .sc-toolbar { + background-color: $white; + border-right: 0; + margin-left: 18.6%; + padding-left: 0; + vertical-align: middle; + width: 100%; + + @-moz-document url-prefix() { + .sc-tool-group > .sc-switch-text-type { + margin: 0; + position: relative; + top: 8px; + } + } + + .sm-target-track-change-enable { + button { + background-color: transparent; + border: 1px solid $black; + border-radius: 4px; + color: $inactive-grey; + line-height: 0; + outline: none; + padding: 0 19px; + position: relative; + } + + button::after { + content: 'Record Changes'; + } + + .track-changes-active { + background-color: $blue; + border-color: $blue; + color: $white; + padding: 0 10px; + text-decoration: none; + + &:after { + content: 'Recording Changes'; + } + } + } + + .sm-target-track-change-toggle-view { + cursor: pointer; + + button { + background-color: transparent; + border: 1px solid $black; + border-radius: 4px; + color: $inactive-grey; + cursor: pointer; + line-height: 0; + outline: none; + padding: 0 19px; + position: relative; + } + + button::after { + content: 'show changes'; + } + + .track-changes-view-active { + background-color: $blue; + border-color: $blue; + color: $white; + padding: 0 19px; + text-decoration: none; + } + + .track-changes-view-active::after { + content: 'Hide changes'; + } + } + + .sm-target-document, + .sm-target-annotations, + .sm-target-insert, + .sm-target-default { + padding: 0 9px; + + .sc-button { + background: transparent; + border: 0; + border-radius: 0; + font-size: 14px; + outline: none; + padding: 0 12px; + + &:hover { + background: transparent; + + &:disabled { + background: transparent; + } + } + } + + .sm-active { + // background: $toolbar-active-bg; + color: $black; + margin: 0; + outline: none; + padding: 0; + + &:after { + bottom: 8px; + color: $black; + content: 'x'; + font-size: 8px; + left: 21px; + position: absolute; + } + } + } + + } // End sc-toolbar + .view-mode { + font-size: 14px; + position: absolute; + right: 5.5%; + text-align: center; + top: 10px; + z-index: 9999; + } +} +} diff --git a/app/components/SimpleEditor/elements/comment/CommentBubble.js b/app/components/SimpleEditor/elements/comment/CommentBubble.js index 5c79d60fd28b04a05adbc54dc4042ac789d60591..e668fa6a9262abad3770236bd9bae1c52c472710 100644 --- a/app/components/SimpleEditor/elements/comment/CommentBubble.js +++ b/app/components/SimpleEditor/elements/comment/CommentBubble.js @@ -10,15 +10,19 @@ class CommentBubble extends Tool { const title = 'Create a new comment' - const icon = $$(Icon, { icon: 'fa-comment' }) - .addClass('sc-comment-icon') + 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(icon) + .append(iconBubble) + .append(iconPlus) return bubble } @@ -50,14 +54,17 @@ class CommentBubble extends Tool { // 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') + const containerId = this.context.editor.props.containerId + const contentPanel = '#content-panel-' + containerId setTimeout(() => { // read comment below - const documentElementWidth = documentElement.offsetWidth + let documentElement = document.querySelector(contentPanel + ' .se-content') + let overlayContainer = document.querySelector(contentPanel + ' .sc-overlay') + let fix = 15 + + const documentElementWidth = documentElement.offsetWidth / 1.85 const overlayContainerLeft = overlayContainer.offsetLeft - const left = documentElementWidth - overlayContainerLeft - 15 + const left = documentElementWidth - overlayContainerLeft - fix // unhide it first, as the bubble has no height otherwise this.el.removeClass('sc-overlay-bubble-hidden') diff --git a/app/components/SimpleEditor/elements/comment/ResolvedCommentPackage.js b/app/components/SimpleEditor/elements/comment/ResolvedCommentPackage.js index 43688237bf76728372f3635482ccd73d6704027d..8fd2d089332ad8d28b6fc3d2d7a7de0e22ad6659 100644 --- a/app/components/SimpleEditor/elements/comment/ResolvedCommentPackage.js +++ b/app/components/SimpleEditor/elements/comment/ResolvedCommentPackage.js @@ -6,7 +6,6 @@ export default { name: 'resolved-comment', configure: function (config) { config.addNode(ResolvedComment) - config.addCommand(ResolvedComment.type, ResolvedCommentCommand, { nodeType: ResolvedComment.type }) config.addConverter('html', ResolvedCommentHTMLConverter) } diff --git a/app/components/SimpleEditor/elements/comment/comment.scss b/app/components/SimpleEditor/elements/comment/comment.scss index 6aadfc83e81d62c341a454729f3b70212073c89d..e06adadda866c0a29918a2bdee4cf155bc13a36e 100644 --- a/app/components/SimpleEditor/elements/comment/comment.scss +++ b/app/components/SimpleEditor/elements/comment/comment.scss @@ -4,30 +4,26 @@ $mid-gray: #b0b0b0; $dark-gray: #868686; // $light-green: #eafaf0; -$light-green: #e0efe0; -$green: #228b46; +$light-yellow: #fff5e6; $white: #fff; +$yellow: #ffab20; + .sc-comment { - border-bottom: 2px solid $green; + border-bottom: 2px solid $yellow; border-radius: 3px 3px 0 0; } .sc-comment-active { - background-color: $light-green; + background-color: $light-yellow; } + .sc-overlay { .se-active-tools { .sc-overlay-bubble { - background: $white; - border: 1px solid $mid-gray; - border-radius: 50px; - box-shadow: 0 2px 3px $mid-gray; - cursor: pointer; - height: 35px; + background: transparent; position: absolute; - width: 35px; z-index: 200; } @@ -35,21 +31,31 @@ $white: #fff; display: none; } - .sc-comment-icon { - color: $dark-gray; - font-size: 20px; + .sc-comment-icon-bubble { + color: $yellow; + cursor: pointer; + font-size: 28px; + font-weight: normal; left: 8px; position: relative; - // top: 6.7px; - top: 0; + top: 20px; + } + + .sc-comment-icon-plus { + bottom: 24px; + color: $yellow; + cursor: pointer; + font-size: 12px; + left: 17px; + position: relative; } .action-icon { bottom: 2px; - color: $green; + color: $yellow; font-size: 9px; position: relative; right: 10px; } } -} + } diff --git a/app/components/SimpleEditor/elements/icons/addComment.svg b/app/components/SimpleEditor/elements/icons/addComment.svg new file mode 100644 index 0000000000000000000000000000000000000000..6ecd376e5f52d99fdaaaebda5f5654154511588e --- /dev/null +++ b/app/components/SimpleEditor/elements/icons/addComment.svg @@ -0,0 +1,4 @@ +<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M0 0h24v24H0z" fill="none"/> + <path d="M14 10H2v2h12v-2zm0-4H2v2h12V6zm4 8v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2 16h8v-2H2v2z"/> +</svg> \ No newline at end of file diff --git a/app/components/SimpleEditor/elements/icons/note.svg b/app/components/SimpleEditor/elements/icons/note.svg new file mode 100644 index 0000000000000000000000000000000000000000..884c4417d7fa6b4e088b7825f7a50e3ea482fbe1 --- /dev/null +++ b/app/components/SimpleEditor/elements/icons/note.svg @@ -0,0 +1,4 @@ +<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/> + <path d="M0 0h24v24H0z" fill="none"/> +</svg> \ No newline at end of file diff --git a/app/components/SimpleEditor/elements/icons/resolveAllComments.svg b/app/components/SimpleEditor/elements/icons/resolveAllComments.svg new file mode 100644 index 0000000000000000000000000000000000000000..ee14b42225ff608863fd48183cedac286b9c87f6 --- /dev/null +++ b/app/components/SimpleEditor/elements/icons/resolveAllComments.svg @@ -0,0 +1,9 @@ +<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <path d="M0 0h24v24H0V0z" id="a"/> + </defs> + <clipPath id="b"> + <use overflow="visible" xlink:href="#a"/> + </clipPath> + <path clip-path="url(#b)" d="M14 10H2v2h12v-2zm0-4H2v2h12V6zM2 16h8v-2H2v2zm19.5-4.5L23 13l-6.99 7-4.51-4.5L13 14l3.01 3 5.49-5.5z"/> +</svg> \ No newline at end of file diff --git a/app/components/SimpleEditor/elements/isolatedNote/IsolatedNote.js b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNote.js new file mode 100644 index 0000000000000000000000000000000000000000..79e0c5eedb6b7e57428e625f36386816437ed208 --- /dev/null +++ b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNote.js @@ -0,0 +1,11 @@ +import {DocumentNode} from 'substance' + +class IsolatedNote extends DocumentNode {} + +IsolatedNote.define({ + type: 'isolated-note', + content: 'text', + calloutId: 'string' +}) + +export default IsolatedNote diff --git a/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteComponent.js b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..91ba76dc545f1d3f80284d8199b5dbf13b80fe17 --- /dev/null +++ b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteComponent.js @@ -0,0 +1,40 @@ +import { IsolatedNodeComponent } from 'substance' +import TextPropertyEditor from './TextPropertyEditor' + +class IsolatedNoteComponent extends IsolatedNodeComponent { + render ($$) { + // TODO + // why class sc-entity?? + // rename dataId to calloutId ?? if so, change in provider + // is there a better way than rendering a text property editor??? + // review ref name + // do we need a ref ?? + // why are we giving it a p tagname?? + + const { node } = this.props + const nodeId = node.id + + const editing = this.isDisabled() ? 'selection' : 'full' + + const textPropertyEditor = $$(TextPropertyEditor, { + editing, + multiLine: false, + path: [nodeId, 'content'], + tagName: 'p' + }).ref('noteContentEditor-' + nodeId) + + const el = $$('div') + .addClass('sc-entity') + .attr('data-id', nodeId) + .append(textPropertyEditor) + + return el + } + + isDisabled () { + const { editor } = this.context + return editor.props.disabled + } +} + +export default IsolatedNoteComponent diff --git a/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteHTMLConverter.js b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteHTMLConverter.js new file mode 100644 index 0000000000000000000000000000000000000000..d1db56e5d0311815c76e11552604a6215f6a905f --- /dev/null +++ b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNoteHTMLConverter.js @@ -0,0 +1,21 @@ +// TODO +// review tag name +// this converter shouldn't exist + +export default { + type: 'isolated-note', + tagName: 'isolated-note', + + import: function (el, node, converter) { + node.content = converter.annotatedText(el, [node.id, 'content']) + node.calloutId = el.attr('data-parent-id') + }, + + export: function (node, el, converter) { + el.append( + converter.annotatedText([node.id, 'content']) + ) + + el.setAttribute('data-parent-id', node.calloutId) + } +} diff --git a/app/components/SimpleEditor/elements/isolatedNote/IsolatedNotePackage.js b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNotePackage.js new file mode 100644 index 0000000000000000000000000000000000000000..f482079fee7de40833c26ca369a74554f0818ba3 --- /dev/null +++ b/app/components/SimpleEditor/elements/isolatedNote/IsolatedNotePackage.js @@ -0,0 +1,12 @@ +import IsolatedNote from './IsolatedNote' +import IsolatedNoteComponent from './IsolatedNoteComponent' +import IsolatedNoteHTMLConverter from './IsolatedNoteHTMLConverter' + +export default { + name: 'isolated-note', + configure: function (config) { + config.addNode(IsolatedNote) + config.addComponent(IsolatedNote.type, IsolatedNoteComponent) + config.addConverter('html', IsolatedNoteHTMLConverter) + } +} diff --git a/app/components/SimpleEditor/elements/isolatedNote/TextPropertyEditor.js b/app/components/SimpleEditor/elements/isolatedNote/TextPropertyEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..76e15e2861fb5e4dab615520d38cd6b08de66015 --- /dev/null +++ b/app/components/SimpleEditor/elements/isolatedNote/TextPropertyEditor.js @@ -0,0 +1,72 @@ +import { TextPropertyEditor as SubstanceTextPropertyEditor } from 'substance' + +/* + TODO -- SERIOUS REVIEW, we shouldn't need to duplicate code here. + This has duplicate overrides of all track changes functions from our + container editor. This is because TextProperyEditor extends Surface directly. +*/ + +class TextPropertyEditor extends SubstanceTextPropertyEditor { + onTextInput (event) { + if (!this.isTrackChangesOn()) { + return super.onTextInput(event) + } + + let surface = this.getSurface() + if (!surface) return + return surface.onTextInput(event) + } + + onTextInputShim (event) { + if (!this.isTrackChangesOn()) { + return super.onTextInputShim(event) + } + + let surface = this.getSurface() + if (!surface) return + return surface.onTextInputShim(event) + } + + _handleDeleteKey (event) { + // If isloated note has no content and you keep pressing backspace, + // it gets deleted. + const selection = this.editorSession.getSelection() + if (selection.start.offset === 0 && selection.end.offset === 0) { + super.onTextInputShim(event) + return super.onTextInput(event) + } + + if (!this.isTrackChangesOn()) { + return super._handleDeleteKey(event) + } + + let surface = this.getSurface() + if (!surface) return + return surface._handleDeleteKey(event) + } + + _handleSpaceKey (event) { + if (!this.isTrackChangesOn()) { + return super._handleSpaceKey(event) + } + + let surface = this.getSurface() + if (!surface) return + return surface._handleSpaceKey(event) + } + + isTrackChangesOn () { + return this.context.editor.props.trackChanges + } + + getSurface () { + const containerId = this.getContainerId() + return this.context.surfaceManager.getSurface(containerId) + } + + getContainerId () { + return this.context.editor.props.containerId + } +} + +export default TextPropertyEditor diff --git a/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypePackage.js b/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypePackage.js index 07d8c896cd5e299afefb95c98020806252cc7781..ccb0c7b52fd6618161490f7013820b6d88fdd4c8 100644 --- a/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypePackage.js +++ b/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypePackage.js @@ -1,11 +1,18 @@ -import {SwitchTextTypeCommand} from 'substance' +import { SwitchTextTypeCommand } from 'substance' import LeftSwitchTextTypeTool from './LeftSwitchTextTypeTool' +// TODO +// this package should have a better name + export default { name: 'switch-text-type', configure: function (config, options) { config.addToolGroup('text') config.addCommand('switch-text-type', SwitchTextTypeCommand) - config.addTool('switch-text-type', LeftSwitchTextTypeTool, {toolGroup: options.toolGroup || 'text'}) + + // is this || here necessary? + config.addTool('switch-text-type', LeftSwitchTextTypeTool, { + toolGroup: options.toolGroup || 'text' + }) } } diff --git a/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypeTool.js b/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypeTool.js index 19e029c3031901aa7a97dfc5ba423a6d5548f2da..22eba9a861f244a9ef8f7e3805c7a5fd79d59f93 100644 --- a/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypeTool.js +++ b/app/components/SimpleEditor/elements/left_switch_text_type/LeftSwitchTextTypeTool.js @@ -1,20 +1,37 @@ import { forEach, Tool } from 'substance' +// TODO -- why forEach from substance and not lodash? + class LeftSwitchTextTypeTool extends Tool { constructor (...args) { super(...args) - let text = this.context.editorSession.configurator.config.textTypes - this.extendProps({textTypes: text}) + + const { editorSession } = this.context + // TODO -- is this correct? + let textTypes = editorSession.configurator.config.textTypes + + this.extendProps({ + textTypes: textTypes + }) } + // TODO -- necessary? didMount (...args) { super.didMount(...args) } + // TODO -- rewrite all of this render ($$) { - let labelProvider = this.context.labelProvider - let el = $$('ul').addClass('sc-switch-text-type') + const { labelProvider } = this.context + + let el = $$('ul') + .addClass('sc-switch-text-type') + .append( + $$('li') + .append('Text Types') + .addClass('heading') + ) forEach(this.props.textTypes, function (textType) { let item = $$('li') @@ -45,25 +62,44 @@ class LeftSwitchTextTypeTool extends Tool { } executeCommand (textType) { - this.context.commandManager.executeCommand(this.getCommandName(), { + const { commandManager } = this.context + + // TODO -- do we need to CALL the function in the argument?? + commandManager.executeCommand(this.getCommandName(), { textType: textType }) } - handleClick (e) { - e.preventDefault() + // TODO -- needs comments + handleClick (event) { + event.preventDefault() + if (this.isReadOnlyMode()) return - this.executeCommand(e.currentTarget.dataset.type) + + const type = event.currentTarget.dataset.type + this.executeCommand(type) + } + + getContainerId () { + const editor = this.getEditor() + return editor.props.containerId + } + + getEditor () { + return this.context.editor } getSurface () { - return this.context.surfaceManager.getSurface('body') + const { surfaceManager } = this.context + const containerId = this.getContainerId() + + return surfaceManager.getSurface(containerId) } // TODO -- review how the side toolbar gets disabled isReadOnlyMode () { const surface = this.getSurface() - if (!surface) return true // HACK + if (!surface) return true // HACK -- ??? return surface.isReadOnlyMode() } diff --git a/app/components/SimpleEditor/elements/modal_warning/ModalWarning.js b/app/components/SimpleEditor/elements/modal_warning/ModalWarning.js index c92e365e8c5eecf052c35c26736b4c977dc3916d..1477d0c5a5bc60a802060293568a2193e1e21150 100644 --- a/app/components/SimpleEditor/elements/modal_warning/ModalWarning.js +++ b/app/components/SimpleEditor/elements/modal_warning/ModalWarning.js @@ -14,29 +14,30 @@ class ModalWarning extends Modal { let el = $$('div') .addClass('sc-modal') - const closeButton = $$(Icon, { icon: 'fa-close' }) - .addClass('sc-close-modal') + const closeButton = $$('div').addClass('circle') + .append($$(Icon, { icon: 'fa-close' }) + .addClass('sc-close-modal')) .on('click', this._closeModal) const modalHeader = $$('div') .addClass('sc-modal-header') - .append('Unsaved Content') + .append('There are unsaved changes. Do you want to save your work?') .append(closeButton) - const modalMessage = $$('div') - .addClass('sc-modal-body') - .append('Are you sure you want to exit the chapter without saving ?') + // const modalMessage = $$('div') + // .addClass('sc-modal-body') + // .append('Are you sure you want to exit the chapter without saving ?') const saveExit = $$('button') .addClass('sc-modal-button') .addClass('sc-modal-button-save-exit') - .append('save & exit') + .append('Save and quit') .on('click', this._saveExitWriter) const exit = $$('button') .addClass('sc-modal-button') .addClass('sc-modal-button-exit') - .append('exit') + .append('Quit without saving') .on('click', this._exitWriter) const modalActionsContainer = $$('div') @@ -51,7 +52,6 @@ class ModalWarning extends Modal { el.append( $$('div').addClass('se-body') .append(modalHeader) - .append(modalMessage) .append(modalActionsContainer) ) @@ -70,7 +70,7 @@ class ModalWarning extends Modal { _saveExitWriter () { this.context.editor.editorSession.save() if (this.backButton) { - setTimeout(() => { this.context.editor.props.history.go(-2) }, 200) + setTimeout(() => { this.context.editor.props.history.go(-1) }, 200) } else { setTimeout(() => { this.context.editor.props.history.push(this.route) }, 200) } @@ -86,7 +86,7 @@ class ModalWarning extends Modal { this.context.editor.editorSession.save() if (this.backButton) { - setTimeout(() => { this.context.editor.props.history.go(-2) }, 200) + setTimeout(() => { this.context.editor.props.history.go(-1) }, 200) } else { // TODO: Hack Check why cannot rerender editor so can push to url setTimeout(() => { this.context.editor.props.history.push(this.route) }, 200) diff --git a/app/components/SimpleEditor/elements/modal_warning/modalWarning.scss b/app/components/SimpleEditor/elements/modal_warning/modalWarning.scss index 071738904712d3653bdc97d6814b94dc4ae1300e..c8179b47ae25d5afbea92e7e1ae065f9f1c796d2 100644 --- a/app/components/SimpleEditor/elements/modal_warning/modalWarning.scss +++ b/app/components/SimpleEditor/elements/modal_warning/modalWarning.scss @@ -1,9 +1,8 @@ -$background: rgba(0, 0, 0, .5); +$background: rgba(255, 255, 255, .97); $modal-border: #808080; -$button-bg: #d3d3d3; $header-border-color: #e5e5e5; $black: #000; -$hover-button-bg-color: #808080; +$hover-button-bg-color: #4990e2; $red: #a52a2a; $white: #fff; @@ -11,26 +10,42 @@ $white: #fff; background: $background; .se-body { - border: 2px solid $modal-border; + background-color: transparent; border-radius: 0; + box-shadow: none; font-style: italic; font-weight: 500; padding: 1em; - width: 35%; + text-align: center; + top: 20%; + width: 40%; } .sc-modal-header { - border-bottom: 1px solid $header-border-color; - font-size: 24px; - line-height: 32px; + font-size: 20px; } - .sc-close-modal { + .circle { + border: 1px solid $black; + border-radius: 50%; + bottom: 5px; cursor: pointer; float: right; + height: 30px; + position: relative; + width: 30px; + + &:hover { + background-color: $hover-button-bg-color; + border: 1px solid $hover-button-bg-color; + color: $white; + } + } + + .sc-close-modal { font-size: 16px; font-weight: normal; - margin-top: 10px; + } .sc-modal-body { @@ -38,39 +53,44 @@ $white: #fff; } .sc-modal-actions { - float: right; + margin-top: 10%; } .sc-modal-button { - background-color: $button-bg; - border: 3px solid transparent; - border-radius: 3px; - color: $black; cursor: pointer; - display: inline-block; - font-size: 15px; + display: block; + margin: auto; + } + + .sc-modal-button-save-exit { + background-color: $white; + border: 1px solid $black; + border-radius: 4px; + color: $black; + font-size: 16px; font-style: normal; font-weight: 500; margin-bottom: .5em; - padding: 7px 30px; + padding: 8px 32px; text-align: center; text-decoration: none; - text-transform: uppercase; &:hover { background-color: $hover-button-bg-color; + border: 1px solid $hover-button-bg-color; + color: $white; } } - .sc-modal-button-save-exit { - margin-right: 20px; + .sc-modal-button-exit { + background-color: transparent; + border: 0; + color: $black; + font-size: 14px; + margin-top: 3%; &:hover { - color: $white; + color: $hover-button-bg-color; } } - - .sc-modal-button-exit { - color: $red; - } } diff --git a/app/components/SimpleEditor/elements/note/EditNoteTool.js b/app/components/SimpleEditor/elements/note/EditNoteTool.js deleted file mode 100644 index 0d4569d285573d2fa1cfe56e5e9581a0e253ccef..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/elements/note/EditNoteTool.js +++ /dev/null @@ -1,141 +0,0 @@ -import { each, includes, keys } from 'lodash' -import { - documentHelpers, - EditorSession, - ProseEditorConfigurator as Configurator, - Tool -} from 'substance' - -import MiniEditor from '../../miniEditor/miniEditor' -import config from '../../miniEditor/config' -import Importer from '../../SimpleEditorImporter' -import SimpleExporter from '../../SimpleEditorExporter' - -class EditNoteTool extends Tool { - constructor (props) { - super(props) - this.saveNote = this.saveNote.bind(this) - } - - render ($$) { - const miniEditorSession = this._initMiniEditor() - const selected = this.getSelection() - - let el = $$('div').addClass('sc-edit-note-tool') - - if (!selected.node) return el - - el.append($$(MiniEditor, { - editorSession: miniEditorSession - })) - - return el - } - - didMount () { - this.context.editorSession.onUpdate('', this.disableTools, this) - } - - _initMiniEditor () { - const selected = this.getSelection() - if (!selected.node) return - - const configurator = new Configurator().import(config) - configurator.addImporter('html', Importer) - - const importer = configurator.createImporter('html') - const doc = importer.importDocument(selected.node['note-content']) - - const editorSession = new EditorSession(doc, { - configurator: configurator - }) - - editorSession.setSaveHandler({ - saveDocument: this.saveNote - }) - - return editorSession - } - - disableTools () { - const selected = this.getSelection() - if (!selected.node) return - const commandStates = this.context.commandManager.commandStates - each(keys(commandStates), (key) => { - const allowed = ['comment', 'redo', 'save', 'switch-text-type', 'undo', 'note'] - if (!includes(allowed, key)) commandStates[key].disabled = true - }) - this.rerender() - } - - saveNote (source) { - const selected = this.getSelection() - const config = this.context.editorSession.configurator.config - const exporter = new SimpleExporter(config) - const convertedSource = exporter.exportDocument(source) - const editorSession = this.context.editorSession - - editorSession.transaction(function (tx, args) { - const path = [selected.node.id, 'note-content'] - tx.set(path, convertedSource) - }) - - // Return dummy Promise needed in saveDocument - return new Promise(function (resolve, reject) { - resolve() - }) - } - - getSelection () { - // TODO -- write cleaner - const surface = this.context.surfaceManager.getFocusedSurface() - if (!surface) return {} - - const session = this.context.editorSession - const sel = session.getSelection() - - const notes = documentHelpers.getPropertyAnnotationsForSelection( - session.getDocument(), - sel, - { type: 'note' } - ) - - const note = notes[0] - let show = false - - if (typeof note !== 'undefined') { - if ((sel.start.offset === note.start.offset && - sel.end.offset === note.end.offset)) { - show = true - } - } - - // disable when larger selection that just includes a note as well - // const selectionLength = (sel.end.offset - sel.start.offset === 1) - // if (sel.end.offset - sel.) - - if (show) { - return { - node: note - } - } else { - return { - node: null - } - } - } - - getSurface () { - const surfaceManager = this.context.surfaceManager - return surfaceManager.getFocusedSurface() - } - - isEditorReadOnly () { - const surface = this.getSurface() - return surface.isReadOnlyMode() - } -} - -EditNoteTool.type = 'edit-note' - -export default EditNoteTool diff --git a/app/components/SimpleEditor/elements/note/Note.js b/app/components/SimpleEditor/elements/note/Note.js index 82579d2abe8647801c701959589dba0104bd6250..51af949caa2935a9170c7553d515fa98642330d7 100644 --- a/app/components/SimpleEditor/elements/note/Note.js +++ b/app/components/SimpleEditor/elements/note/Note.js @@ -6,7 +6,7 @@ Note.define({ 'type': 'note', 'note-content': { type: 'string', - optional: true + default: '' } }) diff --git a/app/components/SimpleEditor/elements/note/NoteCommand.js b/app/components/SimpleEditor/elements/note/NoteCommand.js index 198e7d5c5e303112eec192cef0af09f1332e1f03..42dabee8c671f0c2cf27efc544868a9084f9b624 100644 --- a/app/components/SimpleEditor/elements/note/NoteCommand.js +++ b/app/components/SimpleEditor/elements/note/NoteCommand.js @@ -6,6 +6,16 @@ class NoteCommand extends InsertInlineNodeCommand { type: 'note' } } + + execute (params) { + let editorSession = params.editorSession + let nodeData = this.createNodeData() + + // TODO -- why is this necessary? + editorSession.transaction((tx) => { + return tx.insertInlineNode(nodeData) + }) + } } NoteCommand.type = 'note' diff --git a/app/components/SimpleEditor/elements/note/NoteComponent.js b/app/components/SimpleEditor/elements/note/NoteComponent.js index a5c9825695cacc2e75c82ec82cb95fe830073e37..0a64c71856729889900191f023f15d457b84839a 100644 --- a/app/components/SimpleEditor/elements/note/NoteComponent.js +++ b/app/components/SimpleEditor/elements/note/NoteComponent.js @@ -1,17 +1,103 @@ /* eslint react/prop-types: 0 */ +import { get } from 'lodash' -import { Component } from 'substance' +import { Component, documentHelpers } from 'substance' class NoteComponent extends Component { + didMount () { + // TODO -- this disables the toolbar + // TODO -- unload listener on dispose + // TODO -- this gets registered once for each note -- to fix use didUpdate + // this should only call disableTools + this.context.editorSession.onUpdate('', this.noteSelected, this) + } + render ($$) { - const el = $$('note') - .attr('note-content', this.props.node['note-content']) + const noteContent = this.props.node['note-content'] + + const el = $$('span') + .attr('note-content', noteContent) .addClass('sc-note') - // .append('a') + .on('click', this.noteSelected) return el } + // TODO -- should this maybe be the provider's job? + noteSelected () { + // TODO -- we already have the note id in the props + const selected = this.getSelection() + if (get(selected, 'node.type') !== 'note') return + + // this shouldn't be here + // it should just be called when the selection updates + // not on click of the note component + this.disableTools(selected) + + const provider = this.getProvider() + const noteId = selected.node.id + + provider.calloutSelected(noteId) + + this.el.addClass('sc-note-active') + this.rerender() + } + + // TODO -- review + disableTools () { + const surface = this.getSurface() + if (!surface) return + surface.disableToolbar() + } + + getSelection () { + // TODO -- write cleaner + const surface = this.getSurface() + if (!surface) return {} + + const session = this.context.editorSession + const sel = session.getSelection() + + const notes = documentHelpers.getPropertyAnnotationsForSelection( + session.getDocument(), + sel, + { type: 'note' } + ) + + // TODO -- scrap all below + const note = notes[0] + let show = false + if (typeof note !== 'undefined') { + if ((sel.start.offset === note.start.offset && + sel.end.offset === note.end.offset)) { + show = true + } + } + + if (show) { + return { + node: note + } + } else { + return { + node: null + } + } + } + + getSurface () { + // TODO -- write with container id + return this.context.surfaceManager.getFocusedSurface() + } + + getCommandStates () { + return this.context.commandManager.commandStates + } + + getProvider () { + return this.context.notesProvider + } + dispose () { this.props.node.off(this) } diff --git a/app/components/SimpleEditor/elements/note/NoteFooter.js b/app/components/SimpleEditor/elements/note/NoteFooter.js deleted file mode 100644 index 65f1467d7f5c9a67335f4d4d736635b61f690e96..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/elements/note/NoteFooter.js +++ /dev/null @@ -1,7 +0,0 @@ -// import { DocumentNode } from 'substance' -// -// class NoteFooter extends DocumentNode {} -// -// NoteFooter.type = 'note-footer' -// -// export default NoteFooter diff --git a/app/components/SimpleEditor/elements/note/NoteFooterComponent.js b/app/components/SimpleEditor/elements/note/NoteFooterComponent.js deleted file mode 100644 index c3e9c999fdcb52e1b41859b3a33cba711ca7f2d4..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/elements/note/NoteFooterComponent.js +++ /dev/null @@ -1,16 +0,0 @@ -// import { BlockNodeComponent } from 'substance' -// -// class NoteFooterComponent extends BlockNodeComponent { -// render ($$) { -// let el = $$('div') -// .addClass('sc-note-footer') -// -// el.append( -// 'this is something I guess' -// ) -// -// return el -// } -// } -// -// export default NoteFooterComponent diff --git a/app/components/SimpleEditor/elements/note/NoteHTMLConverter.js b/app/components/SimpleEditor/elements/note/NoteHTMLConverter.js index 2ce72eab5137194ebf425db8035f10fe59f465df..6f78450c6bb25354048738e574cf1dee8deb2aaf 100644 --- a/app/components/SimpleEditor/elements/note/NoteHTMLConverter.js +++ b/app/components/SimpleEditor/elements/note/NoteHTMLConverter.js @@ -1,3 +1,5 @@ +// TODO -- es6 this + export default { type: 'note', tagName: 'note', diff --git a/app/components/SimpleEditor/elements/note/NotePackage.js b/app/components/SimpleEditor/elements/note/NotePackage.js index 53ac1427f413e6312c8b35d714250471f9b8bce3..e53cb9989647dc5fb49559e5073e14759dac9e88 100644 --- a/app/components/SimpleEditor/elements/note/NotePackage.js +++ b/app/components/SimpleEditor/elements/note/NotePackage.js @@ -1,4 +1,3 @@ -import EditNoteTool from './EditNoteTool' import Note from './Note' import NoteCommand from './NoteCommand' import NoteComponent from './NoteComponent' @@ -14,7 +13,6 @@ export default { config.addConverter('html', NoteHTMLConverter) config.addCommand(Note.type, NoteCommand, { nodeType: Note.type }) config.addTool('note', NoteTool, { toolGroup: 'annotations' }) - config.addTool('note', EditNoteTool, { toolGroup: 'overlay' }) config.addIcon('note', { 'fontawesome': 'fa-bookmark' }) config.addLabel('note', { en: 'Note' diff --git a/app/components/SimpleEditor/elements/note/PromptTextArea.js b/app/components/SimpleEditor/elements/note/PromptTextArea.js deleted file mode 100644 index a431f57c5c1bca2d8ccdf7319874f2a86d07d357..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/elements/note/PromptTextArea.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint react/prop-types: 0 */ - -import { Component } from 'substance' - -class TextArea extends Component { - render ($$) { - const { disabled, path, placeholder, rows } = this.props - - const editorSession = this.context.editorSession - const doc = editorSession.getDocument() - const val = doc.get(path) - - const el = $$('textarea') - .attr({ - // cols: this.props.columns || '40', - placeholder: placeholder || 'Type your text here', - rows: rows || '1' - }) - .addClass('se-note-textarea') - .append(val) - .on('keyup', this.textAreaAdjust) - - if (disabled) el.attr('disabled', 'true') - - return el - } - - textAreaAdjust (event) { - let textArea = event.path[0] - - textArea.style.height = '1px' - textArea.style.height = (textArea.scrollHeight) + 'px' - } -} - -export default TextArea diff --git a/app/components/SimpleEditor/elements/note/note.scss b/app/components/SimpleEditor/elements/note/note.scss index 5e54d1c6ec6e2cdb69e55f5909740bcef04bf76b..01a3748a232e95ebcac32b8ee34ca51fa035aeb8 100644 --- a/app/components/SimpleEditor/elements/note/note.scss +++ b/app/components/SimpleEditor/elements/note/note.scss @@ -1,5 +1,6 @@ -$gray: #eee; -$red: #591818; +$gray: #333; +$white: #fff; +$blue: #4990e2; .sc-prose-editor { counter-reset: note; @@ -8,63 +9,43 @@ $red: #591818; display: inline-block; } - .sc-note { - color: $red; - display: block; - font-weight: bold; - user-select: initial; - } + .sm-not-selected .sc-note, + .sm-co-selected .sc-note { + background-color: $gray; + -webkit-mask: url('./elements/icons/note.svg') no-repeat 100% 100%; + mask: url('./elements/icons/note.svg') no-repeat 100% 100%; + -webkit-mask-size: cover; + mask-size: cover; - .sc-note::after { - content: '['counter(note)']'; - counter-increment: note; - font-size: 11px; - font-style: italic; - font-weight: 400; } -} - -.sc-edit-note-tool-container { - background-color: $gray; - border: 1px solid $red; - display: inline-block; - - .sc-save-area { - display: inline; - float: left; - height: 100%; - padding: 5px 5px 0; - width: 40px; - .sc-save-icon { - color: $red; - cursor: pointer; - position: absolute; - right: 19px; - top: 17px; - - &:hover { - color: $red; - } - } + .sm-selected .sc-note { + background-color: $blue; + -webkit-mask: url('./elements/icons/note.svg') no-repeat 100% 100%; + mask: url('./elements/icons/note.svg') no-repeat 100% 100%; + -webkit-mask-size: cover; + mask-size: cover; } - textarea { - background-color: $gray; - border: transparent; - border-right: 1px dashed $red; - color: $red; - float: left; - font-family: 'Fira Sans'; - font-size: 15px; - min-height: 39px; - outline: none; - overflow: hidden; - resize: none; - width: 310px; + .sc-note { + cursor: pointer; + display: block; + height: 18px; + position: relative; + text-align: center; + top: 12px; + user-select: none; + width: 18px; + } - &[disabled] { - border-right: 0; - } + .sc-note::after { + color: $white; + content: ''counter(note)''; + counter-increment: note; + display: inline-block; + font-size: 10px; + font-weight: bold; + line-height: normal; + vertical-align: top; } } diff --git a/app/components/SimpleEditor/elements/track_change/TrackChangeComponent.js b/app/components/SimpleEditor/elements/track_change/TrackChangeComponent.js index 2abef42b437266a6e8acc4b781ebfc5e259b2bb4..11144a31b1815b40610704d84501aa4ed2fdee39 100644 --- a/app/components/SimpleEditor/elements/track_change/TrackChangeComponent.js +++ b/app/components/SimpleEditor/elements/track_change/TrackChangeComponent.js @@ -20,13 +20,9 @@ class TrackChangeComponent extends AnnotationComponent { .addClass('sc-track-item-reject') .on('click', this.reject) - const separator = $$('span') - .addClass('sc-track-item-separator') - const container = $$('span') .addClass('sc-accept-reject-container') .append(accept) - .append(separator) .append(reject) if (this.shouldHideContainer()) { diff --git a/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js b/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js index bcebbae00f37ff39e788f67011c6980efcebe0fe..136ae3ffd421067ef040069ec074ba8e483df826 100644 --- a/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js +++ b/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js @@ -359,8 +359,9 @@ class TrackChangesProvider extends TOCProvider { sortNodes (nodes) { const changes = clone(nodes) + const containerId = this.config.containerId const doc = this.getDocument() - const container = doc.get('body') + const container = doc.get(containerId) const changesArray = map(changes, annotation => { const blockId = annotation.path[0] diff --git a/app/components/SimpleEditor/elements/track_change/trackChange.scss b/app/components/SimpleEditor/elements/track_change/trackChange.scss index 9b747ea7a45e538462e054856892a0f2a55875e3..d1202cce1ef002f2187fca0e96da389bdcfb540e 100644 --- a/app/components/SimpleEditor/elements/track_change/trackChange.scss +++ b/app/components/SimpleEditor/elements/track_change/trackChange.scss @@ -1,6 +1,4 @@ -$blue: #4a90e2; -$red: #f00; -$yellow: #f7f70c; +$blue: #4990e2; .se-selection-fragment { .sc-accept-reject-container.sc-accept-reject-container-hide { @@ -14,7 +12,6 @@ $yellow: #f7f70c; } .sc-track-change-delete { - // background-color: $red; text-decoration: line-through; } @@ -32,35 +29,26 @@ $yellow: #f7f70c; .sc-track-item-accept::after { color: $blue; - content: 'accept'; + content: 'Accept'; cursor: pointer; display: inline-flex; font-size: 14px; - margin-left: -90px; - margin-top: 20px; - position: absolute; -} - -.sc-track-item-separator::after { - color: $blue; - content: '/'; - cursor: default; - display: inline-flex; - font-size: 14px; - margin-left: -45px; - margin-top: 20px; + margin-left: -20px; + margin-top: 18px; position: absolute; + text-decoration: underline; } .sc-track-item-reject::after { color: $blue; - content: 'reject'; + content: 'Decline'; cursor: pointer; display: inline-flex; font-size: 14px; - margin-left: -35px; - margin-top: 20px; + margin-left: 30px; + margin-top: 18px; position: absolute; + text-decoration: underline; } .sm-target-track-change-enable { diff --git a/app/components/SimpleEditor/miniEditor/miniEditor.js b/app/components/SimpleEditor/miniEditor/miniEditor.js deleted file mode 100644 index 59d82c9fe090e5ff8b9eed1a5ef2abd33fa277a5..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/miniEditor/miniEditor.js +++ /dev/null @@ -1,51 +0,0 @@ -import { - ProseEditor, - Toolbar -} from 'substance' - -import ContainerEditor from '../ContainerEditor' - -class MiniEditor extends ProseEditor { - render ($$) { - const el = $$('div').addClass('sc-mini-editor') - let toolbar = this._renderMiniToolbar($$) - let editor = this._renderEditor($$) - let SplitPane = this.componentRegistry.get('split-pane') - let ScrollPane = this.componentRegistry.get('scroll-pane') - - const contentPanel = $$(ScrollPane, { - name: 'miniEditorContentPanel', - scrollbarPosition: 'right' - }) - .append(editor) - .attr('id', 'content-panel') - .ref('miniEditorContentPanel') - - el.append( - $$(SplitPane, { splitType: 'horizontal' }) - .append(toolbar, contentPanel) - ) - - return el - } - - _renderMiniToolbar ($$) { - let commandStates = this.commandManager.getCommandStates() - return $$('div').addClass('se-toolbar-wrapper').append( - $$(Toolbar, { - commandStates: commandStates, - toolGroups: ['document', 'annotations'] - }).ref('mini_toolbar') - ) - } - - _renderEditor ($$) { - return $$(ContainerEditor, { - editorSession: this.editorSession, - containerId: 'body', - spellcheck: 'native' - }).ref('mini_body') - } -} - -export default MiniEditor diff --git a/app/components/SimpleEditor/miniEditor/miniEditor.scss b/app/components/SimpleEditor/miniEditor/miniEditor.scss deleted file mode 100644 index 08b488faf39b8356f7217e8a671cb707b461e226..0000000000000000000000000000000000000000 --- a/app/components/SimpleEditor/miniEditor/miniEditor.scss +++ /dev/null @@ -1,37 +0,0 @@ -$gray: #591818; -$background: #eee; - -.sc-prose-editor .sc-mini-editor { - border: 1px solid $gray; - height: 160px; - width: 360px; - - .se-toolbar-wrapper { - .sc-toolbar { - background-color: $background; - border: 0; - - .sc-tool-group.sm-layout-horizontal > * { - margin: 0; - } - } - } - - .se-scrollable { - height: 120px; - margin-top: 0; - - .se-content { - background: $background; - color: $gray; - line-height: 20px; - margin: 0; - min-height: 110px; - - .sc-container-editor { - padding: 0; - } - - } - } -} diff --git a/app/components/SimpleEditor/notesEditor/NotesEditor.js b/app/components/SimpleEditor/notesEditor/NotesEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..bfcf53d10ba984ef6d0004d2145470e9e513b77b --- /dev/null +++ b/app/components/SimpleEditor/notesEditor/NotesEditor.js @@ -0,0 +1,168 @@ +import { find, isEmpty, pickBy, values } from 'lodash' +import { ProseEditor } 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' + +class NotesEditor extends ProseEditor { + didMount () { + const provider = this.getProvider() + provider.on('noteSelected', this.scrollTo, this) + + this.editorSession.onUpdate('document', this.findNote, this) + } + + render ($$) { + const el = $$('div').addClass('sc-notes-editor') + + let editor = this._renderEditor($$) + + // TODO -- why are we getting these from the component registry? + 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', 'content-panel-' + this.props.containerId) + .ref('notesEditorContentPanel') + + el.append($$(SplitPane, { sizeA: '100%', splitType: 'horizontal' }) + .append( + contentPanel, + $$('div') // TODO -- why are we appending an empty div at the end? + )) + + return el + } + + _renderEditor ($$) { + const hasIsolatedNotes = this.getIsolatedNodes() + const disabled = (isEmpty(hasIsolatedNotes)) + + return $$(ContainerEditor, { + book: this.props.book, + comments: this.props.comments, + containerId: this.props.containerId, + configurator: this.props.configurator, + editorSession: this.editorSession, + disabled: 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') + // TODO -- we need to be consistent in using camelCase in refs + } + + scrollTo (nodeId) { + const nodes = this.getIsolatedNodes() + + // TODO -- there is a findNote function further down + const note = find(nodes, function (c) { + return c.calloutId === nodeId + }) + + // TODO -- review + if (note) this.refs.notesEditorContentPanel.scrollTo(note.id) + } + + saveNote (isolatedNote) { + const exporter = new SimpleExporter(this.props.configurator.config) + const convertedNode = exporter.convertNode(isolatedNote) + + this.context.editorSession.transaction(function (tx, args) { + const path = [isolatedNote.calloutId, 'note-content'] + tx.set(path, convertedNode.innerHTML.trim()) + }) + } + + 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, (value, key) => { + return value.type === 'isolated-note' + }) + + return values(entries) + } + + getProvider () { + return this.context.notesProvider + } + + getInitialState () { + return { + trackChangesView: this.props.trackChangesView + } + } + + 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 diff --git a/app/components/SimpleEditor/notesEditor/NotesEditorImporter.js b/app/components/SimpleEditor/notesEditor/NotesEditorImporter.js new file mode 100644 index 0000000000000000000000000000000000000000..cfc53cb66ff7f3f3cba44b84ffde84a4cb939395 --- /dev/null +++ b/app/components/SimpleEditor/notesEditor/NotesEditorImporter.js @@ -0,0 +1,75 @@ +import { HTMLImporter } from 'substance' +import SimpleArticle from '../SimpleArticle' + +// TODO -- too much duplication from main importer => refactor +// Would the notes editor work without an importer at all? + +class SimpleImporter extends HTMLImporter { + constructor (config) { + super({ + schema: config.schema, + converters: config.converters, + DocumentClass: SimpleArticle + }) + } + + convertDocument (bodyEls) { + if (!bodyEls.length) bodyEls = [bodyEls] + + this.convertContainer(bodyEls, 'notes') + } + + // TODO -- check substance's implementation of overlapping annotations + + // override substance's internal function to allow for overlapping + // annotations, without adhering to an expand / fuse mode + _createInlineNodes () { + var state = this.state + var doc = state.doc + + /* + substance will break overlapping annotations of the same type into + pieces like this: + + <anno id=1> + <anno id=2></anno> + </anno> + <anno id=2> + </anno> + + when the importer finds the duplicate annotation id, it will remove + the first one altogether as a node from the doc + + here, we are forcing it to keep these duplicates and merge them + */ + + state.inlineNodes.forEach(function (node) { + if (doc.get(node.id)) { + const existing = doc.get(node.id) + const newOne = node + + doc.delete(node.id) + + // only for comments + if (node.id.split('-')[0] === 'comment') { + // if they are adjacent and have the same id, merge the offsets + if ( + existing.startOffset === newOne.endOffset || + existing.endOffset === newOne.startOffset + ) { + node.startOffset = Math.min(existing.startOffset, newOne.startOffset) + node.endOffset = Math.max(existing.endOffset, newOne.endOffset) + + doc.create(node) + } + } else { + doc.create(node) // TODO -- refactor + } + } else { + doc.create(node) + } + }) + } +} + +export default SimpleImporter diff --git a/app/components/SimpleEditor/miniEditor/config.js b/app/components/SimpleEditor/notesEditor/config.js similarity index 63% rename from app/components/SimpleEditor/miniEditor/config.js rename to app/components/SimpleEditor/notesEditor/config.js index e4157d52834d571e43ed166825a3da8c92ca3488..b193becbf3dc9dc1920bf51cd2922059d81d9dab 100644 --- a/app/components/SimpleEditor/miniEditor/config.js +++ b/app/components/SimpleEditor/notesEditor/config.js @@ -2,14 +2,16 @@ import { BasePackage, EmphasisPackage, ParagraphPackage, - PersistencePackage, ProseArticle, StrongPackage, SpellCheckPackage - } from 'substance' -let config = { +import CommentPackage from '../elements/comment/CommentPackage' +import IsolatedNote from '../elements/isolatedNote/IsolatedNotePackage' +import TrackChangePackage from '../elements/track_change/TrackChangePackage' + +const config = { name: 'simple-editor', configure: (config, options) => { config.defineSchema({ @@ -24,8 +26,10 @@ let config = { config.import(ParagraphPackage) config.import(EmphasisPackage) config.import(StrongPackage) - config.import(PersistencePackage) config.import(SpellCheckPackage) + config.import(CommentPackage) + config.import(IsolatedNote) + config.import(TrackChangePackage) } } diff --git a/app/components/SimpleEditor/notesEditor/notesEditor.scss b/app/components/SimpleEditor/notesEditor/notesEditor.scss new file mode 100644 index 0000000000000000000000000000000000000000..9105432ced64bb92d3daefdc936b552dfc1ac4bd --- /dev/null +++ b/app/components/SimpleEditor/notesEditor/notesEditor.scss @@ -0,0 +1,34 @@ +$background: #fff; +$content: #4a4a4a; + +.sc-notes-editor { + margin-top: 6px; + + .se-scrollable { + background: $background; + margin: 0% 0% 5%; + min-height: 135px; + + div.se-content { + background: $background; + box-shadow: none; + color: $content; + font-family: Vollkorn-Italic; + margin: 0; + min-height: 110px; + padding: 0 0 0 2.3%; + + .sc-entity::before { + content: ''counter(note-footer)'. '; + counter-increment: note-footer; + font-family: 'FiraSansCondensed-Bold'; + left: -20px; + position: absolute; + } + + .sc-container-editor { + padding: 0; + } + } + } +} diff --git a/app/components/SimpleEditor/panes/Comments/Comment.js b/app/components/SimpleEditor/panes/Comments/Comment.js index ff9e7c6738074c04286426ea775e0dc4776e9738..171e9e9cd66bb0deb44dd1be9ae9308505a65516 100644 --- a/app/components/SimpleEditor/panes/Comments/Comment.js +++ b/app/components/SimpleEditor/panes/Comments/Comment.js @@ -6,6 +6,7 @@ import { } from 'substance' class Comment extends Component { + // TODO -- what's the deal with resolve icon element? render ($$) { const active = this.props.active let name = this.props.user @@ -29,8 +30,8 @@ class Comment extends Component { .on('click', this.resolve) } - const nameEl = $$('span').addClass('comment-user-name').append(name, ' - ') - const textEl = $$('span').addClass('comment-text').append(text) + const nameEl = $$('div').addClass('comment-user-name').append(name) + const textEl = $$('div').addClass('comment-text').append(text) const entry = $$('div') .addClass('sc-comment-entry') @@ -42,7 +43,7 @@ class Comment extends Component { return $$('div').addClass('single-comment-row') .append( entry, - resolveIconEl + // resolveIconEl ) } diff --git a/app/components/SimpleEditor/panes/Comments/CommentBox.js b/app/components/SimpleEditor/panes/Comments/CommentBox.js index 8e9fa86798ce430340c6295af51038134395acc2..1f1c31ba609dd60580fee3ec086404235c896d27 100644 --- a/app/components/SimpleEditor/panes/Comments/CommentBox.js +++ b/app/components/SimpleEditor/panes/Comments/CommentBox.js @@ -1,8 +1,8 @@ /* eslint react/prop-types: 0 */ import { - Component, - FontAwesomeIcon as Icon + Component + // FontAwesomeIcon as Icon } from 'substance' import CommentList from './CommentList' @@ -17,6 +17,7 @@ class CommentBox extends Component { render ($$) { const { active, entry } = this.props let comments = this.props.comments.data + let name = this.props.user.username if (!active) { comments = comments.slice(0, 1) // preview @@ -37,13 +38,14 @@ class CommentBox extends Component { .on('click', this.makeActive) let resolve = $$('div') + let placeholderMsg = 'add comment' if (active) { if (comments.length > 0) { - const resolveIcon = $$(Icon, { icon: 'fa-check' }) + // const resolveIcon = $$(Icon, { icon: 'fa-check' }) + placeholderMsg = 'reply' resolve = $$('button') - .append(resolveIcon) .attr('title', 'Resolve') .addClass('comment-resolve') .on('click', this.resolve) @@ -51,25 +53,32 @@ class CommentBox extends Component { const textarea = $$('textarea') .attr('rows', '1') + .attr('placeholder', placeholderMsg) .ref('commentReply') .attr('id', entry.id) .on('keydown', this.onKeyDown) .on('input', this.textAreaAdjust) - const replyIcon = $$(Icon, { icon: 'fa-plus' }) + // const replyIcon = $$(Icon, { icon: 'fa-plus' }) // TODO -- refactor classes!!! const reply = $$('button') .attr('disabled', true) .attr('title', 'Reply') .addClass('comment-reply') - .append(replyIcon) + // .append(replyIcon) .ref('commentReplyButton') .on('click', this.reply) + const nameEl = $$('div') + .addClass('comment-user-name') + .append(name) + const inputBox = $$('div') .addClass('sc-new-comment') - .append(textarea, reply, resolve) + .append( + nameEl, textarea, reply, resolve + ) box .addClass('sc-comment-active') diff --git a/app/components/SimpleEditor/panes/Comments/CommentBoxList.js b/app/components/SimpleEditor/panes/Comments/CommentBoxList.js index 164b313c03462649bb2eca7f70a0c2d62d6e7af8..f66fc780760cb4c83631852832be1bf26b174e56 100644 --- a/app/components/SimpleEditor/panes/Comments/CommentBoxList.js +++ b/app/components/SimpleEditor/panes/Comments/CommentBoxList.js @@ -47,13 +47,6 @@ class CommentBoxList extends Component { }) }) - // toggle comments pane - if (listItems.length > 0) { - this.send('showComments') - } else { - this.send('hideComments') - } - return $$('ul') .addClass('sc-comment-pane-list') .append(listItems) @@ -78,10 +71,16 @@ class CommentBoxList extends Component { this.calculateTops(entries, activeEntry) } + // TODO -- why are we looking at css classes? + // TODO move to helper section?? + hasCssClass (element, className) { + return element.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(element.className) + } + calculateTops (entries, active) { let result = [] let boxes = [] - + let self = this _.each(entries, function (entry, pos) { // initialize annotationTop and boxHeight, as there is a chance that // annotation and box elements are not found by the selector @@ -96,7 +95,10 @@ class CommentBoxList extends Component { // get position of annotation in editor const annotationEl = document.querySelector('span[data-id="' + entry.id + '"]') - if (annotationEl) annotationTop = parseInt(annotationEl.offsetTop) + if (annotationEl) { + annotationTop = (self.hasCssClass(annotationEl.offsetParent, 'sc-isolated-node')) + ? annotationEl.offsetParent.offsetTop + annotationEl.offsetTop : annotationEl.offsetTop + } // get height of this comment box const boxEl = document.querySelector('li[data-comment="' + entry.id + '"]') diff --git a/app/components/SimpleEditor/panes/Comments/CommentsProvider.js b/app/components/SimpleEditor/panes/Comments/CommentsProvider.js index ebf24032e3324101ee5804348175b694368e3cec..507001f6b998b7f237bfb1597d440b57c751aefd 100644 --- a/app/components/SimpleEditor/panes/Comments/CommentsProvider.js +++ b/app/components/SimpleEditor/panes/Comments/CommentsProvider.js @@ -32,7 +32,8 @@ class CommentsProvider extends TocProvider { this.emit('comments:updated') } - // TODO -- this will probably not be needed if we stop moving the line height for track changes + // TODO -- this will probably not be needed if we stop moving the + // line height for track changes // offset by the time it takes the animation to change the line height onUiUpdate () { setTimeout(() => { this.update() }, 100) @@ -374,16 +375,18 @@ class CommentsProvider extends TocProvider { } sortNodes (nodes) { - var comments = _.clone(nodes) - var doc = this.getDocument() - var container = doc.get('body') + let comments = _.clone(nodes) + const doc = this.getDocument() + const { containerId } = this.config + const container = doc.get(containerId) // sort notes by // the index of the containing block // their position within that block comments = _.map(comments, function (comment) { const blockId = comment.path[0] - const blockPosition = container.getPosition(blockId) + let blockPosition = container.getPosition(blockId) + const nodePosition = comment.start.offset return { diff --git a/app/components/SimpleEditor/panes/Comments/commentsPane.scss b/app/components/SimpleEditor/panes/Comments/commentsPane.scss index 98205dcbf6264cfc75fe9d42ddb2585488a35a3c..c2966038ff23e3ffed163d253edcd24923d4271e 100644 --- a/app/components/SimpleEditor/panes/Comments/commentsPane.scss +++ b/app/components/SimpleEditor/panes/Comments/commentsPane.scss @@ -3,109 +3,130 @@ $dark-gray: #a2a2a2; $light-gray: #868686; $shadow-gray: #ccc; -$green: #228b46; -$teal: #3e644b; +$blue: #4990e2; $white: #fff; - -.sc-prose-editor .se-content .sc-comment-pane-list { +$yellow: #ffab20; + +.sc-comment-pane-list { + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-family: 'FiraSansCondensed-Bold'; + margin-left: 7%; margin-top: 0; - padding-left: 1%; - width: 37.5%; + width: 100%; li { - background-color: $white; - border: 1px solid $dark-gray; + background-color: transparent; border-bottom: 0; border-radius: 3px; cursor: pointer; height: auto; list-style-type: none; - max-width: 3660%; + max-width: 50%; min-height: 20px; - min-width: 3660%; + min-width: 50%; position: absolute; word-wrap: break-word; z-index: 0; .sc-new-comment { - margin: 5px 10px 5px 5px; + bottom: 10px; + margin: 0 10px 5px 5px; + position: relative; + .comment-user-name { + color: $yellow; + font-size: 14px; + font-weight: bold; + margin-left: 8px; + } } textarea { - background-color: $gray; - border: 1px solid $dark-gray; - border-radius: 3px; + background-color: $white; + border-bottom: 1px dashed $yellow; + border-left: 1px dashed $yellow; + border-right: 0; + border-top: 0; + bottom: 5px; display: block; font-family: 'Fira Sans'; font-size: 14px; - margin-bottom: 5px; + margin: 0 0 5px 12px; outline: none; overflow: hidden; + position: relative; resize: none; width: 100%; } .comment-reply { - background-color: $teal; - border: 0; - border-radius: 50%; - color: $white; + background-color: $yellow; + cursor: pointer; float: right; height: 25px; margin-bottom: 5px; position: relative; right: -4px; - text-align: center; - transition: .2s; width: 25px; + -webkit-mask: url('./elements/icons/addComment.svg') no-repeat 100% 100%; + mask: url('./elements/icons/addComment.svg') no-repeat 100% 100%; + -webkit-mask-size: cover; + mask-size: cover; &:disabled { background-color: $shadow-gray; + -webkit-mask: url('./elements/icons/addComment.svg') no-repeat 100% 100%; + mask: url('./elements/icons/addComment.svg') no-repeat 100% 100%; + -webkit-mask-size: cover; + mask-size: cover; } } .comment-resolve { - background-color: $teal; - border: 0; - border-radius: 50%; - color: $white; + background-color: $blue; + cursor: pointer; float: right; height: 25px; margin-bottom: 5px; margin-right: 5px; - text-align: center; width: 25px; + -webkit-mask: url('./elements/icons/resolveAllComments.svg') no-repeat 100% 100%; + mask: url('./elements/icons/resolveAllComments.svg') no-repeat 100% 100%; + -webkit-mask-size: cover; + mask-size: cover; } .comment-list { font-size: 14px; .single-comment-row { - border-bottom: 1px solid $dark-gray; - // padding: 10px 12px; + line-height: 15px; padding: 3px 12px; .sc-comment-entry { - width: 88%; + width: 100%; } } .comment-user-name { + color: $yellow; font-weight: bold; i { font-weight: 100; } } + + .comment-text { + border-left: 1px solid $dark-gray; + margin-left: 5px; + padding-left: 5px; + } } } .sc-comment-active { - border-bottom: 1px solid $dark-gray; - border-left: 1px solid $dark-gray; - border-right: 1px solid $dark-gray; - border-top: 1px solid $dark-gray; - box-shadow: 4px 4px 4px $shadow-gray; left: -30px; .sc-comment-entry { @@ -135,10 +156,10 @@ $white: #fff; width: 25px; &:hover { - border-color: $teal; + border-color: $blue; i { - color: $teal; + color: $blue; } } @@ -148,20 +169,4 @@ $white: #fff; } } } - - .commnets-bubble { - bottom: 0; - color: $white; - font-size: 19px; - left: 208px; - position: absolute; - top: -21.4px; - } - - // .animation { - // // -webkit-transition: all .2s ease-in-out; - // // -moz-transition: all .2s ease-in-out; - // transition: all .5s ease-in-out; - // // -o-transition: all .2s ease-in-out; - // } } diff --git a/app/components/SimpleEditor/panes/Notes/Notes.js b/app/components/SimpleEditor/panes/Notes/Notes.js index be97a2b8c1ef5f0f95b4b4e3f69bbce187fccf1e..e404563d9ca6a54e86e9ebdadcc7b5663d9dbbe3 100644 --- a/app/components/SimpleEditor/panes/Notes/Notes.js +++ b/app/components/SimpleEditor/panes/Notes/Notes.js @@ -1,89 +1,192 @@ -import { Component } from 'substance' +/* eslint react/prop-types: 0 */ + +import { + Component, + EditorSession, + ProseEditorConfigurator as Configurator +} from 'substance' + +import NotesEditor from '../../notesEditor/NotesEditor' +import config from '../../notesEditor/config' +import Importer from '../../notesEditor/NotesEditorImporter' +import SimpleExporter from '../../SimpleEditorExporter' +import { isEmpty, find } from 'lodash' class Notes extends Component { - // use toc:updated to avoid rewriting TOCProvider's this.handleDocumentChange + constructor (props) { + super(props) + + // TODO -- do we need the binds? + this.resize = this.resize.bind(this) + this.stopResize = this.stopResize.bind(this) + + // TODO -- ?? + this.notesPaneHeight + } + didMount () { - this.context.editorSession.onUpdate('document', this.onNotesUpdated, this) + const { editor, editorSession } = this.context + + editorSession.onUpdate('document', this.onNotesUpdated, this) + + // TODO -- why do we emit this? + editor.emit('ui:updated') + } + + didUpdate () { + // TODO -- could be cleaner? + this.el.el.style.height = this.notesPaneHeight + 'px' + this.computeMainPane() } render ($$) { - const provider = this.getProvider() - const entries = provider.getEntries() - let self = this - - const listItems = entries.map(function (entry, i) { - let extractedElement = '' - if (entry.content) { - extractedElement = self.parseEntryContent($$, entry.content) - return extractedElement - .attr('data-id', entry.id) - .addClass('sc-notes-footer-item') - } - return $$('li') - .attr('data-id', entry.id) - .addClass('sc-notes-footer-item') - .append(extractedElement) + const { + book, + comments, + containerId, + disabled, + fragment, + history, + trackChanges, + trackChangesView, + update, + user + } = this.props + + const { editorSession, configurator } = this._initNotesEditor() + + const title = $$('span').append('Notes') + + const titleContainer = $$('div') + .addClass('notes-title') + .append(title) + + const resizer = $$('div').addClass('resize-area') + + const el = $$('div') + .addClass('notes-container') + .append(titleContainer) + .append(resizer) + + // TODO -- if not fragment, why return anything? should come first + if (!fragment) return el + + const notesEditor = $$(NotesEditor, { + book, + editorSession, + comments, + configurator, + containerId, + history, + disabled, + fragment, + trackChanges, + trackChangesView, + update, + user }) - if (listItems.length === 0) return $$('div') - return $$('ol') - .addClass('sc-notes-footer') - .append(listItems) + el.append(notesEditor) + + // TODO -- needs to be in render? + resizer.addEventListener('mousedown', this.initResize, false) + + return el } - parseEntryContent ($$, content) { - let parser = new DOMParser() - let parsedContent = parser.parseFromString(content, 'text/html').body - let parentElement = parsedContent.childNodes[0] - let children = parentElement.childNodes - let constructedElement = $$('li') - - for (let i = 0; i < children.length; i++) { - if (children[i].nodeName === '#text') { - constructedElement.append(children[i].data) - } else { - let contructedChildElement = $$(children[i].nodeName) - // Case of nested styling, first contruct the sub child node - if (children[i].children.length === 1) { - let contructedSubChildElement = $$(children[i].children[0].nodeName) - this.assignVirtualElementClass(children[i].children[0], contructedSubChildElement) - this.assignVirtualElementClass(children[i], contructedChildElement) - - contructedSubChildElement.append(children[i].children[0].innerText) - contructedChildElement.append(contructedSubChildElement) - } else { - this.assignVirtualElementClass(children[i], contructedChildElement) - contructedChildElement.append(children[i].innerText) - } - - constructedElement.append(contructedChildElement) - } + // TODO -- avoid function names with underscore + _initNotesEditor () { + const configurator = new Configurator().import(config) + configurator.addImporter('html', Importer) + const importer = configurator.createImporter('html') + + const provider = this.getProvider() + + const notes = provider.computeEntries() + const exporter = new SimpleExporter(configurator.config) + let noteContent = '' + + for (var i = 0; i < notes.length; i++) { + let isolatedNoteElement = exporter.createElement('isolated-note') + isolatedNoteElement.setAttribute('data-parent-id', notes[i].id) + isolatedNoteElement.innerHTML = notes[i]['note-content'] + noteContent += isolatedNoteElement.outerHTML } - return constructedElement - } + const doc = importer.importDocument(noteContent) + + const editorSession = new EditorSession(doc, { + configurator: configurator + }) - assignVirtualElementClass (DOMElement, virtualElement) { - switch (DOMElement.nodeName) { - case 'STRONG': - virtualElement.addClass('sc-strong') - break - case 'EM': - virtualElement.addClass('sc-emphasis') - break + return { + editorSession: editorSession, + configurator: configurator } } - getProvider () { - return this.context.notesProvider + // TODO -- if we don't use the event, it shouldn't be in the args + initResize (e) { + window.addEventListener('mousemove', this.resize, false) + window.addEventListener('mouseup', this.stopResize, false) + } + + // TODO -- should be cleaner + resize (e) { + const {containerId} = this.props + this.notesPaneHeight = (this.el.el.offsetHeight + this.el.el.offsetTop - e.clientY) + const scrollPane = document.getElementById('content-panel-' + containerId).children + scrollPane[0].style.minHeight = this.notesPaneHeight - 40 + 'px' + + this.el.el.style.height = this.notesPaneHeight + 'px' + + this.computeMainPane() + } + + stopResize (e) { + window.removeEventListener('mousemove', this.resize, false) + window.removeEventListener('mouseup', this.stopResize, false) + } + + // TODO -- rename + // compute what of main pane? + // and what is the main pane? the notes pane? + computeMainPane () { + const {containerId} = this.context.editor.props + const mainPane = document.getElementById('content-panel-' + containerId) + mainPane.style.height = this.el.el.offsetTop - 175 + 'px' } onNotesUpdated (change) { const notesProvider = this.getProvider() notesProvider.handleDocumentChange(change) - this.rerender() + + // TODO -- use es6 arrow functions + const noteCreated = find(change.created, function (value, key) { + return value.type === 'note' + }) + + const noteDeleted = find(change.deleted, function (value, key) { + return value.type === 'note' + }) + + // TODO -- needs comments + if (!isEmpty(noteCreated) || !isEmpty(noteDeleted)) { + this.rerender() + + if (!isEmpty(noteCreated)) { + notesProvider.calloutSelected(noteCreated.id) + } + + return false + } + } + + getProvider () { + return this.context.notesProvider } + // TODO -- do we need this? dispose () { const provider = this.getProvider() provider.off(this) diff --git a/app/components/SimpleEditor/panes/Notes/NotesProvider.js b/app/components/SimpleEditor/panes/Notes/NotesProvider.js index ba3fe34e032a426b4b1eaad66c4f7e03862dfb30..69fe9ba587bc41f4ae0c43bfef0e5c794c735e4a 100644 --- a/app/components/SimpleEditor/panes/Notes/NotesProvider.js +++ b/app/components/SimpleEditor/panes/Notes/NotesProvider.js @@ -1,5 +1,4 @@ -import _ from 'lodash' - +import { clone, map, pickBy, sortBy } from 'lodash' import { TOCProvider } from 'substance' class NotesProvider extends TOCProvider { @@ -8,38 +7,48 @@ class NotesProvider extends TOCProvider { const nodes = doc.getNodes() // get all notes from the document - const notes = _.pickBy(nodes, function (value, key) { + const noteNodes = pickBy(nodes, (value, key) => { return value.type === 'note' }) - const entries = this.sortNodes(notes) - return entries + const sortedNoteEntries = this.sortNodes(noteNodes) + + return sortedNoteEntries } sortNodes (nodes) { - let notes = _.clone(nodes) + const { containerId } = this.config + + let notes = clone(nodes) const doc = this.getDocument() - const container = doc.get('body') + const container = doc.get(containerId) // sort notes by // the index of the containing block // their position within that block - notes = _.map(notes, function (note) { + notes = map(notes, function (note) { const blockId = note.path[0] const blockPosition = container.getPosition(blockId) const nodePosition = note.start.offset + // TODO -- what is going on here? + // we used to have 'content' instead of 'note-content' + // and the node itself as a property of this object + // they're gone return { id: note.id, - content: note['note-content'], + 'note-content': note['note-content'], blockPosition: blockPosition, - nodePosition: nodePosition, - node: note + nodePosition: nodePosition } }) - return _.sortBy(notes, ['blockPosition', 'nodePosition']) + return sortBy(notes, ['blockPosition', 'nodePosition']) + } + + calloutSelected (noteId) { + this.emit('noteSelected', noteId) } } diff --git a/app/components/SimpleEditor/panes/Notes/notes.scss b/app/components/SimpleEditor/panes/Notes/notes.scss index fa7849cac47f8ffad1b23d3bc415a4724849a8ed..74eb418139ef3919e6f792755c3b3adf0d984f52 100644 --- a/app/components/SimpleEditor/panes/Notes/notes.scss +++ b/app/components/SimpleEditor/panes/Notes/notes.scss @@ -1,14 +1,44 @@ -$gray: #ddd; +$white: #fff; +$light-blue: #e4f0ff; +$black: #000; +$bg-rgba: rgba(255, 255, 255, 0); +$grey: #a3a3a3; -.sc-notes-footer { - border-top: 1px solid $gray; + +.sc-split-pane .notes-container { + background-color: $white; + bottom: 0; counter-reset: note-footer; - font-size: 14px; - list-style-type: none; - padding-top: 25px; -} - -.sc-notes-footer-item::before { - content: ''counter(note-footer)'. '; - counter-increment: note-footer; -} + height: 135px; + left: 19.6%; + max-height: 700px; + min-height: 135px; + position: fixed; + width: 75.4%; + z-index: 0; + + .notes-title { + background: $white; + color: $grey; + position: absolute; + top: -19px; + width: 65px; + z-index: 9999; + + span { + margin-left: 10px; + } + } + + .resize-area { + background-image: linear-gradient(to right, $black 30%, $bg-rgba 0%); + background-position: top; + background-repeat: repeat-x; + background-size: 10px 1px; + cursor: row-resize; + height: 10px; + position: absolute; + top: -6px; + width: 100%; + } + }