cafe_car 0.1.1 → 0.1.2

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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +155 -40
  3. data/Rakefile +9 -1
  4. data/app/assets/fonts/Lexend.css +7 -0
  5. data/app/assets/fonts/Lexend.ttf +0 -0
  6. data/app/assets/stylesheets/cafe_car/themes/defaults.css +58 -59
  7. data/app/assets/stylesheets/cafe_car/tooltips.css +20 -0
  8. data/app/assets/stylesheets/cafe_car/utility.css +1 -2
  9. data/app/assets/stylesheets/cafe_car.css +17 -6
  10. data/app/assets/stylesheets/ui/Alert.css +2 -1
  11. data/app/assets/stylesheets/ui/Button.css +6 -6
  12. data/app/assets/stylesheets/ui/Card.css +7 -3
  13. data/app/assets/stylesheets/ui/Chat.css +33 -0
  14. data/app/assets/stylesheets/ui/Close.css +11 -0
  15. data/app/assets/stylesheets/ui/Grid.css +2 -0
  16. data/app/assets/stylesheets/ui/Icon.css +3 -3
  17. data/app/assets/stylesheets/ui/Layout.css +20 -13
  18. data/app/assets/stylesheets/ui/Modal.css +5 -12
  19. data/app/assets/stylesheets/ui/Navigation.css +13 -5
  20. data/app/assets/stylesheets/ui/Page.css +42 -3
  21. data/app/assets/stylesheets/ui/Table.css +27 -56
  22. data/app/assets/stylesheets/ui/components.css +2 -0
  23. data/app/controllers/cafe_car/examples_controller.rb +1 -1
  24. data/app/controllers/cafe_car/sessions_controller.rb +30 -0
  25. data/app/controllers/concerns/cafe_car/authentication.rb +61 -0
  26. data/app/javascript/cafe_car.js +16 -11
  27. data/app/models/cafe_car/session.rb +18 -0
  28. data/app/policies/cafe_car/session_policy.rb +19 -0
  29. data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +5 -4
  30. data/app/presenters/cafe_car/code_presenter.rb +18 -0
  31. data/app/presenters/cafe_car/date_presenter.rb +1 -0
  32. data/app/presenters/cafe_car/date_time_presenter.rb +2 -2
  33. data/app/presenters/cafe_car/enumerable_presenter.rb +1 -1
  34. data/app/presenters/cafe_car/hash_presenter.rb +3 -8
  35. data/app/presenters/cafe_car/presenter.rb +22 -12
  36. data/app/presenters/cafe_car/string_presenter.rb +2 -2
  37. data/app/ui/cafe_car/ui/button.rb +2 -1
  38. data/app/ui/cafe_car/ui/card.rb +18 -0
  39. data/app/ui/cafe_car/ui/grid.rb +30 -0
  40. data/app/ui/cafe_car/ui/layout.rb +7 -0
  41. data/app/ui/cafe_car/ui/page.rb +5 -1
  42. data/app/views/application/_body.html.haml +2 -1
  43. data/app/views/application/_controls.html.haml +1 -0
  44. data/app/views/application/_debug.html.haml +9 -2
  45. data/app/views/application/_errors.html.haml +4 -8
  46. data/app/views/application/_grid.html.haml +1 -1
  47. data/app/views/application/_grid_item.html.haml +1 -1
  48. data/app/views/application/_head.html.haml +1 -0
  49. data/app/views/application/_index.html.haml +6 -2
  50. data/app/views/application/_index_actions.html.haml +3 -3
  51. data/app/views/application/_navigation.html.haml +7 -0
  52. data/app/views/application/_navigation_links.html.haml +1 -1
  53. data/app/views/application/_notes.html.haml +1 -0
  54. data/app/views/application/_popup.html.haml +7 -0
  55. data/app/views/cafe_car/application/edit.html.haml +1 -1
  56. data/app/views/cafe_car/application/edit.turbo_stream.haml +3 -5
  57. data/app/views/cafe_car/application/index.html.haml +3 -0
  58. data/app/views/cafe_car/application/new.turbo_stream.haml +5 -6
  59. data/app/views/cafe_car/application/show.html.haml +2 -2
  60. data/app/views/cafe_car/examples/ui/_chat.html.haml +3 -0
  61. data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -1
  62. data/app/views/cafe_car/examples/ui/_modal.html.haml +1 -1
  63. data/app/views/passwords_mailer/reset.html.haml +5 -0
  64. data/app/views/passwords_mailer/reset.text.erb +4 -0
  65. data/app/views/ui/_card.html.haml +6 -11
  66. data/app/views/ui/_field.html.haml +1 -7
  67. data/app/views/ui/_modal_close.html.haml +1 -2
  68. data/app/views/ui/_page.html.haml +6 -12
  69. data/config/brakeman.ignore +3 -3
  70. data/config/locales/en.yml +10 -2
  71. data/config/routes.rb +5 -1
  72. data/db/migrate/20251005220017_create_slugs.rb +2 -2
  73. data/lib/cafe_car/active_record.rb +1 -1
  74. data/lib/cafe_car/application_responder.rb +6 -0
  75. data/lib/cafe_car/attributes.rb +1 -1
  76. data/lib/cafe_car/auto_resolver.rb +1 -1
  77. data/lib/cafe_car/component.rb +102 -39
  78. data/lib/cafe_car/context.rb +5 -4
  79. data/lib/cafe_car/controller/filtering.rb +9 -1
  80. data/lib/cafe_car/controller.rb +52 -13
  81. data/lib/cafe_car/core_ext/array.rb +13 -0
  82. data/lib/cafe_car/core_ext/hash.rb +15 -0
  83. data/lib/cafe_car/core_ext/module.rb +15 -0
  84. data/lib/cafe_car/core_ext.rb +0 -2
  85. data/lib/cafe_car/current.rb +4 -1
  86. data/lib/cafe_car/engine.rb +9 -2
  87. data/lib/cafe_car/field_builder.rb +1 -1
  88. data/lib/cafe_car/field_info.rb +14 -12
  89. data/lib/cafe_car/fields.rb +7 -0
  90. data/lib/cafe_car/filter/field_info.rb +1 -1
  91. data/lib/cafe_car/filter/form_builder.rb +2 -2
  92. data/lib/cafe_car/filter_builder.rb +1 -1
  93. data/lib/cafe_car/form_builder.rb +1 -1
  94. data/lib/cafe_car/generators.rb +1 -1
  95. data/lib/cafe_car/helpers.rb +37 -10
  96. data/lib/cafe_car/href_builder.rb +35 -9
  97. data/lib/cafe_car/input_builder.rb +1 -1
  98. data/lib/cafe_car/link_builder.rb +14 -11
  99. data/lib/cafe_car/model.rb +2 -2
  100. data/lib/cafe_car/navigation.rb +10 -10
  101. data/lib/cafe_car/option_helpers.rb +11 -5
  102. data/lib/cafe_car/param_parser.rb +10 -6
  103. data/lib/cafe_car/policy.rb +2 -2
  104. data/lib/cafe_car/query_builder.rb +3 -3
  105. data/lib/cafe_car/resolver.rb +5 -1
  106. data/lib/cafe_car/routing.rb +1 -1
  107. data/lib/cafe_car/table/builder.rb +3 -2
  108. data/lib/cafe_car/table/head_builder.rb +2 -2
  109. data/lib/cafe_car/table/label_builder.rb +1 -1
  110. data/lib/cafe_car/table/row_builder.rb +5 -7
  111. data/lib/cafe_car/table_builder.rb +3 -3
  112. data/lib/cafe_car/ui.rb +2 -0
  113. data/lib/cafe_car/version.rb +1 -1
  114. data/lib/cafe_car/visitors.rb +2 -2
  115. data/lib/cafe_car.rb +25 -0
  116. data/lib/generators/cafe_car/controller/templates/controller.rb.tt +1 -1
  117. data/lib/generators/cafe_car/install/install_generator.rb +0 -1
  118. data/lib/generators/cafe_car/sessions/USAGE +17 -0
  119. data/lib/generators/cafe_car/sessions/sessions_generator.rb +29 -0
  120. data/lib/generators/cafe_car/sessions/templates/create_sessions.rb.tt +12 -0
  121. data/lib/tasks/holdco_tasks.rake +532 -0
  122. data/lib/tasks/templates/tasks_header.md +37 -0
  123. metadata +76 -48
  124. data/app/models/cafe_car/slug.rb +0 -3
  125. data/app/views/ui/_grid.html.haml +0 -17
  126. data/app/views/ui/_layout_menu.html.haml +0 -2
