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