From f0b34f96ffc9cfc887061600fef15a3f55fb126a Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Sun, 17 Dec 2017 11:21:09 +0100 Subject: [PATCH] Network and first signature collection prototyping --- Gemfile | 17 +- Gemfile.lock | 10 + app/assets/javascripts/application.js | 1 + app/controllers/application_controller.rb | 5 + app/controllers/greendays_controller.rb | 5 - .../signature_collections_controller.rb | 75 + app/models/network/signature_collection.rb | 8 + .../signature_collections/_form.html.slim | 12 + ...network_signature_collection.json.jbuilder | 2 + .../signature_collections/edit.html.slim | 8 + .../signature_collections/index.html.slim | 39 + .../signature_collections/index.json.jbuilder | 1 + .../signature_collections/new.html.slim | 5 + .../signature_collections/show.html.slim | 21 + .../signature_collections/show.json.jbuilder | 1 + app/views/pages/network.de.slim | 18 + app/views/supporters/_form.slim | 122 +- app/views/supporters/new.slim | 9 +- config/environments/development.rb | 2 +- config/locales/de.yml | 45 +- config/locales/fr.yml | 4 + config/locales/it.yml | 6 +- config/navigation.rb | 9 + config/routes.rb | 6 + .../signature_collections_controller_spec.rb | 141 ++ .../signature_collections_helper_spec.rb | 15 + .../network/signature_collection_spec.rb | 5 + .../network_signature_collections_spec.rb | 10 + .../signature_collections_routing_spec.rb | 39 + .../edit.html.slim_spec.rb | 24 + .../index.html.slim_spec.rb | 25 + .../new.html.slim_spec.rb | 24 + .../show.html.slim_spec.rb | 18 + vendor/assets/javascripts/list.js | 1758 +++++++++++++++++ vendor/assets/javascripts/list.min.js | 2 + 35 files changed, 2400 insertions(+), 92 deletions(-) create mode 100644 app/controllers/network/signature_collections_controller.rb create mode 100644 app/models/network/signature_collection.rb create mode 100644 app/views/network/signature_collections/_form.html.slim create mode 100644 app/views/network/signature_collections/_network_signature_collection.json.jbuilder create mode 100644 app/views/network/signature_collections/edit.html.slim create mode 100644 app/views/network/signature_collections/index.html.slim create mode 100644 app/views/network/signature_collections/index.json.jbuilder create mode 100644 app/views/network/signature_collections/new.html.slim create mode 100644 app/views/network/signature_collections/show.html.slim create mode 100644 app/views/network/signature_collections/show.json.jbuilder create mode 100644 app/views/pages/network.de.slim create mode 100644 spec/controllers/network/signature_collections_controller_spec.rb create mode 100644 spec/helpers/network/signature_collections_helper_spec.rb create mode 100644 spec/models/network/signature_collection_spec.rb create mode 100644 spec/requests/network/network_signature_collections_spec.rb create mode 100644 spec/routing/network/signature_collections_routing_spec.rb create mode 100644 spec/views/network/signature_collections/edit.html.slim_spec.rb create mode 100644 spec/views/network/signature_collections/index.html.slim_spec.rb create mode 100644 spec/views/network/signature_collections/new.html.slim_spec.rb create mode 100644 spec/views/network/signature_collections/show.html.slim_spec.rb create mode 100644 vendor/assets/javascripts/list.js create mode 100644 vendor/assets/javascripts/list.min.js diff --git a/Gemfile b/Gemfile index 807cce6..aa0f704 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,9 @@ gem 'coffee-rails', '~> 4.1.0' # Integrate Paperclip into Mongoid. gem 'mongoid-paperclip' +# Adds support for multiparameter fields to mongoid 4.x series. +gem 'mongoid-sadstory' + # Markdown rendering gem 'redcarpet' @@ -29,7 +32,7 @@ gem 'paperclip-i18n' # Amazon's S3 file hosting service gem 'aws-sdk', '< 3.0' # see https://github.com/thoughtbot/paperclip/issues/2484 -# A fixtures replacement +# A fixtures replacement gem 'factory_girl' # It's a small library to provide the Rails I18n translations on the JavaScript. @@ -54,7 +57,7 @@ gem 'route_translator' gem 'roadie-rails', '~> 1.0' # Devise is a flexible authentication solution for Rails based on Warden. -gem 'devise' +gem 'devise' # Translations for devise gem 'devise-i18n' @@ -92,7 +95,7 @@ group :development, :test do gem 'byebug' gem 'mongoid-rspec', '3.0.0' gem 'rspec-rails', '~> 3.0' - + gem 'pry-rails' gem 'dotenv-rails' end @@ -100,13 +103,17 @@ end group :development do # A series of things you can use to benchmark a Rails or Ruby app. gem 'derailed_benchmarks' - + # To use all profiling methods available gem 'stackprof' - + # Access an IRB console on exception pages or by using <%= console %> in views gem 'web-console', '~> 2.0' + # Better Errors replaces the standard Rails error page with a much better and more useful error page. + gem "better_errors" + gem "binding_of_caller" + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring # gem 'spring' end diff --git a/Gemfile.lock b/Gemfile.lock index 194156d..d5c7bbb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,6 +50,10 @@ GEM aws-sigv4 (1.0.2) bcrypt (3.1.11) benchmark-ips (2.7.2) + better_errors (2.4.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) bootstrap-sass (3.3.7) @@ -101,6 +105,7 @@ GEM dotenv-rails (2.2.1) dotenv (= 2.2.1) railties (>= 3.2, < 5.2) + erubi (1.7.0) erubis (2.7.0) execjs (2.7.0) factory_girl (4.8.1) @@ -180,6 +185,8 @@ GEM mongoid (~> 5.0) rake rspec (~> 3.3) + mongoid-sadstory (0.0.2) + mongoid mongoid_paranoia (0.2.1) mongoid (>= 4) mongoid-compatibility @@ -354,6 +361,8 @@ PLATFORMS DEPENDENCIES aws-sdk (< 3.0) + better_errors + binding_of_caller bootstrap-sass (~> 3.3.5) byebug capybara @@ -373,6 +382,7 @@ DEPENDENCIES mongoid (~> 5.0.0) mongoid-paperclip mongoid-rspec (= 3.0.0) + mongoid-sadstory mongoid_paranoia paperclip-i18n pry-rails diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c4b3a9a..9ae7ba2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -16,4 +16,5 @@ //= require typeahead.bundle //= require markerclusterer_compiled //= require i18n/translations +//= require list //= require_tree . diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cd27f69..1b79844 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -33,6 +33,11 @@ class ApplicationController < ActionController::Base false end + def only_admin + return true if current_supporter && current_supporter.admin? + raise ActionController::RoutingError, 'Not Found' + end + protected def configure_permitted_parameters diff --git a/app/controllers/greendays_controller.rb b/app/controllers/greendays_controller.rb index b49e929..08be6a4 100644 --- a/app/controllers/greendays_controller.rb +++ b/app/controllers/greendays_controller.rb @@ -57,9 +57,4 @@ class GreendaysController < ApplicationController def greenday_params params.require(:greenday).permit(:title, :address, :homepage, :description, :logo, :delete_logo) end - - def only_admin - return true if current_supporter && current_supporter.admin? - raise ActionController::RoutingError, 'Not Found' - end end diff --git a/app/controllers/network/signature_collections_controller.rb b/app/controllers/network/signature_collections_controller.rb new file mode 100644 index 0000000..396dca7 --- /dev/null +++ b/app/controllers/network/signature_collections_controller.rb @@ -0,0 +1,75 @@ +class Network::SignatureCollectionsController < ApplicationController + before_action :only_admin, except: [:index, :show] + before_action :set_network_signature_collection, only: [:show, :edit, :update, :destroy] + + # GET /network/signature_collections + # GET /network/signature_collections.json + def index + @network_signature_collections = Network::SignatureCollection.all + end + + # GET /network/signature_collections/1 + # GET /network/signature_collections/1.json + def show + end + + # GET /network/signature_collections/new + def new + @network_signature_collection = Network::SignatureCollection.new + end + + # GET /network/signature_collections/1/edit + def edit + end + + # POST /network/signature_collections + # POST /network/signature_collections.json + def create + @network_signature_collection = Network::SignatureCollection.new(network_signature_collection_params) + + respond_to do |format| + if @network_signature_collection.save + format.html { redirect_to @network_signature_collection, notice: 'Signature collection was successfully created.' } + format.json { render :show, status: :created, location: @network_signature_collection } + else + format.html { render :new } + format.json { render json: @network_signature_collection.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /network/signature_collections/1 + # PATCH/PUT /network/signature_collections/1.json + def update + respond_to do |format| + if @network_signature_collection.update(network_signature_collection_params) + format.html { redirect_to @network_signature_collection, notice: 'Signature collection was successfully updated.' } + format.json { render :show, status: :ok, location: @network_signature_collection } + else + format.html { render :edit } + format.json { render json: @network_signature_collection.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /network/signature_collections/1 + # DELETE /network/signature_collections/1.json + def destroy + @network_signature_collection.destroy + respond_to do |format| + format.html { redirect_to network_signature_collections_url, notice: 'Signature collection was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_network_signature_collection + @network_signature_collection = Network::SignatureCollection.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def network_signature_collection_params + params.require(:network_signature_collection).permit(:location_name, :location_zip, :location_address, :event_date_start, :event_date_end) + end +end diff --git a/app/models/network/signature_collection.rb b/app/models/network/signature_collection.rb new file mode 100644 index 0000000..62c7df8 --- /dev/null +++ b/app/models/network/signature_collection.rb @@ -0,0 +1,8 @@ +class Network::SignatureCollection + include Mongoid::Document + field :location_name, type: String + field :location_zip, type: Integer + field :location_address, type: String + field :event_date_start, type: Time + field :event_date_end, type: Time +end diff --git a/app/views/network/signature_collections/_form.html.slim b/app/views/network/signature_collections/_form.html.slim new file mode 100644 index 0000000..347aae5 --- /dev/null +++ b/app/views/network/signature_collections/_form.html.slim @@ -0,0 +1,12 @@ += simple_form_for(@network_signature_collection) do |f| + = f.error_notification + + .form-inputs + = f.input :location_name + = f.input :location_zip, as: :numeric + = f.input :location_address + = f.input :event_date_start, as: :datetime + = f.input :event_date_end, as: :datetime + + .form-actions + = f.button :submit, value: t('shared.save') diff --git a/app/views/network/signature_collections/_network_signature_collection.json.jbuilder b/app/views/network/signature_collections/_network_signature_collection.json.jbuilder new file mode 100644 index 0000000..a25f4ce --- /dev/null +++ b/app/views/network/signature_collections/_network_signature_collection.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! network_signature_collection, :id, :location_name, :location_zip, :location_address, :event_date_start, :event_date_end, :created_at, :updated_at +json.url network_signature_collection_url(network_signature_collection, format: :json) diff --git a/app/views/network/signature_collections/edit.html.slim b/app/views/network/signature_collections/edit.html.slim new file mode 100644 index 0000000..1ad6f97 --- /dev/null +++ b/app/views/network/signature_collections/edit.html.slim @@ -0,0 +1,8 @@ +h1 Editing network_signature_collection + +== render 'form' + += link_to 'Show', @network_signature_collection +' | += link_to 'Back', network_signature_collections_path + diff --git a/app/views/network/signature_collections/index.html.slim b/app/views/network/signature_collections/index.html.slim new file mode 100644 index 0000000..f2b7263 --- /dev/null +++ b/app/views/network/signature_collections/index.html.slim @@ -0,0 +1,39 @@ +h1 = title t('.title') +#signature_collections_table + input.search.form-control.string placeholder="#{t('.search')}" + table.table + thead + tr + th = Network::SignatureCollection.human_attribute_name :location_name + th = Network::SignatureCollection.human_attribute_name :location_zip + th = Network::SignatureCollection.human_attribute_name :location_address + th = Network::SignatureCollection.human_attribute_name :event_date_start + th = Network::SignatureCollection.human_attribute_name :event_date_end + - if admin? + th + th + th + + tbody.list + - @network_signature_collections.each do |network_signature_collection| + tr + td.location_name = network_signature_collection.location_name + td.location_zip = network_signature_collection.location_zip + td.location_address = network_signature_collection.location_address + td.event_date_start = l network_signature_collection.event_date_start, format: :short + td.event_date_end = l network_signature_collection.event_date_end, format: :short + - if admin? + td = link_to t('shared.show'), network_signature_collection + td = link_to t('shared.edit'), edit_network_signature_collection_path(network_signature_collection) + td = link_to t('shared.destroy'), network_signature_collection, data: { confirm: 'Are you sure?' }, method: :delete + +br + += link_to t('.new'), new_network_signature_collection_path + +javascript: + var options = { + valueNames: [ 'location_name', 'location_zip', 'location_address', 'event_date_start', 'event_date_end' ] + }; + + var signature_collections = new List('signature_collections_table', options); diff --git a/app/views/network/signature_collections/index.json.jbuilder b/app/views/network/signature_collections/index.json.jbuilder new file mode 100644 index 0000000..91cbb32 --- /dev/null +++ b/app/views/network/signature_collections/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @network_signature_collections, partial: 'network_signature_collections/network_signature_collection', as: :network_signature_collection diff --git a/app/views/network/signature_collections/new.html.slim b/app/views/network/signature_collections/new.html.slim new file mode 100644 index 0000000..871e036 --- /dev/null +++ b/app/views/network/signature_collections/new.html.slim @@ -0,0 +1,5 @@ +h1 New network_signature_collection + +== render 'form' + += link_to 'Back', network_signature_collections_path diff --git a/app/views/network/signature_collections/show.html.slim b/app/views/network/signature_collections/show.html.slim new file mode 100644 index 0000000..3e619ed --- /dev/null +++ b/app/views/network/signature_collections/show.html.slim @@ -0,0 +1,21 @@ +p#notice = notice + +p + strong = Network::SignatureCollection.human_attribute_name :location_name + = @network_signature_collection.location_name +p + strong = Network::SignatureCollection.human_attribute_name :location_zip + = @network_signature_collection.location_zip +p + strong = Network::SignatureCollection.human_attribute_name :location_address + = @network_signature_collection.location_address +p + strong = Network::SignatureCollection.human_attribute_name :event_date_start + = @network_signature_collection.event_date_start +p + strong = Network::SignatureCollection.human_attribute_name :event_date_end + = @network_signature_collection.event_date_end + += link_to 'Edit', edit_network_signature_collection_path(@network_signature_collection) +' | += link_to 'Back', network_signature_collections_path diff --git a/app/views/network/signature_collections/show.json.jbuilder b/app/views/network/signature_collections/show.json.jbuilder new file mode 100644 index 0000000..c02f016 --- /dev/null +++ b/app/views/network/signature_collections/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "network_signature_collections/network_signature_collection", network_signature_collection: @network_signature_collection diff --git a/app/views/pages/network.de.slim b/app/views/pages/network.de.slim new file mode 100644 index 0000000..56fa7c1 --- /dev/null +++ b/app/views/pages/network.de.slim @@ -0,0 +1,18 @@ +h1 = title 'Netzwerk Legalisierung' + +p Liebe Legalisierende +p Du bist auf der offiziellen Webseite für die Volksinitiative zur Legalisierung von Cannabis in der Schweiz. +p Das Netzwerk Legalisierung ist ein Versuch, eine Kampagne völlig neu zu organisieren. Konkret ermöglicht dir das Netzwerk, die Kampagne für die Legalisierung selbst zu gestalten und die Legalisierung dann auch mit den anderen im Netzwerk voranzutreiben. Das Netzwerk Legalisierung ist also ein grosses Werkzeug, aufgebaut aus vielen kleinen Werkzeugen. +p Entstanden ist die Idee, weil der Verein Legalize it!, der die Initiative lanciert hat, kaum Ressourcen zur Verfügung stehen. Im Gegensatz zu Parteien und grossen Organisationen ist Legalize it! kein durchorganisierter politischer Verein mit tausenden Mitgliedern und einem immer-vollen Konto. Der Verein Legalize it! kann also die Initiative nicht alleine meistern. +p Letztendlich bedeutet dies: Ob die Legalisierung durchkommt, hängt entscheidend von dir und allen anderen Mitwirkenden im Netzwerk ab. Legalisierung passiert nicht, sondern muss gemacht werden. +p Wenig überraschend werden wir beim Verein dafür immer wieder gefragt, wie man sich für die Legalisierung konkret einsetzen kann. Denn Hunderttausende sind von der heutigen Bevormundungspolitik als Konsumierende direkt betroffen. Und sie alle wissen, dass sie direkt betroffen sind. +p Um die Legalisierung voranzubringen, bracht es verschieden Vorgehensweisen. Darum sammeln wir all eure Aktionen auf dieser Plattform. Das Netzwerk Legalisierung ist daher so aufgebaut, dass alle Möglichkeiten, sich für die Legalisierung von Cannabis einzusetzen, auf einer Plattform gesammelt werden. +p Ohne einen stramm-durchorganisierten Verein im Eiltempo hochzuziehen, wollen wir so als schlank-organisiertes und smart organisiertes Netzwerk gemeinsam die Legalisierung erreichen. +h2 So funktioniert es +p Das Netzwerk ist in drei Hauptkategorien und sieben Unterkategorien aufgeteilt. Schau dir die Seiten am besten von links nach rechts mal an, damit du einen Überblick bekommst. +p Die erste Hauptkategorie beschäftigt sich mit dem, was nötig ist, damit die Initiative überhaupt zu Stande kommt. Es geht also um die Unterschriftensammlung und Finanzierung. Erreichen wir die Ziele in der ersten Kategorie nicht, ist es nicht möglich, diese Initiative durchzuführen. +p In der zweiten Hauptkategorie in der Mitte werden die Grundlagen der Überzeugungs- und Kampagnenarbeit thematisiert. Das ist zum einen die Selbstbildung, zum anderen der Aufbau von Netzwerk-Cliquen. +p In der dritten Hauptkategorie rechts geht es um die eigentliche Überzeugungs- und Kampagnenarbeit. Es geht um die Überzeugungsarbeit in deinem eignen Umfeld, halb-öffentliche und öffentliche Überzeugungsarbeit mittels Social Media und politischer Aktionen, sowie die Möglichkeiten das Netzwerk intern zu stärken. In der dritten Hauptkategorie geht es also um deine konkreten Möglichkeiten, die Kampagne selbst zu gestalten und zu organisieren, um die Leute gezielt zu überzeugen. Du findest in der dritten Hauptkategorie dazu einen einführenden Text, das Aktionen- und Veranstaltungstool, sowie Möglichkeiten zum Ideen-Austausch. +p Damit du das Netzwerk nutzen kannst musst du dich lediglich hier registrieren: +- @supporter = Supporter.new += render 'supporters/form' diff --git a/app/views/supporters/_form.slim b/app/views/supporters/_form.slim index ad4e8ad..5284e55 100644 --- a/app/views/supporters/_form.slim +++ b/app/views/supporters/_form.slim @@ -1,67 +1,61 @@ -- title(t('shared.header.title_html')) -.row - .col-xs-12.col-md-7 - h2.sr-only = t('.form') - = simple_form_for @supporter do |f| - = f.input :first_name, placeholder: t('.placeholders.first_name') - = f.input :last_name, placeholder: t('.placeholders.last_name') - = f.input :street, placeholder: t('.placeholders.street') - = f.input :city, placeholder: t('.placeholders.city') - = f.input :zip, placeholder: t('.placeholders.zip') - = f.input :email, placeholder: t('.placeholders.email') - = f.input :tel, placeholder: '+41 (0) xxxx' - = f.input :support, as: :radio_buttons, collection: t('support_type').map{ |x| [x.first, raw(x.last)]}, label_method: :last, value_method: :first - = f.input :li_membership, as: :boolean - = f.input :age_category, as: :radio_buttons, collection: t('age_category'), label_method: :last, value_method: :first - = f.input :comments, as: :text - .form-group - = f.submit value: t('.submit'), class: 'btn btn-success' - .form-group - = link_to t('.terms_and_conditions'), terms_and_conditions_path, class: 'small' += simple_form_for @supporter do |f| + = f.input :first_name, placeholder: t('.placeholders.first_name') + = f.input :last_name, placeholder: t('.placeholders.last_name') + = f.input :street, placeholder: t('.placeholders.street') + = f.input :city, placeholder: t('.placeholders.city') + = f.input :zip, placeholder: t('.placeholders.zip') + = f.input :email, placeholder: t('.placeholders.email') + = f.input :tel, placeholder: '+41 (0) xxxx' + = f.input :support, as: :radio_buttons, collection: t('support_type').map{ |x| [x.first, raw(x.last)]}, label_method: :last, value_method: :first + = f.input :li_membership, as: :boolean + = f.input :age_category, as: :radio_buttons, collection: t('age_category'), label_method: :last, value_method: :first + = f.input :comments, as: :text + .form-group + = f.submit value: t('.submit'), class: 'btn btn-success' + .form-group + = link_to t('.terms_and_conditions'), terms_and_conditions_path, class: 'small' - javascript: - var cities = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.whitespace, - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/cities_autocomplete.json?query=%QUERY', - wildcard: '%QUERY', - } - }); - - var suggestion_template = function (data) { - return '

