phlex-hanami 0.2.0.pre.alpha.2 → 0.2.0.pre.alpha.3
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/CHANGELOG.md +25 -1
- data/lib/phlex/hanami/component.rb +41 -0
- data/lib/phlex/hanami/context.rb +21 -1
- data/lib/phlex/hanami/contextual.rb +294 -0
- data/lib/phlex/hanami/errors/mailer_view_error.rb +1 -0
- data/lib/phlex/hanami/errors/missing_context_error.rb +1 -0
- data/lib/phlex/hanami/errors/relative_path_error.rb +1 -0
- data/lib/phlex/hanami/extensions/mailer.rb +6 -0
- data/lib/phlex/hanami/extensions/slice.rb +12 -3
- data/lib/phlex/hanami/helpers.rb +3 -2
- data/lib/phlex/hanami/mailer/renderable.rb +6 -0
- data/lib/phlex/hanami/mailer/text.rb +19 -6
- data/lib/phlex/hanami/renderable.rb +14 -237
- data/lib/phlex/hanami/rspec.rb +14 -0
- data/lib/phlex/hanami/slice_configured.rb +5 -1
- data/lib/phlex/hanami/slice_configured_context.rb +9 -1
- data/lib/phlex/hanami/testing.rb +156 -0
- data/lib/phlex/hanami.rb +3 -1
- data/sig/external/dry.rbs +9 -0
- data/sig/external/hanami.rbs +77 -0
- data/sig/external/hanami_view.rbs +9 -0
- data/sig/external/phlex.rbs +16 -0
- data/sig/phlex/hanami/component.rbs +9 -0
- data/sig/phlex/hanami/context.rbs +39 -0
- data/sig/phlex/hanami/contextual.rbs +57 -0
- data/sig/phlex/hanami/errors/error.rbs +8 -0
- data/sig/phlex/hanami/errors/mailer_view_error.rbs +9 -0
- data/sig/phlex/hanami/errors/missing_context_error.rbs +9 -0
- data/sig/phlex/hanami/errors/relative_path_error.rbs +9 -0
- data/sig/phlex/hanami/extensions/mailer.rbs +25 -0
- data/sig/phlex/hanami/extensions/slice.rbs +21 -0
- data/sig/phlex/hanami/helpers.rbs +13 -0
- data/sig/phlex/hanami/layout.rbs +9 -0
- data/sig/phlex/hanami/mailer/layout.rbs +11 -0
- data/sig/phlex/hanami/mailer/renderable.rbs +27 -0
- data/sig/phlex/hanami/mailer/text.rbs +47 -0
- data/sig/phlex/hanami/mailer/view.rbs +11 -0
- data/sig/phlex/hanami/mailer.rbs +8 -0
- data/sig/phlex/hanami/renderable.rbs +27 -0
- data/sig/phlex/hanami/rspec.rbs +1 -0
- data/sig/phlex/hanami/slice_configured.rbs +19 -0
- data/sig/phlex/hanami/slice_configured_context.rbs +27 -0
- data/sig/phlex/hanami/testing.rbs +19 -0
- data/sig/phlex/hanami/view.rbs +9 -0
- data/sig/phlex/hanami.rbs +7 -0
- data/sig/phlex-hanami.rbs +1 -2
- metadata +32 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4adab6a5900c422ff7da10ee21be828ba77c1d7523da39173433ff7749516e73
|
|
4
|
+
data.tar.gz: d44d102a14b331b1262f692f8479418d8f08557815830899c3fb9be093f34e25
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 151ad52da0f7b28599a61385974233e109be3a195710f3d2d971783c25202d830cc04b7d85fb8f19c90c6058c2798f0f8e85bc4bc3a81d86003ec242884fb939
|
|
7
|
+
data.tar.gz: 85e34fbfb79b7cb58352575c5d1cdb18066f4034a427a3d55753992b7f5653d3a348e27c98f593b5db2f56f711fc90755965d5c0470a74f0b467192f6368c833
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [v0.2.0-alpha.3] - 2026-09-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Phlex::Hanami::Component`, the base class for everything below a view. It reads the same view context a view
|
|
15
|
+
does, and has neither a layout nor the `call` contract Hanami renders a view through, so Hanami cannot render one
|
|
16
|
+
by accident.
|
|
17
|
+
- `Phlex::Hanami::Contextual`, the view context and the slice on their own, for a component base class you already
|
|
18
|
+
have. `Phlex::Hanami::Renderable` includes it and adds the view half.
|
|
19
|
+
- Support for [Phlex kits]. Extend a component namespace with `Phlex::Kit`, include it in a base view, and a view
|
|
20
|
+
calls its components as methods. A kit registers in the slice container as the module, so `slice["components"]`
|
|
21
|
+
returns the kit rather than raising `NoMethodError`.
|
|
22
|
+
- `Phlex::Hanami::Testing::ViewHelpers`, for testing a view or a component without a request. `render` renders the
|
|
23
|
+
instance you give it, `view_context` builds the context an action would have, and `view_request` fakes a request
|
|
24
|
+
for the session, the flash and the CSRF token.
|
|
25
|
+
- `require "phlex/hanami/rspec"` includes those helpers into every RSpec example group tagged `type: :view`. Under
|
|
26
|
+
any other framework, require `phlex/hanami/testing` and include the module where your view tests run.
|
|
27
|
+
- RBS signatures for the public API, shipped in `sig`.
|
|
28
|
+
- Documentation for [components](docs/usage/components.md), [testing](docs/usage/testing.md) and
|
|
29
|
+
[responses](docs/usage/responses.md).
|
|
30
|
+
|
|
31
|
+
[Phlex kits]: https://www.phlex.fun/components/kits.html
|
|
32
|
+
|
|
10
33
|
## [v0.2.0-alpha.2] - 2026-09-05
|
|
11
34
|
|
|
12
35
|
### Added
|
|
@@ -43,7 +66,8 @@ Initial alpha release by the new maintainer [@aaronmallen](https://github.com/aa
|
|
|
43
66
|
|
|
44
67
|
Initial release, by the previous maintainer [@stephannv](https://github.com/stephannv).
|
|
45
68
|
|
|
46
|
-
[Unreleased]: https://github.com/aaronmallen/phlex-hanami/compare/0.2.0-alpha.
|
|
69
|
+
[Unreleased]: https://github.com/aaronmallen/phlex-hanami/compare/0.2.0-alpha.3...HEAD
|
|
70
|
+
[v0.2.0-alpha.3]: https://github.com/aaronmallen/phlex-hanami/compare/0.2.0-alpha.2...0.2.0-alpha.3
|
|
47
71
|
[v0.2.0-alpha.2]: https://github.com/aaronmallen/phlex-hanami/compare/0.2.0-alpha.1...0.2.0-alpha.2
|
|
48
72
|
[v0.2.0-alpha.1]: https://github.com/aaronmallen/phlex-hanami/releases/tag/0.2.0-alpha.1
|
|
49
73
|
[v0.1.0]: https://github.com/stephannv/phlex-hanami/releases/tag/v0.1.0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phlex
|
|
4
|
+
module Hanami
|
|
5
|
+
# The base class for everything below a view.
|
|
6
|
+
#
|
|
7
|
+
# A component is an ordinary Phlex class you render with `render`. It reads the same context a
|
|
8
|
+
# view does — `path`, `assets`, `content_for`, `flash`, translations — because Phlex shares that
|
|
9
|
+
# context with every component in the tree, however deep. What it does not have is the half of a
|
|
10
|
+
# view that belongs to Hanami: no layout, and no `call(context:, **input)` entry point, because
|
|
11
|
+
# Hanami never calls a component.
|
|
12
|
+
#
|
|
13
|
+
# Subclass this, or include {Contextual} into a base class you already have.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # app/components/card.rb
|
|
17
|
+
# module MyApp
|
|
18
|
+
# module Components
|
|
19
|
+
# class Card < Phlex::Hanami::Component
|
|
20
|
+
# def initialize(post:) = @post = post
|
|
21
|
+
#
|
|
22
|
+
# def view_template
|
|
23
|
+
# article do
|
|
24
|
+
# h2 { a(href: path(:post, id: @post.id)) { @post.title } }
|
|
25
|
+
# yield if block_given?
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# A component takes the context of whoever renders it, not its own slice's, so one written in
|
|
33
|
+
# the app namespace works unchanged inside a slice's view.
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
# @since 0.2.0
|
|
37
|
+
class Component < Phlex::HTML
|
|
38
|
+
include Contextual
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/phlex/hanami/context.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Phlex
|
|
|
17
17
|
|
|
18
18
|
# @api private
|
|
19
19
|
# @since 0.2.0
|
|
20
|
+
#: (singleton(::Hanami::Slice)) -> void
|
|
20
21
|
def self.configure_for_slice(slice)
|
|
21
22
|
extend SliceConfiguredContext.new(slice)
|
|
22
23
|
end
|
|
@@ -27,12 +28,20 @@ module Phlex
|
|
|
27
28
|
#
|
|
28
29
|
# @api public
|
|
29
30
|
# @since 0.2.0
|
|
30
|
-
attr_reader :inflector
|
|
31
|
+
attr_reader :inflector #: Dry::Inflector
|
|
31
32
|
|
|
32
33
|
# @see SliceConfiguredContext#define_new
|
|
33
34
|
#
|
|
34
35
|
# @api private
|
|
35
36
|
# @since 0.2.0
|
|
37
|
+
#: (
|
|
38
|
+
# ?inflector: Dry::Inflector?,
|
|
39
|
+
# ?routes: ::Hanami::Slice::RoutesHelper?,
|
|
40
|
+
# ?assets: ::Hanami::Assets?,
|
|
41
|
+
# ?request: ::Hanami::Action::Request?,
|
|
42
|
+
# ?i18n: ::Hanami::Providers::I18n::Backend?,
|
|
43
|
+
# **untyped
|
|
44
|
+
# ) -> void
|
|
36
45
|
def initialize(inflector: nil, routes: nil, assets: nil, request: nil, i18n: nil, **)
|
|
37
46
|
@inflector = inflector
|
|
38
47
|
@routes = routes
|
|
@@ -50,6 +59,7 @@ module Phlex
|
|
|
50
59
|
#
|
|
51
60
|
# @api public
|
|
52
61
|
# @since 0.2.0
|
|
62
|
+
#: () -> ::Hanami::Assets
|
|
53
63
|
def assets
|
|
54
64
|
raise ::Hanami::ComponentLoadError, "Assets not available. #{assets_hint}" unless @assets
|
|
55
65
|
|
|
@@ -65,6 +75,7 @@ module Phlex
|
|
|
65
75
|
#
|
|
66
76
|
# @api public
|
|
67
77
|
# @since 0.2.0
|
|
78
|
+
#: (Symbol, ?String?) ?{ () -> String } -> String?
|
|
68
79
|
def content_for(key, value = nil)
|
|
69
80
|
if block_given?
|
|
70
81
|
@content_for[key] = yield
|
|
@@ -85,6 +96,7 @@ module Phlex
|
|
|
85
96
|
#
|
|
86
97
|
# @api public
|
|
87
98
|
# @since 0.2.0
|
|
99
|
+
#: () -> String?
|
|
88
100
|
def csrf_token
|
|
89
101
|
request.session[::Hanami::Action::CSRFProtection::CSRF_TOKEN]
|
|
90
102
|
end
|
|
@@ -95,6 +107,7 @@ module Phlex
|
|
|
95
107
|
#
|
|
96
108
|
# @api public
|
|
97
109
|
# @since 0.2.0
|
|
110
|
+
#: () -> ::Hanami::Action::Flash
|
|
98
111
|
def flash
|
|
99
112
|
request.flash
|
|
100
113
|
end
|
|
@@ -105,6 +118,7 @@ module Phlex
|
|
|
105
118
|
#
|
|
106
119
|
# @api public
|
|
107
120
|
# @since 0.2.0
|
|
121
|
+
#: () -> ::Hanami::Providers::I18n::Backend
|
|
108
122
|
def i18n
|
|
109
123
|
raise ::Hanami::ComponentLoadError, "the i18n gem is required to access translations" unless @i18n
|
|
110
124
|
|
|
@@ -113,6 +127,7 @@ module Phlex
|
|
|
113
127
|
|
|
114
128
|
# @api private
|
|
115
129
|
# @since 0.2.0
|
|
130
|
+
#: (self) -> void
|
|
116
131
|
def initialize_copy(source)
|
|
117
132
|
super
|
|
118
133
|
@content_for = source.instance_variable_get(:@content_for).dup
|
|
@@ -126,6 +141,7 @@ module Phlex
|
|
|
126
141
|
#
|
|
127
142
|
# @api public
|
|
128
143
|
# @since 0.2.0
|
|
144
|
+
#: () -> ::Hanami::Action::Request
|
|
129
145
|
def request
|
|
130
146
|
unless @request
|
|
131
147
|
raise ::Hanami::ComponentLoadError, <<~MESSAGE
|
|
@@ -142,6 +158,7 @@ module Phlex
|
|
|
142
158
|
#
|
|
143
159
|
# @api public
|
|
144
160
|
# @since 0.2.0
|
|
161
|
+
#: () -> bool
|
|
145
162
|
def request?
|
|
146
163
|
!!@request
|
|
147
164
|
end
|
|
@@ -154,6 +171,7 @@ module Phlex
|
|
|
154
171
|
#
|
|
155
172
|
# @api public
|
|
156
173
|
# @since 0.2.0
|
|
174
|
+
#: () -> ::Hanami::Slice::RoutesHelper
|
|
157
175
|
def routes
|
|
158
176
|
raise ::Hanami::ComponentLoadError, "the hanami-router gem is required to access routes" unless @routes
|
|
159
177
|
|
|
@@ -166,12 +184,14 @@ module Phlex
|
|
|
166
184
|
#
|
|
167
185
|
# @api public
|
|
168
186
|
# @since 0.2.0
|
|
187
|
+
#: () -> ::Hanami::Action::Request::Session
|
|
169
188
|
def session
|
|
170
189
|
request.session
|
|
171
190
|
end
|
|
172
191
|
|
|
173
192
|
private
|
|
174
193
|
|
|
194
|
+
#: () -> String
|
|
175
195
|
def assets_hint
|
|
176
196
|
if ::Hanami.bundled?("hanami-assets")
|
|
177
197
|
"Have you put files into your assets directory?"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phlex
|
|
4
|
+
module Hanami
|
|
5
|
+
# Gives a Phlex class the Hanami view context and the slice it was defined in.
|
|
6
|
+
#
|
|
7
|
+
# This is the half of the integration a component needs: `path`, `assets`, `content_for`,
|
|
8
|
+
# `flash`, translations and the rest, read off the context Hanami builds for the request.
|
|
9
|
+
# {Renderable} includes it and adds the `call` contract Hanami's `Response#render` expects;
|
|
10
|
+
# include this on its own for a class Hanami never calls directly.
|
|
11
|
+
#
|
|
12
|
+
# {Component} subclasses it already. Include this instead when you have your own component base
|
|
13
|
+
# class.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# module MyApp
|
|
17
|
+
# class Component < Phlex::HTML
|
|
18
|
+
# include Phlex::Hanami::Contextual
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
# @since 0.2.0
|
|
24
|
+
module Contextual
|
|
25
|
+
# @api private
|
|
26
|
+
# @since 0.2.0
|
|
27
|
+
#: (singleton(::Phlex::SGML)) -> void
|
|
28
|
+
def self.included(view_class)
|
|
29
|
+
view_class.extend(::Hanami::SliceConfigurable)
|
|
30
|
+
view_class.extend(ClassMethods)
|
|
31
|
+
return unless defined?(::Hanami::Helpers::I18nHelper)
|
|
32
|
+
|
|
33
|
+
# Order matters: a module included later sits higher in the lookup chain, so the overrides
|
|
34
|
+
# have to go in after Hanami's helper, not alongside it in this module.
|
|
35
|
+
view_class.include(::Hanami::Helpers::I18nHelper)
|
|
36
|
+
view_class.include(I18nOverrides)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Relative-key resolution for Hanami's i18n helper.
|
|
40
|
+
#
|
|
41
|
+
# A Phlex view has no template, so Hanami's implementation — which reads
|
|
42
|
+
# `_context.current_template_name` — cannot work. Included after `Hanami::Helpers::I18nHelper`
|
|
43
|
+
# so that it wins: a module included later sits higher in the lookup chain.
|
|
44
|
+
#
|
|
45
|
+
# @api private
|
|
46
|
+
# @since 0.2.0
|
|
47
|
+
module I18nOverrides
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Resolves a relative translation key (`t(".title")`) against the view's own container key, so
|
|
51
|
+
# `MyApp::Views::Posts::Index` looks up `posts.index.title`.
|
|
52
|
+
#
|
|
53
|
+
# Hanami's own implementation uses `_context.current_template_name`, which is hanami-view's
|
|
54
|
+
# template stack and has no meaning for a Phlex view.
|
|
55
|
+
#: (String | Symbol) -> (String | Symbol)
|
|
56
|
+
def _resolve_i18n_key(key)
|
|
57
|
+
return key unless key.to_s.start_with?(".")
|
|
58
|
+
|
|
59
|
+
base = i18n_key_base
|
|
60
|
+
unless base
|
|
61
|
+
raise ::I18n::ArgumentError,
|
|
62
|
+
"Cannot resolve the relative translation key #{key.inspect} for #{self.class}. " \
|
|
63
|
+
"Give the view a name inside a slice, or use an absolute key (without a leading dot)."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
"#{base}#{key}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The view's container key, dotted: `MyApp::Views::Posts::Index` in the app slice becomes
|
|
70
|
+
# `posts.index`. Nil for an anonymous view, or one outside any slice.
|
|
71
|
+
#: () -> String?
|
|
72
|
+
def i18n_key_base
|
|
73
|
+
view_slice = self.class.slice
|
|
74
|
+
name = self.class.name
|
|
75
|
+
return nil unless view_slice && name
|
|
76
|
+
|
|
77
|
+
view_slice.inflector
|
|
78
|
+
.underscore(name)
|
|
79
|
+
.sub(%r{^#{view_slice.slice_name.path}/}, "")
|
|
80
|
+
.sub(%r{^views/}, "")
|
|
81
|
+
.tr("/", ".")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @api public
|
|
86
|
+
# @since 0.2.0
|
|
87
|
+
module ClassMethods
|
|
88
|
+
# @api private
|
|
89
|
+
# @since 0.2.0
|
|
90
|
+
#: (singleton(::Hanami::Slice)) -> void
|
|
91
|
+
def configure_for_slice(slice)
|
|
92
|
+
extend SliceConfigured.new(slice)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The slice this class belongs to, or nil when it is defined outside a slice namespace.
|
|
96
|
+
#
|
|
97
|
+
# A class with no slice still renders: the context comes from whoever is rendering it, not
|
|
98
|
+
# from the slice. What it loses is the slice-relative half — `t(".title")` has nothing to
|
|
99
|
+
# resolve against and raises.
|
|
100
|
+
#
|
|
101
|
+
# @return [Hanami::Slice, nil]
|
|
102
|
+
#
|
|
103
|
+
# @api public
|
|
104
|
+
# @since 0.2.0
|
|
105
|
+
#: () -> singleton(::Hanami::Slice)?
|
|
106
|
+
def slice
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The URL for an asset.
|
|
112
|
+
#
|
|
113
|
+
# @example
|
|
114
|
+
# script(src: asset_url("app.js"))
|
|
115
|
+
#
|
|
116
|
+
# @param source [String] the asset's source path
|
|
117
|
+
#
|
|
118
|
+
# @return [String] the URL
|
|
119
|
+
#
|
|
120
|
+
# @api public
|
|
121
|
+
# @since 0.2.0
|
|
122
|
+
#: (String) -> String
|
|
123
|
+
def asset_url(source)
|
|
124
|
+
assets[source].url
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# The slice's assets.
|
|
128
|
+
#
|
|
129
|
+
# @api public
|
|
130
|
+
# @since 0.2.0
|
|
131
|
+
#: () -> ::Hanami::Assets
|
|
132
|
+
def assets
|
|
133
|
+
hanami_context.assets
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Stores a string of markup for later use, or reads back what was stored.
|
|
137
|
+
#
|
|
138
|
+
# @api public
|
|
139
|
+
# @since 0.2.0
|
|
140
|
+
#: (Symbol, ?String?) ?{ () -> String } -> String?
|
|
141
|
+
def content_for(...)
|
|
142
|
+
hanami_context.content_for(...)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The current request's CSRF token.
|
|
146
|
+
#
|
|
147
|
+
# @return [String]
|
|
148
|
+
#
|
|
149
|
+
# @api public
|
|
150
|
+
# @since 0.2.0
|
|
151
|
+
#: () -> String?
|
|
152
|
+
def csrf_token
|
|
153
|
+
hanami_context.csrf_token
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The flash hash for the current request.
|
|
157
|
+
#
|
|
158
|
+
# @api public
|
|
159
|
+
# @since 0.2.0
|
|
160
|
+
#: () -> ::Hanami::Action::Flash
|
|
161
|
+
def flash
|
|
162
|
+
hanami_context.flash
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The Hanami view context for this render.
|
|
166
|
+
#
|
|
167
|
+
# Read from Phlex's user context, which is shared with every component in the render tree, so
|
|
168
|
+
# a component nested any number of levels deep sees the same context.
|
|
169
|
+
#
|
|
170
|
+
# @return [Object] a `Hanami::View::Context` when hanami-view is bundled, otherwise a {Context}
|
|
171
|
+
#
|
|
172
|
+
# @raise [MissingContextError] when the view was rendered without a context
|
|
173
|
+
#
|
|
174
|
+
# @api public
|
|
175
|
+
# @since 0.2.0
|
|
176
|
+
#: () -> (::Hanami::View::Context | Context)
|
|
177
|
+
def hanami_context
|
|
178
|
+
context[CONTEXT_KEY] || raise(MissingContextError, self.class)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Whether a Hanami view context is available.
|
|
182
|
+
#
|
|
183
|
+
# @return [Boolean]
|
|
184
|
+
#
|
|
185
|
+
# @api public
|
|
186
|
+
# @since 0.2.0
|
|
187
|
+
#: () -> bool
|
|
188
|
+
def hanami_context?
|
|
189
|
+
!context[CONTEXT_KEY].nil?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# The slice's i18n backend.
|
|
193
|
+
#
|
|
194
|
+
# @api public
|
|
195
|
+
# @since 0.2.0
|
|
196
|
+
#: () -> ::Hanami::Providers::I18n::Backend
|
|
197
|
+
def i18n
|
|
198
|
+
hanami_context.i18n
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The path for a named route.
|
|
202
|
+
#
|
|
203
|
+
# @example
|
|
204
|
+
# a(href: path(:posts)) { "Posts" }
|
|
205
|
+
#
|
|
206
|
+
# @return [String]
|
|
207
|
+
#
|
|
208
|
+
# @api public
|
|
209
|
+
# @since 0.2.0
|
|
210
|
+
#: (*untyped, **untyped) -> String
|
|
211
|
+
def path(...)
|
|
212
|
+
routes.path(...)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The current request.
|
|
216
|
+
#
|
|
217
|
+
# @return [Hanami::Action::Request]
|
|
218
|
+
#
|
|
219
|
+
# @api public
|
|
220
|
+
# @since 0.2.0
|
|
221
|
+
#: () -> ::Hanami::Action::Request
|
|
222
|
+
def request
|
|
223
|
+
hanami_context.request
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Whether the view is being rendered from within a request.
|
|
227
|
+
#
|
|
228
|
+
# @return [Boolean]
|
|
229
|
+
#
|
|
230
|
+
# @api public
|
|
231
|
+
# @since 0.2.0
|
|
232
|
+
#: () -> bool
|
|
233
|
+
def request?
|
|
234
|
+
hanami_context? && hanami_context.request?
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The app's routes helper.
|
|
238
|
+
#
|
|
239
|
+
# @return [Hanami::Slice::RoutesHelper]
|
|
240
|
+
#
|
|
241
|
+
# @api public
|
|
242
|
+
# @since 0.2.0
|
|
243
|
+
#: () -> ::Hanami::Slice::RoutesHelper
|
|
244
|
+
def routes
|
|
245
|
+
hanami_context.routes
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# The session for the current request.
|
|
249
|
+
#
|
|
250
|
+
# @api public
|
|
251
|
+
# @since 0.2.0
|
|
252
|
+
#: () -> ::Hanami::Action::Request::Session
|
|
253
|
+
def session
|
|
254
|
+
hanami_context.session
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# The slice this class belongs to.
|
|
258
|
+
#
|
|
259
|
+
# @return [Hanami::Slice, nil]
|
|
260
|
+
#
|
|
261
|
+
# @api public
|
|
262
|
+
# @since 0.2.0
|
|
263
|
+
#: () -> singleton(::Hanami::Slice)?
|
|
264
|
+
def slice
|
|
265
|
+
self.class.slice
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# The full URL for a named route.
|
|
269
|
+
#
|
|
270
|
+
# Hanami's routes helper returns a `URI`; Phlex writes strings, and rejects anything else as
|
|
271
|
+
# an attribute value, so this hands back the string.
|
|
272
|
+
#
|
|
273
|
+
# @example
|
|
274
|
+
# a(href: url(:posts)) { "Posts" }
|
|
275
|
+
#
|
|
276
|
+
# @return [String]
|
|
277
|
+
#
|
|
278
|
+
# @api public
|
|
279
|
+
# @since 0.2.0
|
|
280
|
+
#: (*untyped, **untyped) -> String
|
|
281
|
+
def url(...)
|
|
282
|
+
routes.url(...).to_s
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private
|
|
286
|
+
|
|
287
|
+
# Hanami's helper modules reach for the view context under this name.
|
|
288
|
+
#: () -> (::Hanami::View::Context | Context)
|
|
289
|
+
def _context
|
|
290
|
+
hanami_context
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
@@ -12,6 +12,7 @@ module Phlex
|
|
|
12
12
|
class MailerViewError < Error
|
|
13
13
|
# @api private
|
|
14
14
|
# @since 0.2.0
|
|
15
|
+
#: (singleton(::Hanami::Mailer), singleton(::Phlex::SGML)) -> void
|
|
15
16
|
def initialize(mailer_class, view_class)
|
|
16
17
|
super(<<~MESSAGE)
|
|
17
18
|
#{mailer_class} is paired with #{view_class}, which is not a mail view.
|
|
@@ -21,6 +21,7 @@ module Phlex
|
|
|
21
21
|
#
|
|
22
22
|
# @api private
|
|
23
23
|
# @since 0.2.0
|
|
24
|
+
#: () -> void
|
|
24
25
|
def self.install
|
|
25
26
|
::Hanami::Mailer.prepend(self)
|
|
26
27
|
::Hanami::Mailer.singleton_class.prepend(ClassMethods)
|
|
@@ -31,6 +32,7 @@ module Phlex
|
|
|
31
32
|
module ClassMethods
|
|
32
33
|
# @api private
|
|
33
34
|
# @since 0.2.0
|
|
35
|
+
#: (singleton(::Hanami::Slice)) -> void
|
|
34
36
|
def configure_for_slice(slice)
|
|
35
37
|
super
|
|
36
38
|
extend SliceConfigured.new(slice)
|
|
@@ -49,6 +51,7 @@ module Phlex
|
|
|
49
51
|
#
|
|
50
52
|
# @api private
|
|
51
53
|
# @since 0.2.0
|
|
54
|
+
#: () -> singleton(::Phlex::SGML)?
|
|
52
55
|
def phlex_view
|
|
53
56
|
mailer_slice = slice
|
|
54
57
|
return nil unless mailer_slice && name
|
|
@@ -69,12 +72,14 @@ module Phlex
|
|
|
69
72
|
#
|
|
70
73
|
# @api private
|
|
71
74
|
# @since 0.2.0
|
|
75
|
+
#: () -> singleton(::Hanami::Slice)?
|
|
72
76
|
def slice
|
|
73
77
|
nil
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
private
|
|
77
81
|
|
|
82
|
+
#: (singleton(::Hanami::Slice)) -> String
|
|
78
83
|
def view_name(mailer_slice)
|
|
79
84
|
mailer_slice.inflector
|
|
80
85
|
.underscore(name)
|
|
@@ -93,6 +98,7 @@ module Phlex
|
|
|
93
98
|
#
|
|
94
99
|
# @api private
|
|
95
100
|
# @since 0.2.0
|
|
101
|
+
#: () -> untyped
|
|
96
102
|
def view
|
|
97
103
|
@view || self.class.phlex_view || super
|
|
98
104
|
end
|
|
@@ -18,26 +18,31 @@ module Phlex
|
|
|
18
18
|
# @api private
|
|
19
19
|
# @since 0.2.0
|
|
20
20
|
module Slice
|
|
21
|
-
# Returns Phlex classes as
|
|
21
|
+
# Returns Phlex classes and kits as themselves, and everything else as the loader would.
|
|
22
22
|
#
|
|
23
23
|
# `component.loader.constant` is what the default autoloading loader already calls, so no
|
|
24
24
|
# extra loading happens here and a constant that fails to load still raises
|
|
25
25
|
# `Dry::System::ComponentNotLoadableError`.
|
|
26
26
|
#
|
|
27
|
+
# A kit is a module rather than a class, and the default loader calls `new` on whatever it
|
|
28
|
+
# resolves, so `slice["components"]` would raise `NoMethodError` for one. Registering it as
|
|
29
|
+
# the module hands back the thing a kit is for.
|
|
30
|
+
#
|
|
27
31
|
# @api private
|
|
28
32
|
# @since 0.2.0
|
|
29
33
|
COMPONENT_INSTANCE = proc { |component, *args, **kwargs|
|
|
30
34
|
constant = component.loader.constant(component)
|
|
31
35
|
|
|
32
|
-
if constant.is_a?(Class) && constant < Phlex::SGML
|
|
36
|
+
if (constant.is_a?(Class) && constant < Phlex::SGML) || constant.is_a?(Phlex::Kit)
|
|
33
37
|
constant
|
|
34
38
|
else
|
|
35
39
|
component.loader.call(component, *args, **kwargs)
|
|
36
40
|
end
|
|
37
|
-
}
|
|
41
|
+
} #: ^(untyped, *untyped, **untyped) -> untyped
|
|
38
42
|
|
|
39
43
|
# @api private
|
|
40
44
|
# @since 0.2.0
|
|
45
|
+
#: (?String?) -> untyped
|
|
41
46
|
def prepare(provider_name = nil)
|
|
42
47
|
result = super
|
|
43
48
|
|
|
@@ -59,6 +64,7 @@ module Phlex
|
|
|
59
64
|
#
|
|
60
65
|
# @api private
|
|
61
66
|
# @since 0.2.0
|
|
67
|
+
#: (singleton(::Hanami::Slice)) -> singleton(Context)?
|
|
62
68
|
def define_view_context(slice)
|
|
63
69
|
return if ::Hanami.bundled?("hanami-view")
|
|
64
70
|
|
|
@@ -78,6 +84,7 @@ module Phlex
|
|
|
78
84
|
#
|
|
79
85
|
# @api private
|
|
80
86
|
# @since 0.2.0
|
|
87
|
+
#: (singleton(::Hanami::Slice)) -> void
|
|
81
88
|
def register_phlex_components(slice)
|
|
82
89
|
component_dirs = slice.container.config.component_dirs
|
|
83
90
|
return if component_dirs.configured?(:instance)
|
|
@@ -95,6 +102,7 @@ module Phlex
|
|
|
95
102
|
#
|
|
96
103
|
# @api private
|
|
97
104
|
# @since 0.2.0
|
|
105
|
+
#: (singleton(::Hanami::Slice)) -> (singleton(::Hanami::View::Context) | singleton(Context))
|
|
98
106
|
def view_context_class(slice)
|
|
99
107
|
return ::Hanami::Extensions::View::Context.context_class(slice) if ::Hanami.bundled?("hanami-view")
|
|
100
108
|
|
|
@@ -103,6 +111,7 @@ module Phlex
|
|
|
103
111
|
|
|
104
112
|
private
|
|
105
113
|
|
|
114
|
+
#: (singleton(::Hanami::Slice)) -> Module
|
|
106
115
|
def views_namespace(slice)
|
|
107
116
|
if slice.namespace.const_defined?(:Views, false)
|
|
108
117
|
slice.namespace.const_get(:Views, false)
|
data/lib/phlex/hanami/helpers.rb
CHANGED
|
@@ -46,7 +46,7 @@ module Phlex
|
|
|
46
46
|
OVERRIDDEN_METHODS = {
|
|
47
47
|
raw: Phlex::SGML.instance_method(:raw),
|
|
48
48
|
tag: Phlex::HTML.instance_method(:tag),
|
|
49
|
-
}.freeze
|
|
49
|
+
}.freeze #: Hash[Symbol, UnboundMethod]
|
|
50
50
|
|
|
51
51
|
# Phlex's implementations, mixed back in over Hanami's.
|
|
52
52
|
#
|
|
@@ -56,10 +56,11 @@ module Phlex
|
|
|
56
56
|
OVERRIDDEN_METHODS.each do |name, method|
|
|
57
57
|
define_method(name) { |*args, **options, &block| method.bind_call(self, *args, **options, &block) }
|
|
58
58
|
end
|
|
59
|
-
end
|
|
59
|
+
end #: Module
|
|
60
60
|
|
|
61
61
|
# @api private
|
|
62
62
|
# @since 0.2.0
|
|
63
|
+
#: (singleton(::Phlex::SGML)) -> void
|
|
63
64
|
def self.included(view_class)
|
|
64
65
|
view_class.include(::Hanami::Extensions::View::StandardHelpers)
|
|
65
66
|
view_class.include(PhlexMethods)
|