Initial commit

This commit is contained in:
Martin Donnelly 2016-03-02 13:53:28 +00:00
commit 8f551e614e
23 changed files with 2019 additions and 0 deletions

79
.gitignore vendored Normal file
View File

@ -0,0 +1,79 @@
# Created by .ignore support plugin (hsz.mobi)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
/dist
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules

30
.jshintrc Normal file
View File

@ -0,0 +1,30 @@
{
"node": 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,
"white": true,
"validthis": true,
"browser" : true,
"jquery":true,
"globals": {
"angular": false
}
}

174
app/css/app.css Normal file
View File

@ -0,0 +1,174 @@
html,
body {
height: 100%;
background-color: #eee;
}
html,
body,
input,
textarea,
buttons {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
}
/**
* Layout CSS
*/
#header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 2;
transition: left 0.2s;
}
#sidedrawer {
position: fixed;
top: 0;
bottom: 0;
width: 200px;
left: -200px;
overflow: auto;
z-index: 2;
background-color: #fff;
transition: transform 0.2s;
}
#content-wrapper {
min-height: 100%;
overflow-x: hidden;
margin-left: 0px;
transition: margin-left 0.2s;
/* sticky bottom */
margin-bottom: -160px;
padding-bottom: 160px;
}
#footer {
height: 160px;
margin-left: 0px;
transition: margin-left 0.2s;
}
@media (min-width: 600px) {
#header {
left: 200px;
}
#sidedrawer {
transform: translate(200px);
}
#content-wrapper {
margin-left: 200px;
}
#footer {
margin-left: 200px;
}
body.hide-sidedrawer #header {
left: 0;
}
body.hide-sidedrawer #sidedrawer {
transform: translate(0px);
}
body.hide-sidedrawer #content-wrapper {
margin-left: 0;
}
body.hide-sidedrawer #footer {
margin-left: 0;
}
}
/**
* Toggle Side drawer
*/
#sidedrawer.active {
transform: translate(200px);
}
/**
* Header CSS
*/
.sidedrawer-toggle {
color: #fff;
cursor: pointer;
font-size: 20px;
line-height: 20px;
margin-right: 10px;
}
.sidedrawer-toggle:hover {
color: #fff;
text-decoration: none;
}
/**
* Footer CSS
*/
#footer {
background-color: #0288D1;
color: #fff;
}
#footer a {
color: #fff;
text-decoration: underline;
}
/**
* Side drawer CSS
*/
#sidedrawer-brand {
/* padding-left: 20px; */
}
#sidedrawer ul {
list-style: none;
}
#sidedrawer > ul {
padding-left: 0px;
}
#sidedrawer > ul > li:first-child {
padding-top: 15px;
}
#sidedrawer strong {
display: block;
padding: 15px 22px;
cursor: pointer;
}
#sidedrawer .entry:hover {
background-color: #E0E0E0;
}
#sidedrawer strong + ul > li {
padding: 6px 0px;
}
#sidedrawer .entry {
padding-bottom: 5px;
border-bottom:1px solid #e0c0e0;
padding-right: 16px;
}
#sidedrawer .entry:after {
font-family:Linearicons-Free;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
float:right;
content:"\e87a"
}

53
app/css/md.css Normal file
View File

@ -0,0 +1,53 @@
body {
font-family: 'Roboto Slab', "Helvetica Neue", Helvetica, Arial;
}
ul {
margin: 0;
padding: 0;
}
li {
display: inline;
margin: 0;
padding: 0 4px 0 0;
}
.dates {
padding: 2px;
border: solid 1px #80007e;
background-color: #ffffff;
}
#btc, #fx {
font-size: 75%;
}
.up, .ontime {
color: darkgreen;
}
.down, .delayed {
color: darkred;
}
.nochange {
color: #000000;
}
.password {
border: 1px solid #cccccc;
background-color: #efefef;
font-family: monospace;
white-space: pre;
}
.mui--text-danger {
color: #F44336;
}
.fnBlock {
font-size:20px;
}
.fnRefresh {
}

67
app/index.html Normal file
View File

@ -0,0 +1,67 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="//fonts.googleapis.com/css?family=Roboto+Slab:400,300,700" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="//cdn.linearicons.com/free/1.0.0/icon-font.min.css">
<link href="//cdn.muicss.com/mui-0.4.6/css/mui.min.css" rel="stylesheet" type="text/css" />
<!-- inject:css -->
<link href="css/app.css" rel="stylesheet" type="text/css" />
<link href="css/md.css" rel="stylesheet" type="text/css" />
<!-- endinject -->
<script src="//cdn.muicss.com/mui-0.4.6/js/mui.min.js"></script>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="libs/ejs.js"></script>
<script src="libs/view.js"></script>
</head>
<body>
<div id="sidedrawer" class="mui--no-user-select">
<div id="sidedrawer-brand" class="mui--appbar-line-height mui--text-title">Recipes</div>
<div class="mui-divider"></div>
<div id="functions" class="fnBlock">
<span id="fnRefresh" class="lnr lnr-sync"></span>
<span id="fnSearch" class="lnr lnr-magnifier"></span>
</div>
<div id='searchbox' class="mui-textfield" style="display: none;">
<input id='newsearch' type="text" placeholder="Search for..">
</div>
<div class="mui-divider"></div>
<div id="listContainer">
</div>
<div class="mui-divider"></div>
<div class="mui-textfield">
<input id='newurl' type="text" placeholder="Add new url">
<div id="addstatus" style="display:none;">Adding...</div>
</div>
</div>
<header id="header">
<div class="mui-appbar mui--appbar-line-height">
<div class="mui-container-fluid">
<a class="sidedrawer-toggle mui--visible-xs-inline-block js-show-sidedrawer"></a>
<a class="sidedrawer-toggle mui--hidden-xs js-hide-sidedrawer"></a>
<span class="mui--text-title mui--visible-xs-inline-block">Brand.io</span>
</div>
</div>
</header>
<div id="content-wrapper">
<div class="mui--appbar-height"></div>
<div class="mui-container-fluid" id="bodyContents">
<!-- content here -->
</div>
</div>
<footer id="footer">
<div class="mui-container-fluid">
<br>
Made with ♥ by Martin</a>
</div>
</footer>
</body>
<!-- inject:js -->
<script src="js/shell.js"></script>
<script src="js/app.js"></script>
<!-- endinject -->
</html>

66
app/index.prod.html Normal file
View File

@ -0,0 +1,66 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="//fonts.googleapis.com/css?family=Roboto+Slab:400,300,700" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="//cdn.linearicons.com/free/1.0.0/icon-font.min.css">
<link href="//cdn.muicss.com/mui-0.4.6/css/mui.min.css" rel="stylesheet" type="text/css" />
<!-- inject:css -->
<link href="css/app.min.css" rel="stylesheet" type="text/css" />
<link href="css/md.min.css" rel="stylesheet" type="text/css" />
<!-- endinject -->
<script src="//cdn.muicss.com/mui-0.4.6/js/mui.min.js"></script>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="libs/ejs_production.js"></script>
</head>
<body>
<div id="sidedrawer" class="mui--no-user-select">
<div id="sidedrawer-brand" class="mui--appbar-line-height mui--text-title">Recipes</div>
<div class="mui-divider"></div>
<div id="functions" class="fnBlock">
<span id="fnRefresh" class="lnr lnr-sync"></span>
<span id="fnSearch" class="lnr lnr-magnifier"></span>
</div>
<div id='searchbox' class="mui-textfield" style="display: none;">
<input id='newsearch' type="text" placeholder="Search for..">
</div>
<div class="mui-divider"></div>
<div id="listContainer">
</div>
<div class="mui-divider"></div>
<div class="mui-textfield">
<input id='newurl' type="text" placeholder="Add new url">
<div id="addstatus" style="display:none;">Adding...</div>
</div>
</div>
<header id="header">
<div class="mui-appbar mui--appbar-line-height">
<div class="mui-container-fluid">
<a class="sidedrawer-toggle mui--visible-xs-inline-block js-show-sidedrawer"></a>
<a class="sidedrawer-toggle mui--hidden-xs js-hide-sidedrawer"></a>
<span class="mui--text-title mui--visible-xs-inline-block">Brand.io</span>
</div>
</div>
</header>
<div id="content-wrapper">
<div class="mui--appbar-height"></div>
<div class="mui-container-fluid" id="bodyContents">
<!-- content here -->
</div>
</div>
<footer id="footer">
<div class="mui-container-fluid">
<br>
Made with ♥ by Martin</a>
</div>
</footer>
</body>
<!-- inject:js -->
<script src="js/main.min.js"></script>
<!-- endinject -->
</html>

