clapton 0.0.14 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +80 -14
- data/lib/clapton/engine.rb +11 -3
- data/lib/clapton/javascripts/dist/client.js +7 -8
- data/lib/clapton/javascripts/dist/components-for-test.js +127 -12
- data/lib/clapton/javascripts/dist/components.js +110 -1
- data/lib/clapton/javascripts/src/actions/handle-action.ts +6 -6
- data/lib/clapton/javascripts/src/components/bold.spec.ts +21 -0
- data/lib/clapton/javascripts/src/components/bold.ts +20 -0
- data/lib/clapton/javascripts/src/components/component.ts +3 -0
- data/lib/clapton/javascripts/src/components/presets.ts +105 -0
- data/lib/clapton/javascripts/src/components-for-test.ts +3 -2
- data/lib/clapton/javascripts/src/components.ts +3 -2
- data/lib/clapton/javascripts/src/dom/update-component.ts +1 -2
- data/lib/clapton/test_helper/base.rb +11 -3
- data/lib/clapton/version.rb +1 -2
- metadata +5 -3
- data/lib/clapton/javascripts/src/components/component.spec.ts +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f420efd6abefefc796433bad77369a93a3686057519240abf5a9e118758ade2
|
4
|
+
data.tar.gz: f34d535b066e305f893bd291e464d2dfd231df8fbf837b04668fcbaa0a0b4b84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08125a3863fc85cae262381041e72fdacdfc1fe8dd4f677cd4f5f55e658b713b3c8fa6238289c2b0fc1b5e4cc1bbb4e9ead48179eee8af6cde67b26fe5293e30'
|
7
|
+
data.tar.gz: 6e9a70c6be7c02f4642d44ce4f766ea8a72460962e8a0437f71bafbdaad11df2f0cfbe6fd4c10953a63ab0d324c5720286a17edc5dc66a59ca4637c5ef239258
|
data/README.md
CHANGED
@@ -37,10 +37,10 @@ class TaskListComponent < Clapton::Component
|
|
37
37
|
@state.tasks.each do |task|
|
38
38
|
@root.add(TaskItemComponent.new(id: task[:id], title: task[:title], due: task[:due], done: task[:done]))
|
39
39
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@root.add(
|
40
|
+
btn = c.button
|
41
|
+
btn.add(c.text("Add Task"))
|
42
|
+
btn.add_action(:click, :TaskListState, :add_task)
|
43
|
+
@root.add(btn)
|
44
44
|
@root.render
|
45
45
|
end
|
46
46
|
end
|
@@ -51,17 +51,17 @@ end
|
|
51
51
|
# app/components/task_item_component.rb
|
52
52
|
class TaskItemComponent < Clapton::Component
|
53
53
|
def render
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
btn = c.button
|
55
|
+
btn.add(c.text(@state.done ? "✅" : "🟩"))
|
56
|
+
btn.add_action(:click, :TaskListState, :toggle_done)
|
57
57
|
|
58
|
-
|
59
|
-
|
58
|
+
tf = c.input(@state, :title)
|
59
|
+
tf.add_action(:input, :TaskListState, :update_title)
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
dt = c.datetime(@state, :due)
|
62
|
+
dt.add_action(:input, :TaskListState, :update_due)
|
63
63
|
|
64
|
-
@root.add(
|
64
|
+
@root.add(btn).add(tf).add(dt)
|
65
65
|
@root.render
|
66
66
|
end
|
67
67
|
end
|
@@ -177,13 +177,13 @@ class TaskListComponent < Clapton::Component
|
|
177
177
|
end
|
178
178
|
```
|
179
179
|
|
180
|
-
### Preset Components
|
180
|
+
### Preset Components Classes
|
181
181
|
|
182
182
|
```ruby
|
183
183
|
block_quote = Clapton::BlockQuote.new
|
184
184
|
block_quote.add(Clapton::Text.new("Hello"))
|
185
185
|
|
186
|
-
box = Clapton
|
186
|
+
box = **Clapton**::Box.new
|
187
187
|
box.add(Clapton::Text.new("Hello"))
|
188
188
|
|
189
189
|
button = Clapton::Button.new
|
@@ -253,6 +253,72 @@ text_field = Clapton::TextField.new(:ExampleState, :example_attribute, { id: "ex
|
|
253
253
|
text = Clapton::Text.new("Hello")`
|
254
254
|
```
|
255
255
|
|
256
|
+
### Preset Component Methods
|
257
|
+
|
258
|
+
```javascript
|
259
|
+
c.bq(...props)
|
260
|
+
c.box(...props)
|
261
|
+
c.b(...props)
|
262
|
+
c.button(...props)
|
263
|
+
c.check(...props)
|
264
|
+
c.code(...props)
|
265
|
+
c.datetime(...props)
|
266
|
+
c.el(...props)
|
267
|
+
c.embed(...props)
|
268
|
+
c.em(...props)
|
269
|
+
c.form(...props)
|
270
|
+
c.h(...props)
|
271
|
+
c.img(...props)
|
272
|
+
c.a(...props)
|
273
|
+
c.li(...props)
|
274
|
+
c.ul(...props)
|
275
|
+
c.ol(...props)
|
276
|
+
c.p(...props)
|
277
|
+
c.q(...props)
|
278
|
+
c.radio(...props)
|
279
|
+
c.select(...props)
|
280
|
+
c.span(...props)
|
281
|
+
c.textarea(...props)
|
282
|
+
c.input(...props)
|
283
|
+
c.text(...props)
|
284
|
+
```
|
285
|
+
|
286
|
+
### Streaming
|
287
|
+
|
288
|
+
Clapton supports streaming.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
# app/states/chat_state.rb
|
292
|
+
class ChatState < Clapton::State
|
293
|
+
attribute :messages
|
294
|
+
|
295
|
+
def send(params)
|
296
|
+
self.messages << { role: "user", content: params[:content] }
|
297
|
+
yield continue: true # Continue the streaming
|
298
|
+
|
299
|
+
client = OpenAI::Client.new(
|
300
|
+
access_token: ENV.fetch("OPENAI_ACCESS_TOKEN"),
|
301
|
+
log_errors: true
|
302
|
+
)
|
303
|
+
self.messages << { role: "assistant", content: "" }
|
304
|
+
client.chat(
|
305
|
+
parameters: {
|
306
|
+
model: "gpt-4o-mini",
|
307
|
+
messages: messages,
|
308
|
+
stream: proc do |chunk, _bytesize|
|
309
|
+
if chunk.dig("choices", 0, "finish_reason") == "stop"
|
310
|
+
yield continue: false # Stop the streaming
|
311
|
+
end
|
312
|
+
|
313
|
+
self.messages.last[:content] << chunk.dig("choices", 0, "delta", "content")
|
314
|
+
yield continue: true
|
315
|
+
end
|
316
|
+
}
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
256
322
|
### Optional
|
257
323
|
|
258
324
|
#### Action Cable
|
data/lib/clapton/engine.rb
CHANGED
@@ -21,6 +21,10 @@ module Clapton
|
|
21
21
|
FileUtils.mkdir_p(components_path) unless components_path.exist?
|
22
22
|
FileUtils.touch(components_path.join(".keep"))
|
23
23
|
|
24
|
+
FileUtils.mkdir_p(Rails.root.join("public", "clapton")) unless Rails.root.join("public", "clapton").exist?
|
25
|
+
File.write(Rails.root.join("public", "clapton", "components.js"), File.read(File.join(__dir__, "javascripts", "dist", "components.js")))
|
26
|
+
File.write(Rails.root.join("public", "clapton", "client.js"), File.read(File.join(__dir__, "javascripts", "dist", "client.js")))
|
27
|
+
|
24
28
|
compile_components
|
25
29
|
|
26
30
|
listener = Listen.to(Rails.root.join("app", "components")) do |modified, added, removed|
|
@@ -32,9 +36,9 @@ module Clapton
|
|
32
36
|
end
|
33
37
|
|
34
38
|
def compile_components
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
puts "Clapton: Compiling components"
|
40
|
+
|
41
|
+
start_time = Time.now
|
38
42
|
Dir.glob(Rails.root.join("app", "components", "**", "*.rb")).each do |file|
|
39
43
|
code = File.read(file)
|
40
44
|
js = ""
|
@@ -44,12 +48,16 @@ module Clapton
|
|
44
48
|
js += "import { #{match[0]}Component } from '#{match[0]}Component';"
|
45
49
|
js += "\n"
|
46
50
|
end
|
51
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\(/, '\1@c.\2(')
|
52
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\./, '\1@c.\2().')
|
47
53
|
js += Ruby2JS.convert(code, preset: true)
|
48
54
|
js += "\n"
|
49
55
|
js += "export { #{File.basename(file, ".rb").camelize} };"
|
50
56
|
js += "\n"
|
51
57
|
File.write(Rails.root.join("public", "clapton", "#{File.basename(file, ".rb").camelize}.js"), js)
|
52
58
|
end
|
59
|
+
end_time = Time.now
|
60
|
+
puts "Clapton: Component compilation took #{end_time - start_time} seconds"
|
53
61
|
end
|
54
62
|
end
|
55
63
|
end
|
@@ -1287,8 +1287,7 @@ function getConfig(name) {
|
|
1287
1287
|
|
1288
1288
|
const updateComponent = async (component, state, property, target) => {
|
1289
1289
|
state[property] = target.value;
|
1290
|
-
component.
|
1291
|
-
const componentName = component.getAttribute("data-component");
|
1290
|
+
const componentName = component.dataset.component;
|
1292
1291
|
const module = await import(`${componentName}`);
|
1293
1292
|
const ComponentClass = module[componentName];
|
1294
1293
|
const instance = new ComponentClass(state, component.dataset.id);
|
@@ -1348,26 +1347,26 @@ const handleAction = async (target, stateName, fn) => {
|
|
1348
1347
|
if (!targetComponent)
|
1349
1348
|
return;
|
1350
1349
|
const component = target.closest(`[data-component]`);
|
1351
|
-
const attribute = target.
|
1350
|
+
const attribute = target.dataset.attribute;
|
1352
1351
|
if (attribute) {
|
1353
|
-
const state = JSON.parse(component.
|
1352
|
+
const state = JSON.parse(component.dataset.state || "{}");
|
1354
1353
|
if (target.tagName === "INPUT") {
|
1355
1354
|
state[attribute] = target.value;
|
1356
|
-
component.
|
1355
|
+
component.dataset.state = JSON.stringify(state);
|
1357
1356
|
}
|
1358
1357
|
}
|
1359
1358
|
claptonChannel.perform("action", {
|
1360
1359
|
data: {
|
1361
1360
|
component: {
|
1362
1361
|
name: stateName.replace("State", "Component"),
|
1363
|
-
id: targetComponent.
|
1362
|
+
id: targetComponent.dataset.id,
|
1364
1363
|
},
|
1365
1364
|
state: {
|
1366
1365
|
name: stateName,
|
1367
1366
|
action: fn,
|
1368
|
-
attributes: JSON.parse(targetComponent.
|
1367
|
+
attributes: JSON.parse(targetComponent.dataset.state || "{}"),
|
1369
1368
|
},
|
1370
|
-
params: JSON.parse(component.
|
1369
|
+
params: JSON.parse(component.dataset.state || "{}")
|
1371
1370
|
}
|
1372
1371
|
});
|
1373
1372
|
};
|
@@ -75,6 +75,20 @@ var Clapton = (function (exports) {
|
|
75
75
|
}
|
76
76
|
}
|
77
77
|
|
78
|
+
class Bold {
|
79
|
+
constructor(attributes = {}) {
|
80
|
+
this.children = [];
|
81
|
+
this.attributes = attributes;
|
82
|
+
}
|
83
|
+
get render() {
|
84
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
85
|
+
}
|
86
|
+
add(child) {
|
87
|
+
this.children.push(child);
|
88
|
+
return this;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
78
92
|
class Button {
|
79
93
|
constructor(attributes = {}) {
|
80
94
|
this.attributes = attributes;
|
@@ -372,18 +386,6 @@ var Clapton = (function (exports) {
|
|
372
386
|
}
|
373
387
|
}
|
374
388
|
|
375
|
-
class Component {
|
376
|
-
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
377
|
-
this._state = state;
|
378
|
-
this.id = id;
|
379
|
-
this._errors = errors;
|
380
|
-
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
381
|
-
}
|
382
|
-
get render() {
|
383
|
-
return this._root.render;
|
384
|
-
}
|
385
|
-
}
|
386
|
-
|
387
389
|
class TextField {
|
388
390
|
constructor(state, attribute, attributes = {}) {
|
389
391
|
this.state = state;
|
@@ -409,7 +411,119 @@ var Clapton = (function (exports) {
|
|
409
411
|
}
|
410
412
|
}
|
411
413
|
|
414
|
+
class TextArea {
|
415
|
+
constructor(state, attribute, attributes = {}) {
|
416
|
+
this.state = state;
|
417
|
+
this.attributes = attributes;
|
418
|
+
this.attribute = attribute;
|
419
|
+
this.attributes["data-attribute"] = attribute;
|
420
|
+
}
|
421
|
+
get render() {
|
422
|
+
return `<textarea ${htmlAttributes(this.attributes)}>${this.state[this.attribute] || ""}</textarea>`;
|
423
|
+
}
|
424
|
+
add_action(event, klass, fn, options = {}) {
|
425
|
+
this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
|
426
|
+
return this;
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
const Clapton = {
|
431
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
432
|
+
};
|
433
|
+
|
434
|
+
class Presets {
|
435
|
+
bq(...props) {
|
436
|
+
return new Clapton.BlockQuote(...props);
|
437
|
+
}
|
438
|
+
box(...props) {
|
439
|
+
return new Clapton.Box(...props);
|
440
|
+
}
|
441
|
+
b(...props) {
|
442
|
+
return new Clapton.Bold(...props);
|
443
|
+
}
|
444
|
+
button(...props) {
|
445
|
+
return new Clapton.Button(...props);
|
446
|
+
}
|
447
|
+
check(...props) {
|
448
|
+
return new Clapton.Checkbox(props[0], props[1], props[2]);
|
449
|
+
}
|
450
|
+
code(...props) {
|
451
|
+
return new Clapton.Code(...props);
|
452
|
+
}
|
453
|
+
datetime(...props) {
|
454
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2]);
|
455
|
+
}
|
456
|
+
el(...props) {
|
457
|
+
return new Clapton.Element(props[0], props[1]);
|
458
|
+
}
|
459
|
+
embed(...props) {
|
460
|
+
return new Clapton.Embed(props[0]);
|
461
|
+
}
|
462
|
+
em(...props) {
|
463
|
+
return new Clapton.Emphasis(...props);
|
464
|
+
}
|
465
|
+
form(...props) {
|
466
|
+
return new Clapton.Form(...props);
|
467
|
+
}
|
468
|
+
h(...props) {
|
469
|
+
return new Clapton.Heading(props[0], props[1]);
|
470
|
+
}
|
471
|
+
img(...props) {
|
472
|
+
return new Clapton.Image(props[0], props[1]);
|
473
|
+
}
|
474
|
+
a(...props) {
|
475
|
+
return new Clapton.Link(props[0], props[1]);
|
476
|
+
}
|
477
|
+
li(...props) {
|
478
|
+
return new Clapton.ListItem(...props);
|
479
|
+
}
|
480
|
+
ul(...props) {
|
481
|
+
return new Clapton.List(...props);
|
482
|
+
}
|
483
|
+
ol(...props) {
|
484
|
+
return new Clapton.OrderedList(...props);
|
485
|
+
}
|
486
|
+
p(...props) {
|
487
|
+
return new Clapton.Paragraph(...props);
|
488
|
+
}
|
489
|
+
q(...props) {
|
490
|
+
return new Clapton.Quote(...props);
|
491
|
+
}
|
492
|
+
radio(...props) {
|
493
|
+
return new Clapton.RadioButton(props[0], props[1], props[2]);
|
494
|
+
}
|
495
|
+
select(...props) {
|
496
|
+
return new Clapton.Select(props[0], props[1], props[2]);
|
497
|
+
}
|
498
|
+
span(...props) {
|
499
|
+
return new Clapton.Span(...props);
|
500
|
+
}
|
501
|
+
textarea(...props) {
|
502
|
+
return new Clapton.TextArea(props[0], props[1], props[2]);
|
503
|
+
}
|
504
|
+
input(...props) {
|
505
|
+
return new Clapton.TextField(props[0], props[1], props[2]);
|
506
|
+
}
|
507
|
+
text(...props) {
|
508
|
+
return new Clapton.Text(props[0]);
|
509
|
+
}
|
510
|
+
}
|
511
|
+
|
512
|
+
class Component {
|
513
|
+
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
514
|
+
this._state = state;
|
515
|
+
this.id = id;
|
516
|
+
this._errors = errors;
|
517
|
+
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
518
|
+
this._c = new Presets();
|
519
|
+
}
|
520
|
+
get render() {
|
521
|
+
return this._root.render;
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
412
525
|
exports.BlockQuote = BlockQuote;
|
526
|
+
exports.Bold = Bold;
|
413
527
|
exports.Box = Box;
|
414
528
|
exports.Button = Button;
|
415
529
|
exports.Checkbox = Checkbox;
|
@@ -432,6 +546,7 @@ var Clapton = (function (exports) {
|
|
432
546
|
exports.Select = Select;
|
433
547
|
exports.Span = Span;
|
434
548
|
exports.Text = Text;
|
549
|
+
exports.TextArea = TextArea;
|
435
550
|
exports.TextField = TextField;
|
436
551
|
|
437
552
|
return exports;
|
@@ -90,6 +90,20 @@ class Button {
|
|
90
90
|
}
|
91
91
|
}
|
92
92
|
|
93
|
+
class Bold {
|
94
|
+
constructor(attributes = {}) {
|
95
|
+
this.children = [];
|
96
|
+
this.attributes = attributes;
|
97
|
+
}
|
98
|
+
get render() {
|
99
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
100
|
+
}
|
101
|
+
add(child) {
|
102
|
+
this.children.push(child);
|
103
|
+
return this;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
93
107
|
class Checkbox {
|
94
108
|
constructor(state, attribute, attributes = {}) {
|
95
109
|
this.state = state;
|
@@ -369,12 +383,91 @@ class Span {
|
|
369
383
|
}
|
370
384
|
}
|
371
385
|
|
386
|
+
class Presets {
|
387
|
+
bq(...props) {
|
388
|
+
return new Clapton.BlockQuote(...props);
|
389
|
+
}
|
390
|
+
box(...props) {
|
391
|
+
return new Clapton.Box(...props);
|
392
|
+
}
|
393
|
+
b(...props) {
|
394
|
+
return new Clapton.Bold(...props);
|
395
|
+
}
|
396
|
+
button(...props) {
|
397
|
+
return new Clapton.Button(...props);
|
398
|
+
}
|
399
|
+
check(...props) {
|
400
|
+
return new Clapton.Checkbox(props[0], props[1], props[2]);
|
401
|
+
}
|
402
|
+
code(...props) {
|
403
|
+
return new Clapton.Code(...props);
|
404
|
+
}
|
405
|
+
datetime(...props) {
|
406
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2]);
|
407
|
+
}
|
408
|
+
el(...props) {
|
409
|
+
return new Clapton.Element(props[0], props[1]);
|
410
|
+
}
|
411
|
+
embed(...props) {
|
412
|
+
return new Clapton.Embed(props[0]);
|
413
|
+
}
|
414
|
+
em(...props) {
|
415
|
+
return new Clapton.Emphasis(...props);
|
416
|
+
}
|
417
|
+
form(...props) {
|
418
|
+
return new Clapton.Form(...props);
|
419
|
+
}
|
420
|
+
h(...props) {
|
421
|
+
return new Clapton.Heading(props[0], props[1]);
|
422
|
+
}
|
423
|
+
img(...props) {
|
424
|
+
return new Clapton.Image(props[0], props[1]);
|
425
|
+
}
|
426
|
+
a(...props) {
|
427
|
+
return new Clapton.Link(props[0], props[1]);
|
428
|
+
}
|
429
|
+
li(...props) {
|
430
|
+
return new Clapton.ListItem(...props);
|
431
|
+
}
|
432
|
+
ul(...props) {
|
433
|
+
return new Clapton.List(...props);
|
434
|
+
}
|
435
|
+
ol(...props) {
|
436
|
+
return new Clapton.OrderedList(...props);
|
437
|
+
}
|
438
|
+
p(...props) {
|
439
|
+
return new Clapton.Paragraph(...props);
|
440
|
+
}
|
441
|
+
q(...props) {
|
442
|
+
return new Clapton.Quote(...props);
|
443
|
+
}
|
444
|
+
radio(...props) {
|
445
|
+
return new Clapton.RadioButton(props[0], props[1], props[2]);
|
446
|
+
}
|
447
|
+
select(...props) {
|
448
|
+
return new Clapton.Select(props[0], props[1], props[2]);
|
449
|
+
}
|
450
|
+
span(...props) {
|
451
|
+
return new Clapton.Span(...props);
|
452
|
+
}
|
453
|
+
textarea(...props) {
|
454
|
+
return new Clapton.TextArea(props[0], props[1], props[2]);
|
455
|
+
}
|
456
|
+
input(...props) {
|
457
|
+
return new Clapton.TextField(props[0], props[1], props[2]);
|
458
|
+
}
|
459
|
+
text(...props) {
|
460
|
+
return new Clapton.Text(props[0]);
|
461
|
+
}
|
462
|
+
}
|
463
|
+
|
372
464
|
class Component {
|
373
465
|
constructor(state = {}, id = Math.random().toString(36).substring(2, 10), errors = []) {
|
374
466
|
this._state = state;
|
375
467
|
this.id = id;
|
376
468
|
this._errors = errors;
|
377
469
|
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
470
|
+
this._c = new Presets();
|
378
471
|
}
|
379
472
|
get render() {
|
380
473
|
return this._root.render;
|
@@ -406,8 +499,24 @@ class Text {
|
|
406
499
|
}
|
407
500
|
}
|
408
501
|
|
502
|
+
class TextArea {
|
503
|
+
constructor(state, attribute, attributes = {}) {
|
504
|
+
this.state = state;
|
505
|
+
this.attributes = attributes;
|
506
|
+
this.attribute = attribute;
|
507
|
+
this.attributes["data-attribute"] = attribute;
|
508
|
+
}
|
509
|
+
get render() {
|
510
|
+
return `<textarea ${htmlAttributes(this.attributes)}>${this.state[this.attribute] || ""}</textarea>`;
|
511
|
+
}
|
512
|
+
add_action(event, klass, fn, options = {}) {
|
513
|
+
this.attributes["data-action"] = `${this.attributes["data-action"] || ""} ${event}->${klass}#${fn}@${options.debounce || 0}`;
|
514
|
+
return this;
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
409
518
|
const Clapton = {
|
410
|
-
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed
|
519
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
411
520
|
};
|
412
521
|
|
413
522
|
export { Clapton };
|
@@ -9,12 +9,12 @@ export const handleAction = async (target: HTMLElement, stateName: string, fn: s
|
|
9
9
|
}
|
10
10
|
if (!targetComponent) return;
|
11
11
|
const component = target.closest(`[data-component]`) as HTMLElement;
|
12
|
-
const attribute = target.
|
12
|
+
const attribute = target.dataset.attribute;
|
13
13
|
if (attribute) {
|
14
|
-
const state = JSON.parse(component.
|
14
|
+
const state = JSON.parse(component.dataset.state || "{}");
|
15
15
|
if (target.tagName === "INPUT") {
|
16
16
|
state[attribute] = (target as HTMLInputElement).value;
|
17
|
-
component.
|
17
|
+
component.dataset.state = JSON.stringify(state);
|
18
18
|
}
|
19
19
|
};
|
20
20
|
claptonChannel.perform(
|
@@ -23,14 +23,14 @@ export const handleAction = async (target: HTMLElement, stateName: string, fn: s
|
|
23
23
|
data: {
|
24
24
|
component: {
|
25
25
|
name: stateName.replace("State", "Component"),
|
26
|
-
id: targetComponent.
|
26
|
+
id: targetComponent.dataset.id,
|
27
27
|
},
|
28
28
|
state: {
|
29
29
|
name: stateName,
|
30
30
|
action: fn,
|
31
|
-
attributes: JSON.parse(targetComponent.
|
31
|
+
attributes: JSON.parse(targetComponent.dataset.state || "{}"),
|
32
32
|
},
|
33
|
-
params: JSON.parse(component.
|
33
|
+
params: JSON.parse(component.dataset.state || "{}")
|
34
34
|
}
|
35
35
|
}
|
36
36
|
);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
2
|
+
import { Text } from "./text"
|
3
|
+
import { Bold } from "./bold"
|
4
|
+
|
5
|
+
describe("Bold", () => {
|
6
|
+
it("returns empty string if no params", () => {
|
7
|
+
expect(new Bold().render).toBe("<strong ></strong>")
|
8
|
+
})
|
9
|
+
|
10
|
+
it("returns attributes and data attributes", () => {
|
11
|
+
expect(new Bold({ id: "1", "data-foo": "bar" }).render).toBe(`<strong id='1' data-foo='bar'></strong>`)
|
12
|
+
})
|
13
|
+
|
14
|
+
|
15
|
+
it("adds children", () => {
|
16
|
+
const text = new Text("Hello")
|
17
|
+
const bold = new Bold()
|
18
|
+
bold.add(text)
|
19
|
+
expect(bold.render).toBe(`<strong >Hello</strong>`)
|
20
|
+
})
|
21
|
+
})
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { htmlAttributes } from "../html/html-attributes";
|
2
|
+
|
3
|
+
export class Bold {
|
4
|
+
attributes: Record<string, any>;
|
5
|
+
children: any[];
|
6
|
+
|
7
|
+
constructor(attributes: Record<string, any> = {}) {
|
8
|
+
this.children = [];
|
9
|
+
this.attributes = attributes;
|
10
|
+
}
|
11
|
+
|
12
|
+
get render(): string {
|
13
|
+
return `<strong ${htmlAttributes(this.attributes)}>${this.children.map(child => child.render).join("")}</strong>`;
|
14
|
+
}
|
15
|
+
|
16
|
+
add(child: any): Bold {
|
17
|
+
this.children.push(child);
|
18
|
+
return this;
|
19
|
+
}
|
20
|
+
}
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import { Box } from "./box";
|
2
|
+
import { Presets } from "./presets";
|
2
3
|
|
3
4
|
export class Component {
|
4
5
|
id: string;
|
5
6
|
_state: any;
|
6
7
|
_errors: any[];
|
7
8
|
_root: Box;
|
9
|
+
_c: Presets;
|
8
10
|
|
9
11
|
constructor(state: any = {}, id: string = Math.random().toString(36).substring(2, 10), errors: any[] = []) {
|
10
12
|
this._state = state;
|
11
13
|
this.id = id;
|
12
14
|
this._errors = errors;
|
13
15
|
this._root = new Box({ data: { component: this.constructor.name, state: JSON.stringify(this._state), id: this.id, errors: this._errors } });
|
16
|
+
this._c = new Presets()
|
14
17
|
}
|
15
18
|
|
16
19
|
get render(): string {
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import { Clapton } from "components";
|
2
|
+
|
3
|
+
export class Presets {
|
4
|
+
bq(...props: any[]) {
|
5
|
+
return new Clapton.BlockQuote(...props)
|
6
|
+
}
|
7
|
+
|
8
|
+
box(...props: any[]) {
|
9
|
+
return new Clapton.Box(...props)
|
10
|
+
}
|
11
|
+
|
12
|
+
b(...props: any[]) {
|
13
|
+
return new Clapton.Bold(...props)
|
14
|
+
}
|
15
|
+
|
16
|
+
button(...props: any[]) {
|
17
|
+
return new Clapton.Button(...props)
|
18
|
+
}
|
19
|
+
|
20
|
+
check(...props: any[]) {
|
21
|
+
return new Clapton.Checkbox(props[0], props[1], props[2])
|
22
|
+
}
|
23
|
+
|
24
|
+
code(...props: any[]) {
|
25
|
+
return new Clapton.Code(...props)
|
26
|
+
}
|
27
|
+
|
28
|
+
datetime(...props: any[]) {
|
29
|
+
return new Clapton.DateTimeField(props[0], props[1], props[2])
|
30
|
+
}
|
31
|
+
|
32
|
+
el(...props: any[]) {
|
33
|
+
return new Clapton.Element(props[0], props[1])
|
34
|
+
}
|
35
|
+
|
36
|
+
embed(...props: any[]) {
|
37
|
+
return new Clapton.Embed(props[0])
|
38
|
+
}
|
39
|
+
|
40
|
+
em(...props: any[]) {
|
41
|
+
return new Clapton.Emphasis(...props)
|
42
|
+
}
|
43
|
+
|
44
|
+
form(...props: any[]) {
|
45
|
+
return new Clapton.Form(...props)
|
46
|
+
}
|
47
|
+
|
48
|
+
h(...props: any[]) {
|
49
|
+
return new Clapton.Heading(props[0], props[1])
|
50
|
+
}
|
51
|
+
|
52
|
+
img(...props: any[]) {
|
53
|
+
return new Clapton.Image(props[0], props[1])
|
54
|
+
}
|
55
|
+
|
56
|
+
a(...props: any[]) {
|
57
|
+
return new Clapton.Link(props[0], props[1])
|
58
|
+
}
|
59
|
+
|
60
|
+
li(...props: any[]) {
|
61
|
+
return new Clapton.ListItem(...props)
|
62
|
+
}
|
63
|
+
|
64
|
+
ul(...props: any[]) {
|
65
|
+
return new Clapton.List(...props)
|
66
|
+
}
|
67
|
+
|
68
|
+
ol(...props: any[]) {
|
69
|
+
return new Clapton.OrderedList(...props)
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
p(...props: any[]) {
|
74
|
+
return new Clapton.Paragraph(...props)
|
75
|
+
}
|
76
|
+
|
77
|
+
q(...props: any[]) {
|
78
|
+
return new Clapton.Quote(...props)
|
79
|
+
}
|
80
|
+
|
81
|
+
radio(...props: any[]) {
|
82
|
+
return new Clapton.RadioButton(props[0], props[1], props[2])
|
83
|
+
}
|
84
|
+
|
85
|
+
select(...props: any[]) {
|
86
|
+
return new Clapton.Select(props[0], props[1], props[2])
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
span(...props: any[]) {
|
91
|
+
return new Clapton.Span(...props)
|
92
|
+
}
|
93
|
+
|
94
|
+
textarea(...props: any[]) {
|
95
|
+
return new Clapton.TextArea(props[0], props[1], props[2])
|
96
|
+
}
|
97
|
+
|
98
|
+
input(...props: any[]) {
|
99
|
+
return new Clapton.TextField(props[0], props[1], props[2])
|
100
|
+
}
|
101
|
+
|
102
|
+
text(...props: any[]) {
|
103
|
+
return new Clapton.Text(props[0])
|
104
|
+
}
|
105
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { BlockQuote } from "./components/block-quote"
|
2
2
|
import { Box } from "./components/box"
|
3
|
+
import { Bold } from "./components/bold"
|
3
4
|
import { Button } from "./components/button"
|
4
5
|
import { Checkbox } from "./components/checkbox"
|
5
6
|
import { Code } from "./components/code"
|
@@ -22,8 +23,8 @@ import { Span } from "./components/span"
|
|
22
23
|
import { Component } from "./components/component"
|
23
24
|
import { TextField } from "./components/text-field"
|
24
25
|
import { Text } from "./components/text"
|
25
|
-
|
26
|
+
import { TextArea } from "./components/text-area"
|
26
27
|
|
27
28
|
export {
|
28
|
-
|
29
|
+
Component, Box, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
29
30
|
};
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { BlockQuote } from "./components/block-quote"
|
2
2
|
import { Box } from "./components/box"
|
3
3
|
import { Button } from "./components/button"
|
4
|
+
import { Bold } from "./components/bold"
|
4
5
|
import { Checkbox } from "./components/checkbox"
|
5
6
|
import { Code } from "./components/code"
|
6
7
|
import { DateTimeField } from "./components/datetime-field"
|
@@ -22,8 +23,8 @@ import { Span } from "./components/span"
|
|
22
23
|
import { Component } from "./components/component"
|
23
24
|
import { TextField } from "./components/text-field"
|
24
25
|
import { Text } from "./components/text"
|
25
|
-
|
26
|
+
import { TextArea } from "./components/text-area"
|
26
27
|
|
27
28
|
export const Clapton = {
|
28
|
-
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed
|
29
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
29
30
|
};
|
@@ -2,8 +2,7 @@ import morphdom from "morphdom";
|
|
2
2
|
|
3
3
|
export const updateComponent = async (component: HTMLElement, state: any, property: string, target: HTMLInputElement) => {
|
4
4
|
state[property] = target.value;
|
5
|
-
component.
|
6
|
-
const componentName = component.getAttribute("data-component") as string;
|
5
|
+
const componentName = component.dataset.component as string;
|
7
6
|
const module = await import(`${componentName}`);
|
8
7
|
const ComponentClass = module[componentName] as any;
|
9
8
|
const instance = new ComponentClass(state, component.dataset.id);
|
@@ -3,14 +3,22 @@ module Clapton
|
|
3
3
|
module Base
|
4
4
|
require "execjs"
|
5
5
|
|
6
|
-
def render_component(component,
|
6
|
+
def render_component(component, params)
|
7
7
|
js = File.read(File.join(__dir__, "..", "javascripts", "dist", "components-for-test.js"))
|
8
8
|
Dir.glob(Rails.root.join("app", "components", "**", "*.rb")).each do |file|
|
9
|
-
|
9
|
+
code = File.read(file)
|
10
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\(/, '\1@c.\2(')
|
11
|
+
code = code.gsub(/([^a-zA-Z0-9])c\.(\w+?)\./, '\1@c.\2().')
|
12
|
+
js += Ruby2JS.convert(code, preset: true)
|
10
13
|
js += "\n"
|
11
14
|
end
|
15
|
+
js = js.sub("const Clapton = {
|
16
|
+
Box, Component, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
17
|
+
};", "const Clapton = {
|
18
|
+
Box, Text, TextField, Button, DateTimeField, BlockQuote, Checkbox, Code, Element, Emphasis, Form, Heading, Image, Link, List, ListItem, OrderedList, Paragraph, Quote, RadioButton, Select, Span, Embed, Bold, TextArea
|
19
|
+
};")
|
12
20
|
context = ExecJS.compile(js)
|
13
|
-
html = context.eval("new #{component.name.camelize}(#{
|
21
|
+
html = context.eval("new #{component.name.camelize}(#{params.to_json}).render")
|
14
22
|
@page = Capybara.string(html)
|
15
23
|
end
|
16
24
|
|
data/lib/clapton/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clapton
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moeki Kawakami
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -6708,6 +6708,8 @@ files:
|
|
6708
6708
|
- lib/clapton/javascripts/src/components.ts
|
6709
6709
|
- lib/clapton/javascripts/src/components/block-quote.spec.ts
|
6710
6710
|
- lib/clapton/javascripts/src/components/block-quote.ts
|
6711
|
+
- lib/clapton/javascripts/src/components/bold.spec.ts
|
6712
|
+
- lib/clapton/javascripts/src/components/bold.ts
|
6711
6713
|
- lib/clapton/javascripts/src/components/box.spec.ts
|
6712
6714
|
- lib/clapton/javascripts/src/components/box.ts
|
6713
6715
|
- lib/clapton/javascripts/src/components/button.spec.ts
|
@@ -6716,7 +6718,6 @@ files:
|
|
6716
6718
|
- lib/clapton/javascripts/src/components/checkbox.ts
|
6717
6719
|
- lib/clapton/javascripts/src/components/code.spec.ts
|
6718
6720
|
- lib/clapton/javascripts/src/components/code.ts
|
6719
|
-
- lib/clapton/javascripts/src/components/component.spec.ts
|
6720
6721
|
- lib/clapton/javascripts/src/components/component.ts
|
6721
6722
|
- lib/clapton/javascripts/src/components/datetime-field.spec.ts
|
6722
6723
|
- lib/clapton/javascripts/src/components/datetime-field.ts
|
@@ -6742,6 +6743,7 @@ files:
|
|
6742
6743
|
- lib/clapton/javascripts/src/components/ordered-list.ts
|
6743
6744
|
- lib/clapton/javascripts/src/components/paragraph.spec.ts
|
6744
6745
|
- lib/clapton/javascripts/src/components/paragraph.ts
|
6746
|
+
- lib/clapton/javascripts/src/components/presets.ts
|
6745
6747
|
- lib/clapton/javascripts/src/components/quote.spec.ts
|
6746
6748
|
- lib/clapton/javascripts/src/components/quote.ts
|
6747
6749
|
- lib/clapton/javascripts/src/components/radio-button.spec.ts
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
2
|
-
import { Component } from "./component"
|
3
|
-
import { Text } from "./text"
|
4
|
-
|
5
|
-
class TestComponent extends Component {
|
6
|
-
get render() {
|
7
|
-
this._root.add(new Text("Hello, world!"))
|
8
|
-
return this._root.render
|
9
|
-
}
|
10
|
-
}
|
11
|
-
|
12
|
-
describe("Component", () => {
|
13
|
-
it("returns empty string if no params", () => {
|
14
|
-
expect(new TestComponent().render).toMatch(/<div data-component='TestComponent' data-state='{}' data-id='.{8}'>Hello, world!<\/div>/)
|
15
|
-
})
|
16
|
-
})
|