diff --git a/.codeclimate.yml b/.codeclimate.yml index aacf2e734..e25ac86aa 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -3,4 +3,18 @@ plugins: rubocop: enabled: true channel: rubocop-1-56-3 - +exclude_patterns: +- "bin/" +- "config/" +- "db/" +- "dist/" +- "features/" +- "**/node_modules/" +- "script/" +- "**/spec/" +- "**/test/" +- "**/tests/" +- "Tests/" +- "**/vendor/" +- "**/*_test.go" +- "**/*.d.ts" diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 4543c0f7d..7b80c2d84 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -51,6 +51,7 @@ linters: - "app/views/layouts/_admin_sidebar.html.haml" - "app/views/proposals/index.html.haml" - "app/views/proposals/show.html.haml" + - "app/views/proposals/_form.html.haml" # Offense count: 28 IdNames: diff --git a/.rubocop.yml b/.rubocop.yml index b44f547da..53babd565 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,7 @@ require: - rubocop-rails - rubocop-capybara - rubocop-performance + - rubocop-factory_bot inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 48a9a2252..be9e93e84 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-02-13 04:01:02 UTC using RuboCop version 1.60.2. +# on 2024-03-12 05:45:42 UTC using RuboCop version 1.61.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -14,19 +14,66 @@ Bundler/OrderedGems: Exclude: - 'Gemfile' -# Offense count: 1 +# Offense count: 180 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: link_or_button, strict +Capybara/ClickLinkOrButtonStyle: + Enabled: false + +# Offense count: 77 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: have_no, not_to Capybara/NegationMatcher: Exclude: - - 'spec/features/versions_spec.rb' + - 'spec/features/cfp_ability_spec.rb' + - 'spec/features/code_of_conduct_spec.rb' + - 'spec/features/info_desk_ability_spec.rb' + - 'spec/features/organizer_ability_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/features/splashpage_spec.rb' + - 'spec/features/sponsor_spec.rb' + - 'spec/features/track_organizer_ability_spec.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: DefaultSelector. +Capybara/RSpec/HaveSelector: + Exclude: + - 'spec/features/cfp_ability_spec.rb' + - 'spec/features/info_desk_ability_spec.rb' + - 'spec/features/organization_admin_ability_spec.rb' + - 'spec/features/organizer_ability_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/features/sponsor_spec.rb' + - 'spec/features/track_organizer_ability_spec.rb' + - 'spec/features/voting_spec.rb' -# Offense count: 98 +# Offense count: 101 # This cop supports safe autocorrection (--autocorrect). Capybara/SpecificFinders: Enabled: false +# Offense count: 8 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, NonImplicitAssociationMethodNames. +# SupportedStyles: explicit, implicit +FactoryBot/AssociationStyle: + Exclude: + - 'spec/factories/comments.rb' + - 'spec/factories/commercials.rb' + - 'spec/factories/conferences.rb' + - 'spec/factories/surveys.rb' + - 'spec/factories/tracks.rb' + +# Offense count: 4 +# Configuration parameters: Include. +# Include: **/*_spec.rb, **/spec/**/*, **/test/**/*, **/features/support/factories/**/*.rb +FactoryBot/FactoryAssociationWithStrategy: + Exclude: + - 'spec/factories/booths.rb' + - 'spec/factories/users.rb' + # Offense count: 13 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. @@ -40,7 +87,7 @@ Lint/AmbiguousBlockAssociation: - 'spec/controllers/schedules_controller_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 5 +# Offense count: 3 # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. Lint/DuplicateBranch: Exclude: @@ -53,28 +100,28 @@ Lint/IneffectiveAccessModifier: - 'app/models/commercial.rb' - 'app/models/conference.rb' -# Offense count: 111 +# Offense count: 110 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 72 -# Offense count: 19 +# Offense count: 14 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 227 + Max: 99 # Offense count: 13 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 270 -# Offense count: 26 +# Offense count: 28 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: Max: 15 -# Offense count: 127 +# Offense count: 126 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 55 @@ -82,9 +129,9 @@ Metrics/MethodLength: # Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 168 + Max: 158 -# Offense count: 25 +# Offense count: 27 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 17 @@ -191,7 +238,7 @@ RSpec/Capybara/FeatureMethods: - 'spec/features/user_spec.rb' - 'spec/features/voting_spec.rb' -# Offense count: 338 +# Offense count: 339 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -204,7 +251,7 @@ RSpec/ContextWording: RSpec/DescribedClass: Enabled: false -# Offense count: 248 +# Offense count: 251 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 222 @@ -259,7 +306,7 @@ RSpec/IdenticalEqualityAssertion: - 'spec/controllers/admin/conferences_controller_spec.rb' # Offense count: 53 -# Configuration parameters: Max. +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. RSpec/IndexedLet: Exclude: - 'spec/controllers/admin/reports_controller_spec.rb' @@ -319,11 +366,11 @@ RSpec/MultipleDescribes: Exclude: - 'spec/models/conference_spec.rb' -# Offense count: 298 +# Offense count: 300 RSpec/MultipleExpectations: Max: 97 -# Offense count: 272 +# Offense count: 274 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 32 @@ -396,6 +443,12 @@ RSpec/Rails/InferredSpecType: - 'spec/helpers/users_helper_spec.rb' - 'spec/routing/routing_spec.rb' +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). +RSpec/ReceiveMessages: + Exclude: + - 'spec/models/track_spec.rb' + # Offense count: 4 RSpec/RepeatedDescription: Exclude: @@ -466,12 +519,12 @@ Style/CaseLikeIf: Exclude: - 'app/views/admin/events/events.xlsx.axlsx' -# Offense count: 103 +# Offense count: 107 # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false -# Offense count: 40 +# Offense count: 43 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never @@ -510,7 +563,7 @@ Style/IdenticalConditionalBranches: - 'app/controllers/admin/booths_controller.rb' - 'app/controllers/admin/events_controller.rb' -# Offense count: 32 +# Offense count: 34 # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false @@ -587,7 +640,7 @@ Style/OptionalBooleanParameter: - 'app/models/event.rb' # Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). +# This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowedCompactTypes. # SupportedStyles: compact, exploded Style/RaiseArgs: @@ -640,7 +693,7 @@ Style/WordArray: EnforcedStyle: percent MinSize: 5 -# Offense count: 260 +# Offense count: 269 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https diff --git a/Gemfile b/Gemfile index 508035858..29ba291ba 100644 --- a/Gemfile +++ b/Gemfile @@ -8,9 +8,6 @@ source 'https://rubygems.org' ruby ENV.fetch('OSEM_RUBY_VERSION', '3.2.2') -# rails-assets requires >= 1.8.4 -abort 'Bundler version >= 1.8.4 is required' if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('1.8.4') - # as web framework if next? gem 'rails', '~> 7' @@ -94,35 +91,6 @@ gem 'jquery-ui-rails', '~> 6.0.1' # for languages validation gem 'iso-639' -# frontend javascripts -source 'https://rails-assets.org' do - # transient dependencies, included here to reduce warnings. - gem 'rails-assets-bootstrap' - gem 'rails-assets-jquery' - # for placeholder images - gem 'rails-assets-holderjs' - # for formating dates - gem 'rails-assets-date.format' - # for or parsing, validating, manipulating, and formatting dates - gem 'rails-assets-momentjs' - # for smooth scrolling - gem 'rails-assets-jquery-smooth-scroll' - # as color picker - gem 'rails-assets-spectrum' - # for color manipulation - gem 'rails-assets-tinycolor' - # for drawing triangle backgrounds - gem 'rails-assets-trianglify' - # for scroll way points - gem 'rails-assets-waypoints' - # for markdown editors - gem 'rails-assets-bootstrap-markdown' - # for select with icon - gem 'rails-assets-bootstrap-select' - gem 'rails-assets-markdown' - gem 'rails-assets-to-markdown', '~> 3' -end - # as date picker gem 'bootstrap3-datetimepicker-rails', '~> 4.17.47' diff --git a/Gemfile.lock b/Gemfile.lock index e886b575a..78a4032ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,30 +7,6 @@ GIT omniauth (~> 2.0) rack -GEM - remote: https://rails-assets.org/ - specs: - rails-assets-bootstrap (3.4.1) - rails-assets-jquery (>= 1.9.1, < 4) - rails-assets-bootstrap-markdown (2.10.0) - rails-assets-bootstrap (~> 3) - rails-assets-bootstrap-select (1.13.10) - rails-assets-bootstrap (>= 3.0.0) - rails-assets-jquery (>= 1.9.1, < 4) - rails-assets-date.format (1.2.3) - rails-assets-holderjs (2.9.6) - rails-assets-jquery (3.6.0) - rails-assets-jquery-smooth-scroll (2.2.0) - rails-assets-jquery (>= 1.7.0) - rails-assets-markdown (0.5.0) - rails-assets-momentjs (2.29.4) - rails-assets-spectrum (1.8.0) - rails-assets-jquery (>= 1.7.2) - rails-assets-tinycolor (1.6.0) - rails-assets-to-markdown (3.1.1) - rails-assets-trianglify (1.2.0) - rails-assets-waypoints (4.0.1) - GEM remote: https://rubygems.org/ specs: @@ -363,7 +339,7 @@ GEM net-imap net-pop net-smtp - marcel (1.0.3) + marcel (1.0.4) matrix (0.4.2) method_source (1.0.0) mime-types (3.4.1) @@ -404,6 +380,8 @@ GEM nio4r (2.7.0) nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) + nokogiri (1.16.2-x86_64-darwin) + racc (~> 1.4) nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) @@ -576,7 +554,7 @@ GEM rspec-mocks (~> 3.12) rspec-support (~> 3.12) rspec-support (3.12.0) - rubocop (1.60.2) + rubocop (1.61.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -587,9 +565,11 @@ GEM rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) - rubocop-capybara (2.18.0) + rubocop-ast (1.31.1) + parser (>= 3.3.0.4) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) rubocop (~> 1.41) rubocop-performance (1.17.1) rubocop (>= 1.7.0, < 2.0) @@ -598,14 +578,15 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - rubocop-rspec (2.20.0) + rubocop-rspec (2.23.0) rubocop (~> 1.33) rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) ruby-oembed (0.16.1) ruby-openid (2.9.2) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) - ruby-vips (2.2.0) + ruby-vips (2.2.1) ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -657,6 +638,7 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.6.3-arm64-darwin) + sqlite3 (1.6.3-x86_64-darwin) sqlite3 (1.6.3-x86_64-linux) ssrf_filter (1.1.2) stripe (5.55.0) @@ -719,6 +701,7 @@ GEM PLATFORMS arm64-darwin-23 + x86_64-darwin-23 x86_64-linux DEPENDENCIES @@ -798,20 +781,6 @@ DEPENDENCIES pronto-rubocop puma rails (~> 7.0) - rails-assets-bootstrap! - rails-assets-bootstrap-markdown! - rails-assets-bootstrap-select! - rails-assets-date.format! - rails-assets-holderjs! - rails-assets-jquery! - rails-assets-jquery-smooth-scroll! - rails-assets-markdown! - rails-assets-momentjs! - rails-assets-spectrum! - rails-assets-tinycolor! - rails-assets-to-markdown (~> 3)! - rails-assets-trianglify! - rails-assets-waypoints! rails-controller-testing rails-i18n recaptcha diff --git a/app/assets/javascripts/osem.js b/app/assets/javascripts/osem.js index 9749a2fa6..3185a984c 100644 --- a/app/assets/javascripts/osem.js +++ b/app/assets/javascripts/osem.js @@ -49,7 +49,7 @@ $(function () { $('.' + id).collapse('hide'); $(`#event_type_${$(this).val()}-help.${id}`).collapse('show'); - $(`#event_type_${$(this).val()}-instructions.${id}`).collapse('show');w + $(`#event_type_${$(this).val()}-instructions.${id}`).collapse('show'); }); $('.dropdown-toggle').dropdown(); @@ -132,6 +132,7 @@ function get_color() { } function word_count(text, divId, maxcount) { + if (!text) { return; } var area = document.getElementById(text.id) Countable.once(area, function(counter) { @@ -162,58 +163,6 @@ function replace_defaut_submission_text(input_selector, new_text, valid_defaults }); } -/* Wait for the DOM to be ready before attaching events to the elements */ -$( document ).ready(function() { - /* Set the minimum and maximum proposal abstract word length */ - function updateEventTypeRequirements() { - var $selected = $("#event_event_type_id option:selected") - var max = $selected.data("max-words"); - var min = $selected.data("min-words"); - - // We replace the default text only if the current field is empty, - // or is set to the default text of another event type. - replace_defaut_submission_text( - '#event_submission_text', - $selected.data("instructions"), - $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) - ); - - $("#abstract-maximum-word-count").text(max); - $("#submission-maximum-word-count").text(max); - $("#abstract-minimum-word-count").text(min); - $("#submission-minimum-word-count").text(min); - word_count($('#event_abstract').get(0), 'abstract-count', max); - } - $("#event_event_type_id").change(updateEventTypeRequirements); - updateEventTypeRequirements(); - - /* Count the proposal abstract length */ - $("#event_abstract").on('input', function() { - var $selected = $("#event_event_type_id option:selected") - var max = $selected.data("max-words"); - word_count(this, 'abstract-count', max); - } ); - - /* Count the submission text length */ - $("#event_submission_text").bind('change keyup paste input', function() { - var $selected = $("event_event_type_id option:selected") - var max = $selected.data("max-words"); - word_count(this, 'submission-count', max); - }); - - /* Listen for reset template button, wait for confirm, and reset. */ - $('.js-resetSubmissionText').click((e) => { - let $selected = $("#event_event_type_id option:selected"); - let $this = $(e.target); - let affirm = confirm($this.data('confirm')); - if (affirm) { - let sub_text = $('#event_submission_text'); - sub_text.val($selected.data('instructions')); - sub_text.trigger('change'); - } - }); -}); - /* Commodity function for modal windows */ window.build_dialog = function(selector, content) { diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index ef90c670c..3eb6fd6f8 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -9,7 +9,7 @@ class ConferencesController < ApplicationController def index @current = Conference.upcoming.reorder(start_date: :asc) - @antiquated = Conference.past.select { |conf| conf.splashpage&.public? } + @antiquated = Conference.past.joins(:splashpage).where(splashpage: { public: true }).includes(:venue, :program, :splashpage, :registration_period) render :new_install if @antiquated.empty? && @current.empty? && User.empty? end diff --git a/app/models/conference.rb b/app/models/conference.rb index dcf525754..21717f7ef 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -44,9 +44,9 @@ class Conference < ApplicationRecord # Dependent destroy will fail as roles#destroy will be cancelled,hence delete_all resourcify :roles, dependent: :delete_all - default_scope { order('start_date DESC') } - scope :upcoming, -> { where('end_date >= ?', Date.current) } - scope :past, -> { where('end_date < ?', Date.current) } + default_scope { order('conferences.start_date DESC') } + scope :upcoming, -> { where('conferences.end_date >= ?', Date.current) } + scope :past, -> { where('conferences.end_date < ?', Date.current) } belongs_to :organization delegate :code_of_conduct, to: :organization diff --git a/app/views/admin/events/_form.html.haml b/app/views/admin/events/_form.html.haml index 538d842a6..ca8b6f547 100644 --- a/app/views/admin/events/_form.html.haml +++ b/app/views/admin/events/_form.html.haml @@ -28,7 +28,7 @@ = form_for(@event.commercials.new, url: conference_program_proposal_commercials_path(conference_id: @conference.short_title, proposal_id: @event)) do |f| = render 'proposals/commercial_form_fields', f: f, commercial: @event.commercials.build %hr - - @event.commercials.each_slice(3) do |slice| + - @event.commercials.includes([:versions]).each_slice(3) do |slice| .row - slice.each do |commercial| - if commercial.persisted? diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 03f5684f0..d479390e7 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -15,9 +15,13 @@ %tr %th{ width: '0' } ID %th{ width: '0' } Confirmed? - %th{ width: '0' } Email - %th{ width: '30%' } Name - %th{ width: '20%' } Conferences Attended + %th{ width: '0' } Name + %th{ width: '0' } EMail + %th{ width: '0' } Username + %th{ width: '15%' } + Conferences + %br + Attended %th{ width: '50%' } Roles %th{ width: '0' } Actions %th{ style: 'display: none' } Confirmed? diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index f73e125a4..64374c3f4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -23,6 +23,7 @@ - if @conference && @conference.custom_css %style{type: 'text/css'} = @conference.custom_css.html_safe + = yield(:script_head) %body{ class: ("conference-#{@conference.short_title}" if @conference) } = render 'layouts/navigation', conference: @conference diff --git a/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb b/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb index d35d8a1f9..7f80e5d3e 100644 --- a/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb +++ b/app/views/mailbot/young_thinkers_ticket_confirmation_template.html.erb @@ -4,13 +4,13 @@ Dear <%= @user.name %>, Thanks! You have successfully booked <%= @ticket_purchase.quantity %> <%= @ticket_purchase.ticket.title %> ticket(s) for the event <%= @conference.title %>. Your transaction id is <%= @ticket_purchase.id %>. - + Please, find the ticket(s) pdf attached. - + The SAP Young Thinkers team will reach out to you with information on how to participate in the event soon. In the meantime, you can check the event page (https://events.sap.com/yt-learning-festival-at-snapcon-2020/en/home) or send an email with your questions to youngthinkers@sap.com. - + Best wishes, - <%= @conference.title %> Team + <%= @conference.title %> Team -<%= render partial: "layouts/mailbot_footer" %> \ No newline at end of file +<%= render partial: "layouts/mailbot_footer" %> diff --git a/app/views/proposals/_form.html.haml b/app/views/proposals/_form.html.haml index 150083d4c..6d5badcb2 100644 --- a/app/views/proposals/_form.html.haml +++ b/app/views/proposals/_form.html.haml @@ -96,3 +96,55 @@ %p.text-right = f.submit @event.persisted? ? 'Update Proposal' : 'Create Proposal', class: 'btn btn-success' + +- content_for :script_head do + :javascript + /* Wait for the DOM to be ready before attaching events to the elements */ + $( document ).ready(function() { + /* Set the minimum and maximum proposal abstract word length */ + function updateEventTypeRequirements() { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + var min = $selected.data("min-words"); + + // We replace the default text only if the current field is empty, + // or is set to the default text of another event type. + replace_defaut_submission_text( + '#event_submission_text', + $selected.data("instructions"), + $("#event_event_type_id option").toArray().map(e => $(e).data('instructions')) + ); + + $("#abstract-maximum-word-count").text(max); + $("#abstract-minimum-word-count").text(min); + word_count($('#event_abstract').get(0), 'abstract-count', max); + } + $("#event_event_type_id").change(updateEventTypeRequirements); + updateEventTypeRequirements(); + + /* Count the proposal abstract length */ + $("#event_abstract").on('input', function() { + var $selected = $("#event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'abstract-count', max); + } ); + + /* Count the submission text length */ + $("#event_submission_text").bind('change keyup paste input', function() { + var $selected = $("event_event_type_id option:selected") + var max = $selected.data("max-words"); + word_count(this, 'submission-count', max); + }); + + $('.js-resetSubmissionText').click((e) => { + console.log('CLICKED') + let $selected = $("#event_event_type_id option:selected"); + let $this = $(e.target); + let affirm = confirm($this.data('confirm')); + if (affirm) { + let sub_text = $('#event_submission_text'); + sub_text.val($selected.data('instructions')); + sub_text.trigger('change'); + } + }); + }); diff --git a/db/schema.rb b/db/schema.rb index 9c9f406ca..d8361a539 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,6 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[7.0].define(version: 2024_02_26_175634) do - # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 0bc07888d..a6bb12161 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -9,6 +9,7 @@ shared_examples 'admin ability' do scenario 'edits a user', feature: true, js: true do visit admin_users_path + wait_for_ajax within "tr#user_#{user.id}" do click_on 'Edit' end diff --git a/spec/support/external_request.rb b/spec/support/external_request.rb index 45f0a5e03..6fd7af57f 100644 --- a/spec/support/external_request.rb +++ b/spec/support/external_request.rb @@ -5,9 +5,10 @@ driver_urls = Webdrivers::Common.subclasses.map do |driver| Addressable::URI.parse(driver.base_url).host end -# Local chromedriver pings GitHub. :( +# Local chromedriver pings many sites. :( driver_urls << 'googlechromelabs.github.io' driver_urls << 'edgedl.me.gvt1.com' # The fuck, Google? +driver_urls << 'storage.googleapis.com' WebMock.disable_net_connect!(allow_localhost: true, allow: [*driver_urls, /stripe.com/]) RSpec.configure do |config| diff --git a/spec/support/wait_for_ajax..rb b/spec/support/wait_for_ajax..rb new file mode 100644 index 000000000..5a524e156 --- /dev/null +++ b/spec/support/wait_for_ajax..rb @@ -0,0 +1,16 @@ +module WaitForAjax + def wait_for_ajax + Timeout.timeout(Capybara.default_max_wait_time) do + puts '...waiting...' + loop until finished_all_ajax_requests? + end + end + + def finished_all_ajax_requests? + page.evaluate_script('jQuery.active').zero? + end +end + +RSpec.configure do |config| + config.include WaitForAjax, type: :feature +end diff --git a/vendor/assets/images/holderjs/test/image.jpg b/vendor/assets/images/holderjs/test/image.jpg new file mode 100644 index 000000000..4d7fa14c9 Binary files /dev/null and b/vendor/assets/images/holderjs/test/image.jpg differ diff --git a/vendor/assets/javascripts/bootstrap-markdown.js b/vendor/assets/javascripts/bootstrap-markdown.js new file mode 100644 index 000000000..eb380aeef --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-markdown.js @@ -0,0 +1 @@ +//= require bootstrap-markdown/bootstrap-markdown.js diff --git a/vendor/assets/javascripts/bootstrap-markdown/bootstrap-markdown.js b/vendor/assets/javascripts/bootstrap-markdown/bootstrap-markdown.js new file mode 100644 index 000000000..21e8c60ea --- /dev/null +++ b/vendor/assets/javascripts/bootstrap-markdown/bootstrap-markdown.js @@ -0,0 +1,1390 @@ +/* =================================================== + * bootstrap-markdown.js v2.10.0 + * http://github.com/toopay/bootstrap-markdown + * =================================================== + * Copyright 2013-2016 Taufan Aditya + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + +(function(factory){ + if (typeof define === "function" && define.amd) { + //RequireJS + define(["jquery"], factory); + } else if (typeof exports === 'object') { + //Backbone.js + factory(require('jquery')); + } else { + //Jquery plugin + factory(jQuery); + } +}(function($){ + "use strict"; // jshint ;_; + + /* MARKDOWN CLASS DEFINITION + * ========================== */ + + var Markdown = function (element, options) { + // @TODO : remove this BC on next major release + // @see : https://github.com/toopay/bootstrap-markdown/issues/109 + var opts = ['autofocus', 'savable', 'hideable', 'width', + 'height', 'resize', 'iconlibrary', 'language', + 'footer', 'fullscreen', 'hiddenButtons', 'disabledButtons']; + $.each(opts,function(_, opt){ + if (typeof $(element).data(opt) !== 'undefined') { + options = typeof options == 'object' ? options : {} + options[opt] = $(element).data(opt) + } + }); + // End BC + + // Class Properties + this.$ns = 'bootstrap-markdown'; + this.$element = $(element); + this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}; + this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data('options')); + this.$oldContent = null; + this.$isPreview = false; + this.$isFullscreen = false; + this.$editor = null; + this.$textarea = null; + this.$handler = []; + this.$callback = []; + this.$nextTab = []; + + this.showEditor(); + }; + + Markdown.prototype = { + + constructor: Markdown + + , __alterButtons: function(name,alter) { + var handler = this.$handler, isAll = (name == 'all'),that = this; + + $.each(handler,function(k,v) { + var halt = true; + if (isAll) { + halt = false; + } else { + halt = v.indexOf(name) < 0; + } + + if (halt === false) { + alter(that.$editor.find('button[data-handler="'+v+'"]')); + } + }); + } + + , __buildButtons: function(buttonsArray, container) { + var i, + ns = this.$ns, + handler = this.$handler, + callback = this.$callback; + + for (i=0;i', { + 'class': 'btn-group' + }); + + for (z=0;z'); + buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass); + if(btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)){ + buttonContainer.removeClass('btn-default'); + } + buttonContainer.attr({ + 'type': 'button', + 'title': this.__localize(button.title) + hotkeyCaption, + 'tabindex': tabIndex, + 'data-provider': ns, + 'data-handler': buttonHandler, + 'data-hotkey': hotkey + }); + if (button.toggle === true){ + buttonContainer.attr('data-toggle', 'button'); + } + buttonIconContainer = $(''); + buttonIconContainer.addClass(buttonIcon); + buttonIconContainer.prependTo(buttonContainer); + + // Attach the button object + btnGroupContainer.append(buttonContainer); + + // Register handler and callback + handler.push(buttonHandler); + callback.push(button.callback); + } + + // Attach the button group into container dom + container.append(btnGroupContainer); + } + } + + return container; + } + , __setListener: function() { + // Set size and resizable Properties + var hasRows = typeof this.$textarea.attr('rows') !== 'undefined', + maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5', + rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows; + + this.$textarea.attr('rows',rowsVal); + if (this.$options.resize) { + this.$textarea.css('resize',this.$options.resize); + } + + this.$textarea.on({ + 'focus' : $.proxy(this.focus, this), + 'keyup' : $.proxy(this.keyup, this), + 'change' : $.proxy(this.change, this), + 'select' : $.proxy(this.select, this) + }); + + if (this.eventSupported('keydown')) { + this.$textarea.on('keydown', $.proxy(this.keydown, this)); + } + + if (this.eventSupported('keypress')) { + this.$textarea.on('keypress', $.proxy(this.keypress, this)) + } + + // Re-attach markdown data + this.$textarea.data('markdown',this); + } + + , __handle: function(e) { + var target = $(e.currentTarget), + handler = this.$handler, + callback = this.$callback, + handlerName = target.attr('data-handler'), + callbackIndex = handler.indexOf(handlerName), + callbackHandler = callback[callbackIndex]; + + // Trigger the focusin + $(e.currentTarget).focus(); + + callbackHandler(this); + + // Trigger onChange for each button handle + this.change(this); + + // Unless it was the save handler, + // focusin the textarea + if (handlerName.indexOf('cmdSave') < 0) { + this.$textarea.focus(); + } + + e.preventDefault(); + } + + , __localize: function(string) { + var messages = $.fn.markdown.messages, + language = this.$options.language; + if ( + typeof messages !== 'undefined' && + typeof messages[language] !== 'undefined' && + typeof messages[language][string] !== 'undefined' + ) { + return messages[language][string]; + } + return string; + } + + , __getIcon: function(src) { + return typeof src == 'object' ? src[this.$options.iconlibrary] : src; + } + + , setFullscreen: function(mode) { + var $editor = this.$editor, + $textarea = this.$textarea; + + if (mode === true) { + $editor.addClass('md-fullscreen-mode'); + $('body').addClass('md-nooverflow'); + this.$options.onFullscreen(this); + } else { + $editor.removeClass('md-fullscreen-mode'); + $('body').removeClass('md-nooverflow'); + + if (this.$isPreview == true) this.hidePreview().showPreview() + } + + this.$isFullscreen = mode; + $textarea.focus(); + } + + , showEditor: function() { + var instance = this, + textarea, + ns = this.$ns, + container = this.$element, + originalHeigth = container.css('height'), + originalWidth = container.css('width'), + editable = this.$editable, + handler = this.$handler, + callback = this.$callback, + options = this.$options, + editor = $( '
', { + 'class': 'md-editor', + click: function() { + instance.focus(); + } + }); + + // Prepare the editor + if (this.$editor === null) { + // Create the panel + var editorHeader = $('
', { + 'class': 'md-header btn-toolbar' + }); + + // Merge the main & additional button groups together + var allBtnGroups = []; + if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]); + if (options.additionalButtons.length > 0) { + // iterate the additional button groups + $.each(options.additionalButtons[0], function(idx, buttonGroup){ + + // see if the group name of the addional group matches an existing group + var matchingGroups = $.grep(allBtnGroups, function(allButtonGroup, allIdx){ + return allButtonGroup.name === buttonGroup.name; + }); + + // if it matches add the addional buttons to that group, if not just add it to the all buttons group + if(matchingGroups.length > 0) { + matchingGroups[0].data = matchingGroups[0].data.concat(buttonGroup.data); + } else { + allBtnGroups.push(options.additionalButtons[0][idx]); + } + + }); + } + + // Reduce and/or reorder the button groups + if (options.reorderButtonGroups.length > 0) { + allBtnGroups = allBtnGroups + .filter(function(btnGroup) { + return options.reorderButtonGroups.indexOf(btnGroup.name) > -1; + }) + .sort(function(a, b) { + if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1; + if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1; + return 0; + }); + } + + // Build the buttons + if (allBtnGroups.length > 0) { + editorHeader = this.__buildButtons([allBtnGroups], editorHeader); + } + + if (options.fullscreen.enable) { + editorHeader.append('
').on('click', '.md-control-fullscreen', function(e) { + e.preventDefault(); + instance.setFullscreen(true); + }); + } + + editor.append(editorHeader); + + // Wrap the textarea + if (container.is('textarea')) { + container.before(editor); + textarea = container; + textarea.addClass('md-input'); + editor.append(textarea); + } else { + var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(), + currentContent = $.trim(rawContent); + + // This is some arbitrary content that could be edited + textarea = $('