lexxy-variables 0.0.3 → 0.0.4
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 +125 -12
- data/lib/lexxy_variables/helper.rb +13 -2
- data/lib/lexxy_variables/pipeline.rb +4 -3
- data/lib/lexxy_variables/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20e1253a50b9ac063ff88624785d48c6103842a46c07256ab867db405497f38a
|
|
4
|
+
data.tar.gz: 6c9097c35bd39c7a81b204de9f68599c56be4fcd319ec660cbcfb5aaa4432f85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c6d6291c6770026b75e2b167262f39d3419fdcb23ba255969391f3a96adce42abb2c3a2f7e73cd775855832483fc7158ab9dd655b481caf80042791262638931
|
|
7
|
+
data.tar.gz: 343e6f7cc524a95b4c5aca8a0230d091acf16a3ef7147cc2324172fc545ca3bc1db91bae1907ef344b6d06fd255874c741b770393d609ee7c4a3124c0f59dbf9
|
data/README.md
CHANGED
|
@@ -44,8 +44,8 @@ in your entrypoint:
|
|
|
44
44
|
|
|
45
45
|
```js
|
|
46
46
|
// app/javascript/application.js
|
|
47
|
+
import * as Lexxy from "lexxy"
|
|
47
48
|
import VariableExtension from "lexxy-variables"
|
|
48
|
-
import * as Lexxy from "@37signals/lexxy"
|
|
49
49
|
|
|
50
50
|
Lexxy.configure({ global: { extensions: [ VariableExtension ] } })
|
|
51
51
|
```
|
|
@@ -54,16 +54,25 @@ Lexxy.configure({ global: { extensions: [ VariableExtension ] } })
|
|
|
54
54
|
npm package. Install it alongside Lexxy:
|
|
55
55
|
|
|
56
56
|
```sh
|
|
57
|
-
yarn add
|
|
57
|
+
yarn add @37signals/lexxy lexxy-variables
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
and register the extension
|
|
60
|
+
and register the extension in your JavaScript.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// app/javascript/application.js
|
|
64
|
+
import * as Lexxy from "@37signals/lexxy"
|
|
65
|
+
import VariableExtension from "lexxy-variables"
|
|
66
|
+
|
|
67
|
+
Lexxy.configure({ global: { extensions: [ VariableExtension ] } })
|
|
68
|
+
```
|
|
61
69
|
|
|
62
70
|
## Minimal configuration
|
|
63
71
|
|
|
64
72
|
`catalog` is the list users pick from in the editor. `assigns` is the lookup that
|
|
65
73
|
turns a key into a value at render time. `catalog` is required and `assigns` is
|
|
66
|
-
optional. Leave it out and the gem reads `value` straight off the catalog item
|
|
74
|
+
optional. Leave it out and the gem reads `value` straight off the catalog item,
|
|
75
|
+
or supply values per render (see [Examples](#examples)).
|
|
67
76
|
|
|
68
77
|
Put the `configure` block in an initializer, e.g. `config/initializers/lexxy_variables.rb`:
|
|
69
78
|
|
|
@@ -101,7 +110,7 @@ On the **display page** (where the saved content is shown to readers), resolve
|
|
|
101
110
|
the stored rich text. This is what swaps each variable chip for its value:
|
|
102
111
|
|
|
103
112
|
```erb
|
|
104
|
-
<%=
|
|
113
|
+
<%= render_variable_content(@record.body) %>
|
|
105
114
|
```
|
|
106
115
|
|
|
107
116
|
Each chip resolves to its value, so the reader sees finished text:
|
|
@@ -113,11 +122,99 @@ Each chip resolves to its value, so the reader sees finished text:
|
|
|
113
122
|
`@record` and `:body` are placeholders. Use whatever model and Action Text
|
|
114
123
|
attribute hold your content.
|
|
115
124
|
|
|
125
|
+
## Examples
|
|
126
|
+
|
|
127
|
+
### Simple replacement from a model
|
|
128
|
+
|
|
129
|
+
First and last name variables, with the values supplied at render time. No Liquid needed. The `catalog` lists the two variables so they appear in the editor:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# config/initializers/lexxy_variables.rb
|
|
133
|
+
LexxyVariables.configure do |c|
|
|
134
|
+
c.catalog = [
|
|
135
|
+
{ key: "first_name", name: "First name" },
|
|
136
|
+
{ key: "last_name", name: "Last name" }
|
|
137
|
+
]
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Editor page:
|
|
142
|
+
|
|
143
|
+
```erb
|
|
144
|
+
<%= form_with model: @message do |form| %>
|
|
145
|
+
<%= form.rich_text_area :body do %>
|
|
146
|
+
<%= lexxy_variables_prompt %>
|
|
147
|
+
<% end %>
|
|
148
|
+
<%= form.submit %>
|
|
149
|
+
<% end %>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Display page:
|
|
153
|
+
|
|
154
|
+
```erb
|
|
155
|
+
<%= render_variable_content(@message.body,
|
|
156
|
+
first_name: @user.first_name,
|
|
157
|
+
last_name: @user.last_name) %>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The catalog entries have no `value` because it's supplied at render time, so the same saved content renders differently per user. The default renderer escapes the values automatically. When a variable name would collide with `context:` or `locale:`, use an `assigns:` hash instead (see [Helper options](#helper-options)).
|
|
161
|
+
|
|
162
|
+
### Static values from config
|
|
163
|
+
|
|
164
|
+
When a value is the same for everyone, it can live on the catalog item itself. With no `assigns` set, the gem reads `value` off the catalog and the helper takes no extra arguments:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
LexxyVariables.configure do |c|
|
|
168
|
+
c.catalog = [ { key: "company", name: "Company", value: "Acme" } ]
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```erb
|
|
173
|
+
<%= render_variable_content(@message.body) %>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Liquid drops and dotted access
|
|
177
|
+
|
|
178
|
+
Liquid enables dotted access like `{{ user.first_name }}`, filters, and drops that expose a whole object. A drop is a small class that defines which methods Liquid can call, and escapes what it returns:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# app/drops/user_drop.rb
|
|
182
|
+
# Liquid output is emitted html_safe after sanitization,
|
|
183
|
+
# so a drop must escape its own values.
|
|
184
|
+
class UserDrop < Liquid::Drop
|
|
185
|
+
def initialize(user) = @user = user
|
|
186
|
+
|
|
187
|
+
def first_name = ERB::Util.html_escape(@user.first_name).to_s
|
|
188
|
+
def full_name = ERB::Util.html_escape(@user.full_name).to_s
|
|
189
|
+
def email = ERB::Util.html_escape(@user.email).to_s
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The dotted keys go in the catalog so they can be inserted, the Liquid renderer is switched on, and `assigns` returns the drop under the name the keys use (`user`):
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
LexxyVariables.configure do |c|
|
|
197
|
+
c.catalog = [
|
|
198
|
+
{ key: "user.first_name", name: "First name" },
|
|
199
|
+
{ key: "user.full_name", name: "Full name" },
|
|
200
|
+
{ key: "user.email", name: "Email" }
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
c.renderer = LexxyVariables::Renderers::Liquid.new
|
|
204
|
+
|
|
205
|
+
c.assigns = ->(_used_keys) { { "user" => UserDrop.new(Current.user) } }
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```erb
|
|
210
|
+
<%= render_variable_content(@message.body) %>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
A `user.first_name` chip becomes `{{ user.first_name }}`, which Liquid runs through the drop. Only the methods defined on the drop are reachable, not arbitrary attributes on the user. Liquid doesn't escape output the way the default renderer does, so the drop escapes its own values. The same goes for any plain string returned from `assigns` or passed inline.
|
|
214
|
+
|
|
116
215
|
## Full configuration
|
|
117
216
|
|
|
118
|
-
`context` is yours to define. The gem passes it untouched to your catalog,
|
|
119
|
-
assigns, and resolve callables, so put whatever they need in it. That might be a
|
|
120
|
-
tenant, `nil`, or any object.
|
|
217
|
+
`context` is yours to define. The gem passes it untouched to your catalog, assigns, and resolve callables, so put whatever they need in it. That might be a tenant, `nil`, or any object.
|
|
121
218
|
|
|
122
219
|
```ruby
|
|
123
220
|
LexxyVariables.configure do |c|
|
|
@@ -145,7 +242,7 @@ end
|
|
|
145
242
|
| Option | Default | What it does |
|
|
146
243
|
| --- | --- | --- |
|
|
147
244
|
| `catalog` | `[]` | The insertable items shown in the `{{` prompt and the toolbar dropdown. A list, a zero-arg lambda, or a `->(context)` lambda. Items respond to `#key` and `#name`, and optionally `#value` and `#attachable_sgid`. |
|
|
148
|
-
| `assigns` | reads `#value` off catalog items | The render-time lookup. A `->(context, used_keys)` or `->(used_keys)` lambda that receives only the keys used in the content being rendered and returns a `{ key => value }` hash. |
|
|
245
|
+
| `assigns` | reads `#value` off catalog items | The render-time lookup. A `->(context, used_keys)` or `->(used_keys)` lambda that receives only the keys used in the content being rendered and returns a `{ key => value }` hash. Per-render values can also be passed straight to `render_variable_content` (see [Helper options](#helper-options)). |
|
|
149
246
|
| `renderer` | `Renderers::Substitution.new` | How placeholders become values. The default is plain, escaped string substitution with no template engine. Swap in `Renderers::Liquid.new` for dotted access, drops, and filters. |
|
|
150
247
|
| `sort` | `:name` | How the catalog is ordered in the prompt and dropdown. `:name` (case-insensitive alphabetical), `:key`, `false` to keep the catalog's given order, or a lambda (a `->(item)` sort key or a `->(a, b)` comparator). |
|
|
151
248
|
| `max_fragment_depth` | `1` | How many levels of `renders_as: :html` chips expand. The default resolves the variables inside a snippet but drops a snippet nested inside another snippet. Raise it to allow deeper nesting. |
|
|
@@ -156,15 +253,31 @@ end
|
|
|
156
253
|
|
|
157
254
|
Both view helpers take `context:` (see [Multi-tenancy](#multi-tenancy)). Beyond
|
|
158
255
|
that, `lexxy_variables_prompt` lets you change the trigger characters and the
|
|
159
|
-
empty state, and `
|
|
256
|
+
empty state, and `render_variable_content` can render under a specific locale by
|
|
160
257
|
wrapping the whole pass in `I18n.with_locale`.
|
|
161
258
|
|
|
162
259
|
```erb
|
|
163
260
|
<%= lexxy_variables_prompt(trigger: "%%", empty_results: t(".no_variables")) %>
|
|
164
261
|
|
|
165
|
-
<%=
|
|
262
|
+
<%= render_variable_content(@record.body, locale: recipient.locale) %>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
You can also pass a variable's value straight to `render_variable_content`, as
|
|
266
|
+
keyword arguments or an `assigns:` hash. These win over whatever the configured
|
|
267
|
+
`assigns` returns, and a value for a key that isn't used in the content is just
|
|
268
|
+
ignored:
|
|
269
|
+
|
|
270
|
+
```erb
|
|
271
|
+
<%# keyword arguments %>
|
|
272
|
+
<%= render_variable_content(@record.body, first_name: @user.first_name) %>
|
|
273
|
+
|
|
274
|
+
<%# same thing, for a name that would clash with context: or locale: %>
|
|
275
|
+
<%= render_variable_content(@record.body, assigns: { first_name: @user.first_name }) %>
|
|
166
276
|
```
|
|
167
277
|
|
|
278
|
+
The default renderer escapes these values for you. Liquid doesn't, so escape
|
|
279
|
+
them yourself or pass a drop.
|
|
280
|
+
|
|
168
281
|
## Multi-tenancy
|
|
169
282
|
|
|
170
283
|
Tenancy is optional. If your app is multi-tenant, pass the tenant through as
|
|
@@ -188,7 +301,7 @@ Both view helpers take the same `context:`. Pass the tenant on the editor page:
|
|
|
188
301
|
and again on the display page:
|
|
189
302
|
|
|
190
303
|
```erb
|
|
191
|
-
<%=
|
|
304
|
+
<%= render_variable_content(@record.body, context: ActsAsTenant.current_tenant) %>
|
|
192
305
|
```
|
|
193
306
|
|
|
194
307
|
Or skip `context` entirely and rely on acts_as_tenant scoping queries to the
|
|
@@ -2,8 +2,19 @@ module LexxyVariables
|
|
|
2
2
|
# Mixed into Action View by the engine. `context` is opaque to the gem and is
|
|
3
3
|
# passed straight through to the host's catalog/assigns/resolve callables.
|
|
4
4
|
module Helper
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# `assigns:` (and any extra keyword args) are per-render values merged on top
|
|
6
|
+
# of the configured `assigns`, so a caller can supply a key's value inline:
|
|
7
|
+
#
|
|
8
|
+
# render_variable_content(@record.body, first_name: @user.first_name)
|
|
9
|
+
#
|
|
10
|
+
# Inline values win over the configured assigns. Keys not used by any chip in
|
|
11
|
+
# the body are ignored. Under the default renderer inline values are
|
|
12
|
+
# HTML-escaped; under Liquid they are emitted as-is, so pre-escape them or
|
|
13
|
+
# pass a drop.
|
|
14
|
+
def render_variable_content(rich_text, context: nil, locale: I18n.locale, assigns: {}, **inline_assigns)
|
|
15
|
+
LexxyVariables::Pipeline.new(self).call(
|
|
16
|
+
rich_text, context: context, locale: locale, assigns: assigns.merge(inline_assigns)
|
|
17
|
+
)
|
|
7
18
|
end
|
|
8
19
|
|
|
9
20
|
# Renders the <lexxy-prompt> the editor extension reads, built from the
|
|
@@ -18,7 +18,7 @@ module LexxyVariables
|
|
|
18
18
|
@config = config
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def call(rich_text, context: nil, locale: I18n.locale)
|
|
21
|
+
def call(rich_text, context: nil, locale: I18n.locale, assigns: {})
|
|
22
22
|
body = rich_text&.body
|
|
23
23
|
return "".html_safe if body.blank?
|
|
24
24
|
|
|
@@ -32,8 +32,9 @@ module LexxyVariables
|
|
|
32
32
|
ActionText::Content.new(fragment.to_html, canonicalize: false)
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
resolved = @config.resolve_assigns(@context, @used_keys.uniq)
|
|
36
|
+
resolved = resolved.merge(assigns.transform_keys(&:to_s)) if assigns.any?
|
|
37
|
+
rendered = @config.renderer.render(html, nonce: @nonce, assigns: resolved)
|
|
37
38
|
|
|
38
39
|
@view.render(layout: @config.content_layout) { rendered.html_safe }
|
|
39
40
|
end
|