diff --git a/node-backend/package-lock.json b/node-backend/package-lock.json index 9ebce232..279df48b 100644 --- a/node-backend/package-lock.json +++ b/node-backend/package-lock.json @@ -8,6 +8,7 @@ "name": "basic-auth-app", "version": "0.0.0", "dependencies": { + "all": "^0.0.0", "cookie-parser": "~1.4.4", "debug": "~2.6.9", "express": "~4.16.1", @@ -85,6 +86,11 @@ "node": ">=0.10.0" } }, + "node_modules/all": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", + "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" + }, "node_modules/amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -1342,6 +1348,11 @@ "repeat-string": "^1.5.2" } }, + "all": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", + "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", diff --git a/node-backend/package.json b/node-backend/package.json index 05aafda2..3705f809 100644 --- a/node-backend/package.json +++ b/node-backend/package.json @@ -7,13 +7,14 @@ "start": "SET DBPORT=65535 && node ./bin/www" }, "dependencies": { + "all": "^0.0.0", "cookie-parser": "~1.4.4", "debug": "~2.6.9", "express": "~4.16.1", "http-errors": "~1.6.3", "jade": "~1.11.0", + "monk": "7.3.3", "morgan": "~1.9.1", - "njwt": "^1.0.0", - "monk": "7.3.3" + "njwt": "^1.0.0" } } diff --git a/package.json b/package.json new file mode 100644 index 00000000..b15126cd --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "react-router-dom": "^6.7.0", + "react-web-editor": "^2.2.6", + "wysiwyg-editor-react": "^0.1.7" + } +} diff --git a/react-frontend/src/App.js b/react-frontend/src/App.js index 37845757..c85605e6 100644 --- a/react-frontend/src/App.js +++ b/react-frontend/src/App.js @@ -1,3 +1,4 @@ +import React from 'react'; import logo from './logo.svg'; import './App.css'; @@ -11,7 +12,7 @@ function App() {

