flash_unified 0.2.0 → 0.3.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 +44 -285
- data/app/helpers/flash_unified/view_helper.rb +23 -1
- data/app/javascript/flash_unified/all.bundle.js +1 -0
- data/app/javascript/flash_unified/flash_unified.js +154 -7
- data/flash_unified.gemspec +3 -1
- data/lib/flash_unified/engine.rb +1 -0
- data/lib/flash_unified/generators/install/install_generator.rb +23 -36
- data/lib/flash_unified/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ff9e758b822e2e06c38f1543fc5e2be3b4aa101879bdd1137ab70f01b0cc350
|
|
4
|
+
data.tar.gz: 1df992f44cd7c2db0992b4130d9047af5c7017a26e3367aa395597a884adbddb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f327a2f3b87d9974b81b1031f5d787cc6b3f03927398030e0e89fb39e8c0e94232cc6ec86195dbe6e56d53a1cb140d07bb5c52d803a34d1f7930c60584aeacc
|
|
7
|
+
data.tar.gz: fcd91ea10bdeccefc552f5c66e6c6e8e4ddc053acb4f3e638b1c3b8e04db51efd6463478464cc7e308e38bbc7db54fb266ccf21ee7df3782569ffe6664abc575
|
data/README.md
CHANGED
|
@@ -2,29 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
FlashUnified provides a unified Flash message rendering mechanism for Rails applications that can be used from both server-side and client-side code.
|
|
4
4
|
|
|
5
|
-
Server-side view helpers embed Flash messages as
|
|
5
|
+
Server-side view helpers embed Flash messages as data in the page, and a lightweight client-side JavaScript library reads those storages and renders messages into visible containers using templates.
|
|
6
6
|
|
|
7
7
|
## Current status
|
|
8
8
|
|
|
9
|
-
This project is considered alpha
|
|
9
|
+
This project is considered alpha through v1.0.0. Public APIs are not yet stable and may change in future releases.
|
|
10
10
|
|
|
11
11
|
## Motivation
|
|
12
12
|
|
|
13
|
-
We
|
|
13
|
+
We had two challenges at the same time.
|
|
14
14
|
|
|
15
|
-
One
|
|
15
|
+
One was to display messages originating from the client-side with the same UI representation as Flash from the server-side. For example, when a large request is blocked by a proxy, we want to display a 413 error as a Flash message. Since the request never reaches the Rails server, this must be handled on the client-side, but we want to display it with the same UI logic as normal Flash.
|
|
16
16
|
|
|
17
|
-
The other
|
|
17
|
+
The other was to display Flash messages from Turbo Frames as well. It's not a problem if Flash is displayed within the frame, but in most cases it will be displayed outside the frame.
|
|
18
18
|
|
|
19
19
|
## How it works
|
|
20
20
|
|
|
21
|
-
The key
|
|
21
|
+
The key point to solving these challenges is that rendering needs to be performed on the JavaScript side. Therefore, we devised a two-stage process that divides responsibilities between the server-side and client-side:
|
|
22
22
|
|
|
23
|
-
1. The server embeds the Flash object
|
|
24
|
-
2.
|
|
25
|
-
|
|
26
|
-
This mechanism is simple; the main requirement is to define rules for how to embed data. This gem defines a DOM structure for the embedding, which we call "storage":
|
|
23
|
+
1. The server embeds the Flash object as a hidden DOM element within the page, renders the page, and returns it.
|
|
24
|
+
2. When the client-side JavaScript detects a page change, it scans those elements, reads the embedded messages, formats them using templates, and inserts (renders) them into the specified container elements. At that time, message elements are removed from the DOM to avoid duplicate displays.
|
|
27
25
|
|
|
26
|
+
The mechanism is simple, and to implement it, we only need to decide on the rules for how to embed. In this gem, we define the embedded DOM structure as follows and call it "storage":
|
|
28
27
|
```erb
|
|
29
28
|
<div data-flash-storage style="display: none;">
|
|
30
29
|
<ul>
|
|
@@ -35,16 +34,13 @@ This mechanism is simple; the main requirement is to define rules for how to emb
|
|
|
35
34
|
</div>
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
The "container" (where Flash messages are displayed) and the "templates" used for formatting are independent of the storage location and can be placed anywhere. This means that even when storage is inside a Turbo Frame, the rendering can target a Flash display area outside the frame.
|
|
41
|
-
|
|
42
|
-
For client-side handling of cases like proxy errors on form submission, instead of rendering an error message directly from JavaScript, embed the message into a container element first and let the same templates and processing flow render the Flash message.
|
|
37
|
+
Since storage is a hidden element, it can be placed anywhere in the page rendered by the server. For Turbo Frames, place it inside the frame.
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
The "container" where Flash messages are displayed and the "templates" for formatting can be placed anywhere regardless of the storage. This means that even with Turbo Frames, it works with Flash rendering areas placed outside the frame.
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
When handling cases on the client-side where a proxy returns an error when a form is submitted, instead of displaying the error message directly from JavaScript, you can render Flash in the same way (using the same templates and processing flow) by temporarily embedding the message as a storage element.
|
|
47
42
|
|
|
43
|
+
On the other hand, in controllers that set Flash, there is no difference from the normal Flash message display procedure:
|
|
48
44
|
```ruby
|
|
49
45
|
if @user.save
|
|
50
46
|
redirect_to @user, notice: "Created successfully."
|
|
@@ -54,308 +50,71 @@ else
|
|
|
54
50
|
end
|
|
55
51
|
```
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
The main implementation task when using this gem is determining when the embedded data should be rendered as Flash messages. Typically this is done with events. Specific handling is left to the implementer, but helpers for automatic event setup are also provided. You can also explicitly call display methods within arbitrary processing.
|
|
60
|
-
|
|
61
|
-
## Main features
|
|
62
|
-
|
|
63
|
-
This gem provides the mechanism organized according to defined rules and helper tools to support implementation.
|
|
53
|
+
In other words, when introducing this gem, **no changes are required to existing controllers**. Also, there is almost no need to change existing page layouts. The DOM elements to be set in views are hidden elements, and you only need to slightly adjust the container area where Flash messages are displayed.
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
- View helpers that render DOM fragments expected by the client:
|
|
67
|
-
- Hidden storage elements for temporarily saving messages in the page
|
|
68
|
-
- Templates for the actual display elements
|
|
69
|
-
- A container element indicating where templates should be inserted
|
|
70
|
-
- Localized messages for HTTP status (for advanced usage)
|
|
55
|
+
The main thing to implement when introducing this gem is the timing to display embedded data as Flash messages. Normally, you will use events. While the specific implementation is left to the implementer, helpers are provided to automatically set up events. You can also explicitly call methods for display within arbitrary processes.
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
- A minimal library in `flash_unified.js` (ES Module). Configure via Importmap or the asset pipeline.
|
|
74
|
-
- `auto.js` for automatic initialization (optional)
|
|
75
|
-
- `turbo_helpers.js` for Turbo integration (optional)
|
|
76
|
-
- `network_helpers.js` for network/HTTP error display (optional)
|
|
57
|
+
## Quick Start
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
- An installer generator that copies the above files into the host application.
|
|
80
|
-
|
|
81
|
-
## Installation
|
|
82
|
-
|
|
83
|
-
Add the following to your application's `Gemfile`:
|
|
59
|
+
### 1. Installation
|
|
84
60
|
|
|
61
|
+
When using Bundler, add the gem entry to your Gemfile:
|
|
85
62
|
```ruby
|
|
86
63
|
gem 'flash_unified'
|
|
87
64
|
```
|
|
88
65
|
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
Run the command:
|
|
91
67
|
```bash
|
|
92
68
|
bundle install
|
|
93
69
|
```
|
|
94
70
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
This gem provides JavaScript, template, and locale translation files from within the engine. Only copy files using the generator if you want to customize them. Details are described below.
|
|
100
|
-
|
|
101
|
-
### 2. JavaScript library setup
|
|
102
|
-
|
|
103
|
-
**Importmap:**
|
|
104
|
-
|
|
105
|
-
Pin the JavaScript modules you use to `config/importmap.rb`:
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
pin "flash_unified", to: "flash_unified/flash_unified.js"
|
|
109
|
-
pin "flash_unified/network_helpers", to: "flash_unified/network_helpers.js"
|
|
110
|
-
pin "flash_unified/turbo_helpers", to: "flash_unified/turbo_helpers.js"
|
|
111
|
-
pin "flash_unified/auto", to: "flash_unified/auto.js"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Use `auto.js` to set up rendering timing automatically. `auto.js` automatically handles Turbo integration event registration, custom event registration, and initial page rendering.
|
|
115
|
-
|
|
116
|
-
If you want to control or implement such events yourself, use the core library `flash_unified.js` to implement rendering processing independently. In that case, `auto.js` is not needed. The helpers `turbo_helpers.js` and `network_helpers.js` are optional, so pin only the ones you will use.
|
|
117
|
-
|
|
118
|
-
**Asset pipeline (Propshaft / Sprockets):**
|
|
119
|
-
|
|
120
|
-
```erb
|
|
121
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/flash_unified.js') %>">
|
|
122
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/network_helpers.js') %>">
|
|
123
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/turbo_helpers.js') %>">
|
|
124
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/auto.js') %>">
|
|
125
|
-
<script type="importmap">
|
|
126
|
-
{
|
|
127
|
-
"imports": {
|
|
128
|
-
"flash_unified": "<%= asset_path('flash_unified/flash_unified.js') %>",
|
|
129
|
-
"flash_unified/auto": "<%= asset_path('flash_unified/auto.js') %>",
|
|
130
|
-
"flash_unified/turbo_helpers": "<%= asset_path('flash_unified/turbo_helpers.js') %>",
|
|
131
|
-
"flash_unified/network_helpers": "<%= asset_path('flash_unified/network_helpers.js') %>"
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
</script>
|
|
135
|
-
<script type="module">
|
|
136
|
-
import "flash_unified/auto";
|
|
137
|
-
</script>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### 3. JavaScript initialization
|
|
141
|
-
|
|
142
|
-
When using helpers, ensure the initialization that registers event handlers runs on page load.
|
|
143
|
-
|
|
144
|
-
**Automatic initialization (simple implementation case):**
|
|
145
|
-
|
|
146
|
-
When using `auto.js`, import `auto` in your JavaScript entry point (e.g., `app/javascript/application.js`):
|
|
147
|
-
```js
|
|
148
|
-
import "flash_unified/auto";
|
|
71
|
+
Or install it directly:
|
|
72
|
+
```bash
|
|
73
|
+
gem install flash_unified
|
|
149
74
|
```
|
|
150
75
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
**Semi-automatic control (Turbo events are set up automatically):**
|
|
154
|
-
|
|
155
|
-
When using `turbo_helpers.js`, initialization is not run automatically after import. Call the methods from the imported module:
|
|
156
|
-
```js
|
|
157
|
-
import { installInitialRenderListener } from "flash_unified";
|
|
158
|
-
import { installTurboRenderListeners } from "flash_unified/turbo_helpers";
|
|
76
|
+
### 2. Client-side setup (Importmap)
|
|
159
77
|
|
|
160
|
-
|
|
161
|
-
|
|
78
|
+
Add to `config/importmap.rb`:
|
|
79
|
+
```ruby
|
|
80
|
+
pin "flash_unified/all", to: "flash_unified/all.bundle.js"
|
|
162
81
|
```
|
|
163
82
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
When implementing event registration and other aspects yourself, you'll typically need to call `renderFlashMessages()` at least on initial page load to process messages that may have been embedded by the server. This has been prepared as `installInitialRenderListener()` since it's a standard procedure:
|
|
169
|
-
|
|
170
|
-
```js
|
|
171
|
-
import { installInitialRenderListener } from "flash_unified";
|
|
172
|
-
installInitialRenderListener();
|
|
83
|
+
Import in your JavaScript entry point (e.g., `app/javascript/application.js`):
|
|
84
|
+
```javascript
|
|
85
|
+
import "flash_unified/all";
|
|
173
86
|
```
|
|
174
87
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
```js
|
|
178
|
-
renderFlashMessages();
|
|
179
|
-
```
|
|
88
|
+
### 3. Server-side setup
|
|
180
89
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
### Helpers
|
|
184
|
-
|
|
185
|
-
Server-side view helpers render the DOM fragments (templates, storage, containers, etc.) that the client expects. There are corresponding partial templates for each helper, but generally you don't need to change anything except the partial template for `flash_templates`.
|
|
186
|
-
|
|
187
|
-
- `flash_global_storage` — a globally placed general-purpose storage element (note: includes `id="flash-storage"`).
|
|
188
|
-
- `flash_storage` — a storage element; include it inside the content you return.
|
|
189
|
-
- `flash_templates` — display element templates used by the client (`<template>` elements).
|
|
190
|
-
- `flash_container` — the container element to place at the target location where users will actually see messages.
|
|
191
|
-
- `flash_general_error_messages` — an element that defines messages for HTTP status codes.
|
|
192
|
-
|
|
193
|
-
Important: the JavaScript relies on specific DOM contracts defined by the gem (for example, adding `id="flash-storage"` to global storage elements and template IDs in the form `flash-message-template-<type>`). Changing these IDs or selectors will break integration, so if you make changes, you must also update the corresponding JavaScript code.
|
|
194
|
-
|
|
195
|
-
### Minimal layout example
|
|
196
|
-
|
|
197
|
-
These are hidden elements so they can be placed anywhere. Typically placing them directly under `<body>` is sufficient:
|
|
90
|
+
Place the "sources" with the helper right after `<body>` in your layout:
|
|
198
91
|
```erb
|
|
199
|
-
|
|
200
|
-
<%=
|
|
201
|
-
|
|
92
|
+
<body>
|
|
93
|
+
<%= flash_unified_sources %>
|
|
94
|
+
...
|
|
202
95
|
```
|
|
203
96
|
|
|
204
|
-
Place
|
|
97
|
+
Place the "container" with the helper at the location where you want to display messages:
|
|
205
98
|
```erb
|
|
206
|
-
|
|
99
|
+
<div class="notify">
|
|
100
|
+
<%= flash_container %>
|
|
101
|
+
...
|
|
207
102
|
```
|
|
208
103
|
|
|
209
|
-
|
|
210
|
-
```erb
|
|
211
|
-
<%= flash_storage %>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Template customization
|
|
104
|
+
That's it — event handlers that monitor page changes will scan storages and render messages into containers.
|
|
215
105
|
|
|
216
|
-
|
|
106
|
+
As mentioned earlier, no changes are required in your controllers for setting Flash messages.
|
|
217
107
|
|
|
218
|
-
|
|
219
|
-
bin/rails generate flash_unified:install --templates
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
You can freely customize by editing the copied `app/views/flash_unified/_templates.html.erb`.
|
|
223
|
-
|
|
224
|
-
Here is a partial example:
|
|
225
|
-
|
|
226
|
-
```erb
|
|
227
|
-
<template id="flash-message-template-notice">
|
|
228
|
-
<div class="flash-notice" role="alert">
|
|
229
|
-
<span class="flash-message-text"></span>
|
|
230
|
-
</div>
|
|
231
|
-
</template>
|
|
232
|
-
<template id="flash-message-template-warning">
|
|
233
|
-
<div class="flash-warning" role="alert">
|
|
234
|
-
<span class="flash-message-text"></span>
|
|
235
|
-
</div>
|
|
236
|
-
</template>
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Template IDs like `flash-message-template-notice` correspond to Flash "types" (e.g., `:notice`, `:alert`, `:warning`). The client references the type contained in messages to select the corresponding template.
|
|
240
|
-
|
|
241
|
-
The client inserts the message string into the `.flash-message-text` element within the template. Otherwise there are no constraints. Feel free to add additional elements (e.g., dismiss buttons) as needed.
|
|
242
|
-
|
|
243
|
-
## JavaScript API and extensions
|
|
244
|
-
|
|
245
|
-
The JavaScript is split into a core library and optional helpers. Use only what you need.
|
|
246
|
-
|
|
247
|
-
### Core (`flash_unified`)
|
|
248
|
-
|
|
249
|
-
- `renderFlashMessages()` — scan storages, render to containers, and remove storages.
|
|
250
|
-
- `appendMessageToStorage(message, type = 'notice')` — append to the global storage.
|
|
251
|
-
- `clearFlashMessages(message?)` — remove rendered messages (all or exact-match only).
|
|
252
|
-
- `processMessagePayload(payload)` — accept `{ type, message }[]` or `{ messages: [...] }`.
|
|
253
|
-
- `installCustomEventListener()` — subscribe to `flash-unified:messages` and process payloads.
|
|
254
|
-
- `storageHasMessages()` — utility to detect existing messages in storage.
|
|
255
|
-
- `startMutationObserver()` — (optional / experimental) monitor insertion of storages/templates and render them.
|
|
256
|
-
- `consumeFlashMessages(keep = false)` — scan all `[data-flash-storage]` elements on the current page and return an array of messages ({ type, message }[]). By default this operation is destructive and removes the storage elements; pass `keep = true` to read without removing.
|
|
257
|
-
- `aggregateFlashMessages()` — a thin wrapper over `consumeFlashMessages(true)` that returns the aggregated messages without removing storage elements. Useful for forwarding messages to external notifier libraries.
|
|
258
|
-
|
|
259
|
-
To display client-generated Flash messages at arbitrary timing, embed the message first and then perform rendering:
|
|
260
|
-
|
|
261
|
-
```js
|
|
262
|
-
import { appendMessageToStorage, renderFlashMessages } from "flash_unified";
|
|
263
|
-
|
|
264
|
-
appendMessageToStorage("File size too large.", "notice");
|
|
265
|
-
renderFlashMessages();
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
To pass server-embedded messages to external libraries like toast instead of rendering them in the page, use `aggregateFlashMessages()` to get messages without destroying storage and pass them to your notification library:
|
|
269
|
-
|
|
270
|
-
```js
|
|
271
|
-
import { aggregateFlashMessages } from "flash_unified";
|
|
272
|
-
|
|
273
|
-
document.addEventListener('turbo:load', () => {
|
|
274
|
-
const msgs = aggregateFlashMessages();
|
|
275
|
-
msgs.forEach(({ type, message }) => {
|
|
276
|
-
YourNotifier[type](message); // like toastr.info(message)
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### Custom event
|
|
282
|
-
|
|
283
|
-
When using custom events, run `installCustomEventListener()` during initialization:
|
|
284
|
-
|
|
285
|
-
```js
|
|
286
|
-
import { installCustomEventListener } from "flash_unified";
|
|
287
|
-
installCustomEventListener();
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Then, at any desired timing, dispatch a `flash-unified:messages` event to the document:
|
|
291
|
-
|
|
292
|
-
```js
|
|
293
|
-
// Example: passing an array
|
|
294
|
-
document.dispatchEvent(new CustomEvent('flash-unified:messages', {
|
|
295
|
-
detail: [
|
|
296
|
-
{ type: 'notice', message: 'Sent successfully.' },
|
|
297
|
-
{ type: 'warning', message: 'Expires in one week.' }
|
|
298
|
-
]
|
|
299
|
-
}));
|
|
300
|
-
|
|
301
|
-
// Example: passing an object
|
|
302
|
-
document.dispatchEvent(new CustomEvent('flash-unified:messages', {
|
|
303
|
-
detail: { messages: [ { type: 'alert', message: 'Operation was cancelled.' } ] }
|
|
304
|
-
}));
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### Turbo helpers (`flash_unified/turbo_helpers`)
|
|
308
|
-
|
|
309
|
-
When using Turbo for partial page updates, you need to perform rendering processing triggered by partial update events. A helper is provided to register those event listeners in bulk:
|
|
310
|
-
|
|
311
|
-
- `installTurboRenderListeners()` — register events for rendering according to Turbo lifecycle.
|
|
312
|
-
- `installTurboIntegration()` — intended for use by `auto.js`, combines `installTurboRenderListeners()` and `installCustomEventListener()`.
|
|
313
|
-
|
|
314
|
-
```js
|
|
315
|
-
import { installTurboRenderListeners } from "flash_unified/turbo_helpers";
|
|
316
|
-
installTurboRenderListeners();
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Network/HTTP error helpers (`flash_unified/network_helpers`)
|
|
320
|
-
|
|
321
|
-
When using network/HTTP error helpers:
|
|
322
|
-
```js
|
|
323
|
-
import { notifyNetworkError, notifyHttpError } from "flash_unified/network_helpers";
|
|
324
|
-
|
|
325
|
-
notifyNetworkError(); // Set and render generic network error message
|
|
326
|
-
notifyHttpError(413); // Set and render HTTP status-specific message
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
- `notifyNetworkError()` — uses generic network error text from `#general-error-messages` for rendering.
|
|
330
|
-
- `notifyHttpError(status)` — similarly uses HTTP status-specific text for rendering.
|
|
331
|
-
|
|
332
|
-
The text used here is written as hidden elements by the server-side view helper `flash_general_error_messages`, and the original text is placed as I18n translation files in `config/locales/http_status_messages.*.yml`.
|
|
333
|
-
|
|
334
|
-
To customize the default translation content, copy translation files to your host app with the following command and edit them:
|
|
335
|
-
|
|
336
|
-
```bash
|
|
337
|
-
bin/rails generate flash_unified:install --locales
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
### Auto initialization entry (`flash_unified/auto`)
|
|
341
|
-
|
|
342
|
-
Importing `flash_unified/auto` automatically runs Turbo integration initialization after DOM ready. The behavior at that time can be controlled with data attributes on `<html>`:
|
|
343
|
-
|
|
344
|
-
- `data-flash-unified-auto-init="false"` — disable automatic initialization.
|
|
345
|
-
- `data-flash-unified-enable-network-errors="true"` — also enable listeners for network/HTTP errors.
|
|
346
|
-
|
|
347
|
-
```erb
|
|
348
|
-
<html data-flash-unified-enable-network-errors="true">
|
|
349
|
-
```
|
|
108
|
+
**For detailed usage** (customization, API reference, Turbo/network helpers, templates, locales, generator usage, and examples), see [`ADVANCED.md`](ADVANCED.md). Examples for using asset pipelines like Sprockets are also provided.
|
|
350
109
|
|
|
351
110
|
## Development
|
|
352
111
|
|
|
353
|
-
For detailed development and testing procedures, see [DEVELOPMENT.md](DEVELOPMENT.md)
|
|
112
|
+
For detailed development and testing procedures, see [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
354
113
|
|
|
355
114
|
## Changelog
|
|
356
115
|
|
|
357
|
-
See
|
|
116
|
+
See the [GitHub Releases page](https://github.com/hiroaki/flash-unified/releases).
|
|
358
117
|
|
|
359
118
|
## License
|
|
360
119
|
|
|
361
|
-
This project is
|
|
120
|
+
This project is released under the 0BSD (Zero-Clause BSD) license. For details, see [LICENSE](LICENSE).
|
|
@@ -27,6 +27,28 @@ module FlashUnified
|
|
|
27
27
|
def flash_general_error_messages
|
|
28
28
|
render partial: "flash_unified/general_error_messages"
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
# Wrapper helper that renders the common flash-unified pieces in a single
|
|
32
|
+
# call. This is a non-destructive convenience helper which calls the
|
|
33
|
+
# existing partial-rendering helpers in a sensible default order. Pass a
|
|
34
|
+
# hash to disable parts, e.g. `flash_unified_sources(container: false)`.
|
|
35
|
+
def flash_unified_sources(options = {})
|
|
36
|
+
opts = {
|
|
37
|
+
global_storage: true,
|
|
38
|
+
templates: true,
|
|
39
|
+
general_errors: true,
|
|
40
|
+
storage: true,
|
|
41
|
+
container: false
|
|
42
|
+
}.merge(options.transform_keys(&:to_sym))
|
|
43
|
+
|
|
44
|
+
parts = []
|
|
45
|
+
parts << flash_global_storage if opts[:global_storage]
|
|
46
|
+
parts << flash_templates if opts[:templates]
|
|
47
|
+
parts << flash_general_error_messages if opts[:general_errors]
|
|
48
|
+
parts << flash_storage if opts[:storage]
|
|
49
|
+
parts << flash_container if opts[:container]
|
|
50
|
+
|
|
51
|
+
safe_join(parts, "\n")
|
|
52
|
+
end
|
|
30
53
|
end
|
|
31
54
|
end
|
|
32
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var d=null;function x(e){if(e!==null&&typeof e!="function")throw new TypeError("Renderer must be a function or null");d=e}function S(e){if(!e||!e.isConnected)return!1;let r=window.getComputedStyle(e);return r&&r.display!=="none"&&r.visibility!=="hidden"&&parseFloat(r.opacity)>0}function N(e={}){let{primaryOnly:r=!1,visibleOnly:n=!1,sortByPriority:t=!1,firstOnly:s=!1,filter:o}=e,i=Array.from(document.querySelectorAll("[data-flash-message-container]"));return r&&(i=i.filter(l=>l.hasAttribute("data-flash-primary")&&l.getAttribute("data-flash-primary")!=="false")),n&&(i=i.filter(S)),typeof o=="function"&&(i=i.filter(o)),t&&i.sort((l,v)=>{let c=Number(l.getAttribute("data-flash-message-container-priority")),m=Number(v.getAttribute("data-flash-message-container-priority")),A=Number.isFinite(c)?c:Number.POSITIVE_INFINITY,C=Number.isFinite(m)?m:Number.POSITIVE_INFINITY;return A-C}),s?i.length>0?[i[0]]:[]:i}function L(){let e=document.documentElement,r=n=>{let t=e.getAttribute(n);if(t!==null)return t===""||t.toLowerCase()==="true"||t==="1"?!0:!(t.toLowerCase()==="false"||t==="0")};return{primaryOnly:r("data-flash-unified-container-primary-only"),visibleOnly:r("data-flash-unified-container-visible-only"),sortByPriority:r("data-flash-unified-container-sort-by-priority"),firstOnly:r("data-flash-unified-container-first-only")}}function F(e){N(L()).forEach(n=>{e.forEach(({type:t,message:s})=>{s&&n.appendChild(I(t,s))})})}function h(){document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){a()},{once:!0}):a()}function a(){let e=g(!1);typeof d=="function"?d(e):F(e)}function g(e=!1){let r=document.querySelectorAll("[data-flash-storage]"),n=[];return r.forEach(t=>{let s=t.querySelector("ul");s&&s.children.length>0&&s.querySelectorAll("li").forEach(o=>{n.push({type:o.dataset.type||"notice",message:o.textContent.trim()})}),e||t.remove()}),n}function O(){return g(!0)}function f(e,r="notice"){let n=document.getElementById("flash-storage");if(!n){console.error('[FlashUnified] #flash-storage not found. Define <div id="flash-storage" style="display:none"></div> in layout.');return}let t=n.querySelector("[data-flash-storage]");t||(t=document.createElement("div"),t.setAttribute("data-flash-storage","true"),t.style.display="none",n.appendChild(t));let s=t.querySelector("ul");s||(s=document.createElement("ul"),t.appendChild(s));let o=document.createElement("li");o.dataset.type=r,o.textContent=e,s.appendChild(o)}function y(){let e=document.documentElement;e.hasAttribute("data-flash-unified-custom-listener")||(e.setAttribute("data-flash-unified-custom-listener","true"),document.addEventListener("flash-unified:messages",function(r){try{M(r.detail)}catch(n){console.error("[FlashUnified] Failed to handle custom payload",n)}}))}function T(e){document.querySelectorAll("[data-flash-message-container]").forEach(r=>{if(typeof e>"u"){r.querySelectorAll("[data-flash-message]")?.forEach(n=>n.remove());return}r.querySelectorAll("[data-flash-message]")?.forEach(n=>{let t=n.querySelector(".flash-message-text");t&&t.textContent.trim()===e&&n.remove()})})}function I(e,r){let n=`flash-message-template-${e}`,t=document.getElementById(n);if(t&&t.content){let s=t.content.firstElementChild;if(!s){console.error(`[FlashUnified] Template #${n} has no root element`);let l=document.createElement("div");return l.setAttribute("role","alert"),l.setAttribute("data-flash-message","true"),l.textContent=r,l}let o=s.cloneNode(!0);o.setAttribute("data-flash-message","true");let i=o.querySelector(".flash-message-text");return i&&(i.textContent=r),o}else{console.error(`[FlashUnified] No template found for type: ${e}`);let s=document.createElement("div");s.setAttribute("role","alert"),s.setAttribute("data-flash-message","true");let o=document.createElement("span");return o.className="flash-message-text",o.textContent=r,s.appendChild(o),s}}function p(){let e=document.querySelectorAll("[data-flash-storage]");for(let r of e){let n=r.querySelector("ul");if(n&&n.children.length>0)return!0}return!1}function M(e){if(!e)return;let r=Array.isArray(e)?e:Array.isArray(e.messages)?e.messages:[];r.length!==0&&(r.forEach(({type:n,message:t})=>{t&&f(String(t),n)}),a())}function R(){let e=document.documentElement;if(e.hasAttribute("data-flash-unified-observer-enabled"))return;e.setAttribute("data-flash-unified-observer-enabled","true"),new MutationObserver(n=>{let t=!1;for(let s of n)s.type==="childList"&&s.addedNodes.forEach(o=>{o instanceof Element&&(o.matches('[data-flash-storage], [data-flash-message-container], template[id^="flash-message-template-"]')&&(t=!0),o.querySelector&&o.querySelector("[data-flash-storage]")&&(t=!0))});t&&a()}).observe(document.body,{childList:!0,subtree:!0})}function u(e){if(p())return;let r=Number(e);if(isNaN(r)||r<0||r>0&&r<400)return;let n;r===0?n="network":n=String(r);let t=document.querySelector("[data-flash-message-container]");if(t&&t.querySelector("[data-flash-message]"))return;let s=document.getElementById("general-error-messages");if(!s){console.error("[FlashUnified] No general error messages element found");return}let o=s.querySelector(`li[data-status="${n}"]`);o?f(o.textContent.trim(),"alert"):console.error(`[FlashUnified] No error message defined for status: ${e}`)}function P(){u(0),a()}function B(e){u(e),a()}function q(){let e=document.documentElement;e.hasAttribute("data-flash-unified-turbo-listeners")||(e.setAttribute("data-flash-unified-turbo-listeners","true"),document.addEventListener("turbo:load",function(){a()}),document.addEventListener("turbo:frame-load",function(){a()}),document.addEventListener("turbo:render",function(){a()}),w())}function w(){let e=new Event("turbo:after-stream-render");document.addEventListener("turbo:before-stream-render",r=>{let n=r.detail.render;r.detail.render=async function(t){await n(t),document.dispatchEvent(e)}}),document.addEventListener("turbo:after-stream-render",function(){a()})}function b(){let e=document.documentElement;e.hasAttribute("data-flash-unified-initialized")||(e.setAttribute("data-flash-unified-initialized","true"),q(),y())}function E(){let e=document.documentElement;e.hasAttribute("data-flash-unified-network-listeners")||(e.setAttribute("data-flash-unified-network-listeners","true"),document.addEventListener("turbo:submit-end",function(r){let n=r.detail.fetchResponse,t;n===void 0?(t=0,console.warn("[FlashUnified] No response received from server. Possible network or proxy error.")):t=n.statusCode,u(t),a()}),document.addEventListener("turbo:fetch-request-error",function(r){u(0),a()}))}if(typeof document<"u"){let e=document.documentElement;if(e.getAttribute("data-flash-unified-auto-init")!=="false"){let n=e.getAttribute("data-flash-unified-enable-network-errors")==="true",t=async()=>{b(),h(),n&&E()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t()}}export{O as aggregateFlashMessages,f as appendMessageToStorage,T as clearFlashMessages,g as consumeFlashMessages,N as getFlashMessageContainers,L as getHtmlContainerOptions,y as installCustomEventListener,h as installInitialRenderListener,E as installNetworkErrorListeners,b as installTurboIntegration,q as installTurboRenderListeners,B as notifyHttpError,P as notifyNetworkError,M as processMessagePayload,a as renderFlashMessages,u as resolveAndAppendErrorMessage,x as setFlashMessageRenderer,R as startMutationObserver,p as storageHasMessages};
|
|
@@ -6,6 +6,150 @@
|
|
|
6
6
|
* @module flash_unified
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Custom renderer function set by user. When null, defaultRenderer is used.
|
|
11
|
+
* @type {Function|null}
|
|
12
|
+
*/
|
|
13
|
+
let customRenderer = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set a custom renderer function to replace the default DOM-based rendering.
|
|
17
|
+
* Pass `null` to reset to default behavior.
|
|
18
|
+
*
|
|
19
|
+
* @param {Function|null} fn - A function that receives an array of message objects: [{type, message}, ...]
|
|
20
|
+
* @returns {void}
|
|
21
|
+
* @throws {TypeError} If fn is neither a function nor null
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* import { setFlashMessageRenderer } from 'flash_unified';
|
|
25
|
+
* // Use toastr for notifications
|
|
26
|
+
* setFlashMessageRenderer((messages) => {
|
|
27
|
+
* messages.forEach(({ type, message }) => {
|
|
28
|
+
* toastr[type === 'alert' ? 'error' : 'info'](message);
|
|
29
|
+
* });
|
|
30
|
+
* });
|
|
31
|
+
*/
|
|
32
|
+
function setFlashMessageRenderer(fn) {
|
|
33
|
+
if (fn !== null && typeof fn !== 'function') {
|
|
34
|
+
throw new TypeError('Renderer must be a function or null');
|
|
35
|
+
}
|
|
36
|
+
customRenderer = fn;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return whether an element is visible (basic heuristic).
|
|
41
|
+
* Considers display/visibility and DOM connection; does not use IntersectionObserver.
|
|
42
|
+
*
|
|
43
|
+
* @param {Element} el
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function isVisible(el) {
|
|
47
|
+
if (!el || !el.isConnected) return false;
|
|
48
|
+
const style = window.getComputedStyle(el);
|
|
49
|
+
return style && style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Collect flash message containers with optional filtering/sorting.
|
|
54
|
+
* By default, returns all elements matching `[data-flash-message-container]`.
|
|
55
|
+
* This function is intended for custom renderers to choose target containers.
|
|
56
|
+
*
|
|
57
|
+
* Options:
|
|
58
|
+
* - primaryOnly?: boolean — If true, only include elements with `data-flash-primary` present or set to "true".
|
|
59
|
+
* - visibleOnly?: boolean — If true, include only elements considered visible.
|
|
60
|
+
* - sortByPriority?: boolean — If true, sort by numeric `data-flash-message-container-priority` ascending (missing treated as Infinity).
|
|
61
|
+
* - firstOnly?: boolean — If true, return at most one element after filtering/sorting.
|
|
62
|
+
* - filter?: (el: Element) => boolean — Additional predicate to include elements.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} [options]
|
|
65
|
+
* @returns {Element[]} Array of container elements
|
|
66
|
+
*/
|
|
67
|
+
function getFlashMessageContainers(options = {}) {
|
|
68
|
+
const {
|
|
69
|
+
primaryOnly = false,
|
|
70
|
+
visibleOnly = false,
|
|
71
|
+
sortByPriority = false,
|
|
72
|
+
firstOnly = false,
|
|
73
|
+
filter
|
|
74
|
+
} = options;
|
|
75
|
+
|
|
76
|
+
// Fixed selector by gem convention
|
|
77
|
+
let list = Array.from(document.querySelectorAll('[data-flash-message-container]'));
|
|
78
|
+
|
|
79
|
+
if (primaryOnly) {
|
|
80
|
+
list = list.filter(el => el.hasAttribute('data-flash-primary') && (el.getAttribute('data-flash-primary') !== 'false'));
|
|
81
|
+
}
|
|
82
|
+
if (visibleOnly) {
|
|
83
|
+
list = list.filter(isVisible);
|
|
84
|
+
}
|
|
85
|
+
if (typeof filter === 'function') {
|
|
86
|
+
list = list.filter(filter);
|
|
87
|
+
}
|
|
88
|
+
if (sortByPriority) {
|
|
89
|
+
list.sort((a, b) => {
|
|
90
|
+
const pa = Number(a.getAttribute('data-flash-message-container-priority'));
|
|
91
|
+
const pb = Number(b.getAttribute('data-flash-message-container-priority'));
|
|
92
|
+
const va = Number.isFinite(pa) ? pa : Number.POSITIVE_INFINITY;
|
|
93
|
+
const vb = Number.isFinite(pb) ? pb : Number.POSITIVE_INFINITY;
|
|
94
|
+
return va - vb;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (firstOnly) {
|
|
98
|
+
return list.length > 0 ? [list[0]] : [];
|
|
99
|
+
}
|
|
100
|
+
return list;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read default container selection options from <html> data-attributes.
|
|
105
|
+
* Supported attributes (all optional):
|
|
106
|
+
* - data-flash-unified-container-primary-only
|
|
107
|
+
* - data-flash-unified-container-visible-only
|
|
108
|
+
* - data-flash-unified-container-sort-by-priority
|
|
109
|
+
* - data-flash-unified-container-first-only
|
|
110
|
+
*
|
|
111
|
+
* Each attribute accepts:
|
|
112
|
+
* - presence with no value → true
|
|
113
|
+
* - "true"/"1" → true
|
|
114
|
+
* - "false"/"0" → false
|
|
115
|
+
* Missing attribute yields undefined (does not override defaults).
|
|
116
|
+
*
|
|
117
|
+
* @returns {{ primaryOnly?: boolean, visibleOnly?: boolean, sortByPriority?: boolean, firstOnly?: boolean }}
|
|
118
|
+
*/
|
|
119
|
+
function getHtmlContainerOptions() {
|
|
120
|
+
const root = document.documentElement;
|
|
121
|
+
const parse = (name) => {
|
|
122
|
+
const val = root.getAttribute(name);
|
|
123
|
+
if (val === null) return undefined;
|
|
124
|
+
if (val === '' || val.toLowerCase() === 'true' || val === '1') return true;
|
|
125
|
+
if (val.toLowerCase() === 'false' || val === '0') return false;
|
|
126
|
+
// Any other non-empty value: treat as true for convenience
|
|
127
|
+
return true;
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
primaryOnly: parse('data-flash-unified-container-primary-only'),
|
|
131
|
+
visibleOnly: parse('data-flash-unified-container-visible-only'),
|
|
132
|
+
sortByPriority: parse('data-flash-unified-container-sort-by-priority'),
|
|
133
|
+
firstOnly: parse('data-flash-unified-container-first-only')
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Default renderer: renders messages into DOM containers using templates.
|
|
139
|
+
*
|
|
140
|
+
* @param {{type: string, message: string}[]} messages - Array of message objects
|
|
141
|
+
* @returns {void}
|
|
142
|
+
*/
|
|
143
|
+
function defaultRenderer(messages) {
|
|
144
|
+
// Allow page-wide defaults via <html> data-attributes
|
|
145
|
+
const containers = getFlashMessageContainers(getHtmlContainerOptions());
|
|
146
|
+
containers.forEach(container => {
|
|
147
|
+
messages.forEach(({ type, message }) => {
|
|
148
|
+
if (message) container.appendChild(createFlashMessageNode(type, message));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
9
153
|
/**
|
|
10
154
|
* Install a one-time listener that calls `renderFlashMessages()` on initial page load.
|
|
11
155
|
*
|
|
@@ -26,6 +170,7 @@ function installInitialRenderListener() {
|
|
|
26
170
|
/**
|
|
27
171
|
* Render messages found in storages into message containers.
|
|
28
172
|
* Delegates message collection to `consumeFlashMessages(false)`, which removes the storage elements.
|
|
173
|
+
* Uses custom renderer if set, otherwise uses default DOM-based rendering.
|
|
29
174
|
*
|
|
30
175
|
* @example
|
|
31
176
|
* import { renderFlashMessages } from 'flash_unified';
|
|
@@ -34,14 +179,13 @@ function installInitialRenderListener() {
|
|
|
34
179
|
* @returns {void}
|
|
35
180
|
*/
|
|
36
181
|
function renderFlashMessages() {
|
|
37
|
-
const containers = document.querySelectorAll('[data-flash-message-container]');
|
|
38
|
-
|
|
39
182
|
const messages = consumeFlashMessages(false);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
183
|
+
|
|
184
|
+
if (typeof customRenderer === 'function') {
|
|
185
|
+
customRenderer(messages);
|
|
186
|
+
} else {
|
|
187
|
+
defaultRenderer(messages);
|
|
188
|
+
}
|
|
45
189
|
}
|
|
46
190
|
|
|
47
191
|
/**
|
|
@@ -282,6 +426,9 @@ function startMutationObserver() {
|
|
|
282
426
|
|
|
283
427
|
export {
|
|
284
428
|
renderFlashMessages,
|
|
429
|
+
setFlashMessageRenderer,
|
|
430
|
+
getFlashMessageContainers,
|
|
431
|
+
getHtmlContainerOptions,
|
|
285
432
|
appendMessageToStorage,
|
|
286
433
|
clearFlashMessages,
|
|
287
434
|
processMessagePayload,
|
data/flash_unified.gemspec
CHANGED
|
@@ -35,7 +35,9 @@ Gem::Specification.new do |spec|
|
|
|
35
35
|
"README.md",
|
|
36
36
|
"CHANGELOG.md",
|
|
37
37
|
"flash_unified.gemspec"
|
|
38
|
-
].reject
|
|
38
|
+
].reject do |f|
|
|
39
|
+
File.directory?(f) || f == "app/javascript/flash_unified/all.entry.js"
|
|
40
|
+
end
|
|
39
41
|
# Ensure version file is included
|
|
40
42
|
files << "lib/flash_unified/version.rb" unless files.include?("lib/flash_unified/version.rb")
|
|
41
43
|
files.uniq
|
data/lib/flash_unified/engine.rb
CHANGED
|
@@ -30,76 +30,63 @@ module FlashUnified
|
|
|
30
30
|
|
|
31
31
|
=== FlashUnified installation instructions ===
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
Quick start (Importmap)
|
|
34
|
+
1) Add to `config/importmap.rb`:
|
|
35
35
|
|
|
36
|
-
pin "flash_unified", to: "flash_unified/
|
|
37
|
-
pin "flash_unified/auto", to: "flash_unified/auto.js"
|
|
38
|
-
pin "flash_unified/turbo_helpers", to: "flash_unified/turbo_helpers.js"
|
|
39
|
-
pin "flash_unified/network_helpers", to: "flash_unified/network_helpers.js"
|
|
36
|
+
pin "flash_unified/all", to: "flash_unified/all.bundle.js"
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
2) Import once in your JavaScript entry point (e.g. `app/javascript/application.js`):
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
import "flash_unified/all";
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
// Configure via <html data-flash-unified-*>:
|
|
47
|
-
// data-flash-unified-auto-init="false" (opt-out)
|
|
48
|
-
// data-flash-unified-enable-network-errors="true" (install Turbo network error listeners)
|
|
42
|
+
3) In your layout (inside `<body>`):
|
|
49
43
|
|
|
44
|
+
<%= flash_unified_sources %>
|
|
45
|
+
<%= flash_container %>
|
|
46
|
+
|
|
47
|
+
Auto-initialization is enabled by default. Control it via `<html>` attributes:
|
|
48
|
+
- `data-flash-unified-auto-init="false"` to disable all automatic wiring.
|
|
49
|
+
- `data-flash-unified-enable-network-errors="true"` to enable network error listeners.
|
|
50
|
+
|
|
51
|
+
Advanced usage (optional)
|
|
50
52
|
Manual control:
|
|
51
53
|
|
|
52
54
|
import { renderFlashMessages, appendMessageToStorage } from "flash_unified";
|
|
53
55
|
import { installTurboRenderListeners } from "flash_unified/turbo_helpers";
|
|
54
56
|
installTurboRenderListeners();
|
|
55
57
|
|
|
56
|
-
Network helpers
|
|
58
|
+
Network helpers:
|
|
57
59
|
|
|
58
60
|
import { notifyNetworkError, notifyHttpError } from "flash_unified/network_helpers";
|
|
59
61
|
// notifyNetworkError();
|
|
60
62
|
// notifyHttpError(413);
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
Propshaft / Sprockets quick start
|
|
65
|
+
Place in `<head>`:
|
|
63
66
|
|
|
64
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/
|
|
65
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/network_helpers.js') %>">
|
|
66
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/turbo_helpers.js') %>">
|
|
67
|
-
<link rel="modulepreload" href="<%= asset_path('flash_unified/auto.js') %>">
|
|
67
|
+
<link rel="modulepreload" href="<%= asset_path('flash_unified/all.bundle.js') %>">
|
|
68
68
|
<script type="importmap">
|
|
69
69
|
{
|
|
70
70
|
"imports": {
|
|
71
|
-
"flash_unified": "<%= asset_path('flash_unified/
|
|
72
|
-
"flash_unified/auto": "<%= asset_path('flash_unified/auto.js') %>",
|
|
73
|
-
"flash_unified/turbo_helpers": "<%= asset_path('flash_unified/turbo_helpers.js') %>",
|
|
74
|
-
"flash_unified/network_helpers": "<%= asset_path('flash_unified/network_helpers.js') %>"
|
|
71
|
+
"flash_unified/all": "<%= asset_path('flash_unified/all.bundle.js') %>"
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
</script>
|
|
78
75
|
<script type="module">
|
|
79
|
-
import "flash_unified/
|
|
76
|
+
import "flash_unified/all";
|
|
80
77
|
</script>
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
How to place partials in your layout
|
|
85
|
-
- The gem's view helpers render engine partials. After running this generator you'll have the partials available under `app/views/flash_unified` and can customize them as needed.
|
|
86
|
-
|
|
87
|
-
Recommended layout snippet (inside `<body>`, global helpers):
|
|
79
|
+
(Optionally map `flash_unified` or other modules if you need manual control APIs.)
|
|
88
80
|
|
|
81
|
+
Layout helpers
|
|
89
82
|
<%= flash_general_error_messages %>
|
|
90
83
|
<%= flash_global_storage %>
|
|
91
84
|
<%= flash_templates %>
|
|
92
|
-
|
|
93
|
-
Place the visible container wherever messages should appear:
|
|
94
|
-
|
|
95
85
|
<%= flash_container %>
|
|
96
|
-
|
|
97
|
-
Embed per-response storage inside content (e.g. Turbo Frame responses):
|
|
98
|
-
|
|
99
86
|
<%= flash_storage %>
|
|
100
87
|
|
|
101
88
|
Documentation
|
|
102
|
-
-
|
|
89
|
+
- See README.md / README.ja.md for customization guidance and advanced scenarios.
|
|
103
90
|
|
|
104
91
|
MSG
|
|
105
92
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flash_unified
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hiroaki
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- LICENSE
|
|
162
162
|
- README.md
|
|
163
163
|
- app/helpers/flash_unified/view_helper.rb
|
|
164
|
+
- app/javascript/flash_unified/all.bundle.js
|
|
164
165
|
- app/javascript/flash_unified/auto.js
|
|
165
166
|
- app/javascript/flash_unified/flash_unified.js
|
|
166
167
|
- app/javascript/flash_unified/network_helpers.js
|