implement simple data storage api
This commit is contained in:
parent
822a012129
commit
9d23d48b67
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,8 +1,9 @@
|
|||||||
*~
|
*~
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
|
||||||
# have to add the bundle to version control so deployment with git subtree works
|
# have to add the bundle to version control so deployment with git subtree works
|
||||||
# browser-bundle.js
|
# browser-bundle.js
|
||||||
# skeleton.css
|
|
||||||
|
|
||||||
browser-bundle.js.map
|
browser-bundle.js.map
|
||||||
|
npm-debug.log.*
|
1
data/comp-123.json
Normal file
1
data/comp-123.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is before","b":"this is after","id":"123"}
|
1
data/comp-367e05cc-3c82-4ed4-8325-89b5fef33dfa.json
Normal file
1
data/comp-367e05cc-3c82-4ed4-8325-89b5fef33dfa.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is string 1","b":"this is string 2","id":"367e05cc-3c82-4ed4-8325-89b5fef33dfa"}
|
1
data/comp-5ba17f67-08cc-4ee3-ad29-ab040fa2d396.json
Normal file
1
data/comp-5ba17f67-08cc-4ee3-ad29-ab040fa2d396.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is another before","b":"this is another after","id":"5ba17f67-08cc-4ee3-ad29-ab040fa2d396"}
|
1
data/comp-xxxxxxxx-3c82-4ed4-8325-89b5fef33dfa.json
Normal file
1
data/comp-xxxxxxxx-3c82-4ed4-8325-89b5fef33dfa.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is another string 1","b":"this is another string 2","id":"xxxxxxxx-3c82-4ed4-8325-89b5fef33dfa"}
|
1
data/id-2e1a606a-04b2-4c64-a86d-28f58007919b.json
Normal file
1
data/id-2e1a606a-04b2-4c64-a86d-28f58007919b.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is string 1","b":"this is string 2","id":"2e1a606a-04b2-4c64-a86d-28f58007919b"}
|
1
data/id-xxxxxxxx-04b2-4c64-a86d-28f58007919b.json
Normal file
1
data/id-xxxxxxxx-04b2-4c64-a86d-28f58007919b.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"a":"this is string 1","b":"this is string 2","id":"xxxxxxxx-04b2-4c64-a86d-28f58007919b"}
|
28710
dist/browser-bundle.js
vendored
28710
dist/browser-bundle.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/img/03-small.jpg
vendored
Normal file
BIN
dist/img/03-small.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
dist/img/03-tiny.jpg
vendored
Normal file
BIN
dist/img/03-tiny.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
dist/img/03-tinyer.jpg
vendored
Normal file
BIN
dist/img/03-tinyer.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
3
dist/main.css
vendored
Normal file
3
dist/main.css
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#masthead .header {
|
||||||
|
font-size: 4em;
|
||||||
|
}
|
12
package.json
12
package.json
@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ebpack --colors",
|
"build": "ebpack --colors",
|
||||||
"start": "webpack --progress --colors --watch",
|
"start": "webpack --progress --colors --watch",
|
||||||
"serve": "node src/server.js",
|
"serve": "node src/server/index.js",
|
||||||
|
"webpack-stats": "webpack --profile --json > stats.json",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
@ -14,8 +15,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-preset-es2015-mod": "^6.6.0",
|
"babel-preset-es2015-mod": "^6.6.0",
|
||||||
"babel-preset-es3": "^1.0.1",
|
"babel-preset-es3": "^1.0.1",
|
||||||
|
"body-parser": "^1.15.2",
|
||||||
"diff": "^3.0.1",
|
"diff": "^3.0.1",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
"jsonfile": "^2.4.0",
|
||||||
|
"markdown-it": "^5.1.0",
|
||||||
"react": "^0.14.5",
|
"react": "^0.14.5",
|
||||||
"react-dom": "^0.14.5",
|
"react-dom": "^0.14.5",
|
||||||
"react-redux": "^4.4.6",
|
"react-redux": "^4.4.6",
|
||||||
@ -23,14 +27,16 @@
|
|||||||
"redux": "^3.5.1",
|
"redux": "^3.5.1",
|
||||||
"redux-router": "^1.0.0-beta5",
|
"redux-router": "^1.0.0-beta5",
|
||||||
"reselect": "^2.5.1",
|
"reselect": "^2.5.1",
|
||||||
"semantic-ui-react": "^0.61.6"
|
"semantic-ui-react": "^0.61.6",
|
||||||
|
"uuid": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.3.26",
|
"babel-core": "^6.3.26",
|
||||||
"babel-loader": "^6.2.0",
|
"babel-loader": "^6.2.0",
|
||||||
"babel-preset-es2015": "^6.3.13",
|
"babel-preset-es2015": "^6.3.13",
|
||||||
|
"babel-preset-es2015-native-modules": "^6.9.4",
|
||||||
"babel-preset-react": "^6.3.13",
|
"babel-preset-react": "^6.3.13",
|
||||||
"copyfiles": "^0.2.2",
|
"copyfiles": "^0.2.2",
|
||||||
"webpack": "^1.12.9"
|
"webpack": "^2.1.0-beta.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import Header from './Header'
|
|||||||
import Footer from './Footer'
|
import Footer from './Footer'
|
||||||
import CompareControls from './CompareControls'
|
import CompareControls from './CompareControls'
|
||||||
|
|
||||||
import Show from './Show'
|
import ShowPlaintext from './ShowPlaintext'
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
isMarkdownFormat: Selectors.isMarkdownFormat(state),
|
isMarkdownFormat: Selectors.isMarkdownFormat(state),
|
||||||
@ -42,9 +42,9 @@ class Compare extends React.Component {
|
|||||||
<Segment>
|
<Segment>
|
||||||
{ this.props.isShowDifference ?
|
{ this.props.isShowDifference ?
|
||||||
|
|
||||||
<Show diff={this.props.diff} isMarkdownFormat={this.props.isMarkdownFormat}>{this.props.diff}</Show>:
|
<ShowPlaintext diff={this.props.diff} isMarkdownFormat={this.props.isMarkdownFormat}>{this.props.diff}</ShowPlaintext>:
|
||||||
|
|
||||||
<Show
|
<ShowPlaintext
|
||||||
text={this.props.isShowOriginal? this.props.safeInput.original: this.props.safeInput.final}
|
text={this.props.isShowOriginal? this.props.safeInput.original: this.props.safeInput.final}
|
||||||
isMarkdownFormat={this.props.isMarkdownFormat}
|
isMarkdownFormat={this.props.isMarkdownFormat}
|
||||||
/>
|
/>
|
||||||
|
11
src/components/Footer.js
Normal file
11
src/components/Footer.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {Segment} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
const Footer = (props) => (
|
||||||
|
<Segment basic padded textAlign="center" as="footer">
|
||||||
|
<p><a href="http://adamarthurryan.com">Adam Brown</a> | This website is <a href="https://github.com/adamarthurryan/dubdiff">open source</a>.</p>
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Footer
|
35
src/components/ShowMarkdown.js
Normal file
35
src/components/ShowMarkdown.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
//use markdown-it to render markdown
|
||||||
|
//alternately use markdown to jsx
|
||||||
|
|
||||||
|
const ShowMarkdown = (props) => {
|
||||||
|
return <div>
|
||||||
|
<pre style={{whiteSpace:'pre-wrap'}}>
|
||||||
|
{props.text ?
|
||||||
|
props.text:
|
||||||
|
props.diff ?
|
||||||
|
diffToPre(props.diff) :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShowMarkdown
|
||||||
|
|
||||||
|
function diffToPre(diff) {
|
||||||
|
return diff.map(part => (
|
||||||
|
part.added ? <span><ins>{part.value}</ins>{ifNotNewlineSpace(part.value)}</span> :
|
||||||
|
part.removed ? <span><del>{part.value}</del>{ifNotNewlineSpace(part.value)}</span> :
|
||||||
|
<span>{part.value}{ifNotNewlineSpace(part.value)}</span>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const ifNotNewlineSpace = str => {
|
||||||
|
console.log(str)
|
||||||
|
return !str.endsWith('\n') ? ' ' : ''
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
|
||||||
const Show = (props) => {
|
const ShowPlaintext = (props) => {
|
||||||
return <div>
|
return <div>
|
||||||
<pre style={{whiteSpace:'pre-wrap'}}>
|
<pre style={{whiteSpace:'pre-wrap'}}>
|
||||||
{props.text ?
|
{props.text ?
|
||||||
@ -14,7 +14,7 @@ const Show = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Show
|
export default ShowPlaintext
|
||||||
|
|
||||||
function diffToPre(diff) {
|
function diffToPre(diff) {
|
||||||
return diff.map(part => (
|
return diff.map(part => (
|
@ -1,16 +0,0 @@
|
|||||||
var express = require('express')
|
|
||||||
var path = require('path')
|
|
||||||
|
|
||||||
var app = express()
|
|
||||||
|
|
||||||
|
|
||||||
app.use(express.static('dist'))
|
|
||||||
app.use('bower_components/*', express.static('bower_components'))
|
|
||||||
app.route('/*')
|
|
||||||
.get(function(req, res) {
|
|
||||||
res.sendFile(path.join(__dirname, '..', 'dist', 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(8080, function () {
|
|
||||||
console.log('Server listening on port 8080.')
|
|
||||||
})
|
|
93
src/server/comparison.js
Normal file
93
src/server/comparison.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const jf = require('jsonfile')
|
||||||
|
const fs = require('fs')
|
||||||
|
const uuid = require('uuid')
|
||||||
|
|
||||||
|
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
router.get('/:id', showComparison)
|
||||||
|
router.post('/:id', createComparisonWithId)
|
||||||
|
router.post('/', createComparison)
|
||||||
|
|
||||||
|
//return the comparison given an id, if it exsits
|
||||||
|
function showComparison(req, res) {
|
||||||
|
const id = req.params.id
|
||||||
|
return readRecord(res, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new comparison
|
||||||
|
function createComparison(req, res) {
|
||||||
|
//generate a new id
|
||||||
|
const id = uuid()
|
||||||
|
const {a,b} = req.body
|
||||||
|
|
||||||
|
return writeRecord(res, id, {a,b,id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new comparison
|
||||||
|
function createComparisonWithId(req, res) {
|
||||||
|
//use the id provided in the req
|
||||||
|
const id = req.params.id
|
||||||
|
const {a, b} = req.body
|
||||||
|
|
||||||
|
|
||||||
|
return writeRecord(res, id, {a, b, id})
|
||||||
|
}
|
||||||
|
|
||||||
|
//reads the record from the database
|
||||||
|
function readRecord(res, id, data) {
|
||||||
|
//generate a filename
|
||||||
|
const filename = fnData(id)
|
||||||
|
|
||||||
|
//check if that file exists
|
||||||
|
fs.exists(filename, function (exists) {
|
||||||
|
//if the file does not exist, return a 404
|
||||||
|
if (!exists) return res.status(404).send(`Data id ${id} not found.`)
|
||||||
|
|
||||||
|
//otherwise, read the file as JSON
|
||||||
|
jf.readFile(filename, function(err, data) {
|
||||||
|
if(err) { return handleError(res, err) }
|
||||||
|
|
||||||
|
//and return
|
||||||
|
return res.json(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//writes the record to the database, if it doesn't exist
|
||||||
|
function writeRecord(res, id, data) {
|
||||||
|
|
||||||
|
//look up its filename
|
||||||
|
var filename = fnData(id)
|
||||||
|
|
||||||
|
//need to test that the file does not exist
|
||||||
|
|
||||||
|
//check if that file exists
|
||||||
|
fs.exists(filename, (exists) => {
|
||||||
|
//if the file already exists, return a 405
|
||||||
|
if (exists) return res.status(405).send(`Data id ${id} is already in use.`)
|
||||||
|
|
||||||
|
|
||||||
|
//and write it to the filesystem
|
||||||
|
jf.writeFile(filename, data, (err) => (
|
||||||
|
err ?
|
||||||
|
handleError(res, err) :
|
||||||
|
//if successful, return the comparison object
|
||||||
|
res.status(201).json(data)
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
|
|
||||||
|
function handleError(res, err) {
|
||||||
|
console.log(err);
|
||||||
|
return res.send(500, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// returns a filename for the given comparison
|
||||||
|
function fnData (id) {
|
||||||
|
return "./data/" + "id-" + id + ".json";
|
||||||
|
}
|
35
src/server/index.js
Normal file
35
src/server/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const path = require('path')
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
const comparisonRouter = require('./comparison')
|
||||||
|
|
||||||
|
|
||||||
|
app.use(express.static('dist'))
|
||||||
|
app.use('bower_components/*', express.static('bower_components'))
|
||||||
|
|
||||||
|
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use('/api/compare', comparisonRouter);
|
||||||
|
|
||||||
|
|
||||||
|
app.route('/*')
|
||||||
|
.get(function(req, res) {
|
||||||
|
res.sendFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(8080, function () {
|
||||||
|
console.log('Server listening on port 8080.')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//router.get('/', controller.index);
|
43
src/util/dubdiff.js
Normal file
43
src/util/dubdiff.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as JsDiff from 'diff'
|
||||||
|
|
||||||
|
|
||||||
|
export function plaintextDiff(original, final) {
|
||||||
|
|
||||||
|
|
||||||
|
let arrOriginal = plaintextSplit(original)
|
||||||
|
let arrFinal = plaintextSplit(final)
|
||||||
|
|
||||||
|
let diff = JsDiff.diffArrays(arrOriginal, arrFinal)
|
||||||
|
diff = plaintextRestoreSpaces(diff)
|
||||||
|
|
||||||
|
|
||||||
|
console.log(diffToLogString(diff))
|
||||||
|
|
||||||
|
return diff
|
||||||
|
|
||||||
|
// return JsDiff.diffWordsWithSpace(original,final)
|
||||||
|
|
||||||
|
|
||||||
|
// let diff = JsDiff.diffLines(original.replace(/ /g, '###\n'), final.replace(/ /g, '###\n'))
|
||||||
|
// console.log(diff, diff.map(({added, removed, value})=>({added, removed, value:value.replace(/###\n/g, ' ')})))
|
||||||
|
// return diff.map(({added, removed, value})=>({added, removed, value:value.replace(/###\n/g, ' ')}))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function diffToLogString(diff) {
|
||||||
|
return diff.map(({added, removed, value}) => {
|
||||||
|
let sym = added ? "+" : removed ? '-' : '/'
|
||||||
|
return sym+value+sym
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let plaintextSplit = text =>text.split(/[ ]|(\n)/)
|
||||||
|
function plaintextRestoreSpaces (diff) {
|
||||||
|
return diff.map(({added, removed, value}) => ({
|
||||||
|
added,
|
||||||
|
removed,
|
||||||
|
value:value.map((str, idx, arr) => (
|
||||||
|
(str!='\n' && (idx<arr.length-1)) ? str+" " : str)
|
||||||
|
).join('')
|
||||||
|
}))
|
||||||
|
}
|
@ -13,7 +13,7 @@ module.exports = {
|
|||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
query: {
|
query: {
|
||||||
presets: ['es2015', 'react'],
|
presets: ['es2015-native-modules', 'react'],
|
||||||
compact: "true"
|
compact: "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user