add rough client interface \n complete server interface for adding revisions \n remove scaffolding examples

This commit is contained in:
Adam Brown 2015-02-07 13:49:04 -05:00
parent 43ea540556
commit 4917885686
32 changed files with 1889 additions and 229 deletions

View File

@ -0,0 +1,31 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'app/document/index/index.html',
controller: 'DocumentIndexCtrl'
})
.when('/user/:userid', {
templateUrl: 'app/document/index/index.html',
controller: 'DocumentIndexCtrl'
})
.when('/:id', {
templateUrl: 'app/document/show/show.html',
controller: 'DocumentShowCtrl'
})
.when('/:id/revision/new', {
templateUrl: 'app/document/revision-new/revision-new.html',
controller: 'DocumentRevisionNewCtrl'
})
.when('/:id/revision/:revisionid', {
templateUrl: 'app/document/revision/revision.html',
controller: 'DocumentRevisionCtrl'
})
.when('/wdiff/:revisionida/:revisionidb', {
templateUrl: 'app/document/wdiff/wdiff.html',
controller: 'DocumentWdiffCtrl'
});
});

View File

@ -0,0 +1,33 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.controller('DocumentIndexCtrl', function ($scope, $routeParams, $http, Auth, $location) {
$scope.documents = [];
$scope.newDocumentTitle;
$scope.getCurrentUser = Auth.getCurrentUser;
$scope.isLoggedIn = Auth.isLoggedIn;
// if routeParams specifies a user, restrict the query to that user
var path = '/api/documents'+ ($routeParams.userid ? '/owner/'+$routeParams.userid : '');
$http.get(path).success(function(documents) {
$scope.documents = documents;
});
$scope.createDocument = function () {
if ($scope.newDocumentTitle == "")
return
//save the document
$http.post('/api/documents', {title: $scope.newDocumentTitle})
.success(function(newDocument) {
$scope.documents.push(newDocument);
//and document view page
$location.path('/'+newDocument._id);
});
};
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
})

View File

@ -0,0 +1,32 @@
nav(ng-include='"components/navbar/navbar.html"')
nav(ng-include='"components/elements/header.html"', onload='title = "documents"')
.container
.row(ng-show='isLoggedIn()')
.col-lg-9.col-md-12.center-block
form.form-inline
.form-group
label(for='title-input')
| New Document
input.form-control(type='text', id='title-input', placeholder='title', ng-model='newDocumentTitle')
button.btn.btn-default(type='submit', ng-click='createDocument()')
| Create
.row
.col-lg-6.col-md-12(ng-repeat='document in documents | orderBy:"currentRevision.created":true' )
h4
a(href='/{{document._id}}')
{{document.title}}
div
p
| State:
{{document.currentRevision.state}}
p
| Updated:
{{document.currentRevision.created}}
pre
{{json(document)}}
footer(ng-include='"components/elements/footer.html"')

View File

@ -0,0 +1,36 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.controller('DocumentRevisionNewCtrl', function ($scope, $routeParams, $http, Auth, $location) {
$scope.revision = {};
$scope.getCurrentUser = Auth.getCurrentUser;
$scope.isLoggedIn = Auth.isLoggedIn;
$scope.isOwner = function () {
var currentUser = Auth.getCurrentUser();
if (!currentUser || !$scope.revision || !$scope.revision.owner)
return false;
return $scope.revision.owner._id == currentUser._id;
};
var path = '/api/documents/' + $routeParams.id;
$http.get(path).success(function(revision) {
$scope.document = document;
$scope.revision = angular.copy(document.currentRevision);
});
$scope.saveRevision = function() {
//save the revision to the document
$http.post('/api/documents/'+$routeParams.id+'/revisions', $scope.revision)
.success(function(newRevision) {
//and redirect to the revision view page
$location.path('/'+$routeParams.id+'/revision/'+newRevision._id);
});
};
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
})

