init
This commit is contained in:
commit
9e2c07ba38
55
.eslintrc
Normal file
55
.eslintrc
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": false,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "stroustrup", {}],
|
||||
"camelcase": "error",
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": [1, "last"],
|
||||
"consistent-this": [1, "_this"],
|
||||
"curly": [1, "multi"],
|
||||
"eol-last": 1,
|
||||
"eqeqeq": 1,
|
||||
"func-names": 1,
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||
"max-len": [1, 180, 2, { "ignoreTemplateLiterals": true, "ignoreStrings": true, "ignoreUrls": true, "ignoreTrailingComments": true, "ignoreComments": true }], // 2 spaces per tab, max 80 chars per line
|
||||
"new-cap": 1,
|
||||
"newline-before-return": "error",
|
||||
"no-array-constructor": 1,
|
||||
"no-inner-declarations": [1, "both"],
|
||||
"no-mixed-spaces-and-tabs": 1,
|
||||
"no-multi-spaces": 2,
|
||||
"no-new-object": 1,
|
||||
"no-shadow-restricted-names": 1,
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||
"prefer-const": "error",
|
||||
"prefer-template": "error",
|
||||
"one-var": 0,
|
||||
"quote-props": ["error", "always"],
|
||||
"quotes": [1, "single"],
|
||||
"radix": 1,
|
||||
"semi": [1, "always"],
|
||||
"space-before-blocks": [1, "always"],
|
||||
"space-infix-ops": 1,
|
||||
"vars-on-top": 1,
|
||||
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||
}
|
||||
|
||||
}
|
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
|
||||
|
||||
# artefacts/screenshots/*.png
|
||||
artefacts/*.txt
|
||||
artefacts/*.json
|
||||
# artefacts/*.html
|
||||
# artefacts/*
|
||||
|
||||
/tests/*.zip
|
||||
|
||||
/output/
|
||||
/dist/
|
||||
!/tests/data/
|
||||
/tests/sink/
|
||||
/debug/
|
||||
/update.sh
|
||||
/setup/web/
|
||||
/backup/
|
||||
|
||||
/archive.tar.gz
|
||||
/user/
|
||||
/zip
|
||||
!/artefacts/
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/*
|
||||
public
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@ -0,0 +1,12 @@
|
||||
language: node_js
|
||||
|
||||
sudo: false
|
||||
|
||||
node_js:
|
||||
- "8.11.1"
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
33
gulp/backbone.js
Normal file
33
gulp/backbone.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const browserify = require('browserify');
|
||||
const gulp = require('gulp');
|
||||
const source = require('vinyl-source-stream');
|
||||
const buffer = require('vinyl-buffer');
|
||||
const uglify = require('gulp-uglify-es').default;
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const gutil = require('gulp-util');
|
||||
const rename = require('gulp-rename');
|
||||
const stripDebug = require('gulp-strip-debug');
|
||||
|
||||
|
||||
gulp.task('bundleApp', function () {
|
||||
// set up the browserify instance on a task basis
|
||||
const b = browserify({
|
||||
'debug': true,
|
||||
'entries': './src/v1/js/app.js'
|
||||
});
|
||||
|
||||
return b.bundle()
|
||||
.pipe(source('app.js'))
|
||||
.pipe(buffer())
|
||||
// .pipe(stripDebug())
|
||||
.pipe(rename('bundle.js'))
|
||||
|
||||
.pipe(sourcemaps.init({ 'loadMaps': true }))
|
||||
// Add transformation tasks to the pipeline here.
|
||||
// .pipe(uglify())
|
||||
.on('error', gutil.log)
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('./live/js'));
|
||||
});
|
5
gulpfile.js
Normal file
5
gulpfile.js
Normal file
@ -0,0 +1,5 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const requireDir = require('require-dir');
|
||||
|
||||
requireDir('./gulp');
|
407
list.js
Normal file
407
list.js
Normal file
@ -0,0 +1,407 @@
|
||||
|
||||
const mailer = new UltraSES({
|
||||
'aws': {
|
||||
'accessKeyId': 'AKIAJWJS75F7WNCGK64A',
|
||||
'secretAccessKey': '8irYxThCp4xxyrbr00HzWcODe2qdNrR7X7S5BKup',
|
||||
'region': 'eu-west-1'
|
||||
}, 'defaults': {
|
||||
'from': 'Martin Donnelly <martind2000@gmail.com>'
|
||||
}
|
||||
});
|
||||
|
||||
const menu = [
|
||||
|
||||
|
||||
{
|
||||
'name': 'butter chicken in silky sauce',
|
||||
'url': 'http://www.marksdailyapple.com/butter-chicken-in-a-silky-sauce/#axzz29jTSubMo',
|
||||
'hash': '5117109d69d399915c0959eabbc383f9'
|
||||
}, {
|
||||
'name': 'steak',
|
||||
'url': 'https://www.farmison.com/community/recipe/how-to-cook-your-beef-fillet-steak',
|
||||
'hash': '35e244830bde1d967298fb9a585854f7'
|
||||
}, {
|
||||
'name': 'hazelnut crusted chicken',
|
||||
'url': 'http://www.marksdailyapple.com/hazelnut-crusted-chicken-with-stealth-coconut/#axzz29jTSubMo',
|
||||
'hash': 'baaf075a2384ff84ef3d9ce4eebfafff'
|
||||
}, { 'name': 'vegetable latkes', 'url': 'http://www.marksdailyapple.com/vegetable-latkes/#axzz29jTSubMo', 'hash': '11a043bfe06505f88b0bcfb59095ecab' },
|
||||
{
|
||||
'name': 'beef stew',
|
||||
'url': 'http://www.marksdailyapple.com/beef-stew-and-chicken-soup-in-35-minutes-or-less/#axzz29jTSubMo',
|
||||
'hash': '45f5a2176ff5547585a5e562e0914776'
|
||||
}, {
|
||||
'name': 'sausage patties',
|
||||
'url': 'https://www.tasteslovely.com/homemade-paleo-breakfast-sausage-patties/',
|
||||
'hash': 'c6152fba4df0de77cb139488eaeb4690'
|
||||
}, {
|
||||
'name': 'primal tex mex torillas',
|
||||
'url': 'http://www.marksdailyapple.com/primal-tex-mex-tortillas-and-taco-seasoning/#axzz29jTSubMo',
|
||||
'hash': '08d2e6f8a56619b833e412473ef31e06'
|
||||
}, {
|
||||
'name': 'primal texas chili',
|
||||
'url': 'http://www.marksdailyapple.com/primal-texas-chili/#axzz29jTSubMo',
|
||||
'hash': '319718f91f88fe96840cbdbf09bdb0b3'
|
||||
}, { 'name': 'moroccan burgers', 'url': 'https://sarahfragoso.com/moroccan-burgers-and-beet-salad/', 'hash': '0a4e01068e349791d6e9f2a0c9a80d98' }, {
|
||||
'name': 'paleo spaghetti',
|
||||
'url': 'https://paleoleap.com/paleo-spaghetti/',
|
||||
'hash': '59394666f0320c909c057fc535b99387'
|
||||
}, {
|
||||
'name': 'primal chicken tikka masala',
|
||||
'url': 'http://www.marksdailyapple.com/primal-chicken-tikka-masala/#axzz29jTSubMo',
|
||||
'hash': '1222c577eb67f601088f7649efd329a9'
|
||||
}, {
|
||||
'name': 'butter stuffed chicken kiev',
|
||||
'url': 'http://www.marksdailyapple.com/butter-stuffed-chicken-kiev/#axzz29jTSubMo',
|
||||
'hash': '1b657d8d5e88af4dba220b875e496155'
|
||||
}, {
|
||||
'name': 'tender lemon parsley brisket',
|
||||
'url': 'http://www.marksdailyapple.com/tender-lemon-parsley-brisket/#axzz29jTSubMo',
|
||||
'hash': '5256dd240e04a62c5b002e5dc4234618'
|
||||
}
|
||||
,{
|
||||
'name': 'taco bowl crispy kale',
|
||||
'url': 'http://www.marksdailyapple.com/taco-bowl-with-crispy-kale-chips/#axzz29jTSubMo',
|
||||
'hash': 'fb3e4a7648a53e93490f6274f731e427'
|
||||
}, {
|
||||
'name': 'lime and basil kebabs',
|
||||
'url': 'http://www.marksdailyapple.com/lime-and-basil-beef-kebabs/#axzz29jTSubMo',
|
||||
'hash': '47d38404c7b5f4b50df1f39b6c26845d'
|
||||
}, {
|
||||
'name': 'sun dried tomato meatballs',
|
||||
'url': 'https://sarahfragoso.com/sun-dried-tomato-meatballs-with-creamy-pesto/',
|
||||
'hash': 'bb22a54c34eed937b17bb232aee5231c'
|
||||
}, {
|
||||
'name': 'slow cooked coconut ginger pork',
|
||||
'url': 'http://www.marksdailyapple.com/slow-cooked-coconut-ginger-pork/#axzz29jTSubMo',
|
||||
'hash': '810acff788fae541bf6f037becc88926'
|
||||
}, {
|
||||
'name': 'balsamic glazed drumsticks',
|
||||
'url': 'http://www.marksdailyapple.com/balsamic-glazed-drumsticks/#axzz29jTSubMo',
|
||||
'hash': 'b4ea4f7e46f8d435ac87e56f4069fb29'
|
||||
}, {
|
||||
'name': 'herb chicken cooked under a brick',
|
||||
'url': 'http://www.marksdailyapple.com/herb-chicken-cooked-under-a-brick/#axzz29jTSubMo',
|
||||
'hash': '296cf41b69560cb8f898044471cef370'
|
||||
}, {
|
||||
'name': 'pork stuffed jalapeno peppers',
|
||||
'url': 'http://www.marksdailyapple.com/pork-stuffed-jalapeno-peppers/#axzz29jTSubMo',
|
||||
'hash': 'd85f7f04d1efe18ca0342a95e6a94318'
|
||||
}, {
|
||||
'name': 'spiced pork butternut squash',
|
||||
'url': 'http://www.marksdailyapple.com/spiced-pork-and-butternut-squash-with-sage/#axzz29jTSubMo',
|
||||
'hash': 'd681bdb030c64156e9155d29533f945b'
|
||||
}, {
|
||||
'name': 'shakshuka',
|
||||
'url': 'http://www.marksdailyapple.com/shakshuka-eggs-poached-in-spicy-tomato-sauce/#axzz29jTSubMo',
|
||||
'hash': 'fdf0904c40e5c037b42ff657538a0670'
|
||||
}, {
|
||||
'name': 'grilled lime chicken',
|
||||
'url': 'http://www.simplyrecipes.com/recipes/grilled_lime_chicken_with_black_bean_sauce/',
|
||||
'hash': '9c7cf2b0f15378cbebfd4cc9c6d72091'
|
||||
}, { 'name': 'eggs benadict burger', 'url': 'https://paleoleap.com/eggs-benedict-burgers/', 'hash': '5d71a32d58190c67ac0597289db0b472' }, {
|
||||
'name': 'asian marinated steaks',
|
||||
'url': 'https://web.archive.org/web/20120330045230/http://www.paleotable.com/2011/01/asian-marinated-steaks.html',
|
||||
'hash': 'bd6a0fe62b93e93a988cdbc1ead53f8f'
|
||||
}, {
|
||||
'name': 'tango burgers',
|
||||
'url': 'https://web.archive.org/web/20120127101048/http://www.paleotable.com/2011/01/tango-burgers.html',
|
||||
'hash': '61f794329b2144d17ef55d1806b635f9'
|
||||
}, {
|
||||
'name': 'meatza',
|
||||
'url': 'https://web.archive.org/web/20120118062740/http://www.paleotable.com/2011/02/meatza.html',
|
||||
'hash': 'cf8306e8f197a1c66337141c87f0355c'
|
||||
}, {
|
||||
'name': 'baked chicken with roasted tomatos',
|
||||
'url': 'https://web.archive.org/web/20120125102938/http://www.paleotable.com/2011/01/baked-chicken-with-roasted-tomatoes.html',
|
||||
'hash': '065784446d4c1296409589bc9e864ac5'
|
||||
}, {
|
||||
'name': 'blackened chicken',
|
||||
'url': 'https://web.archive.org/web/20120420232533/http://www.paleotable.com/2011/02/blackened-chicken.html',
|
||||
'hash': '75bbaf2bac48017ed81a51a6675fe2ca'
|
||||
}, {
|
||||
'name': 'breaded baked chicken',
|
||||
'url': 'https://www.tastesoflizzyt.com/paleo-baked-chicken/',
|
||||
'hash': '801def843d96d8bd8c2bb5cca201a0e9'
|
||||
}, {
|
||||
'name': 'chicken and avocado tostadas',
|
||||
'url': 'https://web.archive.org/web/20120707073826/http://www.paleotable.com/2011/03/chicken-and-avocado-tostadas.html',
|
||||
'hash': '917337fb338a88ddd5d86191514d8b14'
|
||||
}, {
|
||||
'name': 'sausages and pepperonata',
|
||||
'url': 'https://web.archive.org/web/20120707071653/http://www.paleotable.com/2011/01/sausages-and-pepperonata.html',
|
||||
'hash': '600d87a5710f69079e8ca1103dc3a03d'
|
||||
}, {
|
||||
'name': 'Mediterranean Beef Stew with Green Olive Pesto',
|
||||
'url': 'http://www.health-bent.com/soups/paleo-mediterranean-beef-stew',
|
||||
'hash': 'acd935ee010ce4f06de7a3f593c140dc'
|
||||
}, {
|
||||
'name': 'Beanless Chili',
|
||||
'url': 'https://web.archive.org/web/20121004005636/http://www.realfoodfreaks.com/2012/04/02/chili-beanless-cuz-i-dont-eat-em/',
|
||||
'hash': 'e4db2a9a21f7ac494ca8a8b988d2ea2b'
|
||||
}, {
|
||||
'name': 'Grilled Steak with Roasted Jalapeño Chimichurri',
|
||||
'url': 'https://www.seriouseats.com/recipes/2012/05/ted-allens-grilled-steak-with-roasted-jalepeno.html',
|
||||
'hash': '52eb19e90993d5fbe2efa5451b291462'
|
||||
}, {
|
||||
'name': 'Lime and Coconut Chicken',
|
||||
'url': 'http://chaosinthekitchen.com/2009/07/lime-and-coconut-chicken/',
|
||||
'hash': '7f7bda5e4f91b3efb63c2982c9780d21'
|
||||
}, {
|
||||
'name': 'BEEF AND VEGETABLE CHILI',
|
||||
'url': 'http://www.paleoplan.com/2011/10-12/steak-and-vegetable-chili/',
|
||||
'hash': 'd9a16cfd6a533d820536ba231e68fe2b'
|
||||
}, {
|
||||
'name': 'PORK LOIN WITH PEPPERS, MUSHROOMS AND ONIONS',
|
||||
'url': 'https://www.paleoplan.com/2010/02-23/pork-loin-with-peppers-mushrooms-n-onions/',
|
||||
'hash': '7c3b3f53e7acc60b651f65cd9743f9bd'
|
||||
}, {
|
||||
'name': 'Melt In Your Mouth Slow Cooker Beef Brisket',
|
||||
'url': 'https://paleogrubs.com/beef-brisket-recipe',
|
||||
'hash': '92e81d0fc1d96b1669d0dcd98a0e355e'
|
||||
}, {
|
||||
'name': 'Yummy Sweet Potato Gnocchi',
|
||||
'url': 'https://paleogrubs.com/sweet-potato-gnocchi-recipe',
|
||||
'hash': '9c1c921687e157a5670c6746bbceeee4'
|
||||
}, {
|
||||
'name': 'Simple Beef and Broccoli Stir Fry',
|
||||
'url': 'https://paleogrubs.com/beef-and-broccoli-recipe',
|
||||
'hash': '13019ef536801ac1a7d702b1b856ce0b'
|
||||
}, {
|
||||
'name': 'Crockpot Balsamic Roast Beef',
|
||||
'url': 'https://www.primallyinspired.com/crockpot-balsamic-roast-beef/',
|
||||
'hash': 'a725b6277a8c56cc9eca3e3f66128278'
|
||||
}, {
|
||||
'name': 'Quick & Easy Crock Pot Chili',
|
||||
'url': 'https://www.healthstartsinthekitchen.com/2014/01/04/quick-crock-pot-chili/',
|
||||
'hash': '3d119153603010aaad3b921da7ddeed0'
|
||||
}, {
|
||||
'name': 'Roasted, Herbed Beef Tenderloin Recipe',
|
||||
'url': 'https://www.chowhound.com/recipes/roasted-herbed-beef-tenderloin-30901',
|
||||
'hash': '1162e3121bfa2e3acbf9b112ca917318'
|
||||
}, {
|
||||
'name': 'Chorizo Carbonara',
|
||||
'url': 'https://www.jamieoliver.com/recipes/pasta-recipes/chorizo-carbonara-with-catalan-market-salad/#1EmLf9RiqepY4Z95.97',
|
||||
'hash': '5a81acc6802404c16ccb595946638706'
|
||||
}, {
|
||||
'name': 'swedish meatballs',
|
||||
'url': 'https://www.paleorunningmomma.com/paleo-swedish-meatballs-whole30/',
|
||||
'hash': '77724ec628b02f8ce7914a03c1266162'
|
||||
}, { 'name': 'Hungarian Beef Goulash', 'url': 'https://paleoleap.com/hungarian-beef-goulash/', 'hash': '45c162ef0c0bab221120df4236bdcbf3' }, {
|
||||
'name': 'Beef Bourguignon',
|
||||
'url': 'http://paleoleap.com/beef-bourguignon/',
|
||||
'hash': '16fb7eb44aec586069f9e34251c5f49a'
|
||||
}, {
|
||||
'name': 'Minted pesto chicken stir-fry',
|
||||
'url': 'http://paleoleap.com/quick-and-easy-paleo-stir-fries/',
|
||||
'hash': 'e3a28f3e02eab851a46c8e5455172791'
|
||||
}, {
|
||||
'name': 'Coconut curry stir-fry',
|
||||
'url': 'http://paleoleap.com/quick-and-easy-paleo-stir-fries/',
|
||||
'hash': '4efbc7c204e2a003edf3027187f44d27'
|
||||
}, {
|
||||
'name': 'Citrus beef salad stir-fry',
|
||||
'url': 'http://paleoleap.com/quick-and-easy-paleo-stir-fries/',
|
||||
'hash': '14ac93f7b0c699cc0ed4ca91891029b0'
|
||||
},
|
||||
{ 'name': 'a normal chicken stir fry', 'url': '', 'hash': 'bbbef5bd2c023ebd904cc87d4c82c047' }, {
|
||||
'name': 'hunters chicken ( chicken wrapped in bacon / pancetta )',
|
||||
'url': '',
|
||||
'hash': '68575933d8055d3d893ebcce9d4a5461'
|
||||
}, {
|
||||
'name': 'Caribbean Pork Chops with Mango Salsa | Paleo Grubs',
|
||||
'url': 'https://paleogrubs.com/caribbean-pork-chops-recipe',
|
||||
'hash': '40d87c777140a4d09e4354ad1931efdb'
|
||||
}, {
|
||||
'name': 'Paleo Pork Tenderloin with Fig and Balsamic Glaze',
|
||||
'url': 'http://paleogrubs.com/balsamic-pork-tenderloin-recipe',
|
||||
'hash': 'ff4a57645ef5da39e7f138fda943f720'
|
||||
}, {
|
||||
'name': 'Chicken Cordon-Bacon',
|
||||
'url': 'https://fastpaleo.com/recipe/bacon-wrapped-chicken-cordon-bleu/',
|
||||
'hash': '8752697521079bc508bf42a3de61c03e'
|
||||
}, {
|
||||
'name': 'Grilled Tandoori Chicken',
|
||||
'url': 'http://cookingweekends.blogspot.com/2012/05/grilled-tandoori-chicken.html',
|
||||
'hash': 'cf57581455fc17e6e9b657b0d2bbaa6f'
|
||||
}, {
|
||||
'name': 'Stuffed Chicken Breast (paleo and grain free)',
|
||||
'url': 'http://www.cavegirlcuisine.com/recipes/stuffed-chicken-breast/',
|
||||
'hash': '709e1d41d3d0999a2c8443e0a170783f'
|
||||
}, {
|
||||
'name': 'Lemon Chicken Breast Recipe',
|
||||
'url': 'https://www.everydaymaven.com/lemon-chicken-breast/',
|
||||
'hash': '370ca9c8e6a4bb5ff892f066a82c4071'
|
||||
}, {
|
||||
'name': 'Grilled Lemon Garlic Rosemary Chicken Breasts',
|
||||
'url': 'https://www.phoenixhelix.com/2013/07/21/grilled-lemon-garlic-rosemary-chicken-breasts/',
|
||||
'hash': '463ee932b56b617bde9812f6032a6974'
|
||||
}, {
|
||||
'name': 'Paleo Spicy Mustard Crockpot Chicken',
|
||||
'url': 'https://www.plaidandpaleo.com/2013/10/paleo-spicy-mustard-crockpot-chicken.html',
|
||||
'hash': 'a6c8493204d4a117f512f516e7ebbc38'
|
||||
}, {
|
||||
'name': 'Creamy Paleo Chicken Skillet',
|
||||
'url': 'http://paleogrubs.com/skillet-chicken-recipe',
|
||||
'hash': '9b5602e594a5bba813b06835b3acef78'
|
||||
}, {
|
||||
'name': 'Sweet and Savory Moroccan Chicken',
|
||||
'url': 'http://paleogrubs.com/moroccan-chicken-recipe',
|
||||
'hash': 'bff3e9ac8dc0b2e0c59ddbb0d3cce27a'
|
||||
}, {
|
||||
'name': 'Simple and Addictive Chicken Kabobs',
|
||||
'url': 'http://paleogrubs.com/chicken-kabob-recipe',
|
||||
'hash': 'ad4f16c87dc76df8f53f0c57e517d574'
|
||||
}, {
|
||||
'name': 'Indian Paleo Stew with Chicken',
|
||||
'url': 'http://paleogrubs.com/indian-paleo-stew-recipe',
|
||||
'hash': 'd9fad92471bf383f97b512d4b0a81fb2'
|
||||
}, {
|
||||
'name': 'Slow-Cooker Swedish Kalops Stew',
|
||||
'url': 'https://web.archive.org/web/20130501032143/http://www.mylivingnutrition.com/2013/04/11/slow-cooker-swedish-kalops-stew/',
|
||||
'hash': '866b670b9da5974e56b7a7759b406897'
|
||||
}, {
|
||||
'name': 'Beef, Bacon and Rum Stew',
|
||||
'url': 'http://domesticsoul.com/2011/12/beef-bacon-rum-stew-recipe.html',
|
||||
'hash': '7601dee9f0ab54604b1afca6978bda53'
|
||||
}, {
|
||||
'name': 'Paleo Orange Cinnamon Beef Stew',
|
||||
'url': 'https://thehealthyfoodie.com/orange-cinnamon-beef-stew/',
|
||||
'hash': '9c2190641bb066f638bf87f3147a5347'
|
||||
}, { 'name': 'pepper steak', 'url': 'http://paleoleap.com/pepper-steak/', 'hash': 'e61d26358e9854fb6e661df024cc4aac' }, {
|
||||
'name': 'GARLIC ROASTED COD',
|
||||
'url': 'http://paleoleap.com/garlic-roasted-cod/',
|
||||
'hash': '860ef03f3f862e46ee7a12468cee4a8f'
|
||||
}, {
|
||||
'name': 'Easy Bacon-Wrapped Paleo Meatloaf Muffins',
|
||||
'url': 'https://paleogrubs.com/bacon-wrapped-meatloaf-muffins',
|
||||
'hash': '41eeb396e1a113b34383a3ab286ac38f'
|
||||
}, {
|
||||
'name': 'Easy Ground Beef Stuffed Peppers',
|
||||
'url': 'https://paleogrubs.com/ground-beef-stuffed-peppers',
|
||||
'hash': '4349670e1f9c08d89491e83fea5f3041'
|
||||
}, {
|
||||
'name': 'balsamic grilled chicken with greek style salad',
|
||||
'url': 'https://paleoleap.com/balsamic-grilled-chicken-greek-style-salad/',
|
||||
'hash': '9b9ee3d83b88ed2c8b4f4ff50ca0b879'
|
||||
}, {
|
||||
'name': 'slow cooked hawaiian style kalua pork',
|
||||
'url': 'https://paleoleap.com/slow-cooked-hawaiian-style-kalua-pork/',
|
||||
'hash': 'b3411415c6ea78f4e4b73596b0b3aa0c'
|
||||
}, {
|
||||
'name': 'Sweet Thai Chili Chicken With Roasted Peppers',
|
||||
'url': 'http://paleoleap.com/sweet-thai-chili-chicken-roasted-peppers/',
|
||||
'hash': '4f524ce44423b2abe6ee5e92b379e69b'
|
||||
}, {
|
||||
'name': 'Steak Ratatouille',
|
||||
'url': 'http://www.jamieoliver.com/recipes/beef-recipes/grilled-steak-ratatouille-saffron-rice/',
|
||||
'hash': 'f21ac2e974c00ac45245aa68c83f477c'
|
||||
}, {
|
||||
'name': 'Slow Cooked Balsamic Pork Roast',
|
||||
'url': 'https://paleoleap.com/slow-cooked-balsamic-pork-roast/',
|
||||
'hash': 'c2c3c4b5c60fe6549c59d4dd1c6d55bf'
|
||||
}, {
|
||||
'name': 'Seared Cod with Fresh Herb Sauce',
|
||||
'url': 'http://paleoleap.com/seared-cod-fresh-herb-sauce/',
|
||||
'hash': '85e1f67407636b53958918142080635f'
|
||||
}, {
|
||||
'name': 'Honey Ginger Apple Shredded Pork',
|
||||
'url': 'https://paleomg.com/honey-ginger-apple-shredded-pork/',
|
||||
'hash': 'bd0485bf2f1e0019298e08c38011641b'
|
||||
}, {
|
||||
'name': 'Orange Pineapple Pulled Pork',
|
||||
'url': 'http://www.smokedngrilled.com/orange-pineapple-pulled-pork/',
|
||||
'hash': 'afb59b8879277bbeb2b5ec3b9b33ec52'
|
||||
}, { 'name': 'Brazilian Curry Chicken', 'url': 'https://paleomg.com/brazilian-curry-chicken/', 'hash': 'd2c78d69054333cb52a9be6bbbf3a497' }, {
|
||||
'name': 'Teriyaki Meatballs',
|
||||
'url': 'https://paleogrubs.com/teriyaki-meatballs',
|
||||
'hash': '4285b9472917c5c03402b973721d506f'
|
||||
}, {
|
||||
'name': 'Paleo Slow Cooker Bacon BBQ Chicken',
|
||||
'url': 'https://paleogrubs.com/chicken-breast-recipes',
|
||||
'hash': '3391a35433f97ed9c58918a41eb59fa7'
|
||||
}, {
|
||||
'name': 'Honey-Lime Grilled Chicken Skewers',
|
||||
'url': 'https://paleogrubs.com/honey-lime-grilled-chicken-skewers',
|
||||
'hash': '8a84beaad4169b3c845f0d65b2f33c29'
|
||||
}, {
|
||||
'name': 'Pork Chop With Applesauce And Roasted Tomatoes',
|
||||
'url': 'https://paleoleap.com/pork-chop-applesauce-roasted-tomatoes/',
|
||||
'hash': '88ec8f4a7e89cec6fe9687a38595b318'
|
||||
}, {
|
||||
'name': 'Slow Cooked Jerk Pork Tenderloin',
|
||||
'url': 'https://paleoleap.com/slow-cooked-jerk-pork-tenderloin/',
|
||||
'hash': '74dccaea2c0a83343720aee35ca91dc2'
|
||||
}, { 'name': 'Slow cooked beef cheeks with pork & vanilla', 'url': 'https://irenamacri.com/recipes/slow-cooked-beef-cheeks/', 'hash': 'a39657743c78e7a1e5438243444af9b5' }];
|
||||
|
||||
const soups = [
|
||||
{
|
||||
'name': 'chicken soup',
|
||||
'url':
|
||||
'http://www.marksdailyapple.com/beef-stew-and-chicken-soup-in-35-minutes-or-less/#axzz29jTSubMo',
|
||||
'hash': 'f5970ca52c37f67cd1fda14edad7fdce'
|
||||
},
|
||||
{
|
||||
'name': 'Tom Kha Gai -- Thai Coconut Soup',
|
||||
'url': 'https://www.angsarap.net/2012/03/08/tom-kha-gai/',
|
||||
'hash': '670819e255ec45b344ec1cc30ad436fe'
|
||||
},
|
||||
{
|
||||
'name': 'Roasted Tomato Soup with Fresh Basil',
|
||||
'url': 'https://civilizedcaveman.com/recipes/soups/roasted-tomato-soup/',
|
||||
'hash': 'a0f61259e62dfe6fdbf4feb42402f9cb'
|
||||
},
|
||||
{
|
||||
'name': 'Tom Yum Gai - hot and sour soup',
|
||||
'url': 'http://angsarap.net/2013/08/07/tom-yum-gai/',
|
||||
'hash': 'f0edd41d24a04b2a4dcf72f685f2367b'
|
||||
},
|
||||
{
|
||||
'name': 'Coconut Lime Chicken Soup',
|
||||
'url': 'https://paleoleap.com/coconut-lime-chicken-soup/',
|
||||
'hash': 'd2e316b3f58fe2191367d24d5e20deb9'
|
||||
},
|
||||
{
|
||||
'name': 'Cream of Chicken Soup',
|
||||
'url': 'https://paleogrubs.com/crockpot-cream-of-chicken-soup',
|
||||
'hash': '46e33b03f670037de46d3425fdce4e78'
|
||||
},
|
||||
{
|
||||
'name': 'Roasted Carrot Ginger Paleo Soup',
|
||||
'url': 'https://paleogrubs.com/soup-recipes',
|
||||
'hash': '26d8938367235d9c891ec1b87fd50cd8'
|
||||
},
|
||||
{
|
||||
'name': 'Slow Cooker Taco Soup',
|
||||
'url': 'https://paleogrubs.com/slow-cooker-taco-soup',
|
||||
'hash': '926dcc730930547b3dabdc623613f8e9'
|
||||
},
|
||||
{
|
||||
'name': 'Chicken Soup - MD',
|
||||
'url':
|
||||
'https://paleogrubs.com/wp-content/uploads/2019/03/Homemade-Chicken-No-Noodle-Soup-1.jpg',
|
||||
'hash': 'dc7eacaaf9b2d2fe6d16273d20f5d052'
|
||||
},
|
||||
{
|
||||
'name': 'Ham Soup - MD',
|
||||
'url':
|
||||
'https://paleogrubs.com/wp-content/uploads/2019/03/Homemade-Chicken-No-Noodle-Soup-1.jpg',
|
||||
'hash': 'ec2bc0d8622baeab237b9057a828933c'
|
||||
},
|
||||
{
|
||||
'name': 'Chicken & Chorizo - MD',
|
||||
'url':
|
||||
'https://paleogrubs.com/wp-content/uploads/2019/03/Homemade-Chicken-No-Noodle-Soup-1.jpg',
|
||||
'hash': 'ab727cbfbc73cea127c61cc742b38d25'
|
||||
},
|
||||
{
|
||||
'name': 'Chickpea Soup - Jess',
|
||||
'url':
|
||||
'https://paleogrubs.com/wp-content/uploads/2019/03/Homemade-Chicken-No-Noodle-Soup-1.jpg',
|
||||
'hash': '85620ea6a3dc6692ee772adf2e4abb40'
|
||||
},
|
||||
{
|
||||
'name': 'Crockpot Paleo Minestrone Soup',
|
||||
'url': 'https://www.ourpaleolife.com/crockpot-paleo-minestrone-soup/',
|
||||
'hash': 'b9d1e302487be049a6b1c573e007e751'
|
||||
}
|
||||
];
|
12
list.json
Normal file
12
list.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"name": "Asian pepper steak crock pot recipe",
|
||||
"url": "https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe/#axzz29jTSubMo",
|
||||
"hash": "bd96f4ac0089d553a93b8a37052568b6"
|
||||
},
|
||||
{
|
||||
"name": "Beef burgandy",
|
||||
"url": "http://www.marksdailyapple.com/beef-burgundy-recipe/#axzz29jTSubMo",
|
||||
"hash": "1da0aafeccfe6a5b4beb853fdbec7aca"
|
||||
}
|
||||
]
|
428
live/css/App.css
Normal file
428
live/css/App.css
Normal file
@ -0,0 +1,428 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto');
|
||||
|
||||
/* Global Styles */
|
||||
:root {
|
||||
--primary-color: #64B5F6;
|
||||
--dark-color: #333333;
|
||||
--light-color: #f4f4f4;
|
||||
--danger-color: #dc3545;
|
||||
--success-color: #28a745;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dataRow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
/* Text Styles*/
|
||||
.x-large {
|
||||
font-size: 4rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 3rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.text-dark {
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Center All */
|
||||
.all-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
padding: 1rem;
|
||||
border: #ccc 1px dotted;
|
||||
margin: 0.7rem 0;
|
||||
}
|
||||
|
||||
/* List */
|
||||
.list {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.list li {
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
/* Padding */
|
||||
.p {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.p-1 {
|
||||
padding: 1rem;
|
||||
}
|
||||
.p-2 {
|
||||
padding: 2rem;
|
||||
}
|
||||
.p-3 {
|
||||
padding: 3rem;
|
||||
}
|
||||
.py {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
.py-1 {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.py-2 {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
.py-3 {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
/* Margin */
|
||||
.m {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
.m-1 {
|
||||
margin: 1rem;
|
||||
}
|
||||
.m-2 {
|
||||
margin: 2rem;
|
||||
}
|
||||
.m-3 {
|
||||
margin: 3rem;
|
||||
}
|
||||
.my {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.my-1 {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.my-2 {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.my-3 {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background: var(--light-color);
|
||||
color: #333;
|
||||
padding: 0.4rem 1.3rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-right: 0.5rem;
|
||||
transition: opacity 0.2s ease-in;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.3rem 1rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.7rem;
|
||||
text-align: center;
|
||||
margin: 0.3rem;
|
||||
background: var(--light-color);
|
||||
color: #333;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 0.7rem;
|
||||
margin: 1rem 0;
|
||||
opacity: 0.9;
|
||||
background: var(--light-color);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.bg-primary,
|
||||
.badge-primary,
|
||||
.alert-primary {
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.btn-light,
|
||||
.bg-light,
|
||||
.badge-light,
|
||||
.alert-light {
|
||||
background: var(--light-color);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-dark,
|
||||
.bg-dark,
|
||||
.badge-dark,
|
||||
.alert-dark {
|
||||
background: var(--dark-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger,
|
||||
.bg-danger,
|
||||
.badge-danger,
|
||||
.alert-danger {
|
||||
background: var(--danger-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success,
|
||||
.bg-success,
|
||||
.badge-success,
|
||||
.alert-success {
|
||||
background: var(--success-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-white,
|
||||
.bg-white,
|
||||
.badge-white,
|
||||
.alert-white {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: #ccc solid 1px;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
opacity: 0.60;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
.btn:enabled:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.bg-light,
|
||||
.badge-light {
|
||||
border: #ccc solid 1px;
|
||||
}
|
||||
|
||||
.round-img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
input {
|
||||
margin: 1.2rem 0;
|
||||
}
|
||||
|
||||
.form-text {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='email'],
|
||||
input[type='password'],
|
||||
input[type='date'],
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.4rem;
|
||||
font-size: 1.2rem;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
input[type='submit'],
|
||||
button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table th {
|
||||
background: var(--light-color);
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.7rem 2rem;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: #fff;
|
||||
padding: 0.45rem;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.navbar a:hover {
|
||||
color: var(--light-color);
|
||||
}
|
||||
|
||||
.navbar .welcome span {
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
|
||||
/* Mobile Styles */
|
||||
@media (max-width: 700px) {
|
||||
.hide-sm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grid-2,
|
||||
.grid-3,
|
||||
.grid-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Text Styles */
|
||||
.x-large {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
57
live/css/login.css
Normal file
57
live/css/login.css
Normal file
@ -0,0 +1,57 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
|
||||
.btn { display: inline-block; *display: inline; *zoom: 1; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; }
|
||||
.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; }
|
||||
.btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
|
||||
.btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; }
|
||||
.btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; }
|
||||
.btn-primary.active { color: rgba(255, 255, 255, 0.75); }
|
||||
.btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); }
|
||||
.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; }
|
||||
.btn-block { width: 100%; display:block; }
|
||||
|
||||
* { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; }
|
||||
|
||||
html { width: 100%; height:100%; overflow:hidden; }
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height:100%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background: #092756;
|
||||
background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%),-moz-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%);
|
||||
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -webkit-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -o-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -ms-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -ms-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), linear-gradient(to bottom, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), linear-gradient(135deg, #670d10 0%,#092756 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3E1D6D', endColorstr='#092756',GradientType=1 );
|
||||
}
|
||||
.login {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -150px 0 0 -150px;
|
||||
width:300px;
|
||||
height:300px;
|
||||
}
|
||||
.login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2);
|
||||
-webkit-transition: box-shadow .5s ease;
|
||||
-moz-transition: box-shadow .5s ease;
|
||||
-o-transition: box-shadow .5s ease;
|
||||
-ms-transition: box-shadow .5s ease;
|
||||
transition: box-shadow .5s ease;
|
||||
}
|
||||
input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); }
|
BIN
live/favicon.ico
Normal file
BIN
live/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 B |
BIN
live/gfx/recipes.png
Normal file
BIN
live/gfx/recipes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
3659
live/js/bundle.js
Normal file
3659
live/js/bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
1
live/js/bundle.js.map
Normal file
1
live/js/bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
18
live/login.html
Normal file
18
live/login.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
<link href="/css/login.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login">
|
||||
<h1>Login</h1>
|
||||
<form action="auth" method="post">
|
||||
<input type="text" name="u" placeholder="Username" required="required" />
|
||||
<input type="password" name="p" placeholder="Password" required="required" />
|
||||
<button type="submit" class="btn btn-primary btn-block btn-large">Let me in.</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
83
live/recipes.html
Normal file
83
live/recipes.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Aida Recipes</title>
|
||||
<link href="/css/app.css" rel="stylesheet" type="text/css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<div id="header" class="navbar bg-primary">
|
||||
<h2>
|
||||
Recipes
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<button class="btn btn-sm" id="new" type="button">New Recipe</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="editor" class="container" style="display:none ;">
|
||||
<form autocomplete="off">
|
||||
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" value="" required/>
|
||||
|
||||
<label for="url">Url:</label>
|
||||
<input type="text" id="url" name="url" value="" required/>
|
||||
|
||||
<label for="md">Markdown:</label>
|
||||
<textarea id="md" name="md" cols="50" rows="10"></textarea>
|
||||
|
||||
<label for="meat">Meat</label>
|
||||
<select id="meat" name="meat" required>
|
||||
<option></option>
|
||||
<option value="1">Chicken</option>
|
||||
<option value="2">Beef</option>
|
||||
<option value="3">Pork</option>
|
||||
<option value="4">Fish</option>
|
||||
<option value="5">Egg</option>
|
||||
<option value="6">Vegetable</option>
|
||||
|
||||
</select>
|
||||
|
||||
<label for="mealtype">Meal type</label>
|
||||
<select id="mealtype" name="mealtype" required>
|
||||
<option></option>
|
||||
<option value="1">Main</option>
|
||||
<option value="2">Soup</option>
|
||||
</select>
|
||||
|
||||
<input id="_id" name="id" type="hidden" value="" disabled/>
|
||||
<input type="hidden" id="short" name="short" value="" disabled/>
|
||||
<input type="hidden" id="hash" name="hash" value="" disabled/>
|
||||
<input type="hidden" id="lastused" name="lastused" value="" disabled/>
|
||||
|
||||
<div class="my text-right">
|
||||
|
||||
<button class="btn btn-danger btn-sm" id="delete" type="button">Delete</button>
|
||||
<button class="btn btn-sm" id="close" type="button">Close</button>
|
||||
<button class="btn btn-primary btn-sm" id="save" type="button">Save</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="content" class="container">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script type='module' src="js/bundle.js" async></script>
|
||||
|
||||
</body>
|
||||
</html>
|
1
live/tpl/header.html
Normal file
1
live/tpl/header.html
Normal file
@ -0,0 +1 @@
|
||||
<button class="new">New Recipe</button>
|
12
live/tpl/recipe-details.html
Normal file
12
live/tpl/recipe-details.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="form-left-col">
|
||||
<label>Id:</label>
|
||||
<input id="wineId" name="_id" type="text" value="<%= _id %>" disabled />
|
||||
|
||||
<label>Name:</label>
|
||||
<input type="text" id="name" name="name" value="<%= name %>" required/>
|
||||
|
||||
|
||||
|
||||
<button class="save">Save</button>
|
||||
<button class="delete">Delete</button>
|
||||
</div>
|
1
live/tpl/recipe-list-item.html
Normal file
1
live/tpl/recipe-list-item.html
Normal file
@ -0,0 +1 @@
|
||||
<a href='#recipe/<%= _id %>'><%= name %></a>
|
6006
package-lock.json
generated
Normal file
6006
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
package.json
Normal file
41
package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "menuserver",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"backbone": "^1.4.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"helmet": "^3.21.2",
|
||||
"jquery": "^3.4.1",
|
||||
"lodash": "^4.17.15",
|
||||
"markdown": "^0.5.0",
|
||||
"minibus": "^3.1.0",
|
||||
"nosql": "^6.1.0",
|
||||
"schema-check": "0.0.7",
|
||||
"short-hash": "^1.0.0",
|
||||
"sqlite3": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^16.5.0",
|
||||
"eslint": "^6.6.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-strip-debug": "^3.0.0",
|
||||
"gulp-uglify-es": "^2.0.0",
|
||||
"gulp-util": "^3.0.8",
|
||||
"require-dir": "^1.2.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0"
|
||||
}
|
||||
}
|
311
preload.js
Normal file
311
preload.js
Normal file
@ -0,0 +1,311 @@
|
||||
var sqlite3 = require('sqlite3').verbose();
|
||||
var db = new sqlite3.Database('./menu.db');
|
||||
|
||||
const _ = require('lodash');
|
||||
const shortHash = require('short-hash');
|
||||
|
||||
const SchemaCheck = require('schema-check');
|
||||
|
||||
const { promisify } = require('util');
|
||||
|
||||
const eachAsync = promisify(db.each);
|
||||
|
||||
const meats = ['Chicken', 'Beef', 'Pork'];
|
||||
const mealTypes = ['Meal', 'Soup'];
|
||||
|
||||
const runSetup = false;
|
||||
|
||||
const obj = {
|
||||
'name': 'garlic pulled pork',
|
||||
'url': 'https://www.marksdailyapple.com/garlic-pulled-pork',
|
||||
'md': `Garlic Pulled Pork
|
||||
===
|
||||
|
||||
[Source](https://www.marksdailyapple.com/garlic-pulled-pork)
|
||||
|
||||
Ingredients:
|
||||
---
|
||||
|
||||
- 1 pork shoulder cut (butt or picnic), weighing 3-4 pounds
|
||||
- 1-2 tablespoons kosher salt
|
||||
- 1/2 teaspoon cumin
|
||||
- 1 teaspoon of black pepper
|
||||
- 1-2 tablespoons of granulated garlic or garlic powder
|
||||
- optional: 6 fresh garlic cloves, peeled
|
||||
- The juice of one lime (or sour orange, if you can get one)
|
||||
- 1 onion
|
||||
- 1 bay leaf
|
||||
|
||||
Instructions:
|
||||
---
|
||||
|
||||
1. Mix together salt, cumin, black pepper and granulated garlic.
|
||||
|
||||
1. Juice the lime over the seasonings and rub the mixture all over the pork.
|
||||
|
||||
1. If you love garlic as much as Pat does, you might want to use fresh garlic, too. Use a knife to slice six thin cuts in the pork and push each clove securely inside each cut. You don’t want the fresh garlic to fall out and touch the cooking vessel or it will burn and affect the flavor of the meat.
|
||||
|
||||
1. The meat should sit out of refrigeration a half hour before you put it in the oven. This ensures that it will cook evenly throughout. If you want to let the meat marinate in the rub longer than this, put it in the fridge for an hour or even overnight.
|
||||
|
||||
1. When you’re ready to cook, preheat the oven to 250 degrees Fahrenheit. Place the roast in a pan with one sliced onion and a bay leaf. Cover and roast for three to four hours, or until the middle of the roast reads about 190 degrees and falls apart easily when pulled with a fork.
|
||||
|
||||
1. Let the roast rest for twenty minutes or so, then uncover. You’ll notice a lot of liquid at the bottom. Use it as a sauce for the meat, which you will now viciously attack with two forks. It’ll fall apart pretty readily, and you’ll get the idea of the shredding method after a couple of pulls. Enjoy!
|
||||
|
||||
`,
|
||||
'meat':1,
|
||||
'mealtype':1
|
||||
};
|
||||
|
||||
const objB = {
|
||||
'name': 'Tom Kha Gai -- Thai Coconut Soup',
|
||||
'url': 'https://www.angsarap.net/2012/03/08/tom-kha-gai/',
|
||||
'md' :`# Tom Kha Gai
|
||||
|
||||
[Source](https://www.angsarap.net/2012/03/08/tom-kha-gai/)
|
||||
|
||||
## Ingredients
|
||||
|
||||
- 600g chicken breast, sliced
|
||||
- 6 cups chicken stock
|
||||
- 1 cup coconut milk
|
||||
- 4 pcs long tamarind, wrapped in muslin cloth
|
||||
- 3 stalks lemongrass, white part
|
||||
- 4 pcs kaffir limes leaves
|
||||
- 1 1/2 cup straw mushrooms, cut in half
|
||||
- 1 thumb size galangal, cut into 4 pieces
|
||||
- 3 pcs dried chili, chopped
|
||||
- 1 tbsp brown sugar
|
||||
- handful fresh coriander leaves
|
||||
- handful fresh basil leaves
|
||||
- fish sauce or sea salt, according to taste
|
||||
|
||||
## Method
|
||||
|
||||
|
||||
1. Pour in chicken stock in a pot and bring it to a boil.
|
||||
2. Once boiling add chicken, galangal, mushrooms, lemon grass, kaffir, chilli and tamarind.
|
||||
3. Simmer for 8 minutes.
|
||||
4. Press muslin cloth with tamarind on top of the pot to extract the juices then discard.
|
||||
5. Add coconut milk, fish sauce, sugar and simmer for another 2 minutes.
|
||||
6. Place in bowls then top with basil and coriander.
|
||||
`,
|
||||
'meat':1,
|
||||
'mealtype':2
|
||||
};
|
||||
|
||||
const newRaw = {
|
||||
'name': 'Asian pepper steak crock pot recipe',
|
||||
'url': 'https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe',
|
||||
'md': 'Asian Pepper Steak Crock Pot Recipe\n ===\n [Source](https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe/)\n \n Ingredients:\n ---\n * 2 lbs. steak (sirloin is preferable, but any other good cut will do)\n * 2 tbsp coconut oil\n * 1-2 cloves of garlic, minced\n * 1/4 cup wheat-free tamari (similar to soy sauce)\n * 1 16 oz can bean sprouts, drained\n * 1 16 oz can diced tomatoes\n * 1 large green pepper, sliced in thin strips\n * 1 small onion, sliced\n * Salt and pepper to taste\n \n Method:\n ---\n 1. On a chopping board, cut the steak on an angle to make strips about a ½ inch thick. \n \n 2. In a large frying pan, add the oil and heat. Saute the steak until it lightly browns. \n \n 3. Drain excess fat, liberally coat the meat with ground pepper and put the meat in the crock pot. \n \n 4. Add garlic and tamari, and mix so that the steak is thoroughly coated.\n \n 5. Cook in a crock pot on low for 6 hours. \n \n 6. One hour before serving, add sprouts, tomatoes, green peppers and onions and turn crock pot to high. \n \n 7. Cook for one hour and then serve piping hot.',
|
||||
'meat': 2,
|
||||
'mealtype': 1
|
||||
};
|
||||
|
||||
const lorem = {
|
||||
'_id': 6,
|
||||
'name': 'Lorem ipsum dolor sit amet',
|
||||
'url': 'https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe',
|
||||
'md': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
'short': 'asian-pepper-steak-crock-pot-recipe',
|
||||
'hash': 'eac12bae',
|
||||
'meat': 2,
|
||||
'mealtype': 1,
|
||||
'lastused': 0
|
||||
};
|
||||
|
||||
function prepareData(_obj) {
|
||||
const newObj = Object.assign({}, _obj);
|
||||
|
||||
newObj.short = _.kebabCase(newObj.name);
|
||||
newObj.hash = shortHash(newObj.name);
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
function insertRecord(newObj) {
|
||||
const workObj = prepareData(newObj);
|
||||
|
||||
db.serialize(() => {
|
||||
const stmt = db.prepare('INSERT INTO menu VALUES (?,?,?,?,?,?,?,?,?)');
|
||||
|
||||
stmt.run(null, workObj.name, workObj.url, workObj.md, workObj.short, workObj.hash, workObj.meat, workObj.mealtype, 0);
|
||||
|
||||
stmt.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
if (runSetup) {
|
||||
// Create table
|
||||
db.serialize(() => {
|
||||
db.run(`CREATE TABLE "menu" (
|
||||
\t"_id"\tINTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
\t"name"\tTEXT,
|
||||
\t"url"\tTEXT,
|
||||
\t"md"\tTEXT,
|
||||
\t"short"\tTEXT UNIQUE,
|
||||
\t"hash"\tTEXT UNIQUE,
|
||||
\t"meat"\tINTEGER NOT NULL DEFAULT 0,
|
||||
\t"mealtype"\tINTEGER NOT NULL DEFAULT 0,
|
||||
\t"lastused"\tINTEGER NOT NULL DEFAULT 0
|
||||
);`);
|
||||
});
|
||||
|
||||
// Insert record
|
||||
insertRecord(obj);
|
||||
insertRecord(objB);
|
||||
}
|
||||
|
||||
function getAll() {
|
||||
const outgoing = [];
|
||||
const sql = 'SELECT _id, short, hash, name FROM menu';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, [], (err, rows) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
rows.forEach((row) => {
|
||||
outgoing.push(row);
|
||||
});
|
||||
|
||||
resolve(outgoing) ;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getOne(hash) {
|
||||
const sql = 'SELECT * FROM menu WHERE hash = ?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, [hash], (err, row) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
if (!err) resolve(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function insertOne(data) {
|
||||
const sql = 'INSERT INTO menu VALUES (?,?,?,?,?,?,?,?,?)';
|
||||
|
||||
const workObj = prepareData(data);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [null, workObj.name, workObj.url, workObj.md, workObj.short, workObj.hash, workObj.meat, workObj.mealtype, 0], function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row inserted', '_id': this.lastID });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOne(hash) {
|
||||
const sql = 'DELETE FROM menu WHERE hash=?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM menu WHERE hash=?', hash, function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row deleted', 'changes': this.changes });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateOne(data) {
|
||||
const oldHash = data.hash;
|
||||
|
||||
const workObj = prepareData(data);
|
||||
|
||||
const sql = `UPDATE menu
|
||||
SET name = ?, url = ?, md = ?, short = ?, hash = ?, meat = ?, mealtype = ?, lastused = ?
|
||||
WHERE hash = ?`;
|
||||
|
||||
const newData = [workObj.name, workObj.url, workObj.md, workObj.short, workObj.hash, workObj.meat, workObj.mealtype, workObj.lastused, oldHash];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, newData, function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row updated', 'changes': this.changes });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* deleteOne('eac12bae')
|
||||
.then((d) => {
|
||||
console.log('deleteOne', d);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});*/
|
||||
|
||||
/* deleteOne('eac12bae')
|
||||
.then((d) => {
|
||||
console.log('deleteOne', d);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});*/
|
||||
|
||||
insertOne(newRaw)
|
||||
.then((v) => {
|
||||
console.log('inserted', v);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
getAll().then((d) => {
|
||||
console.log('then', d);
|
||||
});
|
||||
|
||||
let oldRec;
|
||||
|
||||
getOne('eac12bae')
|
||||
.then((d) => {
|
||||
console.log('One', d);
|
||||
|
||||
oldRec = d;
|
||||
|
||||
lorem['_id'] = oldRec['_id'];
|
||||
|
||||
updateOne(lorem)
|
||||
.then((v) => {
|
||||
console.log('updated', v);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
getOne('eac12bae')
|
||||
.then((d) => {
|
||||
console.log('One', d);
|
||||
|
||||
oldRec = d;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// console.log(obj);
|
||||
|
||||
// db.insert(obj);
|
||||
/*
|
||||
db.insert({ name: 'Peter ' });
|
||||
db.insert({ name: 'Lucia' });
|
||||
db.insert({ name: 'Igor' });
|
||||
db.insert({ name: 'Stano' });
|
||||
db.insert({ name: 'Jozef' });
|
||||
*/
|
||||
|
||||
// console.log(r);
|
||||
|
||||
// db.update({ name: 'Jozef', age: 33 }).where('name', 'Jozef');
|
79
server.js
Normal file
79
server.js
Normal file
@ -0,0 +1,79 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const session = require('express-session');
|
||||
const path = require('path');
|
||||
const helmet = require('helmet');
|
||||
|
||||
const db = require('./server/lib/loginmanager');
|
||||
|
||||
// create express app
|
||||
const app = express();
|
||||
require('dotenv').config();
|
||||
|
||||
const serverPort = process.env.PORT || 3000;
|
||||
|
||||
const sitePath = 'live';
|
||||
|
||||
app.use(helmet());
|
||||
|
||||
app.use(session({
|
||||
'secret': 'rBLH5#Q89Z4',
|
||||
'resave': true,
|
||||
'saveUninitialized': true
|
||||
}));
|
||||
|
||||
app.get('/', function(request, response) {
|
||||
if (request.session.loggedin !== true)
|
||||
response.sendFile(path.join(`${__dirname }/server/static/login.html`));
|
||||
else
|
||||
response.redirect('/recipes.html');
|
||||
});
|
||||
app.use(express.static(path.join(__dirname, sitePath)));
|
||||
|
||||
// parse requests of content-type - application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ 'extended': true }));
|
||||
|
||||
// parse requests of content-type - application/json
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.post('/auth', function(request, response) {
|
||||
const username = request.body.u;
|
||||
const password = request.body.p;
|
||||
|
||||
console.log(username, password);
|
||||
|
||||
if (username && password)
|
||||
|
||||
db.getOne(username, password)
|
||||
.then((data) => {
|
||||
console.log('data', data);
|
||||
|
||||
if (!data)
|
||||
response.send('Incorrect Username and/or Password!');
|
||||
|
||||
else {
|
||||
request.session.loggedin = true;
|
||||
request.session.username = username;
|
||||
response.redirect('/recipes.html');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
response.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
|
||||
else {
|
||||
response.send('Please enter Username and Password!');
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
|
||||
require('./server/routes/recipe.routes')(app);
|
||||
require('./server/routes/view.routes')(app);
|
||||
|
||||
// listen for requests
|
||||
app.listen(serverPort, () => {
|
||||
console.log('Server is listening on port 3000');
|
||||
});
|
101
server/controllers/recipe.controller.js
Normal file
101
server/controllers/recipe.controller.js
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
// Create and Save a new Note
|
||||
const dbmanager = require('../lib/dbmanager');
|
||||
|
||||
exports.create = (req, res) => {
|
||||
// Validate request
|
||||
console.log('>create req', req.body);
|
||||
|
||||
if(!req.body.name)
|
||||
return res.status(400).send({
|
||||
'message': 'Recipe content can not be empty'
|
||||
});
|
||||
|
||||
dbmanager.insertOne(req.body)
|
||||
.then((data) => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Retrieve and return all notes from the database.
|
||||
exports.findAll = (req, res) => {
|
||||
console.log('>findAll req');
|
||||
|
||||
dbmanager.getAll()
|
||||
.then((data) => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Find a single note with a noteId
|
||||
exports.findOne = (req, res) => {
|
||||
console.log('>findOne req', req.params);
|
||||
|
||||
if(!req.params.recipeId)
|
||||
return res.status(400).send({
|
||||
'message': 'Recipe id missing'
|
||||
});
|
||||
|
||||
const hash = req.params.recipeId;
|
||||
|
||||
dbmanager.getOne(hash)
|
||||
.then((data) => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Update a note identified by the noteId in the request
|
||||
exports.update = (req, res) => {
|
||||
console.log('>update req', req.body.content);
|
||||
if(!req.params.recipeId)
|
||||
return res.status(400).send({
|
||||
'message': 'Recipe id missing'
|
||||
});
|
||||
|
||||
dbmanager.updateOne(req.body)
|
||||
.then((data) => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a note with the specified noteId in the request
|
||||
exports.delete = (req, res) => {
|
||||
console.log('>delete req', req.params.recipeId);
|
||||
|
||||
if(!req.params.recipeId)
|
||||
return res.status(400).send({
|
||||
'message': 'Recipe id missing'
|
||||
});
|
||||
|
||||
const hash = req.params.recipeId;
|
||||
|
||||
dbmanager.deleteOne(hash)
|
||||
.then((data) => {
|
||||
res.send(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
10
server/controllers/t.html
Normal file
10
server/controllers/t.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
48
server/controllers/view.controller.js
Normal file
48
server/controllers/view.controller.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Create and Save a new Note
|
||||
const dbmanager = require('../lib/dbmanager');
|
||||
const markdown = require( 'markdown' ).markdown;
|
||||
|
||||
function makeHtml(data) {
|
||||
const md = data.md;
|
||||
|
||||
const html = markdown.toHTML( md );
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${data.name}</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto');:root {--primary-color: #64B5F6;--dark-color: #333333;--light-color: #f4f4f4;--danger-color: #dc3545;--success-color: #28a745;}* {box-sizing: border-box;margin: 0;padding: 0;}body {font-family: 'Roboto', sans-serif;font-size: 1rem;line-height: 1.6;background-color: #fff;color: #333;}a {color: var(--primary-color);text-decoration: none;}a:hover {color: #666;}ul {list-style: none;}img {width: 100%;}.container {max-width: 1100px;margin: auto;overflow: hidden;padding: 0 2rem;}@media (max-width: 700px) {.hide-sm {display: none;}.grid-2, .grid-3, .grid-4 {grid-template-columns: 1fr;}.x-large {font-size: 3rem;}.large {font-size: 2rem;}.lead {font-size: 1rem;}.navbar {display: block;text-align: center;}.navbar ul {text-align: center;justify-content: center;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
${html}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
exports.findOne = (req, res) => {
|
||||
console.log('>findOne req', req.params);
|
||||
|
||||
if(!req.params.recipeId)
|
||||
return res.status(400).send({
|
||||
'message': 'Recipe id missing'
|
||||
});
|
||||
|
||||
const short = req.params.recipeId;
|
||||
|
||||
dbmanager.getOneShort(short)
|
||||
.then((data) => {
|
||||
res.send(makeHtml(data));
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(500).send({
|
||||
'message': err.message || 'Some error occurred while querying the database.'
|
||||
});
|
||||
});
|
||||
};
|
6
server/lib/connect.js
Normal file
6
server/lib/connect.js
Normal file
@ -0,0 +1,6 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const db = new sqlite3.Database(`${__dirname}/../../menu.db`);
|
||||
|
||||
console.log(`${__dirname}/../../menu.db`);
|
||||
|
||||
module.exports = db;
|
108
server/lib/dbmanager.js
Normal file
108
server/lib/dbmanager.js
Normal file
@ -0,0 +1,108 @@
|
||||
const db = require('../lib/connect');
|
||||
|
||||
const _ = require('lodash');
|
||||
const shortHash = require('short-hash');
|
||||
|
||||
// exports.create = (req, res) => {
|
||||
|
||||
function prepareData(_obj) {
|
||||
const newObj = Object.assign({}, _obj);
|
||||
|
||||
newObj.short = _.kebabCase(newObj.name);
|
||||
newObj.hash = shortHash(newObj.name);
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
exports.getAll = () => {
|
||||
const outgoing = [];
|
||||
const sql = 'SELECT _id, short, hash, name, meat, mealtype FROM menu';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(sql, [], (err, rows) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
rows.forEach((row) => {
|
||||
outgoing.push(row);
|
||||
});
|
||||
|
||||
resolve(outgoing) ;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.getOne = (hash) => {
|
||||
const sql = 'SELECT * FROM menu WHERE hash = ?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, [hash], (err, row) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
if (!err) resolve(row);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.getOneShort = (short) => {
|
||||
const sql = 'SELECT * FROM menu WHERE short = ?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, [short], (err, row) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
if (!err) resolve(row);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.insertOne = (data) => {
|
||||
const sql = 'INSERT INTO menu VALUES (?,?,?,?,?,?,?,?,?)';
|
||||
|
||||
const workObj = prepareData(data);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [null, workObj.name, workObj.url, workObj.md, workObj.short, workObj.hash, workObj.meat, workObj.mealtype, 0], function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row inserted', '_id': this.lastID });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.deleteOne = (hash) => {
|
||||
const sql = 'DELETE FROM menu WHERE hash=?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM menu WHERE hash=?', hash, function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row deleted', 'changes': this.changes });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.updateOne = (data) => {
|
||||
const oldHash = data.hash;
|
||||
|
||||
const workObj = prepareData(data);
|
||||
|
||||
const sql = `UPDATE menu
|
||||
SET name = ?, url = ?, md = ?, short = ?, hash = ?, meat = ?, mealtype = ?, lastused = ?
|
||||
WHERE hash = ?`;
|
||||
|
||||
const newData = [workObj.name, workObj.url, workObj.md, workObj.short, workObj.hash, workObj.meat, workObj.mealtype, workObj.lastused, oldHash];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, newData, function(err) {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
resolve({ 'msg':'Row updated', 'changes': this.changes });
|
||||
});
|
||||
});
|
||||
};
|
14
server/lib/loginmanager.js
Normal file
14
server/lib/loginmanager.js
Normal file
@ -0,0 +1,14 @@
|
||||
const db = require('../lib/connect');
|
||||
|
||||
exports.getOne = (username, password) => {
|
||||
const sql = 'SELECT * FROM accounts WHERE username = ? and password = ?';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(sql, [username, password], (err, row) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
|
||||
if (!err) resolve(row);
|
||||
});
|
||||
});
|
||||
};
|
6
server/routes/recipe.routes.js
Normal file
6
server/routes/recipe.routes.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = (app) => {
|
||||
const view = require('../controllers/view.controller');
|
||||
|
||||
app.route('/view/:recipeId')
|
||||
.get(view.findOne);
|
||||
};
|
12
server/routes/view.routes.js
Normal file
12
server/routes/view.routes.js
Normal file
@ -0,0 +1,12 @@
|
||||
module.exports = (app) => {
|
||||
const recipes = require('../controllers/recipe.controller');
|
||||
|
||||
app.route('/recipes')
|
||||
.get(recipes.findAll)
|
||||
.post(recipes.create);
|
||||
|
||||
app.route('/recipes/:recipeId')
|
||||
.get(recipes.findOne)
|
||||
.put(recipes.update)
|
||||
.delete(recipes.delete);
|
||||
};
|
83
server/static/index.html
Normal file
83
server/static/index.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Aida Recipes</title>
|
||||
<link href="/css/app.css" rel="stylesheet" type="text/css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<div id="header" class="navbar bg-primary">
|
||||
<h2>
|
||||
Recipes
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<button class="btn btn-sm" id="new" type="button">New Recipe</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="editor" class="container" style="display:none ;">
|
||||
<form autocomplete="off">
|
||||
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" value="" required/>
|
||||
|
||||
<label for="url">Url:</label>
|
||||
<input type="text" id="url" name="url" value="" required/>
|
||||
|
||||
<label for="md">Markdown:</label>
|
||||
<textarea id="md" name="md" cols="50" rows="10"></textarea>
|
||||
|
||||
<label for="meat">Meat</label>
|
||||
<select id="meat" name="meat" required>
|
||||
<option></option>
|
||||
<option value="1">Chicken</option>
|
||||
<option value="2">Beef</option>
|
||||
<option value="3">Pork</option>
|
||||
<option value="4">Fish</option>
|
||||
<option value="5">Egg</option>
|
||||
<option value="6">Vegetable</option>
|
||||
|
||||
</select>
|
||||
|
||||
<label for="mealtype">Meal type</label>
|
||||
<select id="mealtype" name="mealtype" required>
|
||||
<option></option>
|
||||
<option value="1">Main</option>
|
||||
<option value="2">Soup</option>
|
||||
</select>
|
||||
|
||||
<input id="_id" name="id" type="hidden" value="" disabled/>
|
||||
<input type="hidden" id="short" name="short" value="" disabled/>
|
||||
<input type="hidden" id="hash" name="hash" value="" disabled/>
|
||||
<input type="hidden" id="lastused" name="lastused" value="" disabled/>
|
||||
|
||||
<div class="my text-right">
|
||||
|
||||
<button class="btn btn-danger btn-sm" id="delete" type="button">Delete</button>
|
||||
<button class="btn btn-sm" id="close" type="button">Close</button>
|
||||
<button class="btn btn-primary btn-sm" id="save" type="button">Save</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="content" class="container">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script type='module' src="js/bundle.js" async></script>
|
||||
|
||||
</body>
|
||||
</html>
|
78
server/static/login.html
Normal file
78
server/static/login.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
<style>
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
|
||||
.btn { display: inline-block; *display: inline; *zoom: 1; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; }
|
||||
.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; }
|
||||
.btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
|
||||
.btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; }
|
||||
.btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; }
|
||||
.btn-primary.active { color: rgba(255, 255, 255, 0.75); }
|
||||
.btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); }
|
||||
.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; }
|
||||
.btn-block { width: 100%; display:block; }
|
||||
|
||||
* { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; }
|
||||
|
||||
html { width: 100%; height:100%; overflow:hidden; }
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height:100%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background: #092756;
|
||||
background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%),-moz-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%);
|
||||
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -webkit-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -o-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -ms-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -ms-linear-gradient(-45deg, #670d10 0%,#092756 100%);
|
||||
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), linear-gradient(to bottom, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), linear-gradient(135deg, #670d10 0%,#092756 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3E1D6D', endColorstr='#092756',GradientType=1 );
|
||||
}
|
||||
.login {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -150px 0 0 -150px;
|
||||
width:300px;
|
||||
height:300px;
|
||||
}
|
||||
.login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2);
|
||||
-webkit-transition: box-shadow .5s ease;
|
||||
-moz-transition: box-shadow .5s ease;
|
||||
-o-transition: box-shadow .5s ease;
|
||||
-ms-transition: box-shadow .5s ease;
|
||||
transition: box-shadow .5s ease;
|
||||
}
|
||||
input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); }
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login">
|
||||
<h1>Login</h1>
|
||||
<form action="auth" method="post">
|
||||
<input type="text" name="u" placeholder="Username" required="required" />
|
||||
<input type="password" name="p" placeholder="Password" required="required" />
|
||||
<button type="submit" class="btn btn-primary btn-block btn-large">Let me in.</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
262
src/v1/js/app.js
Normal file
262
src/v1/js/app.js
Normal file
@ -0,0 +1,262 @@
|
||||
const _ = require('underscore');
|
||||
const axios = require('axios');
|
||||
|
||||
class Recipe {
|
||||
|
||||
constructor() {
|
||||
this.url = 'http://localhost:3000/recipes';
|
||||
this.listData = [];
|
||||
this.content = document.querySelector('#content');
|
||||
this.editor = document.querySelector('#editor');
|
||||
this.fields = ['_id', 'name', 'url', 'md', 'short', 'hash', 'lastused'];
|
||||
this.selects = ['meat', 'mealtype'];
|
||||
|
||||
this.newButton = document.querySelector('#new');
|
||||
this.saveButton = document.querySelector('#save');
|
||||
this.deleteButton = document.querySelector('#delete');
|
||||
this.closeButton = document.querySelector('#close');
|
||||
}
|
||||
|
||||
show(div) {
|
||||
div.style.display = '';
|
||||
}
|
||||
|
||||
hide(div) {
|
||||
div.style.display = 'none';
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.fields.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
elm.value = '';
|
||||
});
|
||||
|
||||
this.selects.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
elm.selectedIndex = 0;
|
||||
});
|
||||
}
|
||||
|
||||
loadForm(workData) {
|
||||
this.fields.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
elm.value = workData[item];
|
||||
});
|
||||
|
||||
this.selects.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
elm.selectedIndex = workData[item];
|
||||
});
|
||||
}
|
||||
|
||||
getForm() {
|
||||
const newObj = {};
|
||||
this.fields.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
newObj[item] = elm.value;
|
||||
});
|
||||
|
||||
this.selects.forEach((item) => {
|
||||
const elm = document.querySelector(`#${item}`);
|
||||
|
||||
newObj[item] = elm.options[elm.selectedIndex].value;
|
||||
});
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
setupClicks() {
|
||||
this.content.addEventListener('click', (event) => {
|
||||
try {
|
||||
const dataset = event.path[0].dataset;
|
||||
|
||||
this.getRecord(dataset.hash.toString());
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
this.newButton.addEventListener('click', () => {
|
||||
setTimeout(() => {
|
||||
this.newForm();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.saveButton.addEventListener('click', () => {
|
||||
setTimeout(() => {
|
||||
this.sendRecord();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.closeButton.addEventListener('click', () => {
|
||||
setTimeout(() => {
|
||||
this.closeForm();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
this.deleteButton.addEventListener('click', () => {
|
||||
setTimeout(() => {
|
||||
this.deleteEntry();
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
deleteEntry() {
|
||||
if (confirm('Are you sure you want to delete this entry?')) {
|
||||
// Save it!
|
||||
const newData = this.getForm();
|
||||
|
||||
this.deleteRecord(newData.hash);
|
||||
}
|
||||
else {
|
||||
// Do nothing!
|
||||
}
|
||||
}
|
||||
|
||||
resetFormButtons() {
|
||||
const elm = document.querySelector('#hash');
|
||||
|
||||
this.deleteButton.disabled = (elm.value === '');
|
||||
}
|
||||
|
||||
newForm() {
|
||||
this.resetForm();
|
||||
|
||||
this.resetFormButtons();
|
||||
this.show(this.editor);
|
||||
this.hide(this.content);
|
||||
}
|
||||
|
||||
closeForm() {
|
||||
this.resetForm();
|
||||
|
||||
this.resetFormButtons();
|
||||
this.hide(this.editor);
|
||||
this.show(this.content);
|
||||
}
|
||||
|
||||
renderList() {
|
||||
const list = this.listData.map((item) => {
|
||||
return `<div class="dataRow" data-id="${item._id}" data-hash="${item.hash}">${item.name}</div>`;
|
||||
});
|
||||
|
||||
this.content.innerHTML = list.join('');
|
||||
}
|
||||
|
||||
getList() {
|
||||
axios.get(this.url)
|
||||
.then( (response) => {
|
||||
// handle success
|
||||
|
||||
this.listData = response.data.slice();
|
||||
this.renderList();
|
||||
})
|
||||
.catch( (error) => {
|
||||
// handle error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
getRecord(hash) {
|
||||
axios.get(`${this.url}/${hash}`)
|
||||
.then( (response) => {
|
||||
// handle success
|
||||
const data = Object.assign({}, response.data);
|
||||
|
||||
this.resetForm();
|
||||
this.loadForm(data);
|
||||
this.resetFormButtons();
|
||||
this.show(this.editor);
|
||||
this.hide(this.content);
|
||||
})
|
||||
.catch( (error) => {
|
||||
// handle error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
postData(data) {
|
||||
axios.post(`${this.url}`, data)
|
||||
.then( (response) => {
|
||||
// handle success
|
||||
if (response.data._id > 0) {
|
||||
this.closeForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.getList();
|
||||
}, 250);
|
||||
}
|
||||
})
|
||||
.catch( (error) => {
|
||||
// handle error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateData(data) {
|
||||
axios.put(`${this.url}/${data.hash}`, data)
|
||||
.then( (response) => {
|
||||
// handle success
|
||||
if (response.data.changes > 0) {
|
||||
this.closeForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.getList();
|
||||
}, 250);
|
||||
}
|
||||
})
|
||||
.catch( (error) => {
|
||||
// handle error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
deleteRecord(hash) {
|
||||
axios.delete(`${this.url}/${hash}`)
|
||||
.then( (response) => {
|
||||
// handle success
|
||||
if (response.data.changes > 0) {
|
||||
this.closeForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.getList();
|
||||
}, 250);
|
||||
}
|
||||
})
|
||||
.catch( (error) => {
|
||||
// handle error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
sendRecord() {
|
||||
const newData = this.getForm();
|
||||
|
||||
if (newData.hash === '')
|
||||
this.postData(newData);
|
||||
else
|
||||
this.updateData(newData);
|
||||
}
|
||||
|
||||
init() {
|
||||
console.log('init');
|
||||
|
||||
this.resetForm();
|
||||
this.setupClicks();
|
||||
this.getList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
(function () {
|
||||
const app = new Recipe();
|
||||
|
||||
app.init();
|
||||
})();
|
||||
|
208
src/v1/js/duff.js
Normal file
208
src/v1/js/duff.js
Normal file
@ -0,0 +1,208 @@
|
||||
const $ = require('jquery');
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const tpl = require('./lib/utils');
|
||||
|
||||
const app = {
|
||||
|
||||
};
|
||||
|
||||
app.Recipe = Backbone.Model.extend({
|
||||
'urlRoot':'http://localhost:3000/recipes',
|
||||
'defaults':{
|
||||
'_id':null,
|
||||
'name':'',
|
||||
'hash':''
|
||||
}
|
||||
});
|
||||
|
||||
app.RecipeCollection = Backbone.Collection.extend({
|
||||
'model':app.Recipe,
|
||||
'url':'http://localhost:3000/recipes'
|
||||
});
|
||||
|
||||
app.HeaderView = Backbone.View.extend({
|
||||
|
||||
'initialize':function () {
|
||||
console.log('>> HeaderView this', this);
|
||||
this.template = _.template(tpl.get('header'));
|
||||
},
|
||||
|
||||
'render':function (eventName) {
|
||||
console.log('>> HeaderView render', this);
|
||||
$(this.el).html(this.template());
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
'events':{
|
||||
'click .new':'newWine'
|
||||
},
|
||||
|
||||
'newRecipe':function (event) {
|
||||
app.navigate('recipe/new', true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.RecipeListView = Backbone.View.extend({
|
||||
|
||||
'tagName':'ul',
|
||||
|
||||
'initialize':function () {
|
||||
this.model.bind('reset', this.render, this);
|
||||
var self = this;
|
||||
this.model.bind('add', function (recipe) {
|
||||
$(self.el).append(new app.RecipeListItemView({ 'model':recipe }).render().el);
|
||||
});
|
||||
},
|
||||
|
||||
'render':function (eventName) {
|
||||
_.each(this.model.models, function (recipe) {
|
||||
$(this.el).append(new app.RecipeListItemView({ 'model':recipe }).render().el);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
app.RecipeListItemView = Backbone.View.extend({
|
||||
|
||||
'tagName':'li',
|
||||
|
||||
'initialize':function () {
|
||||
this.template = _.template(tpl.get('recipe-list-item'));
|
||||
this.model.bind('change', this.render, this);
|
||||
this.model.bind('destroy', this.close, this);
|
||||
},
|
||||
|
||||
'render':function (eventName) {
|
||||
$(this.el).html(this.template(this.model.toJSON()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.RecipeView = Backbone.View.extend({
|
||||
|
||||
'tagName':'div', // Not required since 'div' is the default if no el or tagName specified
|
||||
|
||||
'initialize':function () {
|
||||
this.template = _.template(tpl.get('recipe-details'));
|
||||
this.model.bind('change', this.render, this);
|
||||
},
|
||||
|
||||
'render':function (eventName) {
|
||||
$(this.el).html(this.template(this.model.toJSON()));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
'events':{
|
||||
'change input':'change',
|
||||
'click .save':'saveRecipe',
|
||||
'click .delete':'deleteRecipe'
|
||||
},
|
||||
|
||||
'change':function (event) {
|
||||
var target = event.target;
|
||||
console.log(`changing ${ target.id } from: ${ target.defaultValue } to: ${ target.value}`);
|
||||
// You could change your model on the spot, like this:
|
||||
// var change = {};
|
||||
// change[target.name] = target.value;
|
||||
// this.model.set(change);
|
||||
},
|
||||
|
||||
'saveRecipe':function () {
|
||||
this.model.set({
|
||||
'name':$('#name').val()
|
||||
|
||||
});
|
||||
if (this.model.isNew()) {
|
||||
var self = this;
|
||||
app.recipeList.create(this.model, {
|
||||
'success':function () {
|
||||
app.navigate(`recipe/${ self.model.id}`, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
this.model.save();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
'deleteRecipe':function () {
|
||||
this.model.destroy({
|
||||
'success':function () {
|
||||
alert('Recipe deleted successfully');
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.Router = Backbone.Router.extend({
|
||||
'initialize':function () {
|
||||
$('#header').html(new app.HeaderView().render().el);
|
||||
},
|
||||
'routes': {
|
||||
'': 'list',
|
||||
'recipe/new': 'newRecipe',
|
||||
'recipe/:id': 'recipeDetails'
|
||||
},
|
||||
|
||||
'list':function () {
|
||||
this.before();
|
||||
},
|
||||
'recipeDetails':function (id) {
|
||||
this.before(function () {
|
||||
var recipe = app.router.recipeList.get(id);
|
||||
app.showView('#content', new app.RecipeView({ 'model':recipe }));
|
||||
});
|
||||
},
|
||||
|
||||
'newRecipe':function () {
|
||||
this.before(function () {
|
||||
app.showView('#content', new app.RecipeView({ 'model':new app.recipe() }));
|
||||
});
|
||||
},
|
||||
|
||||
'showView':function (selector, view) {
|
||||
if (this.currentView)
|
||||
this.currentView.close();
|
||||
$(selector).html(view.render().el);
|
||||
this.currentView = view;
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
'before':function (callback) {
|
||||
console.log(this);
|
||||
if (this.recipeList) {
|
||||
if (callback) callback();
|
||||
}
|
||||
else {
|
||||
this.recipeList = new app.RecipeCollection();
|
||||
this.recipeList.fetch({ 'success': () => {
|
||||
console.log(this);
|
||||
console.log(app.router.recipeList);
|
||||
$('#sidebar').html(new app.RecipeListView({ 'model':app.router.recipeList }).render().el);
|
||||
if (callback) callback();
|
||||
} });
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
tpl.loadTemplates(['header', 'recipe-details', 'recipe-list-item'], function () {
|
||||
app.router = new app.Router();
|
||||
Backbone.history.start();
|
||||
});
|
31
src/v1/js/headerview.js
Normal file
31
src/v1/js/headerview.js
Normal file
@ -0,0 +1,31 @@
|
||||
const $ = require('jquery');
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
const tpl = require('./lib/utils');
|
||||
|
||||
const HeaderView = Backbone.View.extend({
|
||||
|
||||
'initialize':function () {
|
||||
console.log('>> this', this);
|
||||
this.template = _.template(tpl.get('header'));
|
||||
},
|
||||
|
||||
'render':function (eventName) {
|
||||
$(this.el).html(this.template());
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
'events':{
|
||||
'click .new':'newWine'
|
||||
},
|
||||
|
||||
'newWine':function (event) {
|
||||
app.navigate('wines/new', true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = { HeaderView };
|
18
src/v1/js/lib/models.js
Normal file
18
src/v1/js/lib/models.js
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const RecipeModel = Backbone.Model.extend({
|
||||
'urlRoot':'http://localhost:3000/recipes',
|
||||
'defaults':{
|
||||
'id':null,
|
||||
'name':'',
|
||||
'hash':''
|
||||
}
|
||||
});
|
||||
|
||||
const RecipeCollection = Backbone.Collection.extend({
|
||||
'model':RecipeModel,
|
||||
'url':'http://localhost:3000/recipes'
|
||||
});
|
||||
|
||||
module.exports = { RecipeModel, RecipeCollection };
|
37
src/v1/js/lib/utils.js
Normal file
37
src/v1/js/lib/utils.js
Normal file
@ -0,0 +1,37 @@
|
||||
const $ = require('jquery');
|
||||
|
||||
module.exports = {
|
||||
|
||||
// Hash of preloaded templates for the app
|
||||
'templates':{},
|
||||
|
||||
// Recursively pre-load all the templates for the app.
|
||||
// This implementation should be changed in a production environment. All the template files should be
|
||||
// concatenated in a single file.
|
||||
'loadTemplates':function (names, callback) {
|
||||
var that = this;
|
||||
|
||||
var loadTemplate = function (index) {
|
||||
var name = names[index];
|
||||
console.log(`Loading template: ${ name}`);
|
||||
$.get(`tpl/${ name }.html`, function (data) {
|
||||
that.templates[name] = data;
|
||||
index++;
|
||||
if (index < names.length)
|
||||
loadTemplate(index);
|
||||
else
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
loadTemplate(0);
|
||||
},
|
||||
|
||||
// Get template by name from hash of preloaded templates
|
||||
'get':function (name) {
|
||||
return this.templates[name];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// module.exports = { tpl };
|
7
test.json
Normal file
7
test.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Asian pepper steak crock pot recipe",
|
||||
"url": "https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe",
|
||||
"md": "Asian Pepper Steak Crock Pot Recipe\n ===\n [Source](https://www.marksdailyapple.com/asian-pepper-steak-crock-pot-recipe/)\n \n Ingredients:\n ---\n * 2 lbs. steak (sirloin is preferable, but any other good cut will do)\n * 2 tbsp coconut oil\n * 1-2 cloves of garlic, minced\n * 1/4 cup wheat-free tamari (similar to soy sauce)\n * 1 16 oz can bean sprouts, drained\n * 1 16 oz can diced tomatoes\n * 1 large green pepper, sliced in thin strips\n * 1 small onion, sliced\n * Salt and pepper to taste\n \n Method:\n ---\n 1. On a chopping board, cut the steak on an angle to make strips about a ½ inch thick. \n \n 2. In a large frying pan, add the oil and heat. Saute the steak until it lightly browns. \n \n 3. Drain excess fat, liberally coat the meat with ground pepper and put the meat in the crock pot. \n \n 4. Add garlic and tamari, and mix so that the steak is thoroughly coated.\n \n 5. Cook in a crock pot on low for 6 hours. \n \n 6. One hour before serving, add sprouts, tomatoes, green peppers and onions and turn crock pot to high. \n \n 7. Cook for one hour and then serve piping hot.",
|
||||
"meat": 2,
|
||||
"mealtype": 1
|
||||
}
|
Loading…
Reference in New Issue
Block a user