add rough client interface \n complete server interface for adding revisions \n remove scaffolding examples
This commit is contained in:
parent
43ea540556
commit
4917885686
31
client/app/document/document.js
Normal file
31
client/app/document/document.js
Normal 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'
|
||||
});
|
||||
|
||||
});
|
33
client/app/document/index/index.controller.js
Normal file
33
client/app/document/index/index.controller.js
Normal 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, " "); };
|
||||
})
|
32
client/app/document/index/index.jade
Normal file
32
client/app/document/index/index.jade
Normal 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"')
|
36
client/app/document/revision-new/revision-new.controller.js
Normal file
36
client/app/document/revision-new/revision-new.controller.js
Normal 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, " "); };
|
||||
})
|
26
client/app/document/revision-new/revision-new.jade
Normal file
26
client/app/document/revision-new/revision-new.jade
Normal 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"')
|
34
client/app/document/revision/revision.controller.js
Normal file
34
client/app/document/revision/revision.controller.js
Normal 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, " "); };
|
||||
|
||||
})
|
28
client/app/document/revision/revision.jade
Normal file
28
client/app/document/revision/revision.jade
Normal 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"')
|
26
client/app/document/show/show.controller.js
Normal file
26
client/app/document/show/show.controller.js
Normal 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, " "); };
|
||||
|
||||
})
|
27
client/app/document/show/show.jade
Normal file
27
client/app/document/show/show.jade
Normal 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"')
|
40
client/app/document/wdiff/wdiff.controller.js
Normal file
40
client/app/document/wdiff/wdiff.controller.js
Normal 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, " "); };
|
||||
|
||||
})
|
19
client/app/document/wdiff/wdiff.jade
Normal file
19
client/app/document/wdiff/wdiff.jade
Normal 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"')
|
@ -9,18 +9,13 @@ angular.module('markdownFormatWdiffApp')
|
||||
$scope.displayAsMarkdown = true;
|
||||
|
||||
$scope.compare = function() {
|
||||
$http.post('/api/wdiff', //+($scope.displayAsMarkdown ? '/markdown': ''),
|
||||
$http.post('/api/wdiff'+($scope.displayAsMarkdown ? '/markdown': ''),
|
||||
{ a: $scope.docA, b: $scope.docB },
|
||||
{headers:{"Content-Type":"application/json"}})
|
||||
.success(function (data) {
|
||||
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 = '';
|
||||
}
|
||||
else {
|
||||
|
@ -1,8 +1,6 @@
|
||||
div(ng-include='"components/navbar/navbar.html"')
|
||||
nav(ng-include='"components/navbar/navbar.html"')
|
||||
|
||||
header#banner.hero-unit
|
||||
.container
|
||||
h1 wdiff
|
||||
nav(ng-include='"components/elements/header.html"', onload='title = "wdiff"')
|
||||
|
||||
.container
|
||||
.row
|
||||
@ -43,10 +41,4 @@ header#banner.hero-unit
|
||||
textarea.form-control(id='docB', ng-model='docB')
|
||||
|
||||
|
||||
footer.footer
|
||||
.container
|
||||
p
|
||||
| Wdiff online
|
||||
= ' | '
|
||||
a(href='https://madanworb.com') Adam Brown
|
||||
|
||||
footer(ng-include='"components/elements/footer.html"')
|
@ -3,7 +3,7 @@
|
||||
angular.module('markdownFormatWdiffApp')
|
||||
.config(function ($routeProvider) {
|
||||
$routeProvider
|
||||
.when('/', {
|
||||
.when('/test/wdiff', {
|
||||
templateUrl: 'app/wdiff/wdiff.html',
|
||||
controller: 'WdiffCtrl'
|
||||
});
|
||||
|
8
client/components/elements/footer.jade
Normal file
8
client/components/elements/footer.jade
Normal 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
|
||||
| .
|
3
client/components/elements/header.jade
Normal file
3
client/components/elements/header.jade
Normal file
@ -0,0 +1,3 @@
|
||||
header#banner.hero-unit
|
||||
.container
|
||||
h1 {{title}}
|
@ -68,6 +68,12 @@
|
||||
<script src="app/account/signup/signup.controller.js"></script>
|
||||
<script src="app/admin/admin.controller.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.js"></script>
|
||||
<script src="components/auth/auth.service.js"></script>
|
||||
|
17
markdown-format-wdiff.sublime-project
Normal file
17
markdown-format-wdiff.sublime-project
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"path": "client"
|
||||
},
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"path": "e2e"
|
||||
},
|
||||
{
|
||||
"follow_symlinks": true,
|
||||
"path": "server"
|
||||
}
|
||||
]
|
||||
}
|
1044
markdown-format-wdiff.sublime-workspace
Normal file
1044
markdown-format-wdiff.sublime-workspace
Normal file
File diff suppressed because it is too large
Load Diff
249
server/api/document/document.controller.js
Normal file
249
server/api/document/document.controller.js
Normal 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);
|
||||
}
|
18
server/api/document/document.model.js
Normal file
18
server/api/document/document.model.js
Normal 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);
|
30
server/api/document/index.js
Normal file
30
server/api/document/index.js
Normal 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;
|
17
server/api/document/revision.model.js
Normal file
17
server/api/document/revision.model.js
Normal 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);
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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);
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
@ -5,7 +5,8 @@
|
||||
var _ = require('lodash'),
|
||||
temp = require('temp'),
|
||||
fs = require('fs'),
|
||||
exec = require('child_process').exec;
|
||||
exec = require('child_process').exec,
|
||||
wdiff = require('../../components/wdiff');
|
||||
|
||||
// Automatically track and cleanup files at exit
|
||||
temp.track();
|
||||
@ -20,7 +21,7 @@ exports.compareMarkdown = function(req, res) {
|
||||
|
||||
// Perform a comparison
|
||||
// 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
|
||||
if (req.headers["content-type"].toLowerCase() != "application/json")
|
||||
@ -32,59 +33,11 @@ exports.compareMarkdown = function(req, res) {
|
||||
var a = req.body.a;
|
||||
var b = req.body.b;
|
||||
|
||||
//!!! 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)
|
||||
wdiff(a,b,asMarkdown, function(err, result){
|
||||
if (err)
|
||||
return handleError(res, err);
|
||||
|
||||
//write the string to the file
|
||||
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};
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
res.json(result);
|
||||
});
|
||||
}
|
||||
|
||||
|
76
server/components/wdiff/index.js
Normal file
76
server/components/wdiff/index.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -8,5 +8,5 @@ module.exports = {
|
||||
uri: 'mongodb://mongodb/markdownformatwdiff-dev'
|
||||
},
|
||||
|
||||
seedDB: false
|
||||
seedDB: true
|
||||
};
|
||||
|
@ -5,45 +5,80 @@
|
||||
|
||||
'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');
|
||||
|
||||
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() {
|
||||
User.create({
|
||||
provider: 'local',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
password: 'test'
|
||||
}, {
|
||||
provider: 'local',
|
||||
role: 'admin',
|
||||
name: 'Admin',
|
||||
email: 'admin@admin.com',
|
||||
password: 'admin'
|
||||
}, function() {
|
||||
console.log('finished populating users');
|
||||
}
|
||||
);
|
||||
});
|
||||
var oldUser;
|
||||
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',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
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
|
||||
}, {
|
||||
owner: testUser,
|
||||
state: "final draft",
|
||||
description: "test revision b",
|
||||
content: "Blah, blah, blah!",
|
||||
document: testDocument
|
||||
}, function (err, revisionA, revisionB) {
|
||||
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)})
|
||||
;
|
@ -9,7 +9,7 @@ var errors = require('./components/errors');
|
||||
module.exports = function(app) {
|
||||
|
||||
// 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/users', require('./api/user'));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user