AC-564 implemented angular-codemirror to provide JSON/YAML syntax highlighting and linting in a full-screen editor.

This commit is contained in:
Chris Houseknecht
2014-02-15 19:23:57 -05:00
parent e4d860e3dc
commit cc9869b198
408 changed files with 77383 additions and 34385 deletions

View File

@@ -0,0 +1,25 @@
{
"name": "angular-codemirror",
"version": "1.0.1",
"dependencies": {
"angular": "latest",
"angular-route": "latest",
"jquery": "latest",
"jqueryui": "latest",
"components-font-awesome": "latest",
"twitter": "latest",
"js-yaml": "latest",
"jsonlint": "latest",
"codemirror": "latest"
},
"homepage": "https://github.com/chouseknecht/angular-codemirror",
"_release": "1.0.1",
"_resolution": {
"type": "version",
"tag": "v1.0.1",
"commit": "9a3d595de6b18faeb0834074f73bde476e4af9ec"
},
"_source": "git://github.com/chouseknecht/angular-codemirror.git",
"_target": "~1.0.0",
"_originalSource": "angular-codemirror"
}

View File

@@ -0,0 +1,2 @@
bower_components/
node_modules/

View File

@@ -0,0 +1,21 @@
{
// Details: https://github.com/victorporof/Sublime-JSHint#using-your-own-jshintrc-options
// Example: https://github.com/jshint/jshint/blob/master/examples/.jshintrc
// Documentation: http://www.jshint.com/docs/
"browser": true,
"jquery": true,
"esnext": true,
"globalstrict": true,
"globals": { "angular":false, "alert":true, "CodeMirror":false, "jsyaml":false },
"strict": false,
"quotmark": false,
"smarttabs": true,
"trailing": true,
"undef": true,
"unused": true,
"eqeqeq": true,
"indent": 4,
"onevar": true,
"newcap": false
}

View File

@@ -0,0 +1,24 @@
AngularCodeMirror
================
Copyright (c) 2014 Chris Houseknecht
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of angular-codemirror and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,29 @@
AngularCodeMirror
=================
Incorporate [CodeMirror](http://www.codemirror.net) into your AngularJS app. Presents the editor in a resizable, draggable modal dialog styled with Twitter Bootstrap. Pass in any valid CodeMirror options to make the editor fit your app needs.
Installation:
-------------
bower install angular-codemirror
Example App:
------------
With [Node.js](http://nodejs.org) installed, you can run the sample app locally. Clone the repo to a local projects directory, install package dependencies, and then run with the included server:
cd projects
git clone git@github.com:chouseknecht/angular-codemirror.git
cd angular-codemirror
bower install
node ./scripts/web-server.js
Point your browser to http://localhost:8000/app/index.html. Click the code editor link.
How To:
-------
If you installed with Bower, then all the dependencies will exist in bower_components. See app/index.html for a template of how to include all the needed .js and .css files. If you want to install dependencies manually, review bower.json for a list of what's needed.
Check the CodeMirror documentation to see what needs to be included for the mode and options you choose. Again, if you installed with Bower, then everything you need should be found under bower_components.
Incorporate into your Angular app by following the example in app/js/sampleApp.js.

View File

@@ -0,0 +1,68 @@
/*********************************************
* Copyright (c) 2013-2014 Chris Houseknecht
*
* SampleForm.js
*
* Demonstrate some of the things you can do with angular-forms.js to
* generate clean, consistent forms in your app.
*
*/
body {
padding-bottom: 80px;
}
#parse-type-group {
margin: 15px 0;
}
#parse-type-label {
display: inline-block;
padding-right: 10px;
font-weight: bold;
}
textarea {
resize: none;
}
a,
a:active,
a:link,
a:hover {
text-decoration: none;
}
.external-editor-link {
display: inline-block;
margin-left: 20px;
}
/*.ui-dialog-titlebar-close {
color: #000;
border: none;
background-color: transparent;
}*/
.red-txt {
color: #dd1b16;
}
.navbar-default .navbar-brand {
font-size: 24px;
color: #000;
}
.navigation {
margin-top: 20px;
padding-right: 15px;
}
.footer {
margin-top: 30px;
}
.footer .navbar-brand {
margin-bottom: 20px;
padding-right: 0;
}

View File

