Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e1a0c0f422 | ||
|
7fdaf8e3ab | ||
|
c633afa184 | ||
|
368d19dd21 | ||
|
8c5ac6ade7 | ||
|
70a22ad5ff | ||
|
bd7161a727 | ||
|
f521e78bd2 | ||
|
ed7ad72c2a | ||
|
cc515695a8 | ||
|
136e9c1c8e | ||
|
a2c2407d3c | ||
|
ae5d02b288 | ||
|
966bdae3e1 | ||
|
9c18a2b73b | ||
|
0bc23bf5d3 | ||
|
660c6e6b19 | ||
|
d66292047f | ||
|
720f123977 | ||
|
6bb76ccd17 | ||
|
0a3a37e64e | ||
|
414c1d570e | ||
|
9c10e06c15 | ||
|
755143c0c3 | ||
|
55a30797ec | ||
|
27582ef871 | ||
|
95e2170c7b | ||
|
9d23d48b67 | ||
|
822a012129 | ||
|
dc96ac104c | ||
|
afce2f47a4 |
@ -1 +0,0 @@
|
|||||||
*.coffee
|
|
@ -1,21 +0,0 @@
|
|||||||
# EditorConfig helps developers define and maintain consistent
|
|
||||||
# coding styles between different editors and IDEs
|
|
||||||
# editorconfig.org
|
|
||||||
|
|
||||||
root = true
|
|
||||||
|
|
||||||
|
|
||||||
[*]
|
|
||||||
|
|
||||||
# Change these settings to your own preference
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# We recommend you to keep these unchanged
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
22
.gitignore
vendored
@ -1,11 +1,13 @@
|
|||||||
bin
|
*~
|
||||||
node_modules
|
node_modules
|
||||||
public
|
data/*
|
||||||
.tmp
|
|
||||||
.sass-cache
|
dist/themes
|
||||||
.idea
|
dist/semantic.min.css
|
||||||
client/bower_components
|
|
||||||
dist
|
browser-bundle.js
|
||||||
server/config/local.env.js
|
browser-bundle.js.map
|
||||||
wdiff-1.2.2
|
npm-debug.log.*
|
||||||
data
|
|
||||||
|
stats.json
|
||||||
|
stats.analyzed.txt
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- '0.10'
|
|
||||||
- '0.11'
|
|
||||||
before_script:
|
|
||||||
- npm install -g bower grunt-cli
|
|
||||||
- gem install sass
|
|
||||||
- bower install
|
|
||||||
services: mongodb
|
|
51
.yo-rc.json
@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"generator-angular-fullstack": {
|
|
||||||
"insertRoutes": true,
|
|
||||||
"registerRoutesFile": "server/routes.js",
|
|
||||||
"routesNeedle": "// Insert routes below",
|
|
||||||
"routesBase": "/api/",
|
|
||||||
"pluralizeRoutes": true,
|
|
||||||
"insertSockets": true,
|
|
||||||
"registerSocketsFile": "server/config/socketio.js",
|
|
||||||
"socketsNeedle": "// Insert sockets below",
|
|
||||||
"filters": {
|
|
||||||
"js": true,
|
|
||||||
"jade": true,
|
|
||||||
"sass": true,
|
|
||||||
"ngroute": true,
|
|
||||||
"bootstrap": true,
|
|
||||||
"uibootstrap": true,
|
|
||||||
"mongoose": true,
|
|
||||||
"auth": true,
|
|
||||||
"oauth": true,
|
|
||||||
"googleAuth": true,
|
|
||||||
"facebookAuth": true,
|
|
||||||
"twitterAuth": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"generator-ng-component": {
|
|
||||||
"routeDirectory": "client/app/",
|
|
||||||
"directiveDirectory": "client/app/",
|
|
||||||
"filterDirectory": "client/app/",
|
|
||||||
"serviceDirectory": "client/app/",
|
|
||||||
"basePath": "client",
|
|
||||||
"moduleName": "",
|
|
||||||
"filters": [
|
|
||||||
"ngroute"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
"js",
|
|
||||||
"jade",
|
|
||||||
"scss"
|
|
||||||
],
|
|
||||||
"directiveSimpleTemplates": "",
|
|
||||||
"directiveComplexTemplates": "",
|
|
||||||
"filterTemplates": "",
|
|
||||||
"serviceTemplates": "",
|
|
||||||
"factoryTemplates": "",
|
|
||||||
"controllerTemplates": "",
|
|
||||||
"decoratorTemplates": "",
|
|
||||||
"providerTemplates": "",
|
|
||||||
"routeTemplates": ""
|
|
||||||
}
|
|
||||||
}
|
|
697
Gruntfile.js
@ -1,697 +0,0 @@
|
|||||||
// Generated on 2015-02-05 using generator-angular-fullstack 2.0.13
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function (grunt) {
|
|
||||||
var localConfig;
|
|
||||||
try {
|
|
||||||
localConfig = require('./server/config/local.env');
|
|
||||||
} catch(e) {
|
|
||||||
localConfig = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Load grunt tasks automatically, when needed
|
|
||||||
require('jit-grunt')(grunt, {
|
|
||||||
express: 'grunt-express-server',
|
|
||||||
useminPrepare: 'grunt-usemin',
|
|
||||||
ngtemplates: 'grunt-angular-templates',
|
|
||||||
cdnify: 'grunt-google-cdn',
|
|
||||||
protractor: 'grunt-protractor-runner',
|
|
||||||
injector: 'grunt-asset-injector',
|
|
||||||
buildcontrol: 'grunt-build-control'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Time how long tasks take. Can help when optimizing build times
|
|
||||||
require('time-grunt')(grunt);
|
|
||||||
|
|
||||||
// Define the configuration for all the tasks
|
|
||||||
grunt.initConfig({
|
|
||||||
|
|
||||||
// Project settings
|
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
|
||||||
yeoman: {
|
|
||||||
// configurable paths
|
|
||||||
client: require('./bower.json').appPath || 'client',
|
|
||||||
dist: 'dist'
|
|
||||||
},
|
|
||||||
express: {
|
|
||||||
options: {
|
|
||||||
port: process.env.PORT || 80
|
|
||||||
},
|
|
||||||
dev: {
|
|
||||||
options: {
|
|
||||||
script: 'server/app.js',
|
|
||||||
debug: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prod: {
|
|
||||||
options: {
|
|
||||||
script: 'dist/server/app.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open: {
|
|
||||||
server: {
|
|
||||||
url: 'http://localhost:<%= express.options.port %>'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
injectJS: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.js',
|
|
||||||
'!<%= yeoman.client %>/{app,components}/**/*.spec.js',
|
|
||||||
'!<%= yeoman.client %>/{app,components}/**/*.mock.js',
|
|
||||||
'!<%= yeoman.client %>/app/app.js'],
|
|
||||||
tasks: ['injector:scripts']
|
|
||||||
},
|
|
||||||
injectCss: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.css'
|
|
||||||
],
|
|
||||||
tasks: ['injector:css']
|
|
||||||
},
|
|
||||||
mochaTest: {
|
|
||||||
files: ['server/**/*.spec.js'],
|
|
||||||
tasks: ['env:test', 'mochaTest']
|
|
||||||
},
|
|
||||||
jsTest: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.spec.js',
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.mock.js'
|
|
||||||
],
|
|
||||||
tasks: ['newer:jshint:all', 'karma']
|
|
||||||
},
|
|
||||||
injectSass: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
|
|
||||||
tasks: ['injector:sass']
|
|
||||||
},
|
|
||||||
sass: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
|
|
||||||
tasks: ['sass', 'autoprefixer']
|
|
||||||
},
|
|
||||||
jade: {
|
|
||||||
files: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/*',
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.jade'],
|
|
||||||
tasks: ['jade']
|
|
||||||
},
|
|
||||||
gruntfile: {
|
|
||||||
files: ['Gruntfile.js']
|
|
||||||
},
|
|
||||||
livereload: {
|
|
||||||
files: [
|
|
||||||
'{.tmp,<%= yeoman.client %>}/{app,components}/**/*.css',
|
|
||||||
'{.tmp,<%= yeoman.client %>}/{app,components}/**/*.html',
|
|
||||||
'{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',
|
|
||||||
'!{.tmp,<%= yeoman.client %>}{app,components}/**/*.spec.js',
|
|
||||||
'!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js',
|
|
||||||
'<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}'
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
livereload: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
express: {
|
|
||||||
files: [
|
|
||||||
'server/**/*.{js,json}'
|
|
||||||
],
|
|
||||||
tasks: ['express:dev', 'wait'],
|
|
||||||
options: {
|
|
||||||
livereload: true,
|
|
||||||
nospawn: true //Without this option specified express won't be reloaded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Make sure code styles are up to par and there are no obvious mistakes
|
|
||||||
jshint: {
|
|
||||||
options: {
|
|
||||||
jshintrc: '<%= yeoman.client %>/.jshintrc',
|
|
||||||
reporter: require('jshint-stylish')
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
options: {
|
|
||||||
jshintrc: 'server/.jshintrc'
|
|
||||||
},
|
|
||||||
src: [
|
|
||||||
'server/**/*.js',
|
|
||||||
'!server/**/*.spec.js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
serverTest: {
|
|
||||||
options: {
|
|
||||||
jshintrc: 'server/.jshintrc-spec'
|
|
||||||
},
|
|
||||||
src: ['server/**/*.spec.js']
|
|
||||||
},
|
|
||||||
all: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.js',
|
|
||||||
'!<%= yeoman.client %>/{app,components}/**/*.spec.js',
|
|
||||||
'!<%= yeoman.client %>/{app,components}/**/*.mock.js'
|
|
||||||
],
|
|
||||||
test: {
|
|
||||||
src: [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.spec.js',
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.mock.js'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Empties folders to start fresh
|
|
||||||
clean: {
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
dot: true,
|
|
||||||
src: [
|
|
||||||
'.tmp',
|
|
||||||
'<%= yeoman.dist %>/*',
|
|
||||||
'!<%= yeoman.dist %>/.git*',
|
|
||||||
'!<%= yeoman.dist %>/.openshift',
|
|
||||||
'!<%= yeoman.dist %>/Procfile'
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
server: '.tmp'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Add vendor prefixed styles
|
|
||||||
autoprefixer: {
|
|
||||||
options: {
|
|
||||||
browsers: ['last 1 version']
|
|
||||||
},
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
cwd: '.tmp/',
|
|
||||||
src: '{,*/}*.css',
|
|
||||||
dest: '.tmp/'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Debugging with node inspector
|
|
||||||
'node-inspector': {
|
|
||||||
custom: {
|
|
||||||
options: {
|
|
||||||
'web-host': 'localhost'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use nodemon to run server in debug mode with an initial breakpoint
|
|
||||||
nodemon: {
|
|
||||||
debug: {
|
|
||||||
script: 'server/app.js',
|
|
||||||
options: {
|
|
||||||
nodeArgs: ['--debug-brk'],
|
|
||||||
env: {
|
|
||||||
PORT: process.env.PORT || 80
|
|
||||||
},
|
|
||||||
callback: function (nodemon) {
|
|
||||||
nodemon.on('log', function (event) {
|
|
||||||
console.log(event.colour);
|
|
||||||
});
|
|
||||||
|
|
||||||
// opens browser on initial server start
|
|
||||||
nodemon.on('config:update', function () {
|
|
||||||
setTimeout(function () {
|
|
||||||
require('open')('http://localhost:8080/debug?port=5858');
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Automatically inject Bower components into the app
|
|
||||||
wiredep: {
|
|
||||||
target: {
|
|
||||||
src: '<%= yeoman.client %>/index.html',
|
|
||||||
ignorePath: '<%= yeoman.client %>/',
|
|
||||||
exclude: [/bootstrap-sass-official/, /bootstrap.js/, '/json3/', '/es5-shim/', /bootstrap.css/, /font-awesome.css/ ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Renames files for browser caching purposes
|
|
||||||
rev: {
|
|
||||||
dist: {
|
|
||||||
files: {
|
|
||||||
src: [
|
|
||||||
'<%= yeoman.dist %>/public/{,*/}*.js',
|
|
||||||
'<%= yeoman.dist %>/public/{,*/}*.css',
|
|
||||||
'<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
|
|
||||||
'<%= yeoman.dist %>/public/assets/fonts/*'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reads HTML for usemin blocks to enable smart builds that automatically
|
|
||||||
// concat, minify and revision files. Creates configurations in memory so
|
|
||||||
// additional tasks can operate on them
|
|
||||||
useminPrepare: {
|
|
||||||
html: ['<%= yeoman.client %>/index.html'],
|
|
||||||
options: {
|
|
||||||
dest: '<%= yeoman.dist %>/public'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Performs rewrites based on rev and the useminPrepare configuration
|
|
||||||
usemin: {
|
|
||||||
html: ['<%= yeoman.dist %>/public/{,*/}*.html'],
|
|
||||||
css: ['<%= yeoman.dist %>/public/{,*/}*.css'],
|
|
||||||
js: ['<%= yeoman.dist %>/public/{,*/}*.js'],
|
|
||||||
options: {
|
|
||||||
assetsDirs: [
|
|
||||||
'<%= yeoman.dist %>/public',
|
|
||||||
'<%= yeoman.dist %>/public/assets/images'
|
|
||||||
],
|
|
||||||
// This is so we update image references in our ng-templates
|
|
||||||
patterns: {
|
|
||||||
js: [
|
|
||||||
[/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// The following *-min tasks produce minified files in the dist folder
|
|
||||||
imagemin: {
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
cwd: '<%= yeoman.client %>/assets/images',
|
|
||||||
src: '{,*/}*.{png,jpg,jpeg,gif}',
|
|
||||||
dest: '<%= yeoman.dist %>/public/assets/images'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
svgmin: {
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
cwd: '<%= yeoman.client %>/assets/images',
|
|
||||||
src: '{,*/}*.svg',
|
|
||||||
dest: '<%= yeoman.dist %>/public/assets/images'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Allow the use of non-minsafe AngularJS files. Automatically makes it
|
|
||||||
// minsafe compatible so Uglify does not destroy the ng references
|
|
||||||
ngAnnotate: {
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
cwd: '.tmp/concat',
|
|
||||||
src: '*/**.js',
|
|
||||||
dest: '.tmp/concat'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Package all the html partials into a single javascript payload
|
|
||||||
ngtemplates: {
|
|
||||||
options: {
|
|
||||||
// This should be the name of your apps angular module
|
|
||||||
module: 'markdownFormatWdiffApp',
|
|
||||||
htmlmin: {
|
|
||||||
collapseBooleanAttributes: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
removeAttributeQuotes: true,
|
|
||||||
removeEmptyAttributes: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
removeScriptTypeAttributes: true,
|
|
||||||
removeStyleLinkTypeAttributes: true
|
|
||||||
},
|
|
||||||
usemin: 'app/app.js'
|
|
||||||
},
|
|
||||||
main: {
|
|
||||||
cwd: '<%= yeoman.client %>',
|
|
||||||
src: ['{app,components}/**/*.html'],
|
|
||||||
dest: '.tmp/templates.js'
|
|
||||||
},
|
|
||||||
tmp: {
|
|
||||||
cwd: '.tmp',
|
|
||||||
src: ['{app,components}/**/*.html'],
|
|
||||||
dest: '.tmp/tmp-templates.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Replace Google CDN references
|
|
||||||
cdnify: {
|
|
||||||
dist: {
|
|
||||||
html: ['<%= yeoman.dist %>/public/*.html']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Copies remaining files to places other tasks can use
|
|
||||||
copy: {
|
|
||||||
dist: {
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
dot: true,
|
|
||||||
cwd: '<%= yeoman.client %>',
|
|
||||||
dest: '<%= yeoman.dist %>/public',
|
|
||||||
src: [
|
|
||||||
'*.{ico,png,txt}',
|
|
||||||
'.htaccess',
|
|
||||||
'bower_components/**/*',
|
|
||||||
'assets/images/{,*/}*.{webp}',
|
|
||||||
'assets/fonts/**/*',
|
|
||||||
'index.html'
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
expand: true,
|
|
||||||
cwd: '.tmp/images',
|
|
||||||
dest: '<%= yeoman.dist %>/public/assets/images',
|
|
||||||
src: ['generated/*']
|
|
||||||
}, {
|
|
||||||
expand: true,
|
|
||||||
dest: '<%= yeoman.dist %>',
|
|
||||||
src: [
|
|
||||||
'package.json',
|
|
||||||
'server/**/*'
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
expand: true,
|
|
||||||
cwd: '<%= yeoman.client %>',
|
|
||||||
dest: '.tmp/',
|
|
||||||
src: ['{app,components}/**/*.css']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
buildcontrol: {
|
|
||||||
options: {
|
|
||||||
dir: 'dist',
|
|
||||||
commit: true,
|
|
||||||
push: true,
|
|
||||||
connectCommits: false,
|
|
||||||
message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
|
|
||||||
},
|
|
||||||
heroku: {
|
|
||||||
options: {
|
|
||||||
remote: 'heroku',
|
|
||||||
branch: 'master'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openshift: {
|
|
||||||
options: {
|
|
||||||
remote: 'openshift',
|
|
||||||
branch: 'master'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Run some tasks in parallel to speed up the build process
|
|
||||||
concurrent: {
|
|
||||||
server: [
|
|
||||||
'jade',
|
|
||||||
'sass',
|
|
||||||
],
|
|
||||||
test: [
|
|
||||||
'jade',
|
|
||||||
'sass',
|
|
||||||
],
|
|
||||||
debug: {
|
|
||||||
tasks: [
|
|
||||||
'nodemon',
|
|
||||||
'node-inspector'
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
logConcurrentOutput: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dist: [
|
|
||||||
'jade',
|
|
||||||
'sass',
|
|
||||||
//'imagemin',
|
|
||||||
'svgmin'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Test settings
|
|
||||||
karma: {
|
|
||||||
unit: {
|
|
||||||
configFile: 'karma.conf.js',
|
|
||||||
singleRun: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mochaTest: {
|
|
||||||
options: {
|
|
||||||
reporter: 'spec'
|
|
||||||
},
|
|
||||||
src: ['server/**/*.spec.js']
|
|
||||||
},
|
|
||||||
|
|
||||||
protractor: {
|
|
||||||
options: {
|
|
||||||
configFile: 'protractor.conf.js'
|
|
||||||
},
|
|
||||||
chrome: {
|
|
||||||
options: {
|
|
||||||
args: {
|
|
||||||
browser: 'chrome'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
env: {
|
|
||||||
test: {
|
|
||||||
NODE_ENV: 'test'
|
|
||||||
},
|
|
||||||
prod: {
|
|
||||||
NODE_ENV: 'production'
|
|
||||||
},
|
|
||||||
all: localConfig
|
|
||||||
},
|
|
||||||
|
|
||||||
// Compiles Jade to html
|
|
||||||
jade: {
|
|
||||||
compile: {
|
|
||||||
options: {
|
|
||||||
data: {
|
|
||||||
debug: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
files: [{
|
|
||||||
expand: true,
|
|
||||||
cwd: '<%= yeoman.client %>',
|
|
||||||
src: [
|
|
||||||
'{app,components}/**/*.jade'
|
|
||||||
],
|
|
||||||
dest: '.tmp',
|
|
||||||
ext: '.html'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Compiles Sass to CSS
|
|
||||||
sass: {
|
|
||||||
server: {
|
|
||||||
options: {
|
|
||||||
loadPath: [
|
|
||||||
'<%= yeoman.client %>/bower_components',
|
|
||||||
'<%= yeoman.client %>/app',
|
|
||||||
'<%= yeoman.client %>/components'
|
|
||||||
],
|
|
||||||
compass:false
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
'.tmp/app/app.css' : '<%= yeoman.client %>/app/app.scss'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
injector: {
|
|
||||||
options: {
|
|
||||||
|
|
||||||
},
|
|
||||||
// Inject application script files into index.html (doesn't include bower)
|
|
||||||
scripts: {
|
|
||||||
options: {
|
|
||||||
transform: function(filePath) {
|
|
||||||
filePath = filePath.replace('/client/', '');
|
|
||||||
filePath = filePath.replace('/.tmp/', '');
|
|
||||||
return '<script src="' + filePath + '"></script>';
|
|
||||||
},
|
|
||||||
starttag: '<!-- injector:js -->',
|
|
||||||
endtag: '<!-- endinjector -->'
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
'<%= yeoman.client %>/index.html': [
|
|
||||||
['{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',
|
|
||||||
'!{.tmp,<%= yeoman.client %>}/app/app.js',
|
|
||||||
'!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js',
|
|
||||||
'!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Inject component scss into app.scss
|
|
||||||
sass: {
|
|
||||||
options: {
|
|
||||||
transform: function(filePath) {
|
|
||||||
filePath = filePath.replace('/client/app/', '');
|
|
||||||
filePath = filePath.replace('/client/components/', '');
|
|
||||||
return '@import \'' + filePath + '\';';
|
|
||||||
},
|
|
||||||
starttag: '// injector',
|
|
||||||
endtag: '// endinjector'
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
'<%= yeoman.client %>/app/app.scss': [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}',
|
|
||||||
'!<%= yeoman.client %>/app/app.{scss,sass}'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Inject component css into index.html
|
|
||||||
css: {
|
|
||||||
options: {
|
|
||||||
transform: function(filePath) {
|
|
||||||
filePath = filePath.replace('/client/', '');
|
|
||||||
filePath = filePath.replace('/.tmp/', '');
|
|
||||||
return '<link rel="stylesheet" href="' + filePath + '">';
|
|
||||||
},
|
|
||||||
starttag: '<!-- injector:css -->',
|
|
||||||
endtag: '<!-- endinjector -->'
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
'<%= yeoman.client %>/index.html': [
|
|
||||||
'<%= yeoman.client %>/{app,components}/**/*.css'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Used for delaying livereload until after server has restarted
|
|
||||||
grunt.registerTask('wait', function () {
|
|
||||||
grunt.log.ok('Waiting for server reload...');
|
|
||||||
|
|
||||||
var done = this.async();
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
grunt.log.writeln('Done waiting!');
|
|
||||||
done();
|
|
||||||
}, 1500);
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('express-keepalive', 'Keep grunt running', function() {
|
|
||||||
this.async();
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('serve', function (target) {
|
|
||||||
if (target === 'dist') {
|
|
||||||
return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'express-keepalive']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target === 'debug') {
|
|
||||||
return grunt.task.run([
|
|
||||||
'clean:server',
|
|
||||||
'env:all',
|
|
||||||
'injector:sass',
|
|
||||||
'concurrent:server',
|
|
||||||
'injector',
|
|
||||||
'wiredep',
|
|
||||||
'autoprefixer',
|
|
||||||
'concurrent:debug'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
grunt.task.run([
|
|
||||||
'clean:server',
|
|
||||||
'env:all',
|
|
||||||
'injector:sass',
|
|
||||||
'concurrent:server',
|
|
||||||
'injector',
|
|
||||||
'wiredep',
|
|
||||||
'autoprefixer',
|
|
||||||
'express:dev',
|
|
||||||
'wait',
|
|
||||||
'watch'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('server', function () {
|
|
||||||
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
|
|
||||||
grunt.task.run(['serve']);
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('test', function(target) {
|
|
||||||
if (target === 'server') {
|
|
||||||
return grunt.task.run([
|
|
||||||
'env:all',
|
|
||||||
'env:test',
|
|
||||||
'mochaTest'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (target === 'client') {
|
|
||||||
return grunt.task.run([
|
|
||||||
'clean:server',
|
|
||||||
'env:all',
|
|
||||||
'injector:sass',
|
|
||||||
'concurrent:test',
|
|
||||||
'injector',
|
|
||||||
'autoprefixer',
|
|
||||||
'karma'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (target === 'e2e') {
|
|
||||||
return grunt.task.run([
|
|
||||||
'clean:server',
|
|
||||||
'env:all',
|
|
||||||
'env:test',
|
|
||||||
'injector:sass',
|
|
||||||
'concurrent:test',
|
|
||||||
'injector',
|
|
||||||
'wiredep',
|
|
||||||
'autoprefixer',
|
|
||||||
'express:dev',
|
|
||||||
'protractor'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else grunt.task.run([
|
|
||||||
'test:server',
|
|
||||||
'test:client'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.registerTask('build', [
|
|
||||||
'clean:dist',
|
|
||||||
'injector:sass',
|
|
||||||
'concurrent:dist',
|
|
||||||
'injector',
|
|
||||||
'wiredep',
|
|
||||||
'useminPrepare',
|
|
||||||
'autoprefixer',
|
|
||||||
'ngtemplates',
|
|
||||||
'concat',
|
|
||||||
'ngAnnotate',
|
|
||||||
'copy:dist',
|
|
||||||
'cdnify',
|
|
||||||
'cssmin',
|
|
||||||
'uglify',
|
|
||||||
'rev',
|
|
||||||
'usemin'
|
|
||||||
]);
|
|
||||||
|
|
||||||
grunt.registerTask('default', [
|
|
||||||
'newer:jshint',
|
|
||||||
'test',
|
|
||||||
'build'
|
|
||||||
]);
|
|
||||||
};
|
|
60
README.md
@ -1,44 +1,74 @@
|
|||||||
# dubdiff
|
# dubdiff
|
||||||
|
|
||||||
A diff viewer for markdown-formatted documents.
|
A diff viewer for markdown-formatted and plaintext documents.
|
||||||
|
|
||||||
Uses the [`wdiff`](http://www.gnu.org/software/wdiff/) tool as a diffing engine. This produces an output that is more useful for copy-editing tasks. This wdiff comparison is then processed in a way that is aware of markdown formatting. The resulting output attempts to show differences of copy within the final document format (rather than differences of format).
|
These diffs are intended for use in copy-editing. The diffs are performed word-by-word, similarly to how the [GNU `wdiff`](http://www.gnu.org/software/wdiff/) tool works. This produces a more meaningful diff for English-language editing.
|
||||||
|
|
||||||
The markdown-sensitive processing of the wdiff comparison is at `server/components/wdiff/index.js`, for the curious.
|
The diff may be further processed in a way that is aware of markdown formatting. The resulting output attempts to show differences of copy within the final document format (rather than differences of format).
|
||||||
|
|
||||||
|
The markdown-sensitive processing of the wdiff comparison is at `...`, for the curious.
|
||||||
|
|
||||||
|
|
||||||
## Live Version
|
## Version 2
|
||||||
|
|
||||||
|
This is a complete rewrite of Dubdiff with:
|
||||||
|
|
||||||
|
- simpler project architecture
|
||||||
|
- client-side diffing engine and simplified server
|
||||||
|
- server-side rendering
|
||||||
|
- switch to React from Angular
|
||||||
|
- clean up of diffing engine
|
||||||
|
- goal of implementing a HTML diff viewer
|
||||||
|
|
||||||
|
Basically I'm rewriting it for fun.
|
||||||
|
|
||||||
|
|
||||||
|
## Live Server
|
||||||
|
|
||||||
The tool is live at http://dubdiff.com, feel free to use it there.
|
The tool is live at http://dubdiff.com, feel free to use it there.
|
||||||
|
|
||||||
## Provisioning
|
## Provisioning
|
||||||
|
|
||||||
You'll need the following:
|
You'll need node & npm. Then install dependencies with
|
||||||
- node & npm
|
|
||||||
- grunt and bower (`npm install -g grunt bower`)
|
|
||||||
- ruby (`apt install ruby`)
|
|
||||||
- sass (`gem install sass`)
|
|
||||||
- wdiff (`apt install wdiff`)
|
|
||||||
|
|
||||||
The wdiff binary should be placed in the `bin` subfolder, or a link should be made to the binary. Eg. `ln -s /usr/bin/wdiff bin/wdiff`.
|
npm install
|
||||||
|
|
||||||
|
|
||||||
|
To build and launch a dev server:
|
||||||
|
|
||||||
|
npm start
|
||||||
|
npm run server
|
||||||
|
|
||||||
|
To build and launch the production server:
|
||||||
|
|
||||||
|
npm run build:prod
|
||||||
|
npm run serve:prod
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Data is saved to a simple flat file db in the `data` folder. If this folder doesn't exist, create it.
|
||||||
|
|
||||||
npm install && bower install
|
|
||||||
mkdir data
|
mkdir data
|
||||||
|
|
||||||
|
|
||||||
### Low-memory environments
|
### Low-memory environments
|
||||||
|
|
||||||
On a low-memory machine, eg. a DigitalOcean 512MB instance, you will need to enable virtual memory. Use this guide:
|
On a low-memory machine, eg. a DigitalOcean 512MB instance, you will need to enable virtual memory. Use this guide:
|
||||||
|
|
||||||
[How To Configure Virtual Memory (Swap File) on a VPS](https://www.digitalocean.com/community/tutorials/how-to-configure-virtual-memory-swap-file-on-a-vps#2)
|
[How To Configure Virtual Memory (Swap File) on a VPS](https://www.digitalocean.com/community/tutorials/how-to-configure-virtual-memory-swap-file-on-a-vps#2)
|
||||||
|
|
||||||
### Start on boot
|
|
||||||
|
|
||||||
|
### Start on boot
|
||||||
|
|
||||||
To make the application start on boot, run the following:
|
To make the application start on boot, run the following:
|
||||||
|
|
||||||
pm2 start grunt --name dubdiff -- serve:dist
|
# initialize pm2 to start on boot with the systemd boot manager
|
||||||
pm2 startup systemd
|
pm2 startup systemd
|
||||||
|
|
||||||
|
# start the app with pm2
|
||||||
|
pm2 start npm --name dubdiff -- run serve:prod
|
||||||
|
|
||||||
|
# save the current pm2 config so that it can be reloaded on boot
|
||||||
pm2 save
|
pm2 save
|
||||||
|
|
||||||
[Digital Ocean: How To Set Up a Node.js Application for Production on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04)
|
[Digital Ocean: How To Set Up a Node.js Application for Production on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04)
|
||||||
|
|
||||||
|
1
TODO.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Support for displaying and responding to `#markdown` path suffix.
|
24
bower.json
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "markdown-format-wdiff",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"angular": ">=1.2.*",
|
|
||||||
"json3": "~3.3.1",
|
|
||||||
"es5-shim": "~3.0.1",
|
|
||||||
"jquery": "~1.11.0",
|
|
||||||
"bootstrap-sass-official": "~3.1.1",
|
|
||||||
"bootstrap": "~3.1.1",
|
|
||||||
"angular-resource": ">=1.2.*",
|
|
||||||
"angular-cookies": ">=1.2.*",
|
|
||||||
"angular-sanitize": ">=1.2.*",
|
|
||||||
"angular-route": ">=1.2.*",
|
|
||||||
"angular-bootstrap": "~0.11.0",
|
|
||||||
"font-awesome": ">=4.1.0",
|
|
||||||
"lodash": "~2.4.1",
|
|
||||||
"angular-markdown-directive": "~0.3.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"angular-mocks": ">=1.2.*",
|
|
||||||
"angular-scenario": ">=1.2.*"
|
|
||||||
}
|
|
||||||
}
|
|
543
client/.htaccess
@ -1,543 +0,0 @@
|
|||||||
# Apache Configuration File
|
|
||||||
|
|
||||||
# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
|
|
||||||
# to the main server config file (usually called `httpd.conf`), you should add
|
|
||||||
# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Cross-domain AJAX requests |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Enable cross-origin AJAX requests.
|
|
||||||
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
|
|
||||||
# http://enable-cors.org/
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set Access-Control-Allow-Origin "*"
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | CORS-enabled images |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Send the CORS header for images when browsers request it.
|
|
||||||
# https://developer.mozilla.org/en/CORS_Enabled_Image
|
|
||||||
# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
|
||||||
# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
|
|
||||||
|
|
||||||
<IfModule mod_setenvif.c>
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
|
|
||||||
SetEnvIf Origin ":" IS_CORS
|
|
||||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
|
||||||
</FilesMatch>
|
|
||||||
</IfModule>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Web fonts access |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Allow access from all domains for web fonts
|
|
||||||
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
|
|
||||||
Header set Access-Control-Allow-Origin "*"
|
|
||||||
</FilesMatch>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # ERRORS #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | 404 error prevention for non-existing redirected folders |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Prevent Apache from returning a 404 error for a rewrite if a directory
|
|
||||||
# with the same name does not exist.
|
|
||||||
# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
|
|
||||||
# http://www.webmasterworld.com/apache/3808792.htm
|
|
||||||
|
|
||||||
Options -MultiViews
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Custom error messages / pages |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# You can customize what Apache returns to the client in case of an error (see
|
|
||||||
# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
|
|
||||||
|
|
||||||
ErrorDocument 404 /404.html
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # INTERNET EXPLORER #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Better website experience |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Force IE to render pages in the highest available mode in the various
|
|
||||||
# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
|
|
||||||
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
Header set X-UA-Compatible "IE=edge"
|
|
||||||
# `mod_headers` can't match based on the content-type, however, we only
|
|
||||||
# want to send this header for HTML pages and not for the other resources
|
|
||||||
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
|
||||||
Header unset X-UA-Compatible
|
|
||||||
</FilesMatch>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Cookie setting from iframes |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Allow cookies to be set from iframes in IE.
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Screen flicker |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Stop screen flicker in IE on CSS rollovers (this only works in
|
|
||||||
# combination with the `ExpiresByType` directives for images from below).
|
|
||||||
|
|
||||||
# BrowserMatch "MSIE" brokenvary=1
|
|
||||||
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
|
|
||||||
# BrowserMatch "Opera" !brokenvary
|
|
||||||
# SetEnvIf brokenvary 1 force-no-vary
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # MIME TYPES AND ENCODING #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Proper MIME types for all files |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
<IfModule mod_mime.c>
|
|
||||||
|
|
||||||
# Audio
|
|
||||||
AddType audio/mp4 m4a f4a f4b
|
|
||||||
AddType audio/ogg oga ogg
|
|
||||||
|
|
||||||
# JavaScript
|
|
||||||
# Normalize to standard type (it's sniffed in IE anyways):
|
|
||||||
# http://tools.ietf.org/html/rfc4329#section-7.2
|
|
||||||
AddType application/javascript js jsonp
|
|
||||||
AddType application/json json
|
|
||||||
|
|
||||||
# Video
|
|
||||||
AddType video/mp4 mp4 m4v f4v f4p
|
|
||||||
AddType video/ogg ogv
|
|
||||||
AddType video/webm webm
|
|
||||||
AddType video/x-flv flv
|
|
||||||
|
|
||||||
# Web fonts
|
|
||||||
AddType application/font-woff woff
|
|
||||||
AddType application/vnd.ms-fontobject eot
|
|
||||||
|
|
||||||
# Browsers usually ignore the font MIME types and sniff the content,
|
|
||||||
# however, Chrome shows a warning if other MIME types are used for the
|
|
||||||
# following fonts.
|
|
||||||
AddType application/x-font-ttf ttc ttf
|
|
||||||
AddType font/opentype otf
|
|
||||||
|
|
||||||
# Make SVGZ fonts work on iPad:
|
|
||||||
# https://twitter.com/FontSquirrel/status/14855840545
|
|
||||||
AddType image/svg+xml svg svgz
|
|
||||||
AddEncoding gzip svgz
|
|
||||||
|
|
||||||
# Other
|
|
||||||
AddType application/octet-stream safariextz
|
|
||||||
AddType application/x-chrome-extension crx
|
|
||||||
AddType application/x-opera-extension oex
|
|
||||||
AddType application/x-shockwave-flash swf
|
|
||||||
AddType application/x-web-app-manifest+json webapp
|
|
||||||
AddType application/x-xpinstall xpi
|
|
||||||
AddType application/xml atom rdf rss xml
|
|
||||||
AddType image/webp webp
|
|
||||||
AddType image/x-icon ico
|
|
||||||
AddType text/cache-manifest appcache manifest
|
|
||||||
AddType text/vtt vtt
|
|
||||||
AddType text/x-component htc
|
|
||||||
AddType text/x-vcard vcf
|
|
||||||
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | UTF-8 encoding |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
|
|
||||||
AddDefaultCharset utf-8
|
|
||||||
|
|
||||||
# Force UTF-8 for certain file formats.
|
|
||||||
<IfModule mod_mime.c>
|
|
||||||
AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # URL REWRITES #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Rewrite engine |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
|
|
||||||
# necessary for the following directives to work.
|
|
||||||
|
|
||||||
# If your web host doesn't allow the `FollowSymlinks` option, you may need to
|
|
||||||
# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
|
|
||||||
# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
|
|
||||||
|
|
||||||
# Also, some cloud hosting services require `RewriteBase` to be set:
|
|
||||||
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
|
|
||||||
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
Options +FollowSymlinks
|
|
||||||
# Options +SymLinksIfOwnerMatch
|
|
||||||
RewriteEngine On
|
|
||||||
# RewriteBase /
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Suppressing / Forcing the "www." at the beginning of URLs |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# The same content should never be available under two different URLs especially
|
|
||||||
# not with and without "www." at the beginning. This can cause SEO problems
|
|
||||||
# (duplicate content), therefore, you should choose one of the alternatives and
|
|
||||||
# redirect the other one.
|
|
||||||
|
|
||||||
# By default option 1 (no "www.") is activated:
|
|
||||||
# http://no-www.org/faq.php?q=class_b
|
|
||||||
|
|
||||||
# If you'd prefer to use option 2, just comment out all the lines from option 1
|
|
||||||
# and uncomment the ones from option 2.
|
|
||||||
|
|
||||||
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
|
|
||||||
|
|
||||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
# Option 1: rewrite www.example.com → example.com
|
|
||||||
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
RewriteCond %{HTTPS} !=on
|
|
||||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
|
||||||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
# Option 2: rewrite example.com → www.example.com
|
|
||||||
|
|
||||||
# Be aware that the following might not be a good idea if you use "real"
|
|
||||||
# subdomains for certain parts of your website.
|
|
||||||
|
|
||||||
# <IfModule mod_rewrite.c>
|
|
||||||
# RewriteCond %{HTTPS} !=on
|
|
||||||
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
|
|
||||||
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # SECURITY #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Content Security Policy (CSP) |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# You can mitigate the risk of cross-site scripting and other content-injection
|
|
||||||
# attacks by setting a Content Security Policy which whitelists trusted sources
|
|
||||||
# of content for your site.
|
|
||||||
|
|
||||||
# The example header below allows ONLY scripts that are loaded from the current
|
|
||||||
# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
|
|
||||||
# work as-is for your site!
|
|
||||||
|
|
||||||
# To get all the details you'll need to craft a reasonable policy for your site,
|
|
||||||
# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
|
|
||||||
# see the specification: http://w3.org/TR/CSP).
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
|
|
||||||
# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
|
||||||
# Header unset Content-Security-Policy
|
|
||||||
# </FilesMatch>
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | File access |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Block access to directories without a default document.
|
|
||||||
# Usually you should leave this uncommented because you shouldn't allow anyone
|
|
||||||
# to surf through every directory on your server (which may includes rather
|
|
||||||
# private places like the CMS's directories).
|
|
||||||
|
|
||||||
<IfModule mod_autoindex.c>
|
|
||||||
Options -Indexes
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
# Block access to hidden files and directories.
|
|
||||||
# This includes directories used by version control systems such as Git and SVN.
|
|
||||||
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
RewriteCond %{SCRIPT_FILENAME} -d [OR]
|
|
||||||
RewriteCond %{SCRIPT_FILENAME} -f
|
|
||||||
RewriteRule "(^|/)\." - [F]
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
# Block access to backup and source files.
|
|
||||||
# These files may be left by some text editors and can pose a great security
|
|
||||||
# danger when anyone has access to them.
|
|
||||||
|
|
||||||
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
|
|
||||||
Order allow,deny
|
|
||||||
Deny from all
|
|
||||||
Satisfy All
|
|
||||||
</FilesMatch>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Secure Sockets Layer (SSL) |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
|
|
||||||
# prevent `https://www.example.com` when your certificate only allows
|
|
||||||
# `https://secure.example.com`.
|
|
||||||
|
|
||||||
# <IfModule mod_rewrite.c>
|
|
||||||
# RewriteCond %{SERVER_PORT} !^443
|
|
||||||
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
# Force client-side SSL redirection.
|
|
||||||
|
|
||||||
# If a user types "example.com" in his browser, the above rule will redirect him
|
|
||||||
# to the secure version of the site. That still leaves a window of opportunity
|
|
||||||
# (the initial HTTP connection) for an attacker to downgrade or redirect the
|
|
||||||
# request. The following header ensures that browser will ONLY connect to your
|
|
||||||
# server via HTTPS, regardless of what the users type in the address bar.
|
|
||||||
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set Strict-Transport-Security max-age=16070400;
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Server software information |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Avoid displaying the exact Apache version number, the description of the
|
|
||||||
# generic OS-type and the information about Apache's compiled-in modules.
|
|
||||||
|
|
||||||
# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
|
|
||||||
|
|
||||||
# ServerTokens Prod
|
|
||||||
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# # WEB PERFORMANCE #
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Compression |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
<IfModule mod_deflate.c>
|
|
||||||
|
|
||||||
# Force compression for mangled headers.
|
|
||||||
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
|
|
||||||
<IfModule mod_setenvif.c>
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
|
||||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
|
||||||
</IfModule>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# Compress all output labeled with one of the following MIME-types
|
|
||||||
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
|
|
||||||
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
|
|
||||||
# as `AddOutputFilterByType` is still in the core directives).
|
|
||||||
<IfModule mod_filter.c>
|
|
||||||
AddOutputFilterByType DEFLATE application/atom+xml \
|
|
||||||
application/javascript \
|
|
||||||
application/json \
|
|
||||||
application/rss+xml \
|
|
||||||
application/vnd.ms-fontobject \
|
|
||||||
application/x-font-ttf \
|
|
||||||
application/x-web-app-manifest+json \
|
|
||||||
application/xhtml+xml \
|
|
||||||
application/xml \
|
|
||||||
font/opentype \
|
|
||||||
image/svg+xml \
|
|
||||||
image/x-icon \
|
|
||||||
text/css \
|
|
||||||
text/html \
|
|
||||||
text/plain \
|
|
||||||
text/x-component \
|
|
||||||
text/xml
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Content transformations |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Prevent some of the mobile network providers from modifying the content of
|
|
||||||
# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set Cache-Control "no-transform"
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | ETag removal |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Since we're sending far-future expires headers (see below), ETags can
|
|
||||||
# be removed: http://developer.yahoo.com/performance/rules.html#etags.
|
|
||||||
|
|
||||||
# `FileETag None` is not enough for every server.
|
|
||||||
<IfModule mod_headers.c>
|
|
||||||
Header unset ETag
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
FileETag None
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Expires headers (for better cache control) |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# The following expires headers are set pretty far in the future. If you don't
|
|
||||||
# control versioning with filename-based cache busting, consider lowering the
|
|
||||||
# cache time for resources like CSS and JS to something like 1 week.
|
|
||||||
|
|
||||||
<IfModule mod_expires.c>
|
|
||||||
|
|
||||||
ExpiresActive on
|
|
||||||
ExpiresDefault "access plus 1 month"
|
|
||||||
|
|
||||||
# CSS
|
|
||||||
ExpiresByType text/css "access plus 1 year"
|
|
||||||
|
|
||||||
# Data interchange
|
|
||||||
ExpiresByType application/json "access plus 0 seconds"
|
|
||||||
ExpiresByType application/xml "access plus 0 seconds"
|
|
||||||
ExpiresByType text/xml "access plus 0 seconds"
|
|
||||||
|
|
||||||
# Favicon (cannot be renamed!)
|
|
||||||
ExpiresByType image/x-icon "access plus 1 week"
|
|
||||||
|
|
||||||
# HTML components (HTCs)
|
|
||||||
ExpiresByType text/x-component "access plus 1 month"
|
|
||||||
|
|
||||||
# HTML
|
|
||||||
ExpiresByType text/html "access plus 0 seconds"
|
|
||||||
|
|
||||||
# JavaScript
|
|
||||||
ExpiresByType application/javascript "access plus 1 year"
|
|
||||||
|
|
||||||
# Manifest files
|
|
||||||
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
|
|
||||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
|
||||||
|
|
||||||
# Media
|
|
||||||
ExpiresByType audio/ogg "access plus 1 month"
|
|
||||||
ExpiresByType image/gif "access plus 1 month"
|
|
||||||
ExpiresByType image/jpeg "access plus 1 month"
|
|
||||||
ExpiresByType image/png "access plus 1 month"
|
|
||||||
ExpiresByType video/mp4 "access plus 1 month"
|
|
||||||
ExpiresByType video/ogg "access plus 1 month"
|
|
||||||
ExpiresByType video/webm "access plus 1 month"
|
|
||||||
|
|
||||||
# Web feeds
|
|
||||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
|
||||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
|
||||||
|
|
||||||
# Web fonts
|
|
||||||
ExpiresByType application/font-woff "access plus 1 month"
|
|
||||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
|
||||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
|
||||||
ExpiresByType font/opentype "access plus 1 month"
|
|
||||||
ExpiresByType image/svg+xml "access plus 1 month"
|
|
||||||
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Filename-based cache busting |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# If you're not using a build process to manage your filename version revving,
|
|
||||||
# you might want to consider enabling the following directives to route all
|
|
||||||
# requests such as `/css/style.12345.css` to `/css/style.css`.
|
|
||||||
|
|
||||||
# To understand why this is important and a better idea than `*.css?v231`, read:
|
|
||||||
# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
|
|
||||||
|
|
||||||
# <IfModule mod_rewrite.c>
|
|
||||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | File concatenation |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Allow concatenation from within specific CSS and JS files, e.g.:
|
|
||||||
# Inside of `script.combined.js` you could have
|
|
||||||
# <!--#include file="libs/jquery.js" -->
|
|
||||||
# <!--#include file="plugins/jquery.idletimer.js" -->
|
|
||||||
# and they would be included into this single file.
|
|
||||||
|
|
||||||
# <IfModule mod_include.c>
|
|
||||||
# <FilesMatch "\.combined\.js$">
|
|
||||||
# Options +Includes
|
|
||||||
# AddOutputFilterByType INCLUDES application/javascript application/json
|
|
||||||
# SetOutputFilter INCLUDES
|
|
||||||
# </FilesMatch>
|
|
||||||
# <FilesMatch "\.combined\.css$">
|
|
||||||
# Options +Includes
|
|
||||||
# AddOutputFilterByType INCLUDES text/css
|
|
||||||
# SetOutputFilter INCLUDES
|
|
||||||
# </FilesMatch>
|
|
||||||
# </IfModule>
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# | Persistent connections |
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Allow multiple requests to be sent over the same TCP connection:
|
|
||||||
# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
|
|
||||||
|
|
||||||
# Enable if you serve a lot of static content but, be aware of the
|
|
||||||
# possible disadvantages!
|
|
||||||
|
|
||||||
# <IfModule mod_headers.c>
|
|
||||||
# Header set Connection Keep-Alive
|
|
||||||
# </IfModule>
|
|
@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"node": true,
|
|
||||||
"browser": true,
|
|
||||||
"esnext": true,
|
|
||||||
"bitwise": true,
|
|
||||||
"camelcase": true,
|
|
||||||
"curly": true,
|
|
||||||
"eqeqeq": true,
|
|
||||||
"immed": true,
|
|
||||||
"indent": 2,
|
|
||||||
"latedef": true,
|
|
||||||
"newcap": true,
|
|
||||||
"noarg": true,
|
|
||||||
"quotmark": "single",
|
|
||||||
"regexp": true,
|
|
||||||
"undef": true,
|
|
||||||
"unused": true,
|
|
||||||
"strict": true,
|
|
||||||
"trailing": true,
|
|
||||||
"smarttabs": true,
|
|
||||||
"globals": {
|
|
||||||
"jQuery": true,
|
|
||||||
"angular": true,
|
|
||||||
"console": true,
|
|
||||||
"$": true,
|
|
||||||
"_": true,
|
|
||||||
"moment": true,
|
|
||||||
"describe": true,
|
|
||||||
"beforeEach": true,
|
|
||||||
"module": true,
|
|
||||||
"inject": true,
|
|
||||||
"it": true,
|
|
||||||
"expect": true,
|
|
||||||
"browser": true,
|
|
||||||
"element": true,
|
|
||||||
"by": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp', [
|
|
||||||
'ngCookies',
|
|
||||||
'ngResource',
|
|
||||||
'ngSanitize',
|
|
||||||
'ngRoute',
|
|
||||||
'ui.bootstrap',
|
|
||||||
'btford.markdown'
|
|
||||||
])
|
|
||||||
.config(function ($routeProvider, $locationProvider, $httpProvider) {
|
|
||||||
$routeProvider
|
|
||||||
.otherwise ({
|
|
||||||
redirectTo: '/'
|
|
||||||
});
|
|
||||||
|
|
||||||
$locationProvider.html5Mode(true);
|
|
||||||
})
|
|
@ -1,55 +0,0 @@
|
|||||||
$icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";
|
|
||||||
$fa-font-path: "/bower_components/font-awesome/fonts";
|
|
||||||
|
|
||||||
@import 'bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';
|
|
||||||
|
|
||||||
@import 'font-awesome/scss/font-awesome';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* App-wide Styles
|
|
||||||
*/
|
|
||||||
|
|
||||||
.hero-unit {
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-unit h1 a {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browsehappy {
|
|
||||||
margin: 0.2em 0;
|
|
||||||
background: #ccc;
|
|
||||||
color: #000;
|
|
||||||
padding: 0.2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indent {
|
|
||||||
margin-left: 2em;
|
|
||||||
}
|
|
||||||
table.revisions th, table.revisions td{
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
table.revisions td.content {
|
|
||||||
max-height: 30em;
|
|
||||||
}
|
|
||||||
table.revisions .state {
|
|
||||||
width: 7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.controls {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.form-inline.controls > * {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
form.form-inline.controls > .form-group > label, {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component styles are injected through grunt
|
|
||||||
// injector
|
|
||||||
@import 'compare/compare.scss';
|
|
||||||
@import 'modal/modal.scss';
|
|
||||||
// endinjector
|
|
@ -1,18 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.config(function ($routeProvider) {
|
|
||||||
$routeProvider
|
|
||||||
.when('/', {
|
|
||||||
templateUrl: 'app/compare/create/create.html',
|
|
||||||
controller: 'CompareCreateCtrl'
|
|
||||||
})
|
|
||||||
.when('/:id', {
|
|
||||||
templateUrl: 'app/compare/show/show.html',
|
|
||||||
controller: 'CompareShowCtrl'
|
|
||||||
})
|
|
||||||
.when('/:id/:format', {
|
|
||||||
templateUrl: 'app/compare/show/show.html',
|
|
||||||
controller: 'CompareShowCtrl'
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,57 +0,0 @@
|
|||||||
.wdiff ins {
|
|
||||||
background-color: #dbffdb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wdiff del {
|
|
||||||
background-color: #f8cbcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-pre {
|
|
||||||
font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 5px;
|
|
||||||
width: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-well {
|
|
||||||
min-height: 20px;
|
|
||||||
padding: 19px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
//background-color: #f5f5f5;
|
|
||||||
border: 1px solid #e3e3e3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
#docA, #docB {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#banner {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-top: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#banner h1 {
|
|
||||||
font-size: 60px;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-unit {
|
|
||||||
position: relative;
|
|
||||||
padding: 30px 15px;
|
|
||||||
color: #F5F5F5;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
background: #4393B9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 30px 0;
|
|
||||||
margin-top: 70px;
|
|
||||||
border-top: 1px solid #E5E5E5;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.controller('CompareCreateCtrl', function ($scope, $http, $location) {
|
|
||||||
$scope.docA = "";
|
|
||||||
$scope.docB = "";
|
|
||||||
$scope.wdiff = "";
|
|
||||||
$scope.wdiffMarkdown = "";
|
|
||||||
$scope.isMarkdownFormat = true;
|
|
||||||
|
|
||||||
$scope.compare = function() {
|
|
||||||
$http.post('/api/compare',
|
|
||||||
{ a: _.escape($scope.docA), b: _.escape($scope.docB) },
|
|
||||||
{headers:{"Content-Type":"application/json"}})
|
|
||||||
.success(function (comparison) {
|
|
||||||
$location.path('/'+comparison._id);
|
|
||||||
$location.hash($scope.isMarkdownFormat?'markdown':'plaintext');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.toggleMarkdownFormat = function() {
|
|
||||||
if ($scope.isMarkdownFormat) {
|
|
||||||
$scope.isMarkdownFormat = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$scope.isMarkdownFormat = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
@ -1,30 +0,0 @@
|
|||||||
// nav(ng-include='"components/navbar/navbar.html"')
|
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', onload='title = "dubdiff"; ')
|
|
||||||
|
|
||||||
.container
|
|
||||||
|
|
||||||
form.row
|
|
||||||
|
|
||||||
.col-md-2.col-sm-12.form-group
|
|
||||||
.controls.well.col-lg-12
|
|
||||||
a.btn.btn-block.btn-primary(type='button', ng-click='compare()') compare
|
|
||||||
|
|
||||||
.controls.well.btn-group.col-lg-12
|
|
||||||
a.btn.btn-block.btn-primary(ng-class='{"active": isMarkdownFormat}', type='submit', ng-click='toggleMarkdownFormat()')
|
|
||||||
span.glyphicon(ng-class='{"glyphicon-ok": isMarkdownFormat}')
|
|
||||||
| As Markdown
|
|
||||||
|
|
||||||
|
|
||||||
.col-lg-5.col-sm-12.form-group
|
|
||||||
label(for='docA')
|
|
||||||
| Original
|
|
||||||
textarea.form-control(id='docA', ng-model='docA')
|
|
||||||
|
|
||||||
.col-lg-5.col-sm-12.form-group
|
|
||||||
label(for='docB')
|
|
||||||
| Final
|
|
||||||
textarea.form-control(id='docB', ng-model='docB')
|
|
||||||
|
|
||||||
|
|
||||||
footer(ng-include='"components/elements/footer.html"')
|
|
@ -1,57 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var MARKDOWN = "markdown";
|
|
||||||
var PLAINTEXT = "plaintext";
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.controller('CompareShowCtrl', function ($scope, $routeParams, $http, $location) {
|
|
||||||
$scope.wdiff = '';
|
|
||||||
$scope.before = '';
|
|
||||||
$scope.after = '';
|
|
||||||
$scope.isShowWdiff = true;
|
|
||||||
$scope.isMarkdownFormat = true;
|
|
||||||
|
|
||||||
|
|
||||||
var paramFormat = $location.hash();
|
|
||||||
if (paramFormat == "plain" || paramFormat == "plaintext")
|
|
||||||
$scope.isMarkdownFormat = false;
|
|
||||||
|
|
||||||
// if routeParams specifies a user, restrict the query to that user
|
|
||||||
var path = '/api/compare/wdiff/' + $routeParams.id;
|
|
||||||
$http.get(path).success(function(comparison) {
|
|
||||||
$scope.wdiff = comparison.wdiff;
|
|
||||||
|
|
||||||
$scope.before = comparison.a;
|
|
||||||
$scope.after = comparison.b;
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.showBefore = function() {
|
|
||||||
$scope.isShowBefore = true;
|
|
||||||
$scope.isShowAfter = false;
|
|
||||||
$scope.isShowWdiff = false;
|
|
||||||
}
|
|
||||||
$scope.showAfter = function() {
|
|
||||||
$scope.isShowBefore = false;
|
|
||||||
$scope.isShowAfter = true;
|
|
||||||
$scope.isShowWdiff = false;
|
|
||||||
}
|
|
||||||
$scope.showWdiff = function() {
|
|
||||||
$scope.isShowBefore = false;
|
|
||||||
$scope.isShowAfter = false;
|
|
||||||
$scope.isShowWdiff = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.toggleMarkdownFormat = function() {
|
|
||||||
if ($scope.isMarkdownFormat) {
|
|
||||||
$scope.isMarkdownFormat = false;
|
|
||||||
$location.hash('plaintext');
|
|
||||||
$location.replace();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$scope.isMarkdownFormat = true;
|
|
||||||
$location.hash('markdown');
|
|
||||||
$location.replace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
@ -1,46 +0,0 @@
|
|||||||
// nav(ng-include='"components/navbar/navbar.html"')
|
|
||||||
|
|
||||||
nav(ng-include='"components/elements/header.html"', onload='title = "dubdiff"; subtitle ="results"')
|
|
||||||
|
|
||||||
.container
|
|
||||||
.row
|
|
||||||
.col-md-2.col-sm-12
|
|
||||||
.controls.well.btn-group.col-lg-12
|
|
||||||
a.btn.btn-block.btn-primary(ng-class='{"active": isShowBefore}', type='submit', ng-click='showBefore()')
|
|
||||||
| Original
|
|
||||||
a.btn.btn-block.btn-primary(ng-class='{"active": isShowAfter}', type='submit', ng-click='showAfter()')
|
|
||||||
| Final
|
|
||||||
a.btn.btn-block.btn-primary(ng-class='{"active": isShowWdiff}', type='submit', ng-click='showWdiff()')
|
|
||||||
| Difference
|
|
||||||
|
|
||||||
.controls.well.btn-group.col-lg-12
|
|
||||||
a.btn.btn-block.btn-primary(ng-class='{"active": isMarkdownFormat}', type='submit', ng-click='toggleMarkdownFormat()')
|
|
||||||
span.glyphicon(ng-class='{"glyphicon-ok": isMarkdownFormat}')
|
|
||||||
| As Markdown
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
div(ng-if='isMarkdownFormat')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowBefore')
|
|
||||||
div.before(btf-markdown='before')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowWdiff')
|
|
||||||
div.wdiff(btf-markdown='wdiff')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowAfter')
|
|
||||||
div.after(btf-markdown='after')
|
|
||||||
|
|
||||||
|
|
||||||
div(ng-if='!isMarkdownFormat')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowBefore')
|
|
||||||
.content-pre.before(ng-bind-html='before')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowWdiff')
|
|
||||||
.content-pre.wdiff(ng-bind-html='wdiff')
|
|
||||||
|
|
||||||
.col-md-10.col-sm-12.content-well(ng-show='isShowAfter')
|
|
||||||
.content-pre.after(ng-bind-html='after')
|
|
||||||
|
|
||||||
footer(ng-include='"components/elements/footer.html"')
|
|
Before Width: | Height: | Size: 12 KiB |
@ -1,8 +0,0 @@
|
|||||||
div.footer
|
|
||||||
.container
|
|
||||||
p
|
|
||||||
a(href='https://adamarthurryan.com') Adam Brown
|
|
||||||
= ' | '
|
|
||||||
= 'This website is '
|
|
||||||
a(href='https://github.com/adamarthurryan/dubdiff') open source
|
|
||||||
| .
|
|
@ -1,6 +0,0 @@
|
|||||||
header#banner.hero-unit
|
|
||||||
.container
|
|
||||||
h1
|
|
||||||
a(href='/')
|
|
||||||
| {{title}}
|
|
||||||
h3 {{subtitle}}
|
|
@ -1,8 +0,0 @@
|
|||||||
.modal-header
|
|
||||||
button.close(ng-if='modal.dismissable', type='button', ng-click='$dismiss()') ×
|
|
||||||
h4.modal-title(ng-if='modal.title', ng-bind='modal.title')
|
|
||||||
.modal-body
|
|
||||||
p(ng-if='modal.text', ng-bind='modal.text')
|
|
||||||
div(ng-if='modal.html', ng-bind-html='modal.html')
|
|
||||||
.modal-footer
|
|
||||||
button.btn(ng-repeat='button in modal.buttons', ng-class='button.classes', ng-click='button.click($event)', ng-bind='button.text')
|
|
@ -1,25 +0,0 @@
|
|||||||
.modal-primary,
|
|
||||||
.modal-info,
|
|
||||||
.modal-success,
|
|
||||||
.modal-warning,
|
|
||||||
.modal-danger {
|
|
||||||
.modal-header {
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 5px 5px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.modal-primary .modal-header {
|
|
||||||
background: $brand-primary;
|
|
||||||
}
|
|
||||||
.modal-info .modal-header {
|
|
||||||
background: $brand-info;
|
|
||||||
}
|
|
||||||
.modal-success .modal-header {
|
|
||||||
background: $brand-success;
|
|
||||||
}
|
|
||||||
.modal-warning .modal-header {
|
|
||||||
background: $brand-warning;
|
|
||||||
}
|
|
||||||
.modal-danger .modal-header {
|
|
||||||
background: $brand-danger;
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.factory('Modal', function ($rootScope, $modal) {
|
|
||||||
/**
|
|
||||||
* Opens a modal
|
|
||||||
* @param {Object} scope - an object to be merged with modal's scope
|
|
||||||
* @param {String} modalClass - (optional) class(es) to be applied to the modal
|
|
||||||
* @return {Object} - the instance $modal.open() returns
|
|
||||||
*/
|
|
||||||
function openModal(scope, modalClass) {
|
|
||||||
var modalScope = $rootScope.$new();
|
|
||||||
scope = scope || {};
|
|
||||||
modalClass = modalClass || 'modal-default';
|
|
||||||
|
|
||||||
angular.extend(modalScope, scope);
|
|
||||||
|
|
||||||
return $modal.open({
|
|
||||||
templateUrl: 'components/modal/modal.html',
|
|
||||||
windowClass: modalClass,
|
|
||||||
scope: modalScope
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API here
|
|
||||||
return {
|
|
||||||
|
|
||||||
/* Confirmation modals */
|
|
||||||
confirm: {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
|
|
||||||
* @param {Function} del - callback, ran when delete is confirmed
|
|
||||||
* @return {Function} - the function to open the modal (ex. myModalFn)
|
|
||||||
*/
|
|
||||||
delete: function(del) {
|
|
||||||
del = del || angular.noop;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a delete confirmation modal
|
|
||||||
* @param {String} name - name or info to show on modal
|
|
||||||
* @param {All} - any additional args are passed staight to del callback
|
|
||||||
*/
|
|
||||||
return function() {
|
|
||||||
var args = Array.prototype.slice.call(arguments),
|
|
||||||
name = args.shift(),
|
|
||||||
deleteModal;
|
|
||||||
|
|
||||||
deleteModal = openModal({
|
|
||||||
modal: {
|
|
||||||
dismissable: true,
|
|
||||||
title: 'Confirm Delete',
|
|
||||||
html: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>',
|
|
||||||
buttons: [{
|
|
||||||
classes: 'btn-danger',
|
|
||||||
text: 'Delete',
|
|
||||||
click: function(e) {
|
|
||||||
deleteModal.close(e);
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
classes: 'btn-default',
|
|
||||||
text: 'Cancel',
|
|
||||||
click: function(e) {
|
|
||||||
deleteModal.dismiss(e);
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}, 'modal-danger');
|
|
||||||
|
|
||||||
deleteModal.result.then(function(event) {
|
|
||||||
del.apply(event, args);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,17 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes server error when user updates input
|
|
||||||
*/
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.directive('mongooseError', function () {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
require: 'ngModel',
|
|
||||||
link: function(scope, element, attrs, ngModel) {
|
|
||||||
element.on('keydown', function() {
|
|
||||||
return ngModel.$setValidity('mongoose', true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,16 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
angular.module('markdownFormatWdiffApp')
|
|
||||||
.controller('NavbarCtrl', function ($scope, $location) {
|
|
||||||
$scope.menu = [{
|
|
||||||
'title': 'Home',
|
|
||||||
'link': '/'
|
|
||||||
}];
|
|
||||||
|
|
||||||
$scope.isCollapsed = true;
|
|
||||||
|
|
||||||
|
|
||||||
$scope.isActive = function(route) {
|
|
||||||
return route === $location.path();
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,34 +0,0 @@
|
|||||||
div.navbar.navbar-default.navbar-static-top(ng-controller='NavbarCtrl')
|
|
||||||
div.container
|
|
||||||
div.navbar-header
|
|
||||||
button.navbar-toggle(type='button', ng-click='isCollapsed = !isCollapsed')
|
|
||||||
span.sr-only Toggle navigation
|
|
||||||
span.icon-bar
|
|
||||||
span.icon-bar
|
|
||||||
span.icon-bar
|
|
||||||
a.navbar-brand(href='/') markdown-format-wdiff
|
|
||||||
|
|
||||||
div#navbar-main.navbar-collapse.collapse(collapse='isCollapsed')
|
|
||||||
ul.nav.navbar-nav
|
|
||||||
li(ng-repeat='item in menu', ng-class='{active: isActive(item.link)}')
|
|
||||||
a(ng-href='{{item.link}}') {{item.title}}
|
|
||||||
|
|
||||||
li(ng-show='isAdmin()', ng-class='{active: isActive("/admin")}')
|
|
||||||
a(href='/admin') Admin
|
|
||||||
|
|
||||||
ul.nav.navbar-nav.navbar-right
|
|
||||||
li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/signup")}')
|
|
||||||
a(href='/signup') Sign up
|
|
||||||
|
|
||||||
li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/login")}')
|
|
||||||
a(href='/login') Login
|
|
||||||
|
|
||||||
li(ng-show='isLoggedIn()')
|
|
||||||
p.navbar-text Hello {{ getCurrentUser().name }}
|
|
||||||
|
|
||||||
li(ng-show='isLoggedIn()', ng-class='{active: isActive("/settings")}')
|
|
||||||
a(href='/settings')
|
|
||||||
span.glyphicon.glyphicon-cog
|
|
||||||
|
|
||||||
li(ng-show='isLoggedIn()', ng-class='{active: isActive("/logout")}')
|
|
||||||
a(href='', ng-click='logout()') Logout
|
|
Before Width: | Height: | Size: 6.6 KiB |
@ -1,74 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
|
||||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
|
||||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
|
||||||
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<base href="/">
|
|
||||||
<title></title>
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
|
|
||||||
<!-- build:css(client) app/vendor.css -->
|
|
||||||
<!-- bower:css -->
|
|
||||||
<!-- endbower -->
|
|
||||||
<!-- endbuild -->
|
|
||||||
<!-- build:css({.tmp,client}) app/app.css -->
|
|
||||||
<link rel="stylesheet" href="app/app.css">
|
|
||||||
<!-- injector:css -->
|
|
||||||
<!-- endinjector -->
|
|
||||||
<!-- endbuild -->
|
|
||||||
</head>
|
|
||||||
<body ng-app="markdownFormatWdiffApp">
|
|
||||||
<!--[if lt IE 7]>
|
|
||||||
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<!-- Add your site or application content here -->
|
|
||||||
<div ng-view=""></div>
|
|
||||||
|
|
||||||
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-XXXXX-X');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="bower_components/es5-shim/es5-shim.js"></script>
|
|
||||||
<script src="bower_components/json3/lib/json3.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
<!-- build:js({client,node_modules}) app/vendor.js -->
|
|
||||||
<!-- bower:js -->
|
|
||||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
|
||||||
<script src="bower_components/angular/angular.js"></script>
|
|
||||||
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
|
||||||
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
|
|
||||||
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
|
|
||||||
<script src="bower_components/angular-route/angular-route.js"></script>
|
|
||||||
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
|
|
||||||
<script src="bower_components/lodash/dist/lodash.compat.js"></script>
|
|
||||||
<script src="bower_components/showdown/src/showdown.js"></script>
|
|
||||||
<script src="bower_components/angular-markdown-directive/markdown.js"></script>
|
|
||||||
<!-- endbower -->
|
|
||||||
<!-- endbuild -->
|
|
||||||
|
|
||||||
<!-- build:js({.tmp,client}) app/app.js -->
|
|
||||||
<script src="app/app.js"></script>
|
|
||||||
<!-- injector:js -->
|
|
||||||
<script src="app/compare/compare.js"></script>
|
|
||||||
<script src="app/compare/create/create.controller.js"></script>
|
|
||||||
<script src="app/compare/show/show.controller.js"></script>
|
|
||||||
<script src="components/modal/modal.service.js"></script>
|
|
||||||
<script src="components/mongoose-error/mongoose-error.directive.js"></script>
|
|
||||||
<script src="components/navbar/navbar.controller.js"></script>
|
|
||||||
<!-- endinjector -->
|
|
||||||
<!-- endbuild -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||||||
# robotstxt.org
|
|
||||||
|
|
||||||
User-agent: *
|
|
BIN
dist/favicon-16x16.png
vendored
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
dist/favicon-32x32.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/favicon-96x96.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
dist/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/img/03-small.jpg
vendored
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
dist/img/03-tiny.jpg
vendored
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
dist/img/03-tinyer.jpg
vendored
Normal file
After Width: | Height: | Size: 33 KiB |
11
dist/main.css
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#masthead .header {
|
||||||
|
font-size: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ins {
|
||||||
|
background-color: #dbffdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
background-color: #ffdddd;
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file uses the Page Object pattern to define the main page for tests
|
|
||||||
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var MainPage = function() {
|
|
||||||
this.heroEl = element(by.css('.hero-unit'));
|
|
||||||
this.h1El = this.heroEl.element(by.css('h1'));
|
|
||||||
this.imgEl = this.heroEl.element(by.css('img'));
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = new MainPage();
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
describe('Main View', function() {
|
|
||||||
var page;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
browser.get('/');
|
|
||||||
page = require('./main.po');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include jumbotron with correct data', function() {
|
|
||||||
expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
|
|
||||||
expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/);
|
|
||||||
expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,79 +0,0 @@
|
|||||||
// Karma configuration
|
|
||||||
// http://karma-runner.github.io/0.10/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function(config) {
|
|
||||||
config.set({
|
|
||||||
// base path, that will be used to resolve files and exclude
|
|
||||||
basePath: '',
|
|
||||||
|
|
||||||
// testing framework to use (jasmine/mocha/qunit/...)
|
|
||||||
frameworks: ['jasmine'],
|
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
|
||||||
files: [
|
|
||||||
'client/bower_components/jquery/dist/jquery.js',
|
|
||||||
'client/bower_components/angular/angular.js',
|
|
||||||
'client/bower_components/angular-mocks/angular-mocks.js',
|
|
||||||
'client/bower_components/angular-resource/angular-resource.js',
|
|
||||||
'client/bower_components/angular-cookies/angular-cookies.js',
|
|
||||||
'client/bower_components/angular-sanitize/angular-sanitize.js',
|
|
||||||
'client/bower_components/angular-route/angular-route.js',
|
|
||||||
'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
|
|
||||||
'client/bower_components/lodash/dist/lodash.compat.js',
|
|
||||||
'client/app/app.js',
|
|
||||||
'client/app/app.coffee',
|
|
||||||
'client/app/**/*.js',
|
|
||||||
'client/app/**/*.coffee',
|
|
||||||
'client/components/**/*.js',
|
|
||||||
'client/components/**/*.coffee',
|
|
||||||
'client/app/**/*.jade',
|
|
||||||
'client/components/**/*.jade',
|
|
||||||
'client/app/**/*.html',
|
|
||||||
'client/components/**/*.html'
|
|
||||||
],
|
|
||||||
|
|
||||||
preprocessors: {
|
|
||||||
'**/*.jade': 'ng-jade2js',
|
|
||||||
'**/*.html': 'html2js',
|
|
||||||
'**/*.coffee': 'coffee',
|
|
||||||
},
|
|
||||||
|
|
||||||
ngHtml2JsPreprocessor: {
|
|
||||||
stripPrefix: 'client/'
|
|
||||||
},
|
|
||||||
|
|
||||||
ngJade2JsPreprocessor: {
|
|
||||||
stripPrefix: 'client/'
|
|
||||||
},
|
|
||||||
|
|
||||||
// list of files / patterns to exclude
|
|
||||||
exclude: [],
|
|
||||||
|
|
||||||
// web server port
|
|
||||||
port: 8080,
|
|
||||||
|
|
||||||
// level of logging
|
|
||||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
|
|
||||||
|
|
||||||
// enable / disable watching file and executing tests whenever any file changes
|
|
||||||
autoWatch: false,
|
|
||||||
|
|
||||||
|
|
||||||
// Start these browsers, currently available:
|
|
||||||
// - Chrome
|
|
||||||
// - ChromeCanary
|
|
||||||
// - Firefox
|
|
||||||
// - Opera
|
|
||||||
// - Safari (only Mac)
|
|
||||||
// - PhantomJS
|
|
||||||
// - IE (only Windows)
|
|
||||||
browsers: ['PhantomJS'],
|
|
||||||
|
|
||||||
|
|
||||||
// Continuous Integration mode
|
|
||||||
// if true, it capture browsers, run tests and exit
|
|
||||||
singleRun: false
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"folders":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"follow_symlinks": true,
|
|
||||||
"path": "client"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"follow_symlinks": true,
|
|
||||||
"path": "e2e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"follow_symlinks": true,
|
|
||||||
"path": "server"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,862 +0,0 @@
|
|||||||
{
|
|
||||||
"auto_complete":
|
|
||||||
{
|
|
||||||
"selected_items":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"wdif",
|
|
||||||
"wdiffMarkdown"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"selected",
|
|
||||||
"selectedProviders"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"btn",
|
|
||||||
"btn-primary"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"definition",
|
|
||||||
"definition"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"tran",
|
|
||||||
"translateLanguage"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"exp",
|
|
||||||
"expressionSearch"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"define",
|
|
||||||
"defineMeaning"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"colo",
|
|
||||||
"colorFactor"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"dup",
|
|
||||||
"duplicate_user"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"import_exter",
|
|
||||||
"import_external_images_nonce"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"page-break",
|
|
||||||
"page-break-inside"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"array_key",
|
|
||||||
"array_key_exists"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"guess",
|
|
||||||
"guess"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"category",
|
|
||||||
"category_list"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"posts_",
|
|
||||||
"posts_to_fix"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"in",
|
|
||||||
"inline-block"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"text-",
|
|
||||||
"text-decoration"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"get_ca",
|
|
||||||
"get_cat_id"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"flatten",
|
|
||||||
"flatten"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"con",
|
|
||||||
"contains"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"add",
|
|
||||||
"addClass"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"h",
|
|
||||||
"hidden"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"page",
|
|
||||||
"page-break-before"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"buffers":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"file": "client/index.html",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 3458,
|
|
||||||
"line_ending": "Windows"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "bower.json",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 617,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "client/app/app.js",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 349,
|
|
||||||
"line_ending": "Windows"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "server/components/wdiff/index.js",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 8966,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "/C/Users/Adam/Desktop/code/courses/git-status-all",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 113,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "/C/Users/Adam/Downloads/JavaScript Refactoring - Course Notes.md",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 2388,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "/C/Users/Adam/Dropbox (Envato)/Course Content/Code/ready/Create a New JavaScript Framework - How We Built the Daily Mail CMS/notes - QA.md",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 3928,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "/C/Users/Adam/Dropbox (Envato)/Course Content/Code/ready/Create a New JavaScript Framework - Challenges of Rolling Your Own/notes - QA.md",
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 4420,
|
|
||||||
"line_ending": "Unix"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"build_system": "",
|
|
||||||
"build_system_choices":
|
|
||||||
[
|
|
||||||
],
|
|
||||||
"build_varint": "",
|
|
||||||
"command_palette":
|
|
||||||
{
|
|
||||||
"height": 157.0,
|
|
||||||
"last_filter": "",
|
|
||||||
"selected_items":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"Package Control: ins",
|
|
||||||
"Package Control: Install Package"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"install",
|
|
||||||
"Package Control: Install Package"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"isntall",
|
|
||||||
"Package Control: Install Package"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Package Control: in",
|
|
||||||
"Package Control: Install Package"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"width": 571.0
|
|
||||||
},
|
|
||||||
"console":
|
|
||||||
{
|
|
||||||
"height": 256.0,
|
|
||||||
"history":
|
|
||||||
[
|
|
||||||
"import urllib.request,os; pr='Preferences.sublime-settings'; ip='ignored_packages'; n='Package Control'; s=sublime.load_settings(pr); ig=s.get(ip); ig.append(n); s.set(ip,ig); sublime.save_settings('Preferences.sublime-settings'); pf=n+'.sublime-package'; urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler())); by=urllib.request.urlopen('https://packagecontrol.io/'+pf.replace(' ','%20')).read(); open(os.path.join(sublime.installed_packages_path(),pf),'wb').write(by); ig.remove(n); s.set(ip,ig); sublime.save_settings(pr); print('Package Control: 3.0.0 upgrade successful!')",
|
|
||||||
"import urllib.request,os,hashlib; h = '7183a2d3e96f11eeadd761d777e62404' + 'e330c659d4bb41d3bdf022e94cab3cd0'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)",
|
|
||||||
"Package Control: Install Package",
|
|
||||||
"import urllib.request,os,hashlib; h = '7183a2d3e96f11eeadd761d777e62404' + 'e330c659d4bb41d3bdf022e94cab3cd0'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)",
|
|
||||||
"alert(\"a\")",
|
|
||||||
"help"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"distraction_free":
|
|
||||||
{
|
|
||||||
"menu_visible": true,
|
|
||||||
"show_minimap": false,
|
|
||||||
"show_open_files": true,
|
|
||||||
"show_tabs": false,
|
|
||||||
"side_bar_visible": false,
|
|
||||||
"status_bar_visible": false
|
|
||||||
},
|
|
||||||
"expanded_folders":
|
|
||||||
[
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/server",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/server/components",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/server/components/wdiff"
|
|
||||||
],
|
|
||||||
"file_history":
|
|
||||||
[
|
|
||||||
"/C/Users/Adam/Dropbox (Envato)/Course Content/Code/incoming/Create a New JavaScript Framework - Full Stack Reactivity with Milo/notes - edit.md",
|
|
||||||
"/C/Users/Adam/Desktop/code/courses/create-a-new-javascript-framework-full-stack-reactivity-with-milo/README.md",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app/main/main.js",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/bin/markdown-format-wdiff",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app/wdiff/wdiff.scss",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app/wdiff/wdiff.jade",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app/wdiff/wdiff.js",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/server/api/wdiff/wdiff.controller.js",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/server/api/wdiff/index.js",
|
|
||||||
"/C/Users/Adam/Desktop/code/projects/dubdiff/client/app/wdiff/wdiff.controller.js",
|
|
||||||
"/Z/lookup-lists/Gruntfile.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/list/list.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/list/list.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/item/item.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/lookup/index.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/components/lookup/index.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/components/lookup/omegawiki.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/components/lookup/openlibrary.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/components/lookup/dbpedia.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/list.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/item/item.scss",
|
|
||||||
"/Z/docker/data/lookup-lists/server/components/lookup/panlex.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/item/item.controller.js",
|
|
||||||
"/C/Users/adam/AppData/Roaming/Sublime Text 3/Packages/User/Preferences.sublime-settings",
|
|
||||||
"/C/Users/adam/AppData/Roaming/Sublime Text 3/Packages/Default/Preferences.sublime-settings",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/index/index.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/index.html",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/list/list.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/lookup/controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/view/list.scss",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/edit/item.scss",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/express.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/edit/item.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/edit/item.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/environment/production.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/environment/test.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/environment/development.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/view/list.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/list/index.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/view/list.controller.js",
|
|
||||||
"/Z/docker/docker-mean-dev/startup",
|
|
||||||
"/C/Windows/System32/drivers/etc/hosts",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/index/index.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/edit/edit.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/main/main.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/components/navbar/navbar.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/app.scss",
|
|
||||||
"/Z/docker/data/lookup-lists/bower.json",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lookup/lookup.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/edit/edit.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/list/index/username.filter.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/list/list.model.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/api/list/list.socket.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/components/navbar/navbar.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lists/lists.controller.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lists/lists.jade",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lists/lists.scss",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lookup/lookup.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/local.env.js",
|
|
||||||
"/Z/docker/data/lookup-lists/server/config/seed.js",
|
|
||||||
"/Z/docker/data/lookup-lists/client/app/lists/lists.js",
|
|
||||||
"/Z/docker/data/lookup-lists/Gruntfile.js",
|
|
||||||
"/Z/docker/docker-mean-dev/Dockerfile",
|
|
||||||
"/Z/docker/docker-mean-dev/bootstrap.sh",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/api/lookup/index.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/index.html",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/app/lookup/lookup.controller.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/app/lookup/lookup.scss",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/app/lookup/lookup.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/package.json",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/routes.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/api/lookup/controller.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/app/lookup/lookup.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/bower_components/angular-sanitize/angular-sanitize.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/Gruntfile.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/api/list/list.controller.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/components/lookup/omegawiki.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/components/lookup/index.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/bower_components/jquery/src/css/addGetHookIf.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/client/bower_components/jquery/src/wrap.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/api/lookup/omegawiki/index.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-lists/server/api/lookup/provider.js",
|
|
||||||
"/C/Users/adam/Google Drive/projects/contracts/2014 lieutenants pump/product/wireless network manifest.txt",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/views/sparql-query.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/views/index.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/config/seed.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/controllers/home.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/views/omegawiki.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/controllers/sparql.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/controllers/omegawiki.js",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_BabelNet-API-3.0.zip/BabelNet-API-3.0/licenses/README",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/app/views/sparql-results.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/config/express.js",
|
|
||||||
"/C/Users/adam/Downloads/karl-foaf.xrdf",
|
|
||||||
"/C/Users/adam/Downloads/foaf.rdf",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/SPARQL examples.md",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/package.json",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-sparql/README.md",
|
|
||||||
"/C/Users/adam/Downloads/wibi-ver1.0/README.txt",
|
|
||||||
"/C/cygwin/home/adam/.profile",
|
|
||||||
"/C/cygwin/home/adam/.bash_profile",
|
|
||||||
"/C/cygwin/home/adam/.bashrc",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/cldr/wn-cldr-eng.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/cldr/wn-cldr-afr.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/wikt/wn-wikt-kur.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/wikt/wn-wikt-ibl.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/wikt/wn-wikt-hrx.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/wikt/wn-wikt-dng.tab",
|
|
||||||
"/C/Users/adam/AppData/Local/Temp/Temp1_wn-wikt.zip/data/README",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/language-learning-panlex/server/api/dictionary/language.model.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/language-learning-panlex/server/api/dictionary/index.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/language-learning-panlex/server/api/dictionary/dictionary.controller.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/language-learning-panlex/.gitignore",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/README.md",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-notes/.gitignore",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/api/note/note.model.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/routes.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/api/user/user.model.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/api/note/note.controller.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/config/seed.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/api/note/index.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/Gruntfile.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/package.json",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/config/local.env.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-hello/server/views/404.jade",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/learning-mean-holmes/Gruntfile.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/scripttest/Gruntfile.js",
|
|
||||||
"/C/Users/adam/Desktop/vagrant/node/scripttest/package.json"
|
|
||||||
],
|
|
||||||
"find":
|
|
||||||
{
|
|
||||||
"height": 47.0
|
|
||||||
},
|
|
||||||
"find_in_files":
|
|
||||||
{
|
|
||||||
"height": 117.0,
|
|
||||||
"where_history":
|
|
||||||
[
|
|
||||||
"C:\\Users\\adam\\Desktop\\vagrant\\node\\scripttest",
|
|
||||||
"C:\\Users\\adam\\Desktop\\git\\learning-nodejs-passport\\scripttest",
|
|
||||||
"<open folders>",
|
|
||||||
"C:\\Users\\adam\\Google Drive\\projects\\library-private\\website\\theme\\newlibrary-catalog-basic",
|
|
||||||
"C:\\Users\\adam\\Google Drive\\projects\\library-private\\website\\theme\\twentyfourteen",
|
|
||||||
"C:\\Users\\adam\\Google Drive\\projects\\library-public\\website\\theme\\twentyfourteen"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"find_state":
|
|
||||||
{
|
|
||||||
"case_sensitive": false,
|
|
||||||
"find_history":
|
|
||||||
[
|
|
||||||
"livereload",
|
|
||||||
"35729",
|
|
||||||
"!!!",
|
|
||||||
"open",
|
|
||||||
"runt.task.run",
|
|
||||||
"open",
|
|
||||||
"xdg",
|
|
||||||
"git",
|
|
||||||
" <",
|
|
||||||
"callback",
|
|
||||||
"ListViewCtrl",
|
|
||||||
" console.log(\"found one!\");\n console.log(resource);\n",
|
|
||||||
"isloggedin",
|
|
||||||
"user",
|
|
||||||
"!list.user == req.user",
|
|
||||||
"(!list.user == req.user)",
|
|
||||||
"open",
|
|
||||||
"xdg",
|
|
||||||
"fucks",
|
|
||||||
"main",
|
|
||||||
"open",
|
|
||||||
"wait",
|
|
||||||
"express",
|
|
||||||
"express:dev",
|
|
||||||
"aggregate",
|
|
||||||
"type",
|
|
||||||
"prop",
|
|
||||||
"promise",
|
|
||||||
"mongo",
|
|
||||||
"reload",
|
|
||||||
"signup",
|
|
||||||
"localhost",
|
|
||||||
"process.env.PORT",
|
|
||||||
"process.env",
|
|
||||||
"9000",
|
|
||||||
"serve:",
|
|
||||||
"local",
|
|
||||||
"127",
|
|
||||||
"local",
|
|
||||||
"TEMPLATE_DIRS",
|
|
||||||
"provision",
|
|
||||||
"[",
|
|
||||||
"python",
|
|
||||||
"flash",
|
|
||||||
"initializeCapture",
|
|
||||||
"elapsed",
|
|
||||||
"setupMouse",
|
|
||||||
"firebolt",
|
|
||||||
"h1",
|
|
||||||
"Owner",
|
|
||||||
"ruby",
|
|
||||||
"entry-footer",
|
|
||||||
"return_f",
|
|
||||||
" a ",
|
|
||||||
" a",
|
|
||||||
"h1",
|
|
||||||
"Alegreya Sans SC",
|
|
||||||
"entry-title",
|
|
||||||
"font-family",
|
|
||||||
"leto",
|
|
||||||
"script",
|
|
||||||
"font",
|
|
||||||
"newlibrary_catalog_basic_posted_on",
|
|
||||||
"date",
|
|
||||||
"newlibrary_catalog_basic_categorized_blog",
|
|
||||||
"thum",
|
|
||||||
"posts_to_fix",
|
|
||||||
"$html",
|
|
||||||
"external_image_options",
|
|
||||||
"add_media_page",
|
|
||||||
"menu",
|
|
||||||
"external_image_import_all_ajax",
|
|
||||||
"external_images_verify_permission",
|
|
||||||
"admin",
|
|
||||||
"json_encode",
|
|
||||||
"can",
|
|
||||||
"user_can",
|
|
||||||
"action",
|
|
||||||
"query",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
" ",
|
|
||||||
"</a>",
|
|
||||||
"<a",
|
|
||||||
"<a>",
|
|
||||||
"</a>",
|
|
||||||
"<ul>",
|
|
||||||
"<a name=\".*\"></a>",
|
|
||||||
" style=\"color: #ffffff;\"",
|
|
||||||
"blockquote",
|
|
||||||
":before",
|
|
||||||
"nth",
|
|
||||||
"home",
|
|
||||||
"2.0",
|
|
||||||
"10.1",
|
|
||||||
"get_sidebar",
|
|
||||||
"content-",
|
|
||||||
"content:",
|
|
||||||
"content",
|
|
||||||
"10.0",
|
|
||||||
"entry-title",
|
|
||||||
"10.0",
|
|
||||||
"2.0",
|
|
||||||
"add_query",
|
|
||||||
"wp_register",
|
|
||||||
"wp_register_file",
|
|
||||||
"wp_register_style",
|
|
||||||
"font",
|
|
||||||
"Lato",
|
|
||||||
"link",
|
|
||||||
"Lato",
|
|
||||||
"link",
|
|
||||||
"Lato",
|
|
||||||
"font",
|
|
||||||
"nth-child",
|
|
||||||
"nth-of",
|
|
||||||
"downlo",
|
|
||||||
"type",
|
|
||||||
".post-thumbnail",
|
|
||||||
".post-image",
|
|
||||||
"absolute",
|
|
||||||
"entry-header",
|
|
||||||
"FEATURED-CONTENT",
|
|
||||||
"FEATURED-CONTENT-INNER",
|
|
||||||
"archive-header",
|
|
||||||
"entry-header",
|
|
||||||
"#767676"
|
|
||||||
],
|
|
||||||
"highlight": true,
|
|
||||||
"in_selection": false,
|
|
||||||
"preserve_case": false,
|
|
||||||
"regex": false,
|
|
||||||
"replace_history":
|
|
||||||
[
|
|
||||||
"list.user != req.user",
|
|
||||||
"",
|
|
||||||
",",
|
|
||||||
"",
|
|
||||||
"\\n"
|
|
||||||
],
|
|
||||||
"reverse": false,
|
|
||||||
"show_context": true,
|
|
||||||
"use_buffer2": true,
|
|
||||||
"whole_word": false,
|
|
||||||
"wrap": true
|
|
||||||
},
|
|
||||||
"groups":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"selected": 5,
|
|
||||||
"sheets":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"buffer": 0,
|
|
||||||
"file": "client/index.html",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 3458,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/HTML/HTML.tmLanguage",
|
|
||||||
"tab_size": 2,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 516.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 6,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 1,
|
|
||||||
"file": "bower.json",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 617,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/JavaScript/JSON.tmLanguage",
|
|
||||||
"tab_size": 2,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 0.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 7,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 2,
|
|
||||||
"file": "client/app/app.js",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 349,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/JavaScript/JavaScript.tmLanguage",
|
|
||||||
"tab_size": 2,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 0.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 5,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 3,
|
|
||||||
"file": "server/components/wdiff/index.js",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 8966,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
590,
|
|
||||||
590
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/Babel/JavaScript (Babel).sublime-syntax",
|
|
||||||
"tab_size": 2,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 168.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 4,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 4,
|
|
||||||
"file": "/C/Users/Adam/Desktop/code/courses/git-status-all",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 113,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
113,
|
|
||||||
113
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/Text/Plain text.tmLanguage"
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 0.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 1,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 5,
|
|
||||||
"file": "/C/Users/Adam/Downloads/JavaScript Refactoring - Course Notes.md",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 2388,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/Markdown/MultiMarkdown.sublime-syntax"
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 0.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 0,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 6,
|
|
||||||
"file": "/C/Users/Adam/Dropbox (Envato)/Course Content/Code/ready/Create a New JavaScript Framework - How We Built the Daily Mail CMS/notes - QA.md",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 3928,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
3928,
|
|
||||||
3928
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/Markdown/MultiMarkdown.sublime-syntax",
|
|
||||||
"tab_size": 4,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 1786.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 2,
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 7,
|
|
||||||
"file": "/C/Users/Adam/Dropbox (Envato)/Course Content/Code/ready/Create a New JavaScript Framework - Challenges of Rolling Your Own/notes - QA.md",
|
|
||||||
"semi_transient": false,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"buffer_size": 4420,
|
|
||||||
"regions":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"selection":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
1014,
|
|
||||||
1272
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
"syntax": "Packages/Markdown/MultiMarkdown.sublime-syntax",
|
|
||||||
"tab_size": 4,
|
|
||||||
"translate_tabs_to_spaces": true
|
|
||||||
},
|
|
||||||
"translation.x": 0.0,
|
|
||||||
"translation.y": 379.0,
|
|
||||||
"zoom_level": 1.0
|
|
||||||
},
|
|
||||||
"stack_index": 3,
|
|
||||||
"type": "text"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"incremental_find":
|
|
||||||
{
|
|
||||||
"height": 26.0
|
|
||||||
},
|
|
||||||
"input":
|
|
||||||
{
|
|
||||||
"height": 35.0
|
|
||||||
},
|
|
||||||
"layout":
|
|
||||||
{
|
|
||||||
"cells":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
1
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"cols":
|
|
||||||
[
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"rows":
|
|
||||||
[
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"menu_visible": true,
|
|
||||||
"output.exec":
|
|
||||||
{
|
|
||||||
"height": 207.0
|
|
||||||
},
|
|
||||||
"output.find_results":
|
|
||||||
{
|
|
||||||
"height": 0.0
|
|
||||||
},
|
|
||||||
"pinned_build_system": "",
|
|
||||||
"project": "markdown-format-wdiff.sublime-project",
|
|
||||||
"replace":
|
|
||||||
{
|
|
||||||
"height": 50.0
|
|
||||||
},
|
|
||||||
"save_all_on_build": true,
|
|
||||||
"select_file":
|
|
||||||
{
|
|
||||||
"height": 0.0,
|
|
||||||
"last_filter": "",
|
|
||||||
"selected_items":
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"wait",
|
|
||||||
"client\\bower_components\\angular-sanitize\\angular-sanitize.js"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"width": 0.0
|
|
||||||
},
|
|
||||||
"select_project":
|
|
||||||
{
|
|
||||||
"height": 0.0,
|
|
||||||
"last_filter": "",
|
|
||||||
"selected_items":
|
|
||||||
[
|
|
||||||
],
|
|
||||||
"width": 0.0
|
|
||||||
},
|
|
||||||
"select_symbol":
|
|
||||||
{
|
|
||||||
"height": 0.0,
|
|
||||||
"last_filter": "",
|
|
||||||
"selected_items":
|
|
||||||
[
|
|
||||||
],
|
|
||||||
"width": 0.0
|
|
||||||
},
|
|
||||||
"selected_group": 0,
|
|
||||||
"settings":
|
|
||||||
{
|
|
||||||
},
|
|
||||||
"show_minimap": false,
|
|
||||||
"show_open_files": true,
|
|
||||||
"show_tabs": true,
|
|
||||||
"side_bar_visible": true,
|
|
||||||
"side_bar_width": 321.0,
|
|
||||||
"status_bar_visible": true,
|
|
||||||
"template_settings":
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
146
package.json
@ -1,92 +1,64 @@
|
|||||||
{
|
{
|
||||||
"name": "markdown-format-wdiff",
|
"name": "dubdiff",
|
||||||
"version": "0.0.0",
|
"version": "2.0.1",
|
||||||
"main": "server/app.js",
|
"description": "",
|
||||||
|
"main": "src/server/babel.index.js",
|
||||||
|
"scripts": {
|
||||||
|
"copy-css": "cpy --parents --cwd=./node_modules/semantic-ui-css semantic.min.css themes/default/assets/fonts/icons.woff2 ../../dist",
|
||||||
|
"build": "npm run copy-css && webpack --progress --colors",
|
||||||
|
"build:prod": "npm run copy-css && cross-env NODE_ENV=production webpack -p --progress --colors",
|
||||||
|
"build:prod:nocopy": "cross-env NODE_ENV=production webpack -p --progress --colors",
|
||||||
|
"build:watch": "npm run copy-css && webpack --progress --colors --watch",
|
||||||
|
"serve": "node src/server/babel.index.js",
|
||||||
|
"serve:prod": "cross-env NODE_ENV=production node src/server/babel.index.js",
|
||||||
|
"webpack-stats": "webpack --json > stats.json",
|
||||||
|
"lint": "standard --verbose | snazzy",
|
||||||
|
"lint:fix": "standard --fix --verbose | snazzy",
|
||||||
|
"test": "mocha --watch --compilers js:babel-register"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "~1.5.0",
|
"babel-preset-es2015-mod": "^6.6.0",
|
||||||
"chance": "~0.7.3",
|
"babel-preset-es3": "^1.0.1",
|
||||||
"composable-middleware": "^0.3.0",
|
"babel-preset-stage-2": "^6.18.0",
|
||||||
"compression": "~1.0.1",
|
"body-parser": "^1.15.2",
|
||||||
"connect-mongo": "^0.4.1",
|
"diff": "^3.0.1",
|
||||||
"cookie-parser": "~1.0.1",
|
"express": "^4.14.0",
|
||||||
"errorhandler": "~1.0.0",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"express": "~4.0.0",
|
"jsonfile": "^2.4.0",
|
||||||
"express-jwt": "^0.1.3",
|
"markdown-it": "^5.1.0",
|
||||||
"express-session": "~1.0.2",
|
"markdown-to-jsx": "^4.0.3",
|
||||||
"jade": "~1.2.0",
|
"react": "^0.14.5",
|
||||||
"jsonfile": "~2.0.0",
|
"react-dom": "^0.14.5",
|
||||||
"jsonwebtoken": "^0.3.0",
|
"react-redux": "^4.4.6",
|
||||||
"lex": "^1.7.8",
|
"react-router": "~3.0.0",
|
||||||
"lodash": "~2.4.1",
|
"redux": "^3.5.1",
|
||||||
"method-override": "~1.0.0",
|
"redux-thunk": "^2.1.0",
|
||||||
"mongoose": "~3.8.8",
|
"request": "^2.79.0",
|
||||||
"morgan": "~1.0.0",
|
"request-promise-native": "^1.0.3",
|
||||||
"serve-favicon": "~2.0.1",
|
"reselect": "^2.5.1",
|
||||||
"temp": "^0.8.1"
|
"semantic-ui-css": "^2.2.4",
|
||||||
|
"semantic-ui-react": "^0.61.6",
|
||||||
|
"uuid": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"connect-livereload": "~0.4.0",
|
"babel-core": "^6.18.2",
|
||||||
"grunt": "~0.4.4",
|
"babel-loader": "^6.2.0",
|
||||||
"grunt-angular-templates": "^0.5.4",
|
"babel-preset-es2015": "^6.3.13",
|
||||||
"grunt-asset-injector": "^0.1.0",
|
"babel-preset-es2015-native-modules": "^6.9.4",
|
||||||
"grunt-autoprefixer": "~0.7.2",
|
"babel-preset-node6": "^11.0.0",
|
||||||
"grunt-build-control": "DaftMonk/grunt-build-control",
|
"babel-preset-react": "^6.3.13",
|
||||||
"grunt-concurrent": "~0.5.0",
|
"babel-register": "^6.18.0",
|
||||||
"grunt-contrib-clean": "~0.5.0",
|
"chai": "^3.5.0",
|
||||||
"grunt-contrib-concat": "~0.4.0",
|
"copyfiles": "^0.2.2",
|
||||||
"grunt-contrib-copy": "~0.5.0",
|
"cpy-cli": "^1.0.1",
|
||||||
"grunt-contrib-cssmin": "~0.9.0",
|
"cross-env": "^3.1.3",
|
||||||
"grunt-contrib-htmlmin": "~0.2.0",
|
"json-loader": "^0.5.4",
|
||||||
"grunt-contrib-imagemin": "~0.7.1",
|
"mocha": "^3.2.0",
|
||||||
"grunt-contrib-jade": "^0.11.0",
|
"piping": "^1.0.0-rc.4",
|
||||||
"grunt-contrib-jshint": "~0.10.0",
|
"snazzy": "^6.0.0",
|
||||||
"grunt-contrib-sass": "^0.9.2",
|
"standard": "^8.6.0",
|
||||||
"grunt-contrib-uglify": "~0.4.0",
|
"webpack": "^2.1.0-beta.27"
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
}
|
||||||
"grunt-dom-munger": "^3.4.0",
|
|
||||||
"grunt-env": "~0.4.1",
|
|
||||||
"grunt-express-server": "~0.4.17",
|
|
||||||
"grunt-google-cdn": "~0.4.0",
|
|
||||||
"grunt-karma": "~0.8.2",
|
|
||||||
"grunt-mocha-test": "~0.10.2",
|
|
||||||
"grunt-newer": "~0.7.0",
|
|
||||||
"grunt-ng-annotate": "^0.2.3",
|
|
||||||
"grunt-node-inspector": "~0.1.5",
|
|
||||||
"grunt-nodemon": "~0.2.0",
|
|
||||||
"grunt-open": "~0.2.3",
|
|
||||||
"grunt-protractor-runner": "^1.1.0",
|
|
||||||
"grunt-rev": "~0.1.0",
|
|
||||||
"grunt-svgmin": "~0.4.0",
|
|
||||||
"grunt-usemin": "~2.1.1",
|
|
||||||
"grunt-wiredep": "~1.8.0",
|
|
||||||
"jit-grunt": "^0.5.0",
|
|
||||||
"jshint-stylish": "~0.1.5",
|
|
||||||
"karma": "~0.12.9",
|
|
||||||
"karma-chrome-launcher": "~0.1.3",
|
|
||||||
"karma-coffee-preprocessor": "~0.2.1",
|
|
||||||
"karma-firefox-launcher": "~0.1.3",
|
|
||||||
"karma-html2js-preprocessor": "~0.1.0",
|
|
||||||
"karma-jade-preprocessor": "0.0.11",
|
|
||||||
"karma-jasmine": "~0.1.5",
|
|
||||||
"karma-ng-html2js-preprocessor": "~0.1.0",
|
|
||||||
"karma-ng-jade2js-preprocessor": "^0.1.2",
|
|
||||||
"karma-ng-scenario": "~0.1.0",
|
|
||||||
"karma-phantomjs-launcher": "~0.1.4",
|
|
||||||
"karma-requirejs": "~0.2.1",
|
|
||||||
"karma-script-launcher": "~0.1.0",
|
|
||||||
"open": "~0.0.4",
|
|
||||||
"requirejs": "~2.1.11",
|
|
||||||
"should": "~3.3.1",
|
|
||||||
"supertest": "~0.11.0",
|
|
||||||
"time-grunt": "~0.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "node server/app.js",
|
|
||||||
"test": "grunt test",
|
|
||||||
"update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
|
|
||||||
},
|
|
||||||
"private": true
|
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
// Protractor configuration
|
|
||||||
// https://github.com/angular/protractor/blob/master/referenceConf.js
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
exports.config = {
|
|
||||||
// The timeout for each script run on the browser. This should be longer
|
|
||||||
// than the maximum time your application needs to stabilize between tasks.
|
|
||||||
allScriptsTimeout: 110000,
|
|
||||||
|
|
||||||
// A base URL for your application under test. Calls to protractor.get()
|
|
||||||
// with relative paths will be prepended with this.
|
|
||||||
baseUrl: 'http://localhost:' + (process.env.PORT || '9000'),
|
|
||||||
|
|
||||||
// If true, only chromedriver will be started, not a standalone selenium.
|
|
||||||
// Tests for browsers other than chrome will not run.
|
|
||||||
chromeOnly: true,
|
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
|
||||||
specs: [
|
|
||||||
'e2e/**/*.spec.js'
|
|
||||||
],
|
|
||||||
|
|
||||||
// Patterns to exclude.
|
|
||||||
exclude: [],
|
|
||||||
|
|
||||||
// ----- Capabilities to be passed to the webdriver instance ----
|
|
||||||
//
|
|
||||||
// For a full list of available capabilities, see
|
|
||||||
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
|
|
||||||
// and
|
|
||||||
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
|
|
||||||
capabilities: {
|
|
||||||
'browserName': 'chrome'
|
|
||||||
},
|
|
||||||
|
|
||||||
// ----- The test framework -----
|
|
||||||
//
|
|
||||||
// Jasmine and Cucumber are fully supported as a test and assertion framework.
|
|
||||||
// Mocha has limited beta support. You will need to include your own
|
|
||||||
// assertion framework if working with mocha.
|
|
||||||
framework: 'jasmine',
|
|
||||||
|
|
||||||
// ----- Options to be passed to minijasminenode -----
|
|
||||||
//
|
|
||||||
// See the full list at https://github.com/juliemr/minijasminenode
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
defaultTimeoutInterval: 30000
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"node": true,
|
|
||||||
"esnext": true,
|
|
||||||
"bitwise": true,
|
|
||||||
"eqeqeq": true,
|
|
||||||
"immed": true,
|
|
||||||
"latedef": "nofunc",
|
|
||||||
"newcap": true,
|
|
||||||
"noarg": true,
|
|
||||||
"regexp": true,
|
|
||||||
"undef": true,
|
|
||||||
"smarttabs": true,
|
|
||||||
"asi": true,
|
|
||||||
"debug": true
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ".jshintrc",
|
|
||||||
"globals": {
|
|
||||||
"describe": true,
|
|
||||||
"it": true,
|
|
||||||
"before": true,
|
|
||||||
"beforeEach": true,
|
|
||||||
"after": true,
|
|
||||||
"afterEach": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
|
||||||
var Comparison = require('./comparison.model');
|
|
||||||
var wdiff = require('../../components/wdiff');
|
|
||||||
var jf = require('jsonfile');
|
|
||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//return the comparison given an id, if it exsits
|
|
||||||
exports.showComparison = function showComparison(req, res) {
|
|
||||||
//generate a filename
|
|
||||||
var filename = fnComparison(req.params.id);
|
|
||||||
|
|
||||||
//check if that file exists
|
|
||||||
fs.exists(filename, function (exists) {
|
|
||||||
//if the file does not exist, return a 404
|
|
||||||
if (!exists) return res.send(404);
|
|
||||||
|
|
||||||
//otherwise, read the file as JSON
|
|
||||||
jf.readFile(filename, function(err, comparison) {
|
|
||||||
if(err) { return handleError(res, err); }
|
|
||||||
|
|
||||||
//and return
|
|
||||||
return res.json(comparison);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//return a markdown wdiff for the comparison given an id, if it exsits
|
|
||||||
exports.wdiffMarkdownComparison = function wdiffMarkdownComparison(req, res) {
|
|
||||||
//generate a filename
|
|
||||||
var filename = fnComparison(req.params.id);
|
|
||||||
|
|
||||||
//check if that file exists
|
|
||||||
fs.exists(filename, function (exists) {
|
|
||||||
//if the file does not exist, return a 404
|
|
||||||
if (!exists) return res.send(404);
|
|
||||||
|
|
||||||
//otherwise, read the file as JSON
|
|
||||||
jf.readFile(filename, function(err, comparison) {
|
|
||||||
if(err) { return handleError(res, err); }
|
|
||||||
|
|
||||||
//now perform a wdiff on the result
|
|
||||||
wdiff(comparison.a,comparison.b, true, function(err, result) {
|
|
||||||
if (err)
|
|
||||||
return handleError(res, err);
|
|
||||||
|
|
||||||
_.merge(result, comparison)
|
|
||||||
return res.json(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new comparison
|
|
||||||
exports.create = function create(req, res) {
|
|
||||||
var a = req.body.a;
|
|
||||||
var b = req.body.b;
|
|
||||||
|
|
||||||
//create the comparison
|
|
||||||
var comparison = new Comparison(a,b);
|
|
||||||
|
|
||||||
//look up its filename
|
|
||||||
var filename = fnComparison(comparison._id);
|
|
||||||
|
|
||||||
//and write it to the filesystem
|
|
||||||
jf.writeFile(filename, comparison, function(err) {
|
|
||||||
if(err) { return handleError(res, err); }
|
|
||||||
|
|
||||||
//if successful, return the comparison object
|
|
||||||
return res.json(201, comparison);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleError(res, err) {
|
|
||||||
console.log(err);
|
|
||||||
return res.send(500, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// returns a filename for the given comparison
|
|
||||||
function fnComparison (id) {
|
|
||||||
return "./data/" + "comp-" + id + ".json";
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Load Chance
|
|
||||||
var Chance = require('chance');
|
|
||||||
|
|
||||||
// Instantiate Chance so it can be used
|
|
||||||
var chance = new Chance();
|
|
||||||
|
|
||||||
|
|
||||||
var Comparison = function Comparison(a,b) {
|
|
||||||
return {
|
|
||||||
created: Date.now(),
|
|
||||||
a: a,
|
|
||||||
b: b,
|
|
||||||
_id: chance.hash()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Comparison;
|
|
@ -1,18 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var express = require('express');
|
|
||||||
var controller = require('./comparison.controller');
|
|
||||||
|
|
||||||
var router = express.Router();
|
|
||||||
|
|
||||||
//router.get('/', controller.index);
|
|
||||||
|
|
||||||
router.get('/:id', controller.showComparison);
|
|
||||||
//router.get('/:id/:doc', controller.showComparisonDoc);
|
|
||||||
router.get('/wdiff/:id', controller.wdiffMarkdownComparison);
|
|
||||||
//router.get('/wdiff/:id/nomarkdown', controller.wdiffNoMarkdownComparison);
|
|
||||||
|
|
||||||
router.post('/', controller.create);
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Main application file
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Set default node environment to development
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
|
||||||
|
|
||||||
var express = require('express');
|
|
||||||
var config = require('./config/environment');
|
|
||||||
|
|
||||||
|
|
||||||
// Setup server
|
|
||||||
var app = express();
|
|
||||||
var server = require('http').createServer(app);
|
|
||||||
require('./config/express')(app);
|
|
||||||
require('./routes')(app);
|
|
||||||
|
|
||||||
// Start server
|
|
||||||
server.listen(config.port, config.ip, function () {
|
|
||||||
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose app
|
|
||||||
exports = module.exports = app;
|
|
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Error responses
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports[404] = function pageNotFound(req, res) {
|
|
||||||
var viewFilePath = '404';
|
|
||||||
var statusCode = 404;
|
|
||||||
var result = {
|
|
||||||
status: statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
res.status(result.status);
|
|
||||||
res.render(viewFilePath, function (err) {
|
|
||||||
if (err) { return res.json(result, result.status); }
|
|
||||||
|
|
||||||
res.render(viewFilePath);
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,322 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var _ = require('lodash'),
|
|
||||||
temp = require('temp'),
|
|
||||||
fs = require('fs'),
|
|
||||||
exec = require('child_process').exec,
|
|
||||||
Lexer = require('lex');
|
|
||||||
|
|
||||||
var diff = require('diff');
|
|
||||||
|
|
||||||
// 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 = jsdiffEngine
|
|
||||||
|
|
||||||
function jsdiffEngine (a, b, asMarkdown, callback) {
|
|
||||||
|
|
||||||
|
|
||||||
//a few strings have to be escaped: "[-", "-]", "{+", and "+}"
|
|
||||||
a = escapeString(a)
|
|
||||||
b = escapeString(b)
|
|
||||||
|
|
||||||
var diffRes = diff.diffWordsWithSpace(a,b, {ignoreWhitespace:true})
|
|
||||||
var diffStr = diffRes.map (part => {
|
|
||||||
if (part.added) return "{+"+part.value+"+}";
|
|
||||||
else if (part.removed) return "[-"+part.value+"-]";
|
|
||||||
else return part.value;
|
|
||||||
}).join("");
|
|
||||||
|
|
||||||
//if no difference was found by wdiff, err.code will be 0
|
|
||||||
var wdiffSame;
|
|
||||||
wdiffSame = false; //???
|
|
||||||
|
|
||||||
console.log(diffStr)
|
|
||||||
|
|
||||||
var resData = {wdiffNoMarkdown:unescapeString(diffStr), same: wdiffSame};
|
|
||||||
if (asMarkdown) {
|
|
||||||
|
|
||||||
//!!! this needs more sophisticated parsing
|
|
||||||
|
|
||||||
var markdown = unescapeString(rewriteWdiffMarkdown(diffStr))
|
|
||||||
|
|
||||||
resData.wdiff=markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, resData);
|
|
||||||
}
|
|
||||||
|
|
||||||
function wdiffEngine (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
|
|
||||||
|
|
||||||
//a few strings have to be escaped: "[-", "-]", "{+", and "+}"
|
|
||||||
a = escapeString(a)
|
|
||||||
b = escapeString(b)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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 = "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;
|
|
||||||
|
|
||||||
console.log(stdout)
|
|
||||||
|
|
||||||
var resData = {wdiffNoMarkdown:unescapeString(stdout), same: wdiffSame};
|
|
||||||
if (asMarkdown) {
|
|
||||||
|
|
||||||
//!!! this needs more sophisticated parsing
|
|
||||||
|
|
||||||
var markdown = unescapeString(rewriteWdiffMarkdown(stdout))
|
|
||||||
|
|
||||||
resData.wdiff=markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, resData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
var newlineIndex = -1;
|
|
||||||
|
|
||||||
// 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]*[\d]+\.))?[ \t]+/
|
|
||||||
var PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]*[\*\+-])|([ \t]*[\d]+\.))?[ \t]*/
|
|
||||||
//var PREFIX = /^#*/
|
|
||||||
|
|
||||||
|
|
||||||
transform.forEach(function(item) {
|
|
||||||
//newlines are undecorated
|
|
||||||
if (item.string == '\n') {
|
|
||||||
output += '\n';
|
|
||||||
|
|
||||||
//flag the new line
|
|
||||||
newline = true;
|
|
||||||
//and record the offset in the output string
|
|
||||||
newlineIndex = output.length;
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//wrap del strings with tags
|
|
||||||
if (item.state == SDEL) {
|
|
||||||
output += '<del>' + item.string + '</del>';
|
|
||||||
//del doesn't reset the newline state
|
|
||||||
}
|
|
||||||
|
|
||||||
//ins strings have to be handled a little differently:
|
|
||||||
//if this is an ins just after a newline, or after a del after a newline, we need to peel off any markdown formatting prefixes and insert them at the beginning of the line outside the del/ins tags
|
|
||||||
else if (item.state == SINS && newline) {
|
|
||||||
var prestring, poststring;
|
|
||||||
var match = item.string.match(PREFIX);
|
|
||||||
if (match == null)
|
|
||||||
prestring ="";
|
|
||||||
else
|
|
||||||
prestring = match[0];
|
|
||||||
|
|
||||||
poststring = item.string.substring(prestring.length);
|
|
||||||
|
|
||||||
output = output.substring(0, newlineIndex) + prestring + output.substring(newlineIndex);
|
|
||||||
output += '<ins>' + poststring + '</ins>';
|
|
||||||
newline = false;
|
|
||||||
newlineIndex = -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (item.state == SINS) {
|
|
||||||
output += '<ins>' + item.string + '</ins>';
|
|
||||||
}
|
|
||||||
|
|
||||||
//and just output other strings
|
|
||||||
else {
|
|
||||||
output += item.string;
|
|
||||||
//this resets the newline state
|
|
||||||
newline = false;
|
|
||||||
newlineIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function escapeString(str) {
|
|
||||||
str = str.replace(/\[\-/gm, "[-")
|
|
||||||
str = str.replace(/\-\]/gm, "-]")
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
function unescapeString(str) {
|
|
||||||
str = str.replace(/\&\#91\;-/gm, "[-")
|
|
||||||
str = str.replace(/-\&\#93\;/gm, "-]")
|
|
||||||
return str
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Development specific configuration
|
|
||||||
// ==================================
|
|
||||||
module.exports = {
|
|
||||||
// MongoDB connection options
|
|
||||||
mongo: {
|
|
||||||
uri: 'mongodb://mongodb/markdownformatwdiff-dev'
|
|
||||||
},
|
|
||||||
|
|
||||||
seedDB: false
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var path = require('path');
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
function requiredProcessEnv(name) {
|
|
||||||
if(!process.env[name]) {
|
|
||||||
throw new Error('You must set the ' + name + ' environment variable');
|
|
||||||
}
|
|
||||||
return process.env[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
// All configurations will extend these options
|
|
||||||
// ============================================
|
|
||||||
var all = {
|
|
||||||
env: process.env.NODE_ENV,
|
|
||||||
|
|
||||||
// Root path of server
|
|
||||||
root: path.normalize(__dirname + '/../../..'),
|
|
||||||
|
|
||||||
// Server port
|
|
||||||
port: process.env.PORT || 9000,
|
|
||||||
|
|
||||||
// Should we populate the DB with sample data?
|
|
||||||
seedDB: false,
|
|
||||||
|
|
||||||
// Secret for session, you will want to change this and make it an environment variable
|
|
||||||
secrets: {
|
|
||||||
session: 'markdown-format-wdiff-secret'
|
|
||||||
},
|
|
||||||
|
|
||||||
// List of user roles
|
|
||||||
userRoles: ['guest', 'user', 'admin'],
|
|
||||||
|
|
||||||
// MongoDB connection options
|
|
||||||
mongo: {
|
|
||||||
options: {
|
|
||||||
db: {
|
|
||||||
safe: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export the config object based on the NODE_ENV
|
|
||||||
// ==============================================
|
|
||||||
module.exports = _.merge(
|
|
||||||
all,
|
|
||||||
require('./' + process.env.NODE_ENV + '.js') || {});
|
|
@ -1,23 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Production specific configuration
|
|
||||||
// =================================
|
|
||||||
module.exports = {
|
|
||||||
// Server IP
|
|
||||||
ip: process.env.OPENSHIFT_NODEJS_IP ||
|
|
||||||
process.env.IP ||
|
|
||||||
undefined,
|
|
||||||
|
|
||||||
// Server port
|
|
||||||
port: process.env.OPENSHIFT_NODEJS_PORT ||
|
|
||||||
process.env.PORT ||
|
|
||||||
8080,
|
|
||||||
|
|
||||||
// MongoDB connection options
|
|
||||||
mongo: {
|
|
||||||
uri: process.env.MONGOLAB_URI ||
|
|
||||||
process.env.MONGOHQ_URL ||
|
|
||||||
process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME ||
|
|
||||||
'mongodb://mongodb/markdownformatwdiff'
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Test specific configuration
|
|
||||||
// ===========================
|
|
||||||
module.exports = {
|
|
||||||
// MongoDB connection options
|
|
||||||
mongo: {
|
|
||||||
uri: 'mongodb://localhost/markdownformatwdiff-test'
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Express configuration
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var express = require('express');
|
|
||||||
var favicon = require('serve-favicon');
|
|
||||||
var morgan = require('morgan');
|
|
||||||
var compression = require('compression');
|
|
||||||
var bodyParser = require('body-parser');
|
|
||||||
var methodOverride = require('method-override');
|
|
||||||
var cookieParser = require('cookie-parser');
|
|
||||||
var errorHandler = require('errorhandler');
|
|
||||||
var path = require('path');
|
|
||||||
var config = require('./environment');
|
|
||||||
var session = require('express-session');
|
|
||||||
|
|
||||||
module.exports = function(app) {
|
|
||||||
var env = app.get('env');
|
|
||||||
|
|
||||||
app.set('views', config.root + '/server/views');
|
|
||||||
app.set('view engine', 'jade');
|
|
||||||
app.use(compression());
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
app.use(methodOverride());
|
|
||||||
app.use(cookieParser());
|
|
||||||
|
|
||||||
|
|
||||||
if ('production' === env) {
|
|
||||||
app.use(favicon(path.join(config.root, 'public', 'favicon.ico')));
|
|
||||||
app.use(express.static(path.join(config.root, 'public')));
|
|
||||||
app.set('appPath', config.root + '/public');
|
|
||||||
app.use(morgan('dev'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('development' === env || 'test' === env) {
|
|
||||||
app.use(require('connect-livereload')());
|
|
||||||
app.use(express.static(path.join(config.root, '.tmp')));
|
|
||||||
app.use(express.static(path.join(config.root, 'client')));
|
|
||||||
app.set('appPath', 'client');
|
|
||||||
app.use(morgan('dev'));
|
|
||||||
app.use(errorHandler()); // Error handler - has to be last
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// Use local.env.js for environment variables that grunt will set when the server starts locally.
|
|
||||||
// Use for your api keys, secrets, etc. This file should not be tracked by git.
|
|
||||||
//
|
|
||||||
// You will need to set these on the server you deploy to.
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
DOMAIN: 'http://localhost:9000',
|
|
||||||
SESSION_SECRET: 'markdownformatwdiff-secret',
|
|
||||||
|
|
||||||
FACEBOOK_ID: 'app-id',
|
|
||||||
FACEBOOK_SECRET: 'secret',
|
|
||||||
|
|
||||||
TWITTER_ID: 'app-id',
|
|
||||||
TWITTER_SECRET: 'secret',
|
|
||||||
|
|
||||||
GOOGLE_ID: 'app-id',
|
|
||||||
GOOGLE_SECRET: 'secret',
|
|
||||||
|
|
||||||
// Control debug level for modules using visionmedia/debug
|
|
||||||
DEBUG: ''
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Main application routes
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var errors = require('./components/errors');
|
|
||||||
|
|
||||||
module.exports = function(app) {
|
|
||||||
|
|
||||||
// Insert routes below
|
|
||||||
|
|
||||||
app.use('/api/compare', require('./api/comparison'));
|
|
||||||
|
|
||||||
// All undefined asset or api routes should return a 404
|
|
||||||
app.route('/:url(api|components|app|bower_components|assets)/*')
|
|
||||||
.get(errors[404]);
|
|
||||||
|
|
||||||
// All other routes should redirect to the index.html
|
|
||||||
app.route('/*')
|
|
||||||
.get(function(req, res) {
|
|
||||||
res.sendfile(app.get('appPath') + '/index.html');
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,133 +0,0 @@
|
|||||||
doctype html
|
|
||||||
html(lang='en')
|
|
||||||
head
|
|
||||||
meta(charset='utf-8')
|
|
||||||
title Page Not Found :(
|
|
||||||
style.
|
|
||||||
::-moz-selection {
|
|
||||||
background: #b3d4fc;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
::selection {
|
|
||||||
background: #b3d4fc;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
padding: 30px 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: #737373;
|
|
||||||
background: #f0f0f0;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-ms-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
html,
|
|
||||||
input {
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
max-width: 500px;
|
|
||||||
_width: 500px;
|
|
||||||
padding: 30px 20px 50px;
|
|
||||||
border: 1px solid #b3b3b3;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
|
|
||||||
background: #fcfcfc;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0 10px;
|
|
||||||
font-size: 50px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
h1 span {
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 1.5em 0 0.5em;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding: 0 0 0 40px;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 380px;
|
|
||||||
_width: 380px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
/* google search */
|
|
||||||
#goog-fixurl ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#goog-fixurl form {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#goog-wm-qt,
|
|
||||||
#goog-wm-sb {
|
|
||||||
border: 1px solid #bbb;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: normal;
|
|
||||||
vertical-align: top;
|
|
||||||
color: #444;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
#goog-wm-qt {
|
|
||||||
width: 220px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 5px 10px 0 0;
|
|
||||||
box-shadow: inset 0 1px 1px #ccc;
|
|
||||||
}
|
|
||||||
#goog-wm-sb {
|
|
||||||
display: inline-block;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0 10px;
|
|
||||||
margin: 5px 0 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
|
||||||
background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
|
||||||
background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
|
||||||
background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
*overflow: visible;
|
|
||||||
*display: inline;
|
|
||||||
*zoom: 1;
|
|
||||||
}
|
|
||||||
#goog-wm-sb:hover,
|
|
||||||
#goog-wm-sb:focus {
|
|
||||||
border-color: #aaa;
|
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
}
|
|
||||||
#goog-wm-qt:hover,
|
|
||||||
#goog-wm-qt:focus {
|
|
||||||
border-color: #105cb6;
|
|
||||||
outline: 0;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
input::-moz-focus-inner {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
body
|
|
||||||
.container
|
|
||||||
h1
|
|
||||||
| Not found
|
|
||||||
span :(
|
|
||||||
p Sorry, but the page you were trying to view does not exist.
|
|
||||||
p It looks like this was the result of either:
|
|
||||||
ul
|
|
||||||
li a mistyped address
|
|
||||||
li an out-of-date link
|
|
||||||
script.
|
|
||||||
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
|
|
||||||
script(src='//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js')
|
|
72
src/client/LocalStorage.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import * as Actions from '../common/actions'
|
||||||
|
|
||||||
|
/* This component reads the local storage store and adds them to the Redux store.
|
||||||
|
* Local storage is read during the componentDidMount lifecycle method.
|
||||||
|
* Local storage is written during the componentWillReceiveProps lifecycle method.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// an app-specific name for the localStorage state
|
||||||
|
const stateName = 'dubdiff_state'
|
||||||
|
|
||||||
|
// return a new object with the given keys, each assigned to the cooresponding value
|
||||||
|
// from the given object
|
||||||
|
const copyKeys = (obj, keys) => keys.reduce((acc, p) => { acc[p] = obj[p]; return acc }, {})
|
||||||
|
|
||||||
|
// utility method for retrieving json data from the local store
|
||||||
|
/*
|
||||||
|
function getLocalState (keys) {
|
||||||
|
if (window.localStorage.getItem(stateName)) {
|
||||||
|
const localState = JSON.parse(window.localStorage.getItem(stateName))
|
||||||
|
return copyKeys(localState, keys)
|
||||||
|
} else {
|
||||||
|
return copyKeys({}, keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// utility method for writing json data to the local store
|
||||||
|
function setLocalState (state, keys) {
|
||||||
|
let toSave = copyKeys(state, keys)
|
||||||
|
window.localStorage.setItem(stateName, JSON.stringify(toSave))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
input: state.input
|
||||||
|
// the loading/empty/clean state
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onChangeOriginal: (text) => dispatch(Actions.updateOriginalInput(text)),
|
||||||
|
onChangeFinal: (text) => dispatch(Actions.updateFinalInput(text))
|
||||||
|
})
|
||||||
|
|
||||||
|
class LocalStorage extends React.Component {
|
||||||
|
|
||||||
|
// load the state from the local storage
|
||||||
|
componentDidMount () {
|
||||||
|
// only if the status is EMPTY
|
||||||
|
/*
|
||||||
|
if (this.props.input.original=='' && this.props.input.final == '') {
|
||||||
|
const localState = getLocalState(['input'])
|
||||||
|
if (localState.input && localState.input.original)
|
||||||
|
this.props.onChangeOriginal(localState.input.original)
|
||||||
|
if (localState.input && localState.input.final)
|
||||||
|
this.props.onChangeFinal(localState.input.final)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
// save the state to local storage
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
setLocalState(nextProps, ['input'])
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(LocalStorage)
|
||||||
|
|
88
src/client/index.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
|
import * as Redux from 'redux'
|
||||||
|
|
||||||
|
import {Provider} from 'react-redux'
|
||||||
|
|
||||||
|
// import createBrowserHistory from 'history/lib/createBrowserHistory'
|
||||||
|
import {Router, browserHistory} from 'react-router'
|
||||||
|
|
||||||
|
import thunk from 'redux-thunk'
|
||||||
|
|
||||||
|
import * as reducers from '../common/reducers'
|
||||||
|
import routes from '../common/routes'
|
||||||
|
import {Format} from '../common/constants'
|
||||||
|
import * as Actions from '../common/actions'
|
||||||
|
|
||||||
|
import LocalStorage from './LocalStorage'
|
||||||
|
|
||||||
|
// initial state is rehydrated from the server
|
||||||
|
const initialState = JSON.parse(decodeURI(window.__INITIAL_STATE__))
|
||||||
|
|
||||||
|
// create the redux store
|
||||||
|
// initial state is retrieved from localStore
|
||||||
|
const store = Redux.createStore(
|
||||||
|
Redux.combineReducers(reducers),
|
||||||
|
initialState,
|
||||||
|
Redux.compose(
|
||||||
|
Redux.applyMiddleware(thunk),
|
||||||
|
window.devToolsExtension ? window.devToolsExtension() : f => f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
render()
|
||||||
|
|
||||||
|
// read and init the hash after render:
|
||||||
|
// because this parameter is passed as a hash, it isn't available on the server,
|
||||||
|
// so the server initial render will be with plaintext, so we have to start with that.
|
||||||
|
// lame.
|
||||||
|
initFormatHash(store)
|
||||||
|
registerFormatListener(store)
|
||||||
|
|
||||||
|
// detect hash parameter for markdown/plaintext format and initialize store
|
||||||
|
function initFormatHash(store) {
|
||||||
|
// get the has from the window location
|
||||||
|
let hash = window.location.hash.toUpperCase()
|
||||||
|
// strip the hash sign
|
||||||
|
hash = hash.substring(1)
|
||||||
|
|
||||||
|
// dispatch the appropriate action
|
||||||
|
if (hash === Format.MARKDOWN)
|
||||||
|
store.dispatch(Actions.setMarkdownFormat())
|
||||||
|
else if (hash === Format.PLAINTEXT || hash === '')
|
||||||
|
store.dispatch(Actions.setPlaintextFormat())
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen to changes in the redux store and update the url hash parameter when appropriate
|
||||||
|
function registerFormatListener(store) {
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
|
let nextFormat = store.getState().format;
|
||||||
|
|
||||||
|
if (nextFormat === Format.MARKDOWN)
|
||||||
|
window.history.replaceState("", document.title, window.location.pathname+"#"+nextFormat.toLowerCase());
|
||||||
|
else if (nextFormat === Format.PLAINTEXT) {
|
||||||
|
window.history.replaceState("", document.title, window.location.pathname);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsubscribe = store.subscribe(handleChange);
|
||||||
|
return unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<LocalStorage >
|
||||||
|
<Router history={browserHistory}>
|
||||||
|
{routes}
|
||||||
|
</Router>
|
||||||
|
</LocalStorage>
|
||||||
|
</Provider>
|
||||||
|
, document.getElementById('root'))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
150
src/common/actions.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import fetch from 'isomorphic-fetch'
|
||||||
|
import uuid from 'uuid/v4'
|
||||||
|
import {browserHistory} from 'react-router'
|
||||||
|
import {Status, StatusError} from './constants'
|
||||||
|
|
||||||
|
// All state transitions in the app happen in these methods
|
||||||
|
// this includes redux state changes, asyncronous data requests, and browser location changes
|
||||||
|
|
||||||
|
export const updateOriginalInput = (text) =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
dispatch({type: 'UPDATE_ORIGINAL_INPUT', data: text})
|
||||||
|
if (getState().input.original.length > 0) {
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.DIRTY})
|
||||||
|
} else {
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.EMPTY})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateFinalInput = (text) =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
dispatch({type: 'UPDATE_FINAL_INPUT', data: text})
|
||||||
|
if (getState().input.final.length > 0) {
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.DIRTY})
|
||||||
|
} else {
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.EMPTY})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearInput = () =>
|
||||||
|
(dispatch) => {
|
||||||
|
dispatch({type: 'CLEAR_INPUT'})
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.EMPTY})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setPlaintextFormat = () => ({ type: 'SET_PLAINTEXT_FORMAT' })
|
||||||
|
export const setMarkdownFormat = () => ({ type: 'SET_MARKDOWN_FORMAT' })
|
||||||
|
export const showOriginal = () => ({ type: 'SHOW_ORIGINAL' })
|
||||||
|
export const showFinal = () => ({ type: 'SHOW_FINAL' })
|
||||||
|
export const showDifference = () => ({ type: 'SHOW_DIFFERENCE' })
|
||||||
|
|
||||||
|
// if the input is dirty, saves it to the server
|
||||||
|
// creates a new uuid for the same,
|
||||||
|
// then changes the browser location to a comparison view with that id
|
||||||
|
export const compare = () =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
//! !! could test that the input is dirty before triggering a save
|
||||||
|
// if the input is empty, the compare should do nothing
|
||||||
|
// if the input is clean, the compare should not save and keep using the same id
|
||||||
|
|
||||||
|
// start saving the input to the server
|
||||||
|
const id = dispatch(save())
|
||||||
|
|
||||||
|
// we can use the id created by the save method to build a path
|
||||||
|
const comparePath = `/${id}`
|
||||||
|
browserHistory.replace(comparePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the input and return to the edit page
|
||||||
|
export const reset = () =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
dispatch(clearInput())
|
||||||
|
browserHistory.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch to the edit view
|
||||||
|
export const edit = () =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
browserHistory.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// saves the current input fields to the server
|
||||||
|
// creates and returns a new id for the comparison
|
||||||
|
// should this method ensure that the initial state is valid? ('DIRTY')
|
||||||
|
export const save = () =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
// generate an id
|
||||||
|
const id = uuid()
|
||||||
|
|
||||||
|
// set waiting state
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.SAVING})
|
||||||
|
|
||||||
|
const endpointUri = `/api/compare/${id}`
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
a: getState().input.original,
|
||||||
|
b: getState().input.final
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch post request
|
||||||
|
fetch(endpointUri, fetchOptions)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.CLEAN})
|
||||||
|
} else {
|
||||||
|
response.text().then((responseText) => {
|
||||||
|
const error = {message: `${response.status}: ${responseText}`}
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.DIRTY})
|
||||||
|
dispatch({type: 'STATUS_SET_ERROR', data: StatusError.SAVE_ERROR, error})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
//! !! could use a better error message here
|
||||||
|
dispatch({type: 'STATUS_SET', data: Status.DIRTY})
|
||||||
|
dispatch({type: 'STATUS_SET_ERROR', data: StatusError.SAVE_ERROR, error})
|
||||||
|
})
|
||||||
|
|
||||||
|
// return the id after the request has been sent
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
const load = (id) =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
|
||||||
|
//set waiting state
|
||||||
|
dispatch( {type: 'SAVE_STATUS_WAITING'})
|
||||||
|
|
||||||
|
const endpointUri = `/api/compare/${id}`
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'GET'
|
||||||
|
}
|
||||||
|
|
||||||
|
//dispatch post request
|
||||||
|
fetch(endpointUri, fetchOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
dispatch( {type: 'UPDATE_ORIGINAL_INPUT', data:json.a})
|
||||||
|
dispatch( {type: 'UPDATE_FINAL_INPUT', data:json.b})
|
||||||
|
dispatch( {type: 'LOAD_STATUS_LOADED'})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch( {type: 'LOAD_STATUS_FAILED', error})
|
||||||
|
})
|
||||||
|
|
||||||
|
//return the id after the request has been sent
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadIfNeeded = (id) =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
if
|
||||||
|
}
|
||||||
|
*/
|
102
src/common/components/Compare.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Segment, Grid} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
import * as Selectors from '../selectors'
|
||||||
|
|
||||||
|
import {Format} from '../constants'
|
||||||
|
|
||||||
|
import Header from './Header'
|
||||||
|
import Footer from './Footer'
|
||||||
|
import CompareControls from './CompareControls'
|
||||||
|
|
||||||
|
import ShowPlaintext from './ShowPlaintext'
|
||||||
|
import ShowMarkdown from './ShowMarkdown'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
isMarkdownFormat: Selectors.isMarkdownFormat(state),
|
||||||
|
isShowOriginal: Selectors.isShowOriginal(state),
|
||||||
|
isShowFinal: Selectors.isShowFinal(state),
|
||||||
|
isShowDifference: Selectors.isShowDifference(state),
|
||||||
|
safeInput: Selectors.safeInput(state),
|
||||||
|
diff: Selectors.diff(state)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
// loadIfNeeded: (id) => dispatch(Actions.loadIfNeeded())
|
||||||
|
})
|
||||||
|
|
||||||
|
class Compare extends React.Component {
|
||||||
|
/*
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.loadIfNeeded(this.props.routeParams.compareId)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<Segment basic padded>
|
||||||
|
<Grid stackable columns={2}>
|
||||||
|
<Grid.Column width='3'>
|
||||||
|
<CompareControls />
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width='13'>
|
||||||
|
<Segment>
|
||||||
|
{
|
||||||
|
(!this.props.isMarkdownFormat && this.props.isShowDifference)
|
||||||
|
? <ShowPlaintext diff={this.props.diff}>{this.props.diff}</ShowPlaintext>
|
||||||
|
: (this.props.isMarkdownFormat && this.props.isShowDifference)
|
||||||
|
? <ShowMarkdown diff={this.props.diff}>{this.props.diff}</ShowMarkdown>
|
||||||
|
: (!this.props.isMarkdownFormat && !this.props.isShowDifference)
|
||||||
|
? <ShowPlaintext
|
||||||
|
text={this.props.isShowOriginal ? this.props.safeInput.original : this.props.safeInput.final}
|
||||||
|
/>
|
||||||
|
: (this.props.isMarkdownFormat && !this.props.isShowDifference)
|
||||||
|
? <ShowMarkdown
|
||||||
|
text={this.props.isShowOriginal ? this.props.safeInput.original : this.props.safeInput.final}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Segment>
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Compare)
|
||||||
|
|
||||||
|
/* <div ng-if="isMarkdownFormat">
|
||||||
|
<div ng-show="isShowBefore" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div btf-markdown="before" class="before">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="isShowWdiff" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div btf-markdown="wdiff" class="wdiff">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="isShowAfter" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div btf-markdown="after" class="after">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!isMarkdownFormat">
|
||||||
|
<div ng-show="isShowBefore" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div ng-bind-html="before" class="content-pre before"></div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="isShowWdiff" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div ng-bind-html="wdiff" class="content-pre wdiff"></div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="isShowAfter" class="col-md-10 col-sm-12 content-well">
|
||||||
|
<div ng-bind-html="after" class="content-pre after"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
*/
|
60
src/common/components/CompareControls.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Button, Icon, Segment} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
import * as Actions from '../actions'
|
||||||
|
import * as Selectors from '../selectors'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
isMarkdownFormat: Selectors.isMarkdownFormat(state),
|
||||||
|
isShowOriginal: Selectors.isShowOriginal(state),
|
||||||
|
isShowFinal: Selectors.isShowFinal(state),
|
||||||
|
isShowDifference: Selectors.isShowDifference(state)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onSetPlaintextFormat: () => dispatch(Actions.setPlaintextFormat()),
|
||||||
|
onSetMarkdownFormat: () => dispatch(Actions.setMarkdownFormat()),
|
||||||
|
onShowOriginal: () => dispatch(Actions.showOriginal()),
|
||||||
|
onShowFinal: () => dispatch(Actions.showFinal()),
|
||||||
|
onShowDifference: () => dispatch(Actions.showDifference()),
|
||||||
|
onEdit: () => dispatch(Actions.edit())
|
||||||
|
})
|
||||||
|
|
||||||
|
class CompareControls extends React.Component {
|
||||||
|
|
||||||
|
onClickMarkdownFormat () {
|
||||||
|
if (this.props.isMarkdownFormat) {
|
||||||
|
this.props.onSetPlaintextFormat()
|
||||||
|
} else {
|
||||||
|
this.props.onSetMarkdownFormat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<Segment.Group>
|
||||||
|
<Segment>
|
||||||
|
<Button fluid onClick={this.props.onEdit}>Edit</Button>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Segment >
|
||||||
|
<Button fluid onClick={this.props.onShowOriginal} active={this.props.isShowOriginal}>Original</Button>
|
||||||
|
<Button fluid onClick={this.props.onShowFinal} active={this.props.isShowFinal}>Final</Button>
|
||||||
|
<Button fluid onClick={this.props.onShowDifference} active={this.props.isShowDifference}>Difference</Button>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Segment >
|
||||||
|
<Button fluid active={this.props.isMarkdownFormat} type='submit' onClick={this.onClickMarkdownFormat.bind(this)}>
|
||||||
|
{this.props.isMarkdownFormat ? <Icon name='checkmark' /> : <span />}
|
||||||
|
As Markdown
|
||||||
|
</Button>
|
||||||
|
</Segment>
|
||||||
|
</Segment.Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(CompareControls)
|
||||||
|
|
11
src/common/components/Footer.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {Segment} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
const Footer = (props) => (
|
||||||
|
<Segment basic padded textAlign='center' as='footer'>
|
||||||
|
<p><a href='http://adamarthurryan.com'>Adam Brown</a> | This website is <a href='https://github.com/adamarthurryan/dubdiff'>open source</a>.</p>
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Footer
|
34
src/common/components/Header.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Segment, Header, Rail} from 'semantic-ui-react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
|
||||||
|
import * as Actions from '../actions'
|
||||||
|
import SaveStatus from './SaveStatus'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onReset: () => { console.log(Actions.reset()); dispatch(Actions.reset()) }
|
||||||
|
})
|
||||||
|
|
||||||
|
const SiteHeader = (props) => (
|
||||||
|
|
||||||
|
<Segment basic >
|
||||||
|
|
||||||
|
<Segment basic padded textAlign='center' as='header' id='masthead'>
|
||||||
|
<Header><Link onClick={props.onReset}>dubdiff</Link></Header>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Rail internal position='right'>
|
||||||
|
<Segment basic padded>
|
||||||
|
<SaveStatus />
|
||||||
|
</Segment>
|
||||||
|
</Rail>
|
||||||
|
|
||||||
|
</Segment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SiteHeader)
|
63
src/common/components/Main.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Segment, Grid, Form} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
import * as Actions from '../actions'
|
||||||
|
import * as Selectors from '../selectors'
|
||||||
|
|
||||||
|
import Header from './Header'
|
||||||
|
import Footer from './Footer'
|
||||||
|
import MainControls from './MainControls'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
input: state.input,
|
||||||
|
safeInput: Selectors.safeInput(state)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onChangeOriginal: (text) => dispatch(Actions.updateOriginalInput(text)),
|
||||||
|
onChangeFinal: (text) => dispatch(Actions.updateFinalInput(text))
|
||||||
|
})
|
||||||
|
|
||||||
|
class Main extends React.Component {
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<Segment basic padded>
|
||||||
|
<Grid stackable columns={3}>
|
||||||
|
<Grid.Column width='3'>
|
||||||
|
<MainControls />
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width='6'>
|
||||||
|
<Form>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Original</label>
|
||||||
|
<textarea value={this.props.input.original} onChange={event => this.props.onChangeOriginal(event.target.value)} />
|
||||||
|
</Form.Field>
|
||||||
|
</Form>
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width='6'>
|
||||||
|
<Form>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Final</label>
|
||||||
|
<textarea value={this.props.input.final} onChange={event => this.props.onChangeFinal(event.target.value)} />
|
||||||
|
</Form.Field>
|
||||||
|
</Form>
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Main)
|
||||||
|
|
54
src/common/components/MainControls.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Button, Icon, Segment} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
import * as Actions from '../actions'
|
||||||
|
import * as Selectors from '../selectors'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
format: state.format,
|
||||||
|
isMarkdownFormat: Selectors.isMarkdownFormat(state),
|
||||||
|
saveStatus: state.saveStatus
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onSetPlaintextFormat: (format) => dispatch(Actions.setPlaintextFormat()),
|
||||||
|
onSetMarkdownFormat: (format) => dispatch(Actions.setMarkdownFormat()),
|
||||||
|
|
||||||
|
// returns an id for the record to be saved
|
||||||
|
onCompare: () => dispatch(Actions.compare())
|
||||||
|
})
|
||||||
|
|
||||||
|
class MainControls extends React.Component {
|
||||||
|
|
||||||
|
onClickMarkdownFormat () {
|
||||||
|
if (this.props.isMarkdownFormat) {
|
||||||
|
this.props.onSetPlaintextFormat()
|
||||||
|
} else {
|
||||||
|
this.props.onSetMarkdownFormat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<Segment.Group>
|
||||||
|
<Segment >
|
||||||
|
<Button fluid onClick={this.props.onCompare}>Compare</Button>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
|
<Segment >
|
||||||
|
<Button fluid active={this.props.isMarkdownFormat} type='submit' onClick={this.onClickMarkdownFormat.bind(this)}>
|
||||||
|
{this.props.isMarkdownFormat ? <Icon name='checkmark' /> : <span />}
|
||||||
|
As Markdown
|
||||||
|
</Button>
|
||||||
|
</Segment>
|
||||||
|
</Segment.Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MainControls)
|
||||||
|
|
||||||
|
/*
|
||||||
|
<a type="button" onClick={this.onClickCompare.bind(this)} className="btn btn-block btn-primary">compare</a> */
|
65
src/common/components/SaveStatus.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {Message, Icon, Button} from 'semantic-ui-react'
|
||||||
|
|
||||||
|
import * as Actions from '../actions'
|
||||||
|
import {Status, StatusError} from '../constants'
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
status: state.status
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onSave: () => dispatch(Actions.save()),
|
||||||
|
onReset: () => dispatch(Actions.reset())
|
||||||
|
})
|
||||||
|
|
||||||
|
const SaveStatus = (props) => {
|
||||||
|
if (props.status.type === Status.SAVING) {
|
||||||
|
return (
|
||||||
|
<Message size='tiny' floating compact icon>
|
||||||
|
<Icon name='circle notched' loading />
|
||||||
|
<Message.Content>
|
||||||
|
<Message.Header>Saving diff</Message.Header>
|
||||||
|
</Message.Content>
|
||||||
|
</Message>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (props.status.type === Status.LOADING) {
|
||||||
|
return (
|
||||||
|
<Message size='tiny' floating compact icon>
|
||||||
|
<Icon name='circle notched' loading />
|
||||||
|
<Message.Content>
|
||||||
|
<Message.Header>Loading diff</Message.Header>
|
||||||
|
</Message.Content>
|
||||||
|
</Message>
|
||||||
|
)
|
||||||
|
} else if (props.status.hasError && props.status.errorType === StatusError.SAVE_ERROR) {
|
||||||
|
return (
|
||||||
|
<Message size='tiny' floating compact icon>
|
||||||
|
<Icon name='exclamation' />
|
||||||
|
<Message.Content>
|
||||||
|
<Message.Header>Error saving diff</Message.Header>
|
||||||
|
{props.status.error.message}
|
||||||
|
<br />
|
||||||
|
<Button onClick={props.onSave}>Retry</Button>
|
||||||
|
</Message.Content>
|
||||||
|
</Message>
|
||||||
|
)
|
||||||
|
} else if (props.status.hasError && props.status.errorType === StatusError.LOAD_ERROR) {
|
||||||
|
return (
|
||||||
|
<Message size='tiny' floating compact icon>
|
||||||
|
<Icon name='exclamation' />
|
||||||
|
<Message.Content>
|
||||||
|
<Message.Header>Error loading diff</Message.Header>
|
||||||
|
{props.status.error.message}
|
||||||
|
<br />
|
||||||
|
<Button onClick={props.onReset}>New Diff</Button>
|
||||||
|
</Message.Content>
|
||||||
|
</Message>
|
||||||
|
)
|
||||||
|
} else return (<div />)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SaveStatus)
|
20
src/common/components/ShowMarkdown.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import markdownCompiler from 'markdown-to-jsx'
|
||||||
|
|
||||||
|
import {diffToHtml} from '../util/dubdiff'
|
||||||
|
|
||||||
|
const ShowMarkdown = (props) => {
|
||||||
|
return <div>
|
||||||
|
{
|
||||||
|
props.text
|
||||||
|
? markdownCompiler(props.text)
|
||||||
|
: props.diff
|
||||||
|
? markdownCompiler(diffToHtml(props.diff))
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShowMarkdown
|
||||||
|
|
26
src/common/components/ShowPlaintext.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const ShowPlaintext = (props) => {
|
||||||
|
return <div>
|
||||||
|
<pre style={{whiteSpace: 'pre-wrap'}}>
|
||||||
|
{props.text
|
||||||
|
? props.text
|
||||||
|
: props.diff
|
||||||
|
? diffToPre(props.diff)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShowPlaintext
|
||||||
|
|
||||||
|
function diffToPre (diff) {
|
||||||
|
return diff.map((part, index) => (
|
||||||
|
part.added
|
||||||
|
? <ins key={index}>{part.value}</ins>
|
||||||
|
: part.removed
|
||||||
|
? <del key={index}>{part.value}</del>
|
||||||
|
: <span key={index}>{part.value}</span>
|
||||||
|
))
|
||||||
|
}
|
24
src/common/constants.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export const Format = {
|
||||||
|
PLAINTEXT: 'PLAINTEXT',
|
||||||
|
MARKDOWN: 'MARKDOWN'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Show = {
|
||||||
|
ORIGINAL: 'ORIGINAL',
|
||||||
|
FINAL: 'FINAL',
|
||||||
|
DIFFERENCE: 'DIFFERENCE'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Status = {
|
||||||
|
INIT: 'INIT',
|
||||||
|
LOADING: 'LOADING',
|
||||||
|
EMPTY: 'EMPTY',
|
||||||
|
CLEAN: 'CLEAN',
|
||||||
|
DIRTY: 'DIRTY',
|
||||||
|
SAVING: 'SAVING'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusError = {
|
||||||
|
LOAD_ERROR: 'LOAD_ERROR',
|
||||||
|
SAVE_ERROR: 'SAVE_ERROR'
|
||||||
|
}
|
75
src/common/reducers.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import {Format, Show, Status, StatusError} from './constants'
|
||||||
|
|
||||||
|
export function input (state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'UPDATE_ORIGINAL_INPUT':
|
||||||
|
return Object.assign({}, state, {original: action.data})
|
||||||
|
case 'UPDATE_FINAL_INPUT':
|
||||||
|
return Object.assign({}, state, {final: action.data})
|
||||||
|
case 'CLEAR_INPUT':
|
||||||
|
return {original: '', final: ''}
|
||||||
|
default:
|
||||||
|
return state || {original: '', final: ''}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function format (state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_PLAINTEXT_FORMAT':
|
||||||
|
return Format.PLAINTEXT
|
||||||
|
case 'SET_MARKDOWN_FORMAT':
|
||||||
|
return Format.MARKDOWN
|
||||||
|
default:
|
||||||
|
return state || Format.PLAINTEXT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function show (state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SHOW_ORIGINAL':
|
||||||
|
return Show.ORIGINAL
|
||||||
|
case 'SHOW_FINAL':
|
||||||
|
return Show.FINAL
|
||||||
|
case 'SHOW_DIFFERENCE':
|
||||||
|
return Show.DIFFERENCE
|
||||||
|
default:
|
||||||
|
return state || Show.DIFFERENCE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
export function saveStatus (state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SAVE_STATUS_DIRTY':
|
||||||
|
return {dirty: true}
|
||||||
|
case 'SAVE_STATUS_EMPTY':
|
||||||
|
return {dirty: false, empty: true}
|
||||||
|
case 'SAVE_STATUS_SAVED':
|
||||||
|
return {dirty: false, saved: true}
|
||||||
|
case 'SAVE_STATUS_FAILED' :
|
||||||
|
return Object.assign({}, state, {waiting: false, failed: true, error: action.error})
|
||||||
|
case 'SAVE_STATUS_WAITING' :
|
||||||
|
return Object.assign({}, state, {waiting: true, failed: false, error: null})
|
||||||
|
default:
|
||||||
|
return state || {empty: true, dirty:false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// tracks status of the app, especially with respect to loaded and saved user data
|
||||||
|
export function status (state, action) {
|
||||||
|
// the status or error type is valid if it is in the list of Status or StatusError types
|
||||||
|
const isValidStatus = (type) => Status[type] === type
|
||||||
|
const isValidError = (type) => StatusError[type] === type
|
||||||
|
|
||||||
|
// the error is cleared when status changes
|
||||||
|
if (action.type === 'STATUS_SET' && isValidStatus(action.data)) {
|
||||||
|
return {type: action.data, error: null, hasError: false, errorType: null}
|
||||||
|
}
|
||||||
|
// the error is set in addition to the status
|
||||||
|
else if (action.type === 'STATUS_SET_ERROR' && isValidError(action.data)) {
|
||||||
|
return Object.assign({}, state, {error: action.error, hasError: true, errorType: action.data})
|
||||||
|
} else {
|
||||||
|
return state || {type: Status.EMPTY, hasError: false, error: null}
|
||||||
|
}
|
||||||
|
}
|
13
src/common/routes.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {Route} from 'react-router'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Main from './components/Main'
|
||||||
|
import Compare from './components/Compare'
|
||||||
|
|
||||||
|
var routes = [
|
||||||
|
<Route key='root' path='/' component={Main} />,
|
||||||
|
<Route key='compare' path='/:compareId' component={Compare} />
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
59
src/common/selectors.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// per http://redux.js.org/docs/recipes/ComputingDerivedData.html
|
||||||
|
|
||||||
|
import { createSelector } from 'reselect'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {Format, Show} from './constants'
|
||||||
|
|
||||||
|
import * as Dubdiff from './util/dubdiff'
|
||||||
|
|
||||||
|
const input = (state) => state.input
|
||||||
|
const format = (state) => state.format
|
||||||
|
const show = (state) => state.show
|
||||||
|
|
||||||
|
export const safeInput = createSelector(
|
||||||
|
[input],
|
||||||
|
(input) => {
|
||||||
|
//! !! sanitize the input here and return
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const isMarkdownFormat = createSelector(
|
||||||
|
[format],
|
||||||
|
(format) => {
|
||||||
|
return format === Format.MARKDOWN
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const isShow = (type) => createSelector(
|
||||||
|
[show],
|
||||||
|
(show) => {
|
||||||
|
return show === type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const isShowOriginal = isShow(Show.ORIGINAL)
|
||||||
|
export const isShowFinal = isShow(Show.FINAL)
|
||||||
|
export const isShowDifference = isShow(Show.DIFFERENCE)
|
||||||
|
|
||||||
|
export const diff = createSelector(
|
||||||
|
[format, safeInput],
|
||||||
|
(format, safeInput) => {
|
||||||
|
if (format === Format.PLAINTEXT) {
|
||||||
|
return Dubdiff.plaintextDiff(safeInput.original, safeInput.final)
|
||||||
|
} else if (format === Format.MARKDOWN) {
|
||||||
|
return Dubdiff.markdownDiff(safeInput.original, safeInput.final)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
html diff
|
||||||
|
---
|
||||||
|
|
||||||
|
diffHtml(parentOriginal, parentFinal) {
|
||||||
|
create stringOriginal, stringFinal consisting of
|
||||||
|
}
|
||||||
|
*/
|
59
src/common/util/EditorsDiff.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
import {Diff} from 'diff'
|
||||||
|
|
||||||
|
// EditorsDiff is a custom Diff implementation from the jsdiff library
|
||||||
|
// It allows diffing by phrases. Whitespace is ignored for the purpose of comparison,
|
||||||
|
// but is preserved and included in the output.
|
||||||
|
|
||||||
|
const TOKEN_BOUNDARYS = /([\s,.:])/
|
||||||
|
|
||||||
|
class EditorsDiff extends Diff {
|
||||||
|
constructor (tokenBoundaries = TOKEN_BOUNDARYS) {
|
||||||
|
super()
|
||||||
|
this.tokenBoundaries = tokenBoundaries
|
||||||
|
}
|
||||||
|
|
||||||
|
equals (left, right) {
|
||||||
|
return (
|
||||||
|
left.string === right.string
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// splits the input string into a series of word and punctuation tokens
|
||||||
|
// each token is associated with an optional trailing array of spaces
|
||||||
|
tokenize (value) {
|
||||||
|
let tokens = value.split(this.tokenBoundaries)
|
||||||
|
let annotatedTokens = []
|
||||||
|
tokens.forEach(token => {
|
||||||
|
if (isSpace(token)) {
|
||||||
|
if (annotatedTokens.length === 0) {
|
||||||
|
annotatedTokens.push({string: '', whitespace: []})
|
||||||
|
}
|
||||||
|
|
||||||
|
let last = annotatedTokens[annotatedTokens.length - 1]
|
||||||
|
last.whitespace.push(token)
|
||||||
|
} else {
|
||||||
|
annotatedTokens.push({string: token, whitespace: []})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// this final empty token is necessary for the jsdiff diffing engine to work properly
|
||||||
|
annotatedTokens.push({string: '', whitespace: []})
|
||||||
|
return annotatedTokens
|
||||||
|
}
|
||||||
|
join (annotatedTokens) {
|
||||||
|
let tokens = []
|
||||||
|
annotatedTokens.forEach(annotatedToken => {
|
||||||
|
tokens.push(annotatedToken.string)
|
||||||
|
annotatedToken.whitespace.forEach(item => {
|
||||||
|
tokens.push(item)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return tokens.join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorsDiff
|
||||||
|
|
||||||
|
const isSpace = str => /[ ]+/.test(str)
|
||||||
|
const isNewline = str => /[\n]+/.test(str)
|
235
src/common/util/dubdiff.js
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import EditorsDiff from './EditorsDiff'
|
||||||
|
|
||||||
|
let plaintextDiffer = new EditorsDiff()
|
||||||
|
let markdownDiffer = new EditorsDiff(/([\s,.:]|[*\[\]()])/)
|
||||||
|
|
||||||
|
// returns a comparison of the texts as plaintext
|
||||||
|
export function plaintextDiff (original, final) {
|
||||||
|
let diff = plaintextDiffer.diff(original, final)
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a comparison of the texts as markdown
|
||||||
|
export function markdownDiff (original, final) {
|
||||||
|
let diff = markdownDiffer.diff(original, final)
|
||||||
|
diff = rewriteMarkdownDiff(diff)
|
||||||
|
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a string version of the diff, with "{+ ... +}" and "[- ... -]"
|
||||||
|
// representing ins and del blocks
|
||||||
|
export function diffToString (diff, tags = {added: {start: '{+', end: '+}'}, removed: {start: '[-', end: '-]'}, same: {start: '', end: ''}}) {
|
||||||
|
return diff.map(({added, removed, value}) => {
|
||||||
|
let {start, end} = added ? tags.added : (removed ? tags.removed : tags.same)
|
||||||
|
|
||||||
|
let string = value
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
string = value.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return start + string + end
|
||||||
|
}).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function diffToHtml (diff) {
|
||||||
|
return diffToString(diff, {added: {start: '<ins>', end: '</ins>'}, removed: {start: '<del>', end: '</del>'}, same: {start: '', end: ''}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrites the given diff to correctly render as markdown, assuming the source
|
||||||
|
// documents were also valid markdown.
|
||||||
|
// In essence, moves the markdown formatting elements in or out of the inserted and deleted blocks, as appropriate
|
||||||
|
|
||||||
|
// rules:
|
||||||
|
|
||||||
|
// 1. if a multiline del block is followed by an ins block,
|
||||||
|
// the first line of the ins block should be inserted at the end of the first line of the del block
|
||||||
|
// so the markdown will apply to the ins text as it should
|
||||||
|
// 2. multiline ins and del blocks should be broken up into a series of single line blocks
|
||||||
|
// 3. after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
||||||
|
// then that prefix should be moved out of the block
|
||||||
|
|
||||||
|
// not yet implemented rules:
|
||||||
|
// 3. if an ins or del block spans one half of a bold, italic or link string
|
||||||
|
// eg. **Hello <del>World** I</del><ins>Darling** she</ins> said
|
||||||
|
// the block should be broken up to move the formatting code outside
|
||||||
|
// OR the whole formatting string could be brought into the block
|
||||||
|
// eg. **Hello <del>World</del><ins>Darling</ins>** <ins>I</ins><del>she</del> said
|
||||||
|
function rewriteMarkdownDiff (diff) {
|
||||||
|
// apply transformation rules
|
||||||
|
let transformedDiff = diff
|
||||||
|
transformedDiff = applyTransformationRuleMultilineDelThenIns(transformedDiff)
|
||||||
|
transformedDiff = applyTransformationRuleBreakUpDelIns(transformedDiff)
|
||||||
|
transformedDiff = applyTransformationRuleFormattingPrefix(transformedDiff)
|
||||||
|
transformedDiff = applyTransformationRuleRemoveEmpty(transformedDiff)
|
||||||
|
return transformedDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformation rule 1
|
||||||
|
// 1. if a multiline del block is followed by an ins block,
|
||||||
|
// the first line of the ins block should be inserted at the end of the first line of the del block
|
||||||
|
// so the markdown will apply to the ins text as it should
|
||||||
|
function applyTransformationRuleMultilineDelThenIns (diff) {
|
||||||
|
let transformedDiff = []
|
||||||
|
|
||||||
|
const B_ADDED = 'added'
|
||||||
|
const B_REMOVED = 'removed'
|
||||||
|
const B_SAME = 'same'
|
||||||
|
|
||||||
|
let previousBlockType = null
|
||||||
|
let currentBlockType = null
|
||||||
|
let previousBlockWasMultiline = false
|
||||||
|
let currentBlockIsMultiline = false
|
||||||
|
|
||||||
|
// iterate the input tokens to create the intermediate representation
|
||||||
|
diff.forEach((currentBlock) => {
|
||||||
|
previousBlockType = currentBlockType
|
||||||
|
previousBlockWasMultiline = currentBlockIsMultiline
|
||||||
|
currentBlockType = (currentBlock.added ? B_ADDED : (currentBlock.removed ? B_REMOVED : B_SAME))
|
||||||
|
currentBlockIsMultiline = isMultilineDiffBlock(currentBlock)
|
||||||
|
|
||||||
|
// transform rule 1 applys when:
|
||||||
|
// the previous block was a del and had multiple lines
|
||||||
|
// the current block is an ins
|
||||||
|
if (previousBlockType === B_REMOVED && currentBlockType === B_ADDED && previousBlockWasMultiline) {
|
||||||
|
// split the first line from the current block
|
||||||
|
let currentBlockSplit = splitMultilineDiffBlock(currentBlock)
|
||||||
|
|
||||||
|
// pop the previous diff entry
|
||||||
|
let previousBlock = transformedDiff.pop()
|
||||||
|
|
||||||
|
// split the first line from the previous block
|
||||||
|
let previousBlockSplit = splitMultilineDiffBlock(previousBlock)
|
||||||
|
|
||||||
|
// now add the blocks back, interleaving del and ins blocks
|
||||||
|
for (let i = 0; i < Math.max(previousBlockSplit.length, currentBlockSplit.length); i++) {
|
||||||
|
if (i < previousBlockSplit.length) {
|
||||||
|
transformedDiff.push(previousBlockSplit[i])
|
||||||
|
}
|
||||||
|
if (i < currentBlockSplit.length) { transformedDiff.push(currentBlockSplit[i]) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise, we just add the current block to the transformed list
|
||||||
|
transformedDiff.push(currentBlock)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return transformedDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformation rule 2
|
||||||
|
// 2. multiline del and ins blocks should be broken up
|
||||||
|
// into a series of single line blocks
|
||||||
|
function applyTransformationRuleBreakUpDelIns (diff) {
|
||||||
|
let transformedDiff = []
|
||||||
|
|
||||||
|
const B_ADDED = 'added'
|
||||||
|
const B_REMOVED = 'removed'
|
||||||
|
const B_SAME = 'same'
|
||||||
|
let blockType = null
|
||||||
|
let blockIsMultiline = false
|
||||||
|
|
||||||
|
// iterate the input tokens to create the intermediate representation
|
||||||
|
diff.forEach((block) => {
|
||||||
|
blockType = (block.added ? B_ADDED : (block.removed ? B_REMOVED : B_SAME))
|
||||||
|
blockIsMultiline = isMultilineDiffBlock(block)
|
||||||
|
|
||||||
|
// transform rule applys when:
|
||||||
|
// the current block is an ins or del and is multiline
|
||||||
|
if ((blockType === B_REMOVED || blockType === B_ADDED) && blockIsMultiline) {
|
||||||
|
// split the first line from the current block
|
||||||
|
let blockSplit = splitMultilineDiffBlock(block)
|
||||||
|
|
||||||
|
blockSplit.forEach(blockSplitLine => transformedDiff.push(blockSplitLine))
|
||||||
|
} else {
|
||||||
|
// otherwise, we just add the current block to the transformed list
|
||||||
|
transformedDiff.push(block)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return transformedDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformation rule number 4: remove empty blocks
|
||||||
|
function applyTransformationRuleRemoveEmpty (diff) {
|
||||||
|
return diff.filter(({value}) => value.length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches markdown prefixes that affect the formatting of the whole subsequent line
|
||||||
|
// ^ - start of line
|
||||||
|
// ([ \t]*\>)* - blockquotes (possibly nested)
|
||||||
|
// (
|
||||||
|
// ([ \t]*#*) - headers
|
||||||
|
// |([ \t]+[\*\+-]) - unordered lists
|
||||||
|
// |([ \t]+[0-9]+\.) - numeric lists
|
||||||
|
// )?
|
||||||
|
// [ \t]+ - trailing whitespace
|
||||||
|
const MARKDOWN_PREFIX = /^([ \t]*>)*(([ \t]*#*)|([ \t]*[*+\-])|([ \t]*[\d]+\.))?[ \t]+/
|
||||||
|
|
||||||
|
// matches strings that end with a newline followed by some whitespace
|
||||||
|
const NEWLINE_SUFFIX = /\n\s*$/
|
||||||
|
|
||||||
|
// transformation rule 3:
|
||||||
|
// after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
||||||
|
// then that prefix should be moved out of the block
|
||||||
|
// also, if an ins block begins with a formatting prefix and follows immediately after a del block that follows a newline,
|
||||||
|
// the prefix should be moved out of the block _and_ an extra newline character should be added to the beginning of it
|
||||||
|
function applyTransformationRuleFormattingPrefix (diff) {
|
||||||
|
let transformedDiff = []
|
||||||
|
|
||||||
|
let isNewline = true
|
||||||
|
let newlineString = '\n'
|
||||||
|
|
||||||
|
// iterate the input tokens to create the intermediate representation
|
||||||
|
diff.forEach((currentBlock) => {
|
||||||
|
if (isNewline && (currentBlock.added || currentBlock.removed)) {
|
||||||
|
let match = currentBlock.value.match(MARKDOWN_PREFIX)
|
||||||
|
if (match) {
|
||||||
|
let preBlock = {value: match[0]}
|
||||||
|
let postBlock = {added: currentBlock.added, removed: currentBlock.removed, value: currentBlock.value.substring(match[0].length)}
|
||||||
|
|
||||||
|
if (currentBlock.added) {
|
||||||
|
let newlineBlock = {value: newlineString}
|
||||||
|
transformedDiff.push(newlineBlock)
|
||||||
|
}
|
||||||
|
transformedDiff.push(preBlock)
|
||||||
|
transformedDiff.push(postBlock)
|
||||||
|
} else {
|
||||||
|
transformedDiff.push(currentBlock)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transformedDiff.push(currentBlock)
|
||||||
|
isNewline = NEWLINE_SUFFIX.test(currentBlock.value)
|
||||||
|
if (isNewline) {
|
||||||
|
newlineString = currentBlock.value.match(NEWLINE_SUFFIX)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return transformedDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the given diff block contains a newline element
|
||||||
|
function isMultilineDiffBlock ({value}) {
|
||||||
|
return value.indexOf('\n') !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an array of diff blocks that have the same added, removed fields as the given one
|
||||||
|
// but with the string split by newlines
|
||||||
|
// if the diff block has no newlines, an array containing only that diff will be returned
|
||||||
|
// if the diff block has newlines, the resulting array will have a series of blocks,
|
||||||
|
// consisting of the block text, interleaved with newlines
|
||||||
|
// ,
|
||||||
|
// each of which begin with a newline
|
||||||
|
// if the diff block begins with a newline, the returned array will begin with an empty diff
|
||||||
|
function splitMultilineDiffBlock ({added, removed, value}) {
|
||||||
|
let lines = value.split('\n')
|
||||||
|
let blocks = []
|
||||||
|
// lines = lines.filter(line=>line.length>0)
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
blocks.push({added, removed, value: line})
|
||||||
|
if (index < lines.length - 1) blocks.push({value: '\n'})
|
||||||
|
})
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
}
|
37
src/server/babel.index.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
var Path = require('path')
|
||||||
|
|
||||||
|
var srcRoot = Path.join(__dirname, '..')
|
||||||
|
|
||||||
|
// there should be some option for distribution / optimization?
|
||||||
|
var config = {
|
||||||
|
presets: ['node6', 'react'],
|
||||||
|
// enable source maps for non-production instances
|
||||||
|
sourceMaps: (process.env.NODE_ENV !== 'production' ? 'both' : false),
|
||||||
|
// highlightCode: false,
|
||||||
|
sourceRoot: srcRoot,
|
||||||
|
only: /src/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
require('babel-core/register')(config)
|
||||||
|
|
||||||
|
var piping = require('piping')
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
function main () {
|
||||||
|
// Enable piping for non-production environments
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// piping will return false for the initial invocation
|
||||||
|
// the app will be run again in an instance managed by piping
|
||||||
|
if (!piping({hook: true, includeModules: false})) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require('./index.js')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.stack)
|
||||||
|
}
|
||||||
|
}
|
88
src/server/comparison.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import jf from 'jsonfile'
|
||||||
|
import fs from 'fs'
|
||||||
|
import uuid from 'uuid'
|
||||||
|
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
router.get('/:id', showComparison)
|
||||||
|
router.post('/:id', createComparisonWithId)
|
||||||
|
router.post('/', createComparison)
|
||||||
|
|
||||||
|
// return the comparison given an id, if it exsits
|
||||||
|
function showComparison (req, res) {
|
||||||
|
const id = req.params.id
|
||||||
|
return readRecord(res, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new comparison
|
||||||
|
function createComparison (req, res) {
|
||||||
|
// generate a new id
|
||||||
|
const id = uuid()
|
||||||
|
const {a, b} = req.body
|
||||||
|
|
||||||
|
return writeRecord(res, id, {a, b, id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new comparison
|
||||||
|
function createComparisonWithId (req, res) {
|
||||||
|
// use the id provided in the req
|
||||||
|
const id = req.params.id
|
||||||
|
const {a, b} = req.body
|
||||||
|
|
||||||
|
return writeRecord(res, id, {a, b, id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// reads the record from the database
|
||||||
|
function readRecord (res, id, data) {
|
||||||
|
// generate a filename
|
||||||
|
const filename = fnData(id)
|
||||||
|
|
||||||
|
// check if that file exists
|
||||||
|
fs.exists(filename, function (exists) {
|
||||||
|
// if the file does not exist, return a 404
|
||||||
|
if (!exists) return res.status(404).send(`Data id ${id} not found.`)
|
||||||
|
|
||||||
|
// otherwise, read the file as JSON
|
||||||
|
jf.readFile(filename, function (err, data) {
|
||||||
|
if (err) { return handleError(res, err) }
|
||||||
|
|
||||||
|
// and return
|
||||||
|
return res.json(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// writes the record to the database, if it doesn't exist
|
||||||
|
function writeRecord (res, id, data) {
|
||||||
|
// look up its filename
|
||||||
|
var filename = fnData(id)
|
||||||
|
|
||||||
|
// need to test that the file does not exist
|
||||||
|
|
||||||
|
// check if that file exists
|
||||||
|
fs.exists(filename, (exists) => {
|
||||||
|
// if the file already exists, return a 405
|
||||||
|
if (exists) return res.status(405).send(`Data id ${id} is already in use.`)
|
||||||
|
|
||||||
|
// and write it to the filesystem
|
||||||
|
jf.writeFile(filename, data, (err) => (
|
||||||
|
err
|
||||||
|
? handleError(res, err)
|
||||||
|
// if successful, return the comparison object
|
||||||
|
: res.status(201).json(data)
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router
|
||||||
|
|
||||||
|
function handleError (res, err) {
|
||||||
|
console.log(err)
|
||||||
|
return res.send(500, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a filename for the given comparison
|
||||||
|
function fnData (id) {
|
||||||
|
return `./data/${id}.json`
|
||||||
|
}
|
83
src/server/index.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import path from 'path'
|
||||||
|
import bodyParser from 'body-parser'
|
||||||
|
import * as Redux from 'redux'
|
||||||
|
|
||||||
|
import fetch from 'isomorphic-fetch'
|
||||||
|
|
||||||
|
import comparisonRouter from './comparison'
|
||||||
|
|
||||||
|
import * as reducers from '../common/reducers'
|
||||||
|
|
||||||
|
import {Status, StatusError} from '../common/constants'
|
||||||
|
|
||||||
|
import render from './render'
|
||||||
|
|
||||||
|
// set use port 8080 for dev, 80 for production
|
||||||
|
const PORT = (process.env.NODE_ENV !== 'production' ? 8080 : 80)
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// serve the dist static files at /dist
|
||||||
|
app.use('/dist', express.static(path.join(__dirname, '..', '..', 'dist')))
|
||||||
|
|
||||||
|
// serve the comparison api at /api/compare
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
app.use('/api/compare', comparisonRouter)
|
||||||
|
|
||||||
|
// the following routes are for server-side rendering of the app
|
||||||
|
// we should render the comparison directly from the server
|
||||||
|
// this loading logic could be moved into ../common/actions because it is isomorphic
|
||||||
|
app.route('/:comparisonId')
|
||||||
|
.get((req, res) => {
|
||||||
|
const store = createSessionStore()
|
||||||
|
const endpointUri = `http://localhost:${PORT}/api/compare/${req.params.comparisonId}`
|
||||||
|
|
||||||
|
// fetch the comparison
|
||||||
|
fetch(endpointUri)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json()
|
||||||
|
} else {
|
||||||
|
response.text().then(() => {
|
||||||
|
const error = {message: `${response.status}: ${response.statusText}`}
|
||||||
|
initAndRenderError(error, store, req, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(({a, b}) => {
|
||||||
|
initAndRenderComparison({a, b}, store, req, res)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
initAndRenderError(error, store, req, res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
app.route('/')
|
||||||
|
.get((req, res) => {
|
||||||
|
render(createSessionStore(), req, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(PORT, function () {
|
||||||
|
console.log(`Server listening on port ${PORT}.`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// creates the session store
|
||||||
|
function createSessionStore () {
|
||||||
|
// create the redux store
|
||||||
|
return Redux.createStore(
|
||||||
|
Redux.combineReducers(reducers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAndRenderComparison ({a, b}, store, req, res) {
|
||||||
|
store.dispatch({type: 'UPDATE_ORIGINAL_INPUT', data: a})
|
||||||
|
store.dispatch({type: 'UPDATE_FINAL_INPUT', data: b})
|
||||||
|
store.dispatch({type: 'STATUS_SET', data: Status.CLEAN})
|
||||||
|
render(store, req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAndRenderError (error, store, req, res) {
|
||||||
|
store.dispatch({type: 'STATUS_SET', data: Status.EMPTY})
|
||||||
|
store.dispatch({type: 'STATUS_SET_ERROR', data: StatusError.LOAD_ERROR, error})
|
||||||
|
render(store, req, res)
|
||||||
|
}
|
83
src/server/render.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { match, RouterContext } from 'react-router'
|
||||||
|
|
||||||
|
import routes from '../common/routes.js'
|
||||||
|
|
||||||
|
export default function render (store, req, res) {
|
||||||
|
// Send the rendered page back to the client
|
||||||
|
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
|
||||||
|
if (error) {
|
||||||
|
res.status(500).send(errorTemplate('Routing Error:', error.message))
|
||||||
|
} else if (redirectLocation) {
|
||||||
|
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
|
||||||
|
} else if (renderProps) {
|
||||||
|
// Render the component to a string
|
||||||
|
try {
|
||||||
|
const html = renderToString(
|
||||||
|
<Provider store={store}>
|
||||||
|
<RouterContext {...renderProps} />
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Grab the initial state from our Redux store
|
||||||
|
const initialState = store.getState()
|
||||||
|
// and send
|
||||||
|
res.status(200).send(appTemplate(html, initialState))
|
||||||
|
} catch (ex) {
|
||||||
|
console.log('Render Exception:', ex)
|
||||||
|
res.status(500).send(errorTemplate('Render Exception', ex.message, ex))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404).send(errorTemplate('Not found', `${req.url} not found.`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageTemplate = (body) => {
|
||||||
|
return `
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Dubdiff</title>
|
||||||
|
|
||||||
|
<!-- CSS -->
|
||||||
|
<link rel="stylesheet" href="dist/semantic.min.css"/>
|
||||||
|
<link rel="stylesheet" href="dist/main.css"/>
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="dist/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="dist/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="dist/favicon-16x16.png">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorTemplate (title, message, exception) {
|
||||||
|
return pageTemplate(`
|
||||||
|
<h1>${title}</h1>
|
||||||
|
<p>${message}</p>
|
||||||
|
${exception
|
||||||
|
? `<pre>${exception.toString()}</pre>`
|
||||||
|
: ``
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function appTemplate (html, initialState) {
|
||||||
|
return pageTemplate(`
|
||||||
|
<div id="root">${html}</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.__INITIAL_STATE__ = "${encodeURI(JSON.stringify(initialState, null, 2))}"
|
||||||
|
</script>
|
||||||
|
<!-- <script>__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__</script> -->
|
||||||
|
<script type="text/javascript" src="dist/browser-bundle.js"></script>
|
||||||
|
`)
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
lorem ipsum
|
|
@ -1 +0,0 @@
|
|||||||
lorum ipsum
|
|
85
test/dubdiffMarkdown.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* eslint-env node, mocha */
|
||||||
|
/* global expect */
|
||||||
|
/* eslint no-console: 0 */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import chai from 'chai'
|
||||||
|
|
||||||
|
import {markdownDiff, diffToString} from '../src/common/util/dubdiff'
|
||||||
|
|
||||||
|
let diff = (a, b) => diffToString(markdownDiff(a, b))
|
||||||
|
|
||||||
|
const expect = chai.expect // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
describe('dubdiff', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
it('plaintext diffs consecutive words', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This is a smlb sentnce with no errors.',
|
||||||
|
'This is a simple sentence with no errors.'
|
||||||
|
)).to.equal('This is a [-smlb sentnce -]{+simple sentence +}with no errors.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('plaintext diffs with word deletion', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Gonna delete a word.',
|
||||||
|
'Gonna delete word.'
|
||||||
|
)).to.equal('Gonna delete [-a -]word.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('plaintext diffs with word insertion', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Gonna delete word.',
|
||||||
|
'Gonna delete a word.'
|
||||||
|
)).to.equal('Gonna delete {+a +}word.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reorganizes insertions after multiline deletions', () => {
|
||||||
|
expect(diff(
|
||||||
|
`# Title
|
||||||
|
other`,
|
||||||
|
`# Subtitle`
|
||||||
|
)).to.equal('# [-Title-]{+Subtitle+}\n[-other-]')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pulls prefixes out of ins or del blocks after newline', () => {
|
||||||
|
expect(diff(
|
||||||
|
'# Title\n > hello',
|
||||||
|
'# Title\n - goodbye'
|
||||||
|
)).to.equal('# Title\n > [-hello-]\n - {+goodbye+}')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respects bold and italic boundaries', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This *word* **isn\'t** changed.',
|
||||||
|
'This *other one* **is** changed.'
|
||||||
|
)).to.equal('This *[-word-]{+other one+}* **[-isn\'t-]{+is+}** changed.')
|
||||||
|
})
|
||||||
|
it('respects link boundaries in link text', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This [link](https://somewhere.com) is the same.',
|
||||||
|
'This [target](https://somewhere.com) changed.'
|
||||||
|
)).to.equal('This [[-link-]{+target+}](https://somewhere.com) [-is the same-]{+changed+}.')
|
||||||
|
})
|
||||||
|
it('respects link boundaries in link target', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This [link](https://somewhere.com) is the same.',
|
||||||
|
'This [link](https://somewhere.org) changed.'
|
||||||
|
)).to.equal('This [link](https://somewhere.[-com-]{+org+}) [-is the same-]{+changed+}.')
|
||||||
|
})
|
||||||
|
it('deletes a title', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Hello\n# Title 1\n# Title 2',
|
||||||
|
'Hello\n# Title 2'
|
||||||
|
)).to.equal('Hello\n# Title [-1-]\n# [-Title -]2')
|
||||||
|
})
|
||||||
|
it('deletes a more different title', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Hello\n# Filbert\n# Title 2',
|
||||||
|
'Hello\n# Title 2'
|
||||||
|
)).to.equal('Hello\n# [-Filbert-]\n# Title 2')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
68
test/dubdiffPlaintext.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/* eslint-env node, mocha */
|
||||||
|
/* global expect */
|
||||||
|
/* eslint no-console: 0 */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
import chai from 'chai'
|
||||||
|
|
||||||
|
import {plaintextDiff, diffToString} from '../src/common/util/dubdiff'
|
||||||
|
|
||||||
|
let diff = (a, b) => diffToString(plaintextDiff(a, b))
|
||||||
|
|
||||||
|
const expect = chai.expect // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
describe('dubdiff', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
it('diffs single words', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This is a smlb sentence.',
|
||||||
|
'This is a simple sentence.'
|
||||||
|
)).to.equal('This is a [-smlb -]{+simple +}sentence.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('diffs consecutive words', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This is a smlb sentnce with no errors.',
|
||||||
|
'This is a simple sentence with no errors.'
|
||||||
|
)).to.equal('This is a [-smlb sentnce -]{+simple sentence +}with no errors.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('diffs with word deletion', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Gonna delete a word.',
|
||||||
|
'Gonna delete word.'
|
||||||
|
)).to.equal('Gonna delete [-a -]word.')
|
||||||
|
})
|
||||||
|
it('diffs with word insertion', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Gonna add word.',
|
||||||
|
'Gonna add a word.'
|
||||||
|
)).to.equal('Gonna add {+a +}word.')
|
||||||
|
})
|
||||||
|
it('diffs accross newline without weird spaces', () => {
|
||||||
|
expect(diff(
|
||||||
|
'This is a flawed\ncomment',
|
||||||
|
'This is a corrected\nitem'
|
||||||
|
)).to.equal('This is a [-flawed-]{+corrected+}\n[-comment-]{+item+}')
|
||||||
|
})
|
||||||
|
it('doesn\'t add spaces after newline', () => {
|
||||||
|
expect(diff(
|
||||||
|
'\nhere',
|
||||||
|
'\nhere'
|
||||||
|
)).to.equal('\nhere')
|
||||||
|
})
|
||||||
|
it('doesn\'t add spaces before newline', () => {
|
||||||
|
expect(diff(
|
||||||
|
'there\n',
|
||||||
|
'there\n'
|
||||||
|
)).to.equal('there\n')
|
||||||
|
})
|
||||||
|
it('treats punctuation separately', () => {
|
||||||
|
expect(diff(
|
||||||
|
'Hello world.',
|
||||||
|
'Hello, world.'
|
||||||
|
)).to.equal('Hello{+, +}world.')
|
||||||
|
})
|
||||||
|
})
|
35
webpack.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
let config = {
|
||||||
|
cache: true,
|
||||||
|
entry: './src/client/index.js',
|
||||||
|
output: {
|
||||||
|
filename: './dist/browser-bundle.js'
|
||||||
|
},
|
||||||
|
target: 'web',
|
||||||
|
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
query: {
|
||||||
|
presets: ['es2015-native-modules', 'react'],
|
||||||
|
compact: 'true'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ test: /\.json$/, loader: 'json-loader' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fs: 'empty',
|
||||||
|
net: 'empty',
|
||||||
|
tls: 'empty'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
config.devtool = 'cheap-module-source-map'
|
||||||
|
} else {
|
||||||
|
config.devtool = 'eval'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|