Commit 25734983 authored by jgutix's avatar jgutix

Initial commit

parents
Pipeline #490 skipped
{
"globals": {
"db": true,
"acl": true
},
"extends": ["standard", "standard-react"],
"ecmaFeatures": {
"jsx": true
},
"plugins": [
"react"
]
}
.DS_Store
node_modules/
api/db/*
public/assets/*
.idea
# JATS Writer
Powered by PubSweet.
# Preconditions
Node.js, version must be of 4.x.x series, is a requirement, read about how to install it here: [https://nodejs.org/en/](https://nodejs.org/en/)
Warning about `npm`: Because the newly released npm 3 changed the way npm installs nested dependencies (it flattens them), and we rely on previous behaviour (https://github.com/npm/npm/issues/9809) please use npm 2.x while we work on resolving this.
# Install
```bash
$ git clone https://gitlab.coko.foundation/jgutix/jats-writer.git
$ cd jats-writer
$ npm install
```
# Start the server
First, initialize your blog by running and going through the setup process:
```bash
$ NODE_ENV=dev npm run setup
```
To start the JS compilation and webserver, run:
```bash
$ npm run dev
```
Point your browser to: [http://localhost:3000/manage/posts](http://localhost:3000/manage/posts) and login with the chosen admin username and password and all should be well. Visit [http://localhost:3000](http://localhost:3000) for the blog landing page.
# Themes
Themes are a PubSweet component. If you want to write a custom theme, set your theme component in `config.js`. When you require a style from a component, using e.g. `import './Signup.scss'` in `app/components/Signup/Signup.jsx`, we'll automatically find the right themed style (e.g. `app/components/PepperTheme/Signup/Signup.scss` if theme is set to `PepperTheme`). You can then continue working on your themed styles as usual, and the page will hot-reload when you change anything.
# How to look into the database for debugging purposes
Run a PouchDB server (comes with the app):
```bash
$ npm run pouchdb
```
And navigate to [http://localhost:5984/_utils/](http://localhost:5984/_utils/). Click "Add New Database" and enter "dev", to connect to the development database. You should now be able to run queries on your development database.
# Production installation
These are instructions for Ubuntu 15.10, exact steps may vary from OS to OS so if you're using another system, please take this as general guidance only.
First [install node 4.x](https://github.com/nodesource/distributions#debinstall)
```bash
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs
```
Then clone the repository:
```bash
git clone https://gitlab.coko.foundation/jgutix/jats-writer.git
```
Install the required npm modules:
```bash
npm install
```
Build the production JS:
```bash
npm run build
```
Configure your initial admin account:
```bash
NODE_ENV=production npm run setup
```
Start the server:
```bash
npm run start
```
The application should now be accessible through port 80 on your server.
import React from 'react'
import ReactDOM from 'pubsweet-core/node_modules/react-dom'
import configureStore from 'pubsweet-core/app/store/configureStore'
import Root from 'pubsweet-core/app/components/Root'
import { AppContainer } from 'pubsweet-core/node_modules/react-hot-loader'
import { browserHistory } from 'pubsweet-core/node_modules/react-router'
import { syncHistoryWithStore } from 'pubsweet-core/node_modules/react-router-redux'
let store = configureStore(browserHistory, {})
let history = syncHistoryWithStore(browserHistory, store)
const rootEl = document.getElementById('root')
ReactDOM.render(
<AppContainer>
<Root store={store} history={history}/>
</AppContainer>,
rootEl
)
if (module.hot) {
module.hot.accept('pubsweet-core/app/components/Root', () => {
const NextRoot = require('pubsweet-core/app/components/Root').default
ReactDOM.render(
<AppContainer>
<NextRoot store={store} history={history}/>
</AppContainer>,
rootEl
)
})
}
import React from 'react'
import { LinkContainer } from 'pubsweet-core/node_modules/react-router-bootstrap'
import { Navbar, Nav, NavItem, NavbarBrand } from 'pubsweet-core/node_modules/react-bootstrap'
import AuthHelper from 'pubsweet-core/app/helpers/AuthHelper'
import NavbarUser from 'pubsweet-core/app/components/Navigation/NavbarUser'
export default class Navigation extends React.Component {
render () {
const { actions, auth } = this.props
let logoutButtonIfAuthenticated
if (auth.isAuthenticated) {
logoutButtonIfAuthenticated = <NavbarUser
roles={auth.roles}
username={auth.username}
switchRole={actions.switchRole}
onLogoutClick={actions.logoutUser}
/>
}
return (
<Navbar fluid>
<Navbar.Header>
<NavbarBrand>
<a href='#'><img src='/pubsweet.jpg' alt='science'/></a>
</NavbarBrand>
</Navbar.Header>
<Nav eventKey={0}>
<LinkContainer to='/manage/posts'>
<NavItem>Science Posts</NavItem>
</LinkContainer>
{ AuthHelper.showForUser(auth, 'users') &&
<LinkContainer to='/manage/users'>
<NavItem>Users</NavItem>
</LinkContainer>
}
</Nav>
{ logoutButtonIfAuthenticated }
</Navbar>
)
}
}
Navigation.propTypes = {
actions: React.PropTypes.object.isRequired,
auth: React.PropTypes.object
}
'use strict'
var oo = require('substance-scientist/node_modules/substance/util/oo')
var request = require('substance-scientist/node_modules/substance/util/request')
function PubsweetXMLStore () {
}
PubsweetXMLStore.Prototype = function () {
this.readXML = function (documentId, cb) {
var cached = localStorage.getItem(documentId)
if (cached) {
return cb(null, cached)
}
request('GET', '/data/' + documentId + '.xml', null, cb)
}
// TODO make functional
this.writeXML = function (documentId, xml, cb) {
localStorage.setItem(documentId, xml)
cb(null)
}
}
oo.initClass(PubsweetXMLStore)
module.exports = PubsweetXMLStore
var React = require('react')
var ReactDOM = require('pubsweet-core/node_modules/react-dom')
import { bindActionCreators } from 'pubsweet-core/node_modules/redux'
import { connect } from 'pubsweet-core/node_modules/react-redux'
import * as Actions from 'pubsweet-core/app/actions'
var Editor = require('substance-scientist/examples/jats-editor/JATSEditor.js')
var Configurator = require('substance-scientist/packages/common/BaseConfigurator')
var Component = require('substance-scientist/node_modules/substance/ui/Component')
var DocumentSession = require('substance-scientist/node_modules/substance/model/DocumentSession')
var request = require('substance-scientist/node_modules/substance/util/request')
var PublisherPackage = require('substance-scientist/packages/publisher/package')
var PubsweetXMLStore = require('./PubsweetXMLStore')
var path = require('path')
// Jats-editor wrapped in a React component
// ------------------
class ReactScientistWriter extends React.Component {
getWriter () {
return this
}
createDocumentSession () {
return new DocumentSession('')
}
// New props arrived, update the editor
componentDidUpdate () {
var documentSession = this.createDocumentSession()
this.writer.extendProps({
documentSession: documentSession
})
}
save (source, changes, callback) {
// var exporter = new LensArticleExporter()
// this.props.onSave(exporter.exportDocument(source), callback)
console.log('do nothing for now')
}
componentDidMount () {
var self = this
PubsweetXMLStore.prototype.readXML = function(documentId, cb) {
if (self.props.fragment && self.props.fragment.source) {
return cb(null, self.props.fragment.source)
}
return cb(null,
'<?xml version="1.0" encoding="UTF-8"?>' +
'<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.1d3 20150301//EN" "JATS-archivearticle1.dtd">' +
'<article article-type="sample" dtd-version="1.1d3" xmlns:xlink="http://www.w3.org/1999/xlink">' +
'<front>' +
'<article-meta></article-meta>' +
'</front>' +
'<body><p id="p1">Empty Sample</p></body>' +
'<back></back>' +
'</article>')
}
PubsweetXMLStore.prototype.writeXML = function (documentId, xml, cb) {
self.props.onSave(xml, cb)
}
var el = ReactDOM.findDOMNode(this)
var configurator = new Configurator({
name: 'pubsweet-jats-editor',
configure: function (config) {
// Base package with regular JATS support
config.import(PublisherPackage)
// Define XML Store
config.setXMLStore(PubsweetXMLStore)
config.addStyle(path.join(__dirname, 'ScientistWriter.scss'))
}
})
this.writer = Component.mount(Editor, {
documentId: 'vanilla',
configurator: configurator
}, el)
}
componentWillUnmount () {
this.writer.dispose()
}
render () {
let editor
if (this.props.fragment) {
editor = React.DOM.div({
className: 'scientist-writer-wrapper'
})
} else {
editor = <p>Loading</p>
}
return (
<div>
{editor}
</div>
)
}
}
ReactScientistWriter.propTypes = {
fragment: React.PropTypes.object,
id: React.PropTypes.string.isRequired,
onSave: React.PropTypes.func
}
function mapStateToProps (state, ownProps) {
return {
id: ownProps.id,
fragment: _.find(state.fragments, function (f) {
return f.id === ownProps.id
})
}
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(Actions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ReactScientistWriter)
import React from 'react'
import ReactScientistWriter from './ReactScientistWriter'
import { bindActionCreators } from 'pubsweet-core/node_modules/redux'
import { connect } from 'pubsweet-core/node_modules/react-redux'
import * as Actions from 'pubsweet-core/app/actions'
// Styles
import './ScientistWriter.scss'
class ScientistWriter extends React.Component {
constructor (props) {
super(props)
this.save = this.save.bind(this)
this.uploadFile = this.uploadFile.bind(this)
}
save (source, callback) {
let doc = Object.assign(this.props.fragment, {
source: source
})
this.props.actions.updateFragment(doc)
callback(null, source)
}
uploadFile (file, callback) {
return this.props.uploadFile(file, callback)
}
render () {
return <ReactScientistWriter
documentId={this.props.fragment.id}
id={this.props.fragment.id}
version={this.props.fragment.version}
content={this.props.fragment.source}
onSave={this.save}
/>
}
}
ScientistWriter.propTypes = {
fragment: React.PropTypes.object,
actions: React.PropTypes.object,
save: React.PropTypes.func,
uploadFile: React.PropTypes.func,
id: React.PropTypes.string.isRequired
}
// export default ScientistWriter
function mapStateToProps (state, ownProps) {
return {
id: ownProps.params.id,
fragment: _.find(state.fragments, function (f) {
return f.id === ownProps.params.id
})
}
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(Actions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScientistWriter)
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
<html>
<head>
<title>JATS Editor</title>
<meta charset="UTF-8">
<script type="text/javascript" src="/assets/app.js" async="async"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
\ No newline at end of file
import React from 'react'
import { Route } from 'pubsweet-core/node_modules/react-router'
import { requireAuthentication } from 'pubsweet-core/app/components/AuthenticatedComponent'
// Manage
import Manage from 'pubsweet-core/app/components/Manage/Manage'
import PostsManager from 'pubsweet-core/app/components/PostsManager/PostsManager'
import ScientistWriter from '../app/components/Scientist/ScientistWriter'
import UsersManager from 'pubsweet-core/app/components/UsersManager/UsersManager'
// Public
import Blog from 'pubsweet-core/app/components/Blog/Blog'
// Authentication
import Login from 'pubsweet-core/app/components/Login/Login'
import Signup from 'pubsweet-core/app/components/Signup/Signup'
export default (
<Route>
<Route path='/' component={Blog}/>
<Route path='/manage' component={requireAuthentication(Manage)}>
<Route path='posts' component={PostsManager} />
<Route path='sciencewriter/:id' component={ScientistWriter} />
<Route path='users' component={UsersManager} />
</Route>
<Route path='/login' component={Login} />
<Route path='/signup' component={Signup} />
</Route>
)
const api = require('pubsweet-core/api/api')
const http = require('http')
// Get port from environment or default to 3000
var port = process.env.PORT || '3000'
api.set('port', port)
var server = http.createServer(api)
server.listen(port)
server.on('error', onError)
server.on('listening', onListening)
function onError (error) {
throw error
}
function onListening () {
var addr = server.address()
console.log('PubSweet is listening on ' + addr.port)
}
module.exports = {
secret: 'EXAMPLEDONTUSE',
API_ENDPOINT: '/api',
theme: 'PepperTheme',
editor: 'app/components/Scientist/ScientistWriter.jsx',
routes: 'app/routes.jsx',
navigation: 'app/components/Navigation/Navigation.jsx'
}
{
"name": "jats-writer",
"version": "0.0.1-alpha1",
"private": true,
"scripts": {
"setup": "node node_modules/pubsweet-core/api/setup.js",
"build": "webpack --colors --config ./webpack/webpack.prod.config.js",
"start": "PORT=80 NODE_ENV=production node bin/www",
"dev": "NODE_ENV=dev LOADER=hot node bin/www",
"pouchdb": "./node_modules/pubsweet-core/node_modules/pouchdb-server/bin/pouchdb-server --level-prefix=./api/db/ -d ./api/db/",
"test": "NODE_ENV=test ./node_modules/.bin/mocha --harmony api/test/*.js -R nyan",
"eslint": "eslint"
},
"dependencies": {
"pubsweet-core": "git+https://gitlab.coko.foundation/pubsweet/core.git#7a8e4ee",
"scientist": "git+https://github.com/substance/scientist.git#v1.0.0-alpha"
},
"devDependencies": {
"babel-core": "^6.3.0",
"babel-loader": "^6.2.4",
"babel-eslint": "^4.1.3",
"babel-preset-react-hmre": "^1.1.1",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.5.0",
"css-loader": "^0.23.1",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-loader": "^1.2.0",
"eslint-plugin-react": "^3.14.0",
"eslint-plugin-standard": "^1.1.0",
"extract-text-webpack-plugin": "^1.0.1",
"react": "^0.14.7",
"sass-loader": "^3.2.0",
"style-loader": "^0.12.3",
"url-loader": "^0.5.7",
"webpack": "^1.12.14",
"webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.10.0"
},
"standard": {
"parser": "babel-eslint"
}
}
\ No newline at end of file
var path = require('path')
var webpack = require('webpack')
var config = require('../config')
var ThemeResolver = require('pubsweet-core/webpack/ThemeResolver')
var assetsPath = path.join(__dirname, '..', 'public', 'assets')
var publicPath = '/assets/'
// We're including JSX components from our components package,
// but excluding its node_modules.
var commonLoaders = [
{
test: /\.js$|\.jsx$/,
loader: 'babel',
query: {
presets: ['react-hmre', 'es2015', 'react', 'stage-2'],
plugins: ['pubsweet-core/node_modules/react-hot-loader/babel']
},
include: [
new RegExp(path.join(__dirname, '../node_modules/pubsweet-core/app')),
new RegExp(path.join(__dirname, '../app')),
new RegExp(path.join(__dirname, '../../core/app')) // TODO: Temp while linked.
]
},
{ test: /\.png$/, loader: 'url-loader' },
{
test: /\.woff|\.woff2|\.svg|.eot|\.ttf/,
loader: 'url?prefix=font/&limit=10000'
},
{ test: /\.html$/, loader: 'html-loader' },
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.css$|\.scss$/,
exclude: /\.local\.s?css$/, // Exclude local styles from global
loader: 'style-loader!css-loader!sass-loader'
},
{ test: /\.css$|\.scss$/,
include: /\.local\.s?css/, // Local styles
loader: 'style-loader!css-loader?modules&importLoaders=1!sass-loader'
}
]
module.exports = [
{
// The configuration for the client
name: 'app',
target: 'web',
context: path.join(__dirname, '..', 'app'),
entry: {
app: [
'pubsweet-core/node_modules/react-hot-loader/patch',
'webpack-hot-middleware/client',
'./app'
]
},
output: {
// The output directory as absolute path
path: assetsPath,
// The filename of the entry chunk as relative path inside the output.path directory
filename: '[name].js',
// The output path from the view of the Javascript
publicPath: publicPath
},
devtool: 'inline-source-map',
module: {
preLoaders: [{
test: /\.js$|\.jsx$/,
exclude: [/\/node_modules/, /\/lens/, /\/substance/],
loaders: [/*'eslint-loader'*/]
}],
loaders: commonLoaders
},
resolve: {
root: path.resolve(__dirname, '..'),
extensions: ['', '.js', '.jsx', '.json', '.scss'],
alias: {
'editor$': config.editor,
'routes$': config.routes,
'navigation$': config.navigation
}
},
plugins: [
new webpack.ResolverPlugin([ThemeResolver], ['normal', 'context', 'loader']),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('dev')
}),
new webpack.HotModuleReplacementPlugin(),
// new ExtractTextPlugin('styles.css'),
new webpack.NoErrorsPlugin()
],
node: {
fs: 'empty'
}
}
]
var path = require('path')
var webpack = require('webpack')
var config = require('../config')
var ExtractTextPlugin = require('pubsweet-core/node_modules/extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('pubsweet-core/node_modules/html-webpack-plugin')
var ThemeResolver = require('pubsweet-core/webpack/ThemeResolver')
var assetsPath = path.join(__dirname, '..', 'public', 'assets')
var publicPath = '/assets/'
// We're including JSX components from our components package,
// but excluding its node_modules.
var commonLoaders = [
{
test: /\.js$|\.jsx$/,
loader: 'babel',
query: {
presets: ['es2015', 'react', 'stage-2'],
plugins: ['pubsweet-core/node_modules/react-hot-loader/babel']
},
include: [
new RegExp(path.join(__dirname, '../node_modules/pubsweet-core/app')),
new RegExp(path.join(__dirname, '../app')),
new RegExp(path.join(__dirname, '../../core/app')) // TODO: Temp while linked.
]
},
{ test: /\.png$/, loader: 'url-loader' },
{
test: /\.woff|\.woff2|\.svg|.eot|\.ttf/,
loader: 'url?prefix=font/&limit=10000'
},
{ test: /\.html$/, loader: 'html-loader' },
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.css$|\.scss$/,
exclude: /\.local\.s?css$/, // Exclude local styles from global
loader: 'style-loader!css-loader!sass-loader'
},
{ test: /\.css$|\.scss$/,
include: /\.local\.s?css/, // Local styles
loader: 'style-loader!css-loader?modules&importLoaders=1!sass-loader'
}
]
module.exports = [
{
// The configuration for the client
name: 'app',
target: 'web',
context: path.join(__dirname, '..', 'app'),
entry: {
app: [
'./app'
]
},
output: {
path: assetsPath,
filename: '[name]-[hash].js',
publicPath: publicPath
},
module: {
preLoaders: [{
test: /\.js$|\.jsx$/,