150
app/js/app.js Normal file
View File

@ -0,0 +1,150 @@
'use strict';
/**
* Created by Martin on 24/02/2016.
*/
$.fn.pressEnter = function (fn) {
return this.each(function () {
$(this).bind('enterPress', fn);
$(this).keyup(function (e) {
if (e.keyCode === 13) {
$(this).trigger('enterPress');
}
});
});
};
(function () {
console.log('GO!');
var $list = $('#listContainer');
var displayList = function (obj) {
var html = new EJS({url: '/partials/list.ejs'}).render(obj);
console.log(html);
$list.empty();
$list.append(html);
$('#listContainer').find('.entry').not('.emptyMessage').click(function () {
console.log('Clicked list. ' + this.id);
getRecipe(this.id);
});
}, displayPage = function (obj) {
var $bodyContents = $('#bodyContents');
if (obj.reduced.length > 0) {
$bodyContents.empty();
$bodyContents.append(obj.reduced);
}
}, getRecipe = function (id) {
console.log('get recipe');
var url = '/entry/' + id;
var data = '';
$.ajax({
type: 'GET',
url: url,
data: data,
dataType: 'json',
timeout: 10000,
//contentType: ('application/json'),
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
},
success: function (data) {
// console.log(data);
displayPage(data);
},
error: function (xhr, type) {
console.log('ajax error');
console.log(xhr);
console.log(type);
}
});
},
getList = function () {
var url = '/list';
$.ajax({
type: 'GET',
url: url,
data: '',
dataType: 'json',
timeout: 10000,
//contentType: ('application/json'),
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
},
success: function (data) {
// console.log(data);
displayList(data);
},
error: function (xhr, type) {
console.log('ajax error');
console.log(xhr);
console.log(type);
}
});
}, addNew = function (newUrl) {
var url = '/add';
var data = {url: JSON.stringify(newUrl)};
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: 'json',
timeout: 10000,
//contentType: ('application/json'),
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
},
success: function () {
// console.log(data);
// displayList(data);
},
error: function (xhr, type) {
console.log('ajax error');
console.log(xhr);
console.log(type);
}
});
},
start = function () {
getList();
};
$('#newurl').pressEnter(function () {
var url = $(this).val();
if (url !== null) {
console.log('Adding: ' + url);
addNew(url);
$('#addstatus').fadeIn(400).delay(1500).fadeOut(400);
$(this).val('');
/* setTimeout(function () {
getList();
}, 5000);*/
}
});
$('#fnRefresh').on('click', function () {
getList();
});
start();
})();

45
app/js/shell.js Normal file
View File

@ -0,0 +1,45 @@
jQuery(function($) {
var $bodyEl = $('body'),
$sidedrawerEl = $('#sidedrawer');
function showSidedrawer() {
// show overlay
var options = {
onclose: function() {
$sidedrawerEl
.removeClass('active')
.appendTo(document.body);
}
};
var $overlayEl = $(mui.overlay('on', options));
// show element
$sidedrawerEl.appendTo($overlayEl);
setTimeout(function() {
$sidedrawerEl.addClass('active');
}, 20);
}
function hideSidedrawer() {
$bodyEl.toggleClass('hide-sidedrawer');
}
$('.js-show-sidedrawer').on('click', showSidedrawer);
$('.js-hide-sidedrawer').on('click', hideSidedrawer);
});
var $titleEls = $('strong', $sidedrawerEl);
$titleEls
.next()
.hide();
$titleEls.on('click', function() {
$(this).next().slideToggle(200);
});/**
* Created by Martin on 24/02/2016.
*/

505
app/libs/ejs.js Normal file
View File

