lively 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25c5a24a2c298b43882809b4ef1f42e59ad1ab64a9bd4873351506c435e6fd07
4
- data.tar.gz: c2defb6ca553cce597f0093f8b345f71d13f26b4d100b70593b8dab9aac9457d
3
+ metadata.gz: 2eb71ca34e43090f3b06b4577cc5c29ed82d3282df6fb26c69adb02cc5eff5fe
4
+ data.tar.gz: 7df1288ae0defc42656da59f5a12e92f3619adc9993e54a4b5e55ac7d10cd795
5
5
  SHA512:
6
- metadata.gz: a5b2e54d74e5a7101eba9c8a8e6a6c81c2d43396e493267d4d6aad0531b05a0911ec939e6a5c64db59886aef539c6fd02435f56fdc203946db793c5e6ca5797e
7
- data.tar.gz: 2ef972d3dd1154f6d69b3c31cf0ef8a520d85424fe1eb49716c5c35be28016eb0ecf7c1fd288cd4b26199d070bfdd8c1319b4cc2924f8b5113b43d7c26cde525
6
+ metadata.gz: dafdbe14c26702f71de2bc519ac398d141ffc2d1b7f2d1b94f32c44602ea9efc6c083282268f48c7d08798269947322d13bc7005d664b02863fb7bf4f041f031
7
+ data.tar.gz: d65fd9f4c15cd6992c67bf2ffb1ee21d4d86d15643b71e092f38f55386f438bea86e1aa1b0a535a1c0fbd48836e6ec99e77575e751eec513f7c74f77d89064f5
checksums.yaml.gz.sig ADDED
Binary file
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  require 'live'
24
7
  require 'async/websocket/adapters/http'
data/lib/lively/assets.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  module Lively
24
7
  class Assets < Protocol::HTTP::Middleware
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
6
+ module Lively
7
+ module Environment
8
+ module Application
9
+ include Falcon::Environment::Server
10
+
11
+ def application
12
+ if Object.const_defined?(:Application, false)
13
+ ::Application
14
+ else
15
+ Console.warn(self, "No Application class defined, using default.")
16
+ ::Lively::Application
17
+ end
18
+ end
19
+
20
+ def middleware
21
+ ::Protocol::HTTP::Middleware.build do |builder|
22
+ builder.use Lively::Assets, root: File.expand_path('public', self.root)
23
+ builder.use Lively::Assets, root: File.expand_path('../../../public', __dir__)
24
+ builder.use self.application
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
- require 'trenni/template'
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
6
+ require 'xrb/template'
3
7
 
4
8
  module Lively
5
9
  module Pages
@@ -8,8 +12,8 @@ module Lively
8
12
  @title = title
9
13
  @body = body
10
14
 
11
- path = File.expand_path("index.trenni", __dir__)
12
- @template = Trenni::Template.load_file(path)
15
+ path = File.expand_path("index.xrb", __dir__)
16
+ @template = XRB::Template.load_file(path)
13
17
  end
14
18
 
15
19
  attr :title
@@ -10,10 +10,21 @@
10
10
  <link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
11
11
  <link rel="stylesheet" href="/_static/index.css" type="text/css" media="screen" />
12
12
 
