Completing functionality for tags
This commit is contained in:
parent
a2d8b92d22
commit
49eb07102d
|
@ -11,13 +11,13 @@
|
|||
"author": "Alex Hunt",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.1.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"draft-js": "^0.10.4",
|
||||
"draft-js-export-html": "^1.2.0",
|
||||
"draft-js-import-html": "^1.2.1",
|
||||
"font-awesome": "^4.7.0",
|
||||
"history": "^4.7.2",
|
||||
"popper.js": "^1.12.6",
|
||||
"popper.js": "^1.14.5",
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
|
@ -25,7 +25,10 @@
|
|||
"react-router": "^4.2.0",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"redux": "^3.7.2"
|
||||
"redux": "^3.7.2",
|
||||
"webpack": "^4.25.1",
|
||||
"webpack-bundle-analyzer": "^2.13.1",
|
||||
"webpack-cli": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
|
|
|
@ -5,11 +5,16 @@
|
|||
|
||||
const inputActions = {
|
||||
// Handle changes to the document title input box
|
||||
updateDocumentTitleChange: documentTitleInput =>
|
||||
updateDocumentTitleInput: documentTitleInput =>
|
||||
({
|
||||
type: 'UPDATE_DOCUMENT_TITLE',
|
||||
data: documentTitleInput.target.value
|
||||
}),
|
||||
updateColorPickerInput: colorPickerInput =>
|
||||
({
|
||||
type: 'UPDATE_COLOR_PICKER',
|
||||
data: colorPickerInput.target.value
|
||||
}),
|
||||
// Handle changes to the search input box
|
||||
updateSearchInput: searchInput =>
|
||||
({
|
||||
|
|
|
@ -18,12 +18,13 @@ const userActions = {
|
|||
docType: docType
|
||||
}),
|
||||
// Click 'Submit' to save a document edit
|
||||
submitDocumentEdit: (currentDocument, editorState, documentTitleInput, docType) =>
|
||||
submitDocumentEdit: (currentDocument, editorState, documentTitleInput, colorPickerInput, docType) =>
|
||||
({
|
||||
type: 'SUBMIT_DOCUMENT_EDIT',
|
||||
currentDocument: currentDocument,
|
||||
editorState: editorState,
|
||||
documentTitleInput: documentTitleInput,
|
||||
colorPickerInput: colorPickerInput,
|
||||
docType: docType
|
||||
}),
|
||||
// Click 'Cancel' to discard document changes
|
||||
|
@ -43,6 +44,12 @@ const userActions = {
|
|||
docType: docType
|
||||
|
||||
}),
|
||||
// Select a color swatch to assign to a tag
|
||||
selectColorSwatch: color =>
|
||||
({
|
||||
type: 'SELECT_COLOR_SWATCH',
|
||||
data: color
|
||||
}),
|
||||
// Toggle the button to hide or display note highlights
|
||||
toggleHighlight: () =>
|
||||
({
|
||||
|
@ -78,10 +85,11 @@ const userActions = {
|
|||
data: data
|
||||
}),
|
||||
// Click 'Submit' to save annotation
|
||||
submitAnnotation: (annotationNote, selectionState, editorState) =>
|
||||
submitAnnotation: (annotationNote, annotationTag, selectionState, editorState) =>
|
||||
({
|
||||
type: 'SUBMIT_ANNOTATION',
|
||||
annotationNote: annotationNote,
|
||||
annotationTag: annotationTag,
|
||||
selectionState: selectionState,
|
||||
editorState: editorState
|
||||
}),
|
||||
|
@ -98,6 +106,16 @@ const userActions = {
|
|||
type: 'SELECT_ANNOTATION_NOTE',
|
||||
id: id
|
||||
}),
|
||||
// Select a tag from the drop down to go with your annotation
|
||||
selectAnnotationTag: tag =>
|
||||
({
|
||||
type: 'SET_ANNOTATION_TAG',
|
||||
data: tag
|
||||
}),
|
||||
clearAnnotationTag: () =>
|
||||
({
|
||||
type: 'CLEAR_ANNOTATION_TAG'
|
||||
}),
|
||||
}
|
||||
|
||||
export default userActions
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import TagColorPreview from './tagColorPreview'
|
||||
import romanize from '../modules/romanize'
|
||||
import helpers from '../modules/helpers'
|
||||
|
||||
|
@ -38,6 +39,7 @@ export const NoteButton = ({note, currentNote, onClick}) =>
|
|||
export const TagButton = ({tag, currentTag, onClick}) =>
|
||||
<div className ='tag_button'>
|
||||
<button onClick={onClick} className={currentTag.id === tag.id ? 'btn btn-info' : 'btn btn-outline-info inactive_button'}>
|
||||
<TagColorPreview color={tag.color}/>
|
||||
{tag.title}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,11 @@ import React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
|
||||
import { EditorSubmitButton, EditorCancelButton } from './button'
|
||||
import TagColorPreview from './tagColorPreview'
|
||||
import { DocumentList } from './list'
|
||||
import helpers from '../modules/helpers'
|
||||
|
||||
const ChooseAnnotationModal = ({notes, annotationNote, onSubmitClick, selectAnnotationNote}) =>
|
||||
const ChooseAnnotationModal = ({notes, tags, annotationNote, annotationTag, onSubmitClick, selectAnnotationNote, selectAnnotationTag, clearAnnotationTag}) =>
|
||||
<div className='modal fade' id='annotate_modal' tabIndex='-1' role='dialog'>
|
||||
<div className='modal-dialog modal-lg' role='document'>
|
||||
<div className='modal-content'>
|
||||
|
@ -18,9 +19,33 @@ const ChooseAnnotationModal = ({notes, annotationNote, onSubmitClick, selectAnno
|
|||
<DocumentList docs={notes} currentDocument={annotationNote} onDocumentClick={selectAnnotationNote} docType={'notes'}/>
|
||||
</div>
|
||||
<div className='col-md-8 offset-md-1'>
|
||||
<div dangerouslySetInnerHTML={{__html: annotationNote.text}} />
|
||||
<div dangerouslySetInnerHTML={{__html: annotationNote.html_source}} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div id='annotation_tag_dropdown' className='col-md-2'>
|
||||
<button className={annotationNote.id ? 'btn btn-primary dropdown-toggle caret-off' : 'btn btn-primary dropdown-toggle caret-off disabled'} data-toggle='dropdown' type='button'><i className='fa fa-chevron-down'></i>Tags</button>
|
||||
<div className='dropdown-menu'>
|
||||
{tags.map(tag =>
|
||||
<div key={tag.id} className='dropdown-item'>
|
||||
<TagColorPreview color={tag.color}/>
|
||||
<a className='select_annotation_tag' href='#' onClick={()=>selectAnnotationTag(tag)}>{tag.title}</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div id='annotation_tag_preview' className='col-md-8 offset-md-1'>
|
||||
{annotationTag.id &&
|
||||
<div id='selected_annotation_tag'>
|
||||
<TagColorPreview color={annotationTag.color}/>
|
||||
{annotationTag.title}
|
||||
<a id='clear_anntation_tag' href='#' onClick={clearAnnotationTag}>
|
||||
<i className='fa fa-times'></i>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<EditorCancelButton />
|
||||
|
@ -33,8 +58,10 @@ const ChooseAnnotationModal = ({notes, annotationNote, onSubmitClick, selectAnno
|
|||
ChooseAnnotationModal.propTypes = {
|
||||
notes: PropTypes.arrayOf(PropTypes.object),
|
||||
annotationNote: PropTypes.object,
|
||||
annotationTag: PropTypes.object,
|
||||
onSubmitClick: PropTypes.func,
|
||||
selectAnnotationNote: PropTypes.func,
|
||||
selectAnnotationTag: PropTypes.func,
|
||||
}
|
||||
|
||||
export default ChooseAnnotationModal
|
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types'
|
|||
|
||||
import { ReaderAnnotateButton, ReaderEditButton, EditorToolButton, EditorDeleteToolButton, AnnotatorNewButton, AnnotatorRemoveButton } from './button'
|
||||
|
||||
export const ReadModeTopBar = ({setMode}) =>
|
||||
export const ReadModeTopBar = ({docType, setMode}) =>
|
||||
<div className='row'>
|
||||
<div className='col-sm-5'>
|
||||
<ReaderAnnotateButton onClick={()=>setMode('ANNOTATE_MODE')}/>
|
||||
</div>
|
||||
<div className='col-sm-5 offset-sm-2'>
|
||||
{['chapters', 'notes'].indexOf(docType) >= 0 &&
|
||||
<div className='col-sm-5'>
|
||||
<ReaderAnnotateButton onClick={()=>setMode('ANNOTATE_MODE')}/>
|
||||
</div>
|
||||
}
|
||||
<div className='col-sm-5 ml-auto'>
|
||||
<ReaderEditButton onClick={()=>setMode('EDIT_MODE')} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Editor } from 'draft-js'
|
||||
|
||||
import { ReadModeTopBar, EditModeTopBar, AnnotateModeTopBar } from '../components/contentTopBar'
|
||||
import { EditModeBottomBar } from '../components/contentBottomBar'
|
||||
import actions from '../actions'
|
||||
|
@ -15,7 +19,6 @@ const EditorAnnotateMode = ({
|
|||
onRemoveAnnotationClick,
|
||||
cancelEdit,
|
||||
onSubmitClick,
|
||||
documentTitleInput
|
||||
}) =>
|
||||
<div>
|
||||
<div id='editor_metadata'>
|
||||
|
@ -32,7 +35,7 @@ const EditorAnnotateMode = ({
|
|||
removeDisabled={(editorState.getSelection().isCollapsed() ) ? true : false}
|
||||
/>
|
||||
</div>
|
||||
<div id='editor_content'>
|
||||
<div id='editor_content' className={docType === 'tags' ? 'short_editor' : 'tall_editor'}>
|
||||
<Editor editorState={editorState} onChange={onChangeEditorState} keyBindingFn={annotateKeyBindings} />
|
||||
</div>
|
||||
<div id='editor_bottombar'>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Editor } from 'draft-js'
|
|||
|
||||
import { EditModeTopBar } from '../components/contentTopBar'
|
||||
import { EditModeBottomBar } from '../components/contentBottomBar'
|
||||
import TagColorPicker from '../components/tagColorPicker'
|
||||
import actions from '../actions'
|
||||
import DocumentTitle from '../components/documentTitle'
|
||||
import LoadingSpinner from '../components/loadingSpinner'
|
||||
|
@ -19,8 +20,12 @@ const EditorEditMode = ({
|
|||
setMode,
|
||||
cancelEdit,
|
||||
onSubmitClick,
|
||||
colorPickerInput,
|
||||
documentTitleInput,
|
||||
onDocumentTitleChange
|
||||
onDocumentTitleChange,
|
||||
onColorPickerInputChange,
|
||||
onColorSwatchClick,
|
||||
userErrors,
|
||||
}) =>
|
||||
<div>
|
||||
<div id='editor_metadata'>
|
||||
|
@ -36,16 +41,26 @@ const EditorEditMode = ({
|
|||
disabled={!currentDocument.id ? true : false}
|
||||
/>
|
||||
</div>
|
||||
<div id='editor_content'>
|
||||
<div id='editor_content' className={docType === 'tags' ? 'short_editor' : 'tall_editor'}>
|
||||
<Editor
|
||||
editorState={editorState}
|
||||
handleKeyCommand={handleKeyCommand}
|
||||
onChange={onChangeEditorState}
|
||||
/>
|
||||
</div>
|
||||
<div id='editor_attributes'>
|
||||
{docType === 'tags' &&
|
||||
<TagColorPicker colorPickerInput={colorPickerInput} onChange={onColorPickerInputChange} onColorSwatchClick={onColorSwatchClick}/>
|
||||
}
|
||||
</div>
|
||||
<div id='editor_bottombar'>
|
||||
<EditModeBottomBar cancelEdit={cancelEdit} onSubmitClick={onSubmitClick} />
|
||||
</div>
|
||||
<div id='user_errors'>
|
||||
{userErrors.map(error =>
|
||||
<div key={error} className='user_error_message'>{error}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default EditorEditMode
|
|
@ -16,16 +16,16 @@ const EditorReadMode = ({
|
|||
}) =>
|
||||
<div id='editor_read_mode'>
|
||||
<div id='editor_metadata'>
|
||||
{loadingToggle === true &&
|
||||
<LoadingSpinner />
|
||||
}
|
||||
<DocumentTitle docType={docType} currentDocument={currentDocument} />
|
||||
{loadingToggle === true &&
|
||||
<LoadingSpinner />
|
||||
}
|
||||
<DocumentTitle docType={docType} currentDocument={currentDocument} />
|
||||
</div>
|
||||
<div id='editor_topbar'>
|
||||
<ReadModeTopBar setMode={setMode} />
|
||||
<ReadModeTopBar docType={docType} setMode={setMode} />
|
||||
</div>
|
||||
<div id='editor_content'>
|
||||
<Editor editorState={editorState} readOnly={true} />
|
||||
<div id='editor_content' className={docType === 'tags' ? 'short_editor' : 'tall_editor'}>
|
||||
<Editor editorState={editorState} readOnly={true}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react'
|
||||
|
||||
// import defaultTagColors from '../modules/editorSettings'
|
||||
|
||||
const defaultTagColors = [
|
||||
'307EE3',
|
||||
'CF2929',
|
||||
'AB59C2',
|
||||
'9C632A',
|
||||
'F59627',
|
||||
'40b324'
|
||||
]
|
||||
|
||||
const TagColorPicker = ({colorPickerInput, onChange, onColorSwatchClick}) =>
|
||||
<div className='input-group'>
|
||||
<div className='input-group-prepend'>
|
||||
<span className='input-group-text' id='basic-addon1'>#</span>
|
||||
</div>
|
||||
<input type='text' className='form-control' placeholder='Color' value={colorPickerInput} onChange={onChange}/>
|
||||
<div className='input-group-append'>
|
||||
<button className='btn btn-primary dropdown-toggle caret-off' data-toggle='dropdown' type='button'><i className='fa fa-chevron-down'></i></button>
|
||||
<div className='dropdown-menu'>
|
||||
{defaultTagColors.map(color =>
|
||||
<div key={color} className='dropdown-item'>
|
||||
<a className='color_preview' href='#' style={{backgroundColor: '#' + color }} onClick={()=>onColorSwatchClick(color)}></a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default TagColorPicker
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const TagColorPreview = ({color}) =>
|
||||
<div className='tag_button_color_preview' style={{backgroundColor: '#' + color }}>
|
||||
</div>
|
||||
|
||||
export default TagColorPreview
|
||||
|
||||
TagColorPreview.propTypes = {
|
||||
color: PropTypes.string,
|
||||
}
|
|
@ -19,11 +19,15 @@ const EditorContent = ({
|
|||
setMode,
|
||||
cancelEdit,
|
||||
onSubmitClick,
|
||||
onColorSwatchClick,
|
||||
documentTitleInput,
|
||||
colorPickerInput,
|
||||
onDocumentTitleChange,
|
||||
onColorPickerInputChange,
|
||||
onNewAnnotationClick,
|
||||
annotateKeyBindings,
|
||||
onRemoveAnnotationClick,
|
||||
userErrors,
|
||||
}) =>
|
||||
<div id='editor_container'>
|
||||
{mode === 'READ_MODE' &&
|
||||
|
@ -42,13 +46,14 @@ const EditorContent = ({
|
|||
docType={docType}
|
||||
loadingToggle={loadingToggle}
|
||||
handleKeyCommand={handleKeyCommand}
|
||||
onNewAnnotationClick={()=>onNewAnnotationClick(editorState.getSelection())}
|
||||
onRemoveAnnotationClick={()=>onRemoveAnnotationClick(editorState)}
|
||||
annotateKeyBindings={annotateKeyBindings}
|
||||
onChangeEditorState={onChangeEditorState}
|
||||
onToolButtonClick={onToolButtonClick}
|
||||
setMode={setMode}
|
||||
cancelEdit={cancelEdit}
|
||||
onSubmitClick={()=>onSubmitClick(currentDocument, editorState, documentTitleInput, docType)}
|
||||
documentTitleInput={documentTitleInput}
|
||||
onDocumentTitleChange={onDocumentTitleChange}
|
||||
onSubmitClick={()=>onSubmitClick(currentDocument, editorState, documentTitleInput, colorPickerInput, docType)}
|
||||
/>
|
||||
}
|
||||
{mode === 'EDIT_MODE' &&
|
||||
|
@ -58,12 +63,14 @@ const EditorContent = ({
|
|||
docType={docType}
|
||||
loadingToggle={loadingToggle}
|
||||
onChangeEditorState={onChangeEditorState}
|
||||
onNewAnnotationClick={()=>onNewAnnotationClick(editorState.getSelection())}
|
||||
annotateKeyBindings={annotateKeyBindings}
|
||||
onRemoveAnnotationClick={()=>onRemoveAnnotationClick(editorState)}
|
||||
cancelEdit={cancelEdit}
|
||||
onSubmitClick={()=>onSubmitClick(currentDocument, editorState, documentTitleInput, docType)}
|
||||
onSubmitClick={()=>onSubmitClick(currentDocument, editorState, documentTitleInput, colorPickerInput, docType)}
|
||||
documentTitleInput={documentTitleInput}
|
||||
colorPickerInput={colorPickerInput}
|
||||
onColorSwatchClick={onColorSwatchClick}
|
||||
onDocumentTitleChange={onDocumentTitleChange}
|
||||
onColorPickerInputChange={onColorPickerInputChange}
|
||||
userErrors={userErrors}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
@ -75,7 +82,9 @@ const mapStateToProps = (state, props) => {
|
|||
docType: state.docType,
|
||||
editorState: state.editorState,
|
||||
documentTitleInput: state.documentTitleInput,
|
||||
loadingToggle: state.loadingToggle
|
||||
colorPickerInput: state.colorPickerInput,
|
||||
loadingToggle: state.loadingToggle,
|
||||
userErrors: state.userErrors,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +94,10 @@ const mapDispatchToProps = dispatch => {
|
|||
dispatch(actions.updateEditorState(editorState))
|
||||
},
|
||||
onDocumentTitleChange: documentTitleInput => {
|
||||
dispatch(actions.updateDocumentTitleChange(documentTitleInput))
|
||||
dispatch(actions.updateDocumentTitleInput(documentTitleInput))
|
||||
},
|
||||
onColorPickerInputChange: colorPickerInput => {
|
||||
dispatch(actions.updateColorPickerInput(colorPickerInput))
|
||||
},
|
||||
handleKeyCommand: (command, editorState) => {
|
||||
dispatch(actions.handleEditorKeyCommand(editorState, command))
|
||||
|
@ -100,6 +112,9 @@ const mapDispatchToProps = dispatch => {
|
|||
cancelEdit: () => {
|
||||
dispatch(actions.cancelEdit())
|
||||
},
|
||||
onColorSwatchClick: (color) => {
|
||||
dispatch(actions.selectColorSwatch(color))
|
||||
},
|
||||
onNewAnnotationClick: (selectionState) => {
|
||||
dispatch(actions.addAnnotation(selectionState))
|
||||
},
|
||||
|
@ -109,8 +124,8 @@ const mapDispatchToProps = dispatch => {
|
|||
onToolButtonClick: (editorState, style) => {
|
||||
dispatch(actions.applyInlineStyles(editorState, style))
|
||||
},
|
||||
onSubmitClick: (currentDocument, editorState, documentTitleInput, docType) => {
|
||||
dispatch(actions.submitDocumentEdit(currentDocument, editorState, documentTitleInput, docType))
|
||||
onSubmitClick: (currentDocument, editorState, documentTitleInput, colorPickerInput, docType) => {
|
||||
dispatch(actions.submitDocumentEdit(currentDocument, editorState, documentTitleInput, colorPickerInput, docType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,16 @@ const EditorPage = ({
|
|||
notes,
|
||||
currentDocument,
|
||||
annotationNote,
|
||||
annotationTag,
|
||||
editorState,
|
||||
docType,
|
||||
tags,
|
||||
loadingToggle,
|
||||
onDeleteConfirm,
|
||||
onSubmitAnnotationClick,
|
||||
selectAnnotationNote,
|
||||
selectAnnotationTag,
|
||||
clearAnnotationTag,
|
||||
selectionState,
|
||||
}) =>
|
||||
<div id='joyce_reader' className='container-fluid'>
|
||||
|
@ -41,15 +45,26 @@ const EditorPage = ({
|
|||
</Content>
|
||||
</div>
|
||||
<DeleteConfirmModal onDeleteConfirm={()=>onDeleteConfirm(currentDocument.id, docType)}/>
|
||||
<AnnotationModal annotationNote={annotationNote} />
|
||||
<ChooseAnnotationModal notes={notes} annotationNote={annotationNote} onSubmitClick={()=>onSubmitAnnotationClick(annotationNote, selectionState, editorState)} selectAnnotationNote={selectAnnotationNote} />
|
||||
<AnnotationModal annotationNote={annotationNote}/>
|
||||
<ChooseAnnotationModal
|
||||
notes={notes}
|
||||
tags={tags}
|
||||
annotationNote={annotationNote}
|
||||
annotationTag={annotationTag}
|
||||
onSubmitClick={()=>onSubmitAnnotationClick(annotationNote, annotationTag, selectionState, editorState)}
|
||||
selectAnnotationNote={selectAnnotationNote}
|
||||
selectAnnotationTag={selectAnnotationTag}
|
||||
clearAnnotationTag={clearAnnotationTag}
|
||||
/>
|
||||
</div>
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
notes: state.notes,
|
||||
tags: state.tags,
|
||||
currentDocument: state.currentDocument,
|
||||
annotationNote: state.annotationNote,
|
||||
annotationTag: state.annotationTag,
|
||||
editorState: state.editorState,
|
||||
selectionState: state.selectionState,
|
||||
docType: state.docType,
|
||||
|
@ -65,16 +80,24 @@ const mapDispatchToProps = dispatch => {
|
|||
selectAnnotationNote: id => {
|
||||
dispatch(actions.selectAnnotationNote(id))
|
||||
},
|
||||
onSubmitAnnotationClick: (annotationNote, selectionState, editorState) => {
|
||||
dispatch(actions.submitAnnotation(annotationNote, selectionState, editorState))
|
||||
clearAnnotationTag: () => {
|
||||
dispatch(actions.clearAnnotationTag())
|
||||
},
|
||||
selectAnnotationTag: tag => {
|
||||
dispatch(actions.selectAnnotationTag(tag))
|
||||
},
|
||||
onSubmitAnnotationClick: (annotationNote, annotationTag, selectionState, editorState) => {
|
||||
dispatch(actions.submitAnnotation(annotationNote, annotationTag, selectionState, editorState))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorPage.propTypes = {
|
||||
notes: PropTypes.arrayOf(PropTypes.object),
|
||||
tags: PropTypes.arrayOf(PropTypes.object),
|
||||
currentDocument: PropTypes.object,
|
||||
annotationNote: PropTypes.object,
|
||||
annotationTag: PropTypes.object,
|
||||
editorState: PropTypes.object,
|
||||
selectionState: PropTypes.object,
|
||||
docType: PropTypes.string,
|
||||
|
@ -82,6 +105,7 @@ EditorPage.propTypes = {
|
|||
onDeleteConfirm: PropTypes.func,
|
||||
onSubmitAnnotationClick: PropTypes.func,
|
||||
selectAnnotationNote: PropTypes.func,
|
||||
selectAnnotationTag: PropTypes.func,
|
||||
}
|
||||
|
||||
const EditorPageContainer = connect(mapStateToProps, mapDispatchToProps)(EditorPage)
|
||||
|
|
|
@ -7,16 +7,21 @@ import actions from '../actions'
|
|||
const Link = (props) => {
|
||||
const data = props.contentState.getEntity(props.entityKey).getData()
|
||||
return (
|
||||
<a href='#' onClick={()=>props.onAnnotationClick(data.url)} data-toggle='modal' data-target='#annotation_modal' data-url={data.url}>
|
||||
<a href='#'
|
||||
onClick={()=>props.onAnnotationClick(data['url'])}
|
||||
style={{color: '#' + data['data-color']}}
|
||||
data-toggle='modal'
|
||||
data-target='#annotation_modal'
|
||||
data-color={data['data-color']}
|
||||
data-url={data['url']}
|
||||
>
|
||||
{props.children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
annotationNote: state.annotationNote
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
|
@ -28,7 +33,6 @@ const mapDispatchToProps = dispatch => {
|
|||
}
|
||||
|
||||
Link.propTypes = {
|
||||
annotationNote: PropTypes.object,
|
||||
onAnnotationClick: PropTypes.func,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { stateToHTML } from 'draft-js-export-html'
|
|||
|
||||
import actions from '../actions'
|
||||
import helpers from '../modules/helpers'
|
||||
import { validateSubmittedDocument } from '../modules/validation'
|
||||
|
||||
import { html_export_options, convertToSearchText } from '../modules/editorSettings.js'
|
||||
|
||||
|
@ -13,20 +14,26 @@ const joyceInterface = store => next => action => {
|
|||
store.dispatch(actions.getDocumentText({id: action.id, docType: action.docType, state: 'currentDocument'}))
|
||||
break
|
||||
case 'SUBMIT_DOCUMENT_EDIT':
|
||||
const textContent = action.editorState.getCurrentContent()
|
||||
const data = { title: action.documentTitleInput, html_source: stateToHTML(textContent, html_export_options), search_text: convertToSearchText(textContent) }
|
||||
if (action.currentDocument.id) {
|
||||
data.id = action.currentDocument.id
|
||||
}
|
||||
if (action.docType === 'chapters') {
|
||||
if (!action.currentDocument.id) {
|
||||
const nextNumber = store.getState().chapters.length + 1
|
||||
data.number = nextNumber
|
||||
} else {
|
||||
data.number = action.currentDocument.number
|
||||
const errors = validateSubmittedDocument(action.docType, action.documentTitleInput, action.colorPickerInput)
|
||||
if (errors.length < 1) {
|
||||
const textContent = action.editorState.getCurrentContent()
|
||||
const data = { title: action.documentTitleInput, html_source: stateToHTML(textContent, html_export_options), search_text: convertToSearchText(textContent) }
|
||||
if (action.docType === 'tags') {
|
||||
data.color = action.colorPickerInput
|
||||
}
|
||||
if (action.currentDocument.id) {
|
||||
data.id = action.currentDocument.id
|
||||
}
|
||||
if (action.docType === 'chapters') {
|
||||
if (!action.currentDocument.id) {
|
||||
const nextNumber = store.getState().chapters.length + 1
|
||||
data.number = nextNumber
|
||||
} else {
|
||||
data.number = action.currentDocument.number
|
||||
}
|
||||
}
|
||||
store.dispatch(actions.saveDocument({id: data.id ? data.id : null, docType: action.docType, data: data}))
|
||||
}
|
||||
store.dispatch(actions.saveDocument({id: data.id ? data.id : null, docType: action.docType, data: data}))
|
||||
break
|
||||
case 'DELETE_CURRENT_DOCUMENT':
|
||||
store.dispatch(actions.deleteDocument({id: action.id, docType: action.docType}))
|
||||
|
|
|
@ -81,9 +81,14 @@ const joyceRouter = store => next => action => {
|
|||
}
|
||||
break
|
||||
case 'SAVE_DOCUMENT':
|
||||
// If successfully saving a new document, load it by pulling the id from the last document in the list
|
||||
if (action.status === 'success' && !action.id) {
|
||||
store.dispatch(actions.setCurrentDocument(action.data.slice(-1)[0].id, action.docType))
|
||||
}
|
||||
// If successfully saving an existing document, reload the current document
|
||||
if (action.status ==='success' && action.id) {
|
||||
store.dispatch(actions.setCurrentDocument(currentDocument.id, action.docType))
|
||||
}
|
||||
break
|
||||
case 'DELETE_DOCUMENT':
|
||||
if (action.status === 'success' && action.data[0]) {
|
||||
|
|
|
@ -13,12 +13,15 @@ export const html_export_options = {
|
|||
const entityType = entity.get('type').toUpperCase()
|
||||
if (entityType === 'LINK') {
|
||||
const data = entity.getData()
|
||||
console.log('data when saving is:', data['data-color'])
|
||||
return {
|
||||
element: 'a',
|
||||
attributes: {
|
||||
'href': data.url,
|
||||
'href': data['url'],
|
||||
'data-target': '#annotation_modal',
|
||||
'data-toggle': 'modal'
|
||||
'data-toggle': 'modal',
|
||||
'data-color': data['data-color'],
|
||||
'data-tag': data['data-tag']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +34,14 @@ export const convertToSearchText = contentState => {
|
|||
(searchText, block) => ([...searchText, {key: block.key, text: block.text}]),
|
||||
[]
|
||||
)
|
||||
console.log('Search text result:', searchText)
|
||||
return searchText
|
||||
}
|
||||
|
||||
export const defaultTagColors = [
|
||||
'307EE3',
|
||||
'CF2929',
|
||||
'AB59C2',
|
||||
'9C632A',
|
||||
'F59627',
|
||||
'40b324'
|
||||
]
|
|
@ -17,6 +17,7 @@ const regexParseBaseFunction = (path, pattern) => {
|
|||
}
|
||||
|
||||
const regex = {
|
||||
// Route Checks
|
||||
checkIfRedirectPath: path => {
|
||||
return regexCheckBaseFunction(path, /\/(\:id)$/)
|
||||
},
|
||||
|
@ -50,11 +51,16 @@ const regex = {
|
|||
checkPathForID: path => {
|
||||
return regexCheckBaseFunction(path, /\/([0-9A-Za-z0-9\-\_]{18,})$/)
|
||||
},
|
||||
// Route Parsers
|
||||
parseNumberFromPath: path => {
|
||||
return Number(regexParseBaseFunction(path, /\/([0-9]{1,3})$/))
|
||||
},
|
||||
parseIDFromPath: path => {
|
||||
return regexParseBaseFunction(path, /\/([0-9A-Za-z0-9\-\_]{18,})$/)
|
||||
},
|
||||
// Validation Checks
|
||||
checkColorPickerHexValue: input => {
|
||||
return regexCheckBaseFunction(input, /(^[0-9A-F]{6})$|(^[0-9A-F]{3}$)/)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import regex from './regex'
|
||||
|
||||
export const validateSubmittedDocument = (docType, documentTitleInput, colorPickerInput) => {
|
||||
const errors = []
|
||||
if (docType === 'tags') {
|
||||
if (colorPickerInput.length < 1) {
|
||||
errors.push('Please select a tag color.')
|
||||
} else if (!regex.checkColorPickerHexValue(colorPickerInput)) {
|
||||
errors.push('Please select a valid hex code color.')
|
||||
}
|
||||
}
|
||||
if (documentTitleInput.length < 1) {
|
||||
errors.push('Please enter a title.')
|
||||
}
|
||||
return errors
|
||||
}
|
|
@ -4,6 +4,8 @@ const annotationNote = (state={}, action) => {
|
|||
if (action.status === 'success' && action.docType === 'notes' && action.state === 'annotationNote') {
|
||||
return action.data
|
||||
} else { return state }
|
||||
case 'ADD_ANNOTATION':
|
||||
return {}
|
||||
case 'SUBMIT_ANNOTATION':
|
||||
return {}
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const annotationTag = (state={}, action) => {
|
||||
switch(action.type) {
|
||||
case 'SET_ANNOTATION_TAG':
|
||||
return action.data
|
||||
case 'SUBMIT_ANNOTATION':
|
||||
return {}
|
||||
case 'CLEAR_ANNOTATION_TAG':
|
||||
return {}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default annotationTag
|
|
@ -0,0 +1,18 @@
|
|||
const colorPickerInput = (state='', action) => {
|
||||
switch(action.type) {
|
||||
case 'GET_DOCUMENT_TEXT':
|
||||
if (action.status === 'success' && action.state === 'currentDocument' && action.docType === 'tags') {
|
||||
return action.data.color
|
||||
} else { return state }
|
||||
case 'CREATE_DOCUMENT':
|
||||
return ''
|
||||
case 'SELECT_COLOR_SWATCH':
|
||||
return action.data
|
||||
case 'UPDATE_COLOR_PICKER':
|
||||
return action.data.toUpperCase()
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default colorPickerInput
|
|
@ -58,7 +58,7 @@ const editorState = (state=blankEditor, action) => {
|
|||
const contentStateWithEntity = contentState.createEntity(
|
||||
'LINK',
|
||||
'MUTABLE',
|
||||
{url: action.annotationNote.id}
|
||||
{'url': action.annotationNote.id, 'data-color': action.annotationTag.color, 'data-tag': action.annotationTag.title}
|
||||
)
|
||||
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
||||
const contentStateWithLink = Modifier.applyEntity(
|
||||
|
|
|
@ -8,12 +8,14 @@ import notes from './notes'
|
|||
import tags from './tags'
|
||||
import currentDocument from './currentDocument'
|
||||
import annotationNote from './annotationNote'
|
||||
import annotationTag from './annotationTag'
|
||||
import searchResults from './searchResults'
|
||||
|
||||
// User Inputs
|
||||
import editorState from './editorState'
|
||||
import selectionState from './selectionState'
|
||||
import documentTitleInput from './documentTitleInput'
|
||||
import colorPickerInput from './colorPickerInput'
|
||||
import searchInput from './searchInput'
|
||||
|
||||
// Toggles
|
||||
|
@ -22,25 +24,32 @@ import mode from './mode'
|
|||
import highlightToggle from './highlightToggle'
|
||||
import loadingToggle from './loadingToggle'
|
||||
|
||||
// Validation
|
||||
import userErrors from './userErrors'
|
||||
|
||||
const reduceJoyce = combineReducers({
|
||||
routerReducer,
|
||||
//
|
||||
// Documents
|
||||
chapters,
|
||||
notes,
|
||||
tags,
|
||||
currentDocument,
|
||||
annotationNote,
|
||||
annotationTag,
|
||||
searchResults,
|
||||
//
|
||||
// User Inputs
|
||||
editorState,
|
||||
selectionState,
|
||||
colorPickerInput,
|
||||
documentTitleInput,
|
||||
searchInput,
|
||||
//
|
||||
// Toggles
|
||||
docType,
|
||||
mode,
|
||||
highlightToggle,
|
||||
loadingToggle
|
||||
loadingToggle,
|
||||
// Validation
|
||||
userErrors,
|
||||
})
|
||||
|
||||
export default reduceJoyce
|
|
@ -0,0 +1,16 @@
|
|||
import { validateSubmittedDocument } from '../modules/validation'
|
||||
|
||||
const userErrors = (state=[], action) => {
|
||||
switch(action.type) {
|
||||
case 'SUBMIT_DOCUMENT_EDIT':
|
||||
return validateSubmittedDocument(action.docType, action.documentTitleInput, action.colorPickerInput)
|
||||
case 'GET_DOCUMENT_TEXT':
|
||||
if (action.status === 'success' && action.state === 'currentDocument') {
|
||||
return []
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default userErrors
|
|
@ -0,0 +1,16 @@
|
|||
@import "variables";
|
||||
|
||||
.color_preview {
|
||||
display: block;
|
||||
height: 2em;
|
||||
width: 80%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.tag_button_color_preview {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
display: inline-flex;
|
||||
margin-right: 0.5em;
|
||||
border: 1px solid black;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
@import "variables";
|
||||
|
||||
#annotation_tag_preview {
|
||||
border: 1px solid rgba(120, 120, 120, 0.7);
|
||||
border-radius: 5px;
|
||||
background-color: rgba(200, 200, 200, 0.8);
|
||||
padding: .1em;
|
||||
}
|
||||
|
||||
#selected_annotation_tag {
|
||||
border: 1px solid rgba(120, 120, 120, 0.7);
|
||||
border-radius: 0.3rem;
|
||||
background-color: #fafafa;
|
||||
padding: 0.15em 1em;
|
||||
}
|
||||
|
||||
#clear_anntation_tag {
|
||||
float: right;
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
animation-fill-mode: both;
|
||||
}
|
||||
&.hidden_annotations a {
|
||||
color: black;
|
||||
color: black !important;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
|
@ -41,8 +41,8 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
to {
|
||||
color: map-get($theme_colors, "primary");
|
||||
background-color: #f2e9de;
|
||||
// color: map-get($theme_colors, "primary");
|
||||
// background-color: #f2e9de;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
@import "variables";
|
||||
|
||||
#editor_content {
|
||||
height: 43em;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(256,256,256,.8);
|
||||
box-shadow: 2px 5px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
|
@ -22,6 +21,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tall_editor {
|
||||
height: 43em;
|
||||
}
|
||||
|
||||
.short_editor {
|
||||
height: 30em;
|
||||
}
|
||||
|
||||
#editor_metadata {
|
||||
h4 {
|
||||
font-size: 1.75rem
|
||||
|
@ -50,3 +57,14 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
button.caret-off::before {
|
||||
display: none;
|
||||
}
|
||||
button.caret-off::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user_error_message {
|
||||
color: map-get($theme_colors, "danger");
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
@import "sidebar";
|
||||
@import "page";
|
||||
@import "texteditor";
|
||||
@import "button";
|
||||
@import "modal";
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
$fa-font-path: "../../node_modules/font-awesome/fonts";
|
||||
|
|
Loading…
Reference in New Issue