bullet_train 1.0.98 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f545ab368de0faa13d579c9975853610402e70257858038227ffea35a9c5de3
4
- data.tar.gz: 20b325cbc9613ac3c839a61bb12c0da93a1985da33b53f31b74912b0a750f23b
3
+ metadata.gz: 9b766f450a6f4015dc33ff340dbca7fc53957ec141989ab6d16767eb3a1346d0
4
+ data.tar.gz: cc618ee0814669b150d82213304dd51cff59f63b5e7642532965cee14012b9c1
5
5
  SHA512:
6
- metadata.gz: bfc70ebbcdfaead53736055da93818d9232ccd0dd13ebcc1b65262d965e386912f01c1594324a2ac33b01503c541faae650c3b00924f0943c5f42529400fda0f
7
- data.tar.gz: 44b8d4946279797ddcacfe251e26ec680ceb509d6f631fdbfed1b666d579d67006807b4c735fac374c262e1a383e965b6fbfda42ec150fb101272386ad257bdb
6
+ metadata.gz: e37d31a4c45c4c321b7cbadb72a0916a0ec3d66786075c56331d4582574248c5cca4875595979eb6baf3856fa11a27b962a5964a699dcc53429352ec97e2405c
7
+ data.tar.gz: 3aadef0e8b9e0a7048fb948a98f7266ec609dd5940d25b43d3f256e47d14207951f68954a80d62b6002b3d42704933360f139a47598b3266eb7cc9b25a6ab238
@@ -60,17 +60,24 @@ module Account::Invitations::ControllerBase
60
60
  # assume the user needs to create an account.
61
61
  # this is not the default for devise, but a sensible default here.
62
62
  redirect_to new_user_registration_path
63
- elsif @invitation
64
- @team = @invitation.team
65
- if @invitation.is_for?(current_user) || request.post?
66
- @invitation.accept_for(current_user)
67
- redirect_to account_dashboard_path, notice: I18n.t("invitations.notifications.welcome", team_name: @team.name)
63
+
64
+ # session[:invitation_uuid] should only be present if the user is registering for the first time.
65
+ elsif (@invitation = Invitation.find_by(uuid: session[:invitation_uuid] || params[:id]))
66
+ session.delete(:invitation_uuid) if session[:invitation_uuid].present?
67
+
68
+ if @invitation
69
+ @team = @invitation.team
70
+ if @invitation.is_for?(current_user) || request.post?
71
+ @invitation.accept_for(current_user)
72
+ redirect_to account_dashboard_path, notice: I18n.t("invitations.notifications.welcome", team_name: @team.name)
73
+ else
74
+ redirect_to account_invitation_path(@invitation.uuid)
75
+ end
68
76
  else
69
77
  redirect_to account_invitation_path(@invitation.uuid)
70
78
  end
71
79
  else
72
80
  redirect_to account_dashboard_path
73
-
74
81
  end
75
82
  end
76
83
 
@@ -33,13 +33,18 @@ module Controllers::Base
33
33
  end
34
34
  else
35
35
  respond_to do |format|
36
- # TODO we do this for now because it ensures `current_team` doesn't remain set in an invalid state.
37
- format.html { redirect_to [:account, current_user.teams.first], alert: exception.message }
36
+ format.html { redirect_to account_teams_url, alert: exception.message }
38
37
  end
39
38
  end
40
39
  end
41
40
  end
42
41
 
42
+ class_methods do
43
+ def strong_parameters_from_api
44
+ (name.gsub(regex_to_remove_controller_namespace, "Api::#{BulletTrain::Api.current_version.upcase}::") + "::StrongParameters").constantize
45
+ end
46
+ end
47
+
43
48
  # this is an ugly hack, but it's what is recommended at
44
49
  # https://github.com/plataformatec/devise/wiki/How-To:-Create-custom-layouts
45
50
  def layout_by_resource
@@ -117,4 +122,22 @@ module Controllers::Base
117
122
  )
118
123
  end
119
124
  end