View File

@ -0,0 +1,26 @@
nav(ng-include='"components/navbar/navbar.html"')
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
.container
.row
form.col-lg-9.col-md-12.center-block
h4
{{revision.document.title}} - {{revision.created}}
.form-group
label(for='status-input')
| Status
input.form-control(type='text', id='status-input', ng-model='revision.status')
.form-group
label(for='content-input')
| Content
textarea.form-control(id='content-input', ng-model='revision.content')
button.btn.btn-default(ng-click='saveRevision()')
| Save
.row
pre.col-lg-9.col-md-12.center-block
{{json(revision)}}
footer(ng-include='"components/elements/footer.html"')

View File

@ -0,0 +1,34 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.controller('DocumentRevisionCtrl', function ($scope, $routeParams, $http, Auth) {
$scope.revision = {};
$scope.getCurrentUser = Auth.getCurrentUser;
$scope.isLoggedIn = Auth.isLoggedIn;
//returns true if the current user is logged in and is the owner of this document / revision
$scope.isOwner = function () {
var currentUser = Auth.getCurrentUser();
if (!currentUser || !$scope.revision || !$scope.revision.owner)
return false;
return $scope.revision.owner._id == currentUser._id;
}
//returns true if this revision is the current revision in its document
$scope.isCurrent = function () {
if (!$scope.revision)
return false;
return $scope.revision._id == $scope.revision.document.currentRevision;
}
var path = '/api/documents/'+$routeParams.id+'/revisions/' + $routeParams.revisionid;
$http.get(path).success(function(revision) {
$scope.revision = revision;
});
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
})

View File

@ -0,0 +1,28 @@
nav(ng-include='"components/navbar/navbar.html"')
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
.container
.row
span(ng-show='isCurrent()')
| Current revision
a.btn.btn-primary(ng-hide='isCurrent()' href='/wdiff/{{revision._id}}/{{revision.document.currentRevision}}')
| wdiff current
.row
.col-lg-6.col-md-9.center-block
h4
{{revision.created}}
div
p
| State:
{{revision.state}}
p
| Updated:
{{revision.created}}
div(btf-markdown='revision.content')
pre
{{json(revision)}}
footer(ng-include='"components/elements/footer.html"')

View File

@ -0,0 +1,26 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.controller('DocumentShowCtrl', function ($scope, $routeParams, $http, Auth) {
$scope.document = {};
$scope.getCurrentUser = Auth.getCurrentUser;
$scope.isLoggedIn = Auth.isLoggedIn;
$scope.isOwner = function () {
var currentUser = Auth.getCurrentUser();
if (!currentUser || !$scope.document || !$scope.document.owner)
return false;
return $scope.document.owner._id == currentUser._id;
}
// if routeParams specifies a user, restrict the query to that user
var path = '/api/documents/' + $routeParams.id;
$http.get(path).success(function(document) {
$scope.document = document;
});
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
})

View File

@ -0,0 +1,27 @@
nav(ng-include='"components/navbar/navbar.html"')
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{document.title}}')
.container
.row(ng-show='isOwner()')
form.col-lg-12
a.btn.btn-primary(href='/{{document._id}}/revision/new')
| New Revision
.row(ng-repeat='revision in document.revisions | orderBy:"created":true' )
.col-lg-6.col-md-9.center-block
h4
a(href='/{{document._id}}/revision/{{revision._id}}')
{{revision.created}}
div
p
| State:
{{revision.state}}
p
| Updated:
{{revision.created}}
pre
| {{json(revision)}}
footer(ng-include='"components/elements/footer.html"')

View File