@@ -0,0 +1,18 @@
1
+ module CafeCar
2
+ class Session < ApplicationRecord
3
+ belongs_to :user, class_name: CafeCar.user_class_name
4
+
5
+ attribute :email, :string
6
+ attribute :password, :password
7
+ attribute :login, :boolean, default: true
8
+
9
+ validates :email, :password, presence: true, if: :login?
10
+
11
+ before_validation :authenticate, if: :login?
12
+
13
+ def authenticate
14
+ self.user = CafeCar.user_class.authenticate_by(email:, password:) or
15
+ errors.add(:base, "Could not find user with given credentials")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module CafeCar
2
+ class SessionPolicy < ::ApplicationPolicy
3
+ def index? = admin?
4
+ def create? = true
5
+ def show? = true
6
+ def destroy? = admin? || mine?
7
+ def update? = false
8
+
9
+ def title_attribute = :user
10
+
11
+ def permitted_attributes
12
+ [ :email, :password ]
13
+ end
14
+
15
+ class Scope < Scope
16
+ def resolve = scope
17
+ end
18
+ end
19
+ end
@@ -3,12 +3,13 @@ module CafeCar
3
3
  class AttachmentPresenter < CafeCar[:Presenter]
4
4
  option :size
5
5
 
6
- # def url = object.representation(resize_to_limit: [100, 100]).processed.path
7
- def url = object.url
8
- def blank = options.fetch(:blank) { "(none)" }
6
+ # def url = object.representation(resize_to_limit: [300, 300])&.processed&.url
7
+ def url = object.try(:url)
8
+ def blank = options[:blank]
9
+ def logo = self
9
10
 