125
+
126
+ def permitted_fields
127
+ []
128
+ end
129
+
130
+ def permitted_arrays
131
+ {}
132
+ end
133
+
134
+ def process_params(strong_params)
135
+ end
136
+
137
+ def delegate_json_to_api
138
+ respond_to do |format|
139
+ format.html
140
+ format.json { render "#{params[:controller].gsub(/^account\//, "api/#{BulletTrain::Api.current_version}/")}/#{params[:action]}" }
141
+ end
142
+ end
120
143
  end
@@ -19,14 +19,11 @@ module Registrations::ControllerBase
19
19
 
20
20
  # if current_user is defined, that means they were successful registering.
21
21
  if current_user
22
-
23
- # TODO i think this might be redundant. we've added a hook into `session["user_return_to"]` in the
24
- # `invitations#accept` action and that might be enough to get them where they're supposed to be after
25
- # either creating a new account or signing into an existing account.
26
- handle_outstanding_invitation
27
-
28
22
  # if the user doesn't have a team at this point, create one.
29
- unless current_user.teams.any?
23
+ # If the user is accepting an invitation, then the user's current_team is populated
24
+ # with the information attached to their invitation via `@invitation.accept_for` later on,
25
+ # so we don't have to create a default team for them here.
26
+ unless current_user.teams.any? || session[:invitation_uuid].present?
30
27
  current_user.create_default_team
31
28
  end
32
29
 
@@ -1,12 +1,6 @@
1
1
  module Sessions::ControllerBase
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included do
5
- # TODO I'm not sure why the sign-in page started throwing a `ActionController::InvalidAuthenticityToken`. I'm doing
6
- # this as a temporary workaround, but this shouldn't be here long-term.
7
- skip_before_action :verify_authenticity_token, only: [:create]
8
- end
9
-
10
4
  def pre_otp
11
5
  if (@email = params["user"]["email"].downcase.strip.presence)
12
6
  @user = User.find_by(email: @email)
@@ -27,10 +27,6 @@ module Account::FormsHelper
27
27
  string.present? ? string : nil
28
28
  end
29
29
 
30
- def id_for(form, method)
31
- [form.object.class.name, form.index, method].compact.join("_").underscore
32
- end
33
-
34
30
  def model_key(form)
35
31
  form.object.class.name.pluralize.underscore
36
32
  end
@@ -6,4 +6,13 @@ module Helpers::Base
6
6
  # This scope has an order if the SQL changes when we remove any order clause.
7
7
  scope.to_sql != scope.reorder("").to_sql
8
8
  end
9
+
10
+ # TODO This should really be in the API package and included from there.
11
+ if defined?(BulletTrain::Api)
12
+ def render_pagination(json)
13
+ if @pagy
14
+ json.has_more @pagy.has_more
15
+ end
16
+ end
17
+ end
9
18
  end
@@ -1,17 +1,2 @@
1
1
  module InvitationsHelper
2
- def handle_outstanding_invitation
3
- # was this user registering to claim an invitation?
4
- if session[:invitation_uuid].present?
5
-
6
- # try to find the invitation, if it still exists.
7
- invitation = Invitation.find_by_uuid(session[:invitation_uuid])
8
-
9
- # if the invitation was found, claim it for this user.
10
- invitation&.accept_for(current_user)
11
-
12
- # remove the uuid from the session.
13
- session.delete(:invitation_uuid)
14
-
15
- end
16
- end
17
2
  end
@@ -5,6 +5,7 @@ import BulkActionsController from './bulk_actions_controller'
5
5
  import ClipboardController from './clipboard_controller'
6
6
  import FormController from './form_controller'
7
7
  import MobileMenuController from './mobile_menu_controller'
8
+ import TextToggleController from './text_toggle_controller'
8
9
 
9
10
  export const controllerDefinitions = [
10
11
  [BulkActionFormController, 'bulk_action_form_controller.js'],
@@ -12,6 +13,7 @@ export const controllerDefinitions = [
12
13
  [ClipboardController, 'clipboard_controller.js'],
13
14
  [FormController, 'form_controller.js'],
14
15
  [MobileMenuController, 'mobile_menu_controller.js'],
16
+ [TextToggleController, 'text_toggle_controller.js'],
15
17
  ].map(function(d) {
16
18
  const key = d[1]
17
19
  const controller = d[0]
@@ -1,24 +1,26 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
+ import { enter, leave } from "el-transition"
2
3
 
3
4
  export default class extends Controller {
4
- static targets = [ "wrapper"]
5
- static classes = [ "hidden" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper
6
- static values = {
7
- showEventName: String,
8
- hideEventName: String,
9
- }
10
-
11
- toggle() {
12
- const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue
13
- if (this.isWrapperHidden) {
14
- this.showWrapper()
15
- }
16
-
17
- this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))
5
+ static targets = [ "wrapper", "revealable"]
6
+ static classes = [ "hidden" ] // necessary because we're always hiding the mobile menu on larger screens and this is the class used for only mobile screen sizes
7
+
8
+ open() {
9
+ this.showWrapper()
10
+ this.revealableTargets.forEach(revealableTarget => {
11
+ enter(revealableTarget)
12
+ })
18
13
  }
19
14
 
20
- get isWrapperHidden() {
21
- return this.wrapperTarget.classList.contains(this.hiddenClass)
15
+ close() {
16
+ Promise.all(
17
+ this.revealableTargets.map(revealableTarget => {
18
+ return leave(revealableTarget)
19
+ })
20
+ ).then(() => {
21
+ this.hideWrapper()
22
+ })
23
+
22
24
  }
23
25
 
24
26
  showWrapper() {
@@ -0,0 +1,33 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ label: String,
6
+ labelAlternate: String,
7
+ useAlternate: Boolean,
8
+ }
9
+
10
+ connect() {
11
+ this.updateLabel()
12
+ }
13
+
14
+ toggle(event) {
15
+ if (undefined !== event?.detail?.useAlternate) {
16
+ this.useAlternateValue = event.detail.useAlternate
17
+ } else {
18
+ this.useAlternateValue = !this.useAlternateValue
19
+ }
20
+ }
21
+
22
+ useAlternateValueChanged() {
23
+ this.updateLabel()
24
+ }
25
+
26
+ updateLabel() {
27
+ if (!this.hasLabelValue || !this.hasLabelAlternateValue || !this.hasUseAlternateValue) {
28
+ return
29
+ }
30
+
31
+ this.element.textContent = this.useAlternateValue === true ? this.labelAlternateValue : this.labelValue
32
+ }
33
+ }
@@ -1,3 +1,5 @@
1
+ require "rake"
2
+
1
3
  module Records::Base
2
4
  extend ActiveSupport::Concern
3
5
 
@@ -65,4 +67,29 @@ module Records::Base
65
67
  # parent_key = model_name.reflect_on_all_associations(:belongs_to).first.name
66
68
  raise "You're trying to use a feature that requires #{model_name} to have a `collection` method defined that returns the Active Record association that this model belongs to within its parent object."
67
69
  end
70
+
71
+ def seeding?
72
+ rake_tasks = ObjectSpace.each_object(Rake::Task)
73
+ return false if rake_tasks.count.zero?
74
+
75
+ db_seed_task = rake_tasks.find { |task| task.name.match?(/^db:seed$/) }
76
+ db_seed_task.already_invoked
77
+ end
78
+
79
+ # TODO This should really be in the API package and included from there.
80
+ if defined?(BulletTrain::Api)
81
+ def to_api_json
82
+ # TODO So many performance improvements available here.
83
+ controller = "Api::#{BulletTrain::Api.current_version.upcase}::ApplicationController".constantize.new
84
+ # TODO We need to fix host names here.
85
+ controller.request = ActionDispatch::Request.new({})
86
+ local_class_key = self.class.name.underscore.split("/").last.to_sym
87
+ controller.render_to_string(
88
+ "api/#{BulletTrain::Api.current_version}/#{self.class.name.underscore.pluralize}/_#{local_class_key}",
89
+ locals: {
90
+ local_class_key => self
91
+ }
92
+ )
93
+ end
94
+ end
68
95
  end
@@ -37,11 +37,8 @@ module Users::Base
37
37
  after_update :set_teams_time_zone
38
38
  end
39
39
 
40
- # TODO we need to update this to some sort of invalid email address or something
41
- # people know to ignore. it would be a security problem to have this pointing
42
- # at anybody's real email address.
43
40
  def email_is_oauth_placeholder?
44
- !!email.match(/noreply\+.*@bullettrain.co/)
41
+ !!email.match(/noreply@\h{32}\.example\.com/)
45
42
  end
46
43
 
47
44
  def label_string
@@ -42,12 +42,12 @@
42
42
  <%= form.submit t('global.buttons.sign_in'), class: 'button full' %>
43
43
  </div>
44
44
 
45
- <div class="flex items-center">
46
- <input id="remember_me" name="remember_me" type="checkbox" class="h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded">
47
- <label for="remember_me" class="ml-2 block">
48
- Remember me
49
- </label>
50
- </div>
45
+ <% if devise_mapping.rememberable? %>
46
+ <div class="flex items-center">
47
+ <%= form.check_box :remember_me, class: "h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded" %>
48
+ <%= form.label :remember_me, class: "ml-2 block" %>
49
+ </div>
50
+ <% end %>
51
51
  <% end %>
52
52
  <% end %>
53
53
  <% end %>
@@ -37,8 +37,6 @@
37
37
  <div class="h-screen md:h-auto overflow-hidden md:rounded-lg flex shadow"
38
38
  data-controller="mobile-menu"
39
39
  data-mobile-menu-hidden-class="hidden"
40
- data-mobile-menu-show-event-name-value="mobile-menu:show"
41
- data-mobile-menu-hide-event-name-value="mobile-menu:hide"
42
40
  >
43
41
 
44
42
  <% menu = capture do %>
@@ -47,7 +45,7 @@
47
45
 
48
46
  <div class="lg:hidden absolute right-0">
49
47
  <button class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
50
- data-action="reveal#hide"
48
+ data-action="mobile-menu#close"
51
49
  >
52
50
  <span class="sr-only">Close Application Menu</span>
53
51
  <svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
@@ -265,20 +263,12 @@
265
263
 
266
264
  <div class="lg:hidden hidden"
267
265
  data-mobile-menu-target="wrapper"
268
-
269
- data-controller="reveal"
270
- data-reveal-away-value="true"
271
- data-reveal-hide-keys-value="escape"
272
-
273
- data-action="mobile-menu:show->reveal#show mobile-menu:hide->reveal#hide mobile-menu-toggle->reveal#toggle reveal:hidden->mobile-menu#hideWrapper"
274
266
  >
275
267
  <div class="fixed inset-0 flex z-40">
276
268
  <button
277
- data-action="reveal#hide"
278
-
279
- hidden
280
- data-reveal
281
- data-transition
269
+ data-mobile-menu-target="revealable"
270
+ data-action="mobile-menu#close"
271
+
282
272
  data-transition-enter="transition-opacity ease-linear duration-200"
283
273
  data-transition-enter-start="opacity-0"
284
274
  data-transition-enter-end="opacity-100"
@@ -291,9 +281,8 @@
291
281
  <div class="absolute inset-0 bg-light-gradient opacity-75"></div>
292
282
  </button>
293
283
  <div
294
- hidden
295
- data-reveal
296
- data-transition
284
+ data-mobile-menu-target="revealable"
285
+
297
286
  data-transition-enter="transition ease-in-out duration-200 transform"
298
287
  data-transition-enter-start="-translate-x-full"
299
288
  data-transition-enter-end="translate-x-0"
@@ -319,7 +308,7 @@
319
308
  <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0">
320
309
 
321
310
  <button class="lg:hidden h-12 w-12 ml-1 flex-none inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue"
322
- data-action="mobile-menu#toggle"
311
+ data-action="mobile-menu#open"
323
312
  >
324
313
  <span class="sr-only">Open Application Menu</span>
325
314
  <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
@@ -1,6 +1,6 @@
1
1
  # Examples for the `super_select` Field Partial
2
2
 
3
- The `super_select` partial provides a wonderful default UI (in contrast to the vanilla browser experience for this, which is horrible) with optional search and multi-select functionality out-of-the-box. It invokes the [Select2](https://select2.org) library to provide you these features.
3
+ The `super_select` partial provides a wonderful default UI (in contrast to the vanilla browser experience for this, which is horrible) with optional search and multi-select functionality out-of-the-box. It invokes the [Select2][select2] library to provide you these features.
4
4
 
5
5
  ## Define Available Buttons via Localization Yaml
6
6
 
@@ -66,4 +66,62 @@ Here is an example allowing a new option to be entered by the user:
66
66
  other_options: {accepts_new: true} %>
67
67
  </code></pre>
68
68
 
69
- Note: this will set the option `value` (which will be submitted to the server) to the entered text.
69
+ Note: this will set the option `value` (which will be submitted to the server) to the entered text.
70
+ ## Events
71
+
72
+ All events dispatched from the `super_select` partial are [Select2's jQuery events][select2_events] re-dispatched as native DOM events with the following caveats:
73
+
74
+ 1. The native DOM event name is pre-pended with `$`
75
+ 2. The original jQuery event is passed through under `event.detail.event`
76
+
77
+ | Select2 event name | DOM event name |
78
+ |---------------------|----------------------|
79
+ | change | $change |
80
+ | select2:closing | $select2:closing |
81
+ | select2:close | $select2:close |
82
+ | select2:opening | $select2:opening |
83
+ | select2:open | $select2:open |
84
+ | select2:selecting | $select2:selecting |
85
+ | select2:select | $select2:select |
86
+ | select2:unselecting | $select2:unselecting |
87
+ | select2:unselect | $select2:unselect |
88
+ | select2:clearing | $select2:clearing |
89
+ | select2:clear | $select2:clear |
90
+
91
+ For example, catching the `$change` events in a parent `dependent-form-fields` Stimulus controller with a single `updateDependentFields` method would look like this:
92
+
93
+ <pre><code>&lt;div data-controller="dependent-form-fields"&gt;
94
+ &lt;div data-action="$change->dependent-form-fields#updateDependentFields"&gt;
95
+ <%= render 'shared/fields/super_select', form: form, method: :category_id,
96
+ choices: Category.all.map { |category| [category.label_string, category.id] } %>
97
+ &lt;/div&gt;
98
+ &lt;div&gt;
99
+ &lt;!-- This is the dependent field that would get updated when the previous one changes --&gt;
100
+ <%= render 'shared/fields/super_select', form: form, method: :category_id,
101
+ choices: Category.all.map { |category| [category.label_string, category.id] },
102
+ html_options: { data: { 'dependent-form-fields-target': 'dependentField' }} %>
103
+ &lt;/div&gt;
104
+ &lt;/div&gt;</code></pre>
105
+
106
+ And this is an example of what the `dependent-form-fields` Stimulus controller would look like.
107
+
108
+ <pre><code>
109
+ // dependent_form_fields_controller.js
110
+ import { Controller } from "stimulus"
111
+
112
+ export default class extends Controller {
113
+ static targets = [ "dependentField" ]
114
+
115
+ updateDependentFields(event) {
116
+ const originalSelect2Event = event.detail.event
117
+ console.log(`catching event ${event.type}`, originalSelect2Event)
118
+
119
+ this.dependentFieldTargets.forEach((dependentField) => {
120
+ // update dependentField based on value found in originalSelect2Event.target.value
121
+ })
122
+ }
123
+ }
124
+ </code></pre>
125
+
126
+ [select2]: https://select2.org
127
+ [select2_events]: https://select2.org/programmatic-control/events
@@ -109,23 +109,30 @@ Certain form field partials like `buttons` and `super_select` can also have thei
109
109
 
110
110
  ## Available Field Partials
111
111
 
112
- | Field Partial | Multiple Values? | Assignment Helpers | JavaScript Library | Description | Commercial License Required |
113
- | --- | --- | --- | --- | --- | --- |
114
- | `boolean` | | `assign_boolean` | | | |
115
- | [`buttons`](/docs/field-partials/buttons.md) | Optionally | `assign_checkboxes` | | | |
116
- | `cloudinary_image` | | | | | |
117
- | `color_picker` | | | [pickr](https://simonwep.github.io/pickr/) | | |
118
- | `date_and_time_field` | | `assign_date_and_time` | [Date Range Picker](https://www.daterangepicker.com) | | |
119
- | `date_field` | | `assign_date` | [Date Range Picker](https://www.daterangepicker.com) | | |
120
- | `email_field` | | | | | |
121
- | `file_field` | | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
122
- | `options` | Optionally | `assign_checkboxes` | | | |
123
- | `password_field` | | | | | |
124
- | `phone_field` | | | [International Telephone Input](https://intl-tel-input.com) | Ensures telephone numbers are in a format that can be used by providers like Twilio. | |
125
- | [`super_select`](/docs/field-partials/super-select.md) | Optionally | `assign_select_options` | [Select2](https://select2.org) | Provides powerful option search, AJAX search, and multi-select functionality. | |
126
- | `text_area` | | | | | |
127
- | `text_field` | | | | | |
128
- | `trix_editor` | | | [Trix](https://github.com/basecamp/trix) | Basic HTML-powered formatting features and support for at-mentions amongst team members. | |
112
+ | Field Partial | Data Type | Multiple Values? | Assignment Helpers | JavaScript Library | Description | Commercial License Required |
113
+ | --- | --- | --- | --- | --- | --- | --- |
114
+ | `boolean` | `boolean` | `assign_boolean` | | | |
115
+ | [`buttons`](/docs/field-partials/buttons.md) | `string` | Optionally | `assign_checkboxes` | | | |
116
+ | `cloudinary_image` | `string` | | | | | |
117
+ | `color_picker` | | | | [pickr](https://simonwep.github.io/pickr/) | | |
118
+ | `date_and_time_field` | `datetime` | | `assign_date_and_time` | [Date Range Picker](https://www.daterangepicker.com) | | |
119
+ | `date_field` | `date` | | `assign_date` | [Date Range Picker](https://www.daterangepicker.com) | | |
120
+ | `email_field` | `string` | | | | | |
121
+ | `file_field` | `attachment` | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
122
+ | `options` | `string` | Optionally | `assign_checkboxes` | | | |
123
+ | `password_field` | `string` | | | | | |
124
+ | `phone_field` | `string` | | | [International Telephone Input](https://intl-tel-input.com) | Ensures telephone numbers are in a format that can be used by providers like Twilio. | |
125
+ | [`super_select`](/docs/field-partials/super-select.md) | `string` | Optionally | `assign_select_options` | [Select2](https://select2.org) | Provides powerful option search, AJAX search, and multi-select functionality. | |
126
+ | `text_area` | `text` | | | | | |
127
+ | `text_field` | `string` | | | | | |
128
+ | `trix_editor` | `text` | | | [Trix](https://github.com/basecamp/trix) | Basic HTML-powered formatting features and support for at-mentions amongst team members. | |
129
+
130
+ ## A Note On Data Types
131
+ Set the data type to `jsonb` whenever passing the `multiple` option to a new attribute.
132
+ ```
133
+ > rails generate model Project team:references multiple_buttons:jsonb
134
+ > bin/super-scaffold crud Project Team multiple_buttons:buttons{multiple}
135
+ ```
129
136
 
130
137
  ## Additional Field Partials Documentation
131
138
  - [`buttons`](/docs/field-partials/buttons.md)
data/docs/index.md CHANGED
@@ -27,6 +27,7 @@
27
27
  - [Roles and Permissions](/docs/permissions.md)
28
28
  - [Onboarding](/docs/onboarding.md)
29
29
  - [Namespacing](/docs/namespacing.md)
30
+ - [Invitation Only Mode](/docs/invitation_only.md)
30
31
 
31
32
  ## UI
32
33
  - [Field Partials](/docs/field-partials.md)
@@ -0,0 +1,13 @@
1
+ # Invitation Only Mode
2
+
3
+ By providing a randomized string to `ENV["INVITATION_KEYS"]`, you can enable invitation only mode on your Bullet Train application. This will set up your app so that users cannot register to your website unless they have access to a specific link, or if they are invited via email.
4
+
5
+ `config/application.yml`:
6
+ ```ruby
7
+ INVITATION_KEYS: ofr9h5h9ghzeodh
8
+ ```
9
+
10
+ In this case, the user will be able to register their own account by accessing the following link:
11
+ ```
12
+ http://localhost:3000/invitations?key=ofr9h5h9ghzeodh
13
+ ```
data/docs/permissions.md CHANGED
@@ -14,5 +14,5 @@ Because abilities are being evaluated on basically every request, it made sense
14
14
  ### Naming and Labeling
15
15
  What we call a `Role` in the domain model is referred to as “Special Privileges” in the user-facing application. You can rename this to whatever you like in `config/locales/en/roles.en.yml`.
16
16
 
17
- ## Note About Pundit
17
+ ## A Note About Pundit
18
18
  There’s nothing stopping you from utilizing Pundit in a Bullet Train project for specific hard-to-implement cases in your permissions model, but you wouldn’t want to try and replace CanCanCan with it. We do too much automatically with CanCanCan for that to be recommended. That said, in those situations where there is a permission that needs to be implemented that isn’t easily implemented with CanCanCan, consider just writing vanilla Ruby code for that purpose.
@@ -34,7 +34,7 @@ rails g model Message subject:string
34
34
  rails g model Comment content:text
35
35
  ```
36
36
 
37
- Note that in this specific approach we don't need a `team:references` on `Message` and `Comment`. That's because in this approach there are no controllers specific to `Message` and `Comment`, so all permissions are being inforced by checking the ownership of `Entry`. (That's not to say it would be wrong to add them for other reasons, we're just keeping it as simple as possible here.)
37
+ Note that in this specific approach we don't need a `team:references` on `Message` and `Comment`. That's because in this approach there are no controllers specific to `Message` and `Comment`, so all permissions are being enforced by checking the ownership of `Entry`. (That's not to say it would be wrong to add them for other reasons, we're just keeping it as simple as possible here.)
38
38
 
39
39
  ### 2. Super Scaffolding for `Entry`
40
40
 
@@ -59,7 +59,33 @@ fields: &fields
59
59
  "Comment": Comment
60
60
  ```
61
61
 
62
- <small>TODO Insert a live example of what `shared/fields/buttons` looks like with these options passed in.</small>
62
+ We will add this block below in the next step on our `new.html.erb` page so you don't have to worry about it now, but with the options above in place, our buttons partial will now allow your users to select either a `Message` or a `Comment` before creating the `Entry` itself:
63
+
64
+ ```
65
+ <% with_field_settings form: form do %>
66
+ <%= render 'shared/fields/buttons', method: :entryable_type, html_options: {autofocus: true} %>
67
+ <%# 🚅 super scaffolding will insert new fields above this line. %>
68
+ <% end %>
69
+ ```
70
+
71
+ This will produce the following HTML:
72
+ ```
73
+ <div>
74
+ <label class="btn-toggle" data-controller="fields--button-toggle">
75
+ <input data-fields--button-toggle-target="shadowField" type="radio" value="Message" name="entry[entryable_type]" id="entry_entryable_type_message">
76
+ <button type="button" class="button-alternative mb-1.5 mr-1" data-action="fields--button-toggle#clickShadowField">
77
+ Message
78
+ </button>
79
+ </label>
80
+ <label class="btn-toggle" data-controller="fields--button-toggle">
81
+ <input data-fields--button-toggle-target="shadowField" type="radio" value="Comment" name="entry[entryable_type]" id="entry_entryable_type_comment">
82
+ <button type="button" class="button-alternative mb-1.5 mr-1" data-action="fields--button-toggle#clickShadowField">
83
+ Comment
84
+ </button>
85
+ </label>
86
+ </div>
87
+ ```
88
+
63
89
 
64
90
  ### 4. Add Our First Step to `new.html.erb`
65
91
 
@@ -234,9 +260,11 @@ We add this _below_ the Super Scaffolding hook because we want any additional fi
234
260
 
235
261
  ### 10. Add Attributes of the Concrete Children to `show.html.erb`
236
262
 
237
- Under the Super Scaffolding hook in `app/views/account/entries/show.html.erb`, add the following:
263
+ Add the following in `app/views/account/entries/show.html.erb` under the Super Scaffolding hook shown in the example code below:
238
264
 
239
265
  ```
266
+ <%# 🚅 super scaffolding will insert new fields above this line. %>
267
+
240
268
  <% with_attribute_settings object: @entry.entryable, strategy: :label do %>
241
269
  <% case @entry.entryable %>
242
270
  <% when Message %>
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.0.98"
2
+ VERSION = "1.1.1"
3
3
  end
data/lib/bullet_train.rb CHANGED
@@ -129,3 +129,7 @@ end
129
129
  def multiple_locales?
130
130
  @multiple_locales ||= I18n.available_locales.many?
131
131
  end
132
+
133
+ def silence_logs?
134
+ ENV["SILENCE_LOGS"].present?
135
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.98
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-25 00:00:00.000000000 Z
11
+ date: 2022-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -428,8 +428,6 @@ files:
428
428
  - README.md
429
429
  - Rakefile
430
430
  - app/assets/config/bullet_train_manifest.js
431
- - app/assets/javascripts/bullet-train.js
432
- - app/assets/javascripts/bullet-train.js.map
433
431
  - app/controllers/account/invitations_controller.rb
434
432
  - app/controllers/account/memberships_controller.rb
435
433
  - app/controllers/account/onboarding/user_details_controller.rb
@@ -477,6 +475,7 @@ files:
477
475
  - app/javascript/controllers/form_controller.js
478
476
  - app/javascript/controllers/index.js
479
477
  - app/javascript/controllers/mobile_menu_controller.js
478
+ - app/javascript/controllers/text_toggle_controller.js
480
479
  - app/javascript/electron/index.js
481
480
  - app/javascript/index.js
482
481
  - app/mailers/concerns/mailers/base.rb
@@ -609,7 +608,6 @@ files:
609
608
  - db/migrate/20211020200855_add_doorkeeper_application_to_memberships.rb
610
609
  - db/migrate/20211027002944_add_doorkeeper_application_to_users.rb
611
610
  - docs/action-models.md
612
- - docs/api.md
613
611
  - docs/authentication.md
614
612
  - docs/billing/stripe.md
615
613
  - docs/desktop.md
@@ -622,6 +620,7 @@ files:
622
620
  - docs/i18n.md
623
621
  - docs/index.md
624
622
  - docs/indirection.md
623
+ - docs/invitation_only.md
625
624
  - docs/javascript.md
626
625
  - docs/modeling.md
627
626
  - docs/namespacing.md
@@ -1,2 +0,0 @@
1
- import{Controller as e}from"@hotwired/stimulus";function t(e){const t=(e.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(t)return t.replace(/_/g,"-").replace(/\//g,"--")}class l extends e{connect(){this.updateAvailability()}updateFormAndSubmit(e){return this.recreateIdsHiddenFields(),this.createOrUpdateAllField(),!0}updateIds(e){var t;null!=e&&null!=(t=e.detail)&&t.ids&&(this.idsValue=e.detail.ids,this.allValue=e.detail.all),this.updateAvailability(),this.updateButtonLabel()}updateAvailability(){this.element.classList.toggle(this.hiddenClass,0===this.idsValue.length)}updateButtonLabel(){let e=this.buttonIfAllValue;this.idsValue.length&&!1===this.allValue&&(e=this.buttonIfIdsValue.replace("{num}",this.idsValue.length)),"INPUT"===this.buttonTarget.tagName?this.buttonTarget.value=e:this.buttonTarget.textContent=e}recreateIdsHiddenFields(){this.removeIdsHiddenFields(),this.createIdsHiddenFields()}removeIdsHiddenFields(){this.idsHiddenFieldTargets.forEach(e=>{this.element.removeChild(e)})}createIdsHiddenFields(){this.idsValue.forEach(e=>{let t=document.createElement("input");t.type="hidden",t.name=this.objectNameValue+"["+this.idsFieldNameValue+"][]",t.value=e,this.element.appendChild(t)})}createOrUpdateAllField(){this.hasAllHiddenFieldTarget?this.allHiddenFieldTarget.value=this.allValue?"true":"false":this.createAllField()}createAllField(){let e=document.createElement("input");e.type="hidden",e.name=this.objectNameValue+"["+this.allFieldNameValue+"]",e.value=this.allValue?"true":"false",this.element.appendChild(e)}}l.targets=["button","idsHiddenField","allHiddenField"],l.classes=["hidden"],l.values={buttonIfAll:String,buttonIfIds:String,ids:Array,all:Boolean,objectName:String,idsFieldName:String,allFieldName:String};class s extends e{connect(){this.element.classList.add(this.selectableAvailableClass)}toggleSelectable(){this.selectableValue=!this.selectableValue}updateSelectedIds(){this.updateActions(),this.updateSelectAllCheckbox()}updateActions(){this.actionTargets.forEach(e=>{e.dispatchEvent(new CustomEvent("update-ids",{detail:{ids:this.selectedIds,all:this.allSelected}}))})}selectAllOrNone(e){this.allSelected?this.selectNone():this.selectAll(),this.updateSelectAllCheckbox()}selectAll(){this.checkboxTargets.forEach(e=>{e.checked=!0}),this.updateActions()}selectNone(){this.checkboxTargets.forEach(e=>{e.checked=!1}),this.updateActions()}updateSelectAllCheckbox(){let e=this.selectAllCheckboxTarget,t=this.selectAllLabelTarget;this.allSelected?(e.checked=!0,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!0}}))):this.selectedIds.length>0?(e.indeterminate=!0,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}}))):(e.checked=!1,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}})))}selectableValueChanged(){this.element.classList.toggle(this.selectableClass,this.selectableValue),this.updateToggleLabel()}updateToggleLabel(){this.selectableToggleTarget.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:this.selectableValue}}))}get selectedIds(){let e=[];return this.checkboxTargets.forEach(t=>{t.checked&&e.push(t.value)}),e}get allSelected(){return this.selectedIds.length===this.checkboxTargets.length}}s.targets=["checkbox","selectAllCheckbox","action","selectableToggle","selectAllLabel"],s.classes=["selectableAvailable","selectable"],s.values={selectable:Boolean};class i extends e{copy(){this.inputTarget.value=this.sourceTarget.innerText,this.inputTarget.select(),document.execCommand("copy"),this.buttonTarget.innerHTML='<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>',setTimeout(function(){document.getElementById("copied").innerHTML='<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'},1500)}}i.targets=["source","input","button"];class a extends e{constructor(){super(...arguments),this.removeTrailingNewlines=e=>{e.element.innerHTML.match(/<br><\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-10)+"</div>",this.removeTrailingNewlines(e))},this.removeTrailingWhitespace=e=>{e.element.innerHTML.match(/&nbsp;<\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/&nbsp; <\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-13)+"</div>",this.removeTrailingWhitespace(e))}}resetOnSuccess(e){e.detail.success&&e.target.reset()}stripTrix(){this.trixFieldTargets.forEach(e=>{this.removeTrailingNewlines(e.editor),this.removeTrailingWhitespace(e.editor),e.parentElement.querySelector("input").value=e.innerHTML})}submitOnReturn(e){if((e.metaKey||e.ctrlKey)&&13==e.keyCode){e.preventDefault();let t=e.target.closest("form");this.submitForm(t)}}submitForm(e){e.requestSubmit?e.requestSubmit():e.querySelector("[type=submit]").click()}}a.targets=["trixField","scroll"];class n extends e{toggle(){const e=this.isWrapperHidden?this.showEventNameValue:this.hideEventNameValue;this.isWrapperHidden&&this.showWrapper(),this.wrapperTarget.dispatchEvent(new CustomEvent(e))}get isWrapperHidden(){return this.wrapperTarget.classList.contains(this.hiddenClass)}showWrapper(){this.wrapperTarget.classList.remove(this.hiddenClass)}hideWrapper(){this.wrapperTarget.classList.add(this.hiddenClass)}}n.targets=["wrapper"],n.classes=["hidden"],n.values={showEventName:String,hideEventName:String};const r=[[l,"bulk_action_form_controller.js"],[s,"bulk_actions_controller.js"],[i,"clipboard_controller.js"],[a,"form_controller.js"],[n,"mobile_menu_controller.js"]].map(function(e){const l=e[0];return{identifier:t(e[1]),controllerConstructor:l}});document.addEventListener("turbo:load",()=>{navigator.userAgent.toLocaleLowerCase().includes("electron")&&document.body.classList.add("electron")});export{r as controllerDefinitions};
2
- //# sourceMappingURL=bullet-train.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bullet-train.js","sources":["../../../node_modules/@hotwired/stimulus-webpack-helpers/dist/stimulus-webpack-helpers.js","../../javascript/controllers/bulk_action_form_controller.js","../../javascript/controllers/bulk_actions_controller.js","../../javascript/controllers/clipboard_controller.js","../../javascript/controllers/form_controller.js","../../javascript/controllers/mobile_menu_controller.js","../../javascript/controllers/index.js","../../javascript/electron/index.js"],"sourcesContent":["/*\nStimulus Webpack Helpers 1.0.0\nCopyright © 2021 Basecamp, LLC\n */\nfunction definitionsFromContext(context) {\n return context.keys()\n .map((key) => definitionForModuleWithContextAndKey(context, key))\n .filter((value) => value);\n}\nfunction definitionForModuleWithContextAndKey(context, key) {\n const identifier = identifierForContextKey(key);\n if (identifier) {\n return definitionForModuleAndIdentifier(context(key), identifier);\n }\n}\nfunction definitionForModuleAndIdentifier(module, identifier) {\n const controllerConstructor = module.default;\n if (typeof controllerConstructor == \"function\") {\n return { identifier, controllerConstructor };\n }\n}\nfunction identifierForContextKey(key) {\n const logicalName = (key.match(/^(?:\\.\\/)?(.+)(?:[_-]controller\\..+?)$/) || [])[1];\n if (logicalName) {\n return logicalName.replace(/_/g, \"-\").replace(/\\//g, \"--\");\n }\n}\n\nexport { definitionForModuleAndIdentifier, definitionForModuleWithContextAndKey, definitionsFromContext, identifierForContextKey };\n","import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n static targets = [ \"button\", \"idsHiddenField\", \"allHiddenField\" ]\n static classes = [ \"hidden\" ]\n static values = {\n buttonIfAll: String,\n buttonIfIds: String,\n ids: Array,\n all: Boolean,\n objectName: String,\n idsFieldName: String,\n allFieldName: String\n }\n\n connect() {\n this.updateAvailability()\n }\n\n updateFormAndSubmit(event) {\n this.recreateIdsHiddenFields()\n this.createOrUpdateAllField()\n return true\n }\n\n updateIds(event) {\n if (event?.detail?.ids) {\n this.idsValue = event.detail.ids\n this.allValue = event.detail.all\n }\n\n this.updateAvailability()\n this.updateButtonLabel()\n }\n\n updateAvailability() {\n this.element.classList.toggle(this.hiddenClass, this.idsValue.length === 0)\n }\n\n updateButtonLabel() {\n let label = this.buttonIfAllValue\n if (this.idsValue.length && this.allValue === false) {\n label = this.buttonIfIdsValue.replace('{num}', this.idsValue.length)\n }\n\n switch (this.buttonTarget.tagName) {\n case 'INPUT': this.buttonTarget.value = label; break;\n default: this.buttonTarget.textContent = label; break;\n }\n }\n\n recreateIdsHiddenFields() {\n this.removeIdsHiddenFields()\n this.createIdsHiddenFields()\n }\n\n removeIdsHiddenFields() {\n this.idsHiddenFieldTargets.forEach(field => {\n this.element.removeChild(field)\n })\n }\n\n createIdsHiddenFields() {\n this.idsValue.forEach(id => {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.idsFieldNameValue}][]`\n field.value = id\n this.element.appendChild(field)\n })\n }\n\n createOrUpdateAllField() {\n if (this.hasAllHiddenFieldTarget) {\n this.allHiddenFieldTarget.value = this.allValue? 'true': 'false'\n } else {\n this.createAllField()\n }\n }\n\n createAllField() {\n let field = document.createElement('input')\n field.type = 'hidden'\n field.name = `${this.objectNameValue}[${this.allFieldNameValue}]`\n field.value = this.allValue? 'true': 'false'\n this.element.appendChild(field)\n }\n}","import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n static targets = [ \"checkbox\", \"selectAllCheckbox\", \"action\", \"selectableToggle\", \"selectAllLabel\" ]\n static classes = [ \"selectableAvailable\", \"selectable\" ]\n static values = {\n selectable: Boolean\n }\n\n connect() {\n this.element.classList.add(this.selectableAvailableClass)\n }\n\n toggleSelectable() {\n this.selectableValue = !this.selectableValue\n }\n\n updateSelectedIds() {\n this.updateActions()\n this.updateSelectAllCheckbox()\n }\n\n updateActions() {\n this.actionTargets.forEach(actionTarget => {\n actionTarget.dispatchEvent(new CustomEvent('update-ids', { detail: {\n ids: this.selectedIds,\n all: this.allSelected\n }}))\n })\n }\n\n selectAllOrNone(event) {\n if (this.allSelected) {\n this.selectNone()\n } else {\n this.selectAll()\n }\n this.updateSelectAllCheckbox()\n }\n\n selectAll() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = true\n })\n this.updateActions()\n }\n\n selectNone() {\n this.checkboxTargets.forEach(checkbox => {\n checkbox.checked = false\n })\n this.updateActions()\n }\n\n updateSelectAllCheckbox() {\n let checkbox = this.selectAllCheckboxTarget\n let label = this.selectAllLabelTarget\n\n if (this.allSelected) {\n checkbox.checked = true\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: true }} ))\n } else if (this.selectedIds.length > 0) {\n checkbox.indeterminate = true\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n } else {\n checkbox.checked = false\n checkbox.indeterminate = false\n label.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: false }} ))\n }\n }\n\n selectableValueChanged() {\n this.element.classList.toggle(this.selectableClass, this.selectableValue)\n this.updateToggleLabel()\n }\n\n updateToggleLabel() {\n this.selectableToggleTarget.dispatchEvent(new CustomEvent('toggle', { detail: { useAlternate: this.selectableValue }} ))\n }\n\n get selectedIds() {\n let ids = []\n this.checkboxTargets.forEach(checkbox => {\n if (checkbox.checked) {\n ids.push(checkbox.value)\n }\n })\n return ids\n }\n\n get allSelected() {\n return this.selectedIds.length === this.checkboxTargets.length\n }\n}","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = ['source', 'input', 'button']\n\n copy() {\n this.inputTarget.value = this.sourceTarget.innerText\n this.inputTarget.select()\n document.execCommand('copy')\n this.buttonTarget.innerHTML = '<i id=\"copied\" class=\"fas fa-check w-4 h-4 block text-green-600\"></i>'\n setTimeout(function () {\n document.getElementById('copied').innerHTML = '<i class=\"far fa-copy w-4 h-4 block text-gray-600\"></i>'\n }, 1500)\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\n// TODO Some of this feels really specific to the conversation messages form. Should we rename this controller?\nexport default class extends Controller {\n static targets = ['trixField', 'scroll']\n\n resetOnSuccess(e){\n if(e.detail.success) {\n e.target.reset();\n }\n }\n\n stripTrix(){\n this.trixFieldTargets.forEach(element => {\n this.removeTrailingNewlines(element.editor)\n this.removeTrailingWhitespace(element.editor)\n // When doing this as part of the form submission, Trix does not update the input element's value attribute fast enough.\n // In order to submit the stripped value, we manually update it here to fix the race condition\n element.parentElement.querySelector(\"input\").value = element.innerHTML\n })\n }\n\n submitOnReturn(e) {\n if((e.metaKey || e.ctrlKey) && e.keyCode == 13) {\n e.preventDefault();\n let form = e.target.closest(\"form\")\n this.submitForm(form)\n }\n }\n\n removeTrailingNewlines = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/<br><\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -10) + \"</div>\"\n this.removeTrailingNewlines(trixEditor)\n }\n }\n\n removeTrailingWhitespace = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/&nbsp;<\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/&nbsp; <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -13) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n }\n }\n\n submitForm(form) {\n // Right now, Safari and IE don't support the requestSubmit method which is required for Turbo\n // Doing form.submit() doesn't actually fire the submit event which Turbo needs\n if (form.requestSubmit) {\n form.requestSubmit()\n } else {\n form.querySelector(\"[type=submit]\").click()\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = [ \"wrapper\"]\n static classes = [ \"hidden\" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper\n static values = {\n showEventName: String,\n hideEventName: String,\n }\n\n toggle() {\n const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue\n if (this.isWrapperHidden) {\n this.showWrapper()\n }\n \n this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))\n }\n \n get isWrapperHidden() {\n return this.wrapperTarget.classList.contains(this.hiddenClass)\n }\n \n showWrapper() {\n this.wrapperTarget.classList.remove(this.hiddenClass)\n }\n \n hideWrapper() {\n this.wrapperTarget.classList.add(this.hiddenClass)\n }\n}","import { identifierForContextKey } from \"@hotwired/stimulus-webpack-helpers\"\n\nimport BulkActionFormController from './bulk_action_form_controller'\nimport BulkActionsController from './bulk_actions_controller'\nimport ClipboardController from './clipboard_controller'\nimport FormController from './form_controller'\nimport MobileMenuController from './mobile_menu_controller'\n\nexport const controllerDefinitions = [\n [BulkActionFormController, 'bulk_action_form_controller.js'],\n [BulkActionsController, 'bulk_actions_controller.js'],\n [ClipboardController, 'clipboard_controller.js'],\n [FormController, 'form_controller.js'],\n [MobileMenuController, 'mobile_menu_controller.js'],\n].map(function(d) {\n const key = d[1]\n const controller = d[0]\n return {\n identifier: identifierForContextKey(key),\n controllerConstructor: controller\n }\n})\n","document.addEventListener(\"turbo:load\", () => {\n if (navigator.userAgent.toLocaleLowerCase().includes('electron')) {\n document.body.classList.add('electron')\n }\n})"],"names":["identifierForContextKey","key","logicalName","match","replace","_class","connect","this","updateAvailability","updateFormAndSubmit","event","recreateIdsHiddenFields","createOrUpdateAllField","updateIds","_event$detail","detail","ids","idsValue","allValue","all","updateButtonLabel","element","classList","toggle","hiddenClass","length","label","buttonIfAllValue","buttonIfIdsValue","buttonTarget","tagName","value","textContent","removeIdsHiddenFields","createIdsHiddenFields","idsHiddenFieldTargets","forEach","field","removeChild","id","document","createElement","type","name","objectNameValue","idsFieldNameValue","appendChild","hasAllHiddenFieldTarget","allHiddenFieldTarget","createAllField","allFieldNameValue","targets","classes","values","buttonIfAll","String","buttonIfIds","Array","Boolean","objectName","idsFieldName","allFieldName","Controller","add","selectableAvailableClass","toggleSelectable","selectableValue","updateSelectedIds","updateActions","updateSelectAllCheckbox","actionTargets","actionTarget","dispatchEvent","selectedIds","allSelected","selectAllOrNone","selectNone","selectAll","checkboxTargets","checkbox","checked","selectAllCheckboxTarget","selectAllLabelTarget","indeterminate","CustomEvent","useAlternate","selectableValueChanged","selectableClass","updateToggleLabel","selectableToggleTarget","push","selectable","copy","inputTarget","sourceTarget","innerText","select","execCommand","innerHTML","setTimeout","getElementById","removeTrailingNewlines","trixEditor","slice","removeTrailingWhitespace","resetOnSuccess","e","success","target","reset","stripTrix","trixFieldTargets","editor","parentElement","querySelector","submitOnReturn","metaKey","ctrlKey","keyCode","preventDefault","form","closest","submitForm","requestSubmit","click","isWrapperHidden","showEventNameValue","hideEventNameValue","showWrapper","wrapperTarget","eventName","contains","remove","hideWrapper","showEventName","hideEventName","controllerDefinitions","BulkActionFormController","BulkActionsController","ClipboardController","FormController","MobileMenuController","map","d","identifier","controllerConstructor","controller","addEventListener","navigator","userAgent","toLocaleLowerCase","includes","body"],"mappings":"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"}
data/docs/api.md DELETED
@@ -1,3 +0,0 @@
1
- # REST API
2
-
3
- > TODO This section needs to be written and should probably delegate a lot of details to the README of the [Bullet Train API repository](https://github.com/bullet-train-co/bullet_train-api).