mirror of
https://gitlab.silvrtree.co.uk/martind2000/recipes.git
synced 2025-01-11 02:55:08 +00:00
basic working recipe app
This commit is contained in:
parent
4ee49fe2ac
commit
65b2ec022e
167
app/css/app.css
Normal file
167
app/css/app.css
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
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: 768px) {
|
||||||
|
#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;
|
||||||
|
}
|
48
app/index.html
Normal file
48
app/index.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!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="//cdn.muicss.com/mui-0.4.6/css/mui.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="css/app.css" rel="stylesheet" type="text/css" />
|
||||||
|
<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="listContainer">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="mui-divider"><div id="addNew">+ Add</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>
|
||||||
|
<script src="js/shell.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</html>
|
129
app/js/app.js
Normal file
129
app/js/app.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* Created by Martin on 24/02/2016.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
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 .entry").not('.emptyMessage').click(function () {
|
||||||
|
console.log('Clicked list. ' + this.id);
|
||||||
|
getRecipe(this.id);
|
||||||
|
});
|
||||||
|
}, displayPage = function (obj) {
|
||||||
|
var $bodyContents = $('#bodyContents');
|
||||||
|
|
||||||
|
if (obj.list[0].body.length > 0) {
|
||||||
|
$bodyContents.empty();
|
||||||
|
$bodyContents.append(obj.list[0].body);
|
||||||
|
}
|
||||||
|
}, getRecipe = function (id) {
|
||||||
|
console.log('get recipe');
|
||||||
|
var url = '/recipes/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 = '/recipes/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 = '/recipes/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 (data) {
|
||||||
|
console.log(data);
|
||||||
|
// displayList(data);
|
||||||
|
},
|
||||||
|
error: function (xhr, type) {
|
||||||
|
console.log("ajax error");
|
||||||
|
console.log(xhr);
|
||||||
|
console.log(type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
start = function () {
|
||||||
|
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#addNew').click(function () {
|
||||||
|
var url = prompt("Please enter a new url", "");
|
||||||
|
|
||||||
|
if (url != null) {
|
||||||
|
console.log('Adding: ' + url);
|
||||||
|
addNew(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
start();
|
||||||
|
})();
|
45
app/js/shell.js
Normal file
45
app/js/shell.js
Normal 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
505
app/libs/ejs.js
Normal 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 '<' 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
1
app/libs/ejs_production.js
Normal file
1
app/libs/ejs_production.js
Normal file
File diff suppressed because one or more lines are too long
200
app/libs/view.js
Normal file
200
app/libs/view.js
Normal 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
4
app/partials/list.ejs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
<% for(var i=0; i<list.length; i++) {%>
|
||||||
|
<div class="entry" id="<%= list[i].id %>"><%= list[i].title %></div>
|
||||||
|
<% } %>
|
@ -15,6 +15,7 @@
|
|||||||
"express": "^4.13.4",
|
"express": "^4.13.4",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"serve-favicon": "^2.3.0",
|
"serve-favicon": "^2.3.0",
|
||||||
|
"sqlite3": "^3.1.1",
|
||||||
"string": "^3.3.1"
|
"string": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -5,9 +5,12 @@ var express = require('express'), path = require('path'), http = require('http')
|
|||||||
logger = require('morgan'),
|
logger = require('morgan'),
|
||||||
cookieParser = require('cookie-parser'),
|
cookieParser = require('cookie-parser'),
|
||||||
bodyParser = require('body-parser'),
|
bodyParser = require('body-parser'),
|
||||||
|
recipes = require('./server/recipes')
|
||||||
|
|
||||||
|
/*
|
||||||
routes = require('./routes/index'),
|
routes = require('./routes/index'),
|
||||||
users = require('./routes/users')
|
users = require('./routes/users')
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
//train = require('lib/train')
|
//train = require('lib/train')
|
||||||
@ -22,9 +25,9 @@ app.use(logger('dev'));
|
|||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'app')));
|
||||||
|
|
||||||
app.use('/', routes);
|
app.use('/recipes', recipes);
|
||||||
//app.use('/users', users);
|
//app.use('/users', users);
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
2603
server/body.html
2603
server/body.html
File diff suppressed because one or more lines are too long
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by Martin on 22/02/2016.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var http = require('http'), request = require('request'), cheerio = require('cheerio'), util = require('util');
|
|
||||||
var jsonfile = require('jsonfile'), fs = require('fs'), STRING = require('string');
|
|
||||||
var log4js = require('log4js');
|
|
||||||
var logger = log4js.getLogger();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
generic: function (url) {
|
|
||||||
logger.info(url);
|
|
||||||
request(url, function (err, resp, body) {
|
|
||||||
if (err)
|
|
||||||
throw err;
|
|
||||||
|
|
||||||
$ = 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)) {
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
logger.debug(i);
|
|
||||||
|
|
||||||
if (i < generics.length) {
|
|
||||||
var tdihbody = $(generics[i]);
|
|
||||||
logger.debug(tdihbody.length);
|
|
||||||
|
|
||||||
tdihbody = cleaner(tdihbody);
|
|
||||||
logger.debug(title);
|
|
||||||
|
|
||||||
fs.writeFileSync(htmlfile, tdihbody.html());
|
|
||||||
}
|
|
||||||
fs.writeFileSync(bodyfile, $.html());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//module.exports.grabMarksDailyApple('http://www.marksdailyapple.com/spiced-pork-and-butternut-squash-with-sage');
|
|
||||||
module.exports.generic('http://www.health-bent.com/soups/paleo-mediterranean-beef-stew');
|
|
215
server/recipes.js
Normal file
215
server/recipes.js
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* 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 log4js = require('log4js');
|
||||||
|
var logger = log4js.getLogger();
|
||||||
|
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
var sqlite3 = require('sqlite3').verbose();
|
||||||
|
var EventEmitter = require('events');
|
||||||
|
var busEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
var dbfile = process.env.DB_HOME + '/' + "recipes.db";
|
||||||
|
var bodyfile = __dirname + '/' + 'body.html';
|
||||||
|
var htmlfile = __dirname + '/' + 'testoutput.html';
|
||||||
|
var generics = ['ARTICLE', 'div.content_column', 'div.post'];
|
||||||
|
|
||||||
|
var db;
|
||||||
|
|
||||||
|
function createDB() {
|
||||||
|
logger.debug('Creating recipes db...');
|
||||||
|
logger.debug(dbfile);
|
||||||
|
if (!fs.existsSync(dbfile)) {
|
||||||
|
logger.debug('creating db file');
|
||||||
|
fs.openSync(dbfile, 'w');
|
||||||
|
db = new sqlite3.Database(dbfile, createTable);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info('Database already created.');
|
||||||
|
connectDB();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectDB() {
|
||||||
|
"use strict";
|
||||||
|
logger.debug('Connect db.');
|
||||||
|
db = new sqlite3.Database(dbfile);
|
||||||
|
//logger.debug(temp_db);
|
||||||
|
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTable() {
|
||||||
|
logger.debug('Creating temp table...');
|
||||||
|
db.run('CREATE TABLE `recipes` (`id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, `url` TEXT, `html` TEXT, `reduced` TEXT );');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDB() {
|
||||||
|
"use strict";
|
||||||
|
logger.debug('Closing db.');
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 insertRecipe(obj) {
|
||||||
|
logger.debug(obj);
|
||||||
|
|
||||||
|
db.run('BEGIN TRANSACTION');
|
||||||
|
db.run('INSERT INTO `recipes`(`url`,`html`,`reduced`,`title`) VALUES (?,?,?,?);', obj);
|
||||||
|
db.run('commit');
|
||||||
|
}
|
||||||
|
|
||||||
|
var doInsertRecipe = (obj) =>{
|
||||||
|
// logger.info('sendSocket: ' + JSON.stringify(obj));
|
||||||
|
insertRecipe(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
var doGetRecipe = (url) =>{
|
||||||
|
// logger.info('sendSocket: ' + JSON.stringify(obj));
|
||||||
|
genericGrab(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
busEmitter.on('saveRecipeData', doInsertRecipe);
|
||||||
|
|
||||||
|
busEmitter.on('getRecipe', doGetRecipe);
|
||||||
|
|
||||||
|
function genericGrab(url) {
|
||||||
|
logger.info(url);
|
||||||
|
request(url, function (err, resp, body) {
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
|
||||||
|
$ = 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)) {
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
logger.debug(i);
|
||||||
|
|
||||||
|
if (i < generics.length) {
|
||||||
|
var tdihbody = $(generics[i]);
|
||||||
|
var obj = [];
|
||||||
|
|
||||||
|
logger.debug(tdihbody.length);
|
||||||
|
tdihbody = cleaner(tdihbody);
|
||||||
|
logger.debug(title);
|
||||||
|
fs.writeFileSync(htmlfile, tdihbody.html());
|
||||||
|
obj.push(url);
|
||||||
|
obj.push($.html());
|
||||||
|
obj.push(tdihbody.html());
|
||||||
|
obj.push(title);
|
||||||
|
busEmitter.emit("saveRecipeData", obj);
|
||||||
|
|
||||||
|
}
|
||||||
|
fs.writeFileSync(bodyfile, $.html());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/list', function(req, res) {
|
||||||
|
logger.debug('list..');
|
||||||
|
|
||||||
|
// 'select id, title from `recipes` where title is not null;'
|
||||||
|
db.all('select id, title from `recipes` where title is not null;', function (err, rows) {
|
||||||
|
|
||||||
|
var out = [];
|
||||||
|
// logger.debug(err);
|
||||||
|
// logger.debug(rows);
|
||||||
|
rows.forEach(function (row) {
|
||||||
|
|
||||||
|
out.push({"id": row.id, "title": row.title});
|
||||||
|
});
|
||||||
|
|
||||||
|
res.writeHead(200, {"ContentType": "application/json"});
|
||||||
|
//res.send(JSON.stringify(t));
|
||||||
|
res.end(JSON.stringify({list:out}));
|
||||||
|
|
||||||
|
//closeDB();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/entry/:id', function(req, res) {
|
||||||
|
logger.debug('entry..');
|
||||||
|
|
||||||
|
logger.debug(req.params.id);
|
||||||
|
// 'select id, title from `recipes` where title is not null;'
|
||||||
|
var sqlstr = 'select * from `recipes` where id = ' + req.params.id + ';';
|
||||||
|
db.all(sqlstr, function (err, rows) {
|
||||||
|
|
||||||
|
var out = [];
|
||||||
|
// logger.debug(err);
|
||||||
|
// logger.debug(rows);
|
||||||
|
rows.forEach(function (row) {
|
||||||
|
|
||||||
|
var d = {"id": row.id, "title": row.title};
|
||||||
|
|
||||||
|
if (row.reduced.length !== 0) {
|
||||||
|
d.body = row.reduced;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.body = row.html;
|
||||||
|
}
|
||||||
|
out.push(d);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.writeHead(200, {"ContentType": "application/json"});
|
||||||
|
//res.send(JSON.stringify(t));
|
||||||
|
res.end(JSON.stringify({list:out}));
|
||||||
|
|
||||||
|
//closeDB();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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("getRecipe", url);
|
||||||
|
|
||||||
|
|
||||||
|
/* if (data.hasOwnProperty('temp')) {
|
||||||
|
// busEmitter.emit("saveTempData", {time: now.toJSON(), value: parseFloat(data.temp)});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error('No url to add!');
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.error('No data block!');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
|
createDB();
|
||||||
|
//module.exports.grabMarksDailyApple('http://www.marksdailyapple.com/spiced-pork-and-butternut-squash-with-sage');
|
||||||
|
//module.exports.generic('http://www.health-bent.com/soups/paleo-mediterranean-beef-stew');
|
@ -1,113 +1,65 @@
|
|||||||
|
|
||||||
|
<!-- 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">»</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">»</span> Shakshuka (Eggs Poached in Spicy Tomato Sauce)</p></div></div></div><!-- RDFa Breadcrumbs Plugin by Nitin Yawalkar --> <div class="postLiner">
|
||||||
<div class="post-header">
|
<div class="postFlag">
|
||||||
|
<div>
|
||||||
<span class="cat"><a href="http://www.health-bent.com/beef" rel="category tag">Beef</a> <a href="http://www.health-bent.com/soups" rel="category tag">Soups</a></span>
|
1 Sep </div>
|
||||||
|
</div>
|
||||||
<h1>Mediterranean Beef Stew with Green Olive Pesto</h1>
|
<div class="postContent postContentInside">
|
||||||
|
<h1><a href="http://www.marksdailyapple.com/shakshuka-eggs-poached-in-spicy-tomato-sauce/" rel="bookmark" title="Permanent Link to Shakshuka (Eggs Poached in Spicy Tomato Sauce)">
|
||||||
<span class="title-divider"></span>
|
Shakshuka (Eggs Poached in Spicy Tomato Sauce) </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've come to the right place.</p>
|
||||||
<span class="post-date">Posted on April 19, 2010</span>
|
<p>Here's where to start:</p>
|
||||||
|
<ol>
|
||||||
</div>
|
<li>Visit the <a title="Start Here" href="http://www.marksdailyapple.com/welcome-to-marks-daily-apple/?utm_source=mda_wwsgd&utm_medium=link&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&utm_medium=link&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&utm_medium=link&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&utm_medium=link&utm_campaign=mda_wwsgd_pb_homepage" target="_blank">PrimalBlueprint.com</a>. There you'll find <a title="Books and Media" href="https://www.primalblueprint.com/books/?utm_source=mda_wwsgd&utm_medium=link&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&utm_medium=link&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&utm_medium=link&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="Shakshuka" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/shakshuka2.jpg" alt="" width="320" height="212">Whether you’re looking for a new breakfast idea or are fond of serving breakfast for dinner, shakshuka fits the bill. Instead of calling the dish shakshuka you can also just call it “Eggs Poached in Spicy Tomato Sauce” because that’s exactly what this straightforward but surprisingly delicious meal is.</p>
|
||||||
<div class="post-entry">
|
<p>Especially popular in Israel, shakshuka is loved around the world for its comforting flavor and simple preparation. Although the sauce is often sopped up with pita bread, it’s thick enough that you can skip the bread and eat it with a spoon (or spread extra sauce over a hunk of <a title="How to Cook the Perfect Steak" href="http://www.marksdailyapple.com/how-to-cook-the-perfect-steak/#axzz1rkfvTaeG">grilled meat</a> for a really fantastic meal.)</p>
|
||||||
|
<p><span id="more-30717"></span></p>
|
||||||
|
<p>Most recipes for shakshuka call for <a title="Are Your Canned Foods Safe to Eat?" href="http://www.marksdailyapple.com/are-your-canned-foods-safe-to-eat-a-bpa-free-buying-guide/#axzz1rkfvTaeG">canned (or boxed) tomatoes</a>, but you shouldn’t hesitate to use plump, super-ripe fresh tomatoes if you can find them. Tomatoes are the main ingredient in shakshuka and some say that little else, besides <a title="Egg Purchasing Guide" href="http://www.marksdailyapple.com/egg-purchasing-guide/">eggs</a> and garlic, should be added. However, this version leans in the direction of spicing things up with more flavor and variety. Onion, bell pepper, jalapeno, cumin and paprika make the meal more than just a pot of simmered tomatoes.</p>
|
||||||
<div id="getsocialmain"><div class="pf-content"><div class="printfriendly pf-aligncenter"><a href="#" rel="nofollow" onclick="window.print(); return false;" class="noslimstat"><img src="http://www.health-bent.com/wp-content/uploads/print-button.png" alt="Print Friendly"></a></div><p><img src="http://www.health-bent.com/wp-content/uploads/2010/04/done1.jpg" alt="" width="500" height="375"><br>
|
<p>The eggs are added at the end and then cooked until just set. The contrasting flavors and textures in your bowl – creamy, soft eggs swimming in thick, spicy sauce – is what shakshuka is all about.</p>
|
||||||
Beef stew, also known as beef bourguignon, is good, but boring–and we personally think it tastes like straight up sour wine and that is not very tasty, especially by the spoonful. So we’ve added a bit of balsamic vinegar and raisins to counter the sour with a bit of sweet. We’re also swirling in a nice, briny, herbal pesto to brighten up the braise. Another change, we’re not using a crock pot! The dutch oven allows liquid to evaporate from the pot, thus creating a thicker, more intense flavored stew. And heck, I would rather eat in 2 hours than in 6 hours, agreed?</p>
|
<p><em> Servings: 4</em></p>
|
||||||
<h1>Ingredients</h1>
|
<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-28.jpg" alt="" width="540" height="378">
|
||||||
<ul>
|
<ul>
|
||||||
<li>2 T fat of your choice</li>
|
<li>1/4 cup olive oil</li>
|
||||||
<li>2 lb. chuck shoulder, cubed</li>
|
<li>1 to 3 jalapeno peppers, (depending on how spicy you like it) seeded and finely chopped</li>
|
||||||
<li>1 yellow onion, chopped</li>
|
<li>1 green bell pepper, cut into thin strips</li>
|
||||||
<li>3 cloves garlic, finely chopped</li>
|
<li>1 white or yellow onion, finely chopped</li>
|
||||||
<li>1 carrot, finely chopped</li>
|
<li>4 cloves of garlic, finely chopped</li>
|
||||||
<li>3/4 c raisins</li>
|
<li>1/2 teaspoon ground cumin</li>
|
||||||
<li>(1) 28 oz. can fire roasted crushed tomatoes</li>
|
<li>2 teaspoons paprika</li>
|
||||||
<li>1 c red wine</li>
|
<li>28-ounces whole peeled tomatoes in their juice or 2 pounds fresh tomatoes, chopped</li>
|
||||||
<li>1/4 c balsamic vinegar</li>
|
<li>4 to 6 eggs</li>
|
||||||
<li>1 lemon, sliced and seeds removed</li>
|
<li>1/4 cup roughly chopped parsley</li>
|
||||||
<li>S&P</li>
|
<li>Optional: crumbled feta cheese</li>
|
||||||
|
<li>Salt to taste</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>For the Pesto</strong></p>
|
<p><strong>Instructions:</strong></p>
|
||||||
<ul>
|
<p>Preheat the oven to 400 degrees Fahrenheit.</p>
|
||||||
<li>1 handful of basil leaves</li>
|
<p>Heat olive oil over medium-high heat in a deep skillet. Add peppers and onion and sauté until onion is lightly browned, about five minutes.</p>
|
||||||
<li>1/2 handful of mint leaves</li>
|
<img class="alignnone" title="Step 1" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step1-1.jpg" alt="" width="540" height="360">
|
||||||
<li>1/2 c green olives (about 20)</li>
|
<p>Add garlic, cumin and paprika and sauté one minute more.</p>
|
||||||
<li>3 T extra virgin olive oil</li>
|
<img class="alignnone" title="Step 2" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step2-1.jpg" alt="" width="540" height="362">
|
||||||
</ul>
|
<p>Add tomatoes. Break them apart with a large spoon or spatula as they cook. Reduce heat slightly and simmer 15-20 minutes (longer if tomatoes are fresh), stirring occasionally, until sauce has thickened and most of the liquid is gone. Add salt to taste.</p>
|
||||||
<h2>Method</h2>
|
<img class="alignnone" title="Step 3" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step3-1.jpg" alt="" width="540" height="360">
|
||||||
<p>Preheat the oven to 350ºF.</p>
|
<p>Crack the eggs evenly around the skillet. Place the skillet in the oven and cook until the egg whites are set, 6-8 minutes.</p>
|
||||||
<p>In a dutch oven melt your fat. Salt and pepper the cubed chuck pieces, add them to the pot and let them brown on each side. Don’t fidget with them or remove them until you see a visible brown crust on the meat. Remove and reserve on a plate.  To the pot, add the onion, garlic and carrot. Let them sweat and saute until soft, about 10 minutes. Add the meat back to the pot, along with the tomatoes, raisins, red wine and balsamic vinegar. Stir to combine. Top with sliced lemons.</p>
|
<img class="alignnone" title="Step 4" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/step4-1.jpg" alt="" width="540" height="369">
|
||||||
<p><a href="http://www.health-bent.com/wp-content/uploads/2010/04/pre-cook.jpg"><img class="size-thumbnail wp-image-159 alignnone" title="pre-cook" src="http://www.health-bent.com/wp-content/uploads/2010/04/pre-cook-150x150.jpg" alt="" width="150" height="150"></a><a href="../wp-content/uploads/2010/04/parchment.jpg"><img title="parchment" src="../wp-content/uploads/2010/04/parchment-150x150.jpg" alt="" width="150" height="150"></a><a href="../wp-content/uploads/2010/04/cooked.jpg"><img title="cooked" src="../wp-content/uploads/2010/04/cooked-150x150.jpg" alt="" width="150" height="150"></a></p>
|
<p>Garnish with parsley (and feta). Serve warm.</p>
|
||||||
<p>Lay a piece of parchment paper of the the top of the pot and press it down into the pot. Place in the oven and braise for 2 hours. Taste the meat, it should be super tender, if it’s not give it another half hour or so.</p>
|
<p><img class="alignnone" title="Shakshuka" src="http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/shakshuka1.jpg" alt="" width="540" height="374"><br>
|
||||||
<p>Once you’re about 10 minutes from the stew finishing; combine all the ingredients for the pesto in a mini food processor. Pulse until everything has come together.</p>
|
|
||||||
<p>When the stew is out of the oven, take the lemon rinds out, but leave in the flesh…it’ll separate very easily. Swirl in the pesto and serve.</p>
|
<div id="df2vb41" class="dfads-javascript-load"></div>
|
||||||
</div></div><!-- #getsocialmain -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="post-share">
|
<noscript></noscript>
|
||||||
|
</p>
|
||||||
<div class="post-share-box share-comments">
|
|
||||||
<a href="http://www.health-bent.com/soups/paleo-mediterranean-beef-stew#comments"><span>20</span> Comments</a> </div>
|
<div class="clear"></div>
|
||||||
|
<div class="postFooter">
|
||||||
<div class="post-share-box share-buttons">
|
<p class="postAuthor smallCaps">Posted By:
|
||||||
<a target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http://www.health-bent.com/soups/paleo-mediterranean-beef-stew"><i class="fa fa-facebook"></i></a>
|
Worker Bee </p></div>
|
||||||
<a target="_blank" href="https://twitter.com/home?status=Check%20out%20this%20article:%20Mediterranean+Beef+Stew+with+Green+Olive+Pesto%20-%20http://www.health-bent.com/soups/paleo-mediterranean-beef-stew"><i class="fa fa-twitter"></i></a>
|
<div class="clear"></div>
|
||||||
<a data-pin-do="skipLink" target="_blank" href="https://pinterest.com/pin/create/button/?url=http://www.health-bent.com/soups/paleo-mediterranean-beef-stew&media=http://www.health-bent.com/wp-content/uploads/2010/04/done1.jpg&description=Mediterranean Beef Stew with Green Olive Pesto"><i class="fa fa-pinterest"></i></a>
|
|
||||||
<a target="_blank" href="https://plus.google.com/share?url=http://www.health-bent.com/soups/paleo-mediterranean-beef-stew"><i class="fa fa-google-plus"></i></a>
|
<div class="clear"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="post-share-box share-author">
|
|
||||||
<span>By</span> <a href="http://www.health-bent.com/author/megan" title="Posts by Megan Keatley" rel="author">Megan Keatley</a> </div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="post-author">
|
|
||||||
|
|
||||||
<div class="author-img">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="author-content">
|
|
||||||
<h5><a href="http://www.health-bent.com/author/megan" title="Posts by Megan Keatley" rel="author">Megan Keatley</a></h5>
|
|
||||||
<p>Co-author of the bestselling cookbook <a href="http://bitly.com/primalcravings">Primal Cravings</a>.
|
|
||||||
Co-owner of <a href="http://base10crossfit.com">Base 10 CrossFit</a> in Columbia, SC , and co-maker of Food Worth Eating.</p>
|
|
||||||
<a target="_blank" class="author-social" href="http://facebook.com/healthbent"><i class="fa fa-facebook"></i></a> <a target="_blank" class="author-social" href="http://twitter.com/healthbentsays"><i class="fa fa-twitter"></i></a> <a target="_blank" class="author-social" href="http://instagram.com/megankeatley"><i class="fa fa-instagram"></i></a> </div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="post-related"><div class="post-box"><h4 class="post-box-title"><span>You Might Also Like</span></h4></div>
|
|
||||||
<div class="item-related">
|
|
||||||
|
|
||||||
<a href="http://www.health-bent.com/beef/braised-short-ribs-with-figs"><img width="520" height="347" src="http://www.health-bent.com/wp-content/uploads/short-ribs.jpg" class="attachment-misc-thumb size-misc-thumb wp-post-image" alt="short-ribs" srcset="http://www.health-bent.com/wp-content/uploads/short-ribs-300x200.jpg 300w, http://www.health-bent.com/wp-content/uploads/short-ribs.jpg 625w" sizes="(max-width: 520px) 100vw, 520px"></a>
|
|
||||||
|
|
||||||
<h3><a href="http://www.health-bent.com/beef/braised-short-ribs-with-figs">Braised Short Ribs with Figs</a></h3>
|
|
||||||
<span class="date">October 7, 2011</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="item-related">
|
|
||||||
|
|
||||||
<a href="http://www.health-bent.com/soups/paleo-bacon-egg-breakfast-chili"><img width="520" height="347" src="http://www.health-bent.com/wp-content/uploads/paleobreakfastchili-copy.jpg" class="attachment-misc-thumb size-misc-thumb wp-post-image" alt="paleobreakfastchili copy" srcset="http://www.health-bent.com/wp-content/uploads/paleobreakfastchili-copy-300x200.jpg 300w, http://www.health-bent.com/wp-content/uploads/paleobreakfastchili-copy.jpg 625w" sizes="(max-width: 520px) 100vw, 520px"></a>
|
|
||||||
|
|
||||||
<h3><a href="http://www.health-bent.com/soups/paleo-bacon-egg-breakfast-chili">Bacon & Egg Breakfast Chili</a></h3>
|
|
||||||
<span class="date">November 8, 2012</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="item-related">
|
|
||||||
|
|
||||||
|
|
||||||
<h3><a href="http://www.health-bent.com/beef/paleo-chili-cheese-dogs">Primal Chili Cheese Dogs</a></h3>
|
|
||||||
<span class="date">April 11, 2010</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user