10
11
  def image
11
- @template.image_tag url, class: ui.class(:image, size) if url
12
+ @template.image_tag url, **options, class: ui.class(:image, size) if url
12
13
  end
13
14
 
14
15
  def preview = image || blank
@@ -0,0 +1,18 @@
1
+ module CafeCar
2
+ class CodePresenter < self[:Presenter]
3
+ def formatter = Rouge::Formatters::HTML.new
4
+ def source = object.to_s
5
+ def formatted = formatter.format(lexer.lex(source))
6
+
7
+ def options_lexer = options[:lang].try { Rouge::Lexer.find(_1) }
8
+ def guess_lexer = Rouge::Lexer.guess(source:)
9
+
10
+ def lexer
11
+ @lexer ||= options_lexer || guess_lexer
12
+ end
13
+
14
+ def to_html
15
+ ui.Code(formatted.html_safe, class: [ "highlight", lexer.tag ])
16
+ end
17
+ end
18
+ end
@@ -1,4 +1,5 @@
1
1
  module CafeCar
2
2
  class DatePresenter < DateTimePresenter
3
+ def to_html = long
3
4
  end
4
5
  end
@@ -3,9 +3,9 @@ module CafeCar
3
3
  def distance = @template.time_ago_in_words(object)
4
4
  def words = object.past? ? "#{distance} ago" : "in #{distance}"
5
5
  def datetime = object.iso8601
6
- def title = l(object, format: :long)
6
+ def long = l(object, format: :long)
7
7
 
8
8
  def string = words
9
- def to_html = tag.time words, datetime:, title:
9
+ def to_html = tag.time words, datetime:, "data-tip": long
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module CafeCar
5
5
 
6
6
  def to_html
7
7
  object.map { present(_1) }
8
- .then { options[:count] == object.count ? _1 : [_1, "..."] }
8
+ .then { options[:count] == object.count ? _1 : [ _1, "..." ] }
9
9
  .then { safe_join(_1, ", ") }