@ -0,0 +1,505 @@
(function(){
var rsplit = function(string, regex) {
var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
while (result != null)
{
first_idx = result.index; last_idx = regex.lastIndex;
if ((first_idx) != 0)
{
first_bit = string.substring(0,first_idx);
retArr.push(string.substring(0,first_idx));
string = string.slice(first_idx);
}
retArr.push(result[0]);
string = string.slice(result[0].length);
result = regex.exec(string);
}
if (! string == '')
{
retArr.push(string);
}
return retArr;
},
chop = function(string){
return string.substr(0, string.length - 1);
},
extend = function(d, s){
for(var n in s){
if(s.hasOwnProperty(n)) d[n] = s[n]
}
}
EJS = function( options ){
options = typeof options == "string" ? {view: options} : options
this.set_options(options);
if(options.precompiled){
this.template = {};
this.template.process = options.precompiled;
EJS.update(this.name, this);
return;
}
if(options.element)
{
if(typeof options.element == 'string'){
var name = options.element
options.element = document.getElementById( options.element )
if(options.element == null) throw name+'does not exist!'
}
if(options.element.value){
this.text = options.element.value
}else{
this.text = options.element.innerHTML
}
this.name = options.element.id
this.type = '['
}else if(options.url){
options.url = EJS.endExt(options.url, this.extMatch);
this.name = this.name ? this.name : options.url;
var url = options.url
//options.view = options.absolute_url || options.view || options.;
var template = EJS.get(this.name /*url*/, this.cache);
if (template) return template;
if (template == EJS.INVALID_PATH) return null;
try{
this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
}catch(e){}
if(this.text == null){
throw( {type: 'EJS', message: 'There is no template at '+url} );
}
//this.name = url;
}
var template = new EJS.Compiler(this.text, this.type);
template.compile(options, this.name);
EJS.update(this.name, this);
this.template = template;
};
/* @Prototype*/
EJS.prototype = {
/**
* Renders an object with extra view helpers attached to the view.
* @param {Object} object data to be rendered
* @param {Object} extra_helpers an object with additonal view helpers
* @return {String} returns the result of the string
*/
render : function(object, extra_helpers){
object = object || {};
this._extra_helpers = extra_helpers;
var v = new EJS.Helpers(object, extra_helpers || {});
return this.template.process.call(object, object,v);
},
update : function(element, options){
if(typeof element == 'string'){
element = document.getElementById(element)
}
if(options == null){
_template = this;
return function(object){
EJS.prototype.update.call(_template, element, object)
}
}
if(typeof options == 'string'){
params = {}
params.url = options
_template = this;
params.onComplete = function(request){
var object = eval( request.responseText )
EJS.prototype.update.call(_template, element, object)
}
EJS.ajax_request(params)
}else
{
element.innerHTML = this.render(options)
}
},
out : function(){
return this.template.out;
},
/**
* Sets options on this view to be rendered with.
* @param {Object} options
*/
set_options : function(options){
this.type = options.type || EJS.type;
this.cache = options.cache != null ? options.cache : EJS.cache;
this.text = options.text || null;
this.name = options.name || null;
this.ext = options.ext || EJS.ext;
this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
}
};
EJS.endExt = function(path, match){
if(!path) return null;
match.lastIndex = 0
return path+ (match.test(path) ? '' : this.ext )
}
/* @Static*/
EJS.Scanner = function(source, left, right) {
extend(this,
{left_delimiter: left +'%',
right_delimiter: '%'+right,
double_left: left+'%%',
double_right: '%%'+right,
left_equal: left+'%=',
left_comment: left+'%#'})
this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ;
this.source = source;
this.stag = null;
this.lines = 0;
};
EJS.Scanner.to_text = function(input){
if(input == null || input === undefined)
return '';
if(input instanceof Date)
return input.toDateString();
if(input.toString)
return input.toString();
return '';
};
EJS.Scanner.prototype = {
scan: function(block) {
scanline = this.scanline;
regex = this.SplitRegexp;
if (! this.source == '')
{
var source_split = rsplit(this.source, /\n/);
for(var i=0; i<source_split.length; i++) {
var item = source_split[i];
this.scanline(item, regex, block);
}
}
},
scanline: function(line, regex, block) {
this.lines++;
var line_split = rsplit(line, regex);
for(var i=0; i<line_split.length; i++) {
var token = line_split[i];
if (token != null) {
try{
block(token, this);
}catch(e){
throw {type: 'EJS.Scanner', line: this.lines};
}
}
}
}
};
EJS.Buffer = function(pre_cmd, post_cmd) {
this.line = new Array();
this.script = "";
this.pre_cmd = pre_cmd;
this.post_cmd = post_cmd;
for (var i=0; i<this.pre_cmd.length; i++)
{
this.push(pre_cmd[i]);
}
};
EJS.Buffer.prototype = {
push: function(cmd) {
this.line.push(cmd);
},
cr: function() {
this.script = this.script + this.line.join('; ');
this.line = new Array();
this.script = this.script + "\n";
},
close: function() {
if (this.line.length > 0)
{
for (var i=0; i<this.post_cmd.length; i++){
this.push(pre_cmd[i]);
}
this.script = this.script + this.line.join('; ');
line = null;
}
}
};
EJS.Compiler = function(source, left) {
this.pre_cmd = ['var ___ViewO = [];'];
this.post_cmd = new Array();
this.source = ' ';
if (source != null)
{
if (typeof source == 'string')
{
source = source.replace(/\r\n/g, "\n");
source = source.replace(/\r/g, "\n");
this.source = source;
}else if (source.innerHTML){
this.source = source.innerHTML;
}
if (typeof this.source != 'string'){
this.source = "";
}
}
left = left || '<';
var right = '>';
switch(left) {
case '[':
right = ']';
break;
case '<':
break;
default:
throw left+' is not a supported deliminator';
break;
}
this.scanner = new EJS.Scanner(this.source, left, right);
this.out = '';
};
EJS.Compiler.prototype = {
compile: function(options, name) {
options = options || {};
this.out = '';
var put_cmd = "___ViewO.push(";
var insert_cmd = put_cmd;
var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd);
var content = '';
var clean = function(content)
{
content = content.replace(/\\/g, '\\\\');
content = content.replace(/\n/g, '\\n');
content = content.replace(/"/g, '\\"');
return content;
};
this.scanner.scan(function(token, scanner) {
if (scanner.stag == null)
{
switch(token) {
case '\n':
content = content + "\n";
buff.push(put_cmd + '"' + clean(content) + '");');
buff.cr();
content = '';
break;
case scanner.left_delimiter:
case scanner.left_equal:
case scanner.left_comment:
scanner.stag = token;
if (content.length > 0)
{
buff.push(put_cmd + '"' + clean(content) + '")');
}
content = '';
break;
case scanner.double_left:
content = content + scanner.left_delimiter;
break;
default:
content = content + token;
break;
}
}
else {
switch(token) {
case scanner.right_delimiter:
switch(scanner.stag) {
case scanner.left_delimiter:
if (content[content.length - 1] == '\n')
{
content = chop(content);
buff.push(content);
buff.cr();
}
else {
buff.push(content);
}
break;
case scanner.left_equal:
buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
break;
}
scanner.stag = null;
content = '';
break;
case scanner.double_right:
content = content + scanner.right_delimiter;
break;
default:
content = content + token;
break;
}
}
});
if (content.length > 0)
{
// Chould be content.dump in Ruby
buff.push(put_cmd + '"' + clean(content) + '")');
}
buff.close();
this.out = buff.script + ";";
var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";
try{
eval(to_be_evaled);
}catch(e){
if(typeof JSLINT != 'undefined'){
JSLINT(this.out);
for(var i = 0; i < JSLINT.errors.length; i++){
var error = JSLINT.errors[i];
if(error.reason != "Unnecessary semicolon."){
error.line++;
var e = new Error();
e.lineNumber = error.line;
e.message = error.reason;
if(options.view)
e.fileName = options.view;
throw e;
}
}
}else{
throw e;
}
}
}
};
//type, cache, folder
/**
* Sets default options for all views
* @param {Object} options Set view with the following options
* <table class="options">
<tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
<tr>
<td>type</td>
<td>'<'</td>
<td>type of magic tags. Options are '&lt;' or '['
</td>
</tr>
<tr>
<td>cache</td>
<td>true in production mode, false in other modes</td>
<td>true to cache template.
</td>
</tr>
</tbody></table>
*
*/
EJS.config = function(options){
EJS.cache = options.cache != null ? options.cache : EJS.cache;
EJS.type = options.type != null ? options.type : EJS.type;
EJS.ext = options.ext != null ? options.ext : EJS.ext;
var templates_directory = EJS.templates_directory || {}; //nice and private container
EJS.templates_directory = templates_directory;
EJS.get = function(path, cache){
if(cache == false) return null;
if(templates_directory[path]) return templates_directory[path];
return null;
};
EJS.update = function(path, template) {
if(path == null) return;
templates_directory[path] = template ;
};
EJS.INVALID_PATH = -1;
};
EJS.config( {cache: true, type: '<', ext: '.ejs' } );
/**
* @constructor
* By adding functions to EJS.Helpers.prototype, those functions will be available in the
* views.
* @init Creates a view helper. This function is called internally. You should never call it.
* @param {Object} data The data passed to the view. Helpers have access to it through this._data
*/
EJS.Helpers = function(data, extras){
this._data = data;
this._extras = extras;
extend(this, extras );
};
/* @prototype*/
EJS.Helpers.prototype = {
/**
* Renders a new view. If data is passed in, uses that to render the view.
* @param {Object} options standard options passed to a new view.
* @param {optional:Object} data
* @return {String}
*/
view: function(options, data, helpers){
if(!helpers) helpers = this._extras
if(!data) data = this._data;
return new EJS(options).render(data, helpers);
},
/**
* For a given value, tries to create a human representation.
* @param {Object} input the value being converted.
* @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
* @return {String}
*/
to_text: function(input, null_text) {
if(input == null || input === undefined) return null_text || '';
if(input instanceof Date) return input.toDateString();
if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'");
return '';
}
};
EJS.newRequest = function(){
var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
for(var i = 0; i < factories.length; i++) {
try {
var request = factories[i]();
if (request != null) return request;
}
catch(e) { continue;}
}
}
EJS.request = function(path){
var request = new EJS.newRequest()
request.open("GET", path, false);
try{request.send(null);}
catch(e){return null;}
if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
return request.responseText
}
EJS.ajax_request = function(params){
params.method = ( params.method ? params.method : 'GET')
var request = new EJS.newRequest();
request.onreadystatechange = function(){
if(request.readyState == 4){
if(request.status == 200){
params.onComplete(request)
}else
{
params.onComplete(request)
}
}
}
request.open(params.method, params.url)
request.send(null)
}
})();

