utopia 2.31.1 → 2.32.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
- checksums.yaml.gz.sig +0 -0
- data/context/index.yaml +2 -4
- data/context/integrating-with-javascript.md +129 -34
- data/lib/utopia/content/builder.rb +97 -0
- data/lib/utopia/content/document.rb +4 -84
- data/lib/utopia/controller/rewrite.md +1 -1
- data/lib/utopia/version.rb +1 -1
- data/readme.md +1 -1
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2617b169570e3ea31076f246227e4418b59c7f04922b3385a3dabe2488d3202b
|
|
4
|
+
data.tar.gz: 184296cc495e8f7590004e3c3dfb65f55e339df0d5d8a519e6ad05f77b187f75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10b7584f9f058cd3d9db2590fab1386448f089c20f4f2a87244eff850e61b8446b5e741d969d8cf2d31340f2084a28af6232ab69849b29a0001fcf6b2765c3cb
|
|
7
|
+
data.tar.gz: 93445d1b6b9499a4e4949ec93ddaa9e559797a4ce4b0ee496e7113a1925e0e855d3e897f3cf051c3b8881d8af0ba2c70d61a55d328fcd6c774f9567d197e0296
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/index.yaml
CHANGED
|
@@ -19,10 +19,8 @@ files:
|
|
|
19
19
|
title: Server Setup
|
|
20
20
|
description: This guide explains how to deploy a `utopia` web application.
|
|
21
21
|
- path: integrating-with-javascript.md
|
|
22
|
-
title:
|
|
23
|
-
description:
|
|
24
|
-
to simplify deployment packages distributed using `yarn` that implement the `dist`
|
|
25
|
-
sub-directory convention.
|
|
22
|
+
title: Integrating with JavaScript
|
|
23
|
+
description: This guide explains how to integrate JavaScript into your Utopia application.
|
|
26
24
|
- path: what-is-xnode.md
|
|
27
25
|
title: What is XNode?
|
|
28
26
|
description: This guide explains the `xnode` view layer and how it can be used to
|
|
@@ -1,58 +1,153 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Integrating with JavaScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide explains how to integrate JavaScript into your Utopia application.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Using Import Maps
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Import maps provide a modern way to manage JavaScript module dependencies. Utopia includes built-in support for import maps through the {ruby Utopia::ImportMap} class.
|
|
8
|
+
|
|
9
|
+
### Installing JavaScript Libraries
|
|
10
|
+
|
|
11
|
+
First, install the library using npm:
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
$
|
|
14
|
+
$ npm install jquery
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Firstly, ensure your project has a `package.json` file:
|
|
17
|
+
Copy the distribution files to `public/_components`:
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
$
|
|
20
|
+
$ bundle exec bake utopia:node:update
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
This will copy the library's distribution files (typically from `node_modules/*/dist/`) to your `public/_components/` directory, making them available for local serving.
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
### Creating the Import Map
|
|
26
|
+
|
|
27
|
+
Create a global import map in `lib/my_website/import_map.rb`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "utopia/import_map"
|
|
31
|
+
|
|
32
|
+
module MyWebsite
|
|
33
|
+
IMPORT_MAP = Utopia::ImportMap.build(base: "/_components/") do |map|
|
|
34
|
+
map.import("jquery", "./jquery/jquery.js")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
25
37
|
```
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
Then load this in `lib/my_website.rb`:
|
|
28
40
|
|
|
29
|
-
```
|
|
30
|
-
|
|
41
|
+
```ruby
|
|
42
|
+
require_relative "my_website/import_map"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Adding to Your Pages
|
|
46
|
+
|
|
47
|
+
Add it to your page template (`pages/_page.xnode`), using `relative_to` to adjust paths for the current page:
|
|
48
|
+
|
|
49
|
+
```xrb
|
|
50
|
+
<html>
|
|
51
|
+
<head>
|
|
52
|
+
#{MyWebsite::IMPORT_MAP.relative_to(request.path + "/")}
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<!-- Your content -->
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Using the Library
|
|
61
|
+
|
|
62
|
+
Once the import map is set up, you can import and use the library in your scripts:
|
|
63
|
+
|
|
64
|
+
```xrb
|
|
65
|
+
<script type="module">
|
|
66
|
+
// <![CDATA[
|
|
67
|
+
import $ from 'jquery';
|
|
68
|
+
|
|
69
|
+
$(document).ready(function() {
|
|
70
|
+
console.log("jQuery is ready!");
|
|
71
|
+
});
|
|
72
|
+
// ]]>
|
|
73
|
+
</script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Advanced Import Map Features
|
|
78
|
+
|
|
79
|
+
#### Using CDN URLs
|
|
80
|
+
|
|
81
|
+
Import maps support direct CDN imports without downloading files:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
IMPORT_MAP = Utopia::ImportMap.build do |map|
|
|
85
|
+
map.import("react", "https://esm.sh/react@18")
|
|
86
|
+
map.import("vue", "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.esm-browser.js")
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Nested Base URLs
|
|
91
|
+
|
|
92
|
+
You can organize imports from different sources using nested `with(base:)` blocks:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
IMPORT_MAP = Utopia::ImportMap.build do |map|
|
|
96
|
+
# Local components
|
|
97
|
+
map.with(base: "/_components/") do |local|
|
|
98
|
+
local.import "app", "./app.js"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# CDN imports
|
|
102
|
+
map.with(base: "https://cdn.jsdelivr.net/npm/") do |cdn|
|
|
103
|
+
cdn.import "lit", "lit@2.7.5/index.js"
|
|
104
|
+
cdn.import "lit/decorators.js", "lit@2.7.5/decorators.js"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
31
107
|
```
|
|
32
108
|
|
|
33
|
-
|
|
109
|
+
#### Subresource Integrity
|
|
110
|
+
|
|
111
|
+
Add integrity hashes for enhanced security:
|
|
34
112
|
|
|
35
|
-
```
|
|
36
|
-
|
|
113
|
+
```ruby
|
|
114
|
+
IMPORT_MAP = Utopia::ImportMap.build do |map|
|
|
115
|
+
map.import("react", "https://esm.sh/react@18", integrity: "sha384-...")
|
|
116
|
+
end
|
|
37
117
|
```
|
|
38
118
|
|
|
39
|
-
|
|
119
|
+
#### Scoped Imports
|
|
120
|
+
|
|
121
|
+
Use scopes to resolve imports differently based on the **referrer URL** (the page or module location where the import is being made):
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
IMPORT_MAP = Utopia::ImportMap.build do |map|
|
|
125
|
+
map.import("utils", "/utils.js")
|
|
126
|
+
|
|
127
|
+
# When importing from any page under /admin/, use a different utils module
|
|
128
|
+
map.scope("/admin/", {"utils" => "/admin/utils.js"})
|
|
129
|
+
end
|
|
130
|
+
```
|
|
40
131
|
|
|
41
|
-
|
|
132
|
+
When you're on a page at `/admin/dashboard` and you `import "utils"`, it will resolve to `/admin/utils.js`. On other pages, it resolves to `/utils.js`.
|
|
133
|
+
|
|
134
|
+
## Traditional JavaScript
|
|
135
|
+
|
|
136
|
+
You can also use JavaScript by embedding it directly into your HTML, or by creating a JavaScript source file and referencing that.
|
|
42
137
|
|
|
43
138
|
### Embedding Code
|
|
44
139
|
|
|
45
|
-
|
|
140
|
+
When embedding JavaScript directly in XRB templates, wrap the code in CDATA comments to prevent XRB's parser from interpreting special characters like `<`, `>`, and `&`:
|
|
46
141
|
|
|
47
|
-
```
|
|
142
|
+
```xrb
|
|
48
143
|
<html>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
144
|
+
<body>
|
|
145
|
+
<script type="text/javascript">
|
|
146
|
+
// <![CDATA[
|
|
147
|
+
console.log("Hello World")
|
|
148
|
+
// ]]>
|
|
149
|
+
</script>
|
|
150
|
+
</body>
|
|
56
151
|
</html>
|
|
57
152
|
```
|
|
58
153
|
|
|
@@ -66,10 +161,10 @@ console.log("Hello World")
|
|
|
66
161
|
|
|
67
162
|
In your HTML view:
|
|
68
163
|
|
|
69
|
-
```
|
|
164
|
+
```xrb
|
|
70
165
|
<html>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
166
|
+
<body>
|
|
167
|
+
<script type="text/javascript" src="script.js"></script>
|
|
168
|
+
</body>
|
|
74
169
|
</html>
|
|
75
|
-
```
|
|
170
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "xrb/builder"
|
|
7
|
+
|
|
8
|
+
module Utopia
|
|
9
|
+
module Content
|
|
10
|
+
DEFERRED_TAG_NAME = "utopia:deferred".freeze
|
|
11
|
+
|
|
12
|
+
# A builder for rendering Utopia content that extends XRB::Builder with Utopia-specific functionality.
|
|
13
|
+
class Builder < XRB::Builder
|
|
14
|
+
def initialize(parent, tag, node, attributes = tag.to_hash, **options)
|
|
15
|
+
super(**options)
|
|
16
|
+
|
|
17
|
+
@parent = parent
|
|
18
|
+
@tag = tag
|
|
19
|
+
@node = node
|
|
20
|
+
@attributes = attributes
|
|
21
|
+
|
|
22
|
+
@content = nil
|
|
23
|
+
@deferred = []
|
|
24
|
+
@tags = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr :parent
|
|
28
|
+
attr :tag
|
|
29
|
+
attr :attributes
|
|
30
|
+
attr :content
|
|
31
|
+
attr :node
|
|
32
|
+
|
|
33
|
+
# A list of all tags in order of rendering them, which have not been finished yet.
|
|
34
|
+
attr :tags
|
|
35
|
+
|
|
36
|
+
attr :deferred
|
|
37
|
+
|
|
38
|
+
def defer(value = nil, &block)
|
|
39
|
+
@deferred << block
|
|
40
|
+
|
|
41
|
+
XRB::Tag.closed(DEFERRED_TAG_NAME, :id => @deferred.size - 1)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def [](key)
|
|
45
|
+
@attributes[key]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def call(document)
|
|
49
|
+
@content = @output.dup
|
|
50
|
+
@output.clear
|
|
51
|
+
|
|
52
|
+
if node.respond_to? :call
|
|
53
|
+
node.call(document, self)
|
|
54
|
+
else
|
|
55
|
+
document.parse_markup(@content)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return @output
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Override write to directly append to output
|
|
62
|
+
def write(string)
|
|
63
|
+
@output << string
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Override text to handle build_markup protocol
|
|
67
|
+
def text(content)
|
|
68
|
+
return unless content
|
|
69
|
+
|
|
70
|
+
if content.respond_to?(:build_markup)
|
|
71
|
+
content.build_markup(self)
|
|
72
|
+
else
|
|
73
|
+
XRB::Markup.append(@output, content)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def tag_complete(tag)
|
|
78
|
+
tag.write(@output)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Whether this state has any nested tags.
|
|
82
|
+
def empty?
|
|
83
|
+
@tags.empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def tag_begin(tag)
|
|
87
|
+
@tags << tag
|
|
88
|
+
tag.write_opening_tag(@output)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def tag_end(tag)
|
|
92
|
+
raise UnbalancedTagError.new(tag) unless @tags.pop.name == tag.name
|
|
93
|
+
tag.write_closing_tag(@output)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
require_relative "links"
|
|
7
7
|
require_relative "response"
|
|
8
8
|
require_relative "markup"
|
|
9
|
+
require_relative "builder"
|
|
9
10
|
|
|
10
11
|
module Utopia
|
|
11
12
|
module Content
|
|
12
|
-
DEFERRED_TAG_NAME = "utopia:deferred".freeze
|
|
13
|
-
|
|
14
13
|
# This error is raised if a tag doesn't match up when parsing.
|
|
15
14
|
class UnbalancedTagError < StandardError
|
|
16
15
|
def initialize(tag)
|
|
@@ -135,9 +134,9 @@ module Utopia
|
|
|
135
134
|
node ||= lookup_tag(tag)
|
|
136
135
|
|
|
137
136
|
if node
|
|
138
|
-
@current =
|
|
137
|
+
@current = Builder.new(@current, tag, node, tag.to_hash, indent: false)
|
|
139
138
|
|
|
140
|
-
node.tag_begin(self,
|
|
139
|
+
node.tag_begin(self, @current) if node.respond_to?(:tag_begin)
|
|
141
140
|
|
|
142
141
|
return node
|
|
143
142
|
end
|
|
@@ -184,7 +183,7 @@ module Utopia
|
|
|
184
183
|
end
|
|
185
184
|
|
|
186
185
|
def render_node(node, attributes = {})
|
|
187
|
-
@current =
|
|
186
|
+
@current = Builder.new(@current, nil, node, attributes, indent: false)
|
|
188
187
|
|
|
189
188
|
# We keep track of the first thing rendered by this document.
|
|
190
189
|
@first ||= @current
|
|
@@ -230,84 +229,5 @@ module Utopia
|
|
|
230
229
|
@end_tags[-2]
|
|
231
230
|
end
|
|
232
231
|
end
|
|
233
|
-
|
|
234
|
-
# The state of a single tag being rendered within a document instance.
|
|
235
|
-
class Document::State
|
|
236
|
-
def initialize(parent, tag, node, attributes = tag.to_hash)
|
|
237
|
-
@parent = parent
|
|
238
|
-
@tag = tag
|
|
239
|
-
@node = node
|
|
240
|
-
@attributes = attributes
|
|
241
|
-
|
|
242
|
-
@buffer = XRB::MarkupString.new.force_encoding(Encoding::UTF_8)
|
|
243
|
-
@content = nil
|
|
244
|
-
|
|
245
|
-
@deferred = []
|
|
246
|
-
|
|
247
|
-
@tags = []
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
attr :parent
|
|
251
|
-
attr :attributes
|
|
252
|
-
attr :content
|
|
253
|
-
attr :node
|
|
254
|
-
|
|
255
|
-
# A list of all tags in order of rendering them, which have not been finished yet.
|
|
256
|
-
attr :tags
|
|
257
|
-
|
|
258
|
-
attr :deferred
|
|
259
|
-
|
|
260
|
-
def defer(value = nil, &block)
|
|
261
|
-
@deferred << block
|
|
262
|
-
|
|
263
|
-
Tag.closed(DEFERRED_TAG_NAME, :id => @deferred.size - 1)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def [](key)
|
|
267
|
-
@attributes[key]
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def call(document)
|
|
271
|
-
@content = @buffer
|
|
272
|
-
@buffer = XRB::MarkupString.new.force_encoding(Encoding::UTF_8)
|
|
273
|
-
|
|
274
|
-
if node.respond_to? :call
|
|
275
|
-
node.call(document, self)
|
|
276
|
-
else
|
|
277
|
-
document.parse_markup(@content)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
return @buffer
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def write(string)
|
|
284
|
-
@buffer << string
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
def text(string)
|
|
288
|
-
XRB::Markup.append(@buffer, string)
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def tag_complete(tag)
|
|
292
|
-
tag.write(@buffer)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# Whether this state has any nested tags.
|
|
296
|
-
def empty?
|
|
297
|
-
@tags.empty?
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def tag_begin(tag)
|
|
301
|
-
# raise ArgumentError.new("tag_begin: #{tag} is tag.self_closed?") if tag.self_closed?
|
|
302
|
-
|
|
303
|
-
@tags << tag
|
|
304
|
-
tag.write_opening_tag(@buffer)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
def tag_end(tag)
|
|
308
|
-
raise UnbalancedTagError.new(tag) unless @tags.pop.name == tag.name
|
|
309
|
-
tag.write_closing_tag(@buffer)
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
232
|
end
|
|
313
233
|
end
|
data/lib/utopia/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -21,7 +21,7 @@ Please see the [project documentation](https://socketry.github.io/utopia/) for m
|
|
|
21
21
|
|
|
22
22
|
- [Server Setup](https://socketry.github.io/utopia/guides/server-setup/index) - This guide explains how to deploy a `utopia` web application.
|
|
23
23
|
|
|
24
|
-
- [
|
|
24
|
+
- [Integrating with JavaScript](https://socketry.github.io/utopia/guides/integrating-with-javascript/index) - This guide explains how to integrate JavaScript into your Utopia application.
|
|
25
25
|
|
|
26
26
|
- [What is XNode?](https://socketry.github.io/utopia/guides/what-is-xnode/index) - This guide explains the `xnode` view layer and how it can be used to build efficient websites.
|
|
27
27
|
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: utopia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.32.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -273,6 +273,7 @@ files:
|
|
|
273
273
|
- context/what-is-xnode.md
|
|
274
274
|
- lib/utopia.rb
|
|
275
275
|
- lib/utopia/content.rb
|
|
276
|
+
- lib/utopia/content/builder.rb
|
|
276
277
|
- lib/utopia/content/document.rb
|
|
277
278
|
- lib/utopia/content/link.rb
|
|
278
279
|
- lib/utopia/content/links.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|