Fleshed out routing functionality, needs some refactoring

This commit is contained in:
Alex Hunt 2018-05-01 23:52:59 -04:00
parent a212863bf3
commit d24d444683
19 changed files with 175 additions and 199 deletions

View File

@ -4,8 +4,6 @@ from jinja2 import TemplateNotFound
joyce = Blueprint('joyce', __name__)
@joyce.route('/', defaults={'path': ''})
def redirect_root(path):
return redirect('/1')
@joyce.route('/<path:path>')
def show_joyce(path):
return render_template('joyce.html')

View File

@ -19,11 +19,11 @@ export const ReaderAnnotateButton = ({onClick}) =>
</button>
</div>
export const ChapterButton = ({chapter, currentChapter, path}) =>
export const ChapterButton = ({chapter, currentChapter, onClick}) =>
<div className ='chapter_button text-center'>
<Link to={path + chapter.number} className={currentChapter.id === chapter.id ? 'btn btn-dark btn-lg active_button' : 'btn btn-outline-dark btn-lg inactive_button'}>
<button onClick={onClick} className={currentChapter.id === chapter.id ? 'btn btn-dark btn-lg active_button' : 'btn btn-outline-dark btn-lg inactive_button'}>
{romanize(chapter.number)}. {chapter.title}
</Link>
</button>
</div>
export const NoteButton = ({note, currentNote, onClick}) =>

View File

@ -1,20 +1,20 @@
import React from 'react'
import { ChapterButton, NoteButton } from './button'
export const DocumentList = ({notes, chapters, currentDocument, onDocumentClick, path, docType}) =>
export const DocumentList = ({notes, chapters, currentDocument, onDocumentClick, docType}) =>
<div id='document_list'>
{(docType === 'chapters' && chapters.length > 0) &&
<ChapterList chapters={chapters} currentChapter={currentDocument} path={path}/>
<ChapterList chapters={chapters} currentChapter={currentDocument} onChapterClick={onDocumentClick}/>
}
{(docType === 'notes' && notes.length > 0) &&
<NoteList notes={notes} currentNote={currentDocument} onNoteClick={onDocumentClick}/>
}
</div>
export const ChapterList = ({chapters, currentChapter, path}) =>
export const ChapterList = ({chapters, currentChapter, onChapterClick}) =>
<div>
{chapters.map(chapter =>
<ChapterButton key={chapter.id} currentChapter={currentChapter} chapter={chapter} path={path}/>
<ChapterButton key={chapter.id} currentChapter={currentChapter} chapter={chapter} onClick={()=>onChapterClick(chapter.id, 'chapters')}/>
)}
</div>

View File

@ -1,21 +1,19 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
const Navbar = ({currentDocument}) =>
const Navbar = () =>
<nav className='navbar navbar-dark navbar-static-top navbar-expand-lg'>
<a className='navbar-brand' href='/'>The Joyce Project</a>
<Link to='/' className='navbar-brand'>The Joyce Project</Link>
<button className='navbar-toggler' type='button' data-toggle='collapse' data-target='#navbarItems'>
<span className='navbar-toggler-icon'></span>
</button>
<div id='navbarItems' className='collapse navbar-collapse'>
<ul className='navbar-nav mr-auto'>
<li className='nav-item'>
<Link to='/edit' className='nav-link'>Edit</Link>
<Link to='/notes' className='nav-link'>Notes</Link>
</li>
<li className='nav-item'>
<Link to='/notes' className='nav-link'>Notes</Link>
<Link to='/edit' className='nav-link'>Edit</Link>
</li>
<li className='nav-item'>
<Link to='/search' className='nav-link'>Search</Link>
@ -24,12 +22,4 @@ const Navbar = ({currentDocument}) =>
</div>
</nav>
const mapStateToProps = state => {
return {
currentDocument: state.currentDocument,
}
}
const JoyceReaderPageContainer = connect(mapStateToProps)(JoyceReaderPage)
export default Navbar

View File

