lively 0.5.0 → 0.6.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: 1fe993ef2758a622d5da68c03094ea040678f62618b26bd8f888ab6127366300
4
- data.tar.gz: 4919d6e2ec9fd70f2388d838a98740a6f911875ee233e3d351e55e59f426fa46
3
+ metadata.gz: 5792cd3cb017762c79c0c8efcf99724db21dd444aa0e6e1f0c7f4f8e0e61774a
4
+ data.tar.gz: 0275c27630c5e0af1408771737e9fa87276ce3b6e2f06de788eaa9e45d60a6a5
5
5
  SHA512:
6
- metadata.gz: 1b8e0c1657776e309f5e164f79ad66e345aac9a455ef604bf2c326dfef9ce06e2a8f8370cd6052706a825bbf159ce5e5f60ade6b3cfd5f651c38aa2ee3c15bc6
7
- data.tar.gz: 2f2e3e3043956ae4ce65c5a82e82c62af462d1b750ef51d0aa4dc71c9ffc0f731ad80779517fe44263b5c9f57a1d187f84e2cb9d0a325c46855c799c9d404c3d
6
+ metadata.gz: 3c0f91c91d6d25d138aa78332cfb9c51d4f5793f5134c059bed8d1b6d1dd4b327a70f3d8e47106a49965534157d58913952b60f7508e903b461d3e15d48381bb
7
+ data.tar.gz: c4b7817e61109d157bb68c157e8c50590776db6c1eca1c4d7f65936339ba2d02da62f07dfc903cd939d8807231dc382e6b7f18702ef0096266b3bd458b25f4cd
checksums.yaml.gz.sig CHANGED
Binary file
data/bin/lively ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'async/service'
4
+ require_relative '../lib/lively/environment/application'
5
+
6
+ ARGV.each do |path|
7
+ require(path)
8
+ end
9
+
10
+ configuration = Async::Service::Configuration.build do
11
+ service "lively" do
12
+ include Lively::Environment::Application
13
+ end
14
+ end
15
+
16
+ Async::Service::Controller.run(configuration)
@@ -4,12 +4,32 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  require 'live'
7
+ require 'protocol/http/middleware'
7
8
  require 'async/websocket/adapters/http'
8
9
 
9
10
  require_relative 'pages/index'
11
+ require_relative 'hello_world'
10
12
 
11
13
  module Lively
12
14
  class Application < Protocol::HTTP::Middleware
15
+ def self.[](tag)
16
+ klass = Class.new(self)
17
+
18
+ klass.define_singleton_method(:resolver) do
19
+ Live::Resolver.allow(tag)
20
+ end
21
+
22
+ klass.define_method(:body) do
23
+ tag.new
24
+ end
25
+
26
+ return klass
27
+ end
28
+
29
+ def self.resolver
30
+ Live::Resolver.allow(HelloWorld)
31
+ end
32
+
13
33
  def initialize(delegate, resolver: self.class.resolver)
14
34
  super(delegate)
15
35
 
@@ -24,19 +44,23 @@ module Lively
24
44
  self.class.name
25
45
  end
26
46
 
27
- def body
28
- "Hello World"
47
+ def body(...)
48
+ HelloWorld.new(...)
49
+ end
50
+
51
+ def index(...)
52
+ Pages::Index.new(title: self.title, body: self.body(...))
29
53
  end
30
54
 
31
- def index
32
- Pages::Index.new(title: self.title, body: self.body)
55
+ def handle(request, ...)
56
+ return Protocol::HTTP::Response[200, [], [self.index(...).call]]
33
57
  end
34
58
 
35
59
  def call(request)
36
60
  if request.path == '/live'
37
61
  return Async::WebSocket::Adapters::HTTP.open(request, &self.method(:live)) || Protocol::HTTP::Response[400]
38
62
  else
39
- return Protocol::HTTP::Response[200, [], [self.index.call]]
63
+ return handle(request)
40
64
  end
41
65
  end
42
66
  end
@@ -3,13 +3,18 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
+ require_relative '../application'
7
+ require_relative '../assets'
8
+
9
+ require 'falcon/environment/server'
10
+
6
11
  module Lively