File diff suppressed because one or more lines are too long

200
app/libs/view.js Normal file
View File

@ -0,0 +1,200 @@
EJS.Helpers.prototype.date_tag = function(name, value , html_options) {
if(! (value instanceof Date))
value = new Date()
var month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var years = [], months = [], days =[];
var year = value.getFullYear();
var month = value.getMonth();
var day = value.getDate();
for(var y = year - 15; y < year+15 ; y++)
{
years.push({value: y, text: y})
}
for(var m = 0; m < 12; m++)
{
months.push({value: (m), text: month_names[m]})
}
for(var d = 0; d < 31; d++)
{
days.push({value: (d+1), text: (d+1)})
}
var year_select = this.select_tag(name+'[year]', year, years, {id: name+'[year]'} )
var month_select = this.select_tag(name+'[month]', month, months, {id: name+'[month]'})
var day_select = this.select_tag(name+'[day]', day, days, {id: name+'[day]'})
return year_select+month_select+day_select;
}
EJS.Helpers.prototype.form_tag = function(action, html_options) {
html_options = html_options || {};
html_options.action = action
if(html_options.multipart == true) {
html_options.method = 'post';
html_options.enctype = 'multipart/form-data';
}
return this.start_tag_for('form', html_options)
}
EJS.Helpers.prototype.form_tag_end = function() { return this.tag_end('form'); }
EJS.Helpers.prototype.hidden_field_tag = function(name, value, html_options) {
return this.input_field_tag(name, value, 'hidden', html_options);
}
EJS.Helpers.prototype.input_field_tag = function(name, value , inputType, html_options) {
html_options = html_options || {};
html_options.id = html_options.id || name;
html_options.value = value || '';
html_options.type = inputType || 'text';
html_options.name = name;
return this.single_tag_for('input', html_options)
}
EJS.Helpers.prototype.is_current_page = function(url) {
return (window.location.href == url || window.location.pathname == url ? true : false);
}
EJS.Helpers.prototype.link_to = function(name, url, html_options) {
if(!name) var name = 'null';
if(!html_options) var html_options = {}
if(html_options.confirm){
html_options.onclick =
" var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
html_options.confirm = null;
}
html_options.href=url
return this.start_tag_for('a', html_options)+name+ this.tag_end('a');
}
EJS.Helpers.prototype.submit_link_to = function(name, url, html_options){
if(!name) var name = 'null';
if(!html_options) var html_options = {}
html_options.onclick = html_options.onclick || '' ;
if(html_options.confirm){
html_options.onclick =
" var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
html_options.confirm = null;
}
html_options.value = name;
html_options.type = 'submit'
html_options.onclick=html_options.onclick+
(url ? this.url_for(url) : '')+'return false;';
//html_options.href='#'+(options ? Routes.url_for(options) : '')
return this.start_tag_for('input', html_options)
}
EJS.Helpers.prototype.link_to_if = function(condition, name, url, html_options, post, block) {
return this.link_to_unless((condition == false), name, url, html_options, post, block);
}
EJS.Helpers.prototype.link_to_unless = function(condition, name, url, html_options, block) {
html_options = html_options || {};
if(condition) {
if(block && typeof block == 'function') {
return block(name, url, html_options, block);
} else {
return name;
}
} else
return this.link_to(name, url, html_options);
}
EJS.Helpers.prototype.link_to_unless_current = function(name, url, html_options, block) {
html_options = html_options || {};
return this.link_to_unless(this.is_current_page(url), name, url, html_options, block)
}
EJS.Helpers.prototype.password_field_tag = function(name, value, html_options) { return this.input_field_tag(name, value, 'password', html_options); }
EJS.Helpers.prototype.select_tag = function(name, value, choices, html_options) {
html_options = html_options || {};
html_options.id = html_options.id || name;
html_options.value = value;
html_options.name = name;
var txt = ''
txt += this.start_tag_for('select', html_options)
for(var i = 0; i < choices.length; i++)
{
var choice = choices[i];
var optionOptions = {value: choice.value}
if(choice.value == value)
optionOptions.selected ='selected'
txt += this.start_tag_for('option', optionOptions )+choice.text+this.tag_end('option')
}
txt += this.tag_end('select');
return txt;
}
EJS.Helpers.prototype.single_tag_for = function(tag, html_options) { return this.tag(tag, html_options, '/>');}
EJS.Helpers.prototype.start_tag_for = function(tag, html_options) { return this.tag(tag, html_options); }
EJS.Helpers.prototype.submit_tag = function(name, html_options) {
html_options = html_options || {};
//html_options.name = html_options.id || 'commit';
html_options.type = html_options.type || 'submit';
html_options.value = name || 'Submit';
return this.single_tag_for('input', html_options);
}
EJS.Helpers.prototype.tag = function(tag, html_options, end) {
if(!end) var end = '>'
var txt = ' '
for(var attr in html_options) {
if(html_options[attr] != null)
var value = html_options[attr].toString();
else
var value=''
if(attr == "Class") // special case because "class" is a reserved word in IE
attr = "class";
if( value.indexOf("'") != -1 )
txt += attr+'=\"'+value+'\" '
else
txt += attr+"='"+value+"' "
}
return '<'+tag+txt+end;
}
EJS.Helpers.prototype.tag_end = function(tag) { return '</'+tag+'>'; }
EJS.Helpers.prototype.text_area_tag = function(name, value, html_options) {
html_options = html_options || {};
html_options.id = html_options.id || name;
html_options.name = html_options.name || name;
value = value || ''
if(html_options.size) {
html_options.cols = html_options.size.split('x')[0]
html_options.rows = html_options.size.split('x')[1];
delete html_options.size
}
html_options.cols = html_options.cols || 50;
html_options.rows = html_options.rows || 4;
return this.start_tag_for('textarea', html_options)+value+this.tag_end('textarea')
}
EJS.Helpers.prototype.text_tag = EJS.Helpers.prototype.text_area_tag
EJS.Helpers.prototype.text_field_tag = function(name, value, html_options) { return this.input_field_tag(name, value, 'text', html_options); }
EJS.Helpers.prototype.url_for = function(url) {
return 'window.location="'+url+'";'
}
EJS.Helpers.prototype.img_tag = function(image_location, alt, options){
options = options || {};
options.src = image_location
options.alt = alt
return this.single_tag_for('img', options)
}

4
app/partials/list.ejs Normal file
View File

@ -0,0 +1,4 @@
<% for(var i=0; i<list.length; i++) {%>
<div class="entry" id="<%= list[i].id %>"><%= list[i].title %></div>
<% } %>

13
gulp/build.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var gulp = require('gulp');
function handleError(err) {
console.error(err.toString());
this.emit('end');
}
gulp.task('clean', function () {
return gulp.src(['.tmp', 'dist'], { read: false }).pipe($.rimraf());
});

61
gulpfile.js Normal file
View File