@@ -0,0 +1,70 @@
<!doctype html>
<html lang="en" ng-app="sampleApp">
<head>
<meta charset="utf-8">
<title>AngularCodeMirror | Sample application</title>
<link rel="stylesheet" href="/bower_components/twitter/dist/css/bootstrap.min.css" >
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css" >
<link rel="stylesheet" href="/bower_components/jqueryui/themes/redmond/jquery-ui.min.css" >
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css" >
<link rel="stylesheet" href="/bower_components/codemirror/theme/elegant.css" >
<link rel="stylesheet" href="/bower_components/codemirror/addon/lint/lint.css" >
<link rel="stylesheet" href="/lib/AngularCodeMirror.css" >
<link rel="stylesheet" href="/app/css/sampleApp.css" >
<script src="/bower_components/angular/angular.min.js"></script>
<script src="/bower_components/angular-route/angular-route.min.js"></script>
<script src="/app/js/sampleApp.js"></script>
<script src="/lib/AngularCodeMirror.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#nb-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Angular CodeMirror</a>
</div>
</div>
</nav>
<div class="container">
<div id="main-view" ng-view></div>
</div>
<div class="footer navbar-default navbar-fixed-bottom">
<div class="container">
<div class="row">
<div class="col-lg-12">
<a class="navbar-brand pull-right" href="/">Angular CodeMirror</a>
</div>
</div>
</div>
</div>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script src="/bower_components/jqueryui/ui/minified/jquery-ui.min.js"></script>
<script src="/bower_components/twitter/dist/js/bootstrap.min.js"></script>
<!-- bits to support code mirror -->
<script src="/bower_components/js-yaml/js-yaml.min.js"></script>
<script src="/bower_components/jsonlint/lib/jsonlint.js"></script>
<script src="/bower_components/codemirror/lib/codemirror.js"></script>
<script src="/bower_components/codemirror/mode/javascript/javascript.js"></script>
<script src="/bower_components/codemirror/mode/yaml/yaml.js"></script>
<script src="/bower_components/codemirror/addon/lint/lint.js"></script>
<script src="/bower_components/codemirror/addon/lint/json-lint.js"></script>
<script src="/lib/yaml-lint.js"></script>
<script src="/bower_components/codemirror/addon/edit/closebrackets.js"></script>
<script src="/bower_components/codemirror/addon/edit/matchbrackets.js"></script>
<script src="/bower_components/codemirror/addon/selection/active-line.js"></script>
</body>
</html>

View File