' + data.plz + ' - ' + data.city + '

'; +javascript: + var cities = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/cities_autocomplete.json?query=%QUERY', + wildcard: '%QUERY', + } + }); + + var suggestion_template = function (data) { + return '

' + data.plz + ' - ' + data.city + '

'; + } + + $('#supporter_city').typeahead({ + minLength: 2, + },{ + limit: 20, + display: 'city', + templates: { + suggestion: function (data) { + return suggestion_template(data); + } + }, + source: cities + }).on('typeahead:selected', function(event, datum) { + $("#supporter_zip").val(datum.plz); + }); + + $('#supporter_zip').typeahead({ + minLength: 2, + },{ + limit: 20, + display: 'plz', + templates: { + suggestion: function (data) { + return suggestion_template(data); } - - $('#supporter_city').typeahead({ - minLength: 2, - },{ - limit: 20, - display: 'city', - templates: { - suggestion: function (data) { - return suggestion_template(data); - } - }, - source: cities - }).on('typeahead:selected', function(event, datum) { - $("#supporter_zip").val(datum.plz); - }); - - $('#supporter_zip').typeahead({ - minLength: 2, - },{ - limit: 20, - display: 'plz', - templates: { - suggestion: function (data) { - return suggestion_template(data); - } - }, - source: cities - }).on('typeahead:selected', function(event, datum) { - $("#supporter_city").val(datum.city); - }); - aside.col-md-5.col-xs-12 - = render 'right_side' \ No newline at end of file + }, + source: cities + }).on('typeahead:selected', function(event, datum) { + $("#supporter_city").val(datum.city); + }); diff --git a/app/views/supporters/new.slim b/app/views/supporters/new.slim index d98336f..caadf60 100644 --- a/app/views/supporters/new.slim +++ b/app/views/supporters/new.slim @@ -35,5 +35,10 @@ = render 'right_side' - else - = render 'form' - + - title(t('shared.header.title_html')) + .row + .col-xs-12.col-md-7 + h2.sr-only = t('.form') + = render 'form' + aside.col-md-5.col-xs-12 + = render 'right_side' diff --git a/config/environments/development.rb b/config/environments/development.rb index d3d93ef..d689169 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -52,5 +52,5 @@ Rails.application.configure do access_key_id: ENV["S3_ACCESS_KEY"], secret_access_key: ENV["S3_ACCESS_SECRET"] } - } + } end diff --git a/config/locales/de.yml b/config/locales/de.yml index 1171e75..05f36f8 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -19,34 +19,38 @@ de: numbers: Zahlen statements: Statements collection_concept: Wie macht man eine Volksinitiative? - + network: Netzwerk + signature_collections: Unterschriftensammlung + lang: Sprache are_you_sure: Bist du sicher? edit: Bearbeiten + show: Anzeigen + destroy: Löschen save: Speichern description: Volksinitiative zur Legalisierung von Cannabis in der Schweiz. counter: supporters: "Unterstützer:" - amount_of_supporters: Zurzeit haben wir %{amount} Unterstützer. + amount_of_supporters: Zurzeit haben wir %{amount} Unterstützer. input_error: Deine Eingaben konnte nicht gespeichert werden. Überprüfe deine Eingaben. donation: Deine Spende welcome_email: subject: Besten Dank für deine Unterstützung! - + support_type: signer: Ich würde eine Volksinitiative für die Legalisierung von Cannabis unterschreiben. supporter: Ich würde eine Volksinitiative für die Legalisierung von Cannabis unterschreiben und helfe aktiv Unterschriften in meiner Umgebung zu sammeln. # activist: Ich unterschreibe eine Volksinitiative für die Legalisierung von Cannabis und beteilige mich bei der Planung und Koordination einer Volksinitiative. # nonvoting_supporter: Ich bin nicht stimmberechtigt, helfe aber aktiv Unterschriften in meiner Umgebung zu sammeln um Cannabis zu legalisierung. - + age_category: underage: Ich bin jünger als 18 Jahre alt young: Ich bin 18 bis 30 Jahre alt middle: Ich bin 30 bis 64 Jahre alt retired: Ich bin über 64 Jahre alt - + publicity_states: pending: Die Veröffentlichung wurde noch nicht genehmigt approved: Das Statement wurde Veröffentlicht @@ -56,7 +60,7 @@ de: de: Deutsch fr: Français it: Italiano - + supporters: form: form: Formular @@ -76,15 +80,15 @@ de: renew: Statement neu einreichen delete: Statement löschen create_publicity: Deine Meinung zählt, darum bitten wir dir hier die Möglichkeit öffentlich deine Meinung zur Cannabis-Initiative kundzutun. Gib ein Starkes Statement ab und zeig dein Gesicht! Meine Unterstützung öffentlich kundtun ... - + create: timeout: Ihre Eingabe war zu schnell. - + new: title: Hallo %{name} edit: Meine Daten bearbeiten my_data: Meine Daten - + publicities: form: title: Meine Meinung zur Cannabis-Initiative veröffentlichen @@ -92,7 +96,7 @@ de: organisation_placeholder: Parteizugehörigkeit, Verband oder sonstige Organisationen avatar: Lade ein Bild von dir Hoch save: Zur Überprüfung einsenden - + create: success: Besten Dank die Daten werden überprüft. update: @@ -100,16 +104,21 @@ de: pages: show: - not_found: Die Seite konnte nicht gefunden werden. + not_found: Die Seite konnte nicht gefunden werden. thanks: title: Besten Dank für deine Unterstützung! - + statements: title: Statements von unseren Unterstützern submit_your_own_statement: Veröffentliche dein eigenes Statement... read_more: das ganze Statement lesen. - + network: + signature_collections: + index: + new: Neue Sammlung erfasssen + title: Unterschriftensammlungen + search: Suche routes: initiative_text: initiativtext arguments: argumentarium @@ -120,6 +129,8 @@ de: statementes: statements collection_concept: Sammelkonzept greendays: greensaturday + network: netzwerk + signature_collections: unterschriftensammlung mongoid: models: @@ -146,4 +157,10 @@ de: state: Status organisation: Organisation statement: Statement - avatar: Bild \ No newline at end of file + avatar: Bild + network/signature_collection: + location_name: Ort + location_zip: Postleitzahl + location_address: Adresse + event_date_start: Startzeit + event_date_end: Endzeit diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1228043..8ff8647 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -24,6 +24,8 @@ fr: statements: Déclarations collection_concept: Comment faire une initiative populaire? (en allemand) my_account: Mon compte + network: Réseau + signature_collections: Collection des signatures lang: Langue are_you_sure: Tu es sûre? @@ -117,6 +119,8 @@ fr: statements: declarations collection_concept: plan_de_collecte greendays: greensaturday + network: reseau + signature_collections: collection_signature mongoid: models: diff --git a/config/locales/it.yml b/config/locales/it.yml index 2cb487b..6d33893 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -21,7 +21,9 @@ it: statements: Dichiarazioni collection_concept: Come fare un iniziativa popolare? (in tedesco) my_account: Il mio account - + network: Reticolato + signature_collections: collezione di firme + counter: supporters: "Sostenitore:" amount_of_supporters: Al momento abbiamo %{amount} sostenitori. @@ -121,6 +123,8 @@ it: statements: dichiarazioni collection_concept: sistema_di_raccolta greendays: greensaturday + network: reticolato + signature_collections: collezione_firme mongoid: attributes: diff --git a/config/navigation.rb b/config/navigation.rb index f6581bd..ece76a7 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -62,16 +62,25 @@ SimpleNavigation::Configuration.run do |navigation| unless supporter_signed_in? primary.item :home, I18n.t('.shared.topnavigation.support'), "/#{I18n.locale}" end + primary.item :initiative, I18n.t('.shared.topnavigation.initiative'), initiative_text_path do | initiative | initiative.item :arguments, I18n.t('.shared.topnavigation.arguments'), arguments_path initiative.item :faq, I18n.t('.shared.topnavigation.faq'), faq_path initiative.item :numbers, I18n.t('.shared.topnavigation.numbers'), numbers_path initiative.item :collection_concept, I18n.t('.shared.topnavigation.collection_concept'), collection_concept_path end + primary.item :statements, I18n.t('.shared.topnavigation.statements'), statements_path do |statements| statements.item :new_statement, I18n.t('.supporters.publicity.renew'), new_publicity_path end + primary.item :donation, I18n.t('donation'), donation_path + + primary.item :network, I18n.t('.shared.topnavigation.network'), network_path do | network | + network.item :signature_collections, I18n.t('.shared.topnavigation.signature_collections'), network_signature_collections_path + end + + primary.item :blog, 'Blog', 'http://blog.cannabis-initiative.ch/', link_html: { target: '_blank'} if supporter_signed_in? primary.item :home, I18n.t('.shared.topnavigation.my_account'), "/#{I18n.locale}" do |my_account| diff --git a/config/routes.rb b/config/routes.rb index b1845b6..235ddc4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + devise_for :supporters root to: 'supporters#new' @@ -11,6 +12,11 @@ Rails.application.routes.draw do end localized do + get 'network', to: 'pages#show', as: :network, page: 'network' + namespace :network do + resources :signature_collections + end + resources :greendays get '/initiative_text', to: 'pages#show', as: :initiative_text, page: 'initiative_text' get '/arguments', to: 'pages#show', as: :arguments, page: 'arguments' diff --git a/spec/controllers/network/signature_collections_controller_spec.rb b/spec/controllers/network/signature_collections_controller_spec.rb new file mode 100644 index 0000000..4ac397d --- /dev/null +++ b/spec/controllers/network/signature_collections_controller_spec.rb @@ -0,0 +1,141 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. +# +# Also compared to earlier versions of this generator, there are no longer any +# expectations of assigns and templates rendered. These features have been +# removed from Rails core in Rails 5, but can be added back in via the +# `rails-controller-testing` gem. + +RSpec.describe Network::SignatureCollectionsController, type: :controller do + + # This should return the minimal set of attributes required to create a valid + # Network::SignatureCollection. As you add validations to Network::SignatureCollection, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # Network::SignatureCollectionsController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "GET #index" do + it "returns a success response" do + signature_collection = Network::SignatureCollection.create! valid_attributes + get :index, {}, valid_session + expect(response).to be_success + end + end + + describe "GET #show" do + it "returns a success response" do + signature_collection = Network::SignatureCollection.create! valid_attributes + get :show, {:id => signature_collection.to_param}, valid_session + expect(response).to be_success + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, {}, valid_session + expect(response).to be_success + end + end + + describe "GET #edit" do + it "returns a success response" do + signature_collection = Network::SignatureCollection.create! valid_attributes + get :edit, {:id => signature_collection.to_param}, valid_session + expect(response).to be_success + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new Network::SignatureCollection" do + expect { + post :create, {:network_signature_collection => valid_attributes}, valid_session + }.to change(Network::SignatureCollection, :count).by(1) + end + + it "redirects to the created network_signature_collection" do + post :create, {:network_signature_collection => valid_attributes}, valid_session + expect(response).to redirect_to(Network::SignatureCollection.last) + end + end + + context "with invalid params" do + it "returns a success response (i.e. to display the 'new' template)" do + post :create, {:network_signature_collection => invalid_attributes}, valid_session + expect(response).to be_success + end + end + end + + describe "PUT #update" do + context "with valid params" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested network_signature_collection" do + signature_collection = Network::SignatureCollection.create! valid_attributes + put :update, {:id => signature_collection.to_param, :network_signature_collection => new_attributes}, valid_session + signature_collection.reload + skip("Add assertions for updated state") + end + + it "redirects to the network_signature_collection" do + signature_collection = Network::SignatureCollection.create! valid_attributes + put :update, {:id => signature_collection.to_param, :network_signature_collection => valid_attributes}, valid_session + expect(response).to redirect_to(signature_collection) + end + end + + context "with invalid params" do + it "returns a success response (i.e. to display the 'edit' template)" do + signature_collection = Network::SignatureCollection.create! valid_attributes + put :update, {:id => signature_collection.to_param, :network_signature_collection => invalid_attributes}, valid_session + expect(response).to be_success + end + end + end + + describe "DELETE #destroy" do + it "destroys the requested network_signature_collection" do + signature_collection = Network::SignatureCollection.create! valid_attributes + expect { + delete :destroy, {:id => signature_collection.to_param}, valid_session + }.to change(Network::SignatureCollection, :count).by(-1) + end + + it "redirects to the network_signature_collections list" do + signature_collection = Network::SignatureCollection.create! valid_attributes + delete :destroy, {:id => signature_collection.to_param}, valid_session + expect(response).to redirect_to(network_signature_collections_url) + end + end + +end diff --git a/spec/helpers/network/signature_collections_helper_spec.rb b/spec/helpers/network/signature_collections_helper_spec.rb new file mode 100644 index 0000000..1556592 --- /dev/null +++ b/spec/helpers/network/signature_collections_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Network::SignatureCollectionsHelper. For example: +# +# describe Network::SignatureCollectionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Network::SignatureCollectionsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/network/signature_collection_spec.rb b/spec/models/network/signature_collection_spec.rb new file mode 100644 index 0000000..57d6da0 --- /dev/null +++ b/spec/models/network/signature_collection_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Network::SignatureCollection, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/network/network_signature_collections_spec.rb b/spec/requests/network/network_signature_collections_spec.rb new file mode 100644 index 0000000..59db006 --- /dev/null +++ b/spec/requests/network/network_signature_collections_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Network::SignatureCollections", type: :request do + describe "GET /network_signature_collections" do + it "works! (now write some real specs)" do + get network_signature_collections_path + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/routing/network/signature_collections_routing_spec.rb b/spec/routing/network/signature_collections_routing_spec.rb new file mode 100644 index 0000000..d257bd1 --- /dev/null +++ b/spec/routing/network/signature_collections_routing_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" + +RSpec.describe Network::SignatureCollectionsController, type: :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/network/signature_collections").to route_to("network/signature_collections#index") + end + + it "routes to #new" do + expect(:get => "/network/signature_collections/new").to route_to("network/signature_collections#new") + end + + it "routes to #show" do + expect(:get => "/network/signature_collections/1").to route_to("network/signature_collections#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/network/signature_collections/1/edit").to route_to("network/signature_collections#edit", :id => "1") + end + + it "routes to #create" do + expect(:post => "/network/signature_collections").to route_to("network/signature_collections#create") + end + + it "routes to #update via PUT" do + expect(:put => "/network/signature_collections/1").to route_to("network/signature_collections#update", :id => "1") + end + + it "routes to #update via PATCH" do + expect(:patch => "/network/signature_collections/1").to route_to("network/signature_collections#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/network/signature_collections/1").to route_to("network/signature_collections#destroy", :id => "1") + end + + end +end diff --git a/spec/views/network/signature_collections/edit.html.slim_spec.rb b/spec/views/network/signature_collections/edit.html.slim_spec.rb new file mode 100644 index 0000000..3b7ecd1 --- /dev/null +++ b/spec/views/network/signature_collections/edit.html.slim_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +RSpec.describe "network/signature_collections/edit", type: :view do + before(:each) do + @network_signature_collection = assign(:network_signature_collection, Network::SignatureCollection.create!( + :location_name => "MyString", + :location_zip => 1, + :location_address => "MyString" + )) + end + + it "renders the edit network_signature_collection form" do + render + + assert_select "form[action=?][method=?]", network_signature_collection_path(@network_signature_collection), "post" do + + assert_select "input#network_signature_collection_location_name[name=?]", "network_signature_collection[location_name]" + + assert_select "input#network_signature_collection_location_zip[name=?]", "network_signature_collection[location_zip]" + + assert_select "input#network_signature_collection_location_address[name=?]", "network_signature_collection[location_address]" + end + end +end diff --git a/spec/views/network/signature_collections/index.html.slim_spec.rb b/spec/views/network/signature_collections/index.html.slim_spec.rb new file mode 100644 index 0000000..812797d --- /dev/null +++ b/spec/views/network/signature_collections/index.html.slim_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe "network/signature_collections/index", type: :view do + before(:each) do + assign(:network_signature_collections, [ + Network::SignatureCollection.create!( + :location_name => "Location Name", + :location_zip => 2, + :location_address => "Location Address" + ), + Network::SignatureCollection.create!( + :location_name => "Location Name", + :location_zip => 2, + :location_address => "Location Address" + ) + ]) + end + + it "renders a list of network/signature_collections" do + render + assert_select "tr>td", :text => "Location Name".to_s, :count => 2 + assert_select "tr>td", :text => 2.to_s, :count => 2 + assert_select "tr>td", :text => "Location Address".to_s, :count => 2 + end +end diff --git a/spec/views/network/signature_collections/new.html.slim_spec.rb b/spec/views/network/signature_collections/new.html.slim_spec.rb new file mode 100644 index 0000000..4ecb2ba --- /dev/null +++ b/spec/views/network/signature_collections/new.html.slim_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +RSpec.describe "network/signature_collections/new", type: :view do + before(:each) do + assign(:network_signature_collection, Network::SignatureCollection.new( + :location_name => "MyString", + :location_zip => 1, + :location_address => "MyString" + )) + end + + it "renders new network_signature_collection form" do + render + + assert_select "form[action=?][method=?]", network_signature_collections_path, "post" do + + assert_select "input#network_signature_collection_location_name[name=?]", "network_signature_collection[location_name]" + + assert_select "input#network_signature_collection_location_zip[name=?]", "network_signature_collection[location_zip]" + + assert_select "input#network_signature_collection_location_address[name=?]", "network_signature_collection[location_address]" + end + end +end diff --git a/spec/views/network/signature_collections/show.html.slim_spec.rb b/spec/views/network/signature_collections/show.html.slim_spec.rb new file mode 100644 index 0000000..197bea6 --- /dev/null +++ b/spec/views/network/signature_collections/show.html.slim_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe "network/signature_collections/show", type: :view do + before(:each) do + @network_signature_collection = assign(:network_signature_collection, Network::SignatureCollection.create!( + :location_name => "Location Name", + :location_zip => 2, + :location_address => "Location Address" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/Location Name/) + expect(rendered).to match(/2/) + expect(rendered).to match(/Location Address/) + end +end diff --git a/vendor/assets/javascripts/list.js b/vendor/assets/javascripts/list.js new file mode 100644 index 0000000..d40457f --- /dev/null +++ b/vendor/assets/javascripts/list.js @@ -0,0 +1,1758 @@ +/*! List.js v1.5.0 (http://listjs.com) by Jonny Strömberg (http://javve.com) */ +var List = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.l = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; + +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; + +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; + +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 11); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var index = __webpack_require__(4); + +/** + * Whitespace regexp. + */ + +var re = /\s+/; + +/** + * toString reference. + */ + +var toString = Object.prototype.toString; + +/** + * Wrap `el` in a `ClassList`. + * + * @param {Element} el + * @return {ClassList} + * @api public + */ + +module.exports = function(el){ + return new ClassList(el); +}; + +/** + * Initialize a new ClassList for `el`. + * + * @param {Element} el + * @api private + */ + +function ClassList(el) { + if (!el || !el.nodeType) { + throw new Error('A DOM element reference is required'); + } + this.el = el; + this.list = el.classList; +} + +/** + * Add class `name` if not already present. + * + * @param {String} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.add = function(name){ + // classList + if (this.list) { + this.list.add(name); + return this; + } + + // fallback + var arr = this.array(); + var i = index(arr, name); + if (!~i) arr.push(name); + this.el.className = arr.join(' '); + return this; +}; + +/** + * Remove class `name` when present, or + * pass a regular expression to remove + * any which match. + * + * @param {String|RegExp} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.remove = function(name){ + // classList + if (this.list) { + this.list.remove(name); + return this; + } + + // fallback + var arr = this.array(); + var i = index(arr, name); + if (~i) arr.splice(i, 1); + this.el.className = arr.join(' '); + return this; +}; + + +/** + * Toggle class `name`, can force state via `force`. + * + * For browsers that support classList, but do not support `force` yet, + * the mistake will be detected and corrected. + * + * @param {String} name + * @param {Boolean} force + * @return {ClassList} + * @api public + */ + +ClassList.prototype.toggle = function(name, force){ + // classList + if (this.list) { + if ("undefined" !== typeof force) { + if (force !== this.list.toggle(name, force)) { + this.list.toggle(name); // toggle again to correct + } + } else { + this.list.toggle(name); + } + return this; + } + + // fallback + if ("undefined" !== typeof force) { + if (!force) { + this.remove(name); + } else { + this.add(name); + } + } else { + if (this.has(name)) { + this.remove(name); + } else { + this.add(name); + } + } + + return this; +}; + +/** + * Return an array of classes. + * + * @return {Array} + * @api public + */ + +ClassList.prototype.array = function(){ + var className = this.el.getAttribute('class') || ''; + var str = className.replace(/^\s+|\s+$/g, ''); + var arr = str.split(re); + if ('' === arr[0]) arr.shift(); + return arr; +}; + +/** + * Check if class `name` is present. + * + * @param {String} name + * @return {ClassList} + * @api public + */ + +ClassList.prototype.has = +ClassList.prototype.contains = function(name){ + return this.list ? this.list.contains(name) : !! ~index(this.array(), name); +}; + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', + unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', + prefix = bind !== 'addEventListener' ? 'on' : '', + toArray = __webpack_require__(5); + +/** + * Bind `el` event `type` to `fn`. + * + * @param {Element} el, NodeList, HTMLCollection or Array + * @param {String} type + * @param {Function} fn + * @param {Boolean} capture + * @api public + */ + +exports.bind = function(el, type, fn, capture){ + el = toArray(el); + for ( var i = 0; i < el.length; i++ ) { + el[i][bind](prefix + type, fn, capture || false); + } +}; + +/** + * Unbind `el` event `type`'s callback `fn`. + * + * @param {Element} el, NodeList, HTMLCollection or Array + * @param {String} type + * @param {Function} fn + * @param {Boolean} capture + * @api public + */ + +exports.unbind = function(el, type, fn, capture){ + el = toArray(el); + for ( var i = 0; i < el.length; i++ ) { + el[i][unbind](prefix + type, fn, capture || false); + } +}; + + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +module.exports = function(list) { + return function(initValues, element, notCreate) { + var item = this; + + this._values = {}; + + this.found = false; // Show if list.searched == true and this.found == true + this.filtered = false;// Show if list.filtered == true and this.filtered == true + + var init = function(initValues, element, notCreate) { + if (element === undefined) { + if (notCreate) { + item.values(initValues, notCreate); + } else { + item.values(initValues); + } + } else { + item.elm = element; + var values = list.templater.get(item, initValues); + item.values(values); + } + }; + + this.values = function(newValues, notCreate) { + if (newValues !== undefined) { + for(var name in newValues) { + item._values[name] = newValues[name]; + } + if (notCreate !== true) { + list.templater.set(item, item.values()); + } + } else { + return item._values; + } + }; + + this.show = function() { + list.templater.show(item); + }; + + this.hide = function() { + list.templater.hide(item); + }; + + this.matching = function() { + return ( + (list.filtered && list.searched && item.found && item.filtered) || + (list.filtered && !list.searched && item.filtered) || + (!list.filtered && list.searched && item.found) || + (!list.filtered && !list.searched) + ); + }; + + this.visible = function() { + return (item.elm && (item.elm.parentNode == list.list)) ? true : false; + }; + + init(initValues, element, notCreate); + }; +}; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +/** + * A cross-browser implementation of getElementsByClass. + * Heavily based on Dustin Diaz's function: http://dustindiaz.com/getelementsbyclass. + * + * Find all elements with class `className` inside `container`. + * Use `single = true` to increase performance in older browsers + * when only one element is needed. + * + * @param {String} className + * @param {Element} container + * @param {Boolean} single + * @api public + */ + +var getElementsByClassName = function(container, className, single) { + if (single) { + return container.getElementsByClassName(className)[0]; + } else { + return container.getElementsByClassName(className); + } +}; + +var querySelector = function(container, className, single) { + className = '.' + className; + if (single) { + return container.querySelector(className); + } else { + return container.querySelectorAll(className); + } +}; + +var polyfill = function(container, className, single) { + var classElements = [], + tag = '*'; + + var els = container.getElementsByTagName(tag); + var elsLen = els.length; + var pattern = new RegExp("(^|\\s)"+className+"(\\s|$)"); + for (var i = 0, j = 0; i < elsLen; i++) { + if ( pattern.test(els[i].className) ) { + if (single) { + return els[i]; + } else { + classElements[j] = els[i]; + j++; + } + } + } + return classElements; +}; + +module.exports = (function() { + return function(container, className, single, options) { + options = options || {}; + if ((options.test && options.getElementsByClassName) || (!options.test && document.getElementsByClassName)) { + return getElementsByClassName(container, className, single); + } else if ((options.test && options.querySelector) || (!options.test && document.querySelector)) { + return querySelector(container, className, single); + } else { + return polyfill(container, className, single); + } + }; +})(); + + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + +var indexOf = [].indexOf; + +module.exports = function(arr, obj){ + if (indexOf) return arr.indexOf(obj); + for (var i = 0; i < arr.length; ++i) { + if (arr[i] === obj) return i; + } + return -1; +}; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + +/** + * Source: https://github.com/timoxley/to-array + * + * Convert an array-like object into an `Array`. + * If `collection` is already an `Array`, then will return a clone of `collection`. + * + * @param {Array | Mixed} collection An `Array` or array-like object to convert e.g. `arguments` or `NodeList` + * @return {Array} Naive conversion of `collection` to a new `Array`. + * @api public + */ + +module.exports = function toArray(collection) { + if (typeof collection === 'undefined') return []; + if (collection === null) return [null]; + if (collection === window) return [window]; + if (typeof collection === 'string') return [collection]; + if (isArray(collection)) return collection; + if (typeof collection.length != 'number') return [collection]; + if (typeof collection === 'function' && collection instanceof Function) return [collection]; + + var arr = []; + for (var i = 0; i < collection.length; i++) { + if (Object.prototype.hasOwnProperty.call(collection, i) || i in collection) { + arr.push(collection[i]); + } + } + if (!arr.length) return []; + return arr; +}; + +function isArray(arr) { + return Object.prototype.toString.call(arr) === "[object Array]"; +} + + +/***/ }), +/* 6 */ +/***/ (function(module, exports) { + +module.exports = function(s) { + s = (s === undefined) ? "" : s; + s = (s === null) ? "" : s; + s = s.toString(); + return s; +}; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports) { + +/* + * Source: https://github.com/segmentio/extend + */ + +module.exports = function extend (object) { + // Takes an unlimited number of extenders. + var args = Array.prototype.slice.call(arguments, 1); + + // For each extender, copy their properties on our object. + for (var i = 0, source; source = args[i]; i++) { + if (!source) continue; + for (var property in source) { + object[property] = source[property]; + } + } + + return object; +}; + + +/***/ }), +/* 8 */ +/***/ (function(module, exports) { + +module.exports = function(list) { + var addAsync = function(values, callback, items) { + var valuesToAdd = values.splice(0, 50); + items = items || []; + items = items.concat(list.add(valuesToAdd)); + if (values.length > 0) { + setTimeout(function() { + addAsync(values, callback, items); + }, 1); + } else { + list.update(); + callback(items); + } + }; + return addAsync; +}; + + +/***/ }), +/* 9 */ +/***/ (function(module, exports) { + +module.exports = function(list) { + + // Add handlers + list.handlers.filterStart = list.handlers.filterStart || []; + list.handlers.filterComplete = list.handlers.filterComplete || []; + + return function(filterFunction) { + list.trigger('filterStart'); + list.i = 1; // Reset paging + list.reset.filter(); + if (filterFunction === undefined) { + list.filtered = false; + } else { + list.filtered = true; + var is = list.items; + for (var i = 0, il = is.length; i < il; i++) { + var item = is[i]; + if (filterFunction(item)) { + item.filtered = true; + } else { + item.filtered = false; + } + } + } + list.update(); + list.trigger('filterComplete'); + return list.visibleItems; + }; +}; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + +var classes = __webpack_require__(0), + events = __webpack_require__(1), + extend = __webpack_require__(7), + toString = __webpack_require__(6), + getByClass = __webpack_require__(3), + fuzzy = __webpack_require__(19); + +module.exports = function(list, options) { + options = options || {}; + + options = extend({ + location: 0, + distance: 100, + threshold: 0.4, + multiSearch: true, + searchClass: 'fuzzy-search' + }, options); + + + + var fuzzySearch = { + search: function(searchString, columns) { + // Substract arguments from the searchString or put searchString as only argument + var searchArguments = options.multiSearch ? searchString.replace(/ +$/, '').split(/ +/) : [searchString]; + + for (var k = 0, kl = list.items.length; k < kl; k++) { + fuzzySearch.item(list.items[k], columns, searchArguments); + } + }, + item: function(item, columns, searchArguments) { + var found = true; + for(var i = 0; i < searchArguments.length; i++) { + var foundArgument = false; + for (var j = 0, jl = columns.length; j < jl; j++) { + if (fuzzySearch.values(item.values(), columns[j], searchArguments[i])) { + foundArgument = true; + } + } + if(!foundArgument) { + found = false; + } + } + item.found = found; + }, + values: function(values, value, searchArgument) { + if (values.hasOwnProperty(value)) { + var text = toString(values[value]).toLowerCase(); + + if (fuzzy(text, searchArgument, options)) { + return true; + } + } + return false; + } + }; + + + events.bind(getByClass(list.listContainer, options.searchClass), 'keyup', function(e) { + var target = e.target || e.srcElement; // IE have srcElement + list.search(target.value, fuzzySearch.search); + }); + + return function(str, columns) { + list.search(str, columns, fuzzySearch.search); + }; +}; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +var naturalSort = __webpack_require__(18), + getByClass = __webpack_require__(3), + extend = __webpack_require__(7), + indexOf = __webpack_require__(4), + events = __webpack_require__(1), + toString = __webpack_require__(6), + classes = __webpack_require__(0), + getAttribute = __webpack_require__(17), + toArray = __webpack_require__(5); + +module.exports = function(id, options, values) { + + var self = this, + init, + Item = __webpack_require__(2)(self), + addAsync = __webpack_require__(8)(self), + initPagination = __webpack_require__(12)(self); + + init = { + start: function() { + self.listClass = "list"; + self.searchClass = "search"; + self.sortClass = "sort"; + self.page = 10000; + self.i = 1; + self.items = []; + self.visibleItems = []; + self.matchingItems = []; + self.searched = false; + self.filtered = false; + self.searchColumns = undefined; + self.handlers = { 'updated': [] }; + self.valueNames = []; + self.utils = { + getByClass: getByClass, + extend: extend, + indexOf: indexOf, + events: events, + toString: toString, + naturalSort: naturalSort, + classes: classes, + getAttribute: getAttribute, + toArray: toArray + }; + + self.utils.extend(self, options); + + self.listContainer = (typeof(id) === 'string') ? document.getElementById(id) : id; + if (!self.listContainer) { return; } + self.list = getByClass(self.listContainer, self.listClass, true); + + self.parse = __webpack_require__(13)(self); + self.templater = __webpack_require__(16)(self); + self.search = __webpack_require__(14)(self); + self.filter = __webpack_require__(9)(self); + self.sort = __webpack_require__(15)(self); + self.fuzzySearch = __webpack_require__(10)(self, options.fuzzySearch); + + this.handlers(); + this.items(); + this.pagination(); + + self.update(); + }, + handlers: function() { + for (var handler in self.handlers) { + if (self[handler]) { + self.on(handler, self[handler]); + } + } + }, + items: function() { + self.parse(self.list); + if (values !== undefined) { + self.add(values); + } + }, + pagination: function() { + if (options.pagination !== undefined) { + if (options.pagination === true) { + options.pagination = [{}]; + } + if (options.pagination[0] === undefined){ + options.pagination = [options.pagination]; + } + for (var i = 0, il = options.pagination.length; i < il; i++) { + initPagination(options.pagination[i]); + } + } + } + }; + + /* + * Re-parse the List, use if html have changed + */ + this.reIndex = function() { + self.items = []; + self.visibleItems = []; + self.matchingItems = []; + self.searched = false; + self.filtered = false; + self.parse(self.list); + }; + + this.toJSON = function() { + var json = []; + for (var i = 0, il = self.items.length; i < il; i++) { + json.push(self.items[i].values()); + } + return json; + }; + + + /* + * Add object to list + */ + this.add = function(values, callback) { + if (values.length === 0) { + return; + } + if (callback) { + addAsync(values, callback); + return; + } + var added = [], + notCreate = false; + if (values[0] === undefined){ + values = [values]; + } + for (var i = 0, il = values.length; i < il; i++) { + var item = null; + notCreate = (self.items.length > self.page) ? true : false; + item = new Item(values[i], undefined, notCreate); + self.items.push(item); + added.push(item); + } + self.update(); + return added; + }; + + this.show = function(i, page) { + this.i = i; + this.page = page; + self.update(); + return self; + }; + + /* Removes object from list. + * Loops through the list and removes objects where + * property "valuename" === value + */ + this.remove = function(valueName, value, options) { + var found = 0; + for (var i = 0, il = self.items.length; i < il; i++) { + if (self.items[i].values()[valueName] == value) { + self.templater.remove(self.items[i], options); + self.items.splice(i,1); + il--; + i--; + found++; + } + } + self.update(); + return found; + }; + + /* Gets the objects in the list which + * property "valueName" === value + */ + this.get = function(valueName, value) { + var matchedItems = []; + for (var i = 0, il = self.items.length; i < il; i++) { + var item = self.items[i]; + if (item.values()[valueName] == value) { + matchedItems.push(item); + } + } + return matchedItems; + }; + + /* + * Get size of the list + */ + this.size = function() { + return self.items.length; + }; + + /* + * Removes all items from the list + */ + this.clear = function() { + self.templater.clear(); + self.items = []; + return self; + }; + + this.on = function(event, callback) { + self.handlers[event].push(callback); + return self; + }; + + this.off = function(event, callback) { + var e = self.handlers[event]; + var index = indexOf(e, callback); + if (index > -1) { + e.splice(index, 1); + } + return self; + }; + + this.trigger = function(event) { + var i = self.handlers[event].length; + while(i--) { + self.handlers[event][i](self); + } + return self; + }; + + this.reset = { + filter: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].filtered = false; + } + return self; + }, + search: function() { + var is = self.items, + il = is.length; + while (il--) { + is[il].found = false; + } + return self; + } + }; + + this.update = function() { + var is = self.items, + il = is.length; + + self.visibleItems = []; + self.matchingItems = []; + self.templater.clear(); + for (var i = 0; i < il; i++) { + if (is[i].matching() && ((self.matchingItems.length+1) >= self.i && self.visibleItems.length < self.page)) { + is[i].show(); + self.visibleItems.push(is[i]); + self.matchingItems.push(is[i]); + } else if (is[i].matching()) { + self.matchingItems.push(is[i]); + is[i].hide(); + } else { + is[i].hide(); + } + } + self.trigger('updated'); + return self; + }; + + init.start(); +}; + + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +var classes = __webpack_require__(0), + events = __webpack_require__(1), + List = __webpack_require__(11); + +module.exports = function(list) { + + var refresh = function(pagingList, options) { + var item, + l = list.matchingItems.length, + index = list.i, + page = list.page, + pages = Math.ceil(l / page), + currentPage = Math.ceil((index / page)), + innerWindow = options.innerWindow || 2, + left = options.left || options.outerWindow || 0, + right = options.right || options.outerWindow || 0; + + right = pages - right; + + pagingList.clear(); + for (var i = 1; i <= pages; i++) { + var className = (currentPage === i) ? "active" : ""; + + //console.log(i, left, right, currentPage, (currentPage - innerWindow), (currentPage + innerWindow), className); + + if (is.number(i, left, right, currentPage, innerWindow)) { + item = pagingList.add({ + page: i, + dotted: false + })[0]; + if (className) { + classes(item.elm).add(className); + } + addEvent(item.elm, i, page); + } else if (is.dotted(pagingList, i, left, right, currentPage, innerWindow, pagingList.size())) { + item = pagingList.add({ + page: "...", + dotted: true + })[0]; + classes(item.elm).add("disabled"); + } + } + }; + + var is = { + number: function(i, left, right, currentPage, innerWindow) { + return this.left(i, left) || this.right(i, right) || this.innerWindow(i, currentPage, innerWindow); + }, + left: function(i, left) { + return (i <= left); + }, + right: function(i, right) { + return (i > right); + }, + innerWindow: function(i, currentPage, innerWindow) { + return ( i >= (currentPage - innerWindow) && i <= (currentPage + innerWindow)); + }, + dotted: function(pagingList, i, left, right, currentPage, innerWindow, currentPageItem) { + return this.dottedLeft(pagingList, i, left, right, currentPage, innerWindow) || (this.dottedRight(pagingList, i, left, right, currentPage, innerWindow, currentPageItem)); + }, + dottedLeft: function(pagingList, i, left, right, currentPage, innerWindow) { + return ((i == (left + 1)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right)); + }, + dottedRight: function(pagingList, i, left, right, currentPage, innerWindow, currentPageItem) { + if (pagingList.items[currentPageItem-1].values().dotted) { + return false; + } else { + return ((i == (right)) && !this.innerWindow(i, currentPage, innerWindow) && !this.right(i, right)); + } + } + }; + + var addEvent = function(elm, i, page) { + events.bind(elm, 'click', function() { + list.show((i-1)*page + 1, page); + }); + }; + + return function(options) { + var pagingList = new List(list.listContainer.id, { + listClass: options.paginationClass || 'pagination', + item: "

  • ", + valueNames: ['page', 'dotted'], + searchClass: 'pagination-search-that-is-not-supposed-to-exist', + sortClass: 'pagination-sort-that-is-not-supposed-to-exist' + }); + + list.on('updated', function() { + refresh(pagingList, options); + }); + refresh(pagingList, options); + }; +}; + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = function(list) { + + var Item = __webpack_require__(2)(list); + + var getChildren = function(parent) { + var nodes = parent.childNodes, + items = []; + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + items.push(nodes[i]); + } + } + return items; + }; + + var parse = function(itemElements, valueNames) { + for (var i = 0, il = itemElements.length; i < il; i++) { + list.items.push(new Item(valueNames, itemElements[i])); + } + }; + var parseAsync = function(itemElements, valueNames) { + var itemsToIndex = itemElements.splice(0, 50); // TODO: If < 100 items, what happens in IE etc? + parse(itemsToIndex, valueNames); + if (itemElements.length > 0) { + setTimeout(function() { + parseAsync(itemElements, valueNames); + }, 1); + } else { + list.update(); + list.trigger('parseComplete'); + } + }; + + list.handlers.parseComplete = list.handlers.parseComplete || []; + + return function() { + var itemsToIndex = getChildren(list.list), + valueNames = list.valueNames; + + if (list.indexAsync) { + parseAsync(itemsToIndex, valueNames); + } else { + parse(itemsToIndex, valueNames); + } + }; +}; + + +/***/ }), +/* 14 */ +/***/ (function(module, exports) { + +module.exports = function(list) { + var item, + text, + columns, + searchString, + customSearch; + + var prepare = { + resetList: function() { + list.i = 1; + list.templater.clear(); + customSearch = undefined; + }, + setOptions: function(args) { + if (args.length == 2 && args[1] instanceof Array) { + columns = args[1]; + } else if (args.length == 2 && typeof(args[1]) == "function") { + columns = undefined; + customSearch = args[1]; + } else if (args.length == 3) { + columns = args[1]; + customSearch = args[2]; + } else { + columns = undefined; + } + }, + setColumns: function() { + if (list.items.length === 0) return; + if (columns === undefined) { + columns = (list.searchColumns === undefined) ? prepare.toArray(list.items[0].values()) : list.searchColumns; + } + }, + setSearchString: function(s) { + s = list.utils.toString(s).toLowerCase(); + s = s.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&"); // Escape regular expression characters + searchString = s; + }, + toArray: function(values) { + var tmpColumn = []; + for (var name in values) { + tmpColumn.push(name); + } + return tmpColumn; + } + }; + var search = { + list: function() { + for (var k = 0, kl = list.items.length; k < kl; k++) { + search.item(list.items[k]); + } + }, + item: function(item) { + item.found = false; + for (var j = 0, jl = columns.length; j < jl; j++) { + if (search.values(item.values(), columns[j])) { + item.found = true; + return; + } + } + }, + values: function(values, column) { + if (values.hasOwnProperty(column)) { + text = list.utils.toString(values[column]).toLowerCase(); + if ((searchString !== "") && (text.search(searchString) > -1)) { + return true; + } + } + return false; + }, + reset: function() { + list.reset.search(); + list.searched = false; + } + }; + + var searchMethod = function(str) { + list.trigger('searchStart'); + + prepare.resetList(); + prepare.setSearchString(str); + prepare.setOptions(arguments); // str, cols|searchFunction, searchFunction + prepare.setColumns(); + + if (searchString === "" ) { + search.reset(); + } else { + list.searched = true; + if (customSearch) { + customSearch(searchString, columns); + } else { + search.list(); + } + } + + list.update(); + list.trigger('searchComplete'); + return list.visibleItems; + }; + + list.handlers.searchStart = list.handlers.searchStart || []; + list.handlers.searchComplete = list.handlers.searchComplete || []; + + list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'keyup', function(e) { + var target = e.target || e.srcElement, // IE have srcElement + alreadyCleared = (target.value === "" && !list.searched); + if (!alreadyCleared) { // If oninput already have resetted the list, do nothing + searchMethod(target.value); + } + }); + + // Used to detect click on HTML5 clear button + list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'input', function(e) { + var target = e.target || e.srcElement; + if (target.value === "") { + searchMethod(''); + } + }); + + return searchMethod; +}; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + +module.exports = function(list) { + + var buttons = { + els: undefined, + clear: function() { + for (var i = 0, il = buttons.els.length; i < il; i++) { + list.utils.classes(buttons.els[i]).remove('asc'); + list.utils.classes(buttons.els[i]).remove('desc'); + } + }, + getOrder: function(btn) { + var predefinedOrder = list.utils.getAttribute(btn, 'data-order'); + if (predefinedOrder == "asc" || predefinedOrder == "desc") { + return predefinedOrder; + } else if (list.utils.classes(btn).has('desc')) { + return "asc"; + } else if (list.utils.classes(btn).has('asc')) { + return "desc"; + } else { + return "asc"; + } + }, + getInSensitive: function(btn, options) { + var insensitive = list.utils.getAttribute(btn, 'data-insensitive'); + if (insensitive === "false") { + options.insensitive = false; + } else { + options.insensitive = true; + } + }, + setOrder: function(options) { + for (var i = 0, il = buttons.els.length; i < il; i++) { + var btn = buttons.els[i]; + if (list.utils.getAttribute(btn, 'data-sort') !== options.valueName) { + continue; + } + var predefinedOrder = list.utils.getAttribute(btn, 'data-order'); + if (predefinedOrder == "asc" || predefinedOrder == "desc") { + if (predefinedOrder == options.order) { + list.utils.classes(btn).add(options.order); + } + } else { + list.utils.classes(btn).add(options.order); + } + } + } + }; + + var sort = function() { + list.trigger('sortStart'); + var options = {}; + + var target = arguments[0].currentTarget || arguments[0].srcElement || undefined; + + if (target) { + options.valueName = list.utils.getAttribute(target, 'data-sort'); + buttons.getInSensitive(target, options); + options.order = buttons.getOrder(target); + } else { + options = arguments[1] || options; + options.valueName = arguments[0]; + options.order = options.order || "asc"; + options.insensitive = (typeof options.insensitive == "undefined") ? true : options.insensitive; + } + + buttons.clear(); + buttons.setOrder(options); + + + // caseInsensitive + // alphabet + var customSortFunction = (options.sortFunction || list.sortFunction || null), + multi = ((options.order === 'desc') ? -1 : 1), + sortFunction; + + if (customSortFunction) { + sortFunction = function(itemA, itemB) { + return customSortFunction(itemA, itemB, options) * multi; + }; + } else { + sortFunction = function(itemA, itemB) { + var sort = list.utils.naturalSort; + sort.alphabet = list.alphabet || options.alphabet || undefined; + if (!sort.alphabet && options.insensitive) { + sort = list.utils.naturalSort.caseInsensitive; + } + return sort(itemA.values()[options.valueName], itemB.values()[options.valueName]) * multi; + }; + } + + list.items.sort(sortFunction); + list.update(); + list.trigger('sortComplete'); + }; + + // Add handlers + list.handlers.sortStart = list.handlers.sortStart || []; + list.handlers.sortComplete = list.handlers.sortComplete || []; + + buttons.els = list.utils.getByClass(list.listContainer, list.sortClass); + list.utils.events.bind(buttons.els, 'click', sort); + list.on('searchStart', buttons.clear); + list.on('filterStart', buttons.clear); + + return sort; +}; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports) { + +var Templater = function(list) { + var itemSource, + templater = this; + + var init = function() { + itemSource = templater.getItemSource(list.item); + if (itemSource) { + itemSource = templater.clearSourceItem(itemSource, list.valueNames); + } + }; + + this.clearSourceItem = function(el, valueNames) { + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm; + if (valueNames[i].data) { + for (var j = 0, jl = valueNames[i].data.length; j < jl; j++) { + el.setAttribute('data-'+valueNames[i].data[j], ''); + } + } else if (valueNames[i].attr && valueNames[i].name) { + elm = list.utils.getByClass(el, valueNames[i].name, true); + if (elm) { + elm.setAttribute(valueNames[i].attr, ""); + } + } else { + elm = list.utils.getByClass(el, valueNames[i], true); + if (elm) { + elm.innerHTML = ""; + } + } + elm = undefined; + } + return el; + }; + + this.getItemSource = function(item) { + if (item === undefined) { + var nodes = list.list.childNodes, + items = []; + + for (var i = 0, il = nodes.length; i < il; i++) { + // Only textnodes have a data attribute + if (nodes[i].data === undefined) { + return nodes[i].cloneNode(true); + } + } + } else if (/]/g.exec(item)) { + var tbody = document.createElement('tbody'); + tbody.innerHTML = item; + return tbody.firstChild; + } else if (item.indexOf("<") !== -1) { + var div = document.createElement('div'); + div.innerHTML = item; + return div.firstChild; + } else { + var source = document.getElementById(list.item); + if (source) { + return source; + } + } + return undefined; + }; + + this.get = function(item, valueNames) { + templater.create(item); + var values = {}; + for(var i = 0, il = valueNames.length; i < il; i++) { + var elm; + if (valueNames[i].data) { + for (var j = 0, jl = valueNames[i].data.length; j < jl; j++) { + values[valueNames[i].data[j]] = list.utils.getAttribute(item.elm, 'data-'+valueNames[i].data[j]); + } + } else if (valueNames[i].attr && valueNames[i].name) { + elm = list.utils.getByClass(item.elm, valueNames[i].name, true); + values[valueNames[i].name] = elm ? list.utils.getAttribute(elm, valueNames[i].attr) : ""; + } else { + elm = list.utils.getByClass(item.elm, valueNames[i], true); + values[valueNames[i]] = elm ? elm.innerHTML : ""; + } + elm = undefined; + } + return values; + }; + + this.set = function(item, values) { + var getValueName = function(name) { + for (var i = 0, il = list.valueNames.length; i < il; i++) { + if (list.valueNames[i].data) { + var data = list.valueNames[i].data; + for (var j = 0, jl = data.length; j < jl; j++) { + if (data[j] === name) { + return { data: name }; + } + } + } else if (list.valueNames[i].attr && list.valueNames[i].name && list.valueNames[i].name == name) { + return list.valueNames[i]; + } else if (list.valueNames[i] === name) { + return name; + } + } + }; + var setValue = function(name, value) { + var elm; + var valueName = getValueName(name); + if (!valueName) + return; + if (valueName.data) { + item.elm.setAttribute('data-'+valueName.data, value); + } else if (valueName.attr && valueName.name) { + elm = list.utils.getByClass(item.elm, valueName.name, true); + if (elm) { + elm.setAttribute(valueName.attr, value); + } + } else { + elm = list.utils.getByClass(item.elm, valueName, true); + if (elm) { + elm.innerHTML = value; + } + } + elm = undefined; + }; + if (!templater.create(item)) { + for(var v in values) { + if (values.hasOwnProperty(v)) { + setValue(v, values[v]); + } + } + } + }; + + this.create = function(item) { + if (item.elm !== undefined) { + return false; + } + if (itemSource === undefined) { + throw new Error("The list need to have at list one item on init otherwise you'll have to add a template."); + } + /* If item source does not exists, use the first item in list as + source for new items */ + var newItem = itemSource.cloneNode(true); + newItem.removeAttribute('id'); + item.elm = newItem; + templater.set(item, item.values()); + return true; + }; + this.remove = function(item) { + if (item.elm.parentNode === list.list) { + list.list.removeChild(item.elm); + } + }; + this.show = function(item) { + templater.create(item); + list.list.appendChild(item.elm); + }; + this.hide = function(item) { + if (item.elm !== undefined && item.elm.parentNode === list.list) { + list.list.removeChild(item.elm); + } + }; + this.clear = function() { + /* .innerHTML = ''; fucks up IE */ + if (list.list.hasChildNodes()) { + while (list.list.childNodes.length >= 1) + { + list.list.removeChild(list.list.firstChild); + } + } + }; + + init(); +}; + +module.exports = function(list) { + return new Templater(list); +}; + + +/***/ }), +/* 17 */ +/***/ (function(module, exports) { + +/** + * A cross-browser implementation of getAttribute. + * Source found here: http://stackoverflow.com/a/3755343/361337 written by Vivin Paliath + * + * Return the value for `attr` at `element`. + * + * @param {Element} el + * @param {String} attr + * @api public + */ + +module.exports = function(el, attr) { + var result = (el.getAttribute && el.getAttribute(attr)) || null; + if( !result ) { + var attrs = el.attributes; + var length = attrs.length; + for(var i = 0; i < length; i++) { + if (attr[i] !== undefined) { + if(attr[i].nodeName === attr) { + result = attr[i].nodeValue; + } + } + } + } + return result; +}; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var alphabet; +var alphabetIndexMap; +var alphabetIndexMapLength = 0; + +function isNumberCode(code) { + return code >= 48 && code <= 57; +} + +function naturalCompare(a, b) { + var lengthA = (a += '').length; + var lengthB = (b += '').length; + var aIndex = 0; + var bIndex = 0; + + while (aIndex < lengthA && bIndex < lengthB) { + var charCodeA = a.charCodeAt(aIndex); + var charCodeB = b.charCodeAt(bIndex); + + if (isNumberCode(charCodeA)) { + if (!isNumberCode(charCodeB)) { + return charCodeA - charCodeB; + } + + var numStartA = aIndex; + var numStartB = bIndex; + + while (charCodeA === 48 && ++numStartA < lengthA) { + charCodeA = a.charCodeAt(numStartA); + } + while (charCodeB === 48 && ++numStartB < lengthB) { + charCodeB = b.charCodeAt(numStartB); + } + + var numEndA = numStartA; + var numEndB = numStartB; + + while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) { + ++numEndA; + } + while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) { + ++numEndB; + } + + var difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length + if (difference) { + return difference; + } + + while (numStartA < numEndA) { + difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++); + if (difference) { + return difference; + } + } + + aIndex = numEndA; + bIndex = numEndB; + continue; + } + + if (charCodeA !== charCodeB) { + if ( + charCodeA < alphabetIndexMapLength && + charCodeB < alphabetIndexMapLength && + alphabetIndexMap[charCodeA] !== -1 && + alphabetIndexMap[charCodeB] !== -1 + ) { + return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB]; + } + + return charCodeA - charCodeB; + } + + ++aIndex; + ++bIndex; + } + + return lengthA - lengthB; +} + +naturalCompare.caseInsensitive = naturalCompare.i = function(a, b) { + return naturalCompare(('' + a).toLowerCase(), ('' + b).toLowerCase()); +}; + +Object.defineProperties(naturalCompare, { + alphabet: { + get: function() { + return alphabet; + }, + set: function(value) { + alphabet = value; + alphabetIndexMap = []; + var i = 0; + if (alphabet) { + for (; i < alphabet.length; i++) { + alphabetIndexMap[alphabet.charCodeAt(i)] = i; + } + } + alphabetIndexMapLength = alphabetIndexMap.length; + for (i = 0; i < alphabetIndexMapLength; i++) { + if (alphabetIndexMap[i] === undefined) { + alphabetIndexMap[i] = -1; + } + } + }, + }, +}); + +module.exports = naturalCompare; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports) { + +module.exports = function(text, pattern, options) { + // Aproximately where in the text is the pattern expected to be found? + var Match_Location = options.location || 0; + + //Determines how close the match must be to the fuzzy location (specified above). An exact letter match which is 'distance' characters away from the fuzzy location would score as a complete mismatch. A distance of '0' requires the match be at the exact location specified, a threshold of '1000' would require a perfect match to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. + var Match_Distance = options.distance || 100; + + // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match (of both letters and location), a threshold of '1.0' would match anything. + var Match_Threshold = options.threshold || 0.4; + + if (pattern === text) return true; // Exact match + if (pattern.length > 32) return false; // This algorithm cannot be used + + // Set starting location at beginning text and initialise the alphabet. + var loc = Match_Location, + s = (function() { + var q = {}, + i; + + for (i = 0; i < pattern.length; i++) { + q[pattern.charAt(i)] = 0; + } + + for (i = 0; i < pattern.length; i++) { + q[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); + } + + return q; + }()); + + // Compute and return the score for a match with e errors and x location. + // Accesses loc and pattern through being a closure. + + function match_bitapScore_(e, x) { + var accuracy = e / pattern.length, + proximity = Math.abs(loc - x); + + if (!Match_Distance) { + // Dodge divide by zero error. + return proximity ? 1.0 : accuracy; + } + return accuracy + (proximity / Match_Distance); + } + + var score_threshold = Match_Threshold, // Highest score beyond which we give up. + best_loc = text.indexOf(pattern, loc); // Is there a nearby exact match? (speedup) + + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length); + + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); + } + } + + // Initialise the bit arrays. + var matchmask = 1 << (pattern.length - 1); + best_loc = -1; + + var bin_min, bin_mid; + var bin_max = pattern.length + text.length; + var last_rd; + for (var d = 0; d < pattern.length; d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at this + // error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + var start = Math.max(1, loc - bin_mid + 1); + var finish = Math.min(loc + bin_mid, text.length) + pattern.length; + + var rd = Array(finish + 2); + rd[finish + 1] = (1 << d) - 1; + for (var j = finish; j >= start; j--) { + // The alphabet (s) is a sparse hash, so the following line generates + // warnings. + var charMatch = s[text.charAt(j - 1)]; + if (d === 0) { // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | + (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | + last_rd[j + 1]; + } + if (rd[j] & matchmask) { + var score = match_bitapScore_(d, j - 1); + // This match will almost certainly be better than any existing match. + // But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = Math.max(1, 2 * loc - best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + // No hope for a (better) match at greater error levels. + if (match_bitapScore_(d + 1, loc) > score_threshold) { + break; + } + last_rd = rd; + } + + return (best_loc < 0) ? false : true; +}; + + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/vendor/assets/javascripts/list.min.js b/vendor/assets/javascripts/list.min.js new file mode 100644 index 0000000..3cb2737 --- /dev/null +++ b/vendor/assets/javascripts/list.min.js @@ -0,0 +1,2 @@ +/*! List.js v1.5.0 (http://listjs.com) by Jonny Strömberg (http://javve.com) */ +var List=function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.i=function(t){return t},e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=11)}([function(t,e,r){function n(t){if(!t||!t.nodeType)throw new Error("A DOM element reference is required");this.el=t,this.list=t.classList}var i=r(4),s=/\s+/;Object.prototype.toString;t.exports=function(t){return new n(t)},n.prototype.add=function(t){if(this.list)return this.list.add(t),this;var e=this.array(),r=i(e,t);return~r||e.push(t),this.el.className=e.join(" "),this},n.prototype.remove=function(t){if(this.list)return this.list.remove(t),this;var e=this.array(),r=i(e,t);return~r&&e.splice(r,1),this.el.className=e.join(" "),this},n.prototype.toggle=function(t,e){return this.list?("undefined"!=typeof e?e!==this.list.toggle(t,e)&&this.list.toggle(t):this.list.toggle(t),this):("undefined"!=typeof e?e?this.add(t):this.remove(t):this.has(t)?this.remove(t):this.add(t),this)},n.prototype.array=function(){var t=this.el.getAttribute("class")||"",e=t.replace(/^\s+|\s+$/g,""),r=e.split(s);return""===r[0]&&r.shift(),r},n.prototype.has=n.prototype.contains=function(t){return this.list?this.list.contains(t):!!~i(this.array(),t)}},function(t,e,r){var n=window.addEventListener?"addEventListener":"attachEvent",i=window.removeEventListener?"removeEventListener":"detachEvent",s="addEventListener"!==n?"on":"",a=r(5);e.bind=function(t,e,r,i){t=a(t);for(var o=0;o0?setTimeout(function(){e(r,n,i)},1):(t.update(),n(i))};return e}},function(t,e){t.exports=function(t){return t.handlers.filterStart=t.handlers.filterStart||[],t.handlers.filterComplete=t.handlers.filterComplete||[],function(e){if(t.trigger("filterStart"),t.i=1,t.reset.filter(),void 0===e)t.filtered=!1;else{t.filtered=!0;for(var r=t.items,n=0,i=r.length;nv.page,a=new m(t[i],void 0,n),v.items.push(a),r.push(a)}return v.update(),r}},this.show=function(t,e){return this.i=t,this.page=e,v.update(),v},this.remove=function(t,e,r){for(var n=0,i=0,s=v.items.length;i-1&&r.splice(n,1),v},this.trigger=function(t){for(var e=v.handlers[t].length;e--;)v.handlers[t][e](v);return v},this.reset={filter:function(){for(var t=v.items,e=t.length;e--;)t[e].filtered=!1;return v},search:function(){for(var t=v.items,e=t.length;e--;)t[e].found=!1;return v}},this.update=function(){var t=v.items,e=t.length;v.visibleItems=[],v.matchingItems=[],v.templater.clear();for(var r=0;r=v.i&&v.visibleItems.lengthe},innerWindow:function(t,e,r){return t>=e-r&&t<=e+r},dotted:function(t,e,r,n,i,s,a){return this.dottedLeft(t,e,r,n,i,s)||this.dottedRight(t,e,r,n,i,s,a)},dottedLeft:function(t,e,r,n,i,s){return e==r+1&&!this.innerWindow(e,i,s)&&!this.right(e,n)},dottedRight:function(t,e,r,n,i,s,a){return!t.items[a-1].values().dotted&&(e==n&&!this.innerWindow(e,i,s)&&!this.right(e,n))}},a=function(e,r,n){i.bind(e,"click",function(){t.show((r-1)*n+1,n)})};return function(r){var n=new s(t.listContainer.id,{listClass:r.paginationClass||"pagination",item:"
  • ",valueNames:["page","dotted"],searchClass:"pagination-search-that-is-not-supposed-to-exist",sortClass:"pagination-sort-that-is-not-supposed-to-exist"});t.on("updated",function(){e(n,r)}),e(n,r)}}},function(t,e,r){t.exports=function(t){var e=r(2)(t),n=function(t){for(var e=t.childNodes,r=[],n=0,i=e.length;n0?setTimeout(function(){s(e,r)},1):(t.update(),t.trigger("parseComplete"))};return t.handlers.parseComplete=t.handlers.parseComplete||[],function(){var e=n(t.list),r=t.valueNames;t.indexAsync?s(e,r):i(e,r)}}},function(t,e){t.exports=function(t){var e,r,n,i,s={resetList:function(){t.i=1,t.templater.clear(),i=void 0},setOptions:function(t){2==t.length&&t[1]instanceof Array?r=t[1]:2==t.length&&"function"==typeof t[1]?(r=void 0,i=t[1]):3==t.length?(r=t[1],i=t[2]):r=void 0},setColumns:function(){0!==t.items.length&&void 0===r&&(r=void 0===t.searchColumns?s.toArray(t.items[0].values()):t.searchColumns)},setSearchString:function(e){e=t.utils.toString(e).toLowerCase(),e=e.replace(/[-[\]{}()*+?.,\\^$|#]/g,"\\$&"),n=e},toArray:function(t){var e=[];for(var r in t)e.push(r);return e}},a={list:function(){for(var e=0,r=t.items.length;e-1))},reset:function(){t.reset.search(),t.searched=!1}},o=function(e){return t.trigger("searchStart"),s.resetList(),s.setSearchString(e),s.setOptions(arguments),s.setColumns(),""===n?a.reset():(t.searched=!0,i?i(n,r):a.list()),t.update(),t.trigger("searchComplete"),t.visibleItems};return t.handlers.searchStart=t.handlers.searchStart||[],t.handlers.searchComplete=t.handlers.searchComplete||[],t.utils.events.bind(t.utils.getByClass(t.listContainer,t.searchClass),"keyup",function(e){var r=e.target||e.srcElement,n=""===r.value&&!t.searched;n||o(r.value)}),t.utils.events.bind(t.utils.getByClass(t.listContainer,t.searchClass),"input",function(t){var e=t.target||t.srcElement;""===e.value&&o("")}),o}},function(t,e){t.exports=function(t){var e={els:void 0,clear:function(){for(var r=0,n=e.els.length;r]/g.exec(e)){var s=document.createElement("tbody");return s.innerHTML=e,s.firstChild}if(e.indexOf("<")!==-1){var a=document.createElement("div");return a.innerHTML=e,a.firstChild}var o=document.getElementById(t.item);if(o)return o}},this.get=function(e,n){r.create(e);for(var i={},s=0,a=n.length;s=1;)t.list.removeChild(t.list.firstChild)},n()};t.exports=function(t){return new r(t)}},function(t,e){t.exports=function(t,e){var r=t.getAttribute&&t.getAttribute(e)||null;if(!r)for(var n=t.attributes,i=n.length,s=0;s=48&&t<=57}function i(t,e){for(var r=(t+="").length,i=(e+="").length,s=0,l=0;s32)return!1;var o=i,l=function(){var t,r={};for(t=0;t=p;b--){var w=l[t.charAt(b-1)];if(0===g?y[b]=(y[b+1]<<1|1)&w:y[b]=(y[b+1]<<1|1)&w|((v[b+1]|v[b])<<1|1)|v[b+1],y[b]&f){var x=n(g,b-1);if(x<=u){if(u=x,c=b-1,!(c>o))break;p=Math.max(1,2*o-c)}}}if(n(g+1,o)>u)break;v=y}return!(c<0)}}]); \ No newline at end of file