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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b3aad3f3ce716d523463e8bc65d14d4d7e1b87fbe166b3c2a240e52632e8c4f
4
- data.tar.gz: 5837750469a552f3758ad187a0d8087b935cba24983df189e70132ddcab396b7
3
+ metadata.gz: 20e1253a50b9ac063ff88624785d48c6103842a46c07256ab867db405497f38a
4
+ data.tar.gz: 6c9097c35bd39c7a81b204de9f68599c56be4fcd319ec660cbcfb5aaa4432f85
5
5
  SHA512:
6
- metadata.gz: f850a1474ddd89b9652a0a76a670b039246fe8db766168c3acb8df93708b331e59a7586cca403f3ae49bea97dd180007a9c60893c64a6174776cf959d9d593d2
7
- data.tar.gz: 1b246e2ab7e6eb75fa3814e018fb826de38a294e68efe5df05854c14d83882690a5c7e82635995350bd0d87a8bde67290c2212972e73cde95cba5f54dcdde08d
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 lexxy-variables @37signals/lexxy
57
+ yarn add @37signals/lexxy lexxy-variables
58
58
  ```
59
59
 
60
- and register the extension with the same snippet as above.
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
- <%= render_lexxy_content(@record.body) %>
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 `render_lexxy_content` can render under a specific locale by
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
- <%= render_lexxy_content(@record.body, locale: recipient.locale) %>
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
- <%= render_lexxy_content(@record.body, context: ActsAsTenant.current_tenant) %>
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
- def render_lexxy_content(rich_text, context: nil, locale: I18n.locale)
6
- LexxyVariables::Pipeline.new(self).call(rich_text, context: context, locale: locale)
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
- assigns = @config.resolve_assigns(@context, @used_keys.uniq)
36
- rendered = @config.renderer.render(html, nonce: @nonce, assigns: assigns)
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
@@ -1,3 +1,3 @@
1
1
  module LexxyVariables
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lexxy-variables
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Quinn