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 +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/permissions.md +1 -1
- 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 -5
- data/app/assets/javascripts/bullet-train.js +0 -2
- data/app/assets/javascripts/bullet-train.js.map +0 -1
- data/docs/api.md +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b766f450a6f4015dc33ff340dbca7fc53957ec141989ab6d16767eb3a1346d0
|
4
|
+
data.tar.gz: cc618ee0814669b150d82213304dd51cff59f63b5e7642532965cee14012b9c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
+
```
|
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
|
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.
|
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-
|
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(/ <\/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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"}
|
data/docs/api.md
DELETED