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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 924fc6cb5c9006f0c376aed334a42996d8e86a2484959b37a225a15e60d66c60
4
- data.tar.gz: 66fc37bf668645f1cbf29bb7b2fce1ccab169db491fa0337228fccf86a2d6137
3
+ metadata.gz: 2617b169570e3ea31076f246227e4418b59c7f04922b3385a3dabe2488d3202b
4
+ data.tar.gz: 184296cc495e8f7590004e3c3dfb65f55e339df0d5d8a519e6ad05f77b187f75
5
5
  SHA512:
6
- metadata.gz: 798aef264950bc05c2cfd8bf68a40e81f0e7009aba7723d7b89aeb4cdb87f4100296027cc2de20cef2195ae01befecb623e35279493ead238a708a6b14bb6a51
7
- data.tar.gz: b936b7d95de0b3049f9de3e8cf2fdff4f947eb42440dc0011aae833e8fbe4179b165407c17aa8989123ac7f4a836c672bf0ce496df8dc2b30029f4f2938b7f97
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: Installing JavaScript Libraries
23
- description: Utopia integrates with Yarn and provides a [bake task](https://github.com/ioquatix/bake)
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
- # Installing JavaScript Libraries
1
+ # Integrating with JavaScript
2
2
 
3
- Utopia integrates with Yarn and provides a [bake task](https://github.com/ioquatix/bake) to simplify deployment packages distributed using `yarn` that implement the `dist` sub-directory convention.
3
+ This guide explains how to integrate JavaScript into your Utopia application.
4
4
 
5
- ## Installing Yarn
5
+ ## Using Import Maps
6
6
 
7
- If you don't already have yarn installed, make sure you have npm installed and then run the following command:
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
- $ sudo npm install -g yarn
14
+ $ npm install jquery
11
15
  ```
12
16
 
13
- ## Installing jQuery
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
- $ yarn init
20
+ $ bundle exec bake utopia:node:update
19
21
  ```
20
22
 
21
- Then install jquery using `yarn`:
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
- ```bash
24
- $ yarn add jquery
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
- Copy the distribution scripts to `public/_components`:
39
+ Then load this in `lib/my_website.rb`:
28
40
 
29
- ```bash
30
- $ bundle exec bake utopia:node:update
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
- Then add the appropriate `<script>` tags to `pages/_page.xnode`:
109
+ #### Subresource Integrity
110
+
111
+ Add integrity hashes for enhanced security:
34
112
 
35
- ```html
36
- <script type="text/javascript" src="/_components/jquery/jquery.min.js"></script>
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
- ## Using JavaScript
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
- You can use JavaScript by embedding it directly into your HTML, or by creating a JavaScript source file and referencing that.
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
- In your HTML view:
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
- ```trenni
142
+ ```xrb
48
143
  <html>
49
- <body>
50
- <script type="text/javascript">
51
- //<![CDATA[
52
- console.log("Hello World")
53
- //]]>
54
- </script>
55
- </body>
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
- ```trenni
164
+ ```xrb
70
165
  <html>
71
- <body>
72
- <script type="text/javascript" src="script.js"></script>
73
- </body>
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 = State.new(@current, tag, node)
137
+ @current = Builder.new(@current, tag, node, tag.to_hash, indent: false)
139
138
 
140
- node.tag_begin(self, state) if node.respond_to?(:tag_begin)
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 = State.new(@current, nil, node, attributes)
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
@@ -34,7 +34,7 @@ end
34
34
 
35
35
  In your `post.xnode`, as an example:
36
36
 
37
- ```trenni
37
+ ```xrb
38
38
  <content:page>
39
39
  <content:heading>Post #{attributes[:permalink][:id]} about #{attributes[:permalink][:title]}</content:heading>
40
40
 
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2009-2025, by Samuel Williams.
5
5
 
6
6
  module Utopia
7
- VERSION = "2.31.1"
7
+ VERSION = "2.32.0"
8
8
  end
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
- - [Installing JavaScript Libraries](https://socketry.github.io/utopia/guides/integrating-with-javascript/index) - Utopia integrates with Yarn and provides a [bake task](https://github.com/ioquatix/bake) to simplify deployment packages distributed using `yarn` that implement the `dist` sub-directory convention.
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.31.1
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