@@ -0,0 +1,135 @@
/**********************************************
* sampleApp.js
*
* Copyright (c) 2013-2014 Chris Houseknecht
*
* Distributed under The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
'use strict';
angular.module('sampleApp', ['ngRoute', 'AngularCodeMirrorModule'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'partials/main.html',
controller: 'sampleController'
})
.otherwise({
redirectTo: '/'
});
}])
.controller('sampleController', ['$scope', 'AngularCodeMirror', function($scope, AngularCodeMirror) {
$scope.parseType = 'json';
$scope.codeValue = '{}';
var container = document.getElementById('main-view'),
modes = {
yaml: {
mode:"text/x-yaml",
matchBrackets: true,
autoCloseBrackets: true,
styleActiveLine: true,
lineNumbers: true,
gutters: ["CodeMirror-lint-markers"],
lint: true
},
json: {
mode: "application/json",
styleActiveLine: true,
matchBrackets: true,
autoCloseBrackets: true,
lineNumbers: true,
gutters: ["CodeMirror-lint-markers"],
lint: true
}
},
codeMirror = AngularCodeMirror();
codeMirror.addModes(modes);
$scope.parseTypeChange = function() {
var json_obj;
if ($scope.parseType === 'json') {
// converting yaml to json
try {
json_obj = jsyaml.load($scope.codeValue);
if ($.isEmptyObject(json_obj)) {
$scope.codeValue = "{}";
}
else {
$scope.codeValue = JSON.stringify(json_obj, null, " ");
}
}
catch (e) {
alert('Failed to parse valid YAML. ' + e.message);
setTimeout( function() { $scope.$apply( function() { $scope.parseType = 'yaml'; }); }, 500);
}
}
else {
// convert json to yaml
try {
json_obj = JSON.parse($scope.codeValue);
if ($.isEmptyObject(json_obj)) {
$scope.codeValue = '---';
}
else {
$scope.codeValue = jsyaml.safeDump(json_obj);
}
}
catch (e) {
alert('Failed to parse valid JSON. ' + e.message);
setTimeout( function() { $scope.$apply( function() { $scope.parseType = 'json'; }); }, 500 );
}
}
};
$scope.showCodeEditor = function() {
var title = 'Edit ' + $scope.parseType.toUpperCase();
codeMirror.show({
scope: $scope,
container: container,
mode: $scope.parseType,
model: 'codeValue',
title: title
});
};
}])
.directive('afTooltip', [ function() {
return {
link: function(scope, element, attrs) {
var placement = (attrs.placement) ? attrs.placement : 'top';
$(element).tooltip({
html: true,
placement: placement,
title: attrs.afTooltip,
trigger: 'hover',
container: 'body'
});
}
};
}]);

View File

@@ -0,0 +1,20 @@
<div class="row">
<div class="col-lg-12">
<h4>CodeMirror Example</h4>
<div id="parse-type-group">
<div id="parse-type-label">Parse Type:</div>
<label class="radio-inline"><input type="radio" name="parsetype" value="json" ng-model="parseType"
ng-change="parseTypeChange()">JSON</label>
<label class="radio-inline"><input type="radio" name="parsetype" value="yaml" ng-model="parseType"
ng-change="parseTypeChange()">YAML</label>
<a class="external-editor-link" ng-click="showCodeEditor('code')" data-placement="top"
af-tooltip="View in code editor" href=""><i class="fa fa-external-link"></i> Code Editor</a>
</div>
<form>
<div class="form-group">
<textarea rows="10" name="code" id="code_textarea" class="form-control" ng-model="codeValue"></textarea>
</div>
</form>
</div><!-- col-lg-12 -->
</div><!-- row -->

View File

@@ -0,0 +1,15 @@
{
"name": "angular-codemirror",
"version": "0.0.3",
"dependencies": {
"angular": "latest",
"angular-route": "latest",
"jquery": "latest",
"jqueryui": "latest",
"components-font-awesome": "latest",
"twitter": "latest",
"js-yaml": "latest",
"jsonlint": "latest",
"codemirror": "latest"
}
}

View File

@@ -0,0 +1,111 @@
/**********************************************
* AngularCodeMirror.css
*
* CodeMirror.css overrides
*
* Copyright (c) 2014 Chris Houseknecht
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of angular-codemirror and associated files and documentation (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
.CodeMirror {
height: auto;
}
.CodeMirror-activeline-background {
background-color: #f7f7f7;
}
/* Modal dialog overrides to make jqueryui dialog blend in with Twitter.
Why? Twitter's modal is not draggable or resizable, which is not very
useful for a code editor */
.ui-dialog-title {
font-size: 22px;
color: #1778c3;
font-weight: bold;
line-height: normal;
}
.ui-dialog .close {
font-size: 18px;
font-weight: bold;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1;
opacity: .7;
text-shadow: 0 1px 0 #ffffff;
}
.ui-dialog .ui-widget-header {
border-radius: 0;
border: none;
border-bottom: 1px solid #A9A9A9;
height: 55px;
}
.ui-dialog .ui-dialog-titlebar {
padding-bottom: 0;
padding-top: 12px;
}
.ui-dialog .ui-dialog-titlebar {
background-image: none;
background-color: #ffffff;
border-color: #ffffff;
color: #A9A9A9;
}
.ui-dialog .ui-resizable-se {
right: 5px;
bottom: 5px;
background-position: -80px -224px;
color: #171717;
}
.ui-dialog-buttonset button.btn.btn-default.ui-state-hover,
.ui-dialog-buttonset button.btn.btn-default.ui-state-active,
.ui-dialog-buttonset button.btn.btn-default.ui-state-focus {
font-weight: normal;
}
.ui-dialog-buttonset button.btn.btn-primary.ui-state-hover,
.ui-dialog-buttonset button.btn.btn-primary.ui-state-active,
.ui-dialog-buttonset button.btn.btn-primary.ui-state-focus {
background-image: none;
color: #ffffff;
background-color: #2a6496;
border-color: #285e8e;
text-decoration: none;
font-weight: normal;
}
/* Bring the overlay above any TB fixed navs and darken it to match */
.ui-widget-overlay.ui-front {
background-image: none;
background-color: #000;
opacity: .6;
z-index: 1040;
}
/* Make sure code editor dialog is always at top of stack */
[aria-describedby=af-code-editor-modal].ui-front {
z-index: 2050;
}
.CodeMirror-lint-tooltip {
z-index: 2060;
}

View File

