tramway 3.1.2.8 → 4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +76 -16
- data/app/assets/builds/tailwind.css +2 -0
- data/app/assets/javascripts/tramway/tramway.js +174 -1
- data/app/assets/stylesheets/font-awesome.css +2337 -0
- data/app/assets/tailwind/application.css +29 -0
- data/app/components/tramway/chat_component.html.haml +1 -1
- data/app/components/tramway/containers/main_component.rb +1 -3
- data/app/components/tramway/containers/narrow_component.rb +5 -4
- data/app/components/tramway/form/builder.rb +1 -2
- data/app/components/tramway/grid/card_component.html.haml +2 -0
- data/app/components/tramway/grid/card_component.rb +37 -0
- data/app/components/tramway/grid_component.html.haml +18 -0
- data/app/components/tramway/grid_component.rb +76 -0
- data/app/components/tramway/native_text_component.rb +1 -2
- data/app/components/tramway/nav/item_component.rb +4 -2
- data/app/components/tramway/navbar_component.html.haml +104 -110
- data/app/components/tramway/navbar_component.rb +84 -2
- data/app/components/tramway/table/cell_component.rb +9 -1
- data/app/components/tramway/table/header_component.rb +23 -2
- data/app/components/tramway/table/row_component.rb +27 -10
- data/app/components/tramway/table_component.rb +28 -1
- data/app/controllers/tramway/entities_controller.rb +6 -6
- data/app/views/tramway/layouts/application.html.haml +8 -6
- data/config/routes.rb +1 -2
- data/config/tailwind.config.js +57 -0
- data/lib/generators/tramway/install/font_awesome_helpers.rb +39 -0
- data/lib/generators/tramway/install/install_generator.rb +131 -65
- data/lib/generators/tramway/install/layout_helpers.rb +95 -0
- data/lib/tramway/engine.rb +12 -0
- data/lib/tramway/forms/validations.rb +1 -2
- data/lib/tramway/helpers/navbar_helper.rb +3 -1
- data/lib/tramway/helpers/table_helper.rb +27 -0
- data/lib/tramway/helpers/views_helper.rb +26 -2
- data/lib/tramway/version.rb +1 -1
- metadata +28 -4
|
@@ -6,9 +6,20 @@ module Tramway
|
|
|
6
6
|
class RowComponent < Tramway::BaseComponent
|
|
7
7
|
include ContentCells
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
SIZE_CLASSES = {
|
|
10
|
+
small: {
|
|
11
|
+
row: 'div-table-row grid grid-cols-1 gap-2 border-b border-zinc-800 bg-transparent last:border-b-0',
|
|
12
|
+
cell: 'div-table-cell bg-transparent px-4 py-2 text-sm font-medium text-zinc-100 sm:text-sm'
|
|
13
|
+
},
|
|
14
|
+
medium: {
|
|
15
|
+
row: 'div-table-row grid grid-cols-1 gap-4 border-b border-zinc-800 bg-transparent last:border-b-0',
|
|
16
|
+
cell: 'div-table-cell bg-transparent px-6 py-4 text-xs font-medium text-zinc-100 sm:text-base'
|
|
17
|
+
},
|
|
18
|
+
large: {
|
|
19
|
+
row: 'div-table-row grid grid-cols-1 gap-6 border-b border-zinc-800 bg-transparent last:border-b-0',
|
|
20
|
+
cell: 'div-table-cell bg-transparent px-6 py-6 text-lg font-medium text-zinc-100'
|
|
21
|
+
}
|
|
22
|
+
}.freeze
|
|
12
23
|
|
|
13
24
|
option :cells, optional: true, default: -> { [] }
|
|
14
25
|
option :href, optional: true
|
|
@@ -32,7 +43,7 @@ module Tramway
|
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
def desktop_row_classes(cells_count)
|
|
35
|
-
(
|
|
46
|
+
"#{size_classes.fetch(:row)} md:grid-cols-#{cells_count}"
|
|
36
47
|
end
|
|
37
48
|
|
|
38
49
|
def link_row_classes
|
|
@@ -40,25 +51,31 @@ module Tramway
|
|
|
40
51
|
end
|
|
41
52
|
|
|
42
53
|
def cell_classes
|
|
43
|
-
|
|
54
|
+
size_classes.fetch(:cell)
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
def around_render
|
|
47
58
|
ensure_view_context_accessor
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
context = tramway_table_context
|
|
60
|
+
previous_flag = context.tramway_inside_cell
|
|
61
|
+
context.tramway_inside_cell = href.present?
|
|
50
62
|
|
|
51
63
|
yield
|
|
52
64
|
ensure
|
|
53
|
-
|
|
65
|
+
context.tramway_inside_cell = previous_flag if context.respond_to?(:tramway_inside_cell=)
|
|
54
66
|
end
|
|
55
67
|
|
|
56
68
|
private
|
|
57
69
|
|
|
58
70
|
def ensure_view_context_accessor
|
|
59
|
-
|
|
71
|
+
context = tramway_table_context
|
|
72
|
+
return if context.respond_to?(:tramway_inside_cell=)
|
|
60
73
|
|
|
61
|
-
|
|
74
|
+
context.singleton_class.attr_accessor :tramway_inside_cell
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def size_classes
|
|
78
|
+
SIZE_CLASSES.fetch(tramway_table_size) { SIZE_CLASSES[:medium] }
|
|
62
79
|
end
|
|
63
80
|
end
|
|
64
81
|
end
|
|
@@ -3,10 +3,37 @@
|
|
|
3
3
|
module Tramway
|
|
4
4
|
# Table component for rendering a table
|
|
5
5
|
class TableComponent < Tramway::BaseComponent
|
|
6
|
+
TABLE_SIZES = %i[small medium large].freeze
|
|
7
|
+
|
|
8
|
+
option :size, default: -> { :medium }
|
|
6
9
|
option :options, optional: true, default: -> { {} }
|
|
7
10
|
|
|
8
11
|
def table_classes
|
|
9
|
-
'div-table w-full text-left rtl:text-right text-zinc-100'
|
|
12
|
+
'div-table w-full bg-zinc-950 text-left rtl:text-right text-zinc-100'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def around_render
|
|
16
|
+
ensure_view_context_accessor
|
|
17
|
+
context = tramway_table_context
|
|
18
|
+
previous_size = context.tramway_table_size
|
|
19
|
+
context.tramway_table_size = normalized_size
|
|
20
|
+
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
context.tramway_table_size = previous_size if context.respond_to?(:tramway_table_size=)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def normalized_size
|
|
29
|
+
TABLE_SIZES.include?(size) ? size : :medium
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ensure_view_context_accessor
|
|
33
|
+
context = tramway_table_context
|
|
34
|
+
return if context.respond_to?(:tramway_table_size=)
|
|
35
|
+
|
|
36
|
+
context.singleton_class.attr_accessor :tramway_table_size
|
|
10
37
|
end
|
|
11
38
|
end
|
|
12
39
|
end
|
|
@@ -28,7 +28,7 @@ module Tramway
|
|
|
28
28
|
|
|
29
29
|
def show
|
|
30
30
|
@record = tramway_decorate(
|
|
31
|
-
model_class.find(params
|
|
31
|
+
model_class.find(params.expect(:id)),
|
|
32
32
|
namespace: entity.namespace
|
|
33
33
|
).with(view_context:)
|
|
34
34
|
|
|
@@ -40,14 +40,14 @@ module Tramway
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def edit
|
|
43
|
-
@record = tramway_form model_class.find(params
|
|
43
|
+
@record = tramway_form model_class.find(params.expect(:id)), namespace: entity.namespace
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# rubocop:disable Metrics/AbcSize
|
|
47
47
|
def create
|
|
48
48
|
@record = tramway_form model_class.new, namespace: entity.namespace
|
|
49
49
|
|
|
50
|
-
if @record.submit params
|
|
50
|
+
if @record.submit params.require(model_class.model_name.param_key).permit!
|
|
51
51
|
redirect_to Tramway::Engine.routes.url_helpers.public_send(entity.show_helper_method, @record.id),
|
|
52
52
|
notice: t('tramway.notices.created')
|
|
53
53
|
else
|
|
@@ -56,9 +56,9 @@ module Tramway
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def update
|
|
59
|
-
@record = tramway_form model_class.find(params
|
|
59
|
+
@record = tramway_form model_class.find(params.expect(:id)), namespace: entity.namespace
|
|
60
60
|
|
|
61
|
-
if @record.submit params
|
|
61
|
+
if @record.submit params.require(model_class.model_name.param_key).permit!
|
|
62
62
|
redirect_to Tramway::Engine.routes.url_helpers.public_send(entity.show_helper_method, @record.id),
|
|
63
63
|
notice: t('tramway.notices.updated')
|
|
64
64
|
else
|
|
@@ -68,7 +68,7 @@ module Tramway
|
|
|
68
68
|
# rubocop:enable Metrics/AbcSize
|
|
69
69
|
|
|
70
70
|
def destroy
|
|
71
|
-
@record = model_class.find(params
|
|
71
|
+
@record = model_class.find(params.expect(:id))
|
|
72
72
|
|
|
73
73
|
@record.destroy
|
|
74
74
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
%link{rel: "apple-touch-icon", href: "/icon.png"}
|
|
15
15
|
|
|
16
16
|
= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"
|
|
17
|
+
= stylesheet_link_tag "font-awesome", "data-turbo-track": "reload"
|
|
17
18
|
= stylesheet_link_tag "trix", "data-turbo-track": "reload"
|
|
18
19
|
= javascript_include_tag "trix", "data-turbo-track": "reload", defer: true
|
|
19
20
|
= javascript_importmap_tags
|
|
@@ -21,10 +22,11 @@
|
|
|
21
22
|
%body.bg-zinc-950.text-zinc-50
|
|
22
23
|
= tramway_navbar title: 'Tramway'
|
|
23
24
|
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
= tramway_main_container id: 'tramway-main-container', class: 'md:pl-72 transition-[padding-left] duration-300 ease-in-out-strong motion-reduce:duration-0' do
|
|
26
|
+
- if flash.any?
|
|
27
|
+
= tramway_flash text: flash[:notice].presence || flash[:alert],
|
|
28
|
+
type: flash[:notice].present? ? :will : :rage,
|
|
29
|
+
id: 'flash-container'
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
.container.mx-auto.p-4.flex.align-center.justify-center
|
|
32
|
+
= yield
|
data/config/routes.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'tramway/helpers/routes_helper'
|
|
4
4
|
|
|
5
|
-
# rubocop:disable Metrics/BlockLength
|
|
5
|
+
# rubocop:disable-next Metrics/BlockLength
|
|
6
6
|
Tramway::Engine.routes.draw do
|
|
7
7
|
Tramway.config.entities.each do |entity|
|
|
8
8
|
if entity.namespace.present?
|
|
@@ -55,7 +55,6 @@ Tramway::Engine.routes.draw do
|
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
-
# rubocop:enable Metrics/BlockLength
|
|
59
58
|
|
|
60
59
|
Tramway::Engine.routes.routes.map(&:name).compact.each do |route_name|
|
|
61
60
|
%w[path url].each do |suffix|
|
data/config/tailwind.config.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const defaultTheme = require("tailwindcss/defaultTheme")
|
|
2
2
|
|
|
3
|
+
const buildUtilities = (prefix, start, end) =>
|
|
4
|
+
Array.from({ length: end - start + 1 }, (_, index) => `${prefix}${start + index}`)
|
|
5
|
+
|
|
3
6
|
module.exports = {
|
|
4
7
|
safelist: [
|
|
5
8
|
// === Navbar ===
|
|
@@ -13,17 +16,52 @@ module.exports = {
|
|
|
13
16
|
'md:translate-x-0',
|
|
14
17
|
'border-white',
|
|
15
18
|
'h-screen',
|
|
19
|
+
'h-dvh',
|
|
16
20
|
'w-screen',
|
|
21
|
+
'w-72',
|
|
17
22
|
'z-50',
|
|
18
23
|
'inset-0',
|
|
24
|
+
'top-0',
|
|
19
25
|
'top-4',
|
|
20
26
|
'right-6',
|
|
27
|
+
'right-0',
|
|
21
28
|
'text-4xl',
|
|
22
29
|
'transition-transform',
|
|
23
30
|
'-translate-x-full',
|
|
24
31
|
'duration-300',
|
|
25
32
|
'space-y-3',
|
|
26
33
|
'md:justify-between',
|
|
34
|
+
'hidden',
|
|
35
|
+
'md:fixed',
|
|
36
|
+
'md:left-0',
|
|
37
|
+
'md:top-0',
|
|
38
|
+
'md:z-40',
|
|
39
|
+
'md:h-dvh',
|
|
40
|
+
'md:w-72',
|
|
41
|
+
'md:w-24',
|
|
42
|
+
'md:flex-col',
|
|
43
|
+
'md:hidden',
|
|
44
|
+
'md:border-r',
|
|
45
|
+
'md:border-zinc-800',
|
|
46
|
+
'md:bg-zinc-950',
|
|
47
|
+
'md:px-4',
|
|
48
|
+
'md:py-3',
|
|
49
|
+
'md:shadow-sm',
|
|
50
|
+
'md:backdrop-blur',
|
|
51
|
+
'md:overflow-hidden',
|
|
52
|
+
'md:pl-72',
|
|
53
|
+
'md:pl-24',
|
|
54
|
+
'transition-all',
|
|
55
|
+
'ease-in-out',
|
|
56
|
+
'md:transition-[width]',
|
|
57
|
+
'transition-[padding-left]',
|
|
58
|
+
'ease-in-out-strong',
|
|
59
|
+
'md:ease-in-out-strong',
|
|
60
|
+
'ease-out-strong',
|
|
61
|
+
'md:motion-reduce:duration-0',
|
|
62
|
+
'motion-reduce:duration-0',
|
|
63
|
+
'duration-200',
|
|
64
|
+
'rotate-180',
|
|
27
65
|
'pt-16',
|
|
28
66
|
'min-h-8',
|
|
29
67
|
'bg-zinc-950',
|
|
@@ -55,6 +93,7 @@ module.exports = {
|
|
|
55
93
|
'shadow-lg',
|
|
56
94
|
'shadow-sm',
|
|
57
95
|
'w-fit',
|
|
96
|
+
'text-xs',
|
|
58
97
|
'text-base',
|
|
59
98
|
'text-sm',
|
|
60
99
|
'text-lg',
|
|
@@ -64,11 +103,23 @@ module.exports = {
|
|
|
64
103
|
'border-r',
|
|
65
104
|
'border-t',
|
|
66
105
|
'border',
|
|
106
|
+
'border-dotted',
|
|
107
|
+
'border-zinc-700/50',
|
|
108
|
+
'border-zinc-100/60',
|
|
109
|
+
'box-border',
|
|
67
110
|
'gap-4',
|
|
111
|
+
'gap-6',
|
|
68
112
|
'gap-1',
|
|
69
113
|
'gap-2',
|
|
70
114
|
'grid-cols-1',
|
|
71
115
|
'grid',
|
|
116
|
+
'overflow-auto',
|
|
117
|
+
'inset-x-0',
|
|
118
|
+
'inset-y-0',
|
|
119
|
+
...buildUtilities('md:grid-cols-', 1, 12),
|
|
120
|
+
...buildUtilities('md:grid-rows-', 1, 12),
|
|
121
|
+
...buildUtilities('md:col-span-', 1, 12),
|
|
122
|
+
...buildUtilities('md:row-span-', 1, 12),
|
|
72
123
|
'px-4',
|
|
73
124
|
'px-3',
|
|
74
125
|
'py-3',
|
|
@@ -79,6 +130,7 @@ module.exports = {
|
|
|
79
130
|
'inset-0',
|
|
80
131
|
'top-4',
|
|
81
132
|
'right-4',
|
|
133
|
+
'z-10',
|
|
82
134
|
'flex-col',
|
|
83
135
|
'items-center',
|
|
84
136
|
'justify-between',
|
|
@@ -173,6 +225,7 @@ module.exports = {
|
|
|
173
225
|
'sm:px-3',
|
|
174
226
|
'sm:py-2',
|
|
175
227
|
'sm:text-sm',
|
|
228
|
+
'sm:text-base',
|
|
176
229
|
'md:gap-1',
|
|
177
230
|
'md:space-y-4',
|
|
178
231
|
'space-y-2',
|
|
@@ -651,6 +704,10 @@ module.exports = {
|
|
|
651
704
|
fontFamily: {
|
|
652
705
|
sans: ["Inter var", ...defaultTheme.fontFamily.sans],
|
|
653
706
|
},
|
|
707
|
+
transitionTimingFunction: {
|
|
708
|
+
"out-strong": "cubic-bezier(0.23, 1, 0.32, 1)",
|
|
709
|
+
"in-out-strong": "cubic-bezier(0.77, 0, 0.175, 1)",
|
|
710
|
+
},
|
|
654
711
|
keyframes: {
|
|
655
712
|
"accordion-down": {
|
|
656
713
|
from: { height: 0 },
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Generators
|
|
5
|
+
# nodoc
|
|
6
|
+
module InstallGeneratorFontAwesomeHelpers
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def font_awesome_already_present?(content)
|
|
10
|
+
content.include?('stylesheet_link_tag "font-awesome"') ||
|
|
11
|
+
content.include?("stylesheet_link_tag 'font-awesome'")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ensure_font_awesome_in_haml_layout
|
|
15
|
+
content = File.read(application_layout_haml_path)
|
|
16
|
+
return if font_awesome_already_present?(content)
|
|
17
|
+
return unless content.match?(/^\s+= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"/)
|
|
18
|
+
|
|
19
|
+
updated = content.sub(
|
|
20
|
+
/^(\s+= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"\n)/,
|
|
21
|
+
"\\1 = stylesheet_link_tag \"font-awesome\", \"data-turbo-track\": \"reload\"\n"
|
|
22
|
+
)
|
|
23
|
+
File.write(application_layout_haml_path, updated)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ensure_font_awesome_in_erb_layout
|
|
27
|
+
content = File.read(application_layout_erb_path)
|
|
28
|
+
return if font_awesome_already_present?(content)
|
|
29
|
+
return unless content.include?('</head>')
|
|
30
|
+
|
|
31
|
+
updated = content.sub(
|
|
32
|
+
/(\s+<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>\n)/,
|
|
33
|
+
"\\1 <%= stylesheet_link_tag \"font-awesome\", \"data-turbo-track\": \"reload\" %>\n"
|
|
34
|
+
)
|
|
35
|
+
File.write(application_layout_erb_path, updated)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -2,12 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rails/generators'
|
|
4
4
|
require 'fileutils'
|
|
5
|
+
require_relative 'font_awesome_helpers'
|
|
6
|
+
require_relative 'layout_helpers'
|
|
5
7
|
|
|
6
8
|
module Tramway
|
|
7
9
|
module Generators
|
|
8
10
|
# nodoc
|
|
9
|
-
# rubocop:disable Metrics/ModuleLength
|
|
11
|
+
# rubocop:disable-next Metrics/ModuleLength
|
|
10
12
|
module InstallGeneratorHelpers
|
|
13
|
+
TAILWIND_SCROLLBAR_UTILITY = <<~CSS.chomp
|
|
14
|
+
@layer utilities {
|
|
15
|
+
.tramway-scrollbar {
|
|
16
|
+
scrollbar-width: thin;
|
|
17
|
+
scrollbar-color: rgba(82, 82, 91, 0.9) rgba(9, 9, 11, 0.92);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.tramway-scrollbar::-webkit-scrollbar {
|
|
21
|
+
width: 0.5rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.tramway-scrollbar::-webkit-scrollbar-track {
|
|
25
|
+
background: rgba(9, 9, 11, 0.92);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.tramway-scrollbar::-webkit-scrollbar-thumb {
|
|
29
|
+
border-radius: 9999px;
|
|
30
|
+
background: rgba(82, 82, 91, 0.9);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.tramway-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
34
|
+
background: rgba(113, 113, 122, 0.95);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tramway-scrollbar::-webkit-scrollbar-corner {
|
|
38
|
+
background: transparent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
CSS
|
|
42
|
+
|
|
43
|
+
TAILWIND_APPLICATION_STYLESHEET_CONTENT =
|
|
44
|
+
"@import \"tailwindcss\";\n\n#{TAILWIND_SCROLLBAR_UTILITY}\n".freeze
|
|
45
|
+
|
|
11
46
|
private
|
|
12
47
|
|
|
13
48
|
def gem_dependencies
|
|
@@ -78,12 +113,13 @@ module Tramway
|
|
|
78
113
|
|
|
79
114
|
def stimulus_controller_imports
|
|
80
115
|
[
|
|
81
|
-
'import { TramwaySelect, TableRowPreview, UiCheckbox, Tooltip } from "@tramway/tramway"'
|
|
116
|
+
'import { Navbar, TramwaySelect, TableRowPreview, UiCheckbox, Tooltip } from "@tramway/tramway"'
|
|
82
117
|
]
|
|
83
118
|
end
|
|
84
119
|
|
|
85
120
|
def stimulus_controller_registrations
|
|
86
121
|
[
|
|
122
|
+
"application.register('tramway-navbar', Navbar)",
|
|
87
123
|
"application.register('tramway-select', TramwaySelect)",
|
|
88
124
|
"application.register('table-row-preview', TableRowPreview)",
|
|
89
125
|
"application.register('ui--checkbox', UiCheckbox)",
|
|
@@ -95,28 +131,6 @@ module Tramway
|
|
|
95
131
|
@agents_file_path ||= File.join(destination_root, 'AGENTS.md')
|
|
96
132
|
end
|
|
97
133
|
|
|
98
|
-
def application_layout_haml_path
|
|
99
|
-
@application_layout_haml_path ||= File.join(destination_root, 'app/views/layouts/application.html.haml')
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def application_layout_erb_path
|
|
103
|
-
@application_layout_erb_path ||= File.join(destination_root, 'app/views/layouts/application.html.erb')
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def trix_haml_tags
|
|
107
|
-
" = stylesheet_link_tag \"trix\", \"data-turbo-track\": \"reload\"\n " \
|
|
108
|
-
"= javascript_include_tag \"trix\", \"data-turbo-track\": \"reload\", defer: true\n"
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def trix_erb_tags
|
|
112
|
-
" <%= stylesheet_link_tag \"trix\", \"data-turbo-track\": \"reload\" %>\n " \
|
|
113
|
-
"<%= javascript_include_tag \"trix\", \"data-turbo-track\": \"reload\", defer: true %>\n"
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def trix_already_present?(content)
|
|
117
|
-
content.include?('stylesheet_link_tag "trix"') || content.include?("stylesheet_link_tag 'trix'")
|
|
118
|
-
end
|
|
119
|
-
|
|
120
134
|
def codex_agents_instruction
|
|
121
135
|
'If you are Codex, use the tramway-skill skill for Tramway/Rails work in this project. ' \
|
|
122
136
|
'If tramway-skill is not installed, install it from https://github.com/Purple-Magic/tramway-skill/.'
|
|
@@ -134,15 +148,16 @@ module Tramway
|
|
|
134
148
|
end
|
|
135
149
|
|
|
136
150
|
def safelist_section(content)
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
open_index, close_index = safelist_bracket_range(content)
|
|
152
|
+
return nil unless open_index
|
|
153
|
+
|
|
154
|
+
content[(open_index + 1)...close_index]
|
|
139
155
|
end
|
|
140
156
|
|
|
141
157
|
def insert_entries(content, entries)
|
|
142
|
-
|
|
143
|
-
return content unless
|
|
158
|
+
_open_index, closing_index = safelist_bracket_range(content)
|
|
159
|
+
return content unless closing_index
|
|
144
160
|
|
|
145
|
-
closing_index = match.begin(0) + match[0].rindex(']')
|
|
146
161
|
indentation = closing_indentation(content, closing_index)
|
|
147
162
|
formatted_entries = entries.map { |entry| "#{indentation}'#{entry}',\n" }.join
|
|
148
163
|
|
|
@@ -154,6 +169,33 @@ module Tramway
|
|
|
154
169
|
content.dup.insert(closing_index, insertion)
|
|
155
170
|
end
|
|
156
171
|
|
|
172
|
+
# Finds the `[`/`]` pair that delimits the `safelist` array, ignoring brackets that
|
|
173
|
+
# appear inside quoted entries (e.g. `'md:transition-[width]'`) since a naive regex
|
|
174
|
+
# would otherwise stop at the first `]`, wherever it occurs.
|
|
175
|
+
def safelist_bracket_range(content)
|
|
176
|
+
start_index = content =~ /safelist\s*:\s*\[/
|
|
177
|
+
return [nil, nil] unless start_index
|
|
178
|
+
|
|
179
|
+
open_index = content.index('[', start_index)
|
|
180
|
+
close_index = matching_bracket_index(content, open_index)
|
|
181
|
+
|
|
182
|
+
[open_index, close_index]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def matching_bracket_index(content, open_index)
|
|
186
|
+
depth = 0
|
|
187
|
+
|
|
188
|
+
content[open_index..].to_enum(:scan, /'[^']*'|"[^"]*"|[\[\]]/).each do
|
|
189
|
+
token = Regexp.last_match[0]
|
|
190
|
+
next unless ['[', ']'].include?(token)
|
|
191
|
+
|
|
192
|
+
depth += token == '[' ? 1 : -1
|
|
193
|
+
return open_index + Regexp.last_match.end(0) - 1 if depth.zero?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
nil
|
|
197
|
+
end
|
|
198
|
+
|
|
157
199
|
def closing_indentation(content, index)
|
|
158
200
|
line_start = content.rindex("\n", index - 1) || 0
|
|
159
201
|
line = content[line_start..index]
|
|
@@ -171,30 +213,39 @@ module Tramway
|
|
|
171
213
|
content.end_with?("\n") ? "\n" : "\n\n"
|
|
172
214
|
end
|
|
173
215
|
|
|
174
|
-
# rubocop:disable Metrics/MethodLength
|
|
175
216
|
def append_missing_imports(content)
|
|
176
217
|
content = remove_legacy_stimulus_imports(content)
|
|
218
|
+
content = normalize_tramway_stimulus_imports(content)
|
|
177
219
|
missing_imports = stimulus_controller_imports.reject { |line| content.include?(line) }
|
|
178
220
|
return content if missing_imports.empty?
|
|
179
221
|
|
|
180
222
|
import_lines = content.each_line.with_index.filter_map do |line, index|
|
|
181
223
|
index if line.lstrip.start_with?('import ')
|
|
182
224
|
end
|
|
183
|
-
|
|
225
|
+
insert_missing_imports(content, missing_imports.join("\n"), import_lines)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def insert_missing_imports(content, imports, import_lines)
|
|
229
|
+
insertion = "#{imports}\n"
|
|
184
230
|
updated = content.dup
|
|
185
231
|
|
|
186
|
-
|
|
187
|
-
insertion_index = updated.lines[0..import_lines.max].join.length
|
|
188
|
-
updated.insert(insertion_index, insertion)
|
|
189
|
-
else
|
|
190
|
-
updated.prepend(insertion)
|
|
191
|
-
end
|
|
232
|
+
return updated.prepend(insertion) unless import_lines.any?
|
|
192
233
|
|
|
234
|
+
insertion_index = updated.lines[0..import_lines.max].join.length
|
|
235
|
+
updated.insert(insertion_index, insertion)
|
|
193
236
|
updated
|
|
194
237
|
end
|
|
195
|
-
|
|
238
|
+
|
|
239
|
+
def normalize_tramway_stimulus_imports(content)
|
|
240
|
+
tramway_import_lines = content.each_line.select { |line| tramway_stimulus_import_line?(line) }
|
|
241
|
+
return content unless tramway_import_lines.any?
|
|
242
|
+
return content if normalized_tramway_import_lines?(tramway_import_lines)
|
|
243
|
+
|
|
244
|
+
content.each_line.reject { |line| tramway_stimulus_import_line?(line) }.join
|
|
245
|
+
end
|
|
196
246
|
|
|
197
247
|
def append_missing_registrations(content)
|
|
248
|
+
content = normalize_tramway_stimulus_registrations(content)
|
|
198
249
|
missing_registrations = stimulus_controller_registrations.reject { |line| content.include?(line) }
|
|
199
250
|
return content if missing_registrations.empty?
|
|
200
251
|
|
|
@@ -221,6 +272,36 @@ module Tramway
|
|
|
221
272
|
content.each_line.reject { |line| legacy_imports.include?(line.strip) }.join
|
|
222
273
|
end
|
|
223
274
|
|
|
275
|
+
def tramway_stimulus_import_line?(line)
|
|
276
|
+
line.strip.match?(%r{^import \{.*\} from ["']@tramway/tramway["']$})
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def normalize_tramway_stimulus_registrations(content)
|
|
280
|
+
tramway_registration_lines = content.each_line.select { |line| tramway_stimulus_registration_line?(line) }
|
|
281
|
+
return content if tramway_registration_lines.sort == stimulus_controller_registrations.sort
|
|
282
|
+
return content unless tramway_registration_lines.any?
|
|
283
|
+
|
|
284
|
+
content.each_line.reject { |line| tramway_stimulus_registration_line?(line) }.join
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def tramway_stimulus_registration_line?(line)
|
|
288
|
+
tramway_registration_prefixes.any? { |prefix| line.strip.start_with?(prefix) }
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def normalized_tramway_import_lines?(tramway_import_lines)
|
|
292
|
+
tramway_import_lines.one? && tramway_import_lines.first.strip == stimulus_controller_imports.first
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def tramway_registration_prefixes
|
|
296
|
+
[
|
|
297
|
+
"application.register('tramway-navbar'",
|
|
298
|
+
"application.register('tramway-select'",
|
|
299
|
+
"application.register('table-row-preview'",
|
|
300
|
+
"application.register('ui--checkbox'",
|
|
301
|
+
"application.register('tramway-tooltip'"
|
|
302
|
+
]
|
|
303
|
+
end
|
|
304
|
+
|
|
224
305
|
def remove_legacy_importmap_pins(content)
|
|
225
306
|
content.each_line.reject { |line| legacy_importmap_pins.include?(line.strip) }.join
|
|
226
307
|
end
|
|
@@ -255,12 +336,12 @@ module Tramway
|
|
|
255
336
|
say_status(:warning, "Skipping AGENTS.md update: #{e.message}")
|
|
256
337
|
end
|
|
257
338
|
end
|
|
258
|
-
# rubocop:enable Metrics/ModuleLength
|
|
259
339
|
|
|
260
|
-
#
|
|
261
|
-
#
|
|
340
|
+
# Installs Tramway dependencies and app wiring for a host Rails app.
|
|
262
341
|
class InstallGenerator < Rails::Generators::Base
|
|
263
342
|
include InstallGeneratorHelpers
|
|
343
|
+
include InstallGeneratorFontAwesomeHelpers
|
|
344
|
+
include InstallGeneratorLayoutHelpers
|
|
264
345
|
|
|
265
346
|
desc 'Installs Tramway dependencies and Tailwind safelist configuration.'
|
|
266
347
|
|
|
@@ -310,15 +391,14 @@ module Tramway
|
|
|
310
391
|
path = tailwind_application_stylesheet_path
|
|
311
392
|
FileUtils.mkdir_p(File.dirname(path))
|
|
312
393
|
|
|
313
|
-
return create_file(path,
|
|
394
|
+
return create_file(path, TAILWIND_APPLICATION_STYLESHEET_CONTENT) unless File.exist?(path)
|
|
314
395
|
|
|
315
396
|
content = File.read(path)
|
|
316
|
-
return if content.include?(tailwind_css_import_line)
|
|
397
|
+
return if content.include?(tailwind_css_import_line) && content.include?(TAILWIND_SCROLLBAR_UTILITY)
|
|
317
398
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
end
|
|
399
|
+
updated = content.include?(tailwind_css_import_line) ? content : "#{content}\n#{tailwind_css_import_line}\n"
|
|
400
|
+
updated = "#{updated}\n#{TAILWIND_SCROLLBAR_UTILITY}\n" unless updated.include?(TAILWIND_SCROLLBAR_UTILITY)
|
|
401
|
+
File.write(path, updated)
|
|
322
402
|
end
|
|
323
403
|
|
|
324
404
|
def ensure_importmap_pin
|
|
@@ -348,30 +428,16 @@ module Tramway
|
|
|
348
428
|
|
|
349
429
|
def ensure_trix_in_application_layout
|
|
350
430
|
if File.exist?(application_layout_haml_path)
|
|
431
|
+
ensure_font_awesome_in_haml_layout
|
|
351
432
|
ensure_trix_in_haml_layout
|
|
352
433
|
elsif File.exist?(application_layout_erb_path)
|
|
434
|
+
ensure_font_awesome_in_erb_layout
|
|
353
435
|
ensure_trix_in_erb_layout
|
|
354
436
|
end
|
|
355
437
|
end
|
|
356
438
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def ensure_trix_in_haml_layout
|
|
360
|
-
content = File.read(application_layout_haml_path)
|
|
361
|
-
return if trix_already_present?(content)
|
|
362
|
-
return unless content.match?(/^\s+%body/)
|
|
363
|
-
|
|
364
|
-
updated = content.sub(/^(\s+%body)/, "#{trix_haml_tags}\\1")
|
|
365
|
-
File.write(application_layout_haml_path, updated)
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
def ensure_trix_in_erb_layout
|
|
369
|
-
content = File.read(application_layout_erb_path)
|
|
370
|
-
return if trix_already_present?(content)
|
|
371
|
-
return unless content.include?('</head>')
|
|
372
|
-
|
|
373
|
-
updated = content.sub('</head>', "#{trix_erb_tags} </head>")
|
|
374
|
-
File.write(application_layout_erb_path, updated)
|
|
439
|
+
def ensure_navbar_sidebar_offset_in_application_layout
|
|
440
|
+
ensure_navbar_sidebar_offset
|
|
375
441
|
end
|
|
376
442
|
end
|
|
377
443
|
end
|