bullet_train 1.0.97 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/concerns/account/invitations/controller_base.rb +13 -6
- data/app/controllers/concerns/controllers/base.rb +25 -2
- data/app/controllers/concerns/registrations/controller_base.rb +4 -7
- data/app/controllers/concerns/sessions/controller_base.rb +0 -6
- data/app/helpers/account/forms_helper.rb +0 -4
- data/app/helpers/concerns/helpers/base.rb +9 -0
- data/app/helpers/invitations_helper.rb +0 -15
- data/app/javascript/controllers/index.js +2 -0
- data/app/javascript/controllers/mobile_menu_controller.js +18 -16
- data/app/javascript/controllers/text_toggle_controller.js +33 -0
- data/app/models/concerns/records/base.rb +27 -0
- data/app/models/concerns/users/base.rb +1 -4
- data/app/views/devise/sessions/new.html.erb +6 -6
- data/app/views/layouts/docs.html.erb +7 -18
- data/docs/field-partials/super-select.md +60 -2
- data/docs/field-partials.md +24 -17
- data/docs/index.md +1 -0
- data/docs/invitation_only.md +13 -0
- data/docs/super-scaffolding/delegated-types.md +31 -3
- data/lib/bullet_train/version.rb +1 -1
- data/lib/bullet_train.rb +4 -0
- metadata +4 -4
- data/app/assets/javascripts/bullet-train.js +0 -2
- data/app/assets/javascripts/bullet-train.js.map +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a785a7c7fad5eb5409e133c0775eddcceb2dea5a72bd9906d21f3bb1d6e3f2c
|
4
|
+
data.tar.gz: 694e09c9486754e3337a2672407b49e48b0e9a968e25a2779b74fa086512d3b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ed9a76122fc82fb2e39cf43c21d220e6011657590514d50bf0264ae4cb7120341d52f12e439576c13f1d2b407325265bbf3394646432d78147c4dfa14e34742
|
7
|
+
data.tar.gz: 0beecd36dbcf762880fa730e65a7216c880a52e29747166657419c78ed2318151c4df53f570ecd56d383c3c390800594cd35a6717faf3e03f2dcd60e72d31dbd
|
@@ -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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
-
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
21
|
-
|
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
|
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
|
-
|
46
|
-
<
|
47
|
-
|
48
|
-
|
49
|
-
</
|
50
|
-
|
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="
|
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-
|
278
|
-
|
279
|
-
|
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
|
-
|
295
|
-
|
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#
|
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]
|
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><div data-controller="dependent-form-fields">
|
94
|
+
<div data-action="$change->dependent-form-fields#updateDependentFields">
|
95
|
+
<%= render 'shared/fields/super_select', form: form, method: :category_id,
|
96
|
+
choices: Category.all.map { |category| [category.label_string, category.id] } %>
|
97
|
+
</div>
|
98
|
+
<div>
|
99
|
+
<!-- This is the dependent field that would get updated when the previous one changes -->
|
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
|
+
</div>
|
104
|
+
</div></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
|
data/docs/field-partials.md
CHANGED
@@ -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
@@ -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
|
+
```
|
@@ -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
|
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
|
-
|
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
|
-
|
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 %>
|
data/lib/bullet_train/version.rb
CHANGED
data/lib/bullet_train.rb
CHANGED
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
|
4
|
+
version: 1.1.0
|
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-
|
11
|
+
date: 2022-09-02 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
|
@@ -622,6 +621,7 @@ files:
|
|
622
621
|
- docs/i18n.md
|
623
622
|
- docs/index.md
|
624
623
|
- docs/indirection.md
|
624
|
+
- docs/invitation_only.md
|
625
625
|
- docs/javascript.md
|
626
626
|
- docs/modeling.md
|
627
627
|
- 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(/ <\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/ <\/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(/ <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/ <\\/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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"}
|