@@ -23,3 +24,5 @@ function App() { } export default App; + + diff --git a/react-frontend/src/Editor/Webpack.config.js b/react-frontend/src/Editor/Webpack.config.js new file mode 100644 index 00000000..387d74f2 --- /dev/null +++ b/react-frontend/src/Editor/Webpack.config.js @@ -0,0 +1,24 @@ +module.exports = { + entry: "./example/index.js", + output: { + path: './example', + filename: 'bundle.js', + publicPath: '/' + }, + devServer: { + inline: true, + contentBase: './example', + port: 3333 + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel', + query: { + presets: [ 'es2015', 'react' ] + } + }] + } +}; \ No newline at end of file diff --git a/react-frontend/src/Editor/Wysiwyg-editor-react.js b/react-frontend/src/Editor/Wysiwyg-editor-react.js new file mode 100644 index 00000000..e41298ab --- /dev/null +++ b/react-frontend/src/Editor/Wysiwyg-editor-react.js @@ -0,0 +1,185 @@ +'use strict'; + +var React = require('react'); + +var TextEditor = React.createClass({ + displayName: 'TextEditor', + + + getInitialState: function getInitialState() { + return { + ref: this.props.reference || 'wysiwyg_editor', + id: this.props.id || 'wysiwyg_editor', + className: this.props.className || 'well', + style: this.props.style || { maxHeight: '300px', overflow: 'scroll' }, + toolbar_buttons: this.props.toolbar_buttons || ['bold', 'italic', 'underline', 'list', 'link', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'image', 'header'], + show_toolbar: this.props.show_toolbar === undefined ? true : this.props.show_toolbar, + headerTags: ['

', '

', '

', '

', '

', '
'] + }; + }, + componentWillReceiveProps: function componentWillReceiveProps(newProps) { + if (this.state.ref !== newProps.reference || this.state.id !== newProps.id) { + this.setState({ + ref: newProps.reference, + id: newProps.id + }); + } + }, + insertStyle: function insertStyle(type) { + document.execCommand(type, false, null); + this.contentUpdate(); + }, + insertHeading: function insertHeading(h) { + document.execCommand('formatBlock', false, h); + this.contentUpdate(); + }, + insertLink: function insertLink(uri) { + document.execCommand('createLink', false, uri); + this.setState({ link_url: '' }, function () { + this.contentUpdate(); + }); + }, + insertField: function insertField(field, e) { + document.execCommand('insertText', false, field); + this.contentUpdate(); + }, + insertImage: function insertImage(uri) { + document.execCommand('insertImage', false, uri); + this.contentUpdate(); + }, + uploadImage: function uploadImage(e) { + var file = e.target.files[0]; + var reader = new FileReader(); + if (file) { + reader.onload = this.imageIsLoaded; + reader.readAsDataURL(file); + } + }, + imageIsLoaded: function imageIsLoaded(e) { + this.insertImage(e.target.result); + }, + setCursor: function setCursor() { + React.findDOMNode(this.refs[this.state.ref]).focus(); + var sel = window.getSelection(); + var range = document.createRange(); + range.setStart(React.findDOMNode(this.refs[this.state.ref]), 0); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + }, + onMouseDown: function onMouseDown() { + this.setCursor(); + }, + onKeyUp: function onKeyUp(e) { + this.contentUpdate(); + }, + contentUpdate: function contentUpdate() { + var target = React.findDOMNode(this.refs[this.state.ref]); + target.focus(); + this.props.update(target.innerHTML, this.state.ref); + }, + onUrlChange: function onUrlChange(e) { + this.setState({ link_url: e.target.value }); + }, + toolBar: function toolBar() { + if (!this.state.show_toolbar) { + return null; + } + + return this.state.toolbar_buttons.map(function (type) { + return this.getButton(type); + }, this); + }, + getButton: function getButton(type) { + switch (type) { + case 'link': + return React.createElement( + 'div', + { className: 'input-group', key: '_wysiwyg_' + type }, + React.createElement( + 'span', + { className: 'input-group-btn' }, + React.createElement( + 'button', + { key: type, type: 'button', className: 'btn btn-primary', onClick: this.insertLink.bind(null, this.state.link_url) }, + React.createElement('i', { className: 'glyphicon glyphicon-link' }) + ) + ), + React.createElement('input', { type: 'text', className: 'form-control', placeholder: 'URL', value: this.state.link_url, onChange: this.onUrlChange }) + ); + case 'list': + return React.createElement( + 'button', + { key: type, type: 'button', className: 'btn btn-primary', onClick: this.insertStyle.bind(null, 'insertUnorderedList') }, + React.createElement('i', { className: 'glyphicon glyphicon-list' }) + ); + case 'underline': + return React.createElement( + 'button', + { key: type, type: 'button', className: 'btn btn-primary', onClick: this.insertStyle.bind(null, type) }, + 'U' + ); + case 'justify': + var pos = type.split('justify')[1].toLowerCase(); + if (pos === 'full') { + pos = 'justify'; + } + return React.createElement( + 'button', + { key: type, type: 'button', className: 'btn btn-primary', onClick: this.insertStyle.bind(null, type) }, + React.createElement('i', { className: "glyphicon glyphicon-align-" + pos }) + ); + case 'image': + return React.createElement('input', { style: { display: 'inline' }, key: type, type: 'file', className: 'btn btn-primary', onChange: this.uploadImage.bind(this) }); + case 'header': + return this.state.headerTags.map(function (tag, i) { + return React.createElement( + 'button', + { key: type + '_' + tag, type: 'button', className: 'btn btn-primary', onClick: this.insertHeading.bind(null, tag) }, + React.createElement('i', { className: "glyphicon glyphicon-header" }), + i + 1 + ); + }, this); + default: + return React.createElement( + 'button', + { key: type, type: 'button', className: 'btn btn-primary', onClick: this.insertStyle.bind(null, type) }, + React.createElement('i', { className: "glyphicon glyphicon-" + type }) + ); + } + }, + txtEditor: function txtEditor() { + return React.createElement('div', { + id: this.state.id, + ref: this.state.ref, + name: 'text_body', + className: this.state.className, + tabIndex: 0, + key: '0', + contentEditable: true, + onMouseDown: this.onMouseDown, + onTouchStart: this.onMouseDown, + onKeyUp: this.onKeyUp, + onClick: this.props.onClick, + style: this.state.style, + dangerouslySetInnerHTML: { + __html: this.props.html + } + }); + }, + + render: function render() { + return React.createElement( + 'div', + null, + React.createElement( + 'div', + { className: 'form-inline' }, + this.toolBar() + ), + this.txtEditor() + ); + } +}); + +module.exports = TextEditor; \ No newline at end of file diff --git a/react-frontend/src/Editor/wysiwyg-editor-react.jsx b/react-frontend/src/Editor/wysiwyg-editor-react.jsx new file mode 100644 index 00000000..c4440efe --- /dev/null +++ b/react-frontend/src/Editor/wysiwyg-editor-react.jsx @@ -0,0 +1,145 @@ +var React = require( 'react' ); + +var TextEditor = React.createClass({ + + getInitialState: function () { + return { + ref: this.props.reference || 'wysiwyg_editor', + id: this.props.id || 'wysiwyg_editor', + className: this.props.className || 'well', + style: this.props.style || { maxHeight: '300px', overflow: 'scroll' }, + toolbar_buttons: this.props.toolbar_buttons || [ 'bold', 'italic', 'underline', 'list', 'link', 'justifyLeft', 'justifyCenter','justifyRight', 'justifyFull', 'image', 'header' ], + show_toolbar: this.props.show_toolbar === undefined ? true : this.props.show_toolbar, + headerTags: [ '

', '

', '

', '

', '

', '
' ] + } + }, + componentWillReceiveProps: function ( newProps ) { + if ( this.state.ref !== newProps.reference || this.state.id !== newProps.id ) { + this.setState( { + ref: newProps.reference, + id: newProps.id + } ); + } + }, + insertStyle: function ( type ) { + document.execCommand( type, false, null ); + this.contentUpdate(); + }, + insertHeading: function ( h ) { + document.execCommand( 'formatBlock', false, h ); + this.contentUpdate(); + }, + insertLink: function ( uri ) { + document.execCommand( 'createLink', false, uri ); + this.setState( { link_url: '' }, function () { + this.contentUpdate(); + } ); + }, + insertField: function ( field, e ) { + document.execCommand( 'insertText', false, field ); + this.contentUpdate(); + }, + insertImage: function ( uri ) { + document.execCommand( 'insertImage', false, uri ); + this.contentUpdate(); + }, + uploadImage: function ( e ) { + var file = e.target.files[ 0 ]; + var reader = new FileReader(); + if ( file ) { + reader.onload = this.imageIsLoaded; + reader.readAsDataURL( file ); + } + }, + imageIsLoaded: function ( e ) { + this.insertImage( e.target.result ); + }, + setCursor: function () { + React.findDOMNode( this.refs[ this.state.ref ] ).focus(); + var sel = window.getSelection(); + var range = document.createRange(); + range.setStart( React.findDOMNode( this.refs[ this.state.ref ] ), 0 ); + range.collapse( true ); + sel.removeAllRanges(); + sel.addRange( range ); + }, + onMouseDown: function () { + this.setCursor(); + }, + onKeyUp: function ( e ) { + this.contentUpdate(); + }, + contentUpdate: function () { + var target = React.findDOMNode( this.refs[ this.state.ref ] ); + target.focus(); + this.props.update( target.innerHTML, this.state.ref ); + }, + onUrlChange: function ( e ) { + this.setState( { link_url: e.target.value } ); + }, + toolBar: function () { + if ( !this.state.show_toolbar ) { return null; } + + return this.state.toolbar_buttons.map( function ( type ) { + return this.getButton( type ); + }, this ); + }, + getButton: function ( type ) { + switch ( type ) { + case 'link': + return ( +
+ + + + +
+ ); + case 'list': + return ; + case 'underline': + return ; + case 'justify': + var pos = type.split( 'justify' )[ 1 ].toLowerCase(); + if ( pos === 'full' ) { pos = 'justify' } + return ; + case 'image': + return ; + case 'header': + return this.state.headerTags.map( function ( tag, i ) { + return + }, this ); + default: + return ; + } + }, + txtEditor () { + return React.createElement( 'div', { + id: this.state.id, + ref: this.state.ref, + name: 'text_body', + className: this.state.className, + tabIndex: 0, + key: '0', + contentEditable: true, + onMouseDown: this.onMouseDown, + onTouchStart: this.onMouseDown, + onKeyUp: this.onKeyUp, + onClick: this.props.onClick, + style: this.state.style, + dangerouslySetInnerHTML: { + __html: this.props.html + } + } ); + }, + render: function(){ + return ( +
+
{ this.toolBar() }
+ { this.txtEditor() } +
+ ); + } +} ); + +module.exports = TextEditor; \ No newline at end of file