10
10
  .then { with_count _1 }
11
11
  end
@@ -1,11 +1,6 @@
1
1
  module CafeCar
2
- class HashPresenter < self[:Presenter]
3
- def formatter = Rouge::Formatters::HTML.new
4
- def lexer = Rouge::Lexers::JSON.new
5
- def source = JSON.pretty_generate(object)
6
-
7
- def to_html
8
- tag.code(formatter.format(lexer.lex(source)).html_safe, class: 'highlight')
9
- end
2
+ class HashPresenter < self[:CodePresenter]
3
+ def lexer = Rouge::Lexers::JSON.new
4
+ def source = JSON.pretty_generate(object)
10
5
  end
11
6
  end
@@ -31,12 +31,12 @@ module CafeCar
31
31
  end
32
32
 
33
33
  def self.names(klass)
34
- return [klass.to_s.classify] if klass.is_a?(Symbol)
34
+ return [ klass.to_s.classify ] if klass.is_a?(Symbol)
35
35
  klass.ancestors.lazy.map(&:name).compact
36
36
  end
37
37
 
38
38
  def self.show(method, block = nil, **)
39
- show_defaults[method].merge!({block:}.compact, **)
39
+ show_defaults[method].merge!({ block: }.compact, **)
40
40
  end
41
41
 
42
42
  def initialize(template, object, **options, &block)
@@ -61,18 +61,21 @@ module CafeCar
61
61
 
62
62
  def to_html
63
63
  return render(object:, partial:) if has_partial?
64
- return preview if context?(:a)
65
64
  return blank if captured.blank? && blank
66
- link_to(href) { preview } rescue preview
65
+ preview
67
66
  end
68
67
 
69
68
  def title(*, **, &) = show(policy.title_attribute, *, blank: show(:id), **, &)
70
69
  def logo(*, **, &) = show(policy.logo_attribute, *, **, &)
71
70
 
71
+ def timestamps(**, &)
72
+ attributes(*model.info.fields.timestamps.names, **, &)
73
+ end
74
+
72
75
  def attributes(*methods, except: nil, **options, &block)
73
76
  methods = policy.displayable_attributes if methods.empty?
74
77
  methods = methods.flatten.compact
75
- methods -= [*except]
78
+ methods -= [ *except ]
76
79
  capture do
77
80
  methods.map do |method|
78
81
  attribute(method, **options, &block)
@@ -81,10 +84,17 @@ module CafeCar
81
84
  end
82
85
 
83
86
  def links = link(object)
84
- def href = href_for(object)
87
+
88
+ # The object's canonical path, or nil when it isn't independently routable
89
+ # (e.g. a nested resource like a line item).
90
+ def href
91
+ href_for(object)
92
+ rescue NoMethodError, ActionController::UrlGenerationError
93
+ nil
94
+ end
85
95
 
86
96
  def preview(**, &)
87
- ui.Row :space do
97
+ ui.Row :space, href: do
88
98
  concat logo(size: :icon)
89
99
  concat title
90
100
  end
@@ -95,7 +105,7 @@ module CafeCar
95
105
  return "" if content.blank?
96
106
 
97
107
  ui.Field do |f|
98
- concat f.Label(safe_join([human(method), *info_circle(method)], " "), tag: :strong)
108
+ concat f.Label(safe_join([ human(method), *info_circle(method) ], " "), tag: :strong)
99
109
  concat f.Content(content)
100
110
  end
101
111
  end
@@ -125,16 +135,16 @@ module CafeCar
125
135
  end
126
136
 
127
137
  def info_circle(method, *args, **opts, &block)
128
- title = info(method).hint
129
- return unless title
130
- ui.InfoCircle(*args, title:, **opts, &block)
138
+ tip = info(method).hint
139
+ return unless tip
140
+ ui.InfoCircle(*args, tip:, **opts, &block)
131
141
  end
132
142
 
133
143
  def controls(**options, &block)
134
144
  render("controls", object:, options:, &block)
135
145
  end
136
146
 