13
- <script src="/_components/morphdom/morphdom-umd.js"></script>
14
- <script src="/_components/@socketry/live/live.js"></script>
13
+ <script type="importmap">
14
+ {
15
+ "imports": {
16
+ "live": "/_components/@socketry/live/Live.js",
17
+ "morphdom": "/_components/morphdom/morphdom-esm.js"
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <script type="module">
23
+ import {Live} from 'live';
24
+ window.live = Live.start();
25
+ </script>
15
26
  </head>
16
-
27
+
17
28
  <body>
18
29
  #{self.body&.to_html || "No body specified!"}
19
30
  </body>
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
3
6
  module Lively
4
- VERSION = "0.2.1"
7
+ VERSION = "0.4.0"
5
8
  end
data/lib/lively.rb CHANGED
@@ -1,28 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
22
5
 
23
6
  require_relative 'lively/version'
24
7
 
25
8
  require_relative 'lively/assets'
26
9
  require_relative 'lively/application'
27
10
 
28
- require_relative 'lively/environments/application'
11
+ require_relative 'lively/environment/application'
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2021-2024, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ import morphdom from 'morphdom';
2
+
3
+ export class Live {
4
+ static start(options = {}) {
5
+ let window = options.window || globalThis;
6
+ let path = options.path || 'live'
7
+ let base = options.base || window.location.href;
8
+
9
+ let url = new URL(path, base);
10
+ url.protocol = url.protocol.replace('http', 'ws');
11
+
12
+ return new this(window, url);
13
+ }
14
+
15
+ constructor(window, url) {
16
+ this.window = window;
17
+ this.document = window.document;
18
+
19
+ this.url = url;
20
+ this.events = [];
21
+
22
+ this.failures = 0;
23
+
24
+ // Track visibility state and connect if required:
25
+ this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
26
+ this.handleVisibilityChange();
27
+
28
+ // Create a MutationObserver to watch for removed nodes
29
+ this.observer = new this.window.MutationObserver((mutationsList, observer) => {
30
+ for (let mutation of mutationsList) {
31
+ if (mutation.type === 'childList') {
32
+ for (let node of mutation.removedNodes) {
33
+ if (node.classList?.contains('live')) {
34
+ this.unbind(node);
35
+ }
36
+
37
+ // Unbind any child nodes:
38
+ for (let child of node.getElementsByClassName('live')) {
39
+ this.unbind(child);
40
+ }
41
+ }
42
+
43
+ for (let node of mutation.addedNodes) {
44
+ if (node.classList?.contains('live')) {
45
+ this.bind(node);
46
+ }
47
+
48
+ // Bind any child nodes:
49
+ for (let child of node.getElementsByClassName('live')) {
50
+ this.bind(child);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ });
56
+
57
+ this.observer.observe(this.document.body, {childList: true, subtree: true});
58
+
59
+ this.attach();
60
+ }
61
+
62
+ // -- Connection Handling --
63
+
64
+ connect() {
65
+ if (this.server) return this.server;
66
+
67
+ let server = this.server = new this.window.WebSocket(this.url);
68
+
69
+ server.onopen = () => {
70
+ this.failures = 0;
71
+ this.flush();
72
+ };
73
+
74
+ server.onmessage = (message) => {
75
+ const [name, ..._arguments] = JSON.parse(message.data);
76
+
77
+ this[name](..._arguments);
78
+ };
79
+
80
+ // The remote end has disconnected:
81
+ server.addEventListener('error', () => {
82
+ this.failures += 1;
83
+ });
84
+
85
+ server.addEventListener('close', () => {
86
+ // Explicit disconnect will clear `this.server`:
87
+ if (this.server) {
88
+ // We need a minimum delay otherwise this can end up immediately invoking the callback:
89
+ const delay = Math.max(100 * (this.failures + 1) ** 2, 60000);
90
+ setTimeout(() => this.connect(), delay);
91
+ }
92
+
93
+ this.server = null;
94
+ });
95
+
96
+ return server;
97
+ }
98
+
99
+ disconnect() {
100
+ if (this.server) {
101
+ const server = this.server;
102
+ this.server = null;
103
+ server.close();
104
+ }
105
+ }
106
+
107
+ send(message) {
108
+ if (this.server) {
109
+ try {
110
+ return this.server.send(message);
111
+ } catch (error) {
112
+ // Ignore.
113
+ }
114
+ }
115
+
116
+ this.events.push(message);
117
+ }
118
+
119
+ flush() {
120
+ if (this.events.length === 0) return;
121
+
122
+ let events = this.events;
123
+ this.events = [];
124
+
125
+ for (var event of events) {
126
+ this.send(event);
127
+ }
128
+ }
129
+
130
+ handleVisibilityChange() {
131
+ if (this.document.hidden) {
132
+ this.disconnect();
133
+ } else {
134
+ this.connect();
135
+ }
136
+ }
137
+
138
+ bind(element) {
139
+ console.log("bind", element.id, element.dataset);
140
+
141
+ this.send(JSON.stringify(['bind', element.id, element.dataset]));
142
+ }
143
+
144
+ unbind(element) {
145
+ console.log("unbind", element.id, element.dataset);
146
+
147
+ this.send(JSON.stringify(['unbind', element.id]));
148
+ }
149
+
150
+ attach() {
151
+ for (let node of this.document.getElementsByClassName('live')) {
152
+ this.bind(node);
153
+ }
154
+ }
155
+
156
+ createDocumentFragment(html) {
157
+ return this.document.createRange().createContextualFragment(html);
158
+ }
159
+
160
+ reply(options) {
161
+ if (options?.reply) {
162
+ this.send(JSON.stringify(['reply', options.reply]));
163
+ }
164
+ }
165
+
166
+ // -- RPC Methods --
167
+
168
+ update(id, html, options) {
169
+ let element = this.document.getElementById(id);
170
+ let fragment = this.createDocumentFragment(html);
171
+
172
+ morphdom(element, fragment);
173
+
174
+ this.reply(options);
175
+ }
176
+
177
+ replace(selector, html, options) {
178
+ let elements = this.document.querySelectorAll(selector);
179
+ let fragment = this.createDocumentFragment(html);
180
+
181
+ elements.forEach(element => morphdom(element, fragment.cloneNode(true)));
182
+
183
+ this.reply(options);
184
+ }
185
+
186
+ prepend(selector, html, options) {
187
+ let elements = this.document.querySelectorAll(selector);
188
+ let fragment = this.createDocumentFragment(html);
189
+
190
+ elements.forEach(element => element.prepend(fragment.cloneNode(true)));
191
+
192
+ this.reply(options);
193
+ }
194
+
195
+ append(selector, html, options) {
196
+ let elements = this.document.querySelectorAll(selector);
197
+ let fragment = this.createDocumentFragment(html);
198
+
199
+ elements.forEach(element => element.append(fragment.cloneNode(true)));
200
+
201
+ this.reply(options);
202
+ }
203
+
204
+ remove(selector, options) {
205
+ let elements = this.document.querySelectorAll(selector);
206
+
207
+ elements.forEach(element => element.remove());
208
+
209
+ this.reply(options);
210
+ }
211
+
212
+ dispatchEvent(selector, type, options) {
213
+ let elements = this.document.querySelectorAll(selector);
214
+
215
+ elements.forEach(element => element.dispatchEvent(
216
+ new this.window.CustomEvent(type, options)
217
+ ));
218
+
219
+ this.reply(options);
220
+ }
221
+
222
+ // -- Event Handling --
223
+
224
+ forward(id, event) {
225
+ this.connect();
226
+
227
+ this.send(
228
+ JSON.stringify(['event', id, event])
229
+ );
230
+ }
231
+
232
+ forwardEvent(id, event, detail) {
233
+ event.preventDefault();
234
+
235
+ this.forward(id, {type: event.type, detail: detail});
236
+ }
237
+
238
+ forwardFormEvent(id, event, detail) {
239
+ event.preventDefault();
240
+
241
+ let form = event.form;
242
+ let formData = new FormData(form);
243
+
244
+ this.forward(id, {type: event.type, detail: detail, formData: [...formData]});
245
+ }
246
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@socketry/live",
3
+ "type": "module",
4
+ "version": "0.10.0",
5
+ "description": "Live HTML tags for Ruby.",
6
+ "main": "Live.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/socketry/live-js.git"
10
+ },
11
+ "scripts": {
12
+ "test": "node --test"
13
+ },
14
+ "devDependencies": {
15
+ "jsdom": "^24.0",
16
+ "ws": "^8.17"
17
+ },
18
+ "dependencies": {
19
+ "morphdom": "^2.7"
20
+ },
21
+ "keywords": [
22
+ "live",
23
+ "dynamic",
24
+ "html",
25
+ "ruby"
26
+ ],
27
+ "author": "Samuel Williams <samuel.williams@oriontransfer.co.nz> (http://www.codeotaku.com/)",
28
+ "license": "MIT",
29
+ "bugs": {
30
+ "url": "https://github.com/socketry/live-js/issues"
31
+ },
32
+ "homepage": "https://github.com/socketry/live-js#readme"
33
+ }
@@ -0,0 +1,58 @@
1
+ # Live (JavaScript)
2
+
3
+ This is the JavaScript library for implementing the Ruby gem of the same name.
4
+
5
+ ## Document Manipulation
6
+
7
+ ### `live.update(id, html, options)`
8
+
9
+ Updates the content of the element with the given `id` with the given `html`. The `options` parameter is optional and can be used to pass additional options to the update method.
10
+
11
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
12
+
13
+ ### `live.replace(selector, html, options)`
14
+
15
+ Replaces the element(s) selected by the given `selector` with the given `html`. The `options` parameter is optional and can be used to pass additional options to the replace method.
16
+
17
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
18
+
19
+ ### `live.prepend(selector, html, options)`
20
+
21
+ Prepends the given `html` to the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the prepend method.
22
+
23
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
24
+
25
+ ### `live.append(selector, html, options)`
26
+
27
+ Appends the given `html` to the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the append method.
28
+
29
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
30
+
31
+ ### `live.remove(selector, options)`
32
+
33
+ Removes the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the remove method.
34
+
35
+ - `options.reply` - If truthy, the server will reply with `{reply: options.reply}`.
36
+
37
+ ### `live.dispatchEvent(selector, type, options)`
38
+
39
+ Dispatches an event of the given `type` on the element(s) selected by the given `selector`. The `options` parameter is optional and can be used to pass additional options to the dispatchEvent method.
40
+
41
+ - `options.detail` - The detail object to pass to the event.
42
+ - `options.bubbles` - A boolean indicating whether the event should bubble up through the DOM.
43
+ - `options.cancelable` - A boolean indicating whether the event can be canceled.
44
+ - `options.composed` - A boolean indicating whether the event will trigger listeners outside of a shadow root.
45
+
46
+ ## Event Handling
47
+
48
+ ### `live.forward(id, event)`
49
+
50
+ Connect and forward an event on the element with the given `id`. If the connection can't be established, the event will be buffered.
51
+
52
+ ### `live.forwardEvent(id, event, details)`
53
+
54
+ Forward a HTML DOM event to the server. The `details` parameter is optional and can be used to pass additional details to the server.
55
+
56
+ ### `live.forwardFormEvent(id, event, details)`
57
+
58
+ Forward an event which has form data to the server. The `details` parameter is optional and can be used to pass additional details to the server.