diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..ded31c0
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,18 @@
+{
+ "presets": [
+ ["env", {
+ "modules": false,
+ "targets": {
+ "browsers": "> 1%",
+ "uglify": true
+ },
+ "useBuiltIns": true
+ }]
+ ],
+
+ "plugins": [
+ "syntax-dynamic-import",
+ "transform-object-rest-spread",
+ ["transform-class-properties", { "spec": true }]
+ ]
+}
diff --git a/.dockerignore b/.dockerignore
index f9df56c..015453b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,5 +1,7 @@
.git/
-logs/*.*
+log/*.*
db/*.sqlite*
tmp/*.*
public/assets
+public/packs
+node_modules/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a2479fd..be82dae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,8 @@
/public/assets
.DS_Store
+/public/packs
+/public/packs-test
+/node_modules
+yarn-debug.log*
+.yarn-integrity
diff --git a/.postcssrc.yml b/.postcssrc.yml
new file mode 100644
index 0000000..e43bed1
--- /dev/null
+++ b/.postcssrc.yml
@@ -0,0 +1,7 @@
+plugins:
+ postcss-import: {}
+ postcss-cssnext: {
+ features: {
+ customProperties: false
+ }
+ }
\ No newline at end of file
diff --git a/.ruby-version b/.ruby-version
index 35cee72..ab6d278 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.4.3
+2.4.4
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 6c4f61c..f540915 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,16 @@ FROM phusion/passenger-ruby24:latest
# Use baseimage-docker's init process.
CMD ["/sbin/my_init"]
+# Install Node
+RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
+ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
+ echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends nodejs yarn && \
+ apt-get clean && rm -rf /var/lib/apt/lists/*
+
+RUN gem update --system && gem install bundler
+
# Enable Nginx / Passenger
RUN rm -f /etc/service/nginx/down
@@ -21,25 +31,20 @@ ENV RAILS_ENV docker
ENV PASSENGER_APP_ENV docker
# Setup directory and install gems
-RUN mkdir -p /home/app/parsley/
-COPY Gemfile /home/app/parsley/
-COPY Gemfile.lock /home/app/parsley/
-RUN gem install bundler
-RUN cd /home/app/parsley/ && bundle install --jobs 4
-
-# Copy the app into the image
-COPY . /home/app/parsley/
+RUN mkdir -p /home/app/parsley/log /home/app/parsley/tmp
+RUN chown -R app:app /home/app/parsley/
WORKDIR /home/app/parsley/
-# Set log permissions
-RUN mkdir -p /home/app/parsley/log
-RUN chmod 0777 /home/app/parsley/log
+COPY Gemfile* ./
+RUN bundle install --deployment --jobs 4 --without development test
+
+COPY package.json yarn.lock ./
+RUN yarn install --production=true
+
+# Copy the app into the image
+COPY --chown="app" . .
# Compile assets
-RUN env RAILS_ENV=production bundle exec rake assets:clobber assets:precompile
+RUN su app -c "bundle exec rails webpacker:clobber webpacker:compile"
-# Set ownership of the tmp folder
-RUN chown -R app:app /home/app/parsley/tmp
-# Clean up APT when done.
-RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
diff --git a/Gemfile b/Gemfile
index 6924713..3773a2b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,26 +1,22 @@
source 'https://rubygems.org'
-gem 'rails', '5.0.6'
-gem 'sqlite3'
-gem 'pg', '~> 0.21.0'
-gem 'sass-rails', '~> 5.0'
-gem 'uglifier', '>= 1.3.0'
+gem 'rails', '5.2.0'
+gem 'pg', '~> 1.0.0'
-gem 'puma'
+gem 'webpacker', '3.5.3'
+gem 'bootsnap', '>= 1.1.0', require: false
-# See https://github.com/rails/execjs#readme for more supported runtimes
-gem 'therubyracer', platforms: :ruby
-
-# Use jquery as the JavaScript library
-gem 'jquery-rails', '~> 4.3.1'
-gem 'bootstrap-sass', '~> 3.3.7'
-gem 'kaminari', '~> 1.1.1'
-gem 'turbolinks', '~> 5.1.0'
gem 'jbuilder', '~> 2.7'
-gem 'cocoon', '~> 1.2.9'
+#gem 'jbuilder', git: 'https://github.com/rails/jbuilder', branch: 'master'
+
+gem 'oj', '~> 3.6.2'
+
+gem 'kaminari', '~> 1.1.1'
gem 'unitwise', '~> 2.2.0'
gem 'redcarpet', '~> 3.4.0'
+gem 'dalli', '~> 2.7.6'
+
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.11'
@@ -28,11 +24,14 @@ gem 'tzinfo-data'
group :development, :test do
+ gem 'puma', '~> 3.11'
+ gem 'sqlite3'
+
gem 'guard', '~> 2.14.0'
gem 'guard-rspec', require: false
- gem 'rspec-rails', '~> 3.5.0'
+ gem 'rspec-rails', '~> 3.7.2'
gem 'rails-controller-testing'
- gem 'factory_girl_rails', '~> 4.8.0'
- gem 'database_cleaner', '~> 1.5.3'
+ gem 'factory_bot_rails', '~> 4.8.2'
+ gem 'database_cleaner', '~> 1.6.2'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index cde4cfd..5c9b1d9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,65 +1,65 @@
GEM
remote: https://rubygems.org/
specs:
- actioncable (5.0.6)
- actionpack (= 5.0.6)
- nio4r (>= 1.2, < 3.0)
- websocket-driver (~> 0.6.1)
- actionmailer (5.0.6)
- actionpack (= 5.0.6)
- actionview (= 5.0.6)
- activejob (= 5.0.6)
+ actioncable (5.2.0)
+ actionpack (= 5.2.0)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.0)
+ actionpack (= 5.2.0)
+ actionview (= 5.2.0)
+ activejob (= 5.2.0)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.0.6)
- actionview (= 5.0.6)
- activesupport (= 5.0.6)
+ actionpack (5.2.0)
+ actionview (= 5.2.0)
+ activesupport (= 5.2.0)
rack (~> 2.0)
- rack-test (~> 0.6.3)
+ rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.0.6)
- activesupport (= 5.0.6)
+ actionview (5.2.0)
+ activesupport (= 5.2.0)
builder (~> 3.1)
- erubis (~> 2.7.0)
+ erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.0.6)
- activesupport (= 5.0.6)
+ activejob (5.2.0)
+ activesupport (= 5.2.0)
globalid (>= 0.3.6)
- activemodel (5.0.6)
- activesupport (= 5.0.6)
- activerecord (5.0.6)
- activemodel (= 5.0.6)
- activesupport (= 5.0.6)
- arel (~> 7.0)
- activesupport (5.0.6)
+ activemodel (5.2.0)
+ activesupport (= 5.2.0)
+ activerecord (5.2.0)
+ activemodel (= 5.2.0)
+ activesupport (= 5.2.0)
+ arel (>= 9.0)
+ activestorage (5.2.0)
+ actionpack (= 5.2.0)
+ activerecord (= 5.2.0)
+ marcel (~> 0.3.1)
+ activesupport (5.2.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (~> 0.7)
+ i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- arel (7.1.4)
- autoprefixer-rails (8.1.0.1)
- execjs
- bcrypt (3.1.11)
- bootstrap-sass (3.3.7)
- autoprefixer-rails (>= 5.2.1)
- sass (>= 3.3.4)
+ arel (9.0.0)
+ bcrypt (3.1.12)
+ bootsnap (1.3.0)
+ msgpack (~> 1.0)
builder (3.2.3)
- cocoon (1.2.11)
coderay (1.1.2)
concurrent-ruby (1.0.5)
- crass (1.0.3)
- database_cleaner (1.5.3)
+ crass (1.0.4)
+ dalli (2.7.8)
+ database_cleaner (1.6.2)
diff-lcs (1.3)
- erubis (2.7.0)
- execjs (2.7.0)
- factory_girl (4.8.1)
+ erubi (1.7.1)
+ factory_bot (4.8.2)
activesupport (>= 3.0.0)
- factory_girl_rails (4.8.0)
- factory_girl (~> 4.8.0)
+ factory_bot_rails (4.8.2)
+ factory_bot (~> 4.8.2)
railties (>= 3.0.0)
- ffi (1.9.23)
+ ffi (1.9.25)
formatador (0.2.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
@@ -77,15 +77,11 @@ GEM
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
- i18n (0.9.5)
+ i18n (1.0.1)
concurrent-ruby (~> 1.0)
jbuilder (2.7.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
- jquery-rails (4.3.1)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
kaminari (1.1.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
@@ -98,52 +94,59 @@ GEM
activerecord
kaminari-core (= 1.1.1)
kaminari-core (1.1.1)
- libv8 (3.16.14.19)
liner (0.2.4)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
- loofah (2.2.1)
+ loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
- lumberjack (1.0.12)
+ lumberjack (1.0.13)
mail (2.7.0)
mini_mime (>= 0.1.1)
+ marcel (0.3.2)
+ mimemagic (~> 0.3.2)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (0.9.0)
+ mimemagic (0.3.2)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.3)
+ msgpack (1.2.4)
multi_json (1.13.1)
nenv (0.3.0)
- nio4r (2.3.0)
+ nio4r (2.3.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
notiffany (0.1.1)
nenv (~> 0.1)
shellany (~> 0.0)
+ oj (3.6.2)
parslet (1.8.2)
- pg (0.21.0)
+ pg (1.0.0)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
- puma (3.11.3)
- rack (2.0.4)
- rack-test (0.6.3)
- rack (>= 1.0)
- rails (5.0.6)
- actioncable (= 5.0.6)
- actionmailer (= 5.0.6)
- actionpack (= 5.0.6)
- actionview (= 5.0.6)
- activejob (= 5.0.6)
- activemodel (= 5.0.6)
- activerecord (= 5.0.6)
- activesupport (= 5.0.6)
+ puma (3.11.4)
+ rack (2.0.5)
+ rack-proxy (0.6.4)
+ rack
+ rack-test (1.0.0)
+ rack (>= 1.0, < 3)
+ rails (5.2.0)
+ actioncable (= 5.2.0)
+ actionmailer (= 5.2.0)
+ actionpack (= 5.2.0)
+ actionview (= 5.2.0)
+ activejob (= 5.2.0)
+ activemodel (= 5.2.0)
+ activerecord (= 5.2.0)
+ activestorage (= 5.2.0)
+ activesupport (= 5.2.0)
bundler (>= 1.3.0)
- railties (= 5.0.6)
+ railties (= 5.2.0)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
@@ -152,53 +155,41 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
- rails-html-sanitizer (1.0.3)
- loofah (~> 2.0)
- railties (5.0.6)
- actionpack (= 5.0.6)
- activesupport (= 5.0.6)
+ rails-html-sanitizer (1.0.4)
+ loofah (~> 2.2, >= 2.2.2)
+ railties (5.2.0)
+ actionpack (= 5.2.0)
+ activesupport (= 5.2.0)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rake (12.3.0)
+ rake (12.3.1)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
redcarpet (3.4.0)
- ref (2.0.0)
- rspec (3.5.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-core (3.5.4)
- rspec-support (~> 3.5.0)
- rspec-expectations (3.5.0)
+ rspec (3.7.0)
+ rspec-core (~> 3.7.0)
+ rspec-expectations (~> 3.7.0)
+ rspec-mocks (~> 3.7.0)
+ rspec-core (3.7.1)
+ rspec-support (~> 3.7.0)
+ rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-mocks (3.5.0)
+ rspec-support (~> 3.7.0)
+ rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-rails (3.5.2)
+ rspec-support (~> 3.7.0)
+ rspec-rails (3.7.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-support (~> 3.5.0)
- rspec-support (3.5.0)
+ rspec-core (~> 3.7.0)
+ rspec-expectations (~> 3.7.0)
+ rspec-mocks (~> 3.7.0)
+ rspec-support (~> 3.7.0)
+ rspec-support (3.7.1)
ruby_dep (1.5.0)
- sass (3.5.5)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- sass-rails (5.0.7)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
shellany (0.0.1)
signed_multiset (0.2.1)
sprockets (3.7.1)
@@ -209,27 +200,22 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
- therubyracer (0.12.3)
- libv8 (~> 3.16.14.15)
- ref
thor (0.20.0)
thread_safe (0.3.6)
- tilt (2.0.8)
- turbolinks (5.1.0)
- turbolinks-source (~> 5.1)
- turbolinks-source (5.1.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
- tzinfo-data (1.2018.3)
+ tzinfo-data (1.2018.5)
tzinfo (>= 1.0.0)
- uglifier (4.1.8)
- execjs (>= 0.3.0, < 3)
unitwise (2.2.0)
liner (~> 0.2)
memoizable (~> 0.4)
parslet (~> 1.5)
signed_multiset (~> 0.2)
- websocket-driver (0.6.5)
+ webpacker (3.5.3)
+ activesupport (>= 4.2)
+ rack-proxy (>= 0.6.1)
+ railties (>= 4.2)
+ websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
@@ -238,28 +224,25 @@ PLATFORMS
DEPENDENCIES
bcrypt (~> 3.1.11)
- bootstrap-sass (~> 3.3.7)
- cocoon (~> 1.2.9)
- database_cleaner (~> 1.5.3)
- factory_girl_rails (~> 4.8.0)
+ bootsnap (>= 1.1.0)
+ dalli (~> 2.7.6)
+ database_cleaner (~> 1.6.2)
+ factory_bot_rails (~> 4.8.2)
guard (~> 2.14.0)
guard-rspec
jbuilder (~> 2.7)
- jquery-rails (~> 4.3.1)
kaminari (~> 1.1.1)
- pg (~> 0.21.0)
- puma
- rails (= 5.0.6)
+ oj (~> 3.6.2)
+ pg (~> 1.0.0)
+ puma (~> 3.11)
+ rails (= 5.2.0)
rails-controller-testing
redcarpet (~> 3.4.0)
- rspec-rails (~> 3.5.0)
- sass-rails (~> 5.0)
+ rspec-rails (~> 3.7.2)
sqlite3
- therubyracer
- turbolinks (~> 5.1.0)
tzinfo-data
- uglifier (>= 1.3.0)
unitwise (~> 2.2.0)
+ webpacker (= 3.5.3)
BUNDLED WITH
1.16.1
diff --git a/LICENSE b/LICENSE
index 3d88d01..115e637 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 Dan Elbert
+Copyright (c) 2018 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 100c8d8..a4af231 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ A self hosted cookbook
Parsley is released under the MIT License.
-Copyright (C) 2016 Dan Elbert
+Copyright (C) 2018 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/assets/images/grey_wash_wall.png b/app/assets/images/grey_wash_wall.png
deleted file mode 100644
index d54e889..0000000
Binary files a/app/assets/images/grey_wash_wall.png and /dev/null differ
diff --git a/app/assets/images/old_map.png b/app/assets/images/old_map.png
deleted file mode 100644
index eb2373b..0000000
Binary files a/app/assets/images/old_map.png and /dev/null differ
diff --git a/app/assets/images/old_mathematics.png b/app/assets/images/old_mathematics.png
deleted file mode 100644
index 95285ff..0000000
Binary files a/app/assets/images/old_mathematics.png and /dev/null differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 52f2c50..b741814 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -10,24 +10,6 @@
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
-//= require jquery
-//= require jquery_ujs
-//= require turbolinks
-//= require bootstrap-sprockets
-//= require bootstrap-datepicker
-//= require bootstrap-tagsinput
-//= require cocoon
-//= require typeahead
-//= require autosize
-//= require chosen.jquery
-//= require codemirror
-//= require markdown
-//= require underscore
+
//= require_tree .
-// Setup star rating automagic
-$(document).on("turbolinks:load", function() {
-
- $("input[data-rating='true']").starRating();
-
-});
\ No newline at end of file
diff --git a/app/assets/javascripts/calculator.js b/app/assets/javascripts/calculator.js
deleted file mode 100644
index 106b795..0000000
--- a/app/assets/javascripts/calculator.js
+++ /dev/null
@@ -1,124 +0,0 @@
-(function($) {
-
- function State() {
- this.input = null;
- this.outputUnit = null;
- this.density = null;
- this.changed = false;
- }
-
- State.prototype.setInput = function(value) {
- if (value != this.input) {
- this.changed = true;
- this.input = value;
- }
- };
-
- State.prototype.setDensity = function(value) {
- if (value != this.density) {
- this.changed = true;
- this.density = value;
- }
- };
-
- State.prototype.setOutputUnit = function(value) {
- if (value != this.outputUnit) {
- this.changed = true;
- this.outputUnit = value;
- }
- };
-
- State.prototype.is_changed = function() {
- return this.changed;
- };
-
- State.prototype.reset = function() {
- this.changed = false;
- };
-
- var ingredientSearchEngine = new Bloodhound({
- initialize: false,
- datumTokenizer: function(datum) {
- return Bloodhound.tokenizers.whitespace(datum.name);
- },
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- identify: function(datum) { return datum.id; },
- sorter: function(a, b) {
- if (a.name < b.name) {
- return -1;
- } else if (b.name < a.name) {
- return 1;
- } else {
- return 0;
- }
- },
- remote: {
- url: '/calculator/ingredient_search.json?query=%QUERY',
- wildcard: '%QUERY'
- }
- });
-
- $(document).on("turbolinks:load", function() {
-
- var state = new State();
- var $input = $("#input");
- var $output = $("#output");
- var $density = $("#density");
- var $outputUnit = $("#output_unit");
-
- var performUpdate = _.debounce(function() {
- $.getJSON(
- "/calculator/calculate",
- {input: $input.val(), output_unit: $outputUnit.val(), density: $density.val()},
- function(data) {
- if (data.errors.length) {
- $("#errors_panel").show();
- $("#errors_container").html(data.errors.join(" "));
- } else {
- $("#errors_panel").hide();
- }
-
- $output.val(data.output);
- }
- );
-
- }, 500);
-
- var ingredientPicked = function(i) {
- $density.val(i.density);
- $density.trigger('change');
- };
-
- $input.add($outputUnit).add($density).on('change blur keyup', function(evt) {
- state.setInput($input.val());
- state.setOutputUnit($outputUnit.val());
- state.setDensity($density.val());
-
- if (state.is_changed()) {
- performUpdate();
- state.reset();
- }
- });
-
- if ($("#calculator").length) {
- ingredientSearchEngine.initialize(false);
-
- $("#ingredient").typeahead({},
- {
- name: 'ingredients',
- source: ingredientSearchEngine,
- display: function(datum) {
- return datum.name;
- }
- })
- .on("typeahead:select", function(evt, value) {
- ingredientPicked(value);
- })
- .on("typeahead:autocomplete", function(evt, value) {
- ingredientPicked(value);
- });
- }
-
- });
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/checkable.js b/app/assets/javascripts/checkable.js
deleted file mode 100644
index 42f5893..0000000
--- a/app/assets/javascripts/checkable.js
+++ /dev/null
@@ -1,36 +0,0 @@
-(function($) {
-
- var pluginName = "checkable";
-
- var defaultOptions = {
- childrenSelector: "li",
- selectedClass: "checked"
- };
-
- var methods = {
- initialize: function (opts, sources) {
-
- return this.each(function() {
- var options = $.extend({}, defaultOptions, opts);
- $(this).on("click", options.childrenSelector, function(evt) {
- $(evt.currentTarget).toggleClass(options.selectedClass);
- });
- });
- }
- };
-
- var privateMethods = {
-
- };
-
- $.fn[pluginName] = function (method) {
- if (methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else if (typeof method === 'object' || ! method) {
- return methods.initialize.apply(this, arguments);
- } else {
- $.error('Method ' + method + ' does not exist on jQuery.' + pluginName);
- }
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/flash_messages.js b/app/assets/javascripts/flash_messages.js
deleted file mode 100644
index a603ecf..0000000
--- a/app/assets/javascripts/flash_messages.js
+++ /dev/null
@@ -1,46 +0,0 @@
-
-var flashMessageTypeMap = {
- error: "danger",
- notice: "success"
-};
-
-function flashMessage(flashType, message) {
-
- var timeoutIdContainer = {};
-
- if (flashMessageTypeMap[flashType]) {
- flashType = flashMessageTypeMap[flashType];
- }
-
- var closeButton = $(" ")
- .addClass("close")
- .append($(" ").html("×"))
- .bind("click.Flash", function() { $(this).parent().hide({effect: "fade", duration: 1000}); clearTimeout(timeoutIdContainer.id); });
-
- var $flashDiv = $("
")
- .html(message)
- .append(closeButton)
- .addClass("popup")
- .addClass("alert")
- .addClass("alert-" + flashType)
- .hide()
- .appendTo("#flashContainer")
- .show({effect: "pulsate", times: 1, duration: 1500});
-
- timeoutIdContainer.id = setTimeout(function() {
- $flashDiv.unbind(".Flash");
- $flashDiv.hide({effect: "fade", duration: 1000});
- }, 5000);
-}
-
-$(document).on("turbolinks:load", function() {
- $("#flashHolder").find("div").each(function(idx, div) {
- var $div = $(div);
- var type = $div.attr("class");
- var message = $div.html();
-
- $div.remove();
-
- flashMessage(type, message);
- });
-});
\ No newline at end of file
diff --git a/app/assets/javascripts/ingredients.js b/app/assets/javascripts/ingredients.js
deleted file mode 100644
index d32c047..0000000
--- a/app/assets/javascripts/ingredients.js
+++ /dev/null
@@ -1,88 +0,0 @@
-
-window.INGREDIENT_API = {};
-
-(function($) {
-
- function initializeEditor($ingredientForm) {
- usdaFoodSearchEngine.initialize(false);
-
- var $typeahead = $ingredientForm.find(".ndbn_typeahead");
-
- $typeahead.typeahead_search({
- searchUrl: '/ingredients/usda_food_search.html',
- resultsContainer: $ingredientForm.find(".ndbn_results")
- },{
- name: 'usdaFoods',
- source: usdaFoodSearchEngine,
- limit: 20,
- display: function(datum) {
- return datum.name;
- }
- });
-
- $typeahead.on("typeahead_search:selected", function(evt, item) {
- selectNdbn($ingredientForm, item.ndbn);
- });
-
- $ingredientForm.on("click", ".ndbn_results .food_result", function(evt) {
- var $item = $(evt.target);
- var ndbn = $item.data("ndbn");
- selectNdbn($ingredientForm, ndbn);
- return false;
- });
- }
-
- function selectNdbn($ingredientForm, ndbn) {
- var id = $ingredientForm.find("input.id").val();
-
- $ingredientForm.find("input.ndbn").val(ndbn);
- $ingredientForm.attr("action", "/ingredients/" + id + "/select_ndbn").attr("data-remote", "true");
-
- $ingredientForm.submit();
- }
-
- function customTokenizer(str) {
- if (str) {
- var cleaned = str.replace(/,/g, "");
- return Bloodhound.tokenizers.whitespace(cleaned);
- } else {
- return [];
- }
- }
-
- window.INGREDIENT_API.initialize = function() {
- var $ingredientForm = $("#ingredient_form");
-
- if ($ingredientForm.length) {
- initializeEditor($ingredientForm);
- }
- };
-
- var usdaFoodSearchEngine = new Bloodhound({
- initialize: false,
- datumTokenizer: function(datum) {
- var str = datum ? datum.name : null;
- return customTokenizer(str);
- },
- queryTokenizer: customTokenizer,
- identify: function(datum) { return datum.ndbn; },
- sorter: function(a, b) {
- if (a.name < b.name) {
- return -1;
- } else if (b.name < a.name) {
- return 1;
- } else {
- return 0;
- }
- },
- remote: {
- url: '/ingredients/usda_food_search.json?query=%QUERY',
- wildcard: '%QUERY'
- }
- });
-
- $(document).on("turbolinks:load", function() {
- window.INGREDIENT_API.initialize();
- });
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/logs.js b/app/assets/javascripts/logs.js
deleted file mode 100644
index e6a1cdb..0000000
--- a/app/assets/javascripts/logs.js
+++ /dev/null
@@ -1,7 +0,0 @@
-(function($) {
-
- $(document).on("turbolinks:load", function() {
- $(".log-form input.datepicker").datepicker({autoclose: true, todayBtn: "linked", format: "yyyy-mm-dd"});
- });
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/recipe_editor.js b/app/assets/javascripts/recipe_editor.js
deleted file mode 100644
index 0d9ebc9..0000000
--- a/app/assets/javascripts/recipe_editor.js
+++ /dev/null
@@ -1,393 +0,0 @@
-(function($) {
-
- var ingredientSearchEngine = new Bloodhound({
- initialize: false,
- datumTokenizer: function(datum) {
- return Bloodhound.tokenizers.whitespace(datum.name);
- },
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- identify: function(datum) { return datum.id; },
- sorter: function(a, b) {
- if (a.name < b.name) {
- return -1;
- } else if (b.name < a.name) {
- return 1;
- } else {
- return 0;
- }
- },
- prefetch: {
- url: '/ingredients/prefetch.json',
- cache: false
- },
- remote: {
- url: '/ingredients/search.json?query=%QUERY',
- wildcard: '%QUERY'
- }
- });
-
- // var tagSearchEngine = new Bloodhound({
- // initialize: false,
- // datumTokenizer: function(datum) {
- // return Bloodhound.tokenizers.whitespace(datum.name);
- // },
- // queryTokenizer: Bloodhound.tokenizers.whitespace,
- // identify: function(datum) { return datum.id; },
- // sorter: function(a, b) {
- // if (a.name < b.name) {
- // return -1;
- // } else if (b.name < a.name) {
- // return 1;
- // } else {
- // return 0;
- // }
- // },
- // prefetch: {
- // url: '/tags/prefetch.json',
- // cache: false
- // },
- // remote: {
- // url: '/tags/search.json?query=%QUERY',
- // wildcard: '%QUERY'
- // }
- // });
-
- function reorder($container) {
- $container.find("div.nested-fields").each(function(idx, editor) {
- var $editor = $(editor);
- $editor.find('input.sort_order').val(idx + 1).trigger("changed");
- })
- }
-
-
- function initializeIngredientEditor($container, ingredientSearchEngine) {
- // $container is either an element that contains many editors, or a single editor.
- var $editors = $container.find(".ingredient-typeahead").closest(".nested-fields");
-
- $editors.each(function(idx, elem) {
- var $editor = $(elem);
- var $ingredientId = $editor.find("input.ingredient_id");
- var $group = $editor.find("div.typeahead-group");
-
- $editor.find(".ingredient-typeahead").typeahead({},
- {
- name: 'ingredients',
- source: ingredientSearchEngine,
- display: function(datum) {
- return datum.name;
- }
- });
-
- if ($ingredientId.val().length) {
- $group.addClass("has-success");
- }
- });
- }
-
- function ingredientItemPicked($typeahead, datum) {
- var $container = $typeahead.closest(".nested-fields");
- var $ingredientId = $container.find("input.ingredient_id");
- var $group = $container.find("div.typeahead-group");
-
- $ingredientId.val(datum.id);
- $typeahead.typeahead('val', datum.name);
- $group.addClass("has-success");
- }
-
- function ingredientNameChange($typeahead, ingredientSearchEngine) {
- var $container = $typeahead.closest(".nested-fields");
- var $ingredientId = $container.find("input.ingredient_id");
- var $group = $container.find("div.typeahead-group");
-
- var id = $ingredientId.val();
- var value = $typeahead.typeahead('val');
-
- if (id && id.length) {
- var found = ingredientSearchEngine.get([id]);
- if (found && found[0] && found[0].name != value) {
- // User has chosen something custom
- $ingredientId.val('');
-
- $group.removeClass("has-success");
- }
- }
- }
-
- function addIngredient(item) {
- $("#ingredient-list").one("cocoon:before-insert", function(e, $container) {
- var $ingredientId = $container.find("input.ingredient_id");
- var $name = $container.find("input.ingredient-typeahead");
- var $quantity = $container.find("input.quantity");
- var $units = $container.find("input.units");
- var $preparation = $container.find("input.preparation");
-
- $name.val(item.name);
- $ingredientId.val(item.ingredient_id);
- $units.val(item.units);
- $quantity.val(item.quantity);
- $preparation.val(item.preparation);
- });
-
- $("#addIngredientButton").trigger("click");
- }
-
- function getIngredients() {
- var data = [];
- $("#ingredient-list .ingredient-editor").each(function() {
- var $container = $(this);
-
- var $ingredientId = $container.find("input.ingredient_id");
- var $name = $container.find("input.ingredient-typeahead.tt-input");
- var $quantity = $container.find("input.quantity");
- var $units = $container.find("input.units");
- var $preparation = $container.find("input.preparation");
-
- data.push({ingredient_id: $ingredientId.val(), name: $name.typeahead("val"), quantity: $quantity.val(), units: $units.val(), preparation: $preparation.val()});
- });
-
- return data;
- }
-
-
- $(document).on("turbolinks:load", function() {
-
- var $ingredientList = $("#ingredient-list");
- var $tagInput = $("select.tag_names");
- var $stepInput = $("textarea#recipe_step_text");
-
- if ($ingredientList.length) {
- ingredientSearchEngine.initialize(false);
- }
-
- if ($stepInput.length) {
- CodeMirror.fromTextArea(
- $stepInput[0],
- {
- mode: {
- name: 'markdown',
- strikethrough: true
- },
- // config tomfoolery to enable soft tabs
- extraKeys: {
- Tab: function(cm) {
- if (cm.somethingSelected()) {
- cm.indentSelection("add");
- return;
- }
-
- if (cm.options.indentWithTabs)
- cm.replaceSelection("\t", "end", "+input");
- else
- cm.execCommand("insertSoftTab");
- },
- "Shift-Tab": function(cm) {
- cm.indentSelection("subtract");
- }
- },
- indentUnit: 2,
- tabSize: 2,
- indentWithTabs: false,
- lineWrapping: true,
- lineNumbers: true
- }
- );
- }
-
- $tagInput.tagsinput({
- trimValue: true,
- confirmKeys: [9, 13, 32, 44] // tab, enter, space, comma
- });
-
-
- initializeIngredientEditor($ingredientList, ingredientSearchEngine);
-
- $ingredientList
- .on("cocoon:after-insert", function(e, item) {
- reorder($ingredientList);
- initializeIngredientEditor(item, ingredientSearchEngine);
- })
- .on("cocoon:after-remove", function(e, item) {
- if (item.find(".remove-button.existing").length) {
- item.detach().appendTo("#deleted_ingredients");
- }
- reorder($ingredientList);
- })
- .on("typeahead:change", function(evt, value) {
- ingredientNameChange($(evt.target), ingredientSearchEngine);
- })
- .on("typeahead:select", function(evt, value) {
- ingredientItemPicked($(evt.target), value);
- })
- .on("typeahead:autocomplete", function(evt, value) {
- ingredientItemPicked($(evt.target), value);
- })
- .on("click", "button.ingredient_convert_btn", function(evt) {
-
- });
-
- $('#convert_modal')
- .on('show.bs.modal', function (event) {
- var $button = $(event.relatedTarget);
- var $modal = $(this);
-
- var $editor = $button.closest(".ingredient-editor");
-
- $modal.data('ingredient-editor', $editor);
-
- var $quantity = $editor.find("input.quantity");
- var $units = $editor.find("input.units");
- var $ingredientId = $editor.find("input.ingredient_id");
-
- var $modalQuantity = $modal.find("input.quantity");
- var $modalUnits = $modal.find("input.units");
- var $modalIngredientId = $modal.find("input.ingredient_id");
-
- $modalQuantity.val($quantity.val());
- $modalUnits.val($units.val());
- $modalIngredientId.val($ingredientId.val());
- })
- .on("ajax:success", "form", function(evt, data, status, xhr) {
- var $modal = $("#convert_modal");
- var $editor = $modal.data('ingredient-editor');
-
- if (data.success) {
- var $quantity = $editor.find("input.quantity");
- var $units = $editor.find("input.units");
-
- var $modalOutUnits = $modal.find("input.output_units");
-
- $quantity.val(data.output_quantity);
- if ($modalOutUnits.val().length) {
- $units.val($modalOutUnits.val());
- }
-
- $modal.modal('hide');
- } else {
-
- }
-
- $("#modal_form_container").replaceWith($(data.form_html));
- });
-
- var $bulkIngredientsModal = $("#bulk_ingredients_modal");
- var $ingredientBulkInput = $("#ingredient_bulk_input");
- var $ingredientBulkList = $("#ingredient_bulk_parsed_list");
- autosize($ingredientBulkInput);
-
- var parseBulkIngredients = function() {
- var data = $ingredientBulkInput.val();
- $ingredientBulkList.empty();
-
- var parsed = [];
- var x;
-
- var lines = data.replace("\r", "").split("\n");
-
- var regex = /^(?:([\d\/.]+(?:\s+[\d\/]+)?)\s+)?(?:([\w-]+)(?:\s+of)?\s+)?([^,]*)(?:,\s*(.*))?$/i;
-
- var magicFunc = function(str) {
- if (str == "-") {
- return "";
- } else {
- return str;
- }
- };
-
- for (x = 0; x < lines.length; x++) {
- var line = lines[x].trim();
- if (line.length == 0) { continue; }
-
- var barIndex = line.lastIndexOf("|");
- var afterBar = null;
-
- if (barIndex >= 0) {
- afterBar = line.slice(barIndex + 1);
- line = line.slice(0, barIndex);
- }
-
- var match = line.match(regex);
-
- if (match) {
- item = {quantity: magicFunc(match[1]), units: magicFunc(match[2]), name: magicFunc(match[3]), preparation: magicFunc(match[4])};
- if (afterBar) {
- item.name = item.name + ", " + item.preparation;
- item.preparation = afterBar;
- }
- parsed.push(item);
- } else {
- parsed.push(null);
- }
- }
-
- $bulkIngredientsModal.data("bulkData", parsed);
-
- for (x = 0; x < parsed.length; x++) {
- var item = parsed[x];
- if (item != null) {
- $ingredientBulkList.append(
- $(" ")
- .append($(" ").addClass("quantity").text(item.quantity))
- .append($(" ").addClass("units").text(item.units))
- .append($(" ").addClass("name").text(item.name))
- .append($(" ").addClass("preparation").text(item.preparation))
- );
- } else {
- $ingredientBulkList.append(
- $(" ")
- .append($(" ").attr("colspan", "4").text(""))
- );
- }
- }
- };
-
- $bulkIngredientsModal
- .on('show.bs.modal', function (event) {
- var data = getIngredients();
- var x;
- var text = [];
-
- for (x = 0; x < data.length; x++) {
- var item = data[x];
-
- text.push(
- item.quantity + " " +
- (item.units || "-") + " " +
- item.name +
- (item.preparation ? (", " + item.preparation) : "")
- );
- }
-
- $ingredientBulkInput.val(text.join("\n"));
-
- setTimeout(function() {
- parseBulkIngredients();
- autosize.update($ingredientBulkInput);
- }, 250);
- });
-
- $ingredientBulkInput.on('keyup', function() {
- parseBulkIngredients();
- });
-
- $("#bulkIngredientAddSubmit").on("click", function() {
- var parsed = $bulkIngredientsModal.data("bulkData");
- var x;
-
- $("#ingredient-list").find(".remove-button").trigger("click");
-
- if (parsed && parsed.length) {
- for (x = 0; x < parsed.length; x++) {
- var item = parsed[x];
- if (item) {
- addIngredient(item)
- }
- }
- }
-
- $bulkIngredientsModal.modal('hide')
- });
-
- });
-
-
-})(jQuery);
diff --git a/app/assets/javascripts/recipes.js b/app/assets/javascripts/recipes.js
deleted file mode 100644
index d2f328e..0000000
--- a/app/assets/javascripts/recipes.js
+++ /dev/null
@@ -1,33 +0,0 @@
-(function($) {
-
- $(document).on("turbolinks:load", function() {
- $(".recipe-view ul.ingredients").checkable();
- $(".recipe-view div.steps ol").checkable();
-
- var $searchBtn = $("#recipe_index_search_button");
-
- if ($searchBtn.length) {
- var $form = $("#search_form");
- var $nameInput = $("#name_search");
- var $tagsInput = $("#tags_search");
-
- $form.submit(function() {
- $("#criteria_name").val($nameInput.val());
- $("#criteria_tags").val($tagsInput.val());
- });
-
- $searchBtn.click(function(evt) {
- $form.submit();
- });
-
- $nameInput.add($tagsInput).on('keydown', function(evt) {
- if (evt.which == 13) {
- console.log('keydown enter pressed');
- $form.submit();
- }
- });
-
- }
- });
-
-})(jQuery);
diff --git a/app/assets/javascripts/star_rating.js b/app/assets/javascripts/star_rating.js
deleted file mode 100644
index 7a4eb9e..0000000
--- a/app/assets/javascripts/star_rating.js
+++ /dev/null
@@ -1,133 +0,0 @@
-
-(function ($) {
-
- var pluginName = "starRating";
- var defaultOptions = {
- starCount: 5,
- readOnly: false,
- interval: 1,
- size: '30px'
- };
-
- var methods = {
- initialize: function(opts) {
- return this.each(function() {
- var $input = $(this);
- var attrOpts = {};
- var inputData = $input.data();
-
- if (inputData[pluginName.toLowerCase()] === true) {
- if (console && console.log) {
- console.log("star rating has already been initialized; skipping...");
- }
- return;
- }
-
- $input.attr("data-" + pluginName.toLowerCase(), "true");
-
- if (inputData.interval) {
- attrOpts.interval = inputData.interval;
- }
-
- if (inputData.starcount) {
- attrOpts.starCount = inputData.starcount;
- }
-
- if (inputData.size) {
- attrOpts.size = inputData.size;
- }
-
- if ($input.is(":disabled")) {
- attrOpts.readOnly = true;
- }
-
- var options = _.extend({}, defaultOptions, attrOpts, opts);
-
- var $widget = $(" ").addClass("star-rating").css({'font-size': options.size});
- var $emptySet = $(" ").addClass("empty-set").appendTo($widget);
- var $filledSet = $(" ").addClass("filled-set").appendTo($widget);
-
- options.$input = $input;
- options.$emptySet = $emptySet;
-
- for (var x = 1; x <= options.starCount; x++) {
- $emptySet.append(
- $(" ").addClass("star empty")
- );
-
- $filledSet.append(
- $(" ").addClass("star full")
- );
- }
-
- $widget.data(pluginName + ".options", options);
-
- $input.data(pluginName, true).hide().after($widget);
-
- privateMethods.updateStars($widget);
-
- if (!options.readOnly) {
- $widget
- .on("click." + pluginName, function(e) {
- var value = privateMethods.calculateRating($widget, e.pageX);
- privateMethods.setValue($widget, value);
- })
- .on("mousemove." + pluginName, function(e) {
- var value = privateMethods.calculateRating($widget, e.pageX);
- privateMethods.updateStars($widget, value);
- })
- .on("mouseleave." + pluginName, function (e) {
- privateMethods.updateStars($widget);
- });
- }
-
- $input
- .on("change." + pluginName, function() {
- privateMethods.updateStars($widget);
- });
- });
- }
- };
-
- var privateMethods = {
- updateStars: function($widget, value) {
- var options = $widget.data(pluginName + ".options");
- value = (value == null ? (parseFloat(options.$input.val() || 0)) : value);
- $widget.find(".filled-set").css({width: privateMethods.calculateWidth($widget, value)});
- },
-
- setValue: function($widget, value) {
- var options = $widget.data(pluginName + ".options");
- options.$input.val(value);
- privateMethods.updateStars($widget);
- },
-
- calculateWidth: function($widget, value) {
- var options = $widget.data(pluginName + ".options");
- var width = options.$emptySet.width();
- return ((value / options.starCount) * 100).toString() + "%";
- },
-
- // Given a screen X coordinate, calculates the nearest valid value for this rating widget
- calculateRating: function($widget, screenX) {
- var options = $widget.data(pluginName + ".options");
- var offset = options.$emptySet.offset();
- var width = options.$emptySet.width();
- var ratio = (screenX - offset.left) / width;
- ratio = Math.max(0, Math.min(1, ratio));
-
- return Math.ceil(options.starCount * (1 / options.interval) * ratio) / (1 / options.interval);
- }
- };
-
- $.fn[pluginName] = function(method) {
- if (methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else if (typeof method === 'object' || ! method) {
- return methods.initialize.apply(this, arguments);
- } else {
- $.error('Method ' + method + ' does not exist on jQuery.' + pluginName);
- }
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/typeahead_search.js b/app/assets/javascripts/typeahead_search.js
deleted file mode 100644
index 0b8c3bc..0000000
--- a/app/assets/javascripts/typeahead_search.js
+++ /dev/null
@@ -1,112 +0,0 @@
-(function($) {
-
- var pluginName = "typeahead_search";
-
- var defaultOptions = {
- };
-
- var methods = {
- initialize: function (opts, sources) {
-
- return this.each(function() {
- var options = $.extend({}, defaultOptions, opts);
- var $this = $(this);
-
- if ($this.data(pluginName)) {
- return;
- }
-
- $this.data(pluginName, {options: options});
-
- var $inputGroup = $('
');
- var $btnSpan = $(" ");
- var $btn = $(" ").append($(" ").addClass("glyphicon glyphicon-search"));
-
- $btnSpan.append($btn);
-
- $this.after($inputGroup);
- $this.detach();
- $inputGroup.append($this);
- $inputGroup.append($btnSpan);
-
- $this.typeahead(opts, sources);
-
- $btn.on("click", function(evt) {
- privateMethods.search($this);
- });
-
- $this
- .on("typeahead:change", function(evt, value) {
- privateMethods.change($this, value);
- })
- .on("typeahead:select", function(evt, value) {
- privateMethods.select($this, value);
- })
- .on("typeahead:autocomplete", function(evt, value) {
- privateMethods.autocomplete($this, value);
- })
- .on("keydown", function(evt) {
- if (evt.keyCode == 13) {
- evt.preventDefault();
- privateMethods.search($this);
- }
- });
- });
- },
-
- val: function() {
- if (this.length) {
- return $(this[0]).typeahead("val");
- } else {
- return null;
- }
- }
- };
-
- var privateMethods = {
- change: function($this, value) {
-
- },
-
- select: function($this, item) {
- $this.trigger("typeahead_search:selected", item);
- },
-
- autocomplete: function($this, item) {
- $this.trigger("typeahead_search:selected", item);
- },
-
- search: function($this) {
- var options = privateMethods.options($this);
- var input = $this.typeahead("val");
-
- if (input.length && options.searchUrl && options.searchUrl.length) {
- $.get({
- url: options.searchUrl,
- data: {query: input},
- dataType: 'html',
- success: function(data) {
- $(options.resultsContainer).empty().append(data);
- }
- })
- }
-
- $this.typeahead("close");
- },
-
- options: function($this) {
- return $this.data(pluginName).options;
- }
- };
-
- $.fn[pluginName] = function (method) {
- if (methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else if (typeof method === 'object' || ! method) {
- return methods.initialize.apply(this, arguments);
- } else {
- $.error('Method ' + method + ' does not exist on jQuery.' + pluginName);
- }
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/typeahead_selector.js b/app/assets/javascripts/typeahead_selector.js
deleted file mode 100644
index 16a83c4..0000000
--- a/app/assets/javascripts/typeahead_selector.js
+++ /dev/null
@@ -1,70 +0,0 @@
-(function($) {
-
- var pluginName = "typeahead_selector";
-
- var defaultOptions = {
- };
-
- var methods = {
- initialize: function (opts, sources) {
-
- return this.each(function() {
- var options = $.extend({}, defaultOptions, opts);
- var $this = $(this);
- $this.typeahead(opts, sources);
-
- $this
- .on("typeahead:change", function(evt, value) {
- privateMethods.change($this, value);
- })
- .on("typeahead:select", function(evt, value) {
- privateMethods.select($this, value);
- })
- .on("typeahead:autocomplete", function(evt, value) {
- privateMethods.autocomplete($this, value);
- });
- });
- },
-
- val: function() {
- if (this.length) {
- return $(this[0]).data('typeahead_selected');
- } else {
- return null;
- }
- }
- };
-
- var privateMethods = {
- change: function($this, value) {
- var item = $this.data('typeahead_pending');
- if (item) {
- $this.data('typeahead_pending', null);
- $this.data('typeahead_selected', item);
- $this.trigger("typeahead_selector:selected", item);
- } else {
- $this.data('typeahead_selected', null);
- $this.trigger("typeahead_selector:invalid", value);
- }
- },
-
- select: function($this, item) {
- $this.data('typeahead_pending', item);
- },
-
- autocomplete: function($this, item) {
- $this.data('typeahead_pending', item);
- }
- };
-
- $.fn[pluginName] = function (method) {
- if (methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else if (typeof method === 'object' || ! method) {
- return methods.initialize.apply(this, arguments);
- } else {
- $.error('Method ' + method + ' does not exist on jQuery.' + pluginName);
- }
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 36aeb37..243ae18 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -10,97 +10,4 @@
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
* file per style scope.
*
- *= require flash_messages
- *= require chosen
- *= require codemirror
- *= require bootstrap-datepicker3
- *= require bootstrap-tagsinput
- *= require font_references
*/
-
-@import "bootstrap-sprockets";
-@import "journal_custom_colors";
-@import "journal/_variables";
-@import "bootstrap";
-@import "journal/_bootswatch";
-
-@import "typeahead-bootstrap";
-@import "recipes";
-@import "star_rating";
-@import "codemirror_custom";
-
-// Skin overrides
-.has-error {
- .help-block,
- .control-label,
- .radio,
- .checkbox,
- .radio-inline,
- .checkbox-inline,
- &.radio label,
- &.checkbox label,
- &.radio-inline label,
- &.checkbox-inline label,
- .form-control-feedback {
- color: $state-danger-text;
- }
-
- .form-control,
- .form-control:focus {
- border-color: $state-danger-border;
- }
-}
-
-$footer_height: 40px;
-
-html {
- position: relative;
- min-height: 100%;
-}
-body {
- /* Margin bottom by footer height */
- margin-bottom: $footer_height + 20;
- background: image_url("grey_wash_wall.png");
-}
-
-#main_container {
- background: white;
- padding-bottom: 15px;
-}
-
-.footer {
- position: absolute;
- bottom: 0;
- width: 100%;
- height: $footer_height;
- background-color: $gray-lighter;
- border-top: solid 1px $gray-light;
-}
-
-a.sorted {
- position: relative;
-}
-
-a.sorted.asc:after {
- content: " ";
- position: absolute;
- margin: 8px 0 0 6px;
- width: 0;
- height: 0;
- border-left: 5px solid transparent;
- border-right: 5px solid transparent;
-
- border-top: 5px solid black;
-}
-
-a.sorted.desc:after {
- content: " ";
- position: absolute;
- margin: 8px 0 0 6px;
- width: 0;
- height: 0;
- border-left: 5px solid transparent;
- border-right: 5px solid transparent;
-
- border-bottom: 5px solid black;
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/codemirror_custom.scss b/app/assets/stylesheets/codemirror_custom.scss
deleted file mode 100644
index 5dfe50c..0000000
--- a/app/assets/stylesheets/codemirror_custom.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.CodeMirror {
- border: 1px solid $gray-light;
- font-family: inconsolata monospace;
- font-size: 15px;
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/flash_messages.scss b/app/assets/stylesheets/flash_messages.scss
deleted file mode 100644
index b1392f8..0000000
--- a/app/assets/stylesheets/flash_messages.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-
-#flashHolder {
- display: none;
-}
-
-#flashContainer {
- position: fixed;
- width: 100%;
- z-index: 9999;
-}
-
-.alert.popup {
- position: fixed;
- top: 15px;
- left: 50%;
- width: 30em;
- margin-left: -15em;
-}
-
-.alert {
-}
-
-.flash {
- text-align: center;
- width: 30em;
- padding: 5px;
- margin-left: auto;
- margin-right: auto;
-}
-
-.error {
- background-color: #fff6ff;
- border: 3px solid #fda8a8;
-}
-
-.warning {
- background-color: #ffffdd;
- border: 3px solid #ffdd00;
-}
-
-.notice {
- background-color: #D8F6CE;
- border: 3px solid #3ADF00;
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/font_references.scss b/app/assets/stylesheets/font_references.scss
deleted file mode 100644
index 3830510..0000000
--- a/app/assets/stylesheets/font_references.scss
+++ /dev/null
@@ -1,105 +0,0 @@
-@font-face {
- font-family: 'Open Sans';
- font-style: normal;
- font-weight: 300;
- src: local('Open Sans Light'), local('OpenSans-Light'), font_url("open-sans-light.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Open Sans';
- font-style: normal;
- font-weight: 400;
- src: local('Open Sans'), local('OpenSans'), font_url("open-sans.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Open Sans';
- font-style: normal;
- font-weight: 700;
- src: local('Open Sans Bold'), local('OpenSans-Bold'), font_url("open-sans-bold.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Open Sans';
- font-style: italic;
- font-weight: 400;
- src: local('Open Sans Italic'), local('OpenSans-Italic'), font_url("open-sans-italic.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Open Sans';
- font-style: italic;
- font-weight: 300;
- src: local('Open Sans Light Italic'), local('OpenSansLight-Italic'), font_url("open-sans-light-italic.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Open Sans';
- font-style: italic;
- font-weight: 700;
- src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), font_url("open-sans-bold-italic.woff2") format('woff2');
-}
-
-
-@font-face {
- font-family: 'Roboto';
- font-style: normal;
- font-weight: 300;
- src: local('Roboto Light'), local('Roboto-Light'), font_url("roboto-light.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Roboto';
- font-style: normal;
- font-weight: 400;
- src: local('Roboto'), local('Roboto-Regular'), font_url("roboto.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Roboto';
- font-style: normal;
- font-weight: 500;
- src: local('Roboto Medium'), local('Roboto-Medium'), font_url("roboto-medium.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Roboto';
- font-style: normal;
- font-weight: 700;
- src: local('Roboto Bold'), local('Roboto-Bold'), font_url("roboto-bold.woff2") format('woff2');
-}
-
-@font-face {
- font-family: 'Raleway';
- font-style: normal;
- font-weight: 400;
- src: local('Raleway'), font_url("raleway.woff2") format('woff2');
-}
-@font-face {
- font-family: 'Raleway';
- font-style: normal;
- font-weight: 700;
- src: local('Raleway Bold'), local('Raleway-Bold'), font_url("raleway-bold.woff2") format('woff2');
-}
-
-
-@font-face {
- font-family: 'News Cycle';
- font-style: normal;
- font-weight: 400;
- src: local('News Cycle'), local('NewsCycle'), font_url("news-cycle.woff2") format('woff2');
-}
-@font-face {
- font-family: 'News Cycle';
- font-style: normal;
- font-weight: 700;
- src: local('News Cycle Bold'), local('NewsCycle-Bold'), font_url("news-cycle-bold.woff2") format('woff2');
-}
-
-
-@font-face {
- font-family: 'Inconsolata';
- font-style: normal;
- font-weight: 400;
- src: local('Inconsolata Regular'), local('Inconsolata-Regular'), font_url('inconsolata.woff2') format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
-}
-@font-face {
- font-family: 'Inconsolata';
- font-style: normal;
- font-weight: 700;
- src: local('Inconsolata Bold'), local('Inconsolata-Bold'), font_url('inconsolata-bold.woff2') format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/ingredients.scss b/app/assets/stylesheets/ingredients.scss
deleted file mode 100644
index 98f8d50..0000000
--- a/app/assets/stylesheets/ingredients.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the ingredients controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/journal_custom_colors.scss b/app/assets/stylesheets/journal_custom_colors.scss
deleted file mode 100644
index 4196c72..0000000
--- a/app/assets/stylesheets/journal_custom_colors.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-
-$brand-primary: darken(#93C54B, 10%);
-$brand-success: $brand-primary;
-$text-color: #3E3F3A;
-
diff --git a/app/assets/stylesheets/recipes.scss b/app/assets/stylesheets/recipes.scss
deleted file mode 100644
index 647c0b5..0000000
--- a/app/assets/stylesheets/recipes.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-// Place all the styles related to the recipes controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
-
-div.table_tags {
- width: 275px;
-}
-
-div.recipe_list_controls {
- width: 85px;
-}
-
-@mixin editor {
-
- @extend .well;
- @extend .well-sm;
-
- margin-bottom: 10px;
- padding-top: 4px;
-
- .form-group {
- margin-bottom: 10px;
- }
-
- .remove-button {
- @extend .btn;
- @extend .btn-danger;
- @extend .btn-sm;
-
- position: absolute;
- top: 0;
- right: 9px;
- }
-}
-
-div.ingredient-editor {
- @include editor;
-
-}
-
-div#ingredient-list {
- padding-bottom: 15px;
-}
-
-div#ingredient-list {
-
- @media (min-width: $screen-md-min) {
- .ingredient-editor .control-label {
- display: none;
- }
-
- .ingredient-editor:first-child .control-label {
- display: inline-block;
- }
- }
-}
-
-div.recipe-view {
-
- .source {
- @extend .col-xs-6;
- word-wrap: break-word;
- }
-
- .ingredients div {
- padding-bottom: 15px;
- }
-
- .steps div {
- padding-bottom: 15px;
- }
-
- li.checked {
- text-decoration: line-through;
- }
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/star_rating.scss b/app/assets/stylesheets/star_rating.scss
deleted file mode 100644
index 44f1186..0000000
--- a/app/assets/stylesheets/star_rating.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-span.star-rating {
-
- display: inline-block;
- color: gold;
- cursor: default;
- position: relative;
- white-space: nowrap;
-
- .empty-set {
- color: gray;
- opacity: 0.5;
- }
-
- .filled-set {
- overflow-x: hidden;
- position: absolute;
- top: 0;
- left: 0;
- }
-
- span.star {
-
- @extend .glyphicon;
-
- &.empty {
- @extend .glyphicon-star-empty;
- }
-
- &.full {
- @extend .glyphicon-star;
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/assets/stylesheets/typeahead-bootstrap.scss b/app/assets/stylesheets/typeahead-bootstrap.scss
deleted file mode 100644
index 67090b2..0000000
--- a/app/assets/stylesheets/typeahead-bootstrap.scss
+++ /dev/null
@@ -1,64 +0,0 @@
-span.twitter-typeahead .tt-menu,
-span.twitter-typeahead .tt-dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 1000;
- display: none;
- float: left;
- min-width: 160px;
- padding: 5px 0;
- margin: 2px 0 0;
- list-style: none;
- font-size: 14px;
- text-align: left;
- background-color: #ffffff;
- border: 1px solid #cccccc;
- border: 1px solid rgba(0, 0, 0, 0.15);
- border-radius: 4px;
- -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
- background-clip: padding-box;
-}
-
-span.twitter-typeahead .tt-hint {
- color: #A5A5A5;
-}
-
-span.twitter-typeahead .tt-suggestion {
- display: block;
- padding: 3px 20px;
- clear: both;
- font-weight: normal;
- line-height: 1.42857143;
- color: #333333;
- white-space: nowrap;
-}
-
-span.twitter-typeahead .tt-suggestion.tt-cursor,
-span.twitter-typeahead .tt-suggestion:hover,
-span.twitter-typeahead .tt-suggestion:focus {
- color: #ffffff;
- text-decoration: none;
- outline: 0;
- background-color: #337ab7;
-}
-
-span.twitter-typeahead {
- width: 100%;
-}
-.input-group span.twitter-typeahead {
- display: block !important;
-}
-.input-group span.twitter-typeahead .tt-menu,
-.input-group span.twitter-typeahead .tt-dropdown-menu {
- top: 32px !important;
-}
-.input-group.input-group-sm span.twitter-typeahead .tt-menu,
-.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
- top: 28px !important;
-}
-.input-group.input-group-lg span.twitter-typeahead .tt-menu,
-.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
- top: 44px !important;
-}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 08f0768..c0cb0c9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,6 +3,14 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+ def verified_request?
+ if request.content_type == "application/json"
+ true
+ else
+ super()
+ end
+ end
+
def ensure_valid_user
unless current_user?
flash[:warning] = "You must login"
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 3dfb671..2efdb55 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -1,4 +1,11 @@
class HomeController < ApplicationController
+
+ skip_forgery_protection
+
+ def index
+ render layout: false
+ end
+
def about
end
diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb
index 0212da6..e98aeaf 100644
--- a/app/controllers/ingredients_controller.rb
+++ b/app/controllers/ingredients_controller.rb
@@ -1,13 +1,20 @@
class IngredientsController < ApplicationController
- before_action :set_ingredient, only: [:edit, :update, :destroy]
+ before_action :set_ingredient, only: [:show, :edit, :update, :destroy]
- before_action :ensure_valid_user, except: [:index]
+ before_action :ensure_valid_user, except: [:index, :show]
# GET /ingredients
# GET /ingredients.json
def index
- @ingredients = Ingredient.all.order(:name)
+ @ingredients = Ingredient.all.order(:name).page(params[:page]).per(params[:per])
+ if params[:name].present?
+ @ingredients = @ingredients.matches_tokens(:name, params[:name].split.take(4))
+ end
+ end
+
+ def show
+
end
# GET /ingredients/new
@@ -32,7 +39,7 @@ class IngredientsController < ApplicationController
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully created.' }
- format.json { render :show, status: :created, location: @ingredient }
+ format.json { render json: { success: true }, status: :created, location: @ingredient }
else
format.html { render :new }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
@@ -51,7 +58,7 @@ class IngredientsController < ApplicationController
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
- format.json { render :show, status: :ok, location: @ingredient }
+ format.json { render json: { success: true }, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
@@ -85,9 +92,7 @@ class IngredientsController < ApplicationController
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
- respond_to do |format|
- format.js {}
- end
+ render :show
end
def prefetch
@@ -127,7 +132,7 @@ class IngredientsController < ApplicationController
# Never trust parameters from the scary internet, only allow the white list through.
def ingredient_params
- params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
+ params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ash, :iron, :magnesium, :phosphorus, :potassium, :zinc, :copper, :manganese, :vit_c, :vit_b6, :vit_b12, :vit_a, :vit_e, :vit_d, :cholesterol, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
end
def conversion_params
diff --git a/app/controllers/logs_controller.rb b/app/controllers/logs_controller.rb
index 02f4947..bf50151 100644
--- a/app/controllers/logs_controller.rb
+++ b/app/controllers/logs_controller.rb
@@ -2,12 +2,12 @@ class LogsController < ApplicationController
before_action :ensure_valid_user
- before_action :set_log, only: [:show, :edit, :update, :destroy]
+ before_action :set_log, only: [:show, :update, :destroy]
before_action :set_recipe, only: [:new, :create]
before_action :require_recipe, only: [:new, :create]
def index
- @logs = Log.for_user(current_user).order(:date)
+ @logs = Log.for_user(current_user).order(date: :desc).page(params[:page]).per(params[:per])
end
def show
@@ -21,9 +21,9 @@ class LogsController < ApplicationController
def update
ensure_owner(@log) do
if @log.update(log_params)
- redirect_to logs_path, notice: 'Log Entry was successfully updated.'
+ render json: { success: true }
else
- render :edit
+ render json: @log.errors, status: :unprocessable_entity
end
end
end
@@ -45,9 +45,9 @@ class LogsController < ApplicationController
@log.source_recipe = @recipe
if @log.save
- redirect_to logs_path, notice: 'Log Entry was successfully created.'
+ render json: { success: true }
else
- render :new
+ render json: @log.errors, status: :unprocessable_entity
end
end
@@ -61,7 +61,7 @@ class LogsController < ApplicationController
private
def set_log
- @log = Log.find(params[:id])
+ @log = Log.includes({recipe: {recipe_ingredients: {ingredient: :ingredient_units} }}).find(params[:id])
end
def set_recipe
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
index cc90341..a417743 100644
--- a/app/controllers/notes_controller.rb
+++ b/app/controllers/notes_controller.rb
@@ -77,6 +77,6 @@ class NotesController < ApplicationController
# Never trust parameters from the scary internet, only allow the white list through.
def note_params
- params.require(:note).permit(:user_id, :content)
+ params.require(:note).permit(:content)
end
end
diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb
index 1e32c6c..68f2aea 100644
--- a/app/controllers/recipes_controller.rb
+++ b/app/controllers/recipes_controller.rb
@@ -6,7 +6,7 @@ class RecipesController < ApplicationController
# GET /recipes
def index
- @criteria = ViewModels::RecipeCriteria.new(params[:criteria])
+ @criteria = ViewModels::RecipeCriteria.new(criteria_params)
@criteria.page = params[:page]
@criteria.per = params[:per]
@recipes = Recipe.for_criteria(@criteria).includes(:tags)
@@ -40,26 +40,6 @@ class RecipesController < ApplicationController
end
end
- @recipe = RecipeDecorator.decorate(@recipe, view_context)
-
- end
-
- # GET /recipes/1
- def scale
- @scale = params[:factor]
- @recipe.scale(@scale, true)
- @recipe = RecipeDecorator.decorate(@recipe, view_context)
- render :show
- end
-
- # GET /recipes/new
- def new
- @recipe = Recipe.new
- end
-
- # GET /recipes/1/edit
- def edit
- ensure_owner @recipe
end
# POST /recipes
@@ -68,9 +48,9 @@ class RecipesController < ApplicationController
@recipe.user = current_user
if @recipe.save
- redirect_to @recipe, notice: 'Recipe was successfully created.'
+ render json: { success: true }
else
- render :new
+ render json: @recipe.errors, status: :unprocessable_entity
end
end
@@ -78,23 +58,25 @@ class RecipesController < ApplicationController
def update
ensure_owner(@recipe) do
if @recipe.update(recipe_params)
- redirect_to @recipe, notice: 'Recipe was successfully updated.'
+ render json: { success: true }
else
- render :edit
+ render json: @recipe.errors, status: :unprocessable_entity
end
end
end
+ # POST /recipes/preview_steps
+ def preview_steps
+ render json: { rendered_steps: MarkdownProcessor.render(params[:step_text]) }
+ end
+
# DELETE /recipes/1
def destroy
ensure_owner(@recipe) do
@recipe.deleted = true
+ @recipe.save!(validate: false)
- if @recipe.save(validate: false)
- redirect_to recipes_url, notice: 'Recipe was successfully destroyed.'
- else
- redirect_to recipes_url, error: 'Recipe could not be destroyed.'
- end
+ render json: { success: true }
end
end
@@ -108,4 +90,8 @@ class RecipesController < ApplicationController
def recipe_params
params.require(:recipe).permit(:name, :description, :source, :yields, :total_time, :active_time, :step_text, tag_names: [], recipe_ingredients_attributes: [:name, :ingredient_id, :quantity, :units, :preparation, :sort_order, :id, :_destroy])
end
+
+ def criteria_params
+ params.require(:criteria).permit(*ViewModels::RecipeCriteria::PARAMS)
+ end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1b8921a..8a090fc 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,6 +1,12 @@
class UsersController < ApplicationController
- before_action :ensure_valid_user, except: [:login, :verify_login, :new, :create]
+ UserProxy = Struct.new(:user_id)
+
+ before_action :ensure_valid_user, except: [:show, :login, :verify_login, :new, :create]
+ skip_before_action :verify_authenticity_token, only: [:verify_login]
+
+ def show
+ end
def login
@@ -9,18 +15,24 @@ class UsersController < ApplicationController
def logout
set_current_user(nil)
session.destroy
- flash[:notice] = "Logged out"
- redirect_to root_path
+
+ respond_to do |format|
+ format.html { redirect_to root_path, notice: "Logged out" }
+ format.json { render json: { success: true } }
+ end
end
def verify_login
- if user = User.authenticate(params[:username], params[:password])
- set_current_user(user)
- flash[:notice] = "Welcome, #{user.display_name}"
- redirect_to root_path
- else
- flash[:error] = "Invalid credentials"
- render :login
+
+ respond_to do |format|
+ if user = User.authenticate(params[:username], params[:password])
+ set_current_user(user)
+ format.html { redirect_to root_path, notice: "Welcome, #{user.display_name}" }
+ format.json { render json: { success: true, user: { id: user.id, name: user.display_name, admin: user.admin? } } }
+ else
+ format.html { flash[:error] = "Invalid credentials"; render :login }
+ format.json { render json: { success: false, message: 'Invalid Credentials', user: nil } }
+ end
end
end
@@ -31,11 +43,15 @@ class UsersController < ApplicationController
def create
@user = User.new(user_params)
- if @user.save
- set_current_user(@user)
- redirect_to root_path, notice: 'User was successfully created.'
- else
- render action: :new
+ respond_to do |format|
+ if @user.save
+ set_current_user(@user)
+ format.html { redirect_to root_path, notice: 'User created.' }
+ format.json { render :show, status: :created, location: @user }
+ else
+ format.html { render :new }
+ format.json { render json: @user.errors, status: :unprocessable_entity }
+ end
end
end
@@ -45,10 +61,15 @@ class UsersController < ApplicationController
def update
@user = current_user
- if @user.update(user_params)
- redirect_to root_path, notice: 'User account updated'
- else
- render action: 'edit'
+
+ respond_to do |format|
+ if @user.update(user_params)
+ format.html { redirect_to root_path, notice: 'User updated.' }
+ format.json { render :show, status: :created, location: @user }
+ else
+ format.html { render :edit }
+ format.json { render json: @user.errors, status: :unprocessable_entity }
+ end
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 095f5d7..5d3ea78 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -13,57 +13,4 @@ module ApplicationHelper
decorated
end
- def timestamp(time)
- time ? time.strftime('%D %R') : ''
- end
-
- def nav_items
- nav = [
- nav_item('Recipes', recipes_path, 'recipes'),
- nav_item('Ingredients', ingredients_path, 'ingredients'),
- nav_item('Logs', logs_path, 'logs', current_user?),
- nav_item('Calculator', calculator_path, 'calculator'),
- nav_item('About', about_path, 'home'),
- nav_item('Notes', notes_path, 'notes', current_user?),
- nav_item('Admin', admin_users_path, 'admin/users', current_user? && current_user.admin?)
- ]
-
- nav.compact
- end
-
- def profile_nav_items
- if current_user
- [content_tag('li', class: 'dropdown') do
- li_cnt = ''.html_safe
-
- li_cnt << link_to('#', class: 'dropdown-toggle', data: {toggle: 'dropdown'}, role: 'button') do
- ''.html_safe << "#{current_user.display_name}" << content_tag('span', '', class: 'caret')
- end
-
- li_cnt << content_tag('ul', class: 'dropdown-menu') do
- ''.html_safe << nav_item('Profile', edit_user_path) << nav_item('Logout', logout_path)
- end
-
- li_cnt
- end]
- else
- [nav_item('Login', login_path)]
- end
- end
-
- def nav_item(name, url, controller = nil, visible = true)
- !visible ? nil : content_tag('li', link_to(name, url), class: active_for_controller(controller))
- end
-
- def active_for_controller(controller)
- if current_controller == controller
- 'active'
- else
- ''
- end
- end
-
- def current_controller
- params[:controller]
- end
end
diff --git a/app/helpers/ingredients_helper.rb b/app/helpers/ingredients_helper.rb
index e2c8b6e..58448ab 100644
--- a/app/helpers/ingredients_helper.rb
+++ b/app/helpers/ingredients_helper.rb
@@ -1,11 +1,4 @@
module IngredientsHelper
- def ndbn_button_class(ingredient)
- if ingredient.ndbn.present?
- 'btn btn-success'
- else
- 'btn btn-default'
- end
- end
end
diff --git a/app/helpers/recipes_helper.rb b/app/helpers/recipes_helper.rb
index a9417fc..83710ba 100644
--- a/app/helpers/recipes_helper.rb
+++ b/app/helpers/recipes_helper.rb
@@ -1,71 +1,3 @@
module RecipesHelper
- def recipe_time(recipe)
- output = ''.html_safe
- if recipe.total_time.present?
- output << "#{humanize_seconds(recipe.total_time.to_i.minutes)}"
- if recipe.active_time.present?
- output << " (#{humanize_seconds(recipe.active_time.to_i.minutes)} active)"
- end
- elsif recipe.active_time.present?
- output << humanize_seconds(recipe.active_time.to_i.minutes)
- end
-
- output
- end
-
- def humanize_seconds(secs)
- [[60, :s], [60, :m], [24, :h], [1000, :d]].map{ |count, name|
- if secs > 0
- secs, n = secs.divmod(count)
- n == 0 ? nil : "#{n.to_i} #{name}"
- end
- }.compact.reverse.join(' ')
- end
-
- def nutrient_row(recipe, nutrients, heading, nutrient_name)
- content_tag('tr') do
- [
- content_tag('td', heading),
- recipe.parsed_yield ? content_tag('td', nutrients.send("#{nutrient_name}_per".to_sym, recipe.parsed_yield.number)) : nil,
- content_tag('td', nutrients.send("#{nutrient_name}".to_sym))
- ].compact.join("\n".html_safe).html_safe
- end
- end
-
- def index_sort_header(text, field, criteria)
- uri = URI(request.original_fullpath)
- query = Rack::Utils.parse_query(uri.query)
-
- directions = [:asc, :desc]
-
- current_field = criteria.sort_column
- current_direction = criteria.sort_direction
- field_param = 'criteria[sort_column]'
- direction_param = 'criteria[sort_direction]'
-
- if request.get?
- is_sorted = current_field == field.to_sym
-
- if is_sorted && directions.include?(current_direction)
- direction = (directions.reject { |d| d == current_direction }).first
- else
- direction = directions.first
- end
-
- if is_sorted && direction == :asc
- link_class = 'sorted desc'
- elsif is_sorted && direction == :desc
- link_class = 'sorted asc'
- else
- link_class = 'sorted'
- end
-
- query[field_param.to_s] = field.to_s
- query[direction_param.to_s] = direction.to_s
- link_to text, "#{uri.path}?#{query.to_query}", class: link_class
- else
- text
- end
- end
end
diff --git a/app/javascript/components/App.vue b/app/javascript/components/App.vue
new file mode 100644
index 0000000..3df7376
--- /dev/null
+++ b/app/javascript/components/App.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppAutocomplete.vue b/app/javascript/components/AppAutocomplete.vue
new file mode 100644
index 0000000..7a9919e
--- /dev/null
+++ b/app/javascript/components/AppAutocomplete.vue
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+ {{ optionValue(opt) }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppConfirm.vue b/app/javascript/components/AppConfirm.vue
new file mode 100644
index 0000000..5ddca85
--- /dev/null
+++ b/app/javascript/components/AppConfirm.vue
@@ -0,0 +1,47 @@
+
+
+
+ OK
+ Cancel
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppDatePicker.vue b/app/javascript/components/AppDatePicker.vue
new file mode 100644
index 0000000..603b851
--- /dev/null
+++ b/app/javascript/components/AppDatePicker.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppDateTime.vue b/app/javascript/components/AppDateTime.vue
new file mode 100644
index 0000000..c118690
--- /dev/null
+++ b/app/javascript/components/AppDateTime.vue
@@ -0,0 +1,63 @@
+
+
+
+ {{ friendlyString }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppIcon.vue b/app/javascript/components/AppIcon.vue
new file mode 100644
index 0000000..6338437
--- /dev/null
+++ b/app/javascript/components/AppIcon.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppModal.vue b/app/javascript/components/AppModal.vue
new file mode 100644
index 0000000..f015190
--- /dev/null
+++ b/app/javascript/components/AppModal.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppNavbar.vue b/app/javascript/components/AppNavbar.vue
new file mode 100644
index 0000000..4125205
--- /dev/null
+++ b/app/javascript/components/AppNavbar.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppPager.vue b/app/javascript/components/AppPager.vue
new file mode 100644
index 0000000..06f111b
--- /dev/null
+++ b/app/javascript/components/AppPager.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppRating.vue b/app/javascript/components/AppRating.vue
new file mode 100644
index 0000000..d18075e
--- /dev/null
+++ b/app/javascript/components/AppRating.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppTagEditor.vue b/app/javascript/components/AppTagEditor.vue
new file mode 100644
index 0000000..8008fc8
--- /dev/null
+++ b/app/javascript/components/AppTagEditor.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppTextField.vue b/app/javascript/components/AppTextField.vue
new file mode 100644
index 0000000..6088e16
--- /dev/null
+++ b/app/javascript/components/AppTextField.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/AppValidationErrors.vue b/app/javascript/components/AppValidationErrors.vue
new file mode 100644
index 0000000..661bbfb
--- /dev/null
+++ b/app/javascript/components/AppValidationErrors.vue
@@ -0,0 +1,21 @@
+
+
+
+ {{ prop }}: {{ errs.join(", ") }}
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/IngredientEdit.vue b/app/javascript/components/IngredientEdit.vue
new file mode 100644
index 0000000..28c09ee
--- /dev/null
+++ b/app/javascript/components/IngredientEdit.vue
@@ -0,0 +1,263 @@
+
+
+
{{action}} {{ingredient.name || "[Unnamed Ingredient]"}}
+
+
+
+
+
+
+
Nutrient Databank Number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Grams
+
+
+ {{unit.description}}
+ {{unit.gram_weight}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{nutrient.label}}
+
+
+
+
+
+ {{nutrient.unit}}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/IngredientShow.vue b/app/javascript/components/IngredientShow.vue
new file mode 100644
index 0000000..201f720
--- /dev/null
+++ b/app/javascript/components/IngredientShow.vue
@@ -0,0 +1,21 @@
+
+
+ {{ingredient.name}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/LogEdit.vue b/app/javascript/components/LogEdit.vue
new file mode 100644
index 0000000..4536b10
--- /dev/null
+++ b/app/javascript/components/LogEdit.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
Creating Log for {{ log.recipe.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/LogShow.vue b/app/javascript/components/LogShow.vue
new file mode 100644
index 0000000..a52660f
--- /dev/null
+++ b/app/javascript/components/LogShow.vue
@@ -0,0 +1,67 @@
+
+
+
[Log Entry] {{log.recipe.name}}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/NoteEdit.vue b/app/javascript/components/NoteEdit.vue
new file mode 100644
index 0000000..722ed70
--- /dev/null
+++ b/app/javascript/components/NoteEdit.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+ Save
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/RecipeEdit.vue b/app/javascript/components/RecipeEdit.vue
new file mode 100644
index 0000000..1ada677
--- /dev/null
+++ b/app/javascript/components/RecipeEdit.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ingredients
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/RecipeEditIngredientEditor.vue b/app/javascript/components/RecipeEditIngredientEditor.vue
new file mode 100644
index 0000000..2c60302
--- /dev/null
+++ b/app/javascript/components/RecipeEditIngredientEditor.vue
@@ -0,0 +1,214 @@
+
+
+
Bulk Edit
+
+
+
+
+
+
+
+
+ #
+ Unit
+ Name
+ Prep
+
+
+ {{i.quantity}}
+ {{i.units}}
+ {{i.name}}
+ {{i.preparation}}
+
+
+
+
+ Save
+ Cancel
+
+
+
+
+
+
+
Add Ingredient
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/RecipeEditIngredientItem.vue b/app/javascript/components/RecipeEditIngredientItem.vue
new file mode 100644
index 0000000..8b6310e
--- /dev/null
+++ b/app/javascript/components/RecipeEditIngredientItem.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/RecipeShow.vue b/app/javascript/components/RecipeShow.vue
new file mode 100644
index 0000000..47547e0
--- /dev/null
+++ b/app/javascript/components/RecipeShow.vue
@@ -0,0 +1,238 @@
+
+
+
+ Loading...
+
+
+
+
+
+
+
Time
+
{{timeDisplay}}
+
+
+
+
+
+
Yields
+
{{recipe.yields}}
+
+
+
+
+
+
Source
+
{{recipe.source}}
+
+
+
+
+
+
+
+
+
+
+
+ {{i.display_name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Item
+ Value
+
+
+ {{nutrient.label}}
+ {{nutrient.value}}
+
+
+
+
Nutrition Calculation Warnings
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Convert
+ Close
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/The404Page.vue b/app/javascript/components/The404Page.vue
new file mode 100644
index 0000000..ad2916e
--- /dev/null
+++ b/app/javascript/components/The404Page.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheAboutPage.vue b/app/javascript/components/TheAboutPage.vue
new file mode 100644
index 0000000..0488ece
--- /dev/null
+++ b/app/javascript/components/TheAboutPage.vue
@@ -0,0 +1,31 @@
+
+
+
About
+
+
+ A Recipe manager. Source available from my Git repository at https://source.elbert.us .
+
+
+
+ Parsley is released under the MIT License. All code © Dan Elbert 2018.
+
+
+
+ Food data provided by:
+
+ US Department of Agriculture, Agricultural Research Service, Nutrient Data Laboratory. USDA National Nutrient Database for Standard Reference, Release 28. Version Current: September 2015. Internet: http://www.ars.usda.gov/nea/bhnrc/ndl
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheAdminUserEditor.vue b/app/javascript/components/TheAdminUserEditor.vue
new file mode 100644
index 0000000..12d0159
--- /dev/null
+++ b/app/javascript/components/TheAdminUserEditor.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheAdminUserList.vue b/app/javascript/components/TheAdminUserList.vue
new file mode 100644
index 0000000..c5c3715
--- /dev/null
+++ b/app/javascript/components/TheAdminUserList.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
User List
+
+
+
+
+ Name
+ Email
+ Admin?
+
+
+
+
+
+ {{u.name}}
+ {{u.email}}
+ {{u.admin}}
+
+ X
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheCalculator.vue b/app/javascript/components/TheCalculator.vue
new file mode 100644
index 0000000..69991fb
--- /dev/null
+++ b/app/javascript/components/TheCalculator.vue
@@ -0,0 +1,126 @@
+
+
+
Calculator
+
+
+ {{err}}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheIngredient.vue b/app/javascript/components/TheIngredient.vue
new file mode 100644
index 0000000..096e76d
--- /dev/null
+++ b/app/javascript/components/TheIngredient.vue
@@ -0,0 +1,49 @@
+
+
+
+ Loading...
+
+
+
+
+
+
Edit
+
Back
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheIngredientCreator.vue b/app/javascript/components/TheIngredientCreator.vue
new file mode 100644
index 0000000..24c7afa
--- /dev/null
+++ b/app/javascript/components/TheIngredientCreator.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+ Save
+ Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheIngredientEditor.vue b/app/javascript/components/TheIngredientEditor.vue
new file mode 100644
index 0000000..25b7035
--- /dev/null
+++ b/app/javascript/components/TheIngredientEditor.vue
@@ -0,0 +1,64 @@
+
+
+
+
+ Loading...
+
+
+
+
+
+
Save
+
Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheIngredientList.vue b/app/javascript/components/TheIngredientList.vue
new file mode 100644
index 0000000..fc019fb
--- /dev/null
+++ b/app/javascript/components/TheIngredientList.vue
@@ -0,0 +1,163 @@
+
+
+
Ingredients
+
+
+ Create Ingredient
+
+
+
+
+
+
+
+ Name
+ USDA
+ KCal per 100g
+ Density (oz/cup)
+
+
+
+
+
+
+
+
+
+
+
+ {{i.name}}
+
+ {{i.kcal}}
+ {{i.density}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create Ingredient
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheLog.vue b/app/javascript/components/TheLog.vue
new file mode 100644
index 0000000..6251bf8
--- /dev/null
+++ b/app/javascript/components/TheLog.vue
@@ -0,0 +1,51 @@
+
+
+
+ Loading...
+
+
+
+
+
+
+ Edit
+ Back
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheLogCreator.vue b/app/javascript/components/TheLogCreator.vue
new file mode 100644
index 0000000..3d8db85
--- /dev/null
+++ b/app/javascript/components/TheLogCreator.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+ Save Log
+ Cancel
+
+
+
+
+ Save Log
+ Cancel
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheLogEditor.vue b/app/javascript/components/TheLogEditor.vue
new file mode 100644
index 0000000..8f40f14
--- /dev/null
+++ b/app/javascript/components/TheLogEditor.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Save Log
+ Cancel
+
+
+
+
+ Save Log
+ Cancel
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheLogList.vue b/app/javascript/components/TheLogList.vue
new file mode 100644
index 0000000..ec4bd3b
--- /dev/null
+++ b/app/javascript/components/TheLogList.vue
@@ -0,0 +1,93 @@
+
+
+
+ Log Entries
+
+
+
+
+ Recipe
+ Date
+ Rating
+ Notes
+
+
+
+ {{l.recipe.name}}
+
+
+ {{l.notes}}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheNotesList.vue b/app/javascript/components/TheNotesList.vue
new file mode 100644
index 0000000..75ed5ee
--- /dev/null
+++ b/app/javascript/components/TheNotesList.vue
@@ -0,0 +1,98 @@
+
+
+
+ Add Note
+
+
+
+
+
+
+
+
+ Note
+ Date
+
+
+
+
+
+ {{ n.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheRecipe.vue b/app/javascript/components/TheRecipe.vue
new file mode 100644
index 0000000..172050c
--- /dev/null
+++ b/app/javascript/components/TheRecipe.vue
@@ -0,0 +1,79 @@
+
+
+
+ Loading...
+
+
+
{{ recipe.name }}
+
+ {{tag}}
+
+
+ {{recipe.converted_scale}} X
+ {{recipe.converted_system}}
+ {{recipe.converted_unit}}
+
+
+
+
+
+
Edit
+
Back
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheRecipeCreator.vue b/app/javascript/components/TheRecipeCreator.vue
new file mode 100644
index 0000000..b492600
--- /dev/null
+++ b/app/javascript/components/TheRecipeCreator.vue
@@ -0,0 +1,59 @@
+
+
+
+
Creating {{ recipe.name || "[Unamed Recipe]" }}
+
+
+
+
+
+
Save
+
Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheRecipeEditor.vue b/app/javascript/components/TheRecipeEditor.vue
new file mode 100644
index 0000000..a768680
--- /dev/null
+++ b/app/javascript/components/TheRecipeEditor.vue
@@ -0,0 +1,67 @@
+
+
+
+
+ Loading...
+
+
+
Editing {{ recipe.name || "[Unamed Recipe]" }}
+
+
+
+
+
+
+
Save
+
Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheRecipeList.vue b/app/javascript/components/TheRecipeList.vue
new file mode 100644
index 0000000..3f1b2ef
--- /dev/null
+++ b/app/javascript/components/TheRecipeList.vue
@@ -0,0 +1,216 @@
+
+
+
Recipes
+
+
Create Recipe
+
+
+
+
+
+
+
+
+ {{h.label}}
+
+
+ {{h.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{r.name}}
+
+
+ {{tag}}
+
+
+
+
+ --
+
+ {{ r.yields }}
+ {{ formatRecipeTime(r.total_time, r.active_time) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheUserCreator.vue b/app/javascript/components/TheUserCreator.vue
new file mode 100644
index 0000000..8cdd367
--- /dev/null
+++ b/app/javascript/components/TheUserCreator.vue
@@ -0,0 +1,57 @@
+
+
+
+
Create New User
+
+
+
+
+
+
Save
+
Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/TheUserEditor.vue b/app/javascript/components/TheUserEditor.vue
new file mode 100644
index 0000000..d5da994
--- /dev/null
+++ b/app/javascript/components/TheUserEditor.vue
@@ -0,0 +1,77 @@
+
+
+
+
Edit User
+
+
+
+
+
+
Save
+
Cancel
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/UserEdit.vue b/app/javascript/components/UserEdit.vue
new file mode 100644
index 0000000..8009594
--- /dev/null
+++ b/app/javascript/components/UserEdit.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/components/UserLogin.vue b/app/javascript/components/UserLogin.vue
new file mode 100644
index 0000000..12bf320
--- /dev/null
+++ b/app/javascript/components/UserLogin.vue
@@ -0,0 +1,100 @@
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/javascript/config.js b/app/javascript/config.js
new file mode 100644
index 0000000..4ca3355
--- /dev/null
+++ b/app/javascript/config.js
@@ -0,0 +1,4 @@
+
+export default {
+ baseApiUrl: null
+}
\ No newline at end of file
diff --git a/app/javascript/lib/Api.js b/app/javascript/lib/Api.js
new file mode 100644
index 0000000..bef827a
--- /dev/null
+++ b/app/javascript/lib/Api.js
@@ -0,0 +1,412 @@
+import config from '../config';
+import * as Errors from './Errors';
+
+class Api {
+ constructor() {
+ }
+
+ baseUrl() { return config.baseApiUrl; }
+
+ url(path) {
+ return this.baseUrl() + path;
+ }
+
+ checkStatus(response) {
+ if (response.status == 204) {
+ return null;
+ } else if (response.ok) {
+ return response.json();
+ } else if (response.status === 404) {
+ throw new Errors.ApiNotFoundError(response.statusText, response);
+ } else if (response.status === 422) {
+ return response.json().then(json => { throw new Errors.ApiValidationError(null, response, json) }, jsonErr => { throw new Errors.ApiValidationError(null, response, null) });
+ } else {
+ throw new Errors.ApiServerError(response.statusText || "Unknown Server Error", response);
+ }
+ }
+
+ performRequest(url, method, params = {}, headers = {}) {
+ const hasBody = Object.keys(params || {}).length !== 0;
+
+ const reqHeaders = new Headers();
+ reqHeaders.append('Accept', 'application/json');
+ reqHeaders.append('Content-Type', 'application/json');
+
+ for (let key in headers) {
+ reqHeaders.append(key, headers[key]);
+ }
+
+ const opts = {
+ headers: reqHeaders,
+ method: method,
+ credentials: "same-origin"
+ };
+
+ if (hasBody) {
+ opts.body = JSON.stringify(params);
+ }
+
+ return fetch(url, opts).then(this.checkStatus);
+ }
+
+ objectToUrlParams(obj, queryParams = [], prefixes = []) {
+ for (let key in obj) {
+ const val = obj[key];
+ const paramName = prefixes.join("[") + "[".repeat(Math.min(prefixes.length, 1)) + encodeURIComponent(key) + "]".repeat(prefixes.length);
+ if (Array.isArray(val)) {
+ for (let x of val) {
+ queryParams.push(paramName + "[]=" + (x === null ? '' : encodeURIComponent(x)));
+ }
+ } else if (typeof(val) === "object") {
+ this.objectToUrlParams(val, queryParams, prefixes.concat([key]));
+ } else {
+ queryParams.push(paramName + "=" + (val === null ? '' : encodeURIComponent(val)));
+ }
+ }
+
+ return queryParams;
+ }
+
+ buildGetUrl(url, params = {}) {
+ const queryParams = this.objectToUrlParams(params);
+ if (queryParams.length) {
+ url = url + "?" + queryParams.join("&");
+ }
+ return url;
+ }
+
+ get(url, params = {}) {
+ url = this.buildGetUrl(url, params);
+
+ return this.performRequest(url, "GET");
+ }
+
+ cacheFirstGet(url, params = {}, dataHandler) {
+ url = this.buildGetUrl(url, params);
+ let networkDataReceived = false;
+
+ const networkUpdate = this.performRequest(url, "GET", {}, {"Cache-Then-Network": "true"})
+ .then(data => {
+ networkDataReceived = true;
+ return dataHandler(data);
+ });
+
+ return caches.match(url)
+ .then(response => {
+ if (!response) throw Error("No data");
+ return response.json();
+ })
+ .then(data => {
+ // don't overwrite newer network data
+ if (!networkDataReceived) {
+ dataHandler(data);
+ }
+ })
+ .catch(function() {
+ // we didn't get cached data, the network is our last hope:
+ return networkUpdate;
+ });
+ }
+
+ post(url, params = {}) {
+ return this.performRequest(url, "POST", params);
+ }
+
+ patch(url, params = {}) {
+ return this.performRequest(url, "PATCH", params);
+ }
+
+ del(url, params = {}) {
+ return this.performRequest(url, "DELETE", params);
+ }
+
+ getRecipeList(page, per, sortColumn, sortDirection, name, tags, dataHandler) {
+ const params = {
+ criteria: {
+ page: page || null,
+ per: per || null,
+ sort_column: sortColumn || null,
+ sort_direction: sortDirection || null,
+ name: name || null,
+ tags: tags || null
+ }
+ };
+
+ return this.cacheFirstGet("/recipes", params, dataHandler);
+ }
+
+ getRecipe(id, scale = null, system = null, unit = null, dataHandler) {
+ const params = {
+ scale,
+ system,
+ unit
+ };
+
+ return this.cacheFirstGet("/recipes/" + id, params, dataHandler);
+ }
+
+ buildRecipeParams(recipe) {
+ const params = {
+ recipe: {
+ name: recipe.name,
+ description: recipe.description,
+ source: recipe.source,
+ yields: recipe.yields,
+ total_time: recipe.total_time,
+ active_time: recipe.active_time,
+ step_text: recipe.step_text,
+ tag_names: recipe.tag_names,
+ recipe_ingredients_attributes: recipe.ingredients.map(i => {
+ if (i._destroy) {
+ return {
+ id: i.id,
+ _destroy: true
+ };
+ } else {
+ return {
+ id: i.id,
+ name: i.name,
+ ingredient_id: i.ingredient_id,
+ quantity: i.quantity,
+ units: i.units,
+ preparation: i.preparation,
+ sort_order: i.sort_order
+ };
+ }
+ })
+ }
+ };
+ return params;
+ }
+
+ patchRecipe(recipe) {
+ return this.patch("/recipes/" + recipe.id, this.buildRecipeParams(recipe));
+ }
+
+ postRecipe(recipe) {
+ return this.post("/recipes/", this.buildRecipeParams(recipe));
+ }
+
+ deleteRecipe(id) {
+ return this.del("/recipes/" + id);
+ }
+
+ postPreviewSteps(step_text) {
+ const params = {
+ step_text: step_text
+ };
+
+ return this.post("/recipes/preview_steps", params);
+ }
+
+ getSearchIngredients(query) {
+ const params = { query: query };
+ return this.get("/ingredients/search", params);
+ }
+
+ getCalculate(input, output_unit, density) {
+ const params = {
+ input,
+ output_unit,
+ density
+ };
+ return this.get("/calculator/calculate", params);
+ }
+
+ getIngredientList(page, per, name) {
+ const params = {
+ page,
+ per,
+ name
+ };
+
+ return this.get("/ingredients/", params);
+ }
+
+ getIngredient(id) {
+ return this.get("/ingredients/" + id);
+ }
+
+ buildIngredientParams(ingredient) {
+ return {
+ ingredient: {
+ name: ingredient.name,
+ notes: ingredient.notes,
+ ndbn: ingredient.ndbn,
+ density: ingredient.density,
+
+ water: ingredient.water,
+ ash: ingredient.ash,
+ protein: ingredient.protein,
+ kcal: ingredient.kcal,
+ fiber: ingredient.fiber,
+ sugar: ingredient.sugar,
+ carbohydrates: ingredient.carbohydrates,
+ calcium: ingredient.calcium,
+ iron: ingredient.iron,
+ magnesium: ingredient.magnesium,
+ phosphorus: ingredient.phosphorus,
+ potassium: ingredient.potassium,
+ sodium: ingredient.sodium,
+ zinc: ingredient.zinc,
+ copper: ingredient.copper,
+ manganese: ingredient.manganese,
+ vit_c: ingredient.vit_c,
+ vit_b6: ingredient.vit_b6,
+ vit_b12: ingredient.vit_b12,
+ vit_a: ingredient.vit_a,
+ vit_e: ingredient.vit_e,
+ vit_d: ingredient.vit_d,
+ vit_k: ingredient.vit_k,
+ cholesterol: ingredient.cholesterol,
+ lipids: ingredient.lipids,
+
+
+ ingredient_units_attributes: ingredient.ingredient_units.map(iu => {
+ if (iu._destroy) {
+ return {
+ id: iu.id,
+ _destroy: true
+ };
+ } else {
+ return {
+ id: iu.id,
+ name: iu.name,
+ gram_weight: iu.gram_weight
+ };
+ }
+ })
+ }
+ }
+ }
+
+ postIngredient(ingredient) {
+ return this.post("/ingredients/", this.buildIngredientParams(ingredient));
+ }
+
+ patchIngredient(ingredient) {
+ return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient));
+ }
+
+ deleteIngredient(id) {
+ return this.del("/ingredients/" + id);
+ }
+
+ postIngredientSelectNdbn(ingredient) {
+ const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn";
+ return this.post(url, this.buildIngredientParams(ingredient));
+ }
+
+ getUsdaFoodSearch(query) {
+ return this.get("/ingredients/usda_food_search", {query: query});
+ }
+
+ getNoteList() {
+ return this.get("/notes/");
+ }
+
+ postNote(note) {
+ const params = {
+ content: note.content
+ };
+
+ return this.post("/notes/", params);
+ }
+
+ deleteNote(note) {
+ return this.del("/notes/" + note.id);
+ }
+
+ getLogList(page, per) {
+ const params = {
+ page,
+ per
+ };
+
+ return this.get("/logs", params);
+ }
+
+ getLog(id) {
+ return this.get("/logs/" + id);
+ }
+
+ buildLogParams(log) {
+ const recParams = this.buildRecipeParams(log.recipe);
+
+ return {
+ log: {
+ date: log.date,
+ rating: log.rating,
+ notes: log.notes,
+ source_recipe_id: log.source_recipe_id,
+ recipe_attributes: recParams.recipe
+ }
+ };
+ }
+
+ postLog(log) {
+ const params = this.buildLogParams(log);
+ const rec = params.log.recipe_attributes;
+ if (rec && rec.recipe_ingredients_attributes) {
+ rec.recipe_ingredients_attributes.forEach(ri => ri.id = null);
+ }
+ return this.post("/recipes/" + log.original_recipe_id + "/logs/", params);
+ }
+
+ patchLog(log) {
+ return this.patch("/logs/" + log.id, this.buildLogParams(log));
+ }
+
+ getAdminUserList() {
+ return this.get("/admin/users");
+ }
+
+ postUser(userObj) {
+ const params = {
+ user: {
+ username: userObj.username,
+ full_name: userObj.full_name,
+ email: userObj.email,
+ password: userObj.password,
+ password_confirmation: userObj.password_confirmation
+ }
+ };
+
+ return this.post("/user/", params);
+ }
+
+ patchUser(userObj) {
+ const params = {
+ user: {
+ username: userObj.username,
+ full_name: userObj.full_name,
+ email: userObj.email,
+ password: userObj.password,
+ password_confirmation: userObj.password_confirmation
+ }
+ };
+
+ return this.patch("/user/", params);
+ }
+
+ postLogin(username, password) {
+ const params = {
+ username: username,
+ password: password
+ };
+
+ return this.post("/login", params);
+ }
+
+ getLogout() {
+ return this.get("/logout");
+ }
+
+ getCurrentUser() {
+ return this.get("/user")
+ }
+}
+
+const api = new Api();
+
+export default api;
diff --git a/app/javascript/lib/DateTimeUtils.js b/app/javascript/lib/DateTimeUtils.js
new file mode 100644
index 0000000..f73c20d
--- /dev/null
+++ b/app/javascript/lib/DateTimeUtils.js
@@ -0,0 +1,73 @@
+
+function zeroPad(val, length) {
+ return val.toString().padStart(length, "0");
+}
+
+// Ensure the given date is a Date object.
+function toDate(date) {
+ if (date instanceof Date) {
+ return date;
+ } else if (date === null || date.length === 0) {
+ return null;
+ } else {
+ return new Date(date);
+ }
+}
+
+function formatDate(dateObj) {
+ if (dateObj) {
+ return [dateObj.getMonth() + 1, dateObj.getDate(), dateObj.getFullYear() % 100].join("/");
+ } else {
+ return "";
+ }
+}
+
+function formatDateForEdit(dateObj) {
+ if (dateObj) {
+ return [dateObj.getFullYear(), zeroPad(dateObj.getMonth() + 1, 2), zeroPad(dateObj.getDate(), 2)].join("-");
+ } else {
+ return "";
+ }
+}
+
+function formatTimestamp(dateObj) {
+ if (dateObj) {
+ return formatDateForEdit(dateObj) + " " + formatTime(dateObj, false);
+ } else {
+ return "";
+ }
+}
+
+function formatTime(dateObj, use12hour) {
+ if (dateObj) {
+ let h = dateObj.getHours();
+ const m = zeroPad(dateObj.getMinutes(), 2);
+ let meridiem = "";
+
+ if (use12hour) {
+ meridiem = " am";
+
+ if (h === 0) {
+ h = 12;
+ } else if (h > 12) {
+ h = h - 12;
+ meridiem = " pm";
+ }
+ } else {
+ h = h.toString().padStart(2, "0");
+ }
+
+ return h + ":" + m + meridiem;
+
+ } else {
+ return "";
+ }
+}
+
+export default {
+ toDate,
+ formatDate,
+ formatDateForEdit,
+ formatTimestamp,
+ formatTime
+}
\ No newline at end of file
diff --git a/app/javascript/lib/Errors.js b/app/javascript/lib/Errors.js
new file mode 100644
index 0000000..057424f
--- /dev/null
+++ b/app/javascript/lib/Errors.js
@@ -0,0 +1,79 @@
+
+export function ApiError(message, response) {
+ this.message = (message || "Unknown API Error Occurred");
+ this.response = response;
+}
+ApiError.prototype = Object.assign(new Error(), {
+ name: "ApiError",
+
+ responseCode: function() {
+ if (this.response) {
+ return this.response.status;
+ } else {
+ return null;
+ }
+ }
+});
+
+export function ApiServerError(message, response) {
+ this.message = (message || "Unknown API Server Error Occurred");
+ this.response = response;
+}
+ApiServerError.prototype = Object.assign(new ApiError(), {
+ name: "ApiServerError"
+});
+
+
+export function ApiNotFoundError(message, response) {
+ this.message = (message || "Unknown API Server Error Occurred");
+ this.response = response;
+}
+ApiNotFoundError.prototype = Object.assign(new ApiError(), {
+ name: "ApiNotFoundError"
+});
+
+
+export function ApiValidationError(message, response, json) {
+ this.message = (message || "Server returned a validation error");
+ this.response = response;
+ this.json = json;
+}
+ApiValidationError.prototype = Object.assign(new ApiError(), {
+ name: "ApiValidationError",
+
+ validationErrors: function() {
+ const errors = {};
+ if (this.json) {
+ for (let key in this.json) {
+ errors[key] = this.json[key];
+ }
+ } else {
+ errors["base"] = ["unknown error"];
+ }
+ return errors;
+ },
+
+ formattedValidationErrors: function() {
+ const errors = [];
+ if (this.json) {
+ for (let key in this.json) {
+ errors.push(key + ": " + this.json[key].join(", "));
+ }
+ return errors;
+ } else {
+ return ["unable to determine validation errors"];
+ }
+ }
+});
+
+
+export function onlyFor(errorType, handler) {
+ return (err) => {
+ if (err instanceof errorType) {
+ return handler(err);
+ } else {
+ return Promise.reject(err);
+ }
+ };
+}
+
diff --git a/app/javascript/lib/GlobalMixins.js b/app/javascript/lib/GlobalMixins.js
new file mode 100644
index 0000000..61fbc83
--- /dev/null
+++ b/app/javascript/lib/GlobalMixins.js
@@ -0,0 +1,69 @@
+
+import Vue from 'vue';
+import { mapGetters, mapMutations, mapState } from 'vuex';
+import api from "../lib/Api";
+
+Vue.mixin({
+ computed: {
+ ...mapGetters([
+ "isLoading",
+ "isLoggedIn",
+ "isAdmin"
+ ]),
+ ...mapState([
+ "user"
+ ])
+ },
+ methods: {
+ ...mapMutations([
+ 'setError',
+ 'setLoading',
+ 'setUser'
+ ]),
+
+ loadResource(promise) {
+ this.setLoading(true);
+
+ return promise
+ .catch(err => this.setError(err))
+ .then(() => this.setLoading(false));
+ },
+
+ checkAuthentication() {
+ return this.loadResource(api.getCurrentUser().then(user => this.setUser(user)));
+ }
+ }
+});
+
+function clickStrikeClick(evt) {
+ const isStrikable = el => el && el.tagName === "LI";
+ const strikeClass = "is-strikethrough";
+
+ let t = evt.target;
+
+ while (t !== null && t !== this && !isStrikable(t)) {
+ t = t.parentElement;
+ }
+
+ if (isStrikable(t)) {
+ const classList = t.className.split(" ");
+ const strIdx = classList.findIndex(c => c === strikeClass);
+ if (strIdx >= 0) {
+ classList.splice(strIdx, 1);
+ } else {
+ classList.push(strikeClass);
+ }
+
+ t.className = classList.join(" ");
+ }
+}
+
+Vue.directive('click-strike', {
+ bind(el) {
+ el.addEventListener("click", clickStrikeClick);
+ },
+
+ unbind(el) {
+ el.removeEventListener("click", clickStrikeClick);
+ }
+});
\ No newline at end of file
diff --git a/app/javascript/lib/ServiceWorker.js b/app/javascript/lib/ServiceWorker.js
new file mode 100644
index 0000000..b17b44f
--- /dev/null
+++ b/app/javascript/lib/ServiceWorker.js
@@ -0,0 +1,49 @@
+
+function trackInstall(worker, cb) {
+ worker.addEventListener('statechange', function() {
+ //If the worker is now installed, let the user know that there is an update ready
+ if (worker.state == 'installed') {
+ cb();
+ }
+ });
+}
+
+export function swUpdate() {
+ navigator.serviceWorker.getRegistration().then(reg => {
+ if (reg && reg.waiting) {
+ reg.waiting.postMessage("skipWaiting");
+ window.location.reload(true);
+ }
+ });
+}
+
+export function swInit(store) {
+
+ const updateReady = () => store.commit("setUpdateAvailable", true);
+
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register('/sw.js')
+ .then(function (reg) {
+ console.log('Registration succeeded. Scope is ' + reg.scope);
+
+ if (reg.waiting) {
+ updateReady();
+ }
+
+ // If there's an updated worker installing, track its progress. If it becomes "installed", call
+ // indexController._updateReady()
+ if (reg.installing) {
+ trackInstall(reg.installing, updateReady);
+ }
+
+ reg.addEventListener('updatefound', function () {
+ trackInstall(reg.installing, updateReady);
+ });
+
+ }).catch(function (error) {
+ console.log('Registration failed with ' + error);
+ });
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
new file mode 100644
index 0000000..108d278
--- /dev/null
+++ b/app/javascript/packs/application.js
@@ -0,0 +1,69 @@
+import '../styles';
+
+import Vue from 'vue'
+import { sync } from 'vuex-router-sync';
+import { swInit } from "../lib/ServiceWorker";
+import VueProgressBar from "vue-progressbar";
+import config from '../config';
+import store from '../store';
+import router from '../router';
+import '../lib/GlobalMixins';
+import App from '../components/App';
+
+import AppAutocomplete from "../components/AppAutocomplete";
+import AppConfirm from "../components/AppConfirm";
+import AppDateTime from "../components/AppDateTime";
+import AppDatePicker from "../components/AppDatePicker";
+import AppIcon from "../components/AppIcon";
+import AppModal from "../components/AppModal";
+import AppNavbar from "../components/AppNavbar";
+import AppPager from "../components/AppPager";
+import AppRating from "../components/AppRating";
+import AppTagEditor from "../components/AppTagEditor";
+import AppTextField from "../components/AppTextField";
+import AppValidationErrors from "../components/AppValidationErrors";
+
+Vue.component("AppAutocomplete", AppAutocomplete);
+Vue.component("AppConfirm", AppConfirm);
+Vue.component("AppDateTime", AppDateTime);
+Vue.component("AppDatePicker", AppDatePicker);
+Vue.component("AppIcon", AppIcon);
+Vue.component("AppModal", AppModal);
+Vue.component("AppNavbar", AppNavbar);
+Vue.component("AppPager", AppPager);
+Vue.component("AppRating", AppRating);
+Vue.component("AppTagEditor", AppTagEditor);
+Vue.component("AppTextField", AppTextField);
+Vue.component("AppValidationErrors", AppValidationErrors);
+
+
+Vue.use(VueProgressBar, {
+// color: '#bffaf3',
+// failedColor: '#874b4b',
+// thickness: '5px',
+// transition: {
+// speed: '0.2s',
+// opacity: '0.6s',
+// termination: 300
+// },
+// autoRevert: true,
+// location: 'left',
+// inverse: false
+});
+
+sync(store, router);
+swInit(store);
+
+document.addEventListener('DOMContentLoaded', () => {
+
+ const app = document.getElementById('app');
+ config.baseApiUrl = app.dataset.url;
+
+ window.$vm = new Vue({
+ el: '#app',
+ store,
+ router,
+ render: createElement => createElement('App'),
+ components: { App }
+ });
+});
diff --git a/app/javascript/router.js b/app/javascript/router.js
new file mode 100644
index 0000000..20ad8ef
--- /dev/null
+++ b/app/javascript/router.js
@@ -0,0 +1,156 @@
+import Vue from 'vue';
+import Router from 'vue-router';
+
+import The404Page from './components/The404Page';
+import TheAboutPage from './components/TheAboutPage';
+import TheCalculator from './components/TheCalculator';
+
+import TheLog from './components/TheLog';
+import TheLogList from './components/TheLogList';
+import TheLogCreator from './components/TheLogCreator';
+import TheLogEditor from './components/TheLogEditor';
+
+import TheIngredientList from './components/TheIngredientList';
+import TheIngredient from "./components/TheIngredient";
+import TheIngredientEditor from "./components/TheIngredientEditor";
+import TheIngredientCreator from "./components/TheIngredientCreator";
+import TheNotesList from './components/TheNotesList';
+import TheRecipe from './components/TheRecipe';
+import TheRecipeEditor from './components/TheRecipeEditor';
+import TheRecipeCreator from './components/TheRecipeCreator';
+import TheRecipeList from './components/TheRecipeList';
+
+import TheUserCreator from './components/TheUserCreator';
+import TheUserEditor from './components/TheUserEditor';
+
+import TheAdminUserList from './components/TheAdminUserList';
+import TheAdminUserEditor from './components/TheAdminUserEditor';
+
+Vue.use(Router);
+
+const router = new Router({
+ routes: []
+});
+
+router.addRoutes(
+ [
+ {
+ path: '/',
+ redirect: '/recipes'
+ },
+ {
+ path: '/recipes',
+ name: 'recipeList',
+ component: TheRecipeList
+ },
+ {
+ path: '/recipes/new',
+ name: 'new_recipe',
+ component: TheRecipeCreator
+ },
+ {
+ path: '/recipes/:id/edit',
+ name: 'edit_recipe',
+ component: TheRecipeEditor
+ },
+ {
+ path: '/recipe/:id',
+ name: 'recipe',
+ component: TheRecipe
+ },
+ {
+ path: "/about",
+ name: "about",
+ component: TheAboutPage
+ },
+ {
+ path: "/calculator",
+ name: "calculator",
+ component: TheCalculator
+ },
+ {
+ path: "/ingredients",
+ name: "ingredients",
+ component: TheIngredientList
+ },
+ {
+ path: "/ingredients/new",
+ name: "new_ingredient",
+ component: TheIngredientCreator
+ },
+ {
+ path: "/ingredients/:id/edit",
+ name: "edit_ingredient",
+ component: TheIngredientEditor
+ },
+ {
+ path: "/ingredients/:id",
+ name: "ingredient",
+ component: TheIngredient
+ },
+ {
+ path: "/logs",
+ name: "logs",
+ component: TheLogList
+ },
+ {
+ path: "/recipes/:recipeId/logs/new",
+ name: "new_log",
+ component: TheLogCreator
+ },
+ {
+ path: "/logs/:id/edit",
+ name: "edit_log",
+ component: TheLogEditor
+ },
+ {
+ path: "/logs/:id",
+ name: "log",
+ component: TheLog
+ },
+ {
+ path: "/notes",
+ name: "notes",
+ component: TheNotesList
+ },
+ {
+ path: "/logout",
+ name: "logout",
+ beforeEnter: (to, from, next) => {
+ const $store = router.app.$store;
+ $store.dispatch("logout")
+ .then(() => next("/"));
+ }
+ },
+
+ {
+ path: "/user/new",
+ name: "new_user",
+ component: TheUserCreator
+ },
+ {
+ path: "/user/edit",
+ name: "edit_user",
+ component: TheUserEditor
+ },
+
+ {
+ path: "/admin/users",
+ name: "admin_users",
+ component: TheAdminUserList
+ },
+
+ {
+ path: "/admin/users/:id/edit",
+ name: "admin_edit_user",
+ component: TheAdminUserEditor
+ },
+
+ {
+ path: '*',
+ component: The404Page
+ }
+ ]
+);
+
+export default router;
diff --git a/app/javascript/store/index.js b/app/javascript/store/index.js
new file mode 100644
index 0000000..9f26c2e
--- /dev/null
+++ b/app/javascript/store/index.js
@@ -0,0 +1,76 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import api from '../lib/Api';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ strict: process.env.NODE_ENV !== 'production',
+ state: {
+ updateAvailable: false,
+ loadingCount: 0,
+ loading: false,
+ error: null,
+ authChecked: false,
+ user: null,
+
+ // MediaQueryList objects in the root App component maintain this state.
+ mediaQueries: {
+ mobile: false,
+ tablet: false,
+ tabletOnly: false,
+ touch: false,
+ desktop: false,
+ desktopOnly: false,
+ widescreen: false,
+ widescreenOnly: false,
+ fullhd: false
+ }
+ },
+ getters: {
+ isLoading(state) {
+ return state.loading === true;
+ },
+ isLoggedIn(state) {
+ return state.user !== null;
+ },
+ isAdmin(state) {
+ return state.user !== null && state.user.admin === true;
+ }
+ },
+ mutations: {
+ setUpdateAvailable(state, value) {
+ state.updateAvailable = value;
+ },
+
+ setLoading(state, value) {
+ if (value) {
+ state.loadingCount = state.loadingCount + 1;
+ } else {
+ state.loadingCount = state.loadingCount - 1;
+ }
+ state.loading = state.loadingCount !== 0;
+ },
+
+ setError(state, value) {
+ console.log(value);
+ state.error = value;
+ },
+
+ setUser(state, user) {
+ state.authChecked = true;
+ state.user = user;
+ },
+
+ setMediaQuery(state, data) {
+ state.mediaQueries[data.mediaName] = data.value;
+ }
+ },
+ actions: {
+ logout({commit}) {
+ return api.getLogout()
+ .then(() => commit("setUser", null));
+ }
+ }
+});
\ No newline at end of file
diff --git a/app/javascript/styles/_responsive_controls.scss b/app/javascript/styles/_responsive_controls.scss
new file mode 100644
index 0000000..565dced
--- /dev/null
+++ b/app/javascript/styles/_responsive_controls.scss
@@ -0,0 +1,107 @@
+@mixin responsive-button-size($size) {
+ &.is-small-#{$size} {
+ @include button-small;
+ }
+
+ &.is-medium-#{$size} {
+ @include button-medium;
+ }
+
+ &.is-large-#{$size} {
+ @include button-large;
+ }
+}
+
+@mixin responsive-label-size($size) {
+ &.is-small-#{$size} {
+ font-size: $size-small;
+ }
+
+ &.is-medium-#{$size} {
+ font-size: $size-medium;
+ }
+
+ &.is-large-#{$size} {
+ font-size: $size-large;
+ }
+}
+
+@mixin responsive-control-size($size) {
+ &.is-small-#{$size} {
+ @include control-small;
+ }
+
+ &.is-medium-#{$size} {
+ @include control-medium;
+ }
+
+ &.is-large-#{$size} {
+ @include control-large;
+ }
+}
+
+.button {
+ @include mobile {
+ @include responsive-button-size("mobile");
+ }
+
+ @include tablet {
+ @include responsive-button-size("tablet");
+ }
+
+ @include desktop {
+ @include responsive-button-size("desktop");
+ }
+
+ @include widescreen {
+ @include responsive-button-size("widescreen");
+ }
+
+ @include fullhd {
+ @include responsive-button-size("fullhd");
+ }
+}
+
+.label {
+ @include mobile {
+ @include responsive-label-size("mobile");
+ }
+
+ @include tablet {
+ @include responsive-label-size("tablet");
+ }
+
+ @include desktop {
+ @include responsive-label-size("desktop");
+ }
+
+ @include widescreen {
+ @include responsive-label-size("widescreen");
+ }
+
+ @include fullhd {
+ @include responsive-label-size("fullhd");
+ }
+}
+
+.input, .textarea {
+ @include mobile {
+ @include responsive-control-size("mobile");
+ }
+
+ @include tablet {
+ @include responsive-control-size("tablet");
+ }
+
+ @include desktop {
+ @include responsive-control-size("desktop");
+ }
+
+ @include widescreen {
+ @include responsive-control-size("widescreen");
+ }
+
+ @include fullhd {
+ @include responsive-control-size("fullhd");
+ }
+}
\ No newline at end of file
diff --git a/app/javascript/styles/_variables.scss b/app/javascript/styles/_variables.scss
new file mode 100644
index 0000000..bb4405d
--- /dev/null
+++ b/app/javascript/styles/_variables.scss
@@ -0,0 +1,33 @@
+
+
+// coolors.co pallet
+$coolors-dark: rgba(29, 30, 24, 1);
+$coolors-blue: rgba(67, 127, 151, 1);
+$coolors-green: rgba(121, 167, 54, 1);
+$coolors-red: #ab4c34;
+$coolors-yellow: rgba(240, 162, 2, 1);
+
+//$family-sans-serif: "Verdana", "Helvetica Neue", "Helvetica", "Arial", sans-serif !default
+$family-serif: Georgia, "Times New Roman", Times, serif;
+
+// Bluma default overrides
+//$green: #79A736;
+//$red: #d4424e;
+//$blue: #9c36a7;
+
+$green: $coolors-green;
+$blue: $coolors-blue;
+$red: $coolors-red;
+$yellow: $coolors-yellow;
+$dark: $coolors-dark;
+
+$primary: $green;
+$family-primary: $family-serif;
+
+$modal-content-width: 750px;
+
+
+// Make all Bulma variables and functions available
+@import "~bulma/sass/utilities/initial-variables";
+@import "~bulma/sass/utilities/functions";
+
diff --git a/app/javascript/styles/_wide_modal.scss b/app/javascript/styles/_wide_modal.scss
new file mode 100644
index 0000000..c19d548
--- /dev/null
+++ b/app/javascript/styles/_wide_modal.scss
@@ -0,0 +1,18 @@
+
+@include until($desktop) {
+ .modal.is-wide {
+ .modal-content, .modal-card {
+ margin: 0 20px;
+ width: 100%;
+ }
+ }
+}
+
+@include from($desktop) {
+ .modal.is-wide {
+ .modal-content, .modal-card {
+ margin: 0 auto;
+ width: 1000px;
+ }
+ }
+}
diff --git a/app/javascript/styles/index.scss b/app/javascript/styles/index.scss
new file mode 100644
index 0000000..90f7559
--- /dev/null
+++ b/app/javascript/styles/index.scss
@@ -0,0 +1,49 @@
+@import "./variables";
+
+@import "~bulma/sass/utilities/_all";
+@import "~bulma/sass/base/_all";
+@import "~bulma/sass/components/navbar";
+@import "~bulma/sass/components/level";
+@import "~bulma/sass/components/message";
+@import "~bulma/sass/components/modal";
+@import "~bulma/sass/components/pagination";
+@import "~bulma/sass/elements/_all";
+@import "~bulma/sass/grid/columns";
+@import "~bulma/sass/layout/section";
+
+@import "./responsive_controls";
+@import "./wide_modal";
+
+html {
+ height: 100%;
+}
+
+body {
+ min-height: 100%;
+}
+
+body {
+ background-color: $grey-dark;
+ padding-bottom: 2rem;
+}
+
+#app {
+ padding-top: 1rem;
+
+ .container {
+ padding: 1rem;
+ background-color: $white;
+ }
+}
+
+.title, .subtitle, .navbar, .button, .pagination, .modal-card-title, th {
+ font-family: $family-sans-serif;
+}
+
+.pagination:not(:last-child) {
+ margin-bottom: 1em;
+}
+
+.is-strikethrough {
+ text-decoration: line-through;
+}
\ No newline at end of file
diff --git a/app/models/log.rb b/app/models/log.rb
index 21adfdf..cc086ad 100644
--- a/app/models/log.rb
+++ b/app/models/log.rb
@@ -1,6 +1,6 @@
class Log < ApplicationRecord
- belongs_to :recipe
+ belongs_to :recipe, dependent: :destroy
belongs_to :source_recipe, class_name: 'Recipe'
belongs_to :user
diff --git a/app/models/recipe.rb b/app/models/recipe.rb
index e54f21d..2c83260 100644
--- a/app/models/recipe.rb
+++ b/app/models/recipe.rb
@@ -16,31 +16,38 @@ class Recipe < ApplicationRecord
validates :total_time, numericality: true, allow_blank: true
validates :active_time, numericality: true, allow_blank: true
+ attr_accessor :converted_scale, :converted_system, :converted_unit
+
def scale(factor, auto_unit = false)
+ self.converted_scale = factor
recipe_ingredients.each do |ri|
ri.scale(factor, auto_unit)
end
end
def convert_to_metric
+ self.converted_system = 'metric'
recipe_ingredients.each do |ri|
ri.to_metric
end
end
def convert_to_standard
+ self.converted_system = 'standard'
recipe_ingredients.each do |ri|
ri.to_standard
end
end
def convert_to_mass
+ self.converted_unit = 'mass'
recipe_ingredients.each do |ri|
ri.to_mass
end
end
def convert_to_volume
+ self.converted_unit = 'volume'
recipe_ingredients.each do |ri|
ri.to_volume
end
@@ -103,7 +110,7 @@ class Recipe < ApplicationRecord
query = active.order(criteria.sort_column => criteria.sort_direction).page(criteria.page).per(criteria.per)
if criteria.name.present?
- query = query.matches_token(:name, criteria.name)
+ query = query.matches_tokens(:name, criteria.name.split(' '))
end
if criteria.tags.present?
diff --git a/app/models/recipe_ingredient.rb b/app/models/recipe_ingredient.rb
index 939867d..d5bb4dc 100644
--- a/app/models/recipe_ingredient.rb
+++ b/app/models/recipe_ingredient.rb
@@ -1,7 +1,7 @@
class RecipeIngredient < ApplicationRecord
- belongs_to :ingredient
- belongs_to :recipe, inverse_of: :recipe_ingredients
+ belongs_to :ingredient, optional: true
+ belongs_to :recipe, inverse_of: :recipe_ingredients, touch: true
validates :sort_order, presence: true
diff --git a/app/models/view_models/recipe_criteria.rb b/app/models/view_models/recipe_criteria.rb
index a33a5e4..e1fa8f6 100644
--- a/app/models/view_models/recipe_criteria.rb
+++ b/app/models/view_models/recipe_criteria.rb
@@ -10,7 +10,7 @@ module ViewModels
params ||= {}
PARAMS.each do |attr|
setter = "#{attr}="
- if params[attr]
+ if params[attr].present?
self.send(setter, params[attr])
end
end
diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb
deleted file mode 100644
index e69de29..0000000
diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb
deleted file mode 100644
index 3dbeae3..0000000
--- a/app/views/admin/users/index.html.erb
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
- <% if @users.empty? %>
-
No Users
- <% else %>
-
-
-
-
- Name
- Username
- Email
- Admin
-
-
-
-
-
- <% @users.each do |user| %>
-
- <%= link_to user.full_name, edit_admin_user_path(user) %>
- <%= user.username %>
- <%= user.email %>
- <%= user.admin? %>
-
- <%= link_to [:admin, user], method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-sm btn-danger' do %>
-
- <% end %>
-
-
- <% end %>
-
-
- <% end %>
-
-
-
-
diff --git a/app/views/admin/users/index.json.jbuilder b/app/views/admin/users/index.json.jbuilder
new file mode 100644
index 0000000..5191d0b
--- /dev/null
+++ b/app/views/admin/users/index.json.jbuilder
@@ -0,0 +1,5 @@
+
+json.array! @users do |u|
+ json.extract! u, :id, :username, :full_name, :email, :admin
+ json.name u.display_name
+end
\ No newline at end of file
diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb
deleted file mode 100644
index e69de29..0000000
diff --git a/app/views/calculator/index.html.erb b/app/views/calculator/index.html.erb
deleted file mode 100644
index f057314..0000000
--- a/app/views/calculator/index.html.erb
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
- <%= label_tag :input, "Input", class: 'control-label' %>
- <%= text_field_tag :input, nil, class: 'form-control', autofocus: true %>
-
-
- <%= label_tag :output_unit, "Output Unit", class: 'control-label' %>
- <%= text_field_tag :output_unit, nil, class: 'form-control' %>
-
-
-
- <%= label_tag :ingredient, "Ingredient", class: 'control-label' %>
- <%= text_field_tag :ingredient, nil, class: 'form-control' %>
-
-
-
- <%= label_tag :density, "Density", class: 'control-label' %>
- <%= text_field_tag :density, nil, class: 'form-control' %>
-
-
-
- <%= label_tag :output, "Output", class: 'control-label' %>
- <%= text_field_tag :output, nil, class: 'form-control', readonly: true %>
-
-
-
-
-
-
-
-
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
new file mode 100644
index 0000000..bc5bdea
--- /dev/null
+++ b/app/views/home/index.html.erb
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ Parsley
+
+ <%= stylesheet_pack_tag 'application' %>
+
+
+
+
+
+<%= javascript_pack_tag 'application' %>
+
+
+
diff --git a/app/views/home/sw.js.erb b/app/views/home/sw.js.erb
new file mode 100644
index 0000000..feee6d9
--- /dev/null
+++ b/app/views/home/sw.js.erb
@@ -0,0 +1,86 @@
+
+<%
+ pack_assets = [asset_pack_path("application.js"), asset_pack_path("application.css")].select { |a| a.present? }
+%>
+
+var cacheName = "parsley-cache-<%= File.mtime(Webpacker::manifest.config.public_manifest_path).to_i %>";
+
+var staticAssets = [
+ "/"
+ <% pack_assets.each do |a| %>
+ ,"<%= a %>"
+ <% end %>
+];
+
+self.addEventListener('install', function(event) {
+ event.waitUntil(
+ caches.open(cacheName).then(function (cache) {
+ console.log('[ServiceWorker] Caching app shell');
+ return cache.addAll(staticAssets);
+ })
+ );
+});
+
+self.addEventListener('activate', function(event) {
+ event.waitUntil(
+ caches.keys().then(function (keyList) {
+ return Promise.all(keyList.map(function (key) {
+ if (key !== cacheName) {
+ console.log('[ServiceWorker] Removing old cache', key);
+ return caches.delete(key);
+ }
+ }));
+ })
+ );
+});
+
+self.addEventListener('fetch', function(event) {
+ var reqUrl = new URL(event.request.url);
+ var isCacheThenNetwork = event.request.headers.get("Cache-Then-Network") === "true";
+ var x, asset;
+
+ // Cache-first response for static assets
+ for (x = 0; x < staticAssets.length; x++) {
+ asset = staticAssets[x];
+ if (asset === reqUrl.pathname) {
+ event.respondWith(
+ caches.match(event.request).then(function (response) {
+ return response || fetch(event.request);
+ })
+ );
+ return;
+ }
+ }
+
+ // If the 'CacheThenNetwork' header is set, hit the network, cache the page, and return the response
+ if (isCacheThenNetwork) {
+ event.respondWith(caches.open(cacheName).then(function (cache) {
+ return fetch(event.request)
+ .then(function (response) {
+ cache.put(event.request, response.clone());
+ return response;
+ });
+ }));
+
+ return;
+ }
+
+ // Network, falling back to cache by default to support offline browsing of any cached resources
+ event.respondWith(caches.open(cacheName).then(function(cache) {
+ return fetch(event.request)
+ .then(function(response) {
+ cache.put(event.request, response.clone());
+ return response;
+ })
+ .catch(function() {
+ return caches.match(event.request);
+ });
+ }));
+
+});
+
+self.addEventListener('message', function (event) {
+ if (event.data === "skipWaiting") {
+ self.skipWaiting();
+ }
+});
\ No newline at end of file
diff --git a/app/views/ingredients/_form.html.erb b/app/views/ingredients/_form.html.erb
deleted file mode 100644
index af4f632..0000000
--- a/app/views/ingredients/_form.html.erb
+++ /dev/null
@@ -1,151 +0,0 @@
-
-<% has_ndbn = @ingredient.ndbn.present? %>
-
-<%= form_for(@ingredient, html: {id: 'ingredient_form'}) do |f| %>
-
- <%= render partial: 'shared/error_list', locals: {model: @ingredient} %>
-
- <%= f.hidden_field :ndbn, class: 'ndbn' %>
- <%= f.hidden_field :id, class: 'id', disabled: true %>
-
-
- <%= f.label :name, class: 'control-label' %>
- <%= f.text_field :name, class: 'form-control name', autofocus: true %>
-
-
-
-
-
-
-
-
- <%= f.label :density, class: 'control-label' %>
- <%= f.text_field :density, class: 'form-control', disabled: has_ndbn %>
-
-
-
-
-
Ingredient Units
-
-
-
- <%= f.fields_for :ingredient_units do |iu_form| %>
- <%= render partial: 'ingredients/ingredient_unit_fields', locals: {f: iu_form } %>
- <% end %>
-
- <%= link_to_add_association 'add unit', f, :ingredient_units, class: 'btn btn-primary' %>
-
-
-
-
- <% if has_ndbn %>
-
-
-
-
NDBN Unit Weights
-
-
-
-
-
- Name
- Grams
-
-
- <% @ingredient.usda_food.usda_food_weights.each do |w| %>
-
- <%= "#{w.amount} #{w.description}" %>
- <%= w.gram_weight %>
-
- <% end %>
-
-
-
-
- <% end %>
-
-
- <%= f.label :notes, class: 'control-label' %>
- <%= f.text_area :notes, class: 'form-control' %>
-
-
-
-
-
Nutrition Per 100 grams
-
-
-
>
-
-
-
-
- <%= f.label :water, "Grams of Water", class: 'control-label' %>
- <%= f.text_field :water, class: 'form-control' %>
-
-
-
- <%= f.label :protein, "Grams of Protein", class: 'control-label' %>
- <%= f.text_field :protein, class: 'form-control' %>
-
-
-
- <%= f.label :lipids, "Grams of Fat", class: 'control-label' %>
- <%= f.text_field :lipids, class: 'form-control' %>
-
-
-
- <%= f.label :carbohydrates, "Grams of Carbohydrates", class: 'control-label' %>
- <%= f.text_field :carbohydrates, class: 'form-control' %>
-
-
-
- <%= f.label :kcal, "Calories", class: 'control-label' %>
- <%= f.text_field :kcal, class: 'form-control' %>
-
-
-
- <%= f.label :fiber, "Grams of Fiber", class: 'control-label' %>
- <%= f.text_field :fiber, class: 'form-control' %>
-
-
-
- <%= f.label :sugar, "Grams of Sugar", class: 'control-label' %>
- <%= f.text_field :sugar, class: 'form-control' %>
-
-
-
-
- <%= f.label :calcium, "Miligrams of Calcium", class: 'control-label' %>
- <%= f.text_field :calcium, class: 'form-control' %>
-
-
-
- <%= f.label :sodium, "Miligrams of Sodium", class: 'control-label' %>
- <%= f.text_field :sodium, class: 'form-control' %>
-
-
-
- <%= f.label :vit_k, "Micrograms of Vitamin K", class: 'control-label' %>
- <%= f.text_field :vit_k, class: 'form-control' %>
-
-
-
-
-
-
-
-
- <%= f.submit class: 'btn btn-primary' %>
-
-
-<% end %>
diff --git a/app/views/ingredients/_ingredient_unit_fields.html.erb b/app/views/ingredients/_ingredient_unit_fields.html.erb
deleted file mode 100644
index 804bbf8..0000000
--- a/app/views/ingredients/_ingredient_unit_fields.html.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- <%= f.label :name, class: 'control-label' %>
- <%= f.text_field :name, class: 'form-control' %>
-
-
-
- <%= f.label :gram_weight, 'Grams', class: 'control-label' %>
- <%= f.text_field :gram_weight, class: 'form-control' %>
-
-
-
-
-
\ No newline at end of file
diff --git a/app/views/ingredients/edit.html.erb b/app/views/ingredients/edit.html.erb
deleted file mode 100644
index 9e14d8a..0000000
--- a/app/views/ingredients/edit.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
Editing Ingredient
-
- <%= render 'form' %>
-
- <%= link_to 'Back', ingredients_path, class: 'btn btn-default' %>
-
-
-
\ No newline at end of file
diff --git a/app/views/ingredients/index.html.erb b/app/views/ingredients/index.html.erb
deleted file mode 100644
index 1639e4c..0000000
--- a/app/views/ingredients/index.html.erb
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
- <% if @ingredients.empty? %>
-
No Ingredients
- <% else %>
-
- <% if current_user? %>
- <%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-default' %>
- <% end %>
-
-
-
-
-
-
- Name
- USDA
- KCal per 100g
- Density (oz/cup)
- <% if current_user? %>
-
- <% end %>
-
-
-
-
- <% decorate(@ingredients, IngredientDecorator).each do |ingredient| %>
-
- <%= link_to_if current_user?, ingredient.name, edit_ingredient_path(ingredient) %>
- <%= ingredient.ndbn_check %>
- <%= ingredient.kcal %>
- <%= ingredient.density %>
- <% if current_user? %>
-
- <%= link_to ingredient, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-xs btn-danger' do %>
-
- <% end %>
-
- <% end %>
-
- <% end %>
-
-
- <% end %>
-
-
-
- <% if current_user? %>
- <%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-default' %>
- <% end %>
-
-
-
-
diff --git a/app/views/ingredients/index.json.jbuilder b/app/views/ingredients/index.json.jbuilder
new file mode 100644
index 0000000..266b0b5
--- /dev/null
+++ b/app/views/ingredients/index.json.jbuilder
@@ -0,0 +1,20 @@
+
+json.cache_root! [Ingredient.all, @ingredients] do
+
+ json.extract! @ingredients, :total_count, :total_pages, :current_page
+ json.page_size @ingredients.limit_value
+
+ json.ingredients @ingredients do |i|
+ json.extract! i, :id, :name, :ndbn, :kcal
+ json.usda i.ndbn.present?
+
+ if i.density.present?
+ value = UnitConversion::parse(i.density)
+ json.density value.convert('oz/cup').change_formatter(UnitConversion::DecimalFormatter.new).pretty_value
+ else
+ json.density nil
+ end
+
+ end
+
+end
\ No newline at end of file
diff --git a/app/views/ingredients/new.html.erb b/app/views/ingredients/new.html.erb
deleted file mode 100644
index 2262b48..0000000
--- a/app/views/ingredients/new.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
New Ingredient
-
- <%= render 'form' %>
-
- <%= link_to 'Back', ingredients_path, class: 'btn btn-primary' %>
-
-
-
diff --git a/app/views/ingredients/select_ndbn.js.erb b/app/views/ingredients/select_ndbn.js.erb
deleted file mode 100644
index 4eb7e32..0000000
--- a/app/views/ingredients/select_ndbn.js.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
-$("#ingredient_form").replaceWith($("<%= escape_javascript(render(partial: 'ingredients/form')) %>"));
-window.INGREDIENT_API.initialize();
\ No newline at end of file
diff --git a/app/views/ingredients/show.json.jbuilder b/app/views/ingredients/show.json.jbuilder
new file mode 100644
index 0000000..6dcaf6f
--- /dev/null
+++ b/app/views/ingredients/show.json.jbuilder
@@ -0,0 +1,46 @@
+
+json.extract! @ingredient,
+ :id,
+ :name,
+ :ndbn,
+ :usda_food_name,
+ :notes,
+ :density,
+ :water,
+ :ash,
+ :protein,
+ :kcal,
+ :fiber,
+ :sugar,
+ :carbohydrates,
+ :calcium,
+ :iron,
+ :magnesium,
+ :phosphorus,
+ :potassium,
+ :sodium,
+ :zinc,
+ :copper,
+ :manganese,
+ :vit_c,
+ :vit_b6,
+ :vit_b12,
+ :vit_a,
+ :vit_e,
+ :vit_d,
+ :vit_k,
+ :cholesterol,
+ :lipids
+
+if @ingredient.ndbn.present?
+ json.ndbn_units @ingredient.usda_food.usda_food_weights do |fw|
+ json.extract! fw, :amount, :description, :gram_weight
+ end
+else
+ json.ndbn_units []
+end
+
+json.ingredient_units @ingredient.ingredient_units do |iu|
+ json.extract! iu, :id, :name, :gram_weight
+ json._destroy false
+end
\ No newline at end of file
diff --git a/app/views/ingredients/usda_food_search.html.erb b/app/views/ingredients/usda_food_search.html.erb
deleted file mode 100644
index d8db16a..0000000
--- a/app/views/ingredients/usda_food_search.html.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
- per 100 grams
-
-
- NDBN
- Name
- KCal
- Carbs
- Sugar
-
-
-
-<% @foods.each do |f| %>
-
-
- <%= f.ndbn %>
- <%= link_to f.long_description, '#', class: 'food_result', data: {ndbn: f.ndbn} %>
- <%= f.kcal %>
- <%= f.carbohydrates %>
- <%= f.sugar %>
-
-
-<% end %>
-
-
\ No newline at end of file
diff --git a/app/views/ingredients/usda_food_search.json.jbuilder b/app/views/ingredients/usda_food_search.json.jbuilder
index c5e240c..46d9e0f 100644
--- a/app/views/ingredients/usda_food_search.json.jbuilder
+++ b/app/views/ingredients/usda_food_search.json.jbuilder
@@ -1,7 +1,8 @@
json.array! @foods do |f|
- json.extract! f, :ndbn
+ json.extract! f, :ndbn, :kcal, :carbohydrates, :lipid, :protein
json.name f.long_description
+
end
\ No newline at end of file
diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb
deleted file mode 100644
index bf23ff0..0000000
--- a/app/views/kaminari/_first_page.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
-
diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb
deleted file mode 100644
index 6d3a149..0000000
--- a/app/views/kaminari/_gap.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= content_tag :a, raw(t 'views.pagination.truncate') %>
-
diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb
deleted file mode 100644
index fb619ea..0000000
--- a/app/views/kaminari/_last_page.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
-
diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb
deleted file mode 100644
index 15e10e4..0000000
--- a/app/views/kaminari/_next_page.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
-
diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb
deleted file mode 100644
index 8028b45..0000000
--- a/app/views/kaminari/_page.html.erb
+++ /dev/null
@@ -1,9 +0,0 @@
-<% if page.current? %>
-
- <%= content_tag :a, page, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)) %>
-
-<% else %>
-
- <%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)) %>
-
-<% end %>
diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb
deleted file mode 100644
index 2c8757b..0000000
--- a/app/views/kaminari/_paginator.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%= paginator.render do -%>
-
-<% end -%>
diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb
deleted file mode 100644
index d94a50a..0000000
--- a/app/views/kaminari/_prev_page.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote %>
-
diff --git a/app/views/layouts/_flash_messages.html.erb b/app/views/layouts/_flash_messages.html.erb
deleted file mode 100644
index e4e8932..0000000
--- a/app/views/layouts/_flash_messages.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- <% flash.each do |type, values| %>
- <% Array.wrap(values).each do |value| %>
-
<%= value %>
- <% end %>
- <% end %>
-
-
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index e4e688b..7ad8e82 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -9,40 +9,11 @@
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
-
-
<%= csrf_meta_tags %>
-
-
-
-
-
- <% nav_items.each do |li| %>
- <%= li %>
- <% end %>
-
-
- <% profile_nav_items.each do |li| %>
- <%= li %>
- <% end %>
-
-
-
-
-
-<%= render partial: 'layouts/flash_messages' %>
@@ -50,18 +21,6 @@
-
-
-
-
-
-
-
-
-
-
-<%= yield(:page_bottom) %>
-