137
- def i18n_vars(names) = names.merge(*names.map { {_1.to_s.downcase.to_sym => _2.downcase} })
147
+ def i18n_vars(names) = names.merge(*names.map { { _1.to_s.downcase.to_sym => _2.downcase } })
138
148
 
139
149
  def i18n(action, scope: nil, **)
140
150
  vars = i18n_vars Action: t(action, default: action.to_s.humanize),
@@ -9,9 +9,9 @@ module CafeCar
9
9
 
10
10
  case object
11
11
  when %r{^https?://.+\.(png|jpe?g|svg)$}
12
- @template.image_tag object, style: 'width: 1em'
12
+ @template.image_tag object, style: "width: 1em"
13
13
  when %r{^https?://}
14
- link_to(txt, object, target: '_blank', rel: "noopener")
14
+ link_to(txt, object, target: "_blank", rel: "noopener")
15
15
  else
16
16
  txt
17
17
  end
@@ -2,7 +2,8 @@ module CafeCar
2
2
  module UI
3
3
  component :Button do
4
4
  flag :current
5
- flag :primary, :danger
5
+ flag :primary
6
+ flag :danger
6
7
  end
7
8
  end
8
9
  end
@@ -0,0 +1,18 @@
1
+ module CafeCar
2
+ module UI
3
+ component :Card do
4
+ flag :slim
5
+ option :title
6
+ option :subtitle
7
+ option :image
8
+ option :actions
9
+ option :tabs
10
+
11
+ component :Head, :Aside, :Body, :Foot
12
+ component :Title, tag: :h2
13
+ component :Subtitle, tag: :h3
14
+ component :Image, tag: :img
15
+ component :Actions
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module CafeCar
2
+ module UI
3
+ component :Grid do
4
+ option :columns
5
+ option :template
6
+ option :style
7
+
8
+ def attributes
9
+ super.merge(style:)
10
+ end
11
+
12
+ def style
13
+ style = [ *@style ]
14
+ style << "grid-template: #{template}" if template?
15
+ style << "grid-template-columns: #{columns}" if columns?
16
+ style.compact_blank.join("; ").presence
17
+ end
18
+
19
+ def columns
20
+ case @columns
21
+ in Numeric
22
+ "repeat(#{@columns}, 1fr)"
23
+ in [ a, b ]
24
+ "repeat(auto-fill, minmax(#{a}, #{b}))"
25
+ else @columns
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module CafeCar
2
+ module UI
3
+ component :Layout do
4
+ component :Menu, tag: :input, type: :checkbox
5
+ end
6
+ end
7
+ end
@@ -2,9 +2,13 @@ module CafeCar
2
2
  module UI
3
3
  component :Page do
4
4
  flag :slim
5
+ option :title
6
+ option :actions
7
+ option :tabs
5
8
 
6
9
  component :Head, :Aside, :Body, :Foot
7
- component :Title, :Actions
10
+ component :Title, tag: :h2
11
+ component :Actions
8
12
  end
9
13
  end
10
14
  end
@@ -1,6 +1,7 @@
1
1
  = Layout do |l|
2
2
  = l.Sidebar do
3
- %h2= t(:title, scope: [:application], default: Rails.application.name.titleize)
3
+ - title = t(:title, scope: [:application], default: Rails.application.name.titleize)
4
+ %h2= link_to title, root_path
4
5
  = render "navigation"
5
6
  = l.Main { yield }
6
7
  = l.Footer { render("footer") }
@@ -4,6 +4,7 @@
4
4
  - destroy = options.delete(:destroy) { true }
5
5
  - links = link(object)
6
6
 
7
+ -# TODO: Handle resources with missing routes
7
8
  = Controls tag: :span, **options do |c|
8
9
  = c.Link links.index if index
9
10
  = c.Link links.show if show
@@ -3,9 +3,16 @@
3
3
  = Card title: "Debug" do |card|
4
4
  = card.Section do
5
5
  = Field label: "Params" do
6
- = Code parsed_params.pretty_inspect
6
+ = p parsed_params
7
7
 
8
8
  = Field label: "Query" do
9
- = Code scope.to_sql
9
+ = p scope.to_sql, as: :code, lang: :sql
10
+
11
+ - if session = current_session
12
+ = Field label: "Session" do
13
+ = p session.as_json
14
+
15
+ = Field label: "Cookies" do
16
+ = p cookies.signed.as_json.to_s, as: :code
10
17
 
11
18
  = yield :debug
@@ -1,8 +1,4 @@
1
- - return unless (errors = f.object.errors.full_messages.presence)
2
-
3
- = Error do
4
- Correct the errors above.
5
-
6
- -# %ul
7
- -# - errors.each do |message|
8
- -# %li= Error { message }
1
+ - if errors = f.object.errors.full_messages.presence
2
+ = Error do
3
+ = to_sentence(f.object.errors.messages[:base]).presence || "Correct the errors above."
4
+ %p= to_sentence(errors)
@@ -1,3 +1,3 @@
1
- = Grid columns: "repeat(auto-fit, minmax(250px, 1fr))" do |grid|
1
+ = Grid do |grid|
2
2
  - present(objects).each do |object|
3
3
  = render "grid_item", object:, grid:
@@ -1 +1 @@
1
- = Card title: object.title, image: object.logo, href: object
1
+ = Card title: object.title, image: object.logo(href: object), actions: object.controls
@@ -7,6 +7,7 @@
7
7
  %meta{name: "turbo-refresh-method", content: "morph"}
8
8
  %meta{name: "turbo-refresh-scroll", content: "preserve"}
9
9
  %meta{name: "view-transition", content: "same-origin"}
10
+ %meta{name: "turbo-cache-control", content: "no-preview"}
10
11
  = csrf_meta_tags
11
12
  = csp_meta_tag
12
13
 
@@ -1,4 +1,8 @@
1
- - page = params.fetch(:page, 1)
2
1
  = render(view).presence or render("empty")
3
- .center= page_entries_info objects
2
+
3
+ .center
4
+ = page_entries_info objects
5
+ - if filtered?
6
+ matching your filters. #{link(model).index "View all"}
7
+
4
8
  = paginate objects
@@ -1,7 +1,7 @@
1
1
  = Group do
2
- = Button href: url_for(request.params.merge(view: :grid)), title: "Grid View" do
2
+ = Button href: view_url(:grid), tip: "Grid View" do
3
3
  = icon(:view_grid)
4
- = Button href: url_for(request.params.except(:view)), title: "Table View" do
4
+ = Button href: view_url(:table), tip: "Table View" do
5
5
  = icon(:table_rows)
6
6
 
7
- = link(model).new(class: ui.Button(:primary).class_name) { icon(:plus) + _1}
7
+ = link(model.build).new(class: ui.Button(:primary).class_name, params: dot_params) { icon(:plus) + _1}
@@ -1,2 +1,9 @@
1
1
  = Navigation tag: :nav do |nav|
2
2
  = render "navigation_links", nav:
3
+
4
+ - if try(:authenticated?)
5
+ = nav.Link(:bottom, href: try(:session_path)) { p(current_session) }
6
+ - elsif href = try(:new_session_path)
7
+ = nav.Link(:bottom, href:) do
8
+ = Icon t(:new_session, scope: "navigation.icon").to_sym
9
+ Sign In
@@ -2,4 +2,4 @@
2
2
  - if name.present?
3
3
  %h4= name.join(" ").titleize
4
4
  - routes.each do |r|
5
- = r.link class: nav.Link.class_name
5
+ = r.link
@@ -1,4 +1,5 @@
1
1
  - return unless object.respond_to? :notes
2
+ - href_for(:notes) rescue return
2
3
 
3
4
  - ui(object.notes).each do |note|
4
5
  = card.Section :block do
@@ -0,0 +1,7 @@
1
+ = turbo_stream.append_all("body") do
2
+ = Modal :fixed, id: dom_id(object, :modal), class: "popup", data: {turbo_permanent: true} do |m|
3
+ = yield
4
+
5
+ -# = turbo_stream.after_all ui.Page.Body.selector do
6
+ -# = ui.Page.Aside class: "popup", id: dom_id(object, :popup), data: {turbo_permanent: true} do
7
+ -# = yield
@@ -1,6 +1,6 @@
1
1
  - title = breadcrumbs(link(object).index, link(object), title("Edit #{model_name.human}"))
2
2
 
3
- = Page title: do |page|
3
+ = Page :slim, title: do |page|
4
4
  = page.Body do
5
5
  = render "alerts"
6
6
  = ui.Card do |card|
@@ -1,9 +1,7 @@
1
1
  - if action.edit?
2
- -# = turbo_stream.navigate(request.fullpath)
3
- = turbo_stream.append_all("body") do
4
- = Modal :fixed, id: dom_id(object, :modal), data: {turbo_permanent: true} do |m|
5
- = Card title: "Edit #{model_name.human}", actions: m.Close("✕") do |card|
6
- = render "form", card:
2
+ = render "popup" do
3
+ = Card title: "Edit #{model_name.human}", actions: Close { icon(:xmark_circle_solid) } do |card|
4
+ = render "form", card:
7
5
  - else
8
6
  = turbo_stream.replace(dom_id(object, :edit)) do
9
7
  = render "form", card: ui.Card
@@ -10,3 +10,6 @@
10
10
  = render "alerts"
11
11
  = body
12
12
  = render "debug" if debug?
13
+
14
+ = page.Foot do
15
+ = yield :foot
@@ -1,9 +1,8 @@
1
1
  - if action.new?
2
- -# = turbo_stream.navigate(request.fullpath)
3
- = turbo_stream.append_all("body") do
4
- = Modal :fixed, id: dom_id(object, :modal), data: {turbo_permanent: true} do |m|
5
- = Card title: "New #{model_name.human}", actions: m.Close("✕") do |card|
6
- = render "form", card:
7
- - else # create
2
+ = render "popup" do
3
+ = Card title: "New #{model_name.human}", actions: Close { icon(:xmark_circle_solid) } do |card|
4
+ = render "form", card:
5
+
6
+ - else # error during :create action
8
7
  = turbo_stream.replace(dom_id(object, :new)) do
9
8
  = render "form", card: ui.Card
@@ -1,8 +1,8 @@
1
1
  - title = breadcrumbs(link(object).index, title(object))
2
- - summary = ui(object).attributes :updated_at, :created_at, try: true
2
+ - summary = ui(object).timestamps try: true
3
3
  - body = render("show")
4
4
 
5
- = turbo_stream_from(object)
5
+ = turbo_stream_from(object) if object.persisted?
6
6
  = Page :full, title: do |page|
7
7
  = page.Aside do
8
8
  = yield :left
@@ -0,0 +1,3 @@
1
+ = Chat do |c|
2
+ = c.Message :local, "Hello, world!"
3
+ = c.Message :remote, "And hello to you!"
@@ -1 +1 @@
1
- = InfoCircle "Hi", title: "Hello"
1
+ = InfoCircle "Hi", tip: "Hello"
@@ -1,4 +1,4 @@
1
1
  = Modal do |m|
2
2
  = m.Body do
3
- = Card title: "Card in modal", actions: m.Close("✕") do |c|
3
+ = Card title: "Card in modal", actions: m.Close { icon(:xmark_circle_solid) } do |c|
4
4
  = c.Section "Body"
@@ -0,0 +1,5 @@
1
+ %p
2
+ You can reset your password on
3
+ #{link_to "this password reset page", edit_password_url(@user.password_reset_token)}.
4
+
5
+ This link will expire in #{distance_of_time_in_words(0, @user.password_reset_token_expires_in)}.
@@ -0,0 +1,4 @@
1
+ You can reset your password on
2
+ <%= edit_password_url(@user.password_reset_token) %>
3
+
4
+ This link will expire in <%= distance_of_time_in_words(0, @user.password_reset_token_expires_in) %>.
@@ -1,13 +1,8 @@
1
- - title = options.delete(:title)
2
- - image = options.delete(:image)
3
- - actions = options.delete(:actions)
1
+ = card.Image href:, src: image.url if image
4
2
 
5
- = card.wrapper(*flags, **options) do
6
- - if image
7
- = card.Image style: image.url.then { "background-image: url(#{_1})" }
3
+ = card.Head do
4
+ = card.Title(href:) { title }
5
+ = card.Subtitle { subtitle }
6
+ = card.Actions { actions }
8
7
 
9
- = card.Head do
10
- = card.Title(tag: :h2) { title }
11
- = card.Actions { actions }
12
-
13
- = yield
8
+ = yield
@@ -1,7 +1 @@
1
- - label = options.delete(:label)
2
- - content = options.delete(:content)
3
-
4
- = field.wrapper(*flags, **options) do
5
- = field.Label { label }
6
- = field.Content { content }
7
- = yield
1
+ = yield
@@ -1,2 +1 @@
1
- = close.wrapper do
2
- = yield || "✕"
1
+ = yield || "✕"
@@ -1,13 +1,7 @@
1
- :ruby
2
- title ||= nil
3
- actions||= nil
4
- tabs ||= nil
1
+ = page.Head do
2
+ = ui.Layout.Menu
3
+ = page.Title { page.title }
4
+ = page.Tabs { tabs }
5
+ = page.Actions { actions }
5
6
 
6
- = page.wrapper *flags do
7
- = page.Head do
8
- = ui.Layout.Menu
9
- = page.Title(title, tag: :h2)
10
- = page.Tabs(*tabs)
11
- = page.Actions(actions)
12
-
13
- = yield
7
+ = yield
@@ -3,13 +3,13 @@
3
3
  {
4
4
  "warning_type": "Dangerous Eval",
5
5
  "warning_code": 13,
6
- "fingerprint": "2ffb4292b2c37c7f06711cbbc3f7c0a7d62fd147d4743f141d26ab7be2418c76",
6
+ "fingerprint": "9f7c27406a84219ad1d93b07b8c1acd435ad75921861a85a647e32f3d07d1289",
7
7
  "check_name": "Evaluation",
8
8
  "message": "Dynamic string evaluated as code",
9
9
  "file": "lib/cafe_car/auto_resolver.rb",
10
10
  "line": 16,
11
11
  "link": "https://brakemanscanner.org/docs/warning_types/dangerous_eval/",
12
- "code": "TOPLEVEL_BINDING.eval(\"class #{mod.name}::#{name} < CafeCar[:ApplicationController]\\n include CafeCar::Controller\\n recline_in_the_cafe_car\\n self\\nend\\n\", \"lib/cafe_car/auto_resolver.rb\", 16)",
12
+ "code": "TOPLEVEL_BINDING.eval(\"class #{mod.name}::#{name} < CafeCar[:ApplicationController]\\n include CafeCar::Controller\\n cafe_car\\n self\\nend\\n\", \"lib/cafe_car/auto_resolver.rb\", 16)",
13
13
  "render_path": null,
14
14
  "location": {
15
15
  "type": "method",
@@ -73,5 +73,5 @@
73
73
  "note": ""
74
74
  }
75
75
  ],
76
- "brakeman_version": "8.0.2"
76
+ "brakeman_version": "8.0.4"
77
77
  }
@@ -4,11 +4,17 @@ en:
4
4
  index: List
5
5
  show: View
6
6
  destroy: Delete
7
+ new_session: Log In
8
+ auth_required: Please %{new_session} first.
7
9
 
8
10
  navigation:
9
11
  icon:
10
- attachments: pin
11
- blobs: multiple-pages
12
+ new_session: log-in
13
+ sessions: log-in
14
+ attachments: media-image-list
15
+ blobs: media-image-folder
16
+ passwords: key
17
+ versions: multiple-pages
12
18
 
13
19
  list_html:
14
20
  zero: "(none)"
@@ -34,6 +40,8 @@ en:
34
40
  attributes:
35
41
  to_s: Item
36
42
  id: ID
43
+ ip: IP
44
+ ip_address: IP address
37
45
  created_at: Created
38
46
  updated_at: Updated
39
47
  deleted_at: Deleted