bullet_train 1.0.25 → 1.0.28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/helpers/base_helper.rb +6 -0
- data/app/models/concerns/current_attributes/base.rb +36 -0
- data/app/views/layouts/docs.html.erb +305 -0
- data/app/views/layouts/mailer.html.erb +65 -0
- data/app/views/public/home/docs.html.erb +1 -0
- data/app/views/user_mailer/invited.html.erb +29 -0
- data/app/views/user_mailer/welcome.html.erb +31 -0
- data/config/locales/en/base.yml +127 -0
- data/config/locales/en/doorkeeper.en.yml +152 -0
- data/config/locales/en/oauth.en.yml +15 -0
- data/config/locales/en/roles.en.yml +6 -0
- data/config/locales/en/user_mailer.en.yml +36 -0
- data/lib/bullet_train/version.rb +1 -1
- data/lib/bullet_train.rb +86 -0
- data/lib/colorizer.rb +67 -0
- data/lib/string/emoji.rb +10 -0
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9a2cb019fbf151b922e19e9ad2aec5f84ac7fd50045bd23d2ba94478c86a0dc
|
4
|
+
data.tar.gz: e63d7bb9128c48f49ee9e7b388c490173569624c2aaeb35d0c7a8d72d54c5abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbfa5d7e78cb1d62ad68966763a1c8d654c2ca6d080a3ad7a3d6e1126d0b46a44d96dcfeac454e368b5d58388dc2c5ab7f3d41122b8c03acb004e1447c2aced6
|
7
|
+
data.tar.gz: 73abfc993caaf2fbc71fccace9925bf8e897e35db76d4ef9c5cfe50495db45b33fecc924866da25a5b410e70d0a0f100da7f5a3f515cb4d681e2db68a60b1260
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CurrentAttributes::Base
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
attribute :user, :team, :membership, :ability
|
6
|
+
|
7
|
+
resets do
|
8
|
+
Time.zone = nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def user=(user)
|
13
|
+
super
|
14
|
+
|
15
|
+
if user
|
16
|
+
Time.zone = user.time_zone
|
17
|
+
self.ability = Ability.new(user)
|
18
|
+
else
|
19
|
+
Time.zone = nil
|
20
|
+
self.ability = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
update_membership
|
24
|
+
end
|
25
|
+
|
26
|
+
def team=(team)
|
27
|
+
super
|
28
|
+
update_membership
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_membership
|
32
|
+
self.membership = if user && team
|
33
|
+
user.memberships.where(team: team)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<%
|
6
|
+
# we're going to use the
|
7
|
+
body = Nokogiri::HTML(@body)
|
8
|
+
title = body.css('h1').first.text.presence || t('application.tagline')
|
9
|
+
first_paragraph = body.css('p').first
|
10
|
+
preceding_heading = first_paragraph&.xpath("preceding-sibling::h2")
|
11
|
+
description = [preceding_heading&.text, first_paragraph&.text].select(&:present?).join(" — ") || t('application.description')
|
12
|
+
image_url = "https://avatars.githubusercontent.com/u/5976880?s=280&v=4"
|
13
|
+
site_name = "Bullet Train Developer Documentation"
|
14
|
+
%>
|
15
|
+
|
16
|
+
<% content_for :title do %>
|
17
|
+
<title><%= [site_name, title, t('application.tagline')].select(&:present?).uniq.first(2).reverse.join(' — ') %></title>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<%= render 'themes/light/layouts/head' %>
|
21
|
+
<meta content="<%= t('application.keywords') %>" name="keywords" />
|
22
|
+
<meta content="<%= description.truncate(200) %>" name="description" />
|
23
|
+
<meta name="twitter:image:src" content="<%= image_url %>" />
|
24
|
+
<meta name="twitter:site" content="@bullettrainco" />
|
25
|
+
<meta name="twitter:card" content="summary" />
|
26
|
+
<meta name="twitter:title" content="<%= title.truncate(70) %>" />
|
27
|
+
<meta name="twitter:description" content="<%= description.truncate(160) %>" />
|
28
|
+
<meta property="og:image" content="<%= image_url %>" />
|
29
|
+
<meta property="og:site_name" content="<%= site_name %>" />
|
30
|
+
<meta property="og:type" content="object" />
|
31
|
+
<meta property="og:title" content="<%= title.truncate(95) %>" />
|
32
|
+
<meta property="og:url" content="<%= request.base_url + request.path %>" />
|
33
|
+
<meta property="og:description" content="<%= description.truncate(200) %>" />
|
34
|
+
</head>
|
35
|
+
<body class="bg-light-blue-gradient text-gray-700 text-sm font-normal dark:bg-dark-blue-gradient dark:text-sealBlue-900">
|
36
|
+
<div class="md:p-5">
|
37
|
+
<div class="h-screen md:h-auto overflow-hidden md:rounded-lg flex shadow"
|
38
|
+
data-controller="mobile-menu"
|
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
|
+
>
|
43
|
+
|
44
|
+
<% menu = capture do %>
|
45
|
+
<div class="flex items-center flex-shrink-0 p-4 bg-blue-darker md:rounded-tl-lg">
|
46
|
+
<%= image_tag image_path("light/logo/logo.png"), class: 'h-5 w-auto mx-auto' %>
|
47
|
+
|
48
|
+
<div class="lg:hidden absolute right-0">
|
49
|
+
<button class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
50
|
+
data-action="reveal#hide"
|
51
|
+
>
|
52
|
+
<span class="sr-only">Close Application Menu</span>
|
53
|
+
<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">
|
54
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
55
|
+
</svg>
|
56
|
+
</button>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
<nav class="flex-1 space-y-1 overflow-y-auto">
|
61
|
+
<div class="px-5 py-4" id="menu">
|
62
|
+
|
63
|
+
<%= render 'account/shared/menu/section', title: 'Introduction' do %>
|
64
|
+
<%= render 'account/shared/menu/item', url: '/docs', label: 'Table of Contents' do |p| %>
|
65
|
+
<% p.content_for :icon do %>
|
66
|
+
<i class="fal fa-home-lg-alt ti ti-list"></i>
|
67
|
+
<% end %>
|
68
|
+
<% end %>
|
69
|
+
|
70
|
+
<%= render 'account/shared/menu/item', url: '/docs/getting-started', label: 'Getting Started' do |p| %>
|
71
|
+
<% p.content_for :icon do %>
|
72
|
+
<i class="fal fa-terminal ti ti-flag"></i>
|
73
|
+
<% end %>
|
74
|
+
<% end %>
|
75
|
+
|
76
|
+
<%= render 'account/shared/menu/item', url: '/docs/philosophy', label: 'Philosophy' do |p| %>
|
77
|
+
<% p.content_for :icon do %>
|
78
|
+
<i class="fal fa-lightbulb-on ti ti-light-bulb"></i>
|
79
|
+
<% end %>
|
80
|
+
<% end %>
|
81
|
+
|
82
|
+
<%= render 'account/shared/menu/item', url: '/docs/upgrades', label: 'Upgrades' do |p| %>
|
83
|
+
<% p.content_for :icon do %>
|
84
|
+
<i class="fal fa-sparkles ti ti-arrow-up"></i>
|
85
|
+
<% end %>
|
86
|
+
<% end %>
|
87
|
+
|
88
|
+
<%= render 'account/shared/menu/item', url: '/docs/tunneling', label: 'Tunneling' do |p| %>
|
89
|
+
<% p.content_for :icon do %>
|
90
|
+
<i class="fal fa-bolt ti ti-bolt"></i>
|
91
|
+
<% end %>
|
92
|
+
<% end %>
|
93
|
+
<% end %>
|
94
|
+
|
95
|
+
<%= render 'account/shared/menu/section', title: 'Developer Tools' do %>
|
96
|
+
<%= render 'account/shared/menu/item', url: '/docs/super-scaffolding', label: 'Super Scaffolding' do |p| %>
|
97
|
+
<% p.content_for :icon do %>
|
98
|
+
<i class="fal fa-magic ti ti-wand"></i>
|
99
|
+
<% end %>
|
100
|
+
<% end %>
|
101
|
+
|
102
|
+
<%= render 'account/shared/menu/item', url: '/docs/field-partials', label: 'Field Partials' do |p| %>
|
103
|
+
<% p.content_for :icon do %>
|
104
|
+
<i class="fal fa-i-cursor ti ti-text"></i>
|
105
|
+
<% end %>
|
106
|
+
<% end %>
|
107
|
+
|
108
|
+
<%= render 'account/shared/menu/item', url: '/docs/themes', label: 'Themes' do |p| %>
|
109
|
+
<% p.content_for :icon do %>
|
110
|
+
<i class="fal fa-swatchbook ti ti-palette"></i>
|
111
|
+
<% end %>
|
112
|
+
<% end %>
|
113
|
+
|
114
|
+
<%= render 'account/shared/menu/item', url: '/docs/seeds', label: 'Database Seeds' do |p| %>
|
115
|
+
<% p.content_for :icon do %>
|
116
|
+
<i class="fal fa-seedling ti ti-server"></i>
|
117
|
+
<% end %>
|
118
|
+
<% end %>
|
119
|
+
|
120
|
+
<%= render 'account/shared/menu/item', url: '/docs/testing', label: 'Test Suite' do |p| %>
|
121
|
+
<% p.content_for :icon do %>
|
122
|
+
<i class="fal fa-check ti ti-check"></i>
|
123
|
+
<% end %>
|
124
|
+
<% end %>
|
125
|
+
<% end %>
|
126
|
+
|
127
|
+
<%= render 'account/shared/menu/section', title: 'Accounts & Teams' do %>
|
128
|
+
<%= render 'account/shared/menu/item', url: '/docs/authentication', label: 'Authentication' do |p| %>
|
129
|
+
<% p.content_for :icon do %>
|
130
|
+
<i class="fal fa-fingerprint ti ti-id-badge"></i>
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
133
|
+
|
134
|
+
<%= render 'account/shared/menu/item', url: '/docs/teams', label: 'Teams' do |p| %>
|
135
|
+
<% p.content_for :icon do %>
|
136
|
+
<i class="fal fa-users ti ti-user"></i>
|
137
|
+
<% end %>
|
138
|
+
<% end %>
|
139
|
+
|
140
|
+
<%= render 'account/shared/menu/item', url: '/docs/permissions', label: 'Roles & Permissions' do |p| %>
|
141
|
+
<% p.content_for :icon do %>
|
142
|
+
<i class="fal fa-lock-alt ti ti-lock"></i>
|
143
|
+
<% end %>
|
144
|
+
<% end %>
|
145
|
+
|
146
|
+
<%= render 'account/shared/menu/item', url: '/docs/onboarding', label: 'Onboarding' do |p| %>
|
147
|
+
<% p.content_for :icon do %>
|
148
|
+
<i class="fal fa-snowboarding ti ti-direction"></i>
|
149
|
+
<% end %>
|
150
|
+
<% end %>
|
151
|
+
|
152
|
+
<%= render 'account/shared/menu/item', url: '/docs/namespacing', label: 'Namespacing' do |p| %>
|
153
|
+
<% p.content_for :icon do %>
|
154
|
+
<i class="fal fa-object-group ti ti-widgetized"></i>
|
155
|
+
<% end %>
|
156
|
+
<% end %>
|
157
|
+
<% end %>
|
158
|
+
|
159
|
+
<%= render 'account/shared/menu/section', title: 'Billing' do %>
|
160
|
+
<%= render 'account/shared/menu/item', url: '/docs/billing/stripe', label: 'Stripe' do |p| %>
|
161
|
+
<% p.content_for :icon do %>
|
162
|
+
<i class="fab fa-stripe-s ti ti-money"></i>
|
163
|
+
<% end %>
|
164
|
+
<% end %>
|
165
|
+
<% end %>
|
166
|
+
|
167
|
+
<%= render 'account/shared/menu/section', title: 'Integration' do %>
|
168
|
+
<%= render 'account/shared/menu/item', url: '/docs/oauth', label: 'OAuth Providers' do |p| %>
|
169
|
+
<% p.content_for :icon do %>
|
170
|
+
<i class="fal fa-at ti ti-reload"></i>
|
171
|
+
<% end %>
|
172
|
+
<% end %>
|
173
|
+
|
174
|
+
<%= render 'account/shared/menu/item', url: '/docs/api', label: 'REST API' do |p| %>
|
175
|
+
<% p.content_for :icon do %>
|
176
|
+
<i class="fal fa-brackets-curly ti ti-settings"></i>
|
177
|
+
<% end %>
|
178
|
+
<% end if false %>
|
179
|
+
|
180
|
+
<%= render 'account/shared/menu/item', url: '/docs/webhooks/outgoing', label: 'Outgoing Webhooks' do |p| %>
|
181
|
+
<% p.content_for :icon do %>
|
182
|
+
<i class="fal fa-outlet ti ti-pulse"></i>
|
183
|
+
<% end %>
|
184
|
+
<% end if false %>
|
185
|
+
|
186
|
+
<%= render 'account/shared/menu/item', url: '/docs/webhooks/incoming', label: 'Incoming Webhooks' do |p| %>
|
187
|
+
<% p.content_for :icon do %>
|
188
|
+
<i class="fal fa-plug ti ti-plug"></i>
|
189
|
+
<% end %>
|
190
|
+
<% end if false %>
|
191
|
+
<% end %>
|
192
|
+
|
193
|
+
<%= render 'account/shared/menu/section', title: 'Internationalization' do %>
|
194
|
+
<%= render 'account/shared/menu/item', url: '/docs/i18n', label: 'Translations' do |p| %>
|
195
|
+
<% p.content_for :icon do %>
|
196
|
+
<i class="fal fa-language ti ti-world"></i>
|
197
|
+
<% end %>
|
198
|
+
<% end %>
|
199
|
+
<% end if false %>
|
200
|
+
|
201
|
+
<%= render 'account/shared/menu/section', title: 'Add-Ons' do %>
|
202
|
+
<%= render 'account/shared/menu/item', url: '/docs/font-awesome-pro', label: 'Font Awesome Pro' do |p| %>
|
203
|
+
<% p.content_for :icon do %>
|
204
|
+
<i class="fal fa-flag ti ti-flag"></i>
|
205
|
+
<% end %>
|
206
|
+
<% end %>
|
207
|
+
<% end %>
|
208
|
+
|
209
|
+
<%= render 'account/shared/menu/section', title: 'Deployment' do %>
|
210
|
+
<%= render 'account/shared/menu/item', url: '/docs/heroku', label: 'Heroku' do |p| %>
|
211
|
+
<% p.content_for :icon do %>
|
212
|
+
<i class="fal fa-cloud ti ti-cloud-up"></i>
|
213
|
+
<% end %>
|
214
|
+
<% end %>
|
215
|
+
|
216
|
+
<%= render 'account/shared/menu/item', url: '/docs/desktop', label: 'Desktop Applications' do |p| %>
|
217
|
+
<% p.content_for :icon do %>
|
218
|
+
<i class="fal fa-window-restore ti ti-desktop"></i>
|
219
|
+
<% end %>
|
220
|
+
<% end %>
|
221
|
+
<% end %>
|
222
|
+
</div>
|
223
|
+
</nav>
|
224
|
+
<% end %>
|
225
|
+
|
226
|
+
<div class="lg:hidden hidden"
|
227
|
+
data-mobile-menu-target="wrapper"
|
228
|
+
|
229
|
+
data-controller="reveal"
|
230
|
+
data-reveal-away-value="true"
|
231
|
+
data-reveal-hide-keys-value="escape"
|
232
|
+
|
233
|
+
data-action="mobile-menu:show->reveal#show mobile-menu:hide->reveal#hide mobile-menu-toggle->reveal#toggle reveal:hidden->mobile-menu#hideWrapper"
|
234
|
+
>
|
235
|
+
<div class="fixed inset-0 flex z-40">
|
236
|
+
<button
|
237
|
+
data-action="reveal#hide"
|
238
|
+
|
239
|
+
hidden
|
240
|
+
data-reveal
|
241
|
+
data-transition
|
242
|
+
data-transition-enter="transition-opacity ease-linear duration-200"
|
243
|
+
data-transition-enter-start="opacity-0"
|
244
|
+
data-transition-enter-end="opacity-100"
|
245
|
+
data-transition-leave="transition-opacity ease-linear duration-200"
|
246
|
+
data-transition-leave-start="opacity-100"
|
247
|
+
data-transition-leave-end="opacity-0"
|
248
|
+
|
249
|
+
class="fixed inset-0" aria-hidden="true"
|
250
|
+
>
|
251
|
+
<div class="absolute inset-0 bg-light-blue-gradient opacity-75"></div>
|
252
|
+
</button>
|
253
|
+
<div
|
254
|
+
hidden
|
255
|
+
data-reveal
|
256
|
+
data-transition
|
257
|
+
data-transition-enter="transition ease-in-out duration-200 transform"
|
258
|
+
data-transition-enter-start="-translate-x-full"
|
259
|
+
data-transition-enter-end="translate-x-0"
|
260
|
+
data-transition-leave="transition ease-in-out duration-200 transform"
|
261
|
+
data-transition-leave-start="translate-x-0"
|
262
|
+
data-transition-leave-end="-translate-x-full"
|
263
|
+
|
264
|
+
class="relative flex-1 flex flex-col max-w-xs w-full shadow-xl bg-gradient-to-b from-vividBlue-700 to-vividBlue-800 dark:from-sealBlue-200 dark:to-sealBlue-200"
|
265
|
+
>
|
266
|
+
<%= menu %>
|
267
|
+
</div>
|
268
|
+
<div class="flex-shrink-0 w-14" aria-hidden="true"></div>
|
269
|
+
</div>
|
270
|
+
</div>
|
271
|
+
|
272
|
+
<div class="hidden lg:flex lg:flex-shrink-0 bg-gradient-to-b from-vividBlue-700 to-vividBlue-800 dark:from-sealBlue-200 dark:to-sealBlue-200">
|
273
|
+
<div class="w-64">
|
274
|
+
<%= menu %>
|
275
|
+
</div>
|
276
|
+
</div>
|
277
|
+
|
278
|
+
<div class="flex flex-col w-0 flex-1 overflow-hidden bg-gray-100 dark:bg-sealBlue-200 lg:border-l dark:border-gray-500">
|
279
|
+
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0">
|
280
|
+
|
281
|
+
<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"
|
282
|
+
data-action="mobile-menu#toggle"
|
283
|
+
>
|
284
|
+
<span class="sr-only">Open Application Menu</span>
|
285
|
+
<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">
|
286
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
287
|
+
</svg>
|
288
|
+
</button>
|
289
|
+
|
290
|
+
<div class="py-2 px-1">
|
291
|
+
<div class="mx-auto sm:px-6 sm:py-4 px-2">
|
292
|
+
<div class="bg-white rounded-md shadow sm:py-14 sm:px-12 py-10 px-7">
|
293
|
+
<div class="prose" style="max-width: none;">
|
294
|
+
<%= yield %>
|
295
|
+
</div>
|
296
|
+
</div>
|
297
|
+
|
298
|
+
</div>
|
299
|
+
</div>
|
300
|
+
</main>
|
301
|
+
</div>
|
302
|
+
</div>
|
303
|
+
</div>
|
304
|
+
</body>
|
305
|
+
</html>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<head>
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
6
|
+
<title>Welcome to <%= t('application.name') %>!</title>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<% if content_for? :preheader %>
|
10
|
+
<span class="preheader"><% yield :preheader %></span>
|
11
|
+
<% end %>
|
12
|
+
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0">
|
13
|
+
<tr>
|
14
|
+
<td align="center">
|
15
|
+
<table class="email-content" width="100%" cellpadding="0" cellspacing="0">
|
16
|
+
<tr>
|
17
|
+
<td class="email-masthead">
|
18
|
+
<a href="<%= root_url %>" class="email-masthead_name">
|
19
|
+
<%= email_image_tag("light/logo/logo.png", width: image_width_for_height('light/logo/logo.png', 50), height: 50, style: "width: #{image_width_for_height('light/logo/logo.png', 50)}px; height: 50px;") %>
|
20
|
+
</a>
|
21
|
+
</td>
|
22
|
+
</tr>
|
23
|
+
<!-- Email Body -->
|
24
|
+
<tr>
|
25
|
+
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
26
|
+
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0">
|
27
|
+
<!-- Body content -->
|
28
|
+
<tr>
|
29
|
+
<td class="content-cell">
|
30
|
+
|
31
|
+
<%= yield %>
|
32
|
+
|
33
|
+
<% if false %>
|
34
|
+
<!-- Sub copy -->
|
35
|
+
<table class="body-sub">
|
36
|
+
<tr>
|
37
|
+
<td>
|
38
|
+
<p class="sub"><%= t('.trouble') %></p>
|
39
|
+
<p class="sub">{{action_url}}</p>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
</table>
|
43
|
+
<% end %>
|
44
|
+
</td>
|
45
|
+
</tr>
|
46
|
+
</table>
|
47
|
+
</td>
|
48
|
+
</tr>
|
49
|
+
<tr>
|
50
|
+
<td>
|
51
|
+
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0">
|
52
|
+
<tr>
|
53
|
+
<td class="content-cell" align="center">
|
54
|
+
<p class="sub align-center">© <%= t('.all_rights_reserved', year: Date.today.year, product_name: t('application.name')) %></p>
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
</table>
|
58
|
+
</td>
|
59
|
+
</tr>
|
60
|
+
</table>
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
</table>
|
64
|
+
</body>
|
65
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @body = markdown(File.read(Rails.root.to_s + "/docs/#{@file}.md").gsub('.md)', ')')) %>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<% content_for :preheader do %>
|
2
|
+
<%= t('.preview', @values) %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<h1><%= t('.heading', @values) %></h1>
|
6
|
+
<%= t('.body.html', @values) %>
|
7
|
+
|
8
|
+
<!-- Action -->
|
9
|
+
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
10
|
+
<tr>
|
11
|
+
<td align="center">
|
12
|
+
<!-- Border based button https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
13
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
14
|
+
<tr>
|
15
|
+
<td align="center">
|
16
|
+
<table border="0" cellspacing="0" cellpadding="0">
|
17
|
+
<tr>
|
18
|
+
<td>
|
19
|
+
<a href="<%= @cta_url %>" class="button button--" target="_blank"><%= t('.cta.label', @values) %></a>
|
20
|
+
</td>
|
21
|
+
</tr>
|
22
|
+
</table>
|
23
|
+
</td>
|
24
|
+
</tr>
|
25
|
+
</table>
|
26
|
+
</td>
|
27
|
+
</tr>
|
28
|
+
</table>
|
29
|
+
<%= t('.signature.html', @values.merge({support_email: t('application.support_email')})) %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<% content_for :preheader do %>
|
2
|
+
<%= t('.preview', @values) %>
|
3
|
+
<% end %>
|
4
|
+
|
5
|
+
<h1><%= t('.heading', @values) %></h1>
|
6
|
+
<%= t('.body.html', @values) %>
|
7
|
+
|
8
|
+
<h2><%= t('.cta.heading', @values) %></h2>
|
9
|
+
<p><%= t('.cta.body', @values) %></p>
|
10
|
+
<!-- Action -->
|
11
|
+
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
12
|
+
<tr>
|
13
|
+
<td align="center">
|
14
|
+
<!-- Border based button https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
15
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
16
|
+
<tr>
|
17
|
+
<td align="center">
|
18
|
+
<table border="0" cellspacing="0" cellpadding="0">
|
19
|
+
<tr>
|
20
|
+
<td>
|
21
|
+
<a href="<%= @cta_url %>" class="button button--" target="_blank"><%= t('.cta.label', @values) %></a>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
</table>
|
25
|
+
</td>
|
26
|
+
</tr>
|
27
|
+
</table>
|
28
|
+
</td>
|
29
|
+
</tr>
|
30
|
+
</table>
|
31
|
+
<%= t('.signature.html', @values.merge({support_email: t('application.support_email')})) %>
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Files in the config/locales directory are used for internationalization
|
2
|
+
# and are automatically loaded by Rails. If you want to use locales other
|
3
|
+
# than English, add the necessary files in this directory.
|
4
|
+
#
|
5
|
+
# To use the locales, use `I18n.t`:
|
6
|
+
#
|
7
|
+
# I18n.t "hello"
|
8
|
+
#
|
9
|
+
# In views, this is aliased to just `t`:
|
10
|
+
#
|
11
|
+
# <%= t("hello") %>
|
12
|
+
#
|
13
|
+
# To use a different locale, set it with `I18n.locale`:
|
14
|
+
#
|
15
|
+
# I18n.locale = :es
|
16
|
+
#
|
17
|
+
# This would use the information in config/locales/es.yml.
|
18
|
+
#
|
19
|
+
# The following keys must be escaped otherwise they will not be retrieved by
|
20
|
+
# the default I18n backend:
|
21
|
+
#
|
22
|
+
# true, false, on, off, yes, no
|
23
|
+
#
|
24
|
+
# Instead, surround them with single quotes.
|
25
|
+
#
|
26
|
+
# en:
|
27
|
+
# "true": "foo"
|
28
|
+
#
|
29
|
+
# To learn more, please read the Rails Internationalization guide
|
30
|
+
# available at https://guides.rubyonrails.org/i18n.html.
|
31
|
+
|
32
|
+
en:
|
33
|
+
|
34
|
+
global:
|
35
|
+
or: Or
|
36
|
+
more: More
|
37
|
+
is_admin: Admin?
|
38
|
+
person: Person
|
39
|
+
admin: Admin
|
40
|
+
confirm_message: Are you sure?
|
41
|
+
typing_search: Start typing search...
|
42
|
+
sidebar: Sidebar
|
43
|
+
creator: Bullet Train, Inc.
|
44
|
+
buttons:
|
45
|
+
other: Other
|
46
|
+
debug: Debug
|
47
|
+
details: Details
|
48
|
+
revoke: Revoke
|
49
|
+
cancel: Cancel
|
50
|
+
next: Next
|
51
|
+
back: Back
|
52
|
+
new: New
|
53
|
+
edit: Edit
|
54
|
+
settings: Settings
|
55
|
+
configure: Configure
|
56
|
+
remove: Remove
|
57
|
+
delete: Delete
|
58
|
+
update: Update
|
59
|
+
sign_up: Sign Up
|
60
|
+
sign_in: Sign In
|
61
|
+
notifications:
|
62
|
+
not_found: not found
|
63
|
+
all_fields_required: All of these fields are required.
|
64
|
+
dates:
|
65
|
+
today: Today
|
66
|
+
day:
|
67
|
+
one: 1 Day
|
68
|
+
other: '%{count} Days'
|
69
|
+
last_month: Last Month
|
70
|
+
last_week: Last Week
|
71
|
+
last_day:
|
72
|
+
one: Last Day
|
73
|
+
other: Last %{count} Days
|
74
|
+
formats:
|
75
|
+
date: '%m/%d/%Y'
|
76
|
+
date_and_time: '%m/%d/%Y %l:%M %p'
|
77
|
+
|
78
|
+
menus:
|
79
|
+
main:
|
80
|
+
labels:
|
81
|
+
account: Account
|
82
|
+
new: New
|
83
|
+
profile_details: Profile Details
|
84
|
+
logout: Logout
|
85
|
+
skip: Skip For Now
|
86
|
+
dashboard: Dashboard
|
87
|
+
team_members: Team Members
|
88
|
+
webhooks: Webhooks
|
89
|
+
invitations: Invitations
|
90
|
+
billing: Billing
|
91
|
+
upgrade: Upgrade
|
92
|
+
switch_teams: Switch Teams
|
93
|
+
add_new_team: Add New Team
|
94
|
+
edit_account_details: Account Details
|
95
|
+
resources: Resources
|
96
|
+
collaborate: Collaborate
|
97
|
+
integrations: Integrations
|
98
|
+
developers: Developers
|
99
|
+
your_account: Your Account
|
100
|
+
|
101
|
+
breadcrumbs:
|
102
|
+
your_dashboard: Your Dashboard
|
103
|
+
actions:
|
104
|
+
edit: 'Edit'
|
105
|
+
new: 'New'
|
106
|
+
|
107
|
+
fields:
|
108
|
+
date_field:
|
109
|
+
apply: Apply
|
110
|
+
cancel: Clear
|
111
|
+
|
112
|
+
layouts:
|
113
|
+
|
114
|
+
application:
|
115
|
+
raise: we should never be using the application layout.
|
116
|
+
|
117
|
+
mailer:
|
118
|
+
trouble: If you’re having trouble with the button above, copy and paste the URL below into your web browser.
|
119
|
+
all_rights_reserved: "%{year} %{product_name}. All rights reserved."
|
120
|
+
|
121
|
+
public:
|
122
|
+
launch_help: We're helping people launch high quality SaaS products!
|
123
|
+
developed_by: Developed By
|
124
|
+
made_with_love: Made With Love In
|
125
|
+
location: Los Angeles, California
|
126
|
+
follow_us: Follow Us
|
127
|
+
all_rights_reserved: "%{year} Bullet Train, Inc., All Rights Reserved"
|
@@ -0,0 +1,152 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
platform/application:
|
5
|
+
name: 'Name'
|
6
|
+
redirect_uri: 'Redirect URI'
|
7
|
+
errors:
|
8
|
+
models:
|
9
|
+
platform/application:
|
10
|
+
attributes:
|
11
|
+
redirect_uri:
|
12
|
+
fragment_present: 'cannot contain a fragment.'
|
13
|
+
invalid_uri: 'must be a valid URI.'
|
14
|
+
unspecified_scheme: 'must specify a scheme.'
|
15
|
+
relative_uri: 'must be an absolute URI.'
|
16
|
+
secured_uri: 'must be an HTTPS/SSL URI.'
|
17
|
+
forbidden_uri: 'is forbidden by the server.'
|
18
|
+
scopes:
|
19
|
+
not_match_configured: "can only contain the strings \"read\", \"write\", or \"delete\". You can also leave this field blank"
|
20
|
+
|
21
|
+
platform:
|
22
|
+
applications:
|
23
|
+
confirmations:
|
24
|
+
destroy: 'Are you sure?'
|
25
|
+
buttons:
|
26
|
+
edit: 'Edit'
|
27
|
+
destroy: 'Destroy'
|
28
|
+
submit: 'Submit'
|
29
|
+
cancel: 'Cancel'
|
30
|
+
authorize: 'Authorize'
|
31
|
+
form:
|
32
|
+
error: 'Whoops! Check your form for possible errors'
|
33
|
+
help:
|
34
|
+
confidential: 'Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.'
|
35
|
+
redirect_uri: 'Use one line per URI'
|
36
|
+
blank_redirect_uri: "Leave it blank if you configured your provider to use Client Credentials, Resource Owner Password Credentials or any other grant type that doesn't require redirect URI."
|
37
|
+
scopes: 'Separate scopes with spaces. Leave blank to use the default scope.'
|
38
|
+
edit:
|
39
|
+
title: 'Edit application'
|
40
|
+
index:
|
41
|
+
title: 'Your applications'
|
42
|
+
new: 'New Application'
|
43
|
+
name: 'Name'
|
44
|
+
callback_url: 'Callback URL'
|
45
|
+
confidential: 'Confidential?'
|
46
|
+
actions: 'Actions'
|
47
|
+
confidentiality:
|
48
|
+
'yes': 'Yes'
|
49
|
+
'no': 'No'
|
50
|
+
new:
|
51
|
+
title: 'New Application'
|
52
|
+
show:
|
53
|
+
title: 'Application: %{name}'
|
54
|
+
application_id: 'UID'
|
55
|
+
secret: 'Secret'
|
56
|
+
secret_hashed: 'Secret hashed'
|
57
|
+
scopes: 'Scopes'
|
58
|
+
confidential: 'Confidential'
|
59
|
+
callback_urls: 'Callback urls'
|
60
|
+
actions: 'Actions'
|
61
|
+
not_defined: 'Not defined'
|
62
|
+
|
63
|
+
doorkeeper:
|
64
|
+
authorizations:
|
65
|
+
buttons:
|
66
|
+
authorize: 'Authorize'
|
67
|
+
deny: 'Deny'
|
68
|
+
error:
|
69
|
+
title: 'An error has occurred'
|
70
|
+
new:
|
71
|
+
title: 'Authorization required'
|
72
|
+
prompt: 'Authorize %{client_name} to use your account?'
|
73
|
+
able_to: 'This application will be able to'
|
74
|
+
show:
|
75
|
+
title: 'Authorization code'
|
76
|
+
|
77
|
+
authorized_applications:
|
78
|
+
confirmations:
|
79
|
+
revoke: 'Are you sure?'
|
80
|
+
buttons:
|
81
|
+
revoke: 'Revoke'
|
82
|
+
index:
|
83
|
+
title: 'Your authorized applications'
|
84
|
+
application: 'Application'
|
85
|
+
created_at: 'Created At'
|
86
|
+
date_format: '%Y-%m-%d %H:%M:%S'
|
87
|
+
|
88
|
+
pre_authorization:
|
89
|
+
status: 'Pre-authorization'
|
90
|
+
|
91
|
+
errors:
|
92
|
+
messages:
|
93
|
+
# Common error messages
|
94
|
+
invalid_request:
|
95
|
+
unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
|
96
|
+
missing_param: 'Missing required parameter: %{value}.'
|
97
|
+
not_support_pkce: 'Invalid code_verifier parameter. Server does not support pkce.'
|
98
|
+
request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
|
99
|
+
invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
|
100
|
+
unauthorized_client: 'The client is not authorized to perform this request using this method.'
|
101
|
+
access_denied: 'The resource owner or authorization server denied the request.'
|
102
|
+
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
|
103
|
+
invalid_code_challenge_method: 'The code challenge method must be plain or S256.'
|
104
|
+
server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
|
105
|
+
temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
|
106
|
+
|
107
|
+
# Configuration error messages
|
108
|
+
credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
|
109
|
+
resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured.'
|
110
|
+
admin_authenticator_not_configured: 'Access to admin panel is forbidden due to Doorkeeper.configure.admin_authenticator being unconfigured.'
|
111
|
+
|
112
|
+
# Access grant errors
|
113
|
+
unsupported_response_type: 'The authorization server does not support this response type.'
|
114
|
+
|
115
|
+
# Access token errors
|
116
|
+
invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
|
117
|
+
invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
|
118
|
+
unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
|
119
|
+
|
120
|
+
invalid_token:
|
121
|
+
revoked: "The access token was revoked"
|
122
|
+
expired: "The access token expired"
|
123
|
+
unknown: "The access token is invalid"
|
124
|
+
revoke:
|
125
|
+
unauthorized: "You are not authorized to revoke this token"
|
126
|
+
|
127
|
+
flash:
|
128
|
+
applications:
|
129
|
+
create:
|
130
|
+
notice: 'Application created.'
|
131
|
+
destroy:
|
132
|
+
notice: 'Application deleted.'
|
133
|
+
update:
|
134
|
+
notice: 'Application updated.'
|
135
|
+
authorized_applications:
|
136
|
+
destroy:
|
137
|
+
notice: 'Application revoked.'
|
138
|
+
|
139
|
+
layouts:
|
140
|
+
admin:
|
141
|
+
title: 'Doorkeeper'
|
142
|
+
nav:
|
143
|
+
oauth2_provider: 'OAuth2 Provider'
|
144
|
+
applications: 'Applications'
|
145
|
+
home: 'Home'
|
146
|
+
application:
|
147
|
+
title: 'OAuth authorization required'
|
148
|
+
|
149
|
+
scopes:
|
150
|
+
delete: "Your OAuth application is missing the 'delete' scope to access this API method."
|
151
|
+
read: "Your OAuth application is missing the 'read' scope to access this API method."
|
152
|
+
write: "Your OAuth application is missing the 'write' scope to access this API method."
|
@@ -0,0 +1,15 @@
|
|
1
|
+
en:
|
2
|
+
stripe_connect: Stripe
|
3
|
+
# 🚅 super scaffolding will insert new oauth providers above this line.
|
4
|
+
omniauth:
|
5
|
+
team:
|
6
|
+
connected: "We've successfully added that %{provider} account to your team!"
|
7
|
+
denied: "Permission to connect to %{provider} was not granted. Not a problem! You can try again if you want to."
|
8
|
+
already_present: "That %{provider} account is already present for this team!"
|
9
|
+
not_allowed: "You're not allowed to add a %{provider} account to a team you don't have access to."
|
10
|
+
user:
|
11
|
+
connected: "We've successfully connected that %{provider} account to your user account."
|
12
|
+
reconnected: "That %{provider} account is already connected to your user account."
|
13
|
+
already_registered: "That %{provider} account is already connected to another user. Before you can connect that %{provider} account with your user account, you'll need to sign out and sign back in using that account and remove it from that user account profile."
|
14
|
+
email_already_registered: "Sorry, there is already a user registered with the email address %{email}, but this %{provider} account isn't configured for login with that account. Please sign in using your password and then add this account."
|
15
|
+
account_not_found: "Sorry, no existing account was found for that %{provider} account. In order to create a new account, please click the link in the invitation you received via email."
|
@@ -0,0 +1,36 @@
|
|
1
|
+
en:
|
2
|
+
user_mailer:
|
3
|
+
signature: &SIGNATURE
|
4
|
+
html:
|
5
|
+
<p>If you have any questions, please don't hesitate to <a href="mailto:%{support_email}">send us an email</a>.</p>
|
6
|
+
<p>Thanks,<br>Bullet Train, Inc.</p>
|
7
|
+
invited:
|
8
|
+
subject: Invitation to join %{team_name} on Bullet Train!
|
9
|
+
preview: '%{inviter_name} has sent you this invitation.'
|
10
|
+
heading: You're invited!
|
11
|
+
body:
|
12
|
+
html:
|
13
|
+
<p>
|
14
|
+
%{inviter_name} has invited you to join %{team_name} on Bullet Train.
|
15
|
+
You can join %{team_name} by clicking the button below.
|
16
|
+
</p>
|
17
|
+
cta:
|
18
|
+
label: Join %{team_name}
|
19
|
+
signature:
|
20
|
+
<<: *SIGNATURE
|
21
|
+
welcome:
|
22
|
+
subject: Welcome to the Bullet Train demo!
|
23
|
+
preview: This is the default Bullet Train welcome email. Check out the layout!
|
24
|
+
heading: Welcome to Bullet Train!
|
25
|
+
body:
|
26
|
+
html:
|
27
|
+
<p>This is the default welcome email for Bullet Train!</p>
|
28
|
+
<p>If you have any questions about Bullet Train, just reply to this email!</p>
|
29
|
+
<p>It was important to us that Bullet Train applications have a good looking, branded email template by default.</p>
|
30
|
+
<p>Thankfully our friends at <a href="https://postmarkapp.com">Postmark</a> made the foundation of this email template <a href="https://github.com/wildbit/postmark-templates">freely available</a>. From there, we've customized it slightly to better match the default Bullet Train look-and-feel.</p>
|
31
|
+
cta:
|
32
|
+
heading: What's next?
|
33
|
+
body: You can use the following link to always return to your Bullet Train account dashboard.
|
34
|
+
label: View Your Account Dashboard
|
35
|
+
signature:
|
36
|
+
<<: *SIGNATURE
|
data/lib/bullet_train/version.rb
CHANGED
data/lib/bullet_train.rb
CHANGED
@@ -7,6 +7,9 @@ require "bullet_train/super_load_and_authorize_resource"
|
|
7
7
|
require "bullet_train/has_uuid"
|
8
8
|
require "bullet_train/scope_validator"
|
9
9
|
|
10
|
+
require "colorizer"
|
11
|
+
require "string/emoji"
|
12
|
+
|
10
13
|
require "devise"
|
11
14
|
# require "devise-two-factor"
|
12
15
|
# require "rqrcode"
|
@@ -32,3 +35,86 @@ module BulletTrain
|
|
32
35
|
mattr_accessor :routing_concerns, default: []
|
33
36
|
mattr_accessor :linked_gems, default: ["bullet_train"]
|
34
37
|
end
|
38
|
+
|
39
|
+
def default_url_options_from_base_url
|
40
|
+
unless ENV["BASE_URL"].present?
|
41
|
+
if Rails.env.development?
|
42
|
+
ENV["BASE_URL"] ||= "http://localhost:3000"
|
43
|
+
else
|
44
|
+
raise "you need to define the value of ENV['BASE_URL'] in your environment. if you're on heroku, you can do this with `heroku config:add BASE_URL=https://your-app-name.herokuapp.com` (or whatever your configured domain is)."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
parsed_base_url = URI.parse(ENV["BASE_URL"])
|
49
|
+
default_url_options = [:user, :password, :host, :port].map { |key| [key, parsed_base_url.send(key)] }.to_h
|
50
|
+
|
51
|
+
# the name of this property doesn't match up.
|
52
|
+
default_url_options[:protocol] = parsed_base_url.scheme
|
53
|
+
|
54
|
+
default_url_options.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def inbound_email_enabled?
|
58
|
+
ENV["INBOUND_EMAIL_DOMAIN"].present?
|
59
|
+
end
|
60
|
+
|
61
|
+
def subscriptions_enabled?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def free_trial?
|
66
|
+
ENV["STRIPE_FREE_TRIAL_LENGTH"].present?
|
67
|
+
end
|
68
|
+
|
69
|
+
def stripe_enabled?
|
70
|
+
ENV["STRIPE_CLIENT_ID"].present?
|
71
|
+
end
|
72
|
+
|
73
|
+
# 🚅 super scaffolding will insert new oauth providers above this line.
|
74
|
+
|
75
|
+
def webhooks_enabled?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def scaffolding_things_disabled?
|
80
|
+
ENV["HIDE_THINGS"].present? || ENV["HIDE_EXAMPLES"].present?
|
81
|
+
end
|
82
|
+
|
83
|
+
def sample_role_disabled?
|
84
|
+
ENV["HIDE_EXAMPLES"].present?
|
85
|
+
end
|
86
|
+
|
87
|
+
def demo?
|
88
|
+
ENV["DEMO"].present?
|
89
|
+
end
|
90
|
+
|
91
|
+
def cloudinary_enabled?
|
92
|
+
ENV["CLOUDINARY_URL"].present?
|
93
|
+
end
|
94
|
+
|
95
|
+
def two_factor_authentication_enabled?
|
96
|
+
ENV["TWO_FACTOR_ENCRYPTION_KEY"].present?
|
97
|
+
end
|
98
|
+
|
99
|
+
def any_oauth_enabled?
|
100
|
+
[
|
101
|
+
stripe_enabled?,
|
102
|
+
# 🚅 super scaffolding will insert new oauth provider checks above this line.
|
103
|
+
].select(&:present?).any?
|
104
|
+
end
|
105
|
+
|
106
|
+
def invitation_only?
|
107
|
+
ENV["INVITATION_KEYS"].present?
|
108
|
+
end
|
109
|
+
|
110
|
+
def invitation_keys
|
111
|
+
ENV["INVITATION_KEYS"].split(",").map(&:strip)
|
112
|
+
end
|
113
|
+
|
114
|
+
def font_awesome?
|
115
|
+
ENV["FONTAWESOME_NPM_AUTH_TOKEN"].present?
|
116
|
+
end
|
117
|
+
|
118
|
+
def multiple_locales?
|
119
|
+
@multiple_locales ||= I18n.available_locales.many?
|
120
|
+
end
|
data/lib/colorizer.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# https://makandracards.com/makandra/24449-hash-any-ruby-object-into-an-rgb-color
|
2
|
+
module Colorizer
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def colorize(object)
|
6
|
+
# Inspired by Jeremy Ruten (http://stackoverflow.com/questions/1698318/ruby-generate-a-random-hex-color)
|
7
|
+
hash = object.hash # hash an object, returns a Fixnum
|
8
|
+
trimmed_hash = hash & 0xffffff # trim the hash to the size of 6 hex digits (& is bit-wise AND)
|
9
|
+
hex_code = "%06x" % trimmed_hash # format as at least 6 hex digits, pad with zeros
|
10
|
+
"##{hex_code}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def colorize_similarly(object, saturation, lightness)
|
14
|
+
rnd = ((object.hash * 7) % 100) * 0.01
|
15
|
+
hsl_to_rgb(rnd, saturation, lightness)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def hsl_to_rgb(h, sl, l)
|
21
|
+
r = l
|
22
|
+
g = l
|
23
|
+
b = l
|
24
|
+
v = l <= 0.5 ? (l * (1.0 + sl)) : (l + sl - l * sl)
|
25
|
+
if v > 0
|
26
|
+
m = l + l - v
|
27
|
+
sv = (v - m) / v
|
28
|
+
h *= 6.0
|
29
|
+
sextant = h.floor
|
30
|
+
fract = h - sextant
|
31
|
+
vsf = v * sv * fract
|
32
|
+
mid1 = m + vsf
|
33
|
+
mid2 = v - vsf
|
34
|
+
case sextant
|
35
|
+
when 0
|
36
|
+
r = v
|
37
|
+
g = mid1
|
38
|
+
b = m
|
39
|
+
when 1
|
40
|
+
r = mid2
|
41
|
+
g = v
|
42
|
+
b = m
|
43
|
+
when 2
|
44
|
+
r = m
|
45
|
+
g = v
|
46
|
+
b = mid1
|
47
|
+
when 3
|
48
|
+
r = m
|
49
|
+
g = mid2
|
50
|
+
b = v
|
51
|
+
when 4
|
52
|
+
r = mid1
|
53
|
+
g = m
|
54
|
+
b = v
|
55
|
+
when 5
|
56
|
+
r = v
|
57
|
+
g = m
|
58
|
+
b = mid2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
"##{hex_color_component(r)}#{hex_color_component(g)}#{hex_color_component(b)}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def hex_color_component(i)
|
65
|
+
(i * 255).floor.to_s(16).rjust(2, "0")
|
66
|
+
end
|
67
|
+
end
|
data/lib/string/emoji.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Culver
|
@@ -391,6 +391,7 @@ files:
|
|
391
391
|
- app/helpers/account/teams_helper.rb
|
392
392
|
- app/helpers/account/users_helper.rb
|
393
393
|
- app/helpers/attributes_helper.rb
|
394
|
+
- app/helpers/base_helper.rb
|
394
395
|
- app/helpers/email_helper.rb
|
395
396
|
- app/helpers/images_helper.rb
|
396
397
|
- app/helpers/invitation_only_helper.rb
|
@@ -398,6 +399,7 @@ files:
|
|
398
399
|
- app/mailers/concerns/mailers/base.rb
|
399
400
|
- app/mailers/devise_mailer.rb
|
400
401
|
- app/mailers/user_mailer.rb
|
402
|
+
- app/models/concerns/current_attributes/base.rb
|
401
403
|
- app/models/concerns/invitations/base.rb
|
402
404
|
- app/models/concerns/memberships/base.rb
|
403
405
|
- app/models/concerns/records/base.rb
|
@@ -465,12 +467,22 @@ files:
|
|
465
467
|
- app/views/devise/unlocks/new.html.erb
|
466
468
|
- app/views/layouts/account.html.erb
|
467
469
|
- app/views/layouts/devise.html.erb
|
470
|
+
- app/views/layouts/docs.html.erb
|
471
|
+
- app/views/layouts/mailer.html.erb
|
472
|
+
- app/views/public/home/docs.html.erb
|
473
|
+
- app/views/user_mailer/invited.html.erb
|
474
|
+
- app/views/user_mailer/welcome.html.erb
|
475
|
+
- config/locales/en/base.yml
|
468
476
|
- config/locales/en/devise.en.yml
|
477
|
+
- config/locales/en/doorkeeper.en.yml
|
469
478
|
- config/locales/en/invitations.en.yml
|
470
479
|
- config/locales/en/memberships.en.yml
|
480
|
+
- config/locales/en/oauth.en.yml
|
471
481
|
- config/locales/en/onboarding/user_details.en.yml
|
472
482
|
- config/locales/en/onboarding/user_email.en.yml
|
483
|
+
- config/locales/en/roles.en.yml
|
473
484
|
- config/locales/en/teams.en.yml
|
485
|
+
- config/locales/en/user_mailer.en.yml
|
474
486
|
- config/locales/en/users.en.yml
|
475
487
|
- config/routes.rb
|
476
488
|
- db/migrate/20161115160419_devise_create_users.rb
|
@@ -511,6 +523,8 @@ files:
|
|
511
523
|
- lib/bullet_train.rb
|
512
524
|
- lib/bullet_train/engine.rb
|
513
525
|
- lib/bullet_train/version.rb
|
526
|
+
- lib/colorizer.rb
|
527
|
+
- lib/string/emoji.rb
|
514
528
|
- lib/tasks/bullet_train_tasks.rake
|
515
529
|
homepage: https://github.com/bullet-train-co/bullet_train
|
516
530
|
licenses:
|