7
12
  module Environment
8
13
  module Application
9
14
  include Falcon::Environment::Server
10
15
 
11
16
  def application
12
- if Object.const_defined?(:Application, false)
17
+ if Object.const_defined?(:Application)
13
18
  ::Application
14
19
  else
15
20
  Console.warn(self, "No Application class defined, using default.")
@@ -0,0 +1,59 @@
1
+ module Lively
2
+ class HelloWorld < Live::View
3
+ def initialize(...)
4
+ super
5
+
6
+ @clock = nil
7
+ end
8
+
9
+ def bind(page)
10
+ super
11
+
12
+ @clock ||= Async do
13
+ while true
14
+ self.update!
15
+
16
+ sleep 1
17
+ end
18
+ end
19
+ end
20
+
21
+ def close
22
+ @clock&.stop
23
+
24
+ super
25
+ end
26
+
27
+ def render(builder)
28
+ builder.tag(:h1) do
29
+ builder.text("Hello, I'm Lively!")
30
+ end
31
+
32
+ builder.tag(:p) do
33
+ builder.text("The time is #{Time.now}.")
34
+ end
35
+
36
+ builder.tag(:p) do
37
+ builder.text(<<~TEXT)
38
+ Lively is a simple client-server SPA framework. It is designed to be easy to use and understand, while providing a solid foundation for building interactive web applications. Create an `application.rb` file and define your own `Application` class to get started.
39
+ TEXT
40
+ end
41
+
42
+ builder.inline_tag(:pre) do
43
+ builder.text(<<~TEXT)
44
+ #!/usr/bin/env lively
45
+
46
+ class Application < Lively::Application
47
+ def body
48
+ Lively::HelloWorld.new
49
+ end
50
+ end
51
+ TEXT
52
+ end
53
+
54
+ builder.tag(:p) do
55
+ builder.text("Check the `examples/` directory for... you guessed it... more examples.")
56
+ end
57
+ end
58
+ end
59
+ end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  module Lively
7
- VERSION = "0.5.0"
7
+ VERSION = "0.6.0"
8
8
  end
@@ -1,6 +1,13 @@
1
1
  import morphdom from 'morphdom';
2
2
 
