much improvement to client side \n fixed wdiff output for markdown server side
This commit is contained in:
parent
4917885686
commit
ee3bd40ef7
@ -15,10 +15,21 @@ $fa-font-path: "/bower_components/font-awesome/fonts";
|
|||||||
padding: 0.2em 0;
|
padding: 0.2em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.revisions th, table.revisions td{
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.revisions .state {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
// Component styles are injected through grunt
|
// Component styles are injected through grunt
|
||||||
// injector
|
// injector
|
||||||
@import 'account/login/login.scss';
|
@import 'account/login/login.scss';
|
||||||
@import 'admin/admin.scss';
|
@import 'admin/admin.scss';
|
||||||
|
@import 'document/document.scss';
|
||||||
|
@import 'document/revision-new/revision-new.scss';
|
||||||
@import 'wdiff/wdiff.scss';
|
@import 'wdiff/wdiff.scss';
|
||||||
@import 'modal/modal.scss';
|
@import 'modal/modal.scss';
|
||||||
// endinjector
|
// endinjector
|
3
client/app/document/document.scss
Normal file
3
client/app/document/document.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
table.revisions {
|
||||||
|
column-gap: 2em;
|
||||||
|
}
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
angular.module('markdownFormatWdiffApp')
|
||||||
.controller('DocumentIndexCtrl', function ($scope, $routeParams, $http, Auth, $location) {
|
.controller('DocumentIndexCtrl', function ($scope, $routeParams, $http, Auth, $location) {
|
||||||
|
$scope.title = 'Documents';
|
||||||
|
|
||||||
$scope.documents = [];
|
$scope.documents = [];
|
||||||
$scope.newDocumentTitle;
|
$scope.newDocumentTitle = '';
|
||||||
|
|
||||||
$scope.getCurrentUser = Auth.getCurrentUser;
|
$scope.getCurrentUser = Auth.getCurrentUser;
|
||||||
$scope.isLoggedIn = Auth.isLoggedIn;
|
$scope.isLoggedIn = Auth.isLoggedIn;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
nav(ng-include='"components/navbar/navbar.html"')
|
nav(ng-include='"components/navbar/navbar.html"')
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', onload='title = "documents"')
|
nav(ng-include='"components/elements/header.html"')
|
||||||
|
|
||||||
.container
|
.container
|
||||||
.row(ng-show='isLoggedIn()')
|
.row(ng-show='isLoggedIn()')
|
||||||
.col-lg-9.col-md-12.center-block
|
.col-lg-9.col-md-12
|
||||||
form.form-inline
|
form.form-inline
|
||||||
.form-group
|
.form-group
|
||||||
label(for='title-input')
|
label(for='title-input')
|
||||||
@ -15,17 +15,27 @@ nav(ng-include='"components/elements/header.html"', onload='title = "documents"'
|
|||||||
|
|
||||||
.row
|
.row
|
||||||
.col-lg-6.col-md-12(ng-repeat='document in documents | orderBy:"currentRevision.created":true' )
|
.col-lg-6.col-md-12(ng-repeat='document in documents | orderBy:"currentRevision.created":true' )
|
||||||
h4
|
h1
|
||||||
a(href='/{{document._id}}')
|
a(href='/{{document._id}}')
|
||||||
{{document.title}}
|
{{document.title}}
|
||||||
div
|
table.revisions
|
||||||
|
tr
|
||||||
|
th.state State
|
||||||
|
th.created revision
|
||||||
|
tr(ng-repeat='revision in document.revisions | orderBy:"created":true | limitTo:5' )
|
||||||
|
td.state {{revision.state}}
|
||||||
|
td.created
|
||||||
|
h4
|
||||||
|
a(href='/{{document._id}}/revision/{{revision._id}}')
|
||||||
|
{{revision.created}}
|
||||||
|
//div
|
||||||
p
|
p
|
||||||
| State:
|
| State:
|
||||||
{{document.currentRevision.state}}
|
{{document.currentRevision.state}}
|
||||||
p
|
p
|
||||||
| Updated:
|
| Updated:
|
||||||
{{document.currentRevision.created}}
|
{{document.currentRevision.created}}
|
||||||
pre
|
//pre
|
||||||
{{json(document)}}
|
{{json(document)}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
angular.module('markdownFormatWdiffApp')
|
||||||
.controller('DocumentRevisionNewCtrl', function ($scope, $routeParams, $http, Auth, $location) {
|
.controller('DocumentRevisionNewCtrl', function ($scope, $routeParams, $http, Auth, $location) {
|
||||||
|
$scope.title = '';
|
||||||
|
$scope.subtitle = '';
|
||||||
|
|
||||||
$scope.revision = {};
|
$scope.revision = {};
|
||||||
|
|
||||||
|
$scope.stateOptions = ['first draft', 'final draft', 'first edit', 'final edit'];
|
||||||
|
|
||||||
$scope.getCurrentUser = Auth.getCurrentUser;
|
$scope.getCurrentUser = Auth.getCurrentUser;
|
||||||
$scope.isLoggedIn = Auth.isLoggedIn;
|
$scope.isLoggedIn = Auth.isLoggedIn;
|
||||||
@ -17,13 +21,16 @@ angular.module('markdownFormatWdiffApp')
|
|||||||
};
|
};
|
||||||
|
|
||||||
var path = '/api/documents/' + $routeParams.id;
|
var path = '/api/documents/' + $routeParams.id;
|
||||||
$http.get(path).success(function(revision) {
|
$http.get(path).success(function(document) {
|
||||||
$scope.document = document;
|
$scope.document = document;
|
||||||
$scope.revision = angular.copy(document.currentRevision);
|
$scope.revision = angular.copy(document.currentRevision);
|
||||||
|
$scope.title = document.title;
|
||||||
|
$scope.subtitle = 'new revision';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$scope.saveRevision = function() {
|
$scope.saveRevision = function() {
|
||||||
|
alert(JSON.stringify($scope.revision))
|
||||||
//save the revision to the document
|
//save the revision to the document
|
||||||
$http.post('/api/documents/'+$routeParams.id+'/revisions', $scope.revision)
|
$http.post('/api/documents/'+$routeParams.id+'/revisions', $scope.revision)
|
||||||
.success(function(newRevision) {
|
.success(function(newRevision) {
|
||||||
|
@ -1,24 +1,30 @@
|
|||||||
nav(ng-include='"components/navbar/navbar.html"')
|
nav(ng-include='"components/navbar/navbar.html"')
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
|
nav(ng-include='"components/elements/header.html"')
|
||||||
|
|
||||||
.container
|
.container
|
||||||
.row
|
.row
|
||||||
form.col-lg-9.col-md-12.center-block
|
.col-lg-6.col-md-12
|
||||||
h4
|
form
|
||||||
{{revision.document.title}} - {{revision.created}}
|
h4
|
||||||
.form-group
|
{{document.title}} - {{revision.created}}
|
||||||
label(for='status-input')
|
.form-group
|
||||||
| Status
|
label(for='status-input')
|
||||||
input.form-control(type='text', id='status-input', ng-model='revision.status')
|
| Status
|
||||||
.form-group
|
select.form-control(id='status-input', ng-model='revision.state', ng-options='stateOption as stateOption for stateOption in stateOptions')
|
||||||
label(for='content-input')
|
option(value='{{stateOption}}')
|
||||||
| Content
|
//input.form-control(type='text', id='status-input', ng-model='revision.state')
|
||||||
textarea.form-control(id='content-input', ng-model='revision.content')
|
.form-group
|
||||||
button.btn.btn-default(ng-click='saveRevision()')
|
label(for='content-input')
|
||||||
| Save
|
| Content
|
||||||
|
textarea.form-control(id='content-input', ng-model='revision.content')
|
||||||
|
button.btn.btn-primary(ng-click='saveRevision()')
|
||||||
|
| Save
|
||||||
|
|
||||||
.row
|
.col-lg-6.col-md-12
|
||||||
|
div(btf-markdown='revision.content')
|
||||||
|
|
||||||
|
//.row
|
||||||
pre.col-lg-9.col-md-12.center-block
|
pre.col-lg-9.col-md-12.center-block
|
||||||
{{json(revision)}}
|
{{json(revision)}}
|
||||||
|
|
||||||
|
20
client/app/document/revision-new/revision-new.scss
Normal file
20
client/app/document/revision-new/revision-new.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#content-input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (min-width: $screen-md-min) {
|
||||||
|
#content-input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $screen-lg-min) {
|
||||||
|
#content-input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,9 @@ angular.module('markdownFormatWdiffApp')
|
|||||||
.controller('DocumentRevisionCtrl', function ($scope, $routeParams, $http, Auth) {
|
.controller('DocumentRevisionCtrl', function ($scope, $routeParams, $http, Auth) {
|
||||||
$scope.revision = {};
|
$scope.revision = {};
|
||||||
|
|
||||||
|
$scope.title = '';
|
||||||
|
$scope.subtitle = '';
|
||||||
|
|
||||||
$scope.getCurrentUser = Auth.getCurrentUser;
|
$scope.getCurrentUser = Auth.getCurrentUser;
|
||||||
$scope.isLoggedIn = Auth.isLoggedIn;
|
$scope.isLoggedIn = Auth.isLoggedIn;
|
||||||
|
|
||||||
@ -27,6 +30,8 @@ angular.module('markdownFormatWdiffApp')
|
|||||||
var path = '/api/documents/'+$routeParams.id+'/revisions/' + $routeParams.revisionid;
|
var path = '/api/documents/'+$routeParams.id+'/revisions/' + $routeParams.revisionid;
|
||||||
$http.get(path).success(function(revision) {
|
$http.get(path).success(function(revision) {
|
||||||
$scope.revision = revision;
|
$scope.revision = revision;
|
||||||
|
$scope.title = revision.document.title;
|
||||||
|
$scope.subtitle = $scope.isCurrent() ? " (current)":(" ("+$scope.revision.created+")");
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
nav(ng-include='"components/navbar/navbar.html"')
|
nav(ng-include='"components/navbar/navbar.html"')
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
|
nav(ng-include='"components/elements/header.html"')
|
||||||
|
|
||||||
.container
|
.container
|
||||||
.row
|
.row
|
||||||
span(ng-show='isCurrent()')
|
//span(ng-show='isCurrent()')
|
||||||
| Current revision
|
| Current revision
|
||||||
a.btn.btn-primary(ng-hide='isCurrent()' href='/wdiff/{{revision._id}}/{{revision.document.currentRevision}}')
|
a.btn.btn-primary(ng-hide='isCurrent()' href='/wdiff/{{revision._id}}/{{revision.document.currentRevision}}')
|
||||||
| wdiff current
|
| wdiff current
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-lg-6.col-md-9.center-block
|
.col-lg-6.col-md-9
|
||||||
h4
|
|
||||||
{{revision.created}}
|
|
||||||
div
|
div
|
||||||
p
|
p
|
||||||
| State:
|
| State:
|
||||||
{{revision.state}}
|
{{revision.state}}
|
||||||
p
|
|
||||||
| Updated:
|
|
||||||
{{revision.created}}
|
|
||||||
div(btf-markdown='revision.content')
|
div(btf-markdown='revision.content')
|
||||||
pre
|
//pre
|
||||||
{{json(revision)}}
|
{{json(revision)}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
angular.module('markdownFormatWdiffApp')
|
angular.module('markdownFormatWdiffApp')
|
||||||
.controller('DocumentShowCtrl', function ($scope, $routeParams, $http, Auth) {
|
.controller('DocumentShowCtrl', function ($scope, $routeParams, $http, Auth) {
|
||||||
$scope.document = {};
|
$scope.document = {};
|
||||||
|
$scope.title = '';
|
||||||
|
|
||||||
$scope.getCurrentUser = Auth.getCurrentUser;
|
$scope.getCurrentUser = Auth.getCurrentUser;
|
||||||
$scope.isLoggedIn = Auth.isLoggedIn;
|
$scope.isLoggedIn = Auth.isLoggedIn;
|
||||||
@ -19,6 +20,7 @@ angular.module('markdownFormatWdiffApp')
|
|||||||
var path = '/api/documents/' + $routeParams.id;
|
var path = '/api/documents/' + $routeParams.id;
|
||||||
$http.get(path).success(function(document) {
|
$http.get(path).success(function(document) {
|
||||||
$scope.document = document;
|
$scope.document = document;
|
||||||
|
$scope.title = document.title;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
||||||
|
@ -1,26 +1,28 @@
|
|||||||
nav(ng-include='"components/navbar/navbar.html"')
|
nav(ng-include='"components/navbar/navbar.html"')
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{document.title}}')
|
nav(ng-include='"components/elements/header.html"')
|
||||||
|
|
||||||
.container
|
.container
|
||||||
.row(ng-show='isOwner()')
|
.row(ng-show='isOwner()')
|
||||||
form.col-lg-12
|
.col-lg-12
|
||||||
a.btn.btn-primary(href='/{{document._id}}/revision/new')
|
form
|
||||||
| New Revision
|
a.btn.btn-primary(href='/{{document._id}}/revision/new')
|
||||||
|
| New Revision
|
||||||
|
|
||||||
.row(ng-repeat='revision in document.revisions | orderBy:"created":true' )
|
.row
|
||||||
.col-lg-6.col-md-9.center-block
|
.col-lg-6.col-md-9.center-block
|
||||||
h4
|
|
||||||
a(href='/{{document._id}}/revision/{{revision._id}}')
|
table.revisions
|
||||||
{{revision.created}}
|
tr
|
||||||
div
|
th.state State
|
||||||
p
|
th.created revision
|
||||||
| State:
|
tr(ng-repeat='revision in document.revisions | orderBy:"created":true | limitTo:5' )
|
||||||
{{revision.state}}
|
td.state {{revision.state}}
|
||||||
p
|
td.created
|
||||||
| Updated:
|
h4
|
||||||
{{revision.created}}
|
a(href='/{{document._id}}/revision/{{revision._id}}')
|
||||||
pre
|
{{revision.created}}
|
||||||
|
//pre
|
||||||
| {{json(revision)}}
|
| {{json(revision)}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
angular.module('markdownFormatWdiffApp')
|
||||||
.controller('DocumentWdiffCtrl', function ($scope, $routeParams, $http, Auth) {
|
.controller('DocumentWdiffCtrl', function ($scope, $routeParams, $http, Auth) {
|
||||||
$scope.wdiff = "";
|
$scope.wdiff = '';
|
||||||
|
$scope.title = '';
|
||||||
$scope.same = false;
|
$scope.same = false;
|
||||||
$scope.revisionA = {};
|
$scope.revisionA = {};
|
||||||
$scope.revisionB = {};
|
$scope.revisionB = {};
|
||||||
@ -33,6 +34,9 @@ angular.module('markdownFormatWdiffApp')
|
|||||||
$scope.wdiff = result.wdiff;
|
$scope.wdiff = result.wdiff;
|
||||||
$scope.revisionA = result.a;
|
$scope.revisionA = result.a;
|
||||||
$scope.revisionB = result.b;
|
$scope.revisionB = result.b;
|
||||||
|
|
||||||
|
$scope.title = result.a.document.title;
|
||||||
|
$scope.subtitle = "wdiff: "+result.a.created + " / " + result.b.created;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
$scope.json = function (object) { return JSON.stringify(object, null, " "); };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
wdiff.jadenav(ng-include='"components/navbar/navbar.html"')
|
wdiff.jadenav(ng-include='"components/navbar/navbar.html"')
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revision.created}}')
|
nav(ng-include='"components/elements/header.html"')
|
||||||
|
|
||||||
.container
|
.container
|
||||||
.row
|
.row
|
||||||
@ -8,11 +8,8 @@ nav(ng-include='"components/elements/header.html"', ng-onload='title = {{revisio
|
|||||||
|
|
||||||
.row
|
.row
|
||||||
.col-lg-6.col-md-9.center-block
|
.col-lg-6.col-md-9.center-block
|
||||||
h4
|
|
||||||
| wdiff {{a.created}} " -- " {{b.created}}
|
|
||||||
div
|
|
||||||
div(btf-markdown='wdiff')
|
div(btf-markdown='wdiff')
|
||||||
pre
|
//pre
|
||||||
{{json(result)}}
|
{{json(result)}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
header#banner.hero-unit
|
header#banner.hero-unit
|
||||||
.container
|
.container
|
||||||
h1 {{title}}
|
h1 {{title}}
|
||||||
|
h3 {{subtitle}}
|
@ -14,6 +14,7 @@
|
|||||||
"express-session": "~1.0.2",
|
"express-session": "~1.0.2",
|
||||||
"jade": "~1.2.0",
|
"jade": "~1.2.0",
|
||||||
"jsonwebtoken": "^0.3.0",
|
"jsonwebtoken": "^0.3.0",
|
||||||
|
"lex": "^1.7.8",
|
||||||
"lodash": "~2.4.1",
|
"lodash": "~2.4.1",
|
||||||
"method-override": "~1.0.0",
|
"method-override": "~1.0.0",
|
||||||
"mongoose": "~3.8.8",
|
"mongoose": "~3.8.8",
|
||||||
|
@ -22,6 +22,7 @@ exports.index = function(req, res) {
|
|||||||
.find()
|
.find()
|
||||||
.populate('owner', '_id name')
|
.populate('owner', '_id name')
|
||||||
.populate('currentRevision', '_id state created')
|
.populate('currentRevision', '_id state created')
|
||||||
|
.populate('revisions', '_id state created description')
|
||||||
.exec(function (err, documents) {
|
.exec(function (err, documents) {
|
||||||
if(err) { return handleError(res, err); }
|
if(err) { return handleError(res, err); }
|
||||||
return res.json(200, documents);
|
return res.json(200, documents);
|
||||||
@ -33,6 +34,7 @@ exports.indexForUser = function(req, res) {
|
|||||||
Document
|
Document
|
||||||
.find({owner: req.params.userid})
|
.find({owner: req.params.userid})
|
||||||
.populate('owner', '_id name')
|
.populate('owner', '_id name')
|
||||||
|
.populate('revisions', '_id state created description')
|
||||||
.populate('currentRevision', '_id state created')
|
.populate('currentRevision', '_id state created')
|
||||||
.exec(function (err, documents) {
|
.exec(function (err, documents) {
|
||||||
if(err) { return handleError(res, err); }
|
if(err) { return handleError(res, err); }
|
||||||
@ -189,21 +191,36 @@ exports.createRevision = function(req, res) {
|
|||||||
//and the date
|
//and the date
|
||||||
if (req.body.created) { delete req.body.created; }
|
if (req.body.created) { delete req.body.created; }
|
||||||
|
|
||||||
|
//and the id!
|
||||||
|
if (req.body._id) { delete req.body._id; }
|
||||||
|
|
||||||
|
console.log(req.body);
|
||||||
|
|
||||||
//get the record for the parent document
|
//get the record for the parent document
|
||||||
Document.findById(req.params.id).exec(function(err, document){
|
Document.findById(req.params.id).exec(function(err, document){
|
||||||
if (err) { return handleError(res, err); }
|
if (err) { return handleError(res, err); }
|
||||||
if(!document) { return res.send(404); }
|
if(!document) { return res.send(404); }
|
||||||
|
|
||||||
// require user authentication
|
// require user authentication
|
||||||
if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
|
if (! mongoose.Types.ObjectId(document.owner).equals(req.user._id))
|
||||||
{return res.send(401);}
|
{return res.send(401);}
|
||||||
|
|
||||||
|
console.log('---');
|
||||||
|
console.log(document);
|
||||||
|
|
||||||
//set the owner and document fields for the revision
|
//set the owner and document fields for the revision
|
||||||
var revision = _.merge(req.body, {owner: req.user, document: document});
|
var revision = _.merge(req.body, {owner: req.user, document: document});
|
||||||
|
|
||||||
|
console.log('---');
|
||||||
|
console.log(revision);
|
||||||
|
|
||||||
//create the record
|
//create the record
|
||||||
Revision.create(revision, function (err, revision) {
|
Revision.create(revision, function (err, revision) {
|
||||||
if(err) { return handleError(res, err); }
|
if(err) { return handleError(res, err); }
|
||||||
|
if (!revision) {return handleError(res, "Unknown error creating revision");}
|
||||||
|
|
||||||
|
console.log('---');
|
||||||
|
console.log(revision);
|
||||||
|
|
||||||
//and update the document
|
//and update the document
|
||||||
document.revisions.push(revision);
|
document.revisions.push(revision);
|
||||||
@ -219,12 +236,18 @@ exports.createRevision = function(req, res) {
|
|||||||
|
|
||||||
//compares two revisions with wdiff
|
//compares two revisions with wdiff
|
||||||
exports.wdiff = function(req, res) {
|
exports.wdiff = function(req, res) {
|
||||||
Revision.findById(req.params.revisionida).exec(function (err, revisiona) {
|
Revision
|
||||||
|
.findById(req.params.revisionida)
|
||||||
|
.populate('document', 'title')
|
||||||
|
.exec(function (err, revisiona) {
|
||||||
if(err) { return handleError(res, err); }
|
if(err) { return handleError(res, err); }
|
||||||
if(!revisiona) { return res.send(404); }
|
if(!revisiona) { return res.send(404); }
|
||||||
|
|
||||||
|
|
||||||
Revision.findById(req.params.revisionidb).exec(function (err, revisionb) {
|
Revision
|
||||||
|
.findById(req.params.revisionidb)
|
||||||
|
.populate('document', 'title')
|
||||||
|
.exec(function (err, revisionb) {
|
||||||
if(err) { return handleError(res, err); }
|
if(err) { return handleError(res, err); }
|
||||||
if(!revisionb) { return res.send(404); }
|
if(!revisionb) { return res.send(404); }
|
||||||
|
|
||||||
|
@ -3,7 +3,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,
|
||||||
|
Lexer = require('lex');
|
||||||
|
|
||||||
// Automatically track and cleanup files at exit
|
// Automatically track and cleanup files at exit
|
||||||
temp.track();
|
temp.track();
|
||||||
@ -58,12 +59,9 @@ module.exports = function(a, b, asMarkdown, callback) {
|
|||||||
if (asMarkdown) {
|
if (asMarkdown) {
|
||||||
|
|
||||||
//!!! this needs more sophisticated parsing
|
//!!! this needs more sophisticated parsing
|
||||||
//sub del and ins for the wdiff tags
|
|
||||||
var markdown = stdout;
|
var markdown = rewriteWdiffMarkdown(stdout)
|
||||||
markdown = markdown.replace(/\[-/g, '<del>');
|
|
||||||
markdown = markdown.replace(/-\]/g, '</del>');
|
|
||||||
markdown = markdown.replace(/{\+/g, '<ins>');
|
|
||||||
markdown = markdown.replace(/\+}/g, '</ins>');
|
|
||||||
resData.wdiff=markdown;
|
resData.wdiff=markdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,3 +72,178 @@ module.exports = function(a, b, asMarkdown, callback) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rewrites the given wdiff output to correctly render as markdown,
|
||||||
|
assuming the source documents were also valid markdown. */
|
||||||
|
function rewriteWdiffMarkdown(source) {
|
||||||
|
|
||||||
|
//initialize a stack for the lexed input
|
||||||
|
//make it a lodash container, just for kicks
|
||||||
|
var tokens = _([]);
|
||||||
|
|
||||||
|
//define tokens
|
||||||
|
var LDEL = {type:"LDEL"}, RDEL = {type:"RDEL"}, LINS = {type:"LINS"}, RINS = {type:"RINS"};
|
||||||
|
//var STRING = {type: "STRING", value:""};
|
||||||
|
var RDEL_LINS = {type:"RDEL_LINS"};
|
||||||
|
var NEWLINE = {type:"\n"};
|
||||||
|
|
||||||
|
var isStringToken = function (token) { return token.type == "STRING";}
|
||||||
|
|
||||||
|
|
||||||
|
//create a lexer to process the wdiff string
|
||||||
|
var lexer = new Lexer(function (char) {
|
||||||
|
//the default rule creates a string on the stack for unmatched characters
|
||||||
|
//and just adds characters to it as they come in
|
||||||
|
if (tokens.size() == 0 || !isStringToken(tokens.last()))
|
||||||
|
tokens.push({type: "STRING", value:""});
|
||||||
|
|
||||||
|
tokens.last().value += char;
|
||||||
|
});
|
||||||
|
|
||||||
|
//rules for the newline character,
|
||||||
|
//as well as opening and closing (left and right) delete and insert tokens
|
||||||
|
lexer
|
||||||
|
.addRule(/\[-/, function () {
|
||||||
|
tokens.push(LDEL);
|
||||||
|
})
|
||||||
|
.addRule(/-\]/, function () {
|
||||||
|
tokens.push(RDEL);
|
||||||
|
})
|
||||||
|
.addRule(/{\+/, function () {
|
||||||
|
tokens.push(LINS);
|
||||||
|
})
|
||||||
|
.addRule(/\+}/, function () {
|
||||||
|
tokens.push(RINS);
|
||||||
|
})
|
||||||
|
//we have a special rule for joined delete and insert tokens
|
||||||
|
.addRule(/-\] {\+/, function() {
|
||||||
|
tokens.push(RDEL_LINS);
|
||||||
|
})
|
||||||
|
.addRule(/\n/, function () {
|
||||||
|
//tokens.push({type:"STRING", value:"\n"})
|
||||||
|
tokens.push(NEWLINE);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
//do the lexing
|
||||||
|
lexer.setInput(source);
|
||||||
|
lexer.lex();
|
||||||
|
|
||||||
|
//# now we parse and transform the input
|
||||||
|
|
||||||
|
//create a stack for the transformed output
|
||||||
|
var transform = _([]);
|
||||||
|
|
||||||
|
//set the state variables for the parse
|
||||||
|
var SSTRING = "string", SINS = "ins", SDEL = "del", SDELINS = "delins";
|
||||||
|
var state = SSTRING;
|
||||||
|
|
||||||
|
//this is the index of the immediately previous delete string in the transform stack
|
||||||
|
var deleteStartIndex = -1
|
||||||
|
|
||||||
|
//iterate the input tokens to create the intermediate representation
|
||||||
|
tokens.forEach(function(token) {
|
||||||
|
//we add string tokens to the transformed stack
|
||||||
|
if (isStringToken(token)) {
|
||||||
|
//add the string with state information
|
||||||
|
var item = {
|
||||||
|
string: token.value,
|
||||||
|
state: state
|
||||||
|
};
|
||||||
|
//if this is the DELINS state, we will put the string in the transformed stack in a different order
|
||||||
|
// the INS string is spliced into place just after the first DEL string
|
||||||
|
// the point of this is so that the preceeding markdown formatting instructions
|
||||||
|
// on this line are applied equally to the del and ins strings
|
||||||
|
// an extra space is inserted between DEL and INS items, for readibility
|
||||||
|
if (state == SDELINS) {
|
||||||
|
state = SINS;
|
||||||
|
item.state = SINS;
|
||||||
|
var spaceItem = {string: ' ', state: SSTRING};
|
||||||
|
transform.splice(deleteStartIndex+1, 0, item);
|
||||||
|
transform.splice(deleteStartIndex+1, 0, spaceItem);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
transform.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//the various tokens control the transformation mode
|
||||||
|
if (token == LDEL) {
|
||||||
|
state = SDEL;
|
||||||
|
deleteStartIndex = transform.size();
|
||||||
|
}
|
||||||
|
if (token == LINS) {
|
||||||
|
state = SINS;
|
||||||
|
}
|
||||||
|
if (token == RDEL || token == RINS) {
|
||||||
|
state = SSTRING;
|
||||||
|
deleteStartIndex = -1;
|
||||||
|
}
|
||||||
|
if (token == RDEL_LINS) {
|
||||||
|
state = SDELINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == NEWLINE) {
|
||||||
|
transform.push({string: '\n', state: state});
|
||||||
|
}
|
||||||
|
|
||||||
|
//ignore newlines (they get added to the output)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// * now emit the output string
|
||||||
|
var output = "";
|
||||||
|
var newline = true;
|
||||||
|
|
||||||
|
// prefixes are matched as follows:
|
||||||
|
// ^ - start of line
|
||||||
|
// ([ \t]*\>)* - blockquotes (possibly nested)
|
||||||
|
// (
|
||||||
|
// ([ \t]*#*) - headers
|
||||||
|
// |([ \t]+[\*\+-]) - unordered lists
|
||||||
|
// |([ \t]+[0-9]+\.) - numeric lists
|
||||||
|
// )?
|
||||||
|
// [ \t]+ - trailing whitespace
|
||||||
|
var PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]+[\*\+-])|([ \t]+[0-9]+\.))?[ \t]+/
|
||||||
|
//var PREFIX = /^#*/
|
||||||
|
|
||||||
|
|
||||||
|
transform.forEach(function(item) {
|
||||||
|
//newlines are undecorated
|
||||||
|
if (item.string == '\n') {
|
||||||
|
output += '\n';
|
||||||
|
newline = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var prestring = "";
|
||||||
|
var poststring = item.string;
|
||||||
|
|
||||||
|
//if this is a newline, we need to peel off any markdown formatting prefixes
|
||||||
|
//and output them outside the del/ins tags
|
||||||
|
if (newline) {
|
||||||
|
var match = item.string.match(PREFIX);
|
||||||
|
if (match == null)
|
||||||
|
prestring ="";
|
||||||
|
else
|
||||||
|
prestring = match[0];
|
||||||
|
|
||||||
|
poststring = item.string.substring(prestring.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//wrap ins and del strings with tags
|
||||||
|
if (item.state == SDEL)
|
||||||
|
output += prestring+'<del>' + poststring + '</del>';
|
||||||
|
else if (item.state ==SINS)
|
||||||
|
output += prestring+'<ins>' + poststring + '</ins>';
|
||||||
|
|
||||||
|
//and just output other strings
|
||||||
|
else
|
||||||
|
output += prestring+poststring;
|
||||||
|
|
||||||
|
newline = false;
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
|
||||||
|
}
|
@ -8,5 +8,5 @@ module.exports = {
|
|||||||
uri: 'mongodb://mongodb/markdownformatwdiff-dev'
|
uri: 'mongodb://mongodb/markdownformatwdiff-dev'
|
||||||
},
|
},
|
||||||
|
|
||||||
seedDB: true
|
seedDB: false
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user