@ -0,0 +1,61 @@
"use strict";
var gulp = require('gulp'),
autoprefixer = require('gulp-autoprefixer'),
cssnano = require('gulp-cssnano'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
rename = require('gulp-rename'),
concat = require('gulp-concat'),
notify = require('gulp-notify'),
cache = require('gulp-cache'),
livereload = require('gulp-livereload'),
htmlmin = require('gulp-htmlmin'),
inject = require('gulp-inject'),
del = require('del');
gulp.task('scripts', function() {
return gulp.src('app/js/**/*.js')
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter('default'))
.pipe(concat('main.js'))
.pipe(gulp.dest('dist/js'))
.pipe(rename({suffix: '.min'}))
.pipe(uglify())
.pipe(gulp.dest('dist/js'))
.pipe(notify({ message: 'Scripts task complete' }));
});
gulp.task('styles', function() {
return gulp.src('app/css/**/*.css')
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(gulp.dest('dist/css'))
.pipe(rename({suffix: '.min'}))
.pipe(cssnano())
.pipe(gulp.dest('dist/css'))
.pipe(notify({ message: 'Styles task complete' }));
});
gulp.task('partials', function() {
gulp.src(['app/partials/**/*']).pipe(gulp.dest('dist/partials'));
gulp.src(['app/libs/ejs_production.js']).pipe(gulp.dest('dist/libs'));
});
gulp.task('minify-html', function () {
return gulp.src(['app/index.prod.html']).pipe(htmlmin({removeComments: true, collapseWhitespace: true, keepClosingSlash: true}))
.pipe(gulp.dest('dist/index.html'));
});
gulp.task('clean', function() {
return del(['dist']);
});
gulp.task('default', ['clean'], function() {
gulp.start('styles', 'scripts','partials','minify-html');
});

32
keeper-server.js Normal file
View File

@ -0,0 +1,32 @@
var express = require('express'), path = require('path'), http = require('http'),
favicon = require('serve-favicon'),
logger = require('morgan'),
cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'),
keeper = require('./server/keeper')
;
var app = express();
app.set('port', process.env.PORT || 8026);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'app')));
app.use('/', keeper);
/**
* create the server
*/
app.listen(app.get('port'), function () {
console.log('Keeper Server listening on ' + app.get('port'));
});/**
* Created by Martin on 22/02/2016.
*/

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "recipe",
"version": "1.0.0",
"description": "",
"main": "recipe-server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.0",
"cheerio": "^0.20.0",
"cookie-parser": "^1.4.1",
"ejs": "^2.4.1",
"express": "^4.13.4",
"morgan": "^1.7.0",
"nano": "^6.2.0",
"require-dir": "^0.3.0",
"serve-favicon": "^2.3.0",
"sqlite3": "^3.1.1",
"string": "^3.3.1",
"zlib": "^1.0.5"
},
"devDependencies": {
"del": "^2.2.0",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.0",
"gulp-cache": "^0.4.2",
"gulp-concat": "^2.6.0",
"gulp-cssnano": "^2.1.1",
"gulp-htmlmin": "^1.3.0",
"gulp-inject": "^3.0.0",
"gulp-jshint": "^2.0.0",
"gulp-livereload": "^3.8.1",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^1.5.3",
"jshint": "^2.9.1",
"jsonfile": "^2.2.3",
"log4js": "^0.6.31",
"simplecrawler": "^0.6.2"
}
}

77
recipes.json Normal file
View File