@ -10,7 +10,7 @@ import JoyceReaderContentContainer from '../containers/joyceReaderContentContain
const JoyceReaderPage = ({currentDocument, loadingToggle}) =>
<div id='joyce_reader' className='container-fluid'>
<div className="row">
<div id='content_window' className='row'>
<JoyceReaderSidebarContainer />
<Content>
{loadingToggle === true &&

View File

@ -10,7 +10,7 @@ const JoyceReaderSidebar = ({chapters, notes, currentDocument, onDocumentClick,
<div>
<HighlightButton highlightToggle={highlightToggle} onClick={onHighlightClick}/>
<SidebarSpacer />
<DocumentList chapters={chapters} notes={notes} currentDocument={currentDocument} onDocumentClick={onDocumentClick} docType={docType} path={'/'}/>
<DocumentList chapters={chapters} notes={notes} currentDocument={currentDocument} onDocumentClick={onDocumentClick} docType={docType}/>
</div>
</div>

View File

@ -8,23 +8,19 @@ import { clickSearch } from '../actions/userActions'
import { updateSearchInput } from '../actions/inputActions'
const JoyceSearchContent = ({searchResults, searchInput, onSearchInputChange, onSearchClick}) =>
<div id='joyce_search' className='container-fluid'>
<div id='content_window' className='row'>
<div className='container'>
<div className='row'>
<div className='col-sm-2'>
<SearchButton onClick={onSearchClick} searchInput={searchInput}/>
</div>
<div className='col-sm-10'>
<input id='search_input' type='text' value={searchInput} onChange={onSearchInputChange} />
</div>
</div>
<div className='row'>
<SearchResultsBox searchResults={searchResults} />
</div>
<div className='container'>
<div className='row'>
<div className='col-sm-2'>
<SearchButton onClick={onSearchClick} searchInput={searchInput}/>
</div>
<div className='col-sm-10'>
<input id='search_input' type='text' value={searchInput} onChange={onSearchInputChange} />
</div>
</div>
</div>
<div className='row'>
<SearchResultsBox searchResults={searchResults} />
</div>
</div>
const mapStateToProps = state => {
return {

View File

@ -1,13 +1,13 @@
import React from 'react'
import { connect } from 'react-redux'
import Navbar from '../components/navbar'
import JoyceSearchContentContainer from '../containers/joyceSearchContentContainer'
const JoyceSearchPage = () =>
<div>
<Navbar />
<JoyceSearchContentContainer />
<div id='joyce_reader' className='container-fluid'>
<div id='content_window' className='row'>
<JoyceSearchContentContainer />
</div>
</div>
const JoyceSearchPageContainer = connect()(JoyceSearchPage)

View File

@ -1,25 +0,0 @@
import React from 'react'
import { Link } from 'react-router-dom'
const Navbar = () =>
<nav className='navbar navbar-dark navbar-static-top navbar-expand-lg'>
<a className='navbar-brand' href='/'>The Joyce Project</a>
<button className='navbar-toggler' type='button' data-toggle='collapse' data-target='#navbarItems'>
<span className='navbar-toggler-icon'></span>
</button>
<div id='navbarItems' className='collapse navbar-collapse'>
<ul className='navbar-nav mr-auto'>
<li className='nav-item'>
<Link to='/edit' className='nav-link'>Edit</Link>
</li>
<li className='nav-item'>
<Link to='/notes' className='nav-link'>Notes</Link>
</li>
<li className='nav-item'>
<Link to='/search' className='nav-link'>Search</Link>
</li>
</ul>
</div>
</nav>
export default Navbar

View File

@ -3,13 +3,13 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { Route, Redirect } from 'react-router'
import { Route, Redirect, Switch } from 'react-router'
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'
import 'bootstrap'
// src packages
import Navbar from './containers/navbar'
import Navbar from './components/navbar'
import reduceReader from './reducers/reduceReader'
import { selectAnnotationNote } from './actions/userActions'
import { getDocumentList } from './actions/apiActions'
@ -18,43 +18,43 @@ import AnnotateModal from './components/annotateModal'
import AnnotationModal from './components/annotationModal'
import { setCurrentDocument, setDocType } from './actions/userActions'
import { logger, joyceAPI } from './middleware/'
import { getFirstDocument } from './mixins/firstDocument'
import JoyceReaderPageContainer from './containers/joyceReaderPageContainer'
import JoyceEditorPageContainer from './containers/joyceEditorPageContainer'
import JoyceSearchPageContainer from './containers/joyceSearchPageContainer'
// TODO: Pass routing from Flask?
const history = createHistory()
const router = routerMiddleware(history)
const docType = 'chapters'
const store = createStore(reduceReader, applyMiddleware(logger, joyceAPI, router))
store.dispatch(setDocType(docType))
store.dispatch(setDocType('chapters'))
store.dispatch(getDocumentList({docType: 'chapters'}))
store.dispatch(getDocumentList({docType: 'notes'}))
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Navbar />
<Route exact path='/edit' render={() => {
const currentDocument = store.getState().currentDocument
if (store.getState().docType === 'chapters') {
if (currentDocument.id) {
return <Redirect to={'/edit/' + currentDocument.number}/>
} else {
return <Redirect to={'/edit/1'}/>
}
}
}}/>
<Route exact path='/notes' render={() => {
if (store.getState().notes[0]) {
return <Redirect to={'/notes/' + store.getState().notes[0].id} />
}
return <JoyceReaderPageContainer />
}}/>
<Route exact path='/notes/:id' component={JoyceReaderPageContainer} />
<Route exact path='/edit/:id' component={JoyceEditorPageContainer} />
<Route exact path='/:id' component={JoyceReaderPageContainer} />
<Switch>
<Route exact path='/' render={() =>
<Redirect to={'/:id'}/>
}/>
<Route exact path='/edit' render={() =>
<Redirect to={'/edit/:id'}/>
}/>
<Route exact path='/notes' render={() =>
<Redirect to={'/notes/:id'}/>
}/>
<Route exact path='/' render={() =>
<Redirect to={'/:id'}/>
}/>
<Route exact path='/notes/:id' component={JoyceReaderPageContainer} />
<Route exact path='/edit/:id' component={JoyceEditorPageContainer} />
<Route exact path='/search/' component={JoyceSearchPageContainer} />
<Route exact path='/:id' component={JoyceReaderPageContainer} />
</Switch>
<DeleteConfirmModal onDeleteClick={()=>onDeleteClick(currentDocument.id, docType)}/>
<AnnotateModal notes={store.getState().notes} annotationNote={store.getState().annotationNote} onSubmitClick={()=>onSubmitAnnotationClick(store.getState().annotationNote, store.getState().selectionState, store.getState().editorState)} selectAnnotationNote={selectAnnotationNote} />
<AnnotationModal annotationNote={store.getState().annotationNote} />
@ -62,10 +62,4 @@ ReactDOM.render(
</ConnectedRouter>
</Provider>,
document.getElementById('wrapper')
)
store.dispatch(getDocumentList({
docType: docType,
state: 'initialPageLoad'
}))
store.dispatch(getDocumentList({docType: 'notes'}))
)

View File

@ -2,6 +2,7 @@ import axios from 'axios'
import { stateToHTML } from 'draft-js-export-html'
import { stateToMarkdown } from 'draft-js-export-markdown'
import { convertToRaw } from 'draft-js'
import { push } from 'react-router-redux'
import {
getDocumentList,
@ -22,8 +23,6 @@ import {
HTTPPostWriteDocument,
HTTPPostSearchResults } from './http.js'
import { getFirstDocument } from '../mixins/firstDocument'
const html_export_options = {
entityStyleFn: (entity) => {
const entityType = entity.get('type').toUpperCase()
@ -49,32 +48,75 @@ const convertToPlainText = contentState => {
)
}
const selectChapterIDByNumber = (store, number) => {
for (const chapter of store.getState().chapters) {
if (number === chapter.number) {
return chapter.id
}
}
}
// const selectChapterIDByNumber = (store, number) => {
// for (const chapter of store.getState().chapters) {
// if (number === chapter.number) {
// return chapter.id
// }
// }
// }
const parseNumberFromPath = path => {
const match = /\/([0-9]*)$/.exec(path)
if (match) {
if (parseInt(match[1])) {
const number = Number(match[1])
return number
} else {
return null
}
if (match && parseInt(match[1])) {
return Number(match[1])
} else {
return null
}
}
const parseIDFromPath = path => {
const match = /\/([A-Za-z0-9\-\_]{18,})$/.exec(path)
if (match) {
return match[1]
} else {
return null
}
}
const checkIfRedirectPath = path => {
const match = /\/(\:id)$/.exec(path)
if (match) {
return true
} else {
return false
}
}
const selectDocumentIDByPath = (path, docs, docType) => {
if (docType === 'chapters') {
const number = parseNumberFromPath(path)
for (const chapter of docs) {
if (chapter.number === number) {return chapter.id}
}
} else {
const id = parseIDFromPath(path)
for (const doc of docs) {
if (doc.id === id) {return doc.id}
}
}
}
const selectDocTypeIdentifier = (doc, docType) => {
if (docType === 'chapters') {
return String(doc.number)
} else {
return doc.id
}
}
// API Middleware
export const joyceAPI = store => next => action => {
next(action)
switch(action.type) {
case '@@router/LOCATION_CHANGE':
if (checkIfRedirectPath(action.payload.pathname) || action.payload.pathname === '/') {
if (store.getState().currentDocument.id) {
const id = selectDocTypeIdentifier(store.getState().currentDocument, store.getState().docType)
store.dispatch(push(id))
}
}
break
// API Calls
case 'GET_DOCUMENT_LIST':
if (action.status === 'request') {
@ -83,13 +125,14 @@ export const joyceAPI = store => next => action => {
)
}
if (action.status === 'success' && action.docType === store.getState().docType && !store.getState().currentDocument.id) {
if (action.docType === 'chapters') {
const pathNumber = parseNumberFromPath(store.getState().routerReducer.location.pathname)
store.dispatch(setCurrentDocument(selectChapterIDByNumber(store, pathNumber), action.docType))
} else if (action.docType === 'notes') {
store.dispatch(setCurrentDocument(store.getState().notes[0].id, action.docType))
const path = store.getState().routerReducer.location.pathname
if (checkIfRedirectPath(path)) {
store.dispatch(setCurrentDocument(action.data[0].id, action.docType))
} else {
const id = selectDocumentIDByPath(path, action.data, action.docType)
store.dispatch(setCurrentDocument(id, action.docType))
}
}
}
break
case 'GET_DOCUMENT_TEXT':
if (action.status === 'request') {
@ -97,6 +140,13 @@ export const joyceAPI = store => next => action => {
store.dispatch(getDocumentText(response))
)
}
if (action.status === 'success' && action.state === 'currentDocument') {
if (action.docType === 'chapters') {
store.dispatch(push(String(action.data.number)))
} else if (action.docType === 'notes') {
store.dispatch(push(action.data.id))
}
}
break
case 'SAVE_DOCUMENT':
if (action.status === 'request') {
@ -122,13 +172,6 @@ export const joyceAPI = store => next => action => {
}
}
break
// Document Action Middleware
case 'SET_DOC_TYPE':
const firstDocument = getFirstDocument(store, action.docType)
if (firstDocument) {
store.dispatch(getDocumentText({id: firstDocument.id, docType: action.docType, state: 'currentDocument'}))
}
break
case 'SET_CURRENT_DOCUMENT':
store.dispatch(getDocumentText({id: action.id, docType: action.docType, state: 'currentDocument'}))
break
@ -175,15 +218,6 @@ export const joyceAPI = store => next => action => {
)
}
break
case '@@router/LOCATION_CHANGE':
const pathNumber = parseNumberFromPath(action.payload.pathname)
if (pathNumber) {
for (const chapter of store.getState().chapters) {
if (pathNumber === chapter.number) {
store.dispatch(setCurrentDocument(chapter.id, 'chapters'))
}
}
}
default:
break
}

View File

@ -1,8 +0,0 @@
export const getFirstDocument = (store, docType) => {
switch(docType) {
case 'chapters':
return store.getState().chapters[0]
case 'notes':
return store.getState().notes[0]
}
}

5
src/mixins/helpers.js Normal file
View File

@ -0,0 +1,5 @@
const helpers = {
}
export default helpers

0
src/mixins/regex.js Normal file
View File

View File

@ -1,4 +1,4 @@
const docType = (state=null, action) => {
const docType = (state='chapters', action) => {
switch(action.type) {
case 'SET_DOC_TYPE':
return action.docType
@ -6,7 +6,7 @@ const docType = (state=null, action) => {
const path = action.payload.pathname
if (/^\/(notes).*/.exec(path)) {
return 'notes'
} else { return state }
} else{ return state }
default:
return state
}

View File

@ -11,6 +11,8 @@ import docType from './docType'
import { routerReducer } from 'react-router-redux'
import mode from './mode'
import documentTitleInput from './documentTitleInput'
import searchResults from './searchResults'
import searchInput from './searchInput'
const reduceReader = combineReducers({
chapters,
@ -23,7 +25,9 @@ const reduceReader = combineReducers({
loadingToggle,
routerReducer,
mode,
documentTitleInput
documentTitleInput,
searchResults,
searchInput
})
export default reduceReader

View File

@ -20,26 +20,23 @@
border: 1px solid $border_color;
}
.chapter_button > a {
.chapter_button > button {
width: 100%;
margin: 0.25% 0;
padding: 0.2rem 1rem;
font-size: 1rem;
box-shadow: 2px 5px 10px 1px rgba(0, 0, 0, 0.3);
// &:not(.active_button) {
// background-color: rgba(250, 250, 250, 0.8);
// }
}
button {
background-color: rgba(250, 250, 250, 0.8);
}
.chapter_button > a.inactive_button:not(:hover) {
.chapter_button > button.inactive_button:not(:hover) {
background-color: rgba(250, 250, 250, 1);
}
.note_button > a.inactive_button:not(:hover) {
.note_button > button.inactive_button:not(:hover) {
background-color: rgba(250, 250, 250, 0.8);
}

View File

@ -21,3 +21,37 @@ $fa-font-path: "../../node_modules/font-awesome/fonts";
#joyce_reader > div {
margin-top: 0.5em;
}
#joyce_search {
overflow: hidden;
width: 95%;
height: 100%;
background-color: rgba(256,256,256,0.5);
border-left: 1px solid $border_color;
border-right: 1px solid $border_color;
}
#joyce_search > div {
margin-top: 20px;
}
#search_input {
width: 100%;
}
#search_results_box {
overflow-y: scroll;
margin-top: 3%;
height: 75vh;
border: 1px solid $border_color;
border-radius: 5px;
background-color: $search_box_bg;
}
.result_type_group {
background-color: rgba(250, 250, 250, 0.9);
margin: 0 -2%;
padding: 2%;
border-radius: 5px;
border: 1px solid $border_color;
}

View File

@ -1,43 +0,0 @@
@import "variables";
@import "window";
@import "navbar";
@import "sidebar";
@import "page";
@import "node_modules/bootstrap/scss/bootstrap";
$fa-font-path: "../../node_modules/font-awesome/fonts";
@import "node_modules/font-awesome/scss/font-awesome";
#joyce_search {
overflow: hidden;
width: 95%;
height: 100%;
background-color: rgba(256,256,256,0.5);
border-left: 1px solid $border_color;
border-right: 1px solid $border_color;
}
#joyce_search > div {
margin-top: 20px;
}
#search_input {
width: 100%;
}
#search_results_box {
overflow-y: scroll;
margin-top: 3%;
height: 75vh;
border: 1px solid $border_color;
border-radius: 5px;
background-color: $search_box_bg;
}
.result_type_group {
background-color: rgba(250, 250, 250, 0.9);
margin: 0 -2%;
padding: 2%;
border-radius: 5px;
border: 1px solid $border_color;
}