Commit 30ce1056 authored by Alexandros Georgantas's avatar Alexandros Georgantas

Merge branch 'monemvasia' into 'master'

chore(app): upgrade to monemvasia

See merge request !14
parents 030de405 4e583baf
{
"presets": [
"env",
"react",
"stage-2"
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-class-properties"
]
}
\ No newline at end of file
}
.env
.env.*
config/*.env
coverage/
config/*.env
node_modules/
npm-debug.log
_build/*
......@@ -11,3 +12,4 @@ tmp/
uploads/*
yarn-error.log
data
scripts/link.sh
......@@ -2,13 +2,18 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="1.1.1"></a>
## [1.1.1](https://gitlab.coko.foundation/editoria/ucp/compare/v1.1.0...v1.1.1) (2018-12-19)
<a name="1.1.0"></a>
# 1.1.0 (2018-11-20)
### Features
* conventional commits added ([a433e4a](https://gitlab.coko.foundation/editoria/booksprints/commit/a433e4a))
* conventional commits added ([b98b958](https://gitlab.coko.foundation/editoria/ucp/commit/b98b958))
......
# How to write new E2E tests
Editoria uses Cypress to run end to end tests.
Follow the steps below to get up and running:
Make sure your test db is running:
If you have already run `yarn start:services` you should have two Postgres Docker containers running (one for development and one for testing). It might be good to look at the `docker-compose.yml` file in the application’s root folder. Worth noting is that the test db doesn’t have a data folder, as no data is meant to be persisted.
Create and source a `test.env` file (by convention inside the config folder). This should be the same as the development environment file, apart from the following values:
```
NODE_ENV=’test’
POSTGRES_DB=’test’
POSTGRES_USER=’test’
POSTGRES_PASSWORD=’pass’
POSTGRES_PORT=’5433’
```
Once you’ve sourced the `test.env` file, run `yarn setupdb` and create an admin account with username `admin` and password `password`. If you’ve done this step once for the testing db, you don’t need to do it again.
Bring up the server with `yarn server`.
On another terminal window / tab, run `yarn cypress`. This should bring the Cypress app up.
You can run the existing tests by clicking on the “Run all tasks” button at the top right. The tests will run in a browser window.
To make a new test, simply create a new file (eg. `mytest_spec.js`) inside the Cypress/integration folder on your app. You can use the rest of the files in there for reference. If you need to test a page that needs an authenticated user, use the login command to sign in as a particular role (eg. cy.login(‘author’). Should you need to seed data before your tests, you can execute a seed script in a before hook (eg. `cy.exec(filePath)`). You can take a look at `dashboard_spec.js` for an example of this.
You’re all set!
# Detailed instructions on seeding the database before running a test
In order to test your app you need to have data in your database. That means before you run your tests you should seed your database.
You generally have three ways to facilitate this with Cypress:
* `cy.exec()` - to run system commands
* `cy.task()` - to run code in Node.js via the pluginsFile
* `cy.request()` - to make HTTP requests
In Editoria we are going to use the first approach. We are going to create scripts that can populate the database with data or scripts that truncate the database and then we executed with `cy.exec()`.
All we need is creating the script that we want to run for each test and call it before each test.
## Dashboard example
An example test could be a dashboard test, where certain books need to show up and be assigned to certain users.
So first thing is to create a script called e.g. `createBooksWithUsersAndTeams.js` which creates books and assigns’ them to admin and author user: https://gitlab.coko.foundation/editoria/ucp/blob/cypress/scripts/createBooksWithUsersAndTeams.js
And last but not least we should call that script by using ‘before’ and ‘beforeEach’ functions of Cypress like the example below.
```
beforeEach(() => {
cy.exec('node ./scripts/truncateDB.js')
cy.exec('node ./scripts/createBooksWithUsersAndTeams.js')
})
```
# Editoria
This is the Editoria Book Sprints monorepo.
This is the Editoria monorepo.
It consists of the main Editoria application, as well as the different [Pubsweet](https://gitlab.coko.foundation/pubsweet) components and helper libraries that the app is made of.
......@@ -14,13 +14,14 @@ The current features on our list are the following:
|Module |Description |In progress |Done |Issue|
|:---- |--- |:---: |:---: |:---:|
|**Current**|
|Editor |Copy Paste within the editor fix |&#x2714; | | |
|Editor |Upgrade to latest Substance |&#x2714; | | wax/wax#188|
|Export|Paged.js exporter | &#x2714; | | |
|Export|Paged.js CSS file editing | | | |
|Editor |Copy Paste within the editor fix | | | |
|Editor |Upgrade to latest Substance | | | wax/wax#188|
|System |E2E Tests | | |#95, #96|
|BookBuilder |Create RFC for the chapters' reordering | | | #145 |
|Export |Notes at the end of the book | | | #78|
|System |Authsome mode for Book Sprints | &#x2714; | | #146|
|BookBuilder |Per instance configurability | | | |
|**Previous**|
|System |Authsome mode for Book Sprints | |&#x2714; | #146|
|System | Edit Lock | |&#x2714; | #117|
|System |Use docker containers for deployments | |&#x2714; |#89|
|System |Logger | |&#x2714; |#106|
......@@ -30,8 +31,10 @@ The current features on our list are the following:
|Book builder |Drag and Drop perfomance issue | |&#x2714; |#131|
|Book builder |Export EPUB | |&#x2714; |#79|
|Book builder |Filenames for single uploads | |&#x2714; |#112|
|BookBuilder |Create RFC for the chapters' reordering | |&#x2714; | #145 |
|Editor |Code Block | |&#x2714; | wax/wax#174|
|Editor |Add ornament style | |&#x2714; | wax/wax#178|
|Export |Notes at the end of the book | |&#x2714; | #78|
|Export |Fix links | |&#x2714; | #100|
|Export |Fix image captions | |&#x2714; | #113|
......@@ -269,4 +272,15 @@ Yes, currently however it is not available via the user interface. This is on ou
It can. The first conversion is from .docx to HTML, and from there, it’s up to presses to decide what to do with the highly structured, source HTML.
### Can I use Editoria for journals workflow?
It’s possible, but would not be ideal. Coko has developed an open-source tool that is optimized for journals workflow, called xPub. xPub, like Editoria, is modular, so that organizations can develop their own non-hardcoded workflows, mixing and matching modules that other organizations have developed and shared, or create and integrate their own. More at https://coko.foundation/use-cases/
\ No newline at end of file
It’s possible, but would not be ideal. Coko has developed an open-source tool that is optimized for journals workflow, called xPub. xPub, like Editoria, is modular, so that organizations can develop their own non-hardcoded workflows, mixing and matching modules that other organizations have developed and shared, or create and integrate their own. More at https://coko.foundation/use-cases/
### How do my .docx filenames affect how they upload?
Using the "Upload Word Files" button, you can upload multiple .docx files with one click. A few file naming conventions provide useful controls for how the Word files are uploaded:
* .docx files that begin with "a" go into Frontmatter
* .docx files that beegin with "w" go into Backmatter
* files that start with any other letters go into the Body
Additionally:
* By default, files in the Body are regular, numbered chapters. Frontmatter and backmatter components are always unnumbered.
* a "00" anywhere in filename will make it an unnumbered chapter (only in Body)
* "pt0" anywhere in filename will upload the .docx as a Part (only in Body)
\ No newline at end of file
const logger = require('@pubsweet/logger')
const startServer = require('pubsweet-server')
const { startServer } = require('pubsweet-server')
startServer().catch(err => {
logger.error('FATAL ERROR, SHUTTING DOWN:', err)
......
......@@ -2,38 +2,51 @@ import 'regenerator-runtime/runtime'
import React from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'
import { configureStore, Root } from 'pubsweet-client'
import { AppContainer } from 'react-hot-loader'
import createHistory from 'history/createBrowserHistory'
import { Root } from 'pubsweet-client'
// Modals
import ModalProvider from 'editoria-common/src/ModalProvider'
import AddBookModal from 'pubsweet-component-editoria-dashboard/src/modals/AddBookModal'
import DeleteBookModal from 'pubsweet-component-editoria-dashboard/src/modals/DeleteBookModal'
import ArchiveBookModal from 'pubsweet-component-editoria-dashboard/src/modals/ArchiveBookModal'
import DeleteBookComponentModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/DeleteBookComponentModal'
import BookTeamManagerModal from 'pubsweet-component-bookbuilder/src/TeamManager/ConnectedTeamManager'
import WarningModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/WarningModal'
import ErrorModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/ErrorModal'
import UnlockModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/UnlockModal'
import MetadataModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/MetadataModal'
import WorkflowModal from 'pubsweet-component-bookbuilder/src/ui/src/modals/WorkflowModal'
import UnlockedModal from 'pubsweet-component-wax/src/modals/UnlockedModal'
import theme from './theme'
import routes from './routes'
const history = createHistory()
const store = configureStore(history, {})
const modals = {
addBook: AddBookModal,
deleteBook: DeleteBookModal,
archiveBook: ArchiveBookModal,
deleteBookComponent: DeleteBookComponentModal,
bookTeamManager: BookTeamManagerModal,
warningModal: WarningModal,
unlockModal: UnlockModal,
metadataModal: MetadataModal,
workflowModal: WorkflowModal,
errorModal: ErrorModal,
unlockedModal: UnlockedModal,
}
const rootEl = document.getElementById('root')
ReactDOM.render(
<AppContainer>
<Root history={history} routes={routes} store={store} theme={theme} />
</AppContainer>,
<ModalProvider modals={modals}>
<Root history={history} routes={routes} theme={theme} />
</ModalProvider>,
rootEl,
)
if (module.hot) {
module.hot.accept('pubsweet-client/src/components/Root', () => {
// eslint-disable-next-line global-require
const NextRoot = require('pubsweet-client/src/components/Root').default
ReactDOM.render(
<AppContainer>
<NextRoot history={history} routes={routes} store={store} />
</AppContainer>,
rootEl,
)
})
}
export default hot(module)(Root)
import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
import { LinkContainer } from 'react-router-bootstrap'
import { Navbar, Nav, NavItem, NavbarBrand } from 'react-bootstrap'
import { connect } from 'react-redux'
import Authorize from 'pubsweet-client/src/helpers/Authorize'
import NavbarUser from 'pubsweet-component-navigation/NavbarUser'
import actions from 'pubsweet-client/src/actions'
// TODO -- break into smaller components
class Navigation extends React.Component {
constructor(props) {
super(props)
this.collectionId = ''
this.inEditor = null
this.inPaged = null
}
componentDidMount() {
this.shouldAddBookLink()
}
componentWillUpdate() {
this.shouldAddBookLink()
}
shouldAddBookLink() {
const { history } = this.props
const { location } = history
const { pathname } = location
this.collectionId = ''
this.inEditor = null
this.inPaged = null
this.inEditor = pathname.match(/fragments/g)
this.inPaged = pathname.match(/pagedPreviewer\/paged/g)
if (this.inEditor || this.inPaged) {
const pathnameSplitted = pathname.split('/')
this.collectionId = pathnameSplitted[2] // eslint-disable-line
}
}
render() {
const { logoutUser, currentUser } = this.props
let logoutButtonIfAuthenticated
if (currentUser.user === null) return null
if (currentUser.isAuthenticated) {
logoutButtonIfAuthenticated = (
<NavbarUser
onLogoutClick={() => logoutUser('/login')}
user={currentUser.user}
/>
)
}
let BackToBooks
if (this.inEditor || this.inPaged) {
BackToBooks = (
<LinkContainer to={`/books/${this.collectionId}/book-builder`}>
<NavItem>Back to book</NavItem>
</LinkContainer>
)
}
// TODO -- fix object properties underneath
return (
<Navbar fluid>
<Navbar.Header>
<NavbarBrand>
<a href="/">Editoria</a>
</NavbarBrand>
</Navbar.Header>
<Nav>
<LinkContainer to="/books">
<NavItem>Books</NavItem>
</LinkContainer>
<Authorize object="users" operation="can view nav links">
<LinkContainer to="/users">
<NavItem>Users</NavItem>
</LinkContainer>
<LinkContainer to="/globalTeams">
<NavItem>Global Teams</NavItem>
</LinkContainer>
</Authorize>
{BackToBooks}
</Nav>
{logoutButtonIfAuthenticated}
</Navbar>
)
}
}
Navigation.propTypes = {
currentUser: PropTypes.any, // eslint-disable-line
history: PropTypes.any.isRequired, // eslint-disable-line
logoutUser: PropTypes.func.isRequired,
}
export default withRouter(
connect(
state => ({
currentUser: state.currentUser,
}),
{
logoutUser: actions.logoutUser,
},
)(Navigation),
)
.root {
font-style: italic;
}
\ No newline at end of file
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Route, Redirect, withRouter } from 'react-router-dom'
import { actions } from 'pubsweet-client'
class PrivateRoute extends React.Component {
constructor(props) {
super(props)
this.state = {
complete: false,
}
}
componentDidMount() {
const onComplete = () => {
this.setState({
complete: true,
})
}
this.props.getCurrentUser().then(onComplete, onComplete)
}
render() {
const {
currentUser,
getCurrentUser,
component: Component,
...rest
} = this.props
return (
<Route
render={props => {
// complete, authenticated
if (currentUser.isAuthenticated) {
return <Component {...props} />
}
// complete, not authenticated
if (this.state.complete) {
return (
<Redirect
to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
}
// incomplete, loading
if (currentUser.isFetching) {
return <div>loading…</div>
}
// incomplete, not yet loading
return null
}}
{...rest}
/>
)
}
}
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
currentUser: PropTypes.shape({
isAuthenticated: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
}).isRequired,
getCurrentUser: PropTypes.func.isRequired,
location: PropTypes.object.isRequired, // eslint-disable-line
}
export default withRouter(
connect(
state => ({
currentUser: state.currentUser,
}),
{
getCurrentUser: actions.getCurrentUser,
},
)(PrivateRoute),
)
import { css } from 'styled-components'
export default css`
color: #404040;
font-size: 16px;
margin-bottom: 0px;
text-decoration: none;
&:hover {
color: #404040;
text-decoration: none;
}
&:focus,
&:active {
color: #555;
font-weight: bold;
text-decoration: none;
}
`
import { css } from 'styled-components'
// This is not necessary is just a quick
// fix and should be deleted, is done
// temporary so not to mess around with pubsweet
export default css`
display: flex;
`
import { css } from 'styled-components'
export default css`
background-color: #404040;
background-image: none;
border: 1px solid transparent;
border-radius: 0;
color: #fff;
cursor: pointer;
display: block;
font-size: 14px;
font-weight: normal;
line-height: 1.42857;
margin-bottom: 20px;
padding: 6px 12px;
text-align: center;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
width: 100%;
&:hover {
background-color: #404040;
}
`
import { css } from 'styled-components'
export default css`
border-left: 4px solid #404040;
border-right: 4px solid #d8d8d8;
padding-left: 40px !important;
padding-right: 40px !important;
`
import { css } from 'styled-components'
export default {
H1: css`
color: #404040;
margin-bottom: 5px;
margin-top: 5px;
padding-bottom: 20px;
padding-top: 0;
`,
}
import { css } from 'styled-components'
export default css`
margin-left: auto;
margin-right: auto;
width: 150px;
img {
filter: grayscale(100%);
margin-bottom: 30px;
margin-top: 30px;
max-width: 100%;
}
`
import styled from 'styled-components'
import { fadeIn, th } from '@pubsweet/ui-toolkit'
const Page = styled.div`
flex: auto;
/* font-family: ${th('fontInterface')}; */
height: 100%;
overflow-y: auto;
/* padding: calc(${th('gridUnit')} * 2); */
> div {
animation: ${fadeIn} 0.5s;
}
`
export default Page
// import React from 'react'
import styled from 'styled-components'
// HACK -- figure out why this is needed
// injectGlobal`
// html {
// height: 100%;
// }
// body {
// height: 100%;
// overflow: hidden;
// }
// `
const PageLayout = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`
export default PageLayout
import { css } from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
export default {
// TODO
// -- input padding: breaking the grid?
// -- small placeholder text? maybe by default?
Label: css`
color: #404040;
margin-bottom: 5px;
`,
Input: css`
border: 2px solid #b0b0b0;
border-right: 4px solid #484848;
box-sizing: border-box;
color: ${props => {
switch (props.validationStatus) {
case 'success':
return props.theme.colorSuccess
case 'warning':
return props.theme.colorWarning
case 'error':
return props.theme.colorError
default:
return 'inherit'
}
}};
height: 34px;
padding: 6px 12px;
transition: ${th('transitionDuration')} ${th('transitionTimingFunction')};
&:focus {
border-color: ${th('colorPrimary')};
color: inherit;
}
&::placeholder {
font-size: ${th('fontSizeBaseSmall')};
line-height: ${th('lineHeightBaseSmall')};
}
`,
}
export { default as Action } from './Action'
export { default as AppBar } from './AppBar'
export { default as FormContainer } from './FormContainer'
export { default as TextField } from './TextField'
export { default as Heading } from './Heading'
export { default as Button } from './Button'
export { default as Logo } from './Logo'
import React from 'react'
import React, { Fragment } from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'
import { createGlobalStyle } from 'styled-components'
// Users and Teams
import UsersManager from 'pubsweet-component-users-manager/UsersManagerContainer'
import GlobalTeamsManager from 'pubsweet-component-editoria-global-teams/src/GlobalTeamsManager'
// import TeamsManager from 'pubsweet-component-teams-manager/TeamsManagerContainer'
// import UsersManager from 'pubsweet-component-users-manager/src/UsersManager'
import GlobalTeamsManager from 'pubsweet-component-editoria-global-teams/src/ConnectedGlobalTeams'
// Authentication
import Login from 'pubsweet-component-login/LoginContainer'
import Signup from 'pubsweet-component-signup/SignupContainer'
import Login from 'editoria-component-login/src/LoginContainer'
import Signup from 'editoria-component-signup/src/SignupContainer'
import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'
// Editor
import Wax from 'pubsweet-component-wax/src/WaxPubsweet'
import Wax from 'pubsweet-component-wax/src/ConnectedWax'
import WithConfig from 'pubsweet-component-wax/src/WithConfig'
// Editoria
import BookBuilder from 'pubsweet-component-bookbuilder/src/BookBuilder'
import Dashboard from 'pubsweet-component-editoria-dashboard/src/Dashboard'
import Manage from 'pubsweet-component-manage/Manage'
import BookBuilder from 'pubsweet-component-bookbuilder/src/ConnectedBookBuilder'
import Dashboard from 'pubsweet-component-editoria-dashboard/src/ConnectedDashboard'
import PagedStyler from 'pubsweet-component-bookbuilder/src/PagedStyler/PagedStyler'
import Navigation from './components/Navigation/Navigation'
import PrivateRoute from './components/PrivateRoute'
// import AuthenticatedManage from './components/AuthenticatedManage/AuthenticatedManage'
import Navigation from 'pubsweet-component-editoria-navigation/src/Navigation'
import PrivateRoute from 'pubsweet-component-editoria-navigation/src/PrivateRoute'
import Connected from 'pubsweet-component-editoria-navigation/src/ConnectedNavigation'
import PageLayout from './elements/PageLayout'
import Page from './elements/Page'
// Pass configuration to editor
const Editor = WithConfig(Wax, {
......@@ -30,107 +33,80 @@ const Editor = WithConfig(Wax, {
lockWhenEditing: true,
pollingTimer: 1500,
autoSave: true,
tools: [
'document',
'strong',
'annotations',
'note',
'inline-note',
'code',
'insert-script',
'ornament',
'diacritics-tool',
'find-and-replace-tool',
'spell-check',
'highlighter',
'full-screen-control',
'shortcuts-modal',
],
menus: {
topToolBar: 'topDefault',
sideToolBar: 'sideDefault',
overlay: 'defaultOverlay',
},
})
// export default (
// <Manage nav={<Navigation />}>
// <Switch>
// <Redirect exact path='/' to='/books' />
// <PrivateRoute exact path='/books' component={Dashboard} />
// <PrivateRoute path='/books/:id/book-builder' component={BookBuilder} />
// <PrivateRoute path='/books/:bookId/fragments/:fragmentId' component={Editor} />
// <PrivateRoute path='/teams' component={TeamsManager} />
// <PrivateRoute path='/users' component={UsersManager} />
// <Route path='/login' component={Login} />
// <Route path='/signup' component={Signup} />
// <Route path='/password-reset' component={PasswordReset} />
// </Switch>
// </Manage>