@ -0,0 +1,40 @@
'use strict';
angular.module('markdownFormatWdiffApp')
.controller('DocumentWdiffCtrl', function ($scope, $routeParams, $http, Auth) {
$scope.wdiff = "";
$scope.same = false;
$scope.revisionA = {};
$scope.revisionB = {};
$scope.getCurrentUser = Auth.getCurrentUser;
$scope.isLoggedIn = Auth.isLoggedIn;
/*
//returns true if the current user is logged in and is the owner of this document / revision
$scope.isOwner = function () {
var currentUser = Auth.getCurrentUser();
if (!currentUser || !$scope.revision || !$scope.revision.owner)
return false;
return $scope.revision.owner._id == currentUser._id;
}
//returns true if this revision is the current revision in its document
$scope.isCurrent = function () {
if (!$scope.revision)
return false;
return $scope.revision._id == $scope.revision.document.currentRevision._id;
}
*/
var path = '/api/documents/wdiff/'+$routeParams.revisionida+'/'+$routeParams.revisionidb;
$http.get(path).success(function(result) {
$scope.result = result;
$scope.wdiff = result.wdiff;
$scope.revisionA = result.a;
$scope.revisionB = result.b;
});
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
})

View File

@ -0,0 +1,19 @@
wdiff.jadenav(ng-include='"components/navbar/navbar.html"')
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
.container
.row
//!!! add controls to view a and b
.row
.col-lg-6.col-md-9.center-block
h4
| wdiff {{a.created}} " -- " {{b.created}}
div
div(btf-markdown='wdiff')
pre
{{json(result)}}
footer(ng-include='"components/elements/footer.html"')

View File