3
3
  export class Live {
4
+ #window;
5
+ #document;
6
+ #server;
7
+ #events;
8
+ #failures;
9
+ #reconnectTimer;
10
+
4
11
  static start(options = {}) {
5
12
  let window = options.window || globalThis;
6
13
  let path = options.path || 'live'
@@ -13,35 +20,37 @@ export class Live {
13
20
  }
14
21
 
15
22
  constructor(window, url) {
16
- this.window = window;
17
- this.document = window.document;
23
+ this.#window = window;
24
+ this.#document = window.document;
18
25
 
19
26
  this.url = url;
20
- this.events = [];
27
+ this.#server = null;
28
+ this.#events = [];
21
29
 
22
- this.failures = 0;
23
- this.reconnectTimer = null;
30
+ this.#failures = 0;
31
+ this.#reconnectTimer = null;
24
32
 
25
33
  // Track visibility state and connect if required:
26
- this.document.addEventListener("visibilitychange", () => this.handleVisibilityChange());
27
- this.handleVisibilityChange();
34
+ this.#document.addEventListener("visibilitychange", () => this.#handleVisibilityChange());
35
+
36
+ this.#handleVisibilityChange();
28
37
 
29
- const elementNodeType = this.window.Node.ELEMENT_NODE;
38
+ const elementNodeType = this.#window.Node.ELEMENT_NODE;
30
39
 
31
40
  // Create a MutationObserver to watch for removed nodes
32
- this.observer = new this.window.MutationObserver((mutationsList, observer) => {
41
+ this.observer = new this.#window.MutationObserver((mutationsList, observer) => {
33
42
  for (let mutation of mutationsList) {
34
43
  if (mutation.type === 'childList') {
35
44
  for (let node of mutation.removedNodes) {
36
45
  if (node.nodeType !== elementNodeType) continue;
37
46
 
38
47
  if (node.classList?.contains('live')) {
39
- this.unbind(node);
48
+ this.#unbind(node);
40
49
  }
41
50
 
42
51
  // Unbind any child nodes:
43
52
  for (let child of node.getElementsByClassName('live')) {
44
- this.unbind(child);
53
+ this.#unbind(child);
45
54
  }
46
55
  }
47
56
 
@@ -49,65 +58,65 @@ export class Live {
49
58
  if (node.nodeType !== elementNodeType) continue;
50
59
 
51
60
  if (node.classList.contains('live')) {
52
- this.bind(node);
61
+ this.#bind(node);
53
62
  }
54
63
 
55
64
  // Bind any child nodes:
56
65
  for (let child of node.getElementsByClassName('live')) {
57
- this.bind(child);
66
+ this.#bind(child);
58
67
  }
59
68
  }
60
69
  }
61
70
  }
62
71
  });
63
72
 
64
- this.observer.observe(this.document.body, {childList: true, subtree: true});
73
+ this.observer.observe(this.#document.body, {childList: true, subtree: true});
65
74
  }
66
75
 
67
76
  // -- Connection Handling --
68
77
 
69
78
  connect() {
70
- if (this.server) {
71
- return this.server;
79
+ if (this.#server) {
80
+ return this.#server;
72
81
  }
73
82
 
74
- let server = this.server = new this.window.WebSocket(this.url);
83
+ let server = this.#server = new this.#window.WebSocket(this.url);
75
84
 
76
- if (this.reconnectTimer) {
77
- clearTimeout(this.reconnectTimer);
78
- this.reconnectTimer = null;
85
+ if (this.#reconnectTimer) {
86
+ clearTimeout(this.#reconnectTimer);
87
+ this.#reconnectTimer = null;
79
88
  }
80
89
 
81
90
  server.onopen = () => {
82
- this.failures = 0;
83
- this.flush();
84
- this.attach();
91
+ this.#failures = 0;
92
+ this.#flush();
93
+ this.#attach();
85
94
  };
86
95
 
87
96
  server.onmessage = (message) => {
88
- const [name, ..._arguments] = JSON.parse(message.data);
97
+ const [name, ...args] = JSON.parse(message.data);
89
98
 
90
- this[name](..._arguments);
99
+ this[name](...args);
91
100
  };
92
101
 
93
102
  // The remote end has disconnected:
94
103
  server.addEventListener('error', () => {
95
- this.failures += 1;
104
+ this.#failures += 1;
96
105
  });
97
106
 
98
107
  server.addEventListener('close', () => {
99
- // Explicit disconnect will clear `this.server`:
100
- if (this.server && !this.reconnectTimer) {
108
+ // Explicit disconnect will clear `this.#server`:
109
+ if (this.#server && !this.#reconnectTimer) {
101
110
  // We need a minimum delay otherwise this can end up immediately invoking the callback:
102
- const delay = Math.max(100 * (this.failures + 1) ** 2, 60000);
103
- this.reconnectTimer = setTimeout(() => {
104
- this.reconnectTimer = null;
111
+ const delay = Math.max(100 * (this.#failures + 1) ** 2, 60000);
112
+ this.#reconnectTimer = setTimeout(() => {
113
+ this.#reconnectTimer = null;
105
114
  this.connect();
106
115
  }, delay);
107
116
  }
108
117
 
109
- if (this.server === server) {
110
- this.server = null;
118
+ if (this.#server === server) {
119
+ this.#server = null;
111
120
  }
112
121
  });
113
122
 
@@ -115,133 +124,149 @@ export class Live {
115
124
  }
116
125
 
117
126
  disconnect() {
118
- if (this.server) {
119
- const server = this.server;
120
- this.server = null;
127
+ if (this.#server) {
128
+ const server = this.#server;
129
+ this.#server = null;
121
130
  server.close();
122
131
  }
123
132
 
124
- if (this.reconnectTimer) {
125
- clearTimeout(this.reconnectTimer);
126
- this.reconnectTimer = null;
133
+ if (this.#reconnectTimer) {
134
+ clearTimeout(this.#reconnectTimer);
135
+ this.#reconnectTimer = null;
127
136
  }
128
137
  }
129
138
 
130
- send(message) {
131
- if (this.server) {
139
+ #send(message) {
140
+ if (this.#server) {
132
141
  try {
133
- return this.server.send(message);
142
+ return this.#server.send(message);
134
143
  } catch (error) {
135
144
  // console.log("Live.send", "failed to send message to server", error);
136
145
  }
137
146
  }
138
147
 
139
- this.events.push(message);
148
+ this.#events.push(message);
140
149
  }
141
150
 
142
- flush() {
143
- if (this.events.length === 0) return;
151
+ #flush() {
152
+ if (this.#events.length === 0) return;
144
153
 
145
- let events = this.events;
146
- this.events = [];
154
+ let events = this.#events;
155
+ this.#events = [];
147
156
 
148
157
  for (var event of events) {
149
- this.send(event);
158
+ this.#send(event);
150
159
  }
151
160
  }
152
161
 
153
- handleVisibilityChange() {
154
- if (this.document.hidden) {
162
+ #handleVisibilityChange() {
163
+ if (this.#document.hidden) {
155
164
  this.disconnect();
156
165
  } else {
157
166
  this.connect();
158
167
  }
159
168
  }
160
169
 
161
- bind(element) {
170
+ #bind(element) {
162
171
  console.log("bind", element.id, element.dataset);
163
172
 
164
- this.send(JSON.stringify(['bind', element.id, element.dataset]));
173
+ this.#send(JSON.stringify(['bind', element.id, element.dataset]));
165
174
  }
166
175
 
167
- unbind(element) {
176
+ #unbind(element) {
168
177
  console.log("unbind", element.id, element.dataset);
169
178
 
170
- if (this.server) {
171
- this.send(JSON.stringify(['unbind', element.id]));
179
+ if (this.#server) {
180
+ this.#send(JSON.stringify(['unbind', element.id]));
172
181
  }
173
182
  }
174
183
 
175
- attach() {
176
- for (let node of this.document.getElementsByClassName('live')) {
177
- this.bind(node);
184
+ #attach() {
185
+ for (let node of this.#document.getElementsByClassName('live')) {
186
+ this.#bind(node);
178
187
  }
179
188
  }
180
189
 
181
- createDocumentFragment(html) {
182
- return this.document.createRange().createContextualFragment(html);
190
+ #createDocumentFragment(html) {
191
+ return this.#document.createRange().createContextualFragment(html);
183
192
  }
184
193
 
185
- reply(options) {
194
+ #reply(options, ...args) {
186
195
  if (options?.reply) {
187
- this.send(JSON.stringify(['reply', options.reply]));
196
+ this.#send(JSON.stringify(['reply', options.reply, ...args]));
188
197
  }
189
198
  }
190
199
 
191
200
  // -- RPC Methods --
192
201
 
202
+ script(id, code, options) {
203
+ let element = this.#document.getElementById(id);
204
+
205
+ try {
206
+ let result = this.#window.Function(code).call(element);
207
+
208
+ this.#reply(options, result);
209
+ } catch (error) {
210
+ this.#reply(options, null, {name: error.name, message: error.message, stack: error.stack});
211
+ }
212
+ }
213
+
193
214
  update(id, html, options) {
194
- let element = this.document.getElementById(id);
195
- let fragment = this.createDocumentFragment(html);
215
+ let element = this.#document.getElementById(id);
216
+ let fragment = this.#createDocumentFragment(html);
196
217
 
197
218
  morphdom(element, fragment);
198
219
 
199
- this.reply(options);
220
+ this.#reply(options);
200
221
  }
201
222
 
202
223
  replace(selector, html, options) {
203
- let elements = this.document.querySelectorAll(selector);
204
- let fragment = this.createDocumentFragment(html);
224
+ let elements = this.#document.querySelectorAll(selector);
225
+ let fragment = this.#createDocumentFragment(html);
205
226
 
206
227
  elements.forEach(element => morphdom(element, fragment.cloneNode(true)));
207
228
 
208
- this.reply(options);
229
+ this.#reply(options);
209
230
  }
210
231
 
211
232
  prepend(selector, html, options) {
212
- let elements = this.document.querySelectorAll(selector);
213
- let fragment = this.createDocumentFragment(html);
233
+ let elements = this.#document.querySelectorAll(selector);
234
+ let fragment = this.#createDocumentFragment(html);
214
235
 
215
236
  elements.forEach(element => element.prepend(fragment.cloneNode(true)));
216
237
 
217
- this.reply(options);
238
+ this.#reply(options);
218
239
  }
219
240
 
220
241
  append(selector, html, options) {
221
- let elements = this.document.querySelectorAll(selector);
222
- let fragment = this.createDocumentFragment(html);
242
+ let elements = this.#document.querySelectorAll(selector);
243
+ let fragment = this.#createDocumentFragment(html);
223
244
 
224
245
  elements.forEach(element => element.append(fragment.cloneNode(true)));
225
246
 
226
- this.reply(options);
247
+ this.#reply(options);
227
248
  }
228
249
 
229
250
  remove(selector, options) {
230
- let elements = this.document.querySelectorAll(selector);
251
+ let elements = this.#document.querySelectorAll(selector);
231
252
 
232
253
  elements.forEach(element => element.remove());
233
254
 
234
- this.reply(options);
255
+ this.#reply(options);
235
256
  }
236
257
 
237
258
  dispatchEvent(selector, type, options) {
238
- let elements = this.document.querySelectorAll(selector);
259
+ let elements = this.#document.querySelectorAll(selector);
239
260
 
240
261
  elements.forEach(element => element.dispatchEvent(
241
- new this.window.CustomEvent(type, options)
262
+ new this.#window.CustomEvent(type, options)
242
263
  ));
243
264
 
244
- this.reply(options);
265
+ this.#reply(options);
266
+ }
267
+
268
+ error(message) {
269
+ console.error("Live.error", ...arguments);
245
270
  }
246
271
 
247
272
  // -- Event Handling --
@@ -249,19 +274,19 @@ export class Live {
249
274
  forward(id, event) {
250
275
  this.connect();
251
276
 
252
- this.send(
277
+ this.#send(
253
278
  JSON.stringify(['event', id, event])
254
279
  );
255
280
  }
256
281
 
257
- forwardEvent(id, event, detail) {
258
- event.preventDefault();
282
+ forwardEvent(id, event, detail, preventDefault = false) {
283
+ if (preventDefault) event.preventDefault();
259
284
 
260
285
  this.forward(id, {type: event.type, detail: detail});
261
286
  }
262
287
 
263
- forwardFormEvent(id, event, detail) {
264
- event.preventDefault();
288
+ forwardFormEvent(id, event, detail, preventDefault = true) {
289
+ if (preventDefault) event.preventDefault();
265
290
 
266
291
  let form = event.form;
267
292
  let formData = new FormData(form);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketry/live",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.13.0",
5
5
  "description": "Live HTML tags for Ruby.",
6
6
  "main": "Live.js",
7
7
  "repository": {
@@ -85,9 +85,9 @@ describe('Live', function () {
85
85
  const live = Live.start({window: dom.window, base: 'http://localhost/'});
86
86
  ok(live);
87
87
 
88
- strictEqual(live.window, dom.window);
89
- strictEqual(live.document, dom.window.document);
90
88
  strictEqual(live.url.href, 'ws://localhost/live');
89
+
90
+ live.disconnect();
91
91
  });
92
92
 
93
93
  it('should connect to the WebSocket server', function () {
@@ -102,26 +102,46 @@ describe('Live', function () {
102
102
  it('should handle visibility changes', async function () {
103
103
  const live = new Live(dom.window, webSocketServerURL);
104
104
 
105
- let hidden = false;
106
- Object.defineProperty(dom.window.document, "hidden", {
107
- get() {return hidden},
108
- });
105
+ // It's tricky to test the method directly.
106
+ // - Changing document.hidden is a hack.
107
+ // - Sending custom events seems to cause a hang.
108
+
109
+ live.connect();
110
+ deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
109
111
 
110
- // The document starts out hidden... we have defined a property to make it not hidden, let's propagate that change:
111
- live.handleVisibilityChange();
112
+ live.disconnect();
112
113
 
113
- // We should receive a bind message for the live element:
114
+ live.connect()
114
115
  deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
115
116
 
116
- hidden = true;
117
- live.handleVisibilityChange();
118
- ok(!live.server);
117
+ live.disconnect();
118
+ });
119
+
120
+ it('can execute scripts', async function () {
121
+ const live = new Live(dom.window, webSocketServerURL);
119
122
 
120
- hidden = false;
121
- live.handleVisibilityChange();
122
- ok(live.server);
123
+ live.connect();
123
124
 
124
- deepStrictEqual(await messages.pop(), ['bind', 'my', {}]);
125
+ const connected = new Promise(resolve => {
126
+ webSocketServer.on('connection', resolve);
127
+ });
128
+
129
+ let socket = await connected;
130
+
131
+ socket.send(
132
+ JSON.stringify(['script', 'my', 'return 1+2', {reply: true}])
133
+ );
134
+
135
+ let successReply = await messages.popUntil(message => message[0] == 'reply');
136
+ strictEqual(successReply[2], 3);
137
+
138
+ socket.send(
139
+ JSON.stringify(['script', 'my', 'throw new Error("Test Error")', {reply: true}])
140
+ );
141
+
142
+ let errorReply = await messages.popUntil(message => message[0] == 'reply');
143
+ strictEqual(errorReply[2], null);
144
+ console.log(errorReply);
125
145
 
126
146
  live.disconnect();
127
147
  });
@@ -178,7 +198,10 @@ describe('Live', function () {
178
198
 
179
199
  dom.window.document.getElementById('my').remove();
180
200
 
181
- let payload = await messages.popUntil(message => message[0] == 'unbind');
201
+ let payload = await messages.popUntil(message => {
202
+ return message[0] == 'unbind' && message[1] == 'my';
203
+ });
204
+
182
205
  deepStrictEqual(payload, ['unbind', 'my']);
183
206
 
184
207
  live.disconnect();
@@ -325,4 +348,10 @@ describe('Live', function () {
325
348
 
326
349
  live.disconnect();
327
350
  });
351
+
352
+ it ('can log errors', function () {
353
+ const live = new Live(dom.window, webSocketServerURL);
354
+
355
+ live.error('my', 'Test Error');
356
+ });
328
357
  });
Binary file
@@ -1 +1,5 @@
1
- /* Default Stylesheet */
1
+ /*
2
+
3
+ Create your own `public/static/index.css` to override the default stylesheet.
4
+
5
+ */
@@ -1,6 +1,6 @@
1
1
 
2
2
  html {
3
- font-family: Monaco, monospace;
3
+ font-family: Arial, sans-serif;
4
4
  font-size: 16px;
5
5
 
6
6
  /* Fix odd text-size in `display: flex` elements on Safari iOS */
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lively
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '0.8'
62
+ version: '0.9'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '0.8'
69
+ version: '0.9'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: xrb
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -83,14 +83,17 @@ dependencies:
83
83
  version: '0'
84
84
  description:
85
85
  email:
86
- executables: []
86
+ executables:
87
+ - lively
87
88
  extensions: []
88
89
  extra_rdoc_files: []
89
90
  files:
91
+ - bin/lively
90
92
  - lib/lively.rb
91
93
  - lib/lively/application.rb
92
94
  - lib/lively/assets.rb
93
95
  - lib/lively/environment/application.rb
96
+ - lib/lively/hello_world.rb
94
97
  - lib/lively/pages/index.rb
95
98
  - lib/lively/pages/index.xrb
96
99
  - lib/lively/version.rb
@@ -104,6 +107,7 @@ files:
104
107
  - public/_components/morphdom/morphdom-umd.js
105
108
  - public/_components/morphdom/morphdom-umd.min.js
106
109
  - public/_components/morphdom/morphdom.js
110
+ - public/_static/Falcon.png
107
111
  - public/_static/icon.png
108
112
  - public/_static/index.css
109
113
  - public/_static/site.css
metadata.gz.sig CHANGED
Binary file