@ -0,0 +1,77 @@
{"recipes":[
{"url":"", "title":""},
{"url":"http://www.simplyrecipes.com/recipes/grilled_lime_chicken_with_black_bean_sauce/", "title":"Grilled Lime Chicken with Black Bean Sauce Recipe"},
{"url":"http://www.marksdailyapple.com/shakshuka-eggs-poached-in-spicy-tomato-sauce/#axzz29jTSubMo", "title":"Shakshuka (Eggs Poached in Spicy Tomato Sauce) "},
{"url":"http://www.marksdailyapple.com/spiced-pork-and-butternut-squash-with-sage/#axzz29jTSubMo", "title":"Spiced Pork and Butternut Squash with Sage"},
{"url":"http://www.marksdailyapple.com/dairy-free-green-goddess-dressing/#axzz29jTSubMo", "title":"Dairy-Free Green Goddess Dressing"},
{"url":"http://www.marksdailyapple.com/pork-stuffed-jalapeno-peppers/#axzz29jTSubMo", "title":"Pork-Stuffed Jalapeño Peppers"},
{"url":"http://www.marksdailyapple.com/herb-chicken-cooked-under-a-brick/#axzz29jTSubMo", "title":"Herb Chicken Cooked Under a Brick"},
{"url":"http://www.marksdailyapple.com/balsamic-glazed-drumsticks/#axzz29jTSubMo", "title":"Balsamic-Glazed Drumsticks"},
{"url":"http://www.marksdailyapple.com/slow-cooked-coconut-ginger-pork/#axzz29jTSubMo", "title":"Slow-Cooked Coconut Ginger Pork"},
{"url":"http://www.marksdailyapple.com/lime-and-basil-beef-kebabs/#axzz29jTSubMo", "title":"Lime and Basil Beef Kebabs"},
{"url":"http://www.marksdailyapple.com/taco-bowl-with-crispy-kale-chips/#axzz29jTSubMo", "title":"Taco Bowl with Crispy Kale Chips"},
{"url":"http://www.marksdailyapple.com/grilled-eggs-with-mexican-chorizo/#axzz29jTSubMo", "title":"Grilled Eggs with Mexican Chorizo"},
{"url":"http://www.marksdailyapple.com/banh-mi-salad/#axzz29jTSubMo", "title":"Banh Mi Salad"},
{"url":"http://www.marksdailyapple.com/tender-lemon-parsley-brisket/#axzz29jTSubMo", "title":"Tender Lemon-Parsley Brisket"},
{"url":"http://www.marksdailyapple.com/butter-stuffed-chicken-kiev/#axzz29jTSubMo", "title":"Butter-Stuffed Chicken Kiev"},
{"url":"http://www.marksdailyapple.com/primal-chicken-tikka-masala/#axzz29jTSubMo", "title":"Primal Chicken Tikka Masala"},
{"url":"http://www.nerdfitness.com/blog/2012/02/21/how-to-cook-paleo-spaghetti/", "title":"Paleo Spaghetti"},
{"url":"http://www.nerdfitness.com/blog/2011/02/21/a-decent-meal/", "title":"How to Grow Up And Cook a Decent Meal"},
{"url":"http://www.marksdailyapple.com/fajita-frittata-with-avocado-salsa/#axzz29jTSubMo", "title":"Fajita Frittata with Avocado Salsa"},
{"url":"http://everydaypaleo.com/2012/09/20/moroccan-burgers-and-beet-salad/#more-5442", "title":"Moroccan Burgers and Beet Salad"},
{"url":"http://www.marksdailyapple.com/dark-chocolate-macadamia-bark-sprinkled-with-sea-salt/#axzz29jTSubMo", "title":"Dark Chocolate Macadamia Bark Sprinkled with Sea Salt"},
{"url":"http://www.marksdailyapple.com/primal-texas-chili/#axzz29jTSubMo", "title":"Primal Texas Chili"},
{"url":"http://www.marksdailyapple.com/sausage-egg-breakfast-bites/#axzz29jTSubMo", "title":"Sausage & Egg Breakfast Bites"},
{"url":"http://www.marksdailyapple.com/primal-tex-mex-tortillas-and-taco-seasoning/#axzz29jTSubMo", "title":"Primal Tex-Mex Tortillas and Taco Seasoning"},
{"url":"http://www.marksdailyapple.com/how-to-make-homemade-sausage/#axzz29jTSubMo", "title":"Homemade Sausage Links and Patties"},
{"url":"http://www.marksdailyapple.com/beef-stew-and-chicken-soup-in-35-minutes-or-less/#axzz29jTSubMo", "title":"Beef Stew and Chicken Soup in 35 Minutes or Less"},
{"url":"http://www.marksdailyapple.com/primal-breakfast-casserole/#axzz29jTSubMo", "title":"Primal Breakfast Casserole"},
{"url":"http://www.marksdailyapple.com/vegetable-latkes/#axzz29jTSubMo", "title":"Vegetable Latkes"},
{"url":"http://www.marksdailyapple.com/primal-holiday-desserts/#axzz29jTSubMo", "title":"Primal Holiday Desserts"},
{"url":"http://www.marksdailyapple.com/hazelnut-crusted-chicken-with-stealth-coconut/#axzz29jTSubMo", "title":"Hazelnut Crusted Chicken with Stealth Coconut"},
{"url":"http://paleolunch.org/", "title":"Paleo Lunch - Your Source For Paleo Diet Recipes"},
{"url":"http://www.marksdailyapple.com/how-to-cook-the-perfect-steak/#axzz29jTSubMo", "title":"How to Cook the Perfect Steak"},
{"url":"http://www.marksdailyapple.com/primal-blueprint-snacks/#axzz29jTSubMo", "title":"Primal Blueprint Snacks"},
{"url":"http://www.marksdailyapple.com/top-10-ways-to-go-nuts/#axzz29jTSubMo", "title":"Creative Nut Recipes"},
{"url":"http://www.marksdailyapple.com/primal-energy-bar-redux/#axzz29jTSubMo", "title":"Primal Energy Bar Redux"},
{"url":"http://www.marksdailyapple.com/butter-chicken-in-a-silky-sauce/#axzz29jTSubMo", "title":"Butter Chicken in a Silky Sauce"},
{"url":"http://www.marksdailyapple.com/spicy-chicken-and-bacon-poppers/#axzz29jTSubMo", "title":"Spicy Chicken and Bacon Poppers"},
{"url":"http://www.marksdailyapple.com/crispy-nut-and-herb-fried-chicken-with-creamy-avocado/#axzz29jTSubMo", "title":"Crispy Nut and Herb Fried Chicken with Creamy Avocado"},
{"url":"http://www.marksdailyapple.com/sesame-chicken-and-rice-with-fiery-ginger-and-chile-sauce/#axzz29jTSubMo", "title":"Sesame Chicken and “Rice” with Fiery Ginger and Chile Sauce"},
{"url":"http://www.marksdailyapple.com/crock-pot-pork-stuffed-peppers/#axzz29jTSubMo", "title":"Crock Pot Pork-Stuffed Peppers"},
{"url":"http://www.marksdailyapple.com/garlic-pulled-pork/#axzz29jTSubMo", "title":"Garlic Pulled Pork"},
{"url":"http://www.marksdailyapple.com/cocoa-and-coconut-snacks/#axzz29jTSubMo", "title":"Cocoa and Coconut Snacks"},
{"url":"http://www.marksdailyapple.com/5-sweet-savory-primal-shakes/#axzz29jTSubMo", "title":"5 Sweet and Savory Primal Shakes"},
{"url":"http://www.marksdailyapple.com/almond-banana-pancakes/#axzz29jTSubMo", "title":"Almond Banana Pancakes"},
{"url":"http://www.marksdailyapple.com/omelet-muffins/#axzz29jTSubMo", "title":"Omelet Muffins"},
{"url":"http://www.marksdailyapple.com/frittata-aleta/#axzz29jTSubMo", "title":"Frittata Aleta"},
{"url":"http://www.marksdailyapple.com/primal-meatballs/#axzz29jTSubMo", "title":"Italian Sausage Meatballs with Fresh Herbs"},
{"url":"http://www.marksdailyapple.com/beef-burgundy-recipe/#axzz29jTSubMo", "title":"Beef Burgundy"},
{"url":"http://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe/#axzz29jTSubMo", "title":"Asian Pepper Steak Crock Pot Recipe"},
{"url":"http://everydaypaleo.com/2012/06/29/sun-dried-tomato-meatballs-with-creamy-pesto/", "title":""},
{"url":"http://paleodietlifestyle.com/eggs-benedict-burgers/", "title":"Eggs Benedict Burgers"},
{"url":"http://paleodietlifestyle.com/coconut-squares/", "title":"Coconut Squares"},
{"url":"http://www.paleotable.com/2011/01/asian-marinated-steaks.html", "title":"Asian-Marinated Steaks"},
{"url":"http://www.paleotable.com/2011/01/tango-burgers.html", "title":"Tango Burgers"},
{"url":"http://www.paleotable.com/2011/02/meatza.html", "title":"Meatza"},
{"url":"http://www.paleotable.com/2011/01/baked-chicken-with-roasted-tomatoes.html", "title":"aked Chicken with Roasted Tomatoes"},
{"url":"http://www.paleotable.com/2011/02/blackened-chicken.html", "title":"blackened-chicken"},
{"url":"http://www.paleotable.com/2011/01/breaded-baked-chicken.html", "title":""},
{"url":"http://www.paleotable.com/2011/03/chicken-and-avocado-tostadas.html", "title":""},
{"url":"http://www.paleotable.com/2011/01/sausages-and-pepperonata.html", "title":""},
{"url":"http://www.health-bent.com/soups/paleo-mediterranean-beef-stew", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
{"url":"", "title":""},
]}

BIN
server/body.html Normal file

Binary file not shown.

241
server/keeper.js Normal file
View File

@ -0,0 +1,241 @@
"use strict";
/**
* Created by Martin on 22/02/2016.
*/
var express = require('express');
var http = require('http'), request = require('request'), cheerio = require('cheerio'), util = require('util');
var jsonfile = require('jsonfile'), fs = require('fs'), STRING = require('string');
var zlib = require("zlib");
var log4js = require('log4js');
var logger = log4js.getLogger();
var router = express.Router();
var EventEmitter = require('events');
var nano = require('nano')('http://localhost:5984');
var busEmitter = new EventEmitter();
var db_name = 'keeper';
var dbCouch = nano.use(db_name);
var bodyfile = __dirname + '/' + 'body.html';
var htmlfile = __dirname + '/' + 'testoutput.html';
var generics = ['ARTICLE', 'div.content_column', 'div.post'];
function cleaner(b) {
var _b = b;
var unwanted = ['div#disqus_thread', 'SCRIPT', 'FOOTER', 'div.ssba', '.shareaholic-canvas', '.yarpp-related', 'div.dfad', 'div.postFooterShare', 'div#nextPrevLinks', '.post-comments'];
for (var i = 0; i < unwanted.length; i++) {
_b.find(unwanted[i]).remove();
}
return _b;
}
function insertBookmark(obj) {
logger.debug('Inserting into couch...');
logger.info(util.inspect(obj));
dbCouch.insert(obj, function(err, body,header) {
if (err) {
logger.error('Error inserting into couch');
return;
}
});
logger.debug('Insert done..');
}
var doInsertBookmark = (obj) =>{
// logger.info('sendSocket: ' + JSON.stringify(obj));
insertBookmark(obj);
};
var doGetBookmark = (url) =>{
// logger.info('sendSocket: ' + JSON.stringify(obj));
genericGrab(url);
};
var doGetBookmarkRes = (url,res) =>{
logger.debug('doGetBookmarkRes');
// logger.info('sendSocket: ' + JSON.stringify(obj));
genericGrab(url,res);
};
// Events
busEmitter.on('saveBookmarkData', doInsertBookmark);
busEmitter.on('getBookmark', doGetBookmark);
busEmitter.on('getBookmarkRes', doGetBookmarkRes);
function processBody(body,url) {
var $ = cheerio.load(body);
var title = $('TITLE').text();
// try to find a body to grab
var i = 0;
while (($(generics[i]).length == 0) && (i < generics.length)) {
// logger.info(generics[i]);
// logger.info($(generics[i]));
// logger.info('i: ' + i + ', ' + $(generics[i]).length);
i++;
}
logger.debug(i);
if (i < generics.length) {
var tdihbody = $(generics[i]);
var obj = {};
logger.debug(tdihbody.length);
tdihbody = cleaner(tdihbody);
logger.debug(title);
obj.url=url;
obj.html = $.html();
obj.reduced = STRING(tdihbody.html()).collapseWhitespace().s;
obj.title = STRING(title).collapseWhitespace().s;
busEmitter.emit("saveBookmarkData", obj);
return obj;
}
}
function genericGrab(url,res) {
logger.info(url);
request(url, function (err, resp, body) {
if (err)
throw err;
console.log("headers: ", resp.headers);
console.log(resp.statusCode);
logger.info('A');
logger.info(body);
if (resp.headers.hasOwnProperty('content-encoding')) {
logger.warn('content-encoding');
if (resp.headers['content-encoding'] == 'gzip') {
// to test http://chaosinthekitchen.com/2009/07/lime-and-coconut-chicken/
var gunzip = zlib.createGunzip();
var jsonString = '';
resp.pipe(gunzip);
gunzip.on('data', function (chunk) {
jsonString += chunk;
});
gunzip.on('end', function () {
console.log((jsonString));
callback(JSON.stringify(jsonString));
});
gunzip.on('error', function (e) {
console.log(e);
});
}
else
{
logger.info('Processing other body...');
var b = processBody(body,url);
console.log(b);
if (res != null)
{
res.render('grabbed');
}
}
} else
{
logger.info('Processing body...');
var b = processBody(body,url);
if (res != null)
{
console.log({data:b});
res.render('grabbed',{data:b});
}
}
logger.info('END');
//fs.writeFileSync(htmlfile, tdihbody.html());
// fs.writeFileSync(bodyfile, $.html());
});
}
router.get('/list', function (req, res) {
logger.debug('list..');
dbCouch.view('titles','titles',function(err, body) {
if (!err) {
var outJSON = [];
body.rows.forEach(function(doc) {
logger.info(doc);
outJSON.push({id:doc.id, title:doc.value })
});
//logger.debug(util.inspect(body));
res.writeHead(200, {"ContentType": "application/json"});
res.end(JSON.stringify({list: outJSON}));
} else
{
res.writeHead(500, {"ContentType": "application/json"});
res.end(JSON.stringify({}));
}
});
});
router.get('/entry/:id', function (req, res) {
logger.debug('entry..');
logger.debug(req.params.id);
dbCouch.get(req.params.id,function(err, body) {
if (!err) {
var outJSON = {};
logger.debug(body);
outJSON.title = body.title;
outJSON.reduced = body.reduced;
//logger.debug(util.inspect(body));
res.writeHead(200, {"ContentType": "application/json"});
res.end(JSON.stringify(outJSON));
} else
{
res.writeHead(500, {"ContentType": "application/json"});
res.end(JSON.stringify({}));
}
});
});
router.post('/add', function (req, res) {
logger.debug('add entry..');
var t = req.body;
if (t.hasOwnProperty('url')) {
var url = JSON.parse(t.url.toString());
logger.debug(url);
busEmitter.emit("getBookmark", url);
}
else {
logger.error('No data block!');
}
res.writeHead(200, {"ContentType": "application/json"});
res.end(JSON.stringify({adding: url}));
});
router.get('/new', function (req, res) {
logger.debug('Save new');
busEmitter.emit("getBookmarkRes", req.query.url ,res);
});
module.exports = router;

42
server/maker.js Normal file
View File

@ -0,0 +1,42 @@
var nano = require('nano')('http://localhost:5984');
// clean up the database we created previously
nano.db.destroy('keeper', function() {
// create a new database
nano.db.create('keeper', function() {
// specify the database we are going to use
var keeper = nano.use('keeper');
// and insert a document in it
/* keeper.insert({ crazy: true }, 'rabbit', function(err, body, header) {
if (err) {
console.log('[alice.insert] ', err.message);
return;
}
console.log('you have inserted the rabbit.')
console.log(body);
});*/
keeper.insert(
{ "views":
{ "titles":
{ "map": function(doc) { emit(null, doc.title); } }
}
}, '_design/titles', function (error, response) {
console.log("_design/titles added");
});
keeper.insert(
{ "views":
{ "reducedView":
{ "map": function(doc) { emit(null, [doc.title, doc.reduced]); } }
}
}, '_design/reducedView', function (error, response) {
console.log("_design/reducedView added");
});
});
});/**
* Created by Martin on 02/03/2016.
*/

52
server/simplecrawler.js Normal file
View File

@ -0,0 +1,52 @@
var Crawler = require("simplecrawler"),fs = require('fs');
//var myCrawler = new Crawler("http://www.bbc.co.uk/food/recipes/chicken_piperade_with_23608");
var myCrawler = new Crawler("www.bbc.co.uk", "/food/recipes/chicken_piperade_with_23608", 80);
var htmlfile = __dirname + '/' + 'test.html';
myCrawler.maxDepth = 1;
//myCrawler.interval = 10000; // Ten seconds
myCrawler.maxConcurrency = 1;
myCrawler.on('crawlstart', function() {
console.log('Crawling started...');
});
myCrawler.on('fetchstart ', function(a, b) {
console.log('fetchstart ...');
console.log(a);
console.log(b);
});
myCrawler.on('fetcherror ', function(a, b) {
console.log('Crawling error...');
console.log(a);
console.log(b);
});
myCrawler.on('fetchclienterror ', function(a, b) {
console.log('fetchclienterror error...');
console.log(a);
console.log(b);
});
myCrawler.on('queueadd ', function(a) {
console.log('fetchclienterror error...');
console.log(a);
});
myCrawler.on("fetchcomplete", function(queueItem, responseBuffer, response) {
console.log("I just received %s (%d bytes)", queueItem.url, responseBuffer.length);
console.log("It was a resource of type %s", response.headers['content-type']);
// Do something with the data in responseBuffer
fs.writeFileSync(htmlfile, responseBuffer);
});
myCrawler.start();

63
server/testoutput.html Normal file
View File

@ -0,0 +1,63 @@
<!-- RDFa Breadcrumbs Plugin by Nitin Yawalkar --><div class="breadcrumb breadcrumbs"><div class="rdfa-breadcrumb"><div xmlns:v="http://rdf.data-vocabulary.org/#"><p><span class="breadcrumbs-title"> </span><span typeof="v:Breadcrumb"><a rel="v:url" property="v:title" href="http://www.marksdailyapple.com/" class="home">Home</a></span> <span class="separator">&#xBB;</span> <span typeof="v:Breadcrumb"><a rel="v:url" property="v:title" href="http://www.marksdailyapple.com/category/recipes/" title="Recipes">Recipes</a></span> <span class="separator">&#xBB;</span> Herb Chicken Cooked Under a Brick</p></div></div></div><!-- RDFa Breadcrumbs Plugin by Nitin Yawalkar --> <div class="postLiner">
<div class="postFlag">
<div>
18 Aug </div>
</div>
<div class="postContent postContentInside">
<h1><a href="http://www.marksdailyapple.com/herb-chicken-cooked-under-a-brick/" rel="bookmark" title="Permanent Link to Herb Chicken Cooked Under a Brick">
Herb Chicken Cooked Under a Brick </a></h1>
<div class="wwsgd" style="display:none;"><div style="border: thin dotted black; padding: 10px 10px 0 10px; margin-bottom: 10px;"><p>Welcome! If you want to lose weight, gain muscle, increase energy levels or just generally look and feel healthier you&apos;ve come to the right place.</p>
<p>Here&apos;s where to start:</p>
<ol>
<li>Visit the <a title="Start Here" href="http://www.marksdailyapple.com/welcome-to-marks-daily-apple/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_start_here" target="_blank">Start Here</a> and <a title="Primal Blueprint 101" href="http://www.marksdailyapple.com/primal-blueprint-101/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_pb_101" target="_blank">Primal Blueprint 101</a> pages to learn more about the Primal lifestyle.</li>
<li>Subscribe to my <a title="Subscribe" href="http://www.marksdailyapple.com/subscribe-to-blog/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_newsletter" target="_blank">weekly newsletter</a> to receive an eBook called <em>Primal Blueprint Fitness</em> and more - all for free.</li>
<li>Cut to the chase by visiting <a title="PrimalBlueprint.com" href="https://www.primalblueprint.com/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_pb_homepage" target="_blank">PrimalBlueprint.com</a>. There you&apos;ll find <a title="Books and Media" href="https://www.primalblueprint.com/books/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_books" target="_blank">books</a>, <a title="Primal Kitchen" href="https://www.primalblueprint.com/primal-kitchen/mayo-12-oz/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_food" target="_blank">food</a>, and the best <a title="Supplements" href="https://www.primalblueprint.com/supplements/?utm_source=mda_wwsgd&amp;utm_medium=link&amp;utm_campaign=mda_wwsgd_supplements" target="_blank">supplements</a> on the planet to help you take control of your health for life.</li>
</ol>
<p>Thanks for visiting!</p></div></div><p><img class="alignright" title="Brick Chicken" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/BrickChicken2.jpg" alt="" width="320" height="212">Who would&#x2019;ve guessed that the secret to the juiciest, most tender <a title="Choosing Chicken: A Primal Purchasing Guide " href="http://www.marksdailyapple.com/chicken-labels/#axzz2302s02YL">chicken</a> breast you&#x2019;ve ever tasted was a brick? Not a fancy culinary instrument that happens to be called a brick, but an actual brick, the type used to build houses and fireplaces and to landscape yards. A brick set on top of a cooking chicken applies just enough pressure to push the bird against the hot pan, crisping up the skin and cooking all the meat evenly and quickly before it dries out. The bird comes out juicy and tender on the inside, crispy and golden on the outside.</p>
<p><span id="more-30764"></span></p>
<p>As long as you have a few bricks laying around, the technique couldn&#x2019;t be easier. First, remove the backbone from the chicken so the bird can be splayed out flat. With a pair of kitchen shears, this is quick work. Next, rub the chicken down with something tasty. In this case, a smoky, herbal rub made from thyme, oregano, garlic and smoked paprika add tons of flavor. You can go this route, or use any of your own favorite rubs or <a title="How to Marinate Meat" href="http://www.marksdailyapple.com/how-to-marinate-meat/#axzz2302kCvBn">marinades</a>.</p>
<p>Now, it&#x2019;s time for the bricks to work their magic. Heat an ovenproof skillet on the stove and set the chicken in it, skin side down. Put the bricks on top and leave it alone for 6-8 minutes. Transfer the skillet to a hot oven and leave the chicken alone again, with bricks on top, for 20 minutes or so. Flip the bird, let it cook a little longer, and you&#x2019;re minutes away from tasting a culinary miracle. The chicken breasts are not only moist, they&#x2019;re down right succulent. The rest of the bird is amazing too. You might as well make room in your kitchen cupboard now to permanently store two bricks. After trying this recipe, you&#x2019;ll never want to roast chicken any other way.</p>
<p><strong>Ingredients:</strong></p>
<img class="alignnone" title="Ingredients" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/ingredients-29.jpg" alt="" width="540" height="360">
<ul>
<li>1 whole chicken, 3-4 pounds (approx 1.5 kg)</li>
<li>2 tablespoons oil (30 ml)</li>
<li>1 tablespoon fresh thyme (15 ml)</li>
<li>1 teaspoon dried oregano (5 ml)</li>
<li>1/4 teaspoon garlic powder (approx 1 ml)</li>
<li>1 teaspoon smoked paprika (5 ml)</li>
<li>1/2 teaspoon salt (2.5 ml)</li>
<li>1/4 teaspoon pepper (approx 1 ml)</li>
</ul>
<p><strong>Tools:</strong></p>
<ul>
<li>Ovenproof skillet</li>
<li>1-2 bricks, wrapped in foil</li>
</ul>
<p><strong>Instructions:</strong></p>
<p>In a bowl, mix together 1 tablespoon (15 ml) of the oil with the thyme, oregano, garlic powder, paprika, salt and pepper. Set aside.</p>
<p>Set the chicken on a cutting board breast side down.</p>
<p>Starting at the tail, use a knife or better yet, kitchen shears, to cut all the way down the back, keeping as close to the backbone as you can. Then, cut down the other side of the backbone, splitting the chicken open. Remove the backbone.</p>
<img class="alignnone" title="Step 1" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step1-2.jpg" alt="" width="540" height="441">
<img class="alignnone" title="Step 2" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step2-2.jpg" alt="" width="540" height="360">
<p>Spread the chicken open, lightly pressing down to flatten it. Rub the spice mixture all over the chicken, getting some under the skin and directly onto the meat.</p>
<img class="alignnone" title="Step 3" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step3-2.jpg" alt="" width="540" height="360">
<p>Preheat oven to 400 &#xB0;F (204 &#xB0;C)</p>
<p>Heat the remaining tablespoon of oil in a large ovenproof skillet over medium-high heat. When the skillet is really hot, add the chicken skin side down and place the bricks on top to push the bird down against the skillet. You can get away with using one brick if the chicken is small, but larger birds usually need two bricks.</p>
<img class="alignnone" title="Step 4" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step4-2.jpg" alt="" width="540" height="360">
<p>Cook until the skin is golden brown, 6-8 minutes (it&#x2019;s okay to take the bricks off and peek).</p>
<img class="alignnone" title="Cooked Chicken" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/cooked_chicken.jpg" alt="" width="540" height="360">
<p>Put the skillet in the oven and roast the chicken with the bricks on top for 25 minutes. Take off the bricks and turn the chicken over. Put the bricks back on and roast another 10 or so minutes until the chicken is done. The juices should run clear when you pierce the bird with a fork; you can also stab it with a thermometer and make sure it reads at least 165 &#xB0;F (74 &#xB0;C).</p>
<p><img class="alignnone" title="Brick Chicken" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/BrickChicken2.jpg" alt="" width="540" height="360"><br>
<div></div></p>
<div class="clear"></div>
<div class="postFooter">
<p class="postAuthor smallCaps">Posted By:
Worker Bee </p></div>
<div class="clear"></div>
<div class="clear"></div>
</div>
</div>

20
views/grabbed.ejs Normal file
View File

@ -0,0 +1,20 @@
<html>
<head>
<link href="//cdn.muicss.com/mui-0.4.6/css/mui.min.css" rel="stylesheet" type="text/css" />
<script src="//cdn.muicss.com/mui-0.4.6/js/mui.min.js"></script>
</head>
<body>
<div class="mui-container">
<div class="mui-panel">
<div class="mui-text-headline mui-text-accent">Grabbed</div>
</div>
<div id="container" class="mui-panel">
<%= data.title %>
</div>
<div id="container" class="mui-panel">
<%- data.reduced %>
</div>
</div>
</body>
</html>