@ -9,18 +9,13 @@ angular.module('markdownFormatWdiffApp')
$scope.displayAsMarkdown = true; $scope.displayAsMarkdown = true;
$scope.compare = function() { $scope.compare = function() {
$http.post('/api/wdiff', //+($scope.displayAsMarkdown ? '/markdown': ''), $http.post('/api/wdiff'+($scope.displayAsMarkdown ? '/markdown': ''),
{ a: $scope.docA, b: $scope.docB }, { a: $scope.docA, b: $scope.docB },
{headers:{"Content-Type":"application/json"}}) {headers:{"Content-Type":"application/json"}})
.success(function (data) { .success(function (data) {
if ($scope.displayAsMarkdown) { if ($scope.displayAsMarkdown) {
var markdown = data.wdiff;
markdown = markdown.replace(/\[-/g, '<del>');
markdown = markdown.replace(/-\]/g, '</del>');
markdown = markdown.replace(/{\+/g, '<ins>');
markdown = markdown.replace(/\+}/g, '</ins>');
$scope.wdiffMarkdown = markdown; //data.markdown; $scope.wdiffMarkdown = data.markdown; //data.markdown;
$scope.wdiff = ''; $scope.wdiff = '';
} }
else { else {

View File

@ -1,8 +1,6 @@
div(ng-include='"components/navbar/navbar.html"') nav(ng-include='"components/navbar/navbar.html"')
header#banner.hero-unit nav(ng-include='"components/elements/header.html"', onload='title = "wdiff"')
.container
h1 wdiff
.container .container
.row .row
@ -43,10 +41,4 @@ header#banner.hero-unit
textarea.form-control(id='docB', ng-model='docB') textarea.form-control(id='docB', ng-model='docB')
footer.footer footer(ng-include='"components/elements/footer.html"')
.container
p
| Wdiff online
= ' | '
a(href='https://madanworb.com') Adam Brown

View File

@ -3,7 +3,7 @@
angular.module('markdownFormatWdiffApp') angular.module('markdownFormatWdiffApp')
.config(function ($routeProvider) { .config(function ($routeProvider) {
$routeProvider $routeProvider
.when('/', { .when('/test/wdiff', {
templateUrl: 'app/wdiff/wdiff.html', templateUrl: 'app/wdiff/wdiff.html',
controller: 'WdiffCtrl' controller: 'WdiffCtrl'
}); });

View File

@ -0,0 +1,8 @@
div.footer
.container
p
a(href='https://madanworb.com') Adam Brown
= ' | '
| This website is
a(href='https://github.com/adamarthurryan/wdiff-markdown-editor') open source
| .

View File

@ -0,0 +1,3 @@
header#banner.hero-unit
.container
h1 {{title}}

View File

@ -68,6 +68,12 @@
<script src="app/account/signup/signup.controller.js"></script> <script src="app/account/signup/signup.controller.js"></script>
<script src="app/admin/admin.controller.js"></script> <script src="app/admin/admin.controller.js"></script>
<script src="app/admin/admin.js"></script> <script src="app/admin/admin.js"></script>
<script src="app/document/document.js"></script>
<script src="app/document/index/index.controller.js"></script>
<script src="app/document/revision-new/revision-new.controller.js"></script>
<script src="app/document/revision/revision.controller.js"></script>
<script src="app/document/show/show.controller.js"></script>
<script src="app/document/wdiff/wdiff.controller.js"></script>
<script src="app/wdiff/wdiff.controller.js"></script> <script src="app/wdiff/wdiff.controller.js"></script>
<script src="app/wdiff/wdiff.js"></script> <script src="app/wdiff/wdiff.js"></script>
<script src="components/auth/auth.service.js"></script> <script src="components/auth/auth.service.js"></script>

View File

@ -0,0 +1,17 @@
{
"folders":
[
{
"follow_symlinks": true,
"path": "client"
},
{
"follow_symlinks": true,
"path": "e2e"
},
{
"follow_symlinks": true,
"path": "server"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,249 @@
/**
* Using Rails-like standard naming convention for endpoints.
* GET /documents -> index
* POST /documents -> create
* GET /documents/:id -> show
* PUT /documents/:id -> update
* DELETE /documents/:id -> destroy
*/
'use strict';
var _ = require('lodash');
var Document = require('./document.model');
var Revision = require('./revision.model');
var wdiff = require('../../components/wdiff');
var mongoose = require('mongoose');
// Get a list of all documents
exports.index = function(req, res) {
Document
.find()
.populate('owner', '_id name')
.populate('currentRevision', '_id state created')
.exec(function (err, documents) {
if(err) { return handleError(res, err); }
return res.json(200, documents);
});
};
// Get a list of all documents
exports.indexForUser = function(req, res) {
Document
.find({owner: req.params.userid})
.populate('owner', '_id name')
.populate('currentRevision', '_id state created')
.exec(function (err, documents) {
if(err) { return handleError(res, err); }
return res.json(200, documents);
});
};
// Get a single document
exports.show = function(req, res) {
Document
.findById(req.params.id)
.populate('owner', '_id name')
.populate('currentRevision', '_id state created description content')
.populate('revisions', '_id state created description')
.exec(function (err, document) {
if(err) { return handleError(res, err); }
if(!document) { return res.send(404); }
return res.json(document);
});
};
// Creates a new document with the current user as owner
exports.create = function(req, res) {
//we do not allow the api client to change the owner willy-nilly!
//can this be set in the schema somehow?
if (req.body.owner) { delete req.body.owner; }
//nor document id
if (req.body._id) { delete req.body._id; }
//the client cant add revisions either
if (req.body.revisions) { delete req.body.revisions; }
if (req.body.currentRevision) { delete req.body.currentRevision; }
//add the current user to the document object
var document = _.merge(req.body, {owner: req.user._id});
//and add to the db
Document.create(document, function(err, document) {
if(err) { return handleError(res, err); }
//create an initial, empty revision
var revision = {document: document, status: 'empty', content:''};
Revision.create(revision, function(err, revision) {
if(err) { return handleError(res, err); }
//then add that revision back to the document
document.currentRevision = revision;
document.revisions = [revision];
//save the document and return
document.save(function (err, document) {
if(err) { return handleError(res, err); }
return res.json(201, document);
});
});
});
};
/*
// Updates an existing document in the DB.
exports.update = function(req, res) {
//we don't allow the id to be updated
//!!! do we even need to worry about this? Prolly not!
if(req.body._id) { delete req.body._id; }
//??? should we allow the revisions to be updated here? Prolly not
if (req.body.revisions) { delete req.body.revisions; }
Document.findById(req.params.id, function (err, document) {
if (err) { return handleError(res, err); }
if(!document) { return res.send(404); }
// require user authentication
if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
{return res.send(401);}
//do it
var updated = _.merge(document, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, document);
});
});
};
*/
// Deletes a document from the DB.
exports.destroy = function(req, res) {
Document.findById(req.params.id, function (err, document) {
if(err) { return handleError(res, err); }
if(!document) { return res.send(404); }
// require user authentication
if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
{return res.send(401);}
//do it
document.remove(function(err) {
if(err) { return handleError(res, err); }
return res.send(204);
});
});
};
// Get a list of all revisions for a document
exports.indexRevisionsForDocument = function(req, res) {
Revision
.find({document: req.params.id})
.populate('owner', '_id name')
.populate('document', '_id title currentRevision')
.exec(function (err, revisions) {
if(err) { return handleError(res, err); }
if(!revisions) { return res.send(404); }
return res.json(200, revisions);
});
};
// Show a revision by id
exports.showRevision = function(req, res) {
Revision
.findById(req.params.revisionid)
.populate('owner', '_id name')
.populate('document', '_id title currentRevision')
.exec(function (err, revision) {
if(err) { return handleError(res, err); }
if(!revision) { return res.send(404); }
//ensure this revision actually belongs to the specified document
if (! mongoose.Types.ObjectId(revision.document._id).equals(req.params.id))
{return res.status(403).send("Mismatch between revision id and document id.");}
// ??? require user authentication
//if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
// {return res.send(401);}
return res.json(revision);
});
};
// Create a new revision and add it to a document (with the current user as owner)
// This can only be performed by the user who owns the document
exports.createRevision = function(req, res) {
//we do not allow the api client to change the owner willy-nilly!
//can this be set in the schema somehow?
if (req.body.owner) { delete req.body.owner; }
//similarly the document
if (req.body.document) { delete req.body.document; }
//and the date
if (req.body.created) { delete req.body.created; }
//get the record for the parent document
Document.findById(req.params.id).exec(function(err, document){
if (err) { return handleError(res, err); }
if(!document) { return res.send(404); }
// require user authentication
if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
{return res.send(401);}
//set the owner and document fields for the revision
var revision = _.merge(req.body, {owner: req.user, document: document});
//create the record
Revision.create(revision, function (err, revision) {
if(err) { return handleError(res, err); }
//and update the document
document.revisions.push(revision);
document.currentRevision = revision;
document.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, revision);
});
});
});
};
//compares two revisions with wdiff
exports.wdiff = function(req, res) {
Revision.findById(req.params.revisionida).exec(function (err, revisiona) {
if(err) { return handleError(res, err); }
if(!revisiona) { return res.send(404); }
Revision.findById(req.params.revisionidb).exec(function (err, revisionb) {
if(err) { return handleError(res, err); }
if(!revisionb) { return res.send(404); }
//??? do we care if both revisions have the same document?
//??? if they both have the same user?
//??? if the current user is the owner?
wdiff(revisiona.content, revisionb.content, true, function(err, result){
if(err) { return handleError(res, err); }
result.a = revisiona;
result.b = revisionb;
res.json(result);
});
});
});
};
function handleError(res, err) {
return res.send(500, err);
}

View File

@ -0,0 +1,18 @@
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var DocumentSchema = new Schema({
title: String,
owner: {type: Schema.Types.ObjectId, ref: 'User'},
//is it necessary to have the revisions list?
//yes - it maintains the order
revisions : [{ type: Schema.Types.ObjectId, ref: 'Revision' }],
//do we need this? - current revision is just revisions[n-1]
currentRevision : {type: Schema.Types.ObjectId, ref: 'Revision'}
});
module.exports = mongoose.model('Document', DocumentSchema);

View File

@ -0,0 +1,30 @@
'use strict';
var express = require('express');
var controller = require('./document.controller');
var auth = require('../../auth/auth.service');
var router = express.Router();
router.get('/', controller.index);
router.get('/owner/:userid', controller.indexForUser);
router.get('/:id', controller.show);
router.post('/', auth.isAuthenticated(), controller.create);
//router.put('/:id', auth.isAuthenticated(), controller.update);
//router.patch('/:id', auth.isAuthenticated(), controller.update);
router.delete('/:id', auth.isAuthenticated(), controller.destroy);
router.get('/:id/revisions', controller.indexRevisionsForDocument);
router.get('/:id/revisions/:revisionid', controller.showRevision);
router.post('/:id/revisions', auth.isAuthenticated(), controller.createRevision);
/*
router.put('/:id/revisions/:revisionid', auth.isAuthenticated(), controller.updateRevision);
router.patch('/:id/revisions/:revisionid', auth.isAuthenticated(), ontroller.updateRevision);
router.delete('/:id/revisions/:revisionid', auth.isAuthenticated(), controller.destroyRevision);
*/
router.get('/wdiff/:revisionida/:revisionidb', controller.wdiff);
module.exports = router;

View File

@ -0,0 +1,17 @@
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var RevisionSchema = new Schema({
state: String,
//This property is denormalized for efficient querying
owner: {type: Schema.Types.ObjectId, ref: 'User'},
created: {type: Date, default: Date.now},
description: String,
content: String,
document : {type: Schema.Types.ObjectId, ref: 'Document'}
});
module.exports = mongoose.model('Revision', RevisionSchema);

View File

@ -1,15 +0,0 @@
'use strict';
var express = require('express');
var controller = require('./thing.controller');
var router = express.Router();
router.get('/', controller.index);
router.get('/:id', controller.show);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.patch('/:id', controller.update);
router.delete('/:id', controller.destroy);
module.exports = router;

View File

@ -1,68 +0,0 @@
/**
* Using Rails-like standard naming convention for endpoints.
* GET /things -> index
* POST /things -> create
* GET /things/:id -> show
* PUT /things/:id -> update
* DELETE /things/:id -> destroy
*/
'use strict';
var _ = require('lodash');
var Thing = require('./thing.model');
// Get list of things
exports.index = function(req, res) {
Thing.find(function (err, things) {
if(err) { return handleError(res, err); }
return res.json(200, things);
});
};
// Get a single thing
exports.show = function(req, res) {
Thing.findById(req.params.id, function (err, thing) {
if(err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
return res.json(thing);
});
};
// Creates a new thing in the DB.
exports.create = function(req, res) {
Thing.create(req.body, function(err, thing) {
if(err) { return handleError(res, err); }
return res.json(201, thing);
});
};
// Updates an existing thing in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, thing);
});
});
};
// Deletes a thing from the DB.
exports.destroy = function(req, res) {
Thing.findById(req.params.id, function (err, thing) {
if(err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
thing.remove(function(err) {
if(err) { return handleError(res, err); }
return res.send(204);
});
});
};
function handleError(res, err) {
return res.send(500, err);
}

View File

@ -1,12 +0,0 @@
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ThingSchema = new Schema({
name: String,
info: String,
active: Boolean
});
module.exports = mongoose.model('Thing', ThingSchema);

View File

@ -1,20 +0,0 @@
'use strict';
var should = require('should');
var app = require('../../app');
var request = require('supertest');
describe('GET /api/things', function() {
it('should respond with JSON array', function(done) {
request(app)
.get('/api/things')
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
res.body.should.be.instanceof(Array);
done();
});
});
});

View File

@ -5,7 +5,8 @@
var _ = require('lodash'), var _ = require('lodash'),
temp = require('temp'), temp = require('temp'),
fs = require('fs'), fs = require('fs'),
exec = require('child_process').exec; exec = require('child_process').exec,
wdiff = require('../../components/wdiff');
// Automatically track and cleanup files at exit // Automatically track and cleanup files at exit
temp.track(); temp.track();
@ -20,7 +21,7 @@ exports.compareMarkdown = function(req, res) {
// Perform a comparison // Perform a comparison
// The request should be a json object with two string fields: 'a' and 'b' // The request should be a json object with two string fields: 'a' and 'b'
function doCompare(req, res, isMarkdown) { function doCompare(req, res, asMarkdown) {
//check for properly formatted request //check for properly formatted request
if (req.headers["content-type"].toLowerCase() != "application/json") if (req.headers["content-type"].toLowerCase() != "application/json")
@ -32,59 +33,11 @@ exports.compareMarkdown = function(req, res) {
var a = req.body.a; var a = req.body.a;
var b = req.body.b; var b = req.body.b;
//!!! this nested file-open is not a good pattern wdiff(a,b,asMarkdown, function(err, result){
// better would be to use promises and write the two files asynchronously
// open the first file
temp.open('wdiffa-', function(err, filea) {
//handle errors
if (err) if (err)
return handleError(res, err); return handleError(res, err);
//write the string to the file res.json(result);
fs.write(filea.fd, a);
//close the file
fs.close(filea.fd, function(err) {
if (err)
return handleError(res, err);
//open the second file
temp.open('wdiffa-', function(err, fileb) {
if (err)
return handleError(res, err);
//write the string to the file
fs.write(fileb.fd, b);
//close the file
fs.close(fileb.fd, function(err) {
if (err)
return handleError(res, err);
var cmd = "./bin/wdiff " + filea.path + " " +fileb.path;
exec(cmd, function(err, stdout) {
if (err && err.code!=1 && err.code!=0) {
return handleError(res,err);
}
//if no difference was found by wdiff, err.code will be 0
var wdiffSame;
wdiffSame = (err && err.code == 0) ? true:false;
//sub del and ins
var markdown = stdout;
markdown = markdown.replace(/\[-/g, '<del>');
markdown = markdown.replace(/-\]/g, '</del>');
markdown = markdown.replace(/{\+/g, '<ins>');
markdown = markdown.replace(/\+}/g, '</ins>');
var resData = {wdiff:stdout, same: wdiffSame, markdown: markdown};
});
});
});
});
}); });
} }

View File

@ -0,0 +1,76 @@
'use strict';
var _ = require('lodash'),
temp = require('temp'),
fs = require('fs'),
exec = require('child_process').exec;
// Automatically track and cleanup files at exit
temp.track();
// Perform a comparison between a and b
// the callback should have parameters (err, result)
module.exports = function(a, b, asMarkdown, callback) {
//!!! this nested file-open is not a good pattern
// better would be to use promises and write the two files asynchronously
// open the first file
temp.open('wdiffa-', function(err, filea) {
//handle errors
if (err)
return callback(err);
//write the string to the file
fs.write(filea.fd, a);
//close the file
fs.close(filea.fd, function(err) {
if (err)
return callback(err);
//open the second file
temp.open('wdiffa-', function(err, fileb) {
if (err)
return callback(err);
//write the string to the file
fs.write(fileb.fd, b);
//close the file
fs.close(fileb.fd, function(err) {
if (err)
return callback(err);
var cmd = "./bin/wdiff " + filea.path + " " +fileb.path;
exec(cmd, function(err, stdout) {
if (err && err.code!=1 && err.code!=0) {
return callback(err);
}
//if no difference was found by wdiff, err.code will be 0
var wdiffSame;
wdiffSame = (err && err.code == 0) ? true:false;
var resData = {wdiff:stdout, same: wdiffSame};
if (asMarkdown) {
//!!! this needs more sophisticated parsing
//sub del and ins for the wdiff tags
var markdown = stdout;
markdown = markdown.replace(/\[-/g, '<del>');
markdown = markdown.replace(/-\]/g, '</del>');
markdown = markdown.replace(/{\+/g, '<ins>');
markdown = markdown.replace(/\+}/g, '</ins>');
resData.wdiff=markdown;
}
return callback(null, resData);
});
});
});
});
});
}

View File

@ -8,5 +8,5 @@ module.exports = {
uri: 'mongodb://mongodb/markdownformatwdiff-dev' uri: 'mongodb://mongodb/markdownformatwdiff-dev'
}, },
seedDB: false seedDB: true
}; };

View File

@ -5,45 +5,80 @@
'use strict'; 'use strict';
var Thing = require('../api/thing/thing.model'); var Document = require('../api/document/document.model');
var Revision = require('../api/document/revision.model');
var User = require('../api/user/user.model'); var User = require('../api/user/user.model');
Thing.find({}).remove(function() {
Thing.create({
name : 'Development Tools',
info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.'
}, {
name : 'Server and Client integration',
info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.'
}, {
name : 'Smart Build System',
info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html'
}, {
name : 'Modular Structure',
info : 'Best practice client and server structures allow for more code reusability and maximum scalability'
}, {
name : 'Optimized Build',
info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.'
},{
name : 'Deployment Ready',
info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators'
});
});
User.find({}).remove(function() { var oldUser;
User.create({ var testUser;
var testDocument;
var testRevisionIds = [];
Document.find({owner:null}).remove();
Revision.find({owner:null}).remove();
User.findOne({email:'test@test.com'}).exec()
.then(function(user) {
oldUser = user;
console.log('removing test revisions');
Revision.find({owner: user}).remove().exec();
console.log('removing test documents');
Document.find({owner: user}).remove().exec();
console.log('removing test user');
User.find({email:'test@test.com'}).remove().exec();
}, function (err) { console.log('err: '+err)})
.then(function() {
console.log('creating test user');
return User.create({
provider: 'local', provider: 'local',
name: 'Test User', name: 'Test User',
email: 'test@test.com', email: 'test@test.com',
password: 'test' password: 'test'
});
}, function (err) { console.log('err: '+err)})
.then(function(user) {
testUser = user;
console.log('creating test document');
return Document.create({
owner: testUser,
title: "test document",
});
}, function (err) { console.log('err: '+err)})
.then(function(document) {
testDocument = document;
console.log('creating test revisions');
return Revision.create({
owner: testUser,
state: "rough draft",
description: "test revision a",
content: "blah, blah, blah",
document: testDocument
}, { }, {
provider: 'local', owner: testUser,
role: 'admin', state: "final draft",
name: 'Admin', description: "test revision b",
email: 'admin@admin.com', content: "Blah, blah, blah!",
password: 'admin' document: testDocument
}, function() { }, function (err, revisionA, revisionB) {
console.log('finished populating users'); console.log('updating document with revisions');
} testRevisionIds = [revisionA, revisionB];
); testDocument.currentRevision = revisionB;
}); testDocument.revisions.push(revisionA);
testDocument.revisions.push(revisionB);
return testDocument.save();
});
}, function (err) { console.log('err: '+err)})
.then(function() {
console.log('finished seeding db');
}, function (err) { console.log('err: '+err)})
;

View File

@ -9,7 +9,7 @@ var errors = require('./components/errors');
module.exports = function(app) { module.exports = function(app) {
// Insert routes below // Insert routes below
app.use('/api/things', require('./api/thing')); app.use('/api/documents', require('./api/document'));
app.use('/api/wdiff', require('./api/wdiff')); app.use('/api/wdiff', require('./api/wdiff'));
app.use('/api/users', require('./api/user')); app.use('/api/users', require('./api/user'));