@@ -0,0 +1,103 @@
/**********************************************
* AngularCodeMirror.js
*
* Copyright (c) 2014 Chris Houseknecht
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of angular-codemirror and associated files and documentation (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
'use strict';
angular.module('AngularCodeMirrorModule', [])
.factory('AngularCodeMirror', [ function() {
return function() {
var fn = function() {
this.show = function(params) {
var scope = params.scope,
target = (typeof params.container === "string") ? document.getElementById(params.container) : params.container,
mode = params.mode,
model = params.model,
title = params.title || 'Code Editor',
modes = this.modes,
myCodeMirror;
this.html = "<div id=\"af-code-editor-modal\"><div id=\"af-code\"></div>\n</div>\n";
if ($('#af-code-editor-modal').length === 0) {
$(target).append(this.html);
}
else {
$('#af-code-editor-modal').remove();
$(target).append(this.html);
}
$('#af-code-editor-modal').dialog({
title: title,
resizable: true,
width: Math.ceil($(window).width() * 0.9),
height: Math.ceil($(window).height() * 0.8),
position: "center",
show: true,
closeOnEscape: true,
modal: true,
autoOpen: true,
buttons: [
{ text: "Cancel", id: "af-code-edit-cancel", click: function() { $(this).dialog('close'); } },
{ text: "OK", id: "af-code-edit-ok", click:
function() {
scope.$apply(function() { scope[model] = myCodeMirror.getValue(); });
$(this).dialog('close');
}
}
],
open: function() {
// fix buttons- make them more twittery
$('.ui-dialog[aria-describedby="af-code-editor-modal"]').find('.ui-dialog-titlebar button')
.empty().attr({'class': 'close'}).text('x');
$('#af-code-edit-cancel').attr({ "class": "btn btn-default" }).empty().html("<i class=\"fa fa-times\"></i> Cancel");
$('#af-code-edit-ok').attr({ "class": "btn btn-primary" }).empty().html("<i class=\"fa fa-check\"></i> Save");
var options = modes[mode];
options.value = scope[model];
myCodeMirror = CodeMirror(document.getElementById('af-code'), options);
}
});
};
// Don't maintain modes here. Use this.addModes() to set/override available modes
this.modes = {};
// Add or override available modes.
this.addModes = function(obj) {
for (var key in obj) {
if (this.modes[key]) {
delete this.modes[key];
}
this.modes[key] = angular.copy(obj[key]);
}
};
};
return new fn();
};
}]);

View File

@@ -0,0 +1,15 @@
// Add YAML lint support to CodeMirror. Submitted pull request #2266
// Depends on js-yaml.js from https://github.com/nodeca/js-yaml
// declare global: jsyaml
CodeMirror.registerHelper("lint", "yaml", function(text) {
var found = [];
try { jsyaml.load(text); }
catch(e) {
var loc = e.mark;
found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message });
}
return found;
});
CodeMirror.yamlValidator = CodeMirror.lint.yaml; // deprecated

View File

@@ -0,0 +1,12 @@
#!/bin/bash
#
# Minify angular-forms.js
#
# ./compile.sh
#
if [ -f ../angular-forms.min.js ]; then
rm ../angular-forms.min.js
fi
java -jar ../bower_components/closure-compiler/compiler.jar --js ../angular-forms.js --js_output_file ../angular-forms.min.js

View File

@@ -0,0 +1,244 @@
#!/usr/bin/env node
var util = require('util'),
http = require('http'),
fs = require('fs'),
url = require('url'),
events = require('events');
var DEFAULT_PORT = 8000;
function main(argv) {
new HttpServer({
'GET': createServlet(StaticServlet),
'HEAD': createServlet(StaticServlet)
}).start(Number(argv[2]) || DEFAULT_PORT);
}
function escapeHtml(value) {
return value.toString().
replace('<', '&lt;').
replace('>', '&gt;').
replace('"', '&quot;');
}
function createServlet(Class) {
var servlet = new Class();
return servlet.handleRequest.bind(servlet);
}
/**
* An Http server implementation that uses a map of methods to decide
* action routing.
*
* @param {Object} Map of method => Handler function
*/
function HttpServer(handlers) {
this.handlers = handlers;
this.server = http.createServer(this.handleRequest_.bind(this));
}
HttpServer.prototype.start = function(port) {
this.port = port;
this.server.listen(port);
util.puts('Http Server running at http://localhost:' + port + '/');
};
HttpServer.prototype.parseUrl_ = function(urlString) {
var parsed = url.parse(urlString);
parsed.pathname = url.resolve('/', parsed.pathname);
return url.parse(url.format(parsed), true);
};
HttpServer.prototype.handleRequest_ = function(req, res) {
var logEntry = req.method + ' ' + req.url;
if (req.headers['user-agent']) {
logEntry += ' ' + req.headers['user-agent'];
}
util.puts(logEntry);
req.url = this.parseUrl_(req.url);
var handler = this.handlers[req.method];
if (!handler) {
res.writeHead(501);
res.end();
} else {
handler.call(this, req, res);
}
};
/**
* Handles static content.
*/
function StaticServlet() {}
StaticServlet.MimeMap = {
'txt': 'text/plain',
'html': 'text/html',
'css': 'text/css',
'xml': 'application/xml',
'json': 'application/json',
'js': 'application/javascript',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'png': 'image/png',
  'svg': 'image/svg+xml'
};
StaticServlet.prototype.handleRequest = function(req, res) {
var self = this;
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
return String.fromCharCode(parseInt(hex, 16));
});
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
fs.stat(path, function(err, stat) {
if (err)
return self.sendMissing_(req, res, path);
if (stat.isDirectory())
return self.sendDirectory_(req, res, path);
return self.sendFile_(req, res, path);
});
}
StaticServlet.prototype.sendError_ = function(req, res, error) {
res.writeHead(500, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>Internal Server Error</title>\n');
res.write('<h1>Internal Server Error</h1>');
res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
util.puts('500 Internal Server Error');
util.puts(util.inspect(error));
};
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(404, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>404 Not Found</title>\n');
res.write('<h1>Not Found</h1>');
res.write(
'<p>The requested URL ' +
escapeHtml(path) +
' was not found on this server.</p>'
);
res.end();
util.puts('404 Not Found: ' + path);
};
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(403, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>403 Forbidden</title>\n');
res.write('<h1>Forbidden</h1>');
res.write(
'<p>You do not have permission to access ' +
escapeHtml(path) + ' on this server.</p>'
);
res.end();
util.puts('403 Forbidden: ' + path);
};
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
res.writeHead(301, {
'Content-Type': 'text/html',
'Location': redirectUrl
});
res.write('<!doctype html>\n');
res.write('<title>301 Moved Permanently</title>\n');
res.write('<h1>Moved Permanently</h1>');
res.write(
'<p>The document has moved <a href="' +
redirectUrl +
'">here</a>.</p>'
);
res.end();
util.puts('301 Moved Permanently: ' + redirectUrl);
};
StaticServlet.prototype.sendFile_ = function(req, res, path) {
var self = this;
var file = fs.createReadStream(path);
res.writeHead(200, {
'Content-Type': StaticServlet.
MimeMap[path.split('.').pop()] || 'text/plain'
});
if (req.method === 'HEAD') {
res.end();
} else {
file.on('data', res.write.bind(res));
file.on('close', function() {
res.end();
});
file.on('error', function(error) {
self.sendError_(req, res, error);
});
}
};
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
var self = this;
if (path.match(/[^\/]$/)) {
req.url.pathname += '/';
var redirectUrl = url.format(url.parse(url.format(req.url)));
return self.sendRedirect_(req, res, redirectUrl);
}
fs.readdir(path, function(err, files) {
if (err)
return self.sendError_(req, res, error);
if (!files.length)
return self.writeDirectoryIndex_(req, res, path, []);
var remaining = files.length;
files.forEach(function(fileName, index) {
fs.stat(path + '/' + fileName, function(err, stat) {
if (err)
return self.sendError_(req, res, err);
if (stat.isDirectory()) {
files[index] = fileName + '/';
}
if (!(--remaining))
return self.writeDirectoryIndex_(req, res, path, files);
});
});
});
};
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
path = path.substring(1);
res.writeHead(200, {
'Content-Type': 'text/html'
});
if (req.method === 'HEAD') {
res.end();
return;
}
res.write('<!doctype html>\n');
res.write('<title>' + escapeHtml(path) + '</title>\n');
res.write('<style>\n');
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
res.write('</style>\n');
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
res.write('<ol>');
files.forEach(function(fileName) {
if (fileName.charAt(0) !== '.') {
res.write('<li><a href="' +
escapeHtml(fileName) + '">' +
escapeHtml(fileName) + '</a></li>');
}
});
res.write('</ol>');
res.end();
};
// Must be last,
main(process.argv);