lissio 0.1.0.beta1
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 +7 -0
- data/.gitignore +5 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +53 -0
- data/README.md +184 -0
- data/Rakefile +5 -0
- data/lib/lissio.rb +5 -0
- data/lib/lissio/server.rb +148 -0
- data/lissio.gemspec +27 -0
- data/opal/lissio.rb +26 -0
- data/opal/lissio/adapter.rb +45 -0
- data/opal/lissio/adapter/rest.rb +268 -0
- data/opal/lissio/adapter/storage.rb +167 -0
- data/opal/lissio/application.rb +66 -0
- data/opal/lissio/collection.rb +70 -0
- data/opal/lissio/component.rb +177 -0
- data/opal/lissio/component/alert.rb +110 -0
- data/opal/lissio/component/container.rb +56 -0
- data/opal/lissio/component/markdown.rb +332 -0
- data/opal/lissio/component/tooltip.rb +373 -0
- data/opal/lissio/model.rb +204 -0
- data/opal/lissio/router.rb +164 -0
- data/opal/lissio/version.rb +3 -0
- data/spec/route_spec.rb +65 -0
- data/spec/router_spec.rb +109 -0
- data/spec/spec_helper.rb +33 -0
- metadata +154 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Lissio
|
5
|
+
|
6
|
+
class Application < Component
|
7
|
+
def self.inherited(klass)
|
8
|
+
super
|
9
|
+
|
10
|
+
klass.include Singleton
|
11
|
+
|
12
|
+
$document.on :load do
|
13
|
+
klass.start
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.expose(what, options = {})
|
18
|
+
if what.start_with?(?@)
|
19
|
+
name = what[1 .. -1]
|
20
|
+
|
21
|
+
define_singleton_method name do
|
22
|
+
instance.__send__ name
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader name
|
26
|
+
else
|
27
|
+
define_singleton_method what do |*args, &block|
|
28
|
+
instance.__send__ what, *args, &block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
expose :start
|
34
|
+
expose :refresh
|
35
|
+
expose :navigate
|
36
|
+
expose :@router
|
37
|
+
|
38
|
+
extend Forwardable
|
39
|
+
def_delegators :@router, :navigate, :route
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@router = Lissio::Router.new(fragment: false)
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
render
|
47
|
+
|
48
|
+
@router.update
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh
|
52
|
+
@router.update
|
53
|
+
end
|
54
|
+
|
55
|
+
element :body
|
56
|
+
|
57
|
+
on :click, 'a[href^="/"]' do |e|
|
58
|
+
unless e.alt? || e.ctrl? || e.meta? || e.shift?
|
59
|
+
e.stop!
|
60
|
+
|
61
|
+
navigate e.target[:href] || e.target.ancestors('a').first[:href]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
require 'forwardable'
|
12
|
+
|
13
|
+
module Lissio
|
14
|
+
|
15
|
+
class Collection
|
16
|
+
def self.adapter(klass = nil, *args, &block)
|
17
|
+
if klass
|
18
|
+
@adapter.uninstall if @adapter
|
19
|
+
|
20
|
+
@adapter = klass.new(self, *args, &block)
|
21
|
+
@adapter.install
|
22
|
+
else
|
23
|
+
@adapter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.model(klass = nil)
|
28
|
+
klass ? @model = klass : @model
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.parse(&block)
|
32
|
+
block ? @parse = block : @parse
|
33
|
+
end
|
34
|
+
|
35
|
+
extend Forwardable
|
36
|
+
def_delegators :class, :adapter, :model
|
37
|
+
def_delegators :@items, :empty?, :length, :[], :to_a
|
38
|
+
|
39
|
+
def initialize(data = nil, *fetched_with)
|
40
|
+
@fetched_with = fetched_with
|
41
|
+
|
42
|
+
if data
|
43
|
+
@items = data.map {|datum|
|
44
|
+
next datum if Model === datum
|
45
|
+
|
46
|
+
if block = self.class.parse
|
47
|
+
block.call(datum)
|
48
|
+
else
|
49
|
+
model.new(datum)
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
include Enumerable
|
56
|
+
|
57
|
+
def each(&block)
|
58
|
+
return enum_for :each unless block
|
59
|
+
|
60
|
+
@items.each(&block)
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def inspect
|
66
|
+
"#<#{self.class.name}: #{@items.inspect}>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#--
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
3
|
+
# Version 2, December 2004
|
4
|
+
#
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
7
|
+
#
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
9
|
+
#++
|
10
|
+
|
11
|
+
module Lissio
|
12
|
+
|
13
|
+
class Component
|
14
|
+
def self.inherited(klass)
|
15
|
+
return if self == Component
|
16
|
+
|
17
|
+
element = @element
|
18
|
+
tag = @tag
|
19
|
+
events = @events
|
20
|
+
|
21
|
+
klass.instance_eval {
|
22
|
+
@element = element if element
|
23
|
+
@tag = tag if tag
|
24
|
+
@events = events.clone if events
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.element(name = nil)
|
29
|
+
name ? @element = name : @element
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.tag(options = nil)
|
33
|
+
options ? @tag = options : @tag
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.events
|
37
|
+
@events ||= Hash.new { |h, k| h[k] = [] }
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.on(name, selector = nil, method = nil, &block)
|
41
|
+
if block
|
42
|
+
events[name] << [selector, block]
|
43
|
+
|
44
|
+
[name, selector, block]
|
45
|
+
elsif method
|
46
|
+
events[name] << [selector, method]
|
47
|
+
|
48
|
+
[name, selector, method]
|
49
|
+
else
|
50
|
+
events[name] << [nil, method]
|
51
|
+
|
52
|
+
[name, nil, method]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.off(id)
|
57
|
+
name, selector, block = id
|
58
|
+
|
59
|
+
events[name].delete([selector, block])
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.render(&block)
|
63
|
+
define_method :render do
|
64
|
+
instance_exec(&block)
|
65
|
+
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.html(string = nil, &block)
|
71
|
+
if block
|
72
|
+
render {
|
73
|
+
if block.arity == 1
|
74
|
+
element.inner_dom { |d|
|
75
|
+
instance_exec(d, &block)
|
76
|
+
}
|
77
|
+
else
|
78
|
+
element.inner_dom(&block)
|
79
|
+
end
|
80
|
+
}
|
81
|
+
else
|
82
|
+
render {
|
83
|
+
element.inner_html = string
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.css(content = nil, &block)
|
89
|
+
if content || block
|
90
|
+
@style.remove if @style
|
91
|
+
|
92
|
+
@style = CSS(content, &block)
|
93
|
+
@style.append_to($document.head)
|
94
|
+
else
|
95
|
+
CSS::StyleSheet.new(@style)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_accessor :parent
|
100
|
+
|
101
|
+
def initialize(parent = nil)
|
102
|
+
@parent = parent
|
103
|
+
end
|
104
|
+
|
105
|
+
def tag
|
106
|
+
{ name: :div }.merge(self.class.tag || {})
|
107
|
+
end
|
108
|
+
|
109
|
+
def element
|
110
|
+
return @element if @element
|
111
|
+
|
112
|
+
scope = parent ? parent.element : $document
|
113
|
+
elem = if elem = self.class.element
|
114
|
+
scope.at(elem)
|
115
|
+
else
|
116
|
+
DOM::Element.create tag[:name]
|
117
|
+
end
|
118
|
+
|
119
|
+
unless elem
|
120
|
+
raise ArgumentError, 'element not found'
|
121
|
+
end
|
122
|
+
|
123
|
+
elem.add_class(*tag[:class]) if tag[:class]
|
124
|
+
elem[:id] = tag[:id] if tag[:id]
|
125
|
+
|
126
|
+
self.class.events.each {|name, blocks|
|
127
|
+
blocks.each {|selector, block|
|
128
|
+
if block.is_a? Symbol
|
129
|
+
elem.on(name, selector, &method(block))
|
130
|
+
else
|
131
|
+
elem.on(name, selector) {|*args|
|
132
|
+
instance_exec(*args, &block)
|
133
|
+
}
|
134
|
+
end
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
@element = elem
|
139
|
+
end
|
140
|
+
|
141
|
+
def on(name, selector = nil, method = nil, &block)
|
142
|
+
self.class.on(name, selector, method, &block)
|
143
|
+
|
144
|
+
if @element
|
145
|
+
if block
|
146
|
+
@element.on(name, selector) {|*args|
|
147
|
+
instance_exec(*args, &block)
|
148
|
+
}
|
149
|
+
elsif method
|
150
|
+
@element.on(name, selector, &method(method))
|
151
|
+
else
|
152
|
+
@element.on(name, &method(method))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
# When overriding, remember to call super as last.
|
160
|
+
def render(*)
|
161
|
+
element.trigger :render, self
|
162
|
+
element
|
163
|
+
end
|
164
|
+
|
165
|
+
def remove
|
166
|
+
@element.remove if @element
|
167
|
+
end
|
168
|
+
|
169
|
+
alias destroy remove
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
Browser::DOM::Builder.for Component do |_, item|
|
174
|
+
item.render
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Lissio; class Component
|
2
|
+
|
3
|
+
class Alert < Component
|
4
|
+
def self.new!(message, options = {})
|
5
|
+
new(message, options.merge({ escape: false }))
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :message, :options
|
9
|
+
|
10
|
+
def initialize(message, options = {})
|
11
|
+
@message = message
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def render
|
16
|
+
if @options[:escape] == false
|
17
|
+
element.inner_html = @message
|
18
|
+
else
|
19
|
+
element << @message
|
20
|
+
end
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
tag class: :alert
|
26
|
+
|
27
|
+
css do
|
28
|
+
rule '.alert' do
|
29
|
+
border 1.px, :solid, :transparent
|
30
|
+
|
31
|
+
padding 15.px
|
32
|
+
|
33
|
+
rule 'a' do
|
34
|
+
font weight: :bold
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.customize(*args, &block)
|
40
|
+
if args.length == 1
|
41
|
+
options = args.first
|
42
|
+
else
|
43
|
+
name, options = args
|
44
|
+
end
|
45
|
+
|
46
|
+
name ||= "alert-custom-#{rand(10000)}"
|
47
|
+
options ||= {}
|
48
|
+
|
49
|
+
if self == Alert
|
50
|
+
inherited = []
|
51
|
+
else
|
52
|
+
inherited = class_names
|
53
|
+
end
|
54
|
+
|
55
|
+
Class.new(self) {
|
56
|
+
define_singleton_method :class_names do
|
57
|
+
inherited + [name]
|
58
|
+
end
|
59
|
+
|
60
|
+
tag class: [:alert, name, *inherited]
|
61
|
+
|
62
|
+
css do
|
63
|
+
rule ".alert#{".#{inherited.join('.')}" unless inherited.empty?}.#{name}" do
|
64
|
+
instance_exec(&block) if block
|
65
|
+
|
66
|
+
if value = options[:background] || options[:bg]
|
67
|
+
background color: value
|
68
|
+
end
|
69
|
+
|
70
|
+
if value = options[:foreground] || options[:fg]
|
71
|
+
color value
|
72
|
+
end
|
73
|
+
|
74
|
+
if value = options[:border]
|
75
|
+
border color: value
|
76
|
+
end
|
77
|
+
|
78
|
+
if value = options[:padding]
|
79
|
+
padding value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
Info = customize :info,
|
87
|
+
background: '#d9edf7',
|
88
|
+
foreground: '#3a87ad',
|
89
|
+
border: '#bce8f1'
|
90
|
+
|
91
|
+
Success = customize :success,
|
92
|
+
message: "The operation was successful.",
|
93
|
+
background: '#dff0d8',
|
94
|
+
foreground: '#468847',
|
95
|
+
border: '#d6e9c6'
|
96
|
+
|
97
|
+
Warning = customize :warning,
|
98
|
+
message: "Something might have gone wrong.",
|
99
|
+
background: '#fcf8e3',
|
100
|
+
foreground: '#c09853',
|
101
|
+
border: '#fbeed5'
|
102
|
+
|
103
|
+
Danger = customize :danger,
|
104
|
+
message: "An unexpected error has occurred.",
|
105
|
+
background: '#f2dede',
|
106
|
+
foreground: '#b94a48',
|
107
|
+
border: '#eed3d7'
|
108
|
+
end
|
109
|
+
|
110
|
+
end; end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Lissio; class Component
|
2
|
+
|
3
|
+
class Container < Component
|
4
|
+
class Definer
|
5
|
+
def initialize(&block)
|
6
|
+
@list = []
|
7
|
+
|
8
|
+
if block.arity == 0
|
9
|
+
instance_exec(&block)
|
10
|
+
else
|
11
|
+
block.call(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(what)
|
16
|
+
@list << what
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_a
|
20
|
+
@list
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(parent, &block)
|
25
|
+
super(parent)
|
26
|
+
|
27
|
+
if block
|
28
|
+
@content = Definer.new(&block).to_a
|
29
|
+
else
|
30
|
+
@content = []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(*content, &block)
|
35
|
+
content = @content.dup if content.empty?
|
36
|
+
content.compact! # FIXME: when it's fixed
|
37
|
+
|
38
|
+
element.clear
|
39
|
+
|
40
|
+
content.each {|c|
|
41
|
+
if String === c
|
42
|
+
element << c
|
43
|
+
else
|
44
|
+
element << c.render
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
if block
|
49
|
+
element << DOM(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end; end
|