fron 0.2.0rc1 → 1.0.0rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +20 -0
  3. data/.reek +2 -0
  4. data/.rubocop.yml +14 -11
  5. data/Gemfile +6 -3
  6. data/Gemfile.lock +73 -86
  7. data/Rakefile +11 -15
  8. data/Readme.md +1 -1
  9. data/fron.gemspec +2 -2
  10. data/lib/fron/version.rb +1 -1
  11. data/opal/fron.rb +2 -0
  12. data/opal/fron/core.rb +1 -0
  13. data/opal/fron/core/behaviors/components.rb +18 -10
  14. data/opal/fron/core/behaviors/events.rb +9 -10
  15. data/opal/fron/core/behaviors/routes.rb +6 -10
  16. data/opal/fron/core/behaviors/style.rb +30 -0
  17. data/opal/fron/core/component.rb +44 -23
  18. data/opal/fron/core/eventable.rb +3 -3
  19. data/opal/fron/core/logger.rb +1 -1
  20. data/opal/fron/core/sheet.rb +140 -0
  21. data/opal/fron/core_ext.rb +1 -0
  22. data/opal/fron/core_ext/array.rb +23 -0
  23. data/opal/fron/core_ext/hash.rb +6 -6
  24. data/opal/fron/core_ext/kernel.rb +10 -1
  25. data/opal/fron/core_ext/numeric.rb +7 -0
  26. data/opal/fron/core_ext/time.rb +6 -0
  27. data/opal/fron/dom/document.rb +6 -3
  28. data/opal/fron/dom/element.rb +79 -19
  29. data/opal/fron/dom/event.rb +5 -1
  30. data/opal/fron/dom/modules/dimensions.rb +0 -14
  31. data/opal/fron/dom/modules/element_accessor.rb +25 -0
  32. data/opal/fron/dom/modules/events.rb +1 -1
  33. data/opal/fron/dom/node.rb +7 -5
  34. data/opal/fron/dom/style.rb +0 -2
  35. data/opal/fron/dom/window.rb +14 -0
  36. data/opal/fron/event_mock.rb +24 -6
  37. data/opal/fron/js/scroll_into_view_if_needed.js +27 -0
  38. data/opal/fron/js/syntetic_event.js +20 -13
  39. data/opal/fron/request/request.rb +21 -19
  40. data/opal/fron/request/response.rb +1 -1
  41. data/opal/fron/storage.rb +2 -0
  42. data/opal/fron/storage/local_storage.rb +3 -45
  43. data/opal/fron/storage/session_storage.rb +12 -0
  44. data/opal/fron/storage/store.rb +54 -0
  45. data/opal/fron/utils/drag.rb +21 -18
  46. data/opal/fron/utils/keyboard.rb +14 -12
  47. data/opal/fron/utils/point.rb +12 -4
  48. data/opal/fron/utils/render_proc.rb +6 -2
  49. data/spec/core-ext/array_spec.rb +10 -2
  50. data/spec/core-ext/numeric_spec.rb +6 -0
  51. data/spec/core/behaviors/style_spec.rb +51 -0
  52. data/spec/core/component_inheritance_spec.rb +10 -15
  53. data/spec/core/component_spec.rb +10 -15
  54. data/spec/dom/element_spec.rb +12 -1
  55. data/spec/dom/modules/classlist_spec.rb +8 -9
  56. data/spec/dom/modules/dimensions_spec.rb +2 -1
  57. data/spec/dom/modules/events_spec.rb +42 -31
  58. data/spec/dom/style_spec.rb +1 -1
  59. data/spec/spec_helper.rb +0 -1
  60. data/spec/utils/drag_spec.rb +2 -2
  61. data/spec/utils/keyboard_spec.rb +4 -1
  62. data/website/application.rb +4 -0
  63. data/website/config.ru +30 -0
  64. data/website/examples/content_editable.rb +29 -0
  65. data/website/examples/converter.rb +49 -0
  66. data/website/examples/icon_button.rb +20 -0
  67. data/website/examples/image_paragraph.rb +33 -0
  68. data/website/examples/my_blue_box.rb +9 -0
  69. data/website/examples/my_box.rb +9 -0
  70. data/website/examples/my_button.rb +27 -0
  71. data/website/examples/my_green_box.rb +14 -0
  72. data/website/examples/source_reader.rb +32 -0
  73. data/website/examples/text_area.rb +42 -0
  74. data/website/pages/components.md.erb +16 -0
  75. data/website/pages/components/composition.md.erb +9 -0
  76. data/website/pages/components/events.md.erb +20 -0
  77. data/website/pages/components/inheritance.md.erb +19 -0
  78. data/website/pages/components/routes.md.erb +49 -0
  79. data/website/pages/components/styles.md.erb +38 -0
  80. data/website/pages/getting-started.md +8 -0
  81. data/website/pages/home.md +4 -0
  82. data/website/pages/intro.md +30 -0
  83. data/website/pages/utilities.md +10 -0
  84. data/website/pages/utilities/local-storage.md.erb +16 -0
  85. data/website/pages/utilities/request.md.erb +12 -0
  86. data/website/setup.rb +162 -0
  87. data/website/vendor/highlight.js +2 -0
  88. data/website/vendor/highlight.ruby.js +1 -0
  89. data/website/vendor/marked.min.js +6 -0
  90. metadata +43 -7
@@ -0,0 +1,9 @@
1
+ # My Box....MINE!MIIINEEEE!!!
2
+ class MyBox < Fron::Component
3
+ tag 'my-box'
4
+
5
+ style background: :red,
6
+ display: :block,
7
+ height: 100.px,
8
+ width: 100.px
9
+ end
@@ -0,0 +1,27 @@
1
+ # Simple button class
2
+ class MyButton < Fron::Component
3
+ # Set the tag
4
+ tag 'my-button'
5
+
6
+ # When clicked greet the user
7
+ on :click, :greet
8
+
9
+ # Some styling...
10
+ style border: '1px solid #CCC',
11
+ display: 'inline-block',
12
+ background: '#F9F9F9',
13
+ padding: '5px 15px',
14
+ borderRadius: 5.px,
15
+ cursor: :pointer
16
+
17
+ # Set default text
18
+ def initialize
19
+ super
20
+ self.text = 'Click Me!'
21
+ end
22
+
23
+ # Greet the user
24
+ def greet
25
+ alert('Hello there!')
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ # Hulk SMASH!
2
+ class MyGreenBox < MyBox
3
+ tag 'my-green-box'
4
+
5
+ style justifyContent: :center,
6
+ alignItems: :center,
7
+ background: :green,
8
+ display: :flex,
9
+ span: { fontWeight: :bold,
10
+ fontSize: 20.px,
11
+ color: '#FFF' }
12
+
13
+ component :span, :span, text: 'Doh!'
14
+ end
@@ -0,0 +1,32 @@
1
+ # Source Reader
2
+ class SourceReader < Fron::Component
3
+ # Set the tag
4
+ tag 'source-reader'
5
+
6
+ # Button for initialize loading
7
+ component :button, 'button', text: 'Load source...'
8
+
9
+ # Styles
10
+ style whiteSpace: 'pre-wrap',
11
+ background: '#F9F9F9',
12
+ borderRadius: 5.px,
13
+ display: :block,
14
+ padding: 20.px
15
+
16
+ # Event to load
17
+ on :click, :button, :load
18
+
19
+ # Load source
20
+ def load
21
+ request.get do |response|
22
+ self.text = response.body.split("\n")[0..2].join("\n") + "\n..."
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # Request object
29
+ def request
30
+ @request ||= Fron::Request.new '/assets/pages/utilities/request.md'
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ # Text Area component
2
+ class TextArea < Fron::Component
3
+ # Set the tag
4
+ tag 'text-area'
5
+
6
+ # Create textarea
7
+ component :textarea, :textarea
8
+
9
+ # Save on input
10
+ on :input, :save
11
+
12
+ # Styles
13
+ style textarea: { border: '1px solid #EEE',
14
+ fontFamily: 'Open Sans',
15
+ minHeight: 100.px,
16
+ fontSize: 18.px,
17
+ padding: 20.px,
18
+ width: '100%' }
19
+
20
+ # Load on initialization
21
+ def initialize
22
+ super
23
+ load
24
+ end
25
+
26
+ # Load from LocalStorage
27
+ def load
28
+ @textarea.value = storage.get(:value).to_s
29
+ end
30
+
31
+ # Save to LocalStorage
32
+ def save
33
+ storage.set :value, @textarea.value
34
+ end
35
+
36
+ private
37
+
38
+ # Helper method
39
+ def storage
40
+ Fron::Storage::LocalStorage
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # 4. Components
2
+ Components are the main part of Fron. Everything is built around / for them, although nothing in the library is using them so you can say that they are the end product.
3
+
4
+ ## What is a component?
5
+ A **component** is basically a custom **HTML Element** that you can **add methods to**, **extend** and **compose** into other components. It is defined by **Fron::Component** class.
6
+
7
+ An input field using contenteditable attribute:
8
+ <%= example 'ContentEditable' %>
9
+
10
+ ## How does it work?
11
+ Basically the **native HTML Element** is **wrapped** in a Ruby class instance. If you are curious about the implementation check the source [here](https://github.com/digitalnatives/fron/blob/master/opal/fron/dom/element.rb) and [here](https://github.com/digitalnatives/fron/blob/master/opal/fron/core/component.rb)
12
+
13
+ ## How it is different from Web Components?
14
+ [Web Components](https://css-tricks.com/modular-future-web-components/) use new technologies that are not available everywhere, also they introduce a lot of concepts that seem odd and complicated.
15
+
16
+ Fron components are straightforward and easy to learn.
@@ -0,0 +1,9 @@
1
+ # 4.2. Composition
2
+ Components can be composited together with the **component** DSL method.
3
+ This allows you to create more complex components like this paragraph component:
4
+
5
+ <%= example 'ImageParagraph' %>
6
+
7
+ As you can see the components we defined (**image** and **content**) are accessible with
8
+ **instance variables** also with **instance methods** but only if there is no method
9
+ defined with that name.
@@ -0,0 +1,20 @@
1
+ # 4.3. Events
2
+ Handling events are pretty easy by using the **on** DSL method in a component:
3
+ when an **event** happens on the element the given **instance method** is called.
4
+
5
+ There are two ways of adding events:
6
+ * Adding events directly to the component is used when it's not important which
7
+ child component triggered the event, or the event isn't delegateable like
8
+ **focus** or **blur**:
9
+ ```ruby
10
+ on :blur, :close
11
+ ```
12
+ * Delegating events with a **selector** is usefull if we only want to handle events
13
+ that were trigger child components matching a specific selector.
14
+ ```ruby
15
+ on :click, 'button > span', :open
16
+ ```
17
+
18
+ For example let's say there is a component for converting **celsius** to **fahrenheit**:
19
+
20
+ <%= example 'Converter' %>
@@ -0,0 +1,19 @@
1
+ # 4.1. Inheritance
2
+ Because **components** are just **ruby classes**, that means that they can be exteded
3
+ and reused.
4
+
5
+ Let's say that there is a **Button** component that greets you when you click it,
6
+ and we would like to have an icon before the text, but it doesn't support that:
7
+
8
+ <%= example 'MyButton' %>
9
+
10
+ What we can do is to create an **IconButton** component that extends the **Button** component,
11
+ and add the functionality to that:
12
+
13
+ <%= example 'IconButton' %>
14
+
15
+ What we did was:
16
+ * we extended the **Button** component so the events, styles and methods are inherited
17
+ * we added two components one for the **icon** and one for the **text (span)**
18
+ * we delegated the two methods **text** and **text=** in order to not break the API
19
+ * we added some styles to the **icon** component
@@ -0,0 +1,49 @@
1
+ # 4.4. Routes
2
+ Routing is supported in Fron via the **Fron::Behaviors::Routes** module, and
3
+ currently is very sparse.
4
+
5
+ When used in a **component**, routes can be defined in order to call an
6
+ _instance method_ when the page location changes to match a perticular
7
+ regular expression or string:
8
+
9
+ ```ruby
10
+ class Main < Fron::Component
11
+ include Fron::Behaviors::Routes
12
+
13
+ route '/users', :users
14
+ route '/posts', :posts
15
+
16
+ def users
17
+ # Display users
18
+ end
19
+
20
+ def posts
21
+ # Display posts
22
+ end
23
+ end
24
+ ```
25
+
26
+ ## Handling Parameters
27
+
28
+ If capture groups are defined for the regular expression than they will be
29
+ forwarded to the method as arguments:
30
+
31
+ ```ruby
32
+ class Main < Fron::Component
33
+ include Fron::Behaviors::Routes
34
+
35
+ route '/users/(.*)', :show_user
36
+
37
+ def show_user(id)
38
+ # Display user with the given id
39
+ end
40
+ end
41
+ ```
42
+
43
+ Things to keep in mind when handling routes:
44
+ * Multiple components can include this module and define routes
45
+ * If multiple components matches a route then only one (the first) will
46
+ be called
47
+ * Components that are not in the DOM can handle routes too
48
+ * The best practice is to have one component to handle all the routes and then
49
+ delegate down the component tree
@@ -0,0 +1,38 @@
1
+ # 4.5. Styles
2
+ One of the things that makes Fron unqiue is the ability to style components
3
+ directly on them. To achieve this we will use the **Fron::Behaviors::Style**
4
+ module (which is automatically included into every component).
5
+
6
+ Once the application is initailized all of the styles are rendered into a
7
+ **style** tag into the head of the document.
8
+
9
+ ## Basic Usage
10
+ To define styles to a **component** you only need to call the **style** DSL
11
+ method with the object for the properties and values:
12
+
13
+ <%= example 'MyBox' %>
14
+
15
+ Tips on creating great style objects:
16
+ * For numbers there is two helper modules **px** and **em**, use them often
17
+ * Symbols can be used for non hypenated CSS values such as _block_ or _flex_
18
+ * Any value can be a Ruby expression
19
+ * Any value can be a lambda that is evaluated when the style is rendered
20
+ * Use caplitalized keys. They are converted to their hypenated versions
21
+
22
+ ## Inheritance
23
+ When a component extends an other component that have styles, all of the style
24
+ definitions are copied over (this happens when the style DSL is used, otherwise
25
+ the **ensure_styles!** method needs to be called). Styles that are defined on the
26
+ child component have priority over the ones defined in the parent component.
27
+
28
+ Also if properties defined multiple times, only the last one will be used.
29
+
30
+ <%= example 'MyBlueBox' %>
31
+
32
+ ## Styling Nested Components
33
+
34
+ If the component have **nested components** then they can be styled by having
35
+ a key with a selectors (like in raw CSS) and the associated value as a hash
36
+ containing the CSS properties and values:
37
+
38
+ <%= example 'MyGreenBox' %>
@@ -0,0 +1,8 @@
1
+ # 2. Getting Started
2
+ This guide is for anyone who wants to get started with Fron. It's recommended to have a basic understanding of the **Ruby / JavaScript / CSS** programming languages and be familiar the **DOM**.
3
+
4
+ In order to try the examples the following prerequisites must be installed:
5
+ * Working Ruby installation. You can get the lastest Ruby from [Ruby download page](https://www.ruby-lang.org/en/downloads/)
6
+ * A web browser such as [Chrome](https://www.mozilla.org/en-US/firefox/new/) or [Firefox](http://www.google.com/chrome/)
7
+
8
+ You can find the examples [here]().
@@ -0,0 +1,4 @@
1
+ # Fron
2
+ ## User Interface with the power of Ruby
3
+
4
+ Fron is a component based User Interface framework.
@@ -0,0 +1,30 @@
1
+ # 1. Introduction
2
+ This guide will walk you through the basic concepts behind Fron. Fron is a library
3
+ for creating dynamic **composeable user interfaces** with Ruby (through Opal).
4
+
5
+ ## Features
6
+ * **Custom components** that can be extended and composed
7
+ * **Behaviors** to specify how components behave
8
+ * **Utilities** such as **Request**, **Shortcuts** or **Drag & Drop**
9
+ * **Testable** with **RSpec**
10
+
11
+ ## Resources
12
+ * Source Code: [https://github.com/digitalnatives/fron](https://github.com/digitalnatives/fron)
13
+ * Yard Documentation: [http://www.rubydoc.info/github/digitalnatives/fron/master](http://www.rubydoc.info/github/digitalnatives/fron/master)
14
+
15
+ ## Why Ruby?
16
+ We chose ruby for these awesome features:
17
+ * It is a **mature language** (20 years)
18
+ * Has a very good **module** and **class** system
19
+ * A **coding style guide** is already present
20
+ * **Testing** is and always have been a core part (Rspec)
21
+ * The **tooling** is great (Rubycritic, Rubocop, Sprockets just to name a few)
22
+
23
+ ## How it is different from [Volt](http://voltframework.com/) or [Vienna](https://github.com/opal/vienna)?
24
+ Both of those frameworks apply the same logic that has been used to create web sites for decades, namely the MVC pattern and templates, also they are not using the DOM
25
+ in a Rubyesque way, they treat the frontend as you would in a Rails application.
26
+
27
+ Fron is different because it is not trying to be a full featured web application, it is just the **fron(tend) part** in **Ruby**.
28
+
29
+ ## It is used in production?
30
+ Currently we use it in production in one of our inhouse products [Nostromo](https://nostromo.io).
@@ -0,0 +1,10 @@
1
+ # 5. Utilities
2
+ Fron provides a few **utility classes** by default:
3
+
4
+ * **Request** - Making and handling requests
5
+ * **LocalStorage** - LocalStorage wrapper
6
+ * **Keyboard** - Adding and handling shortcuts
7
+ * **Drag** - Low level dragging support
8
+
9
+ These classes are not connected to the interfaces directly but helps you solve a lot
10
+ of issues that generally come up for web applications.
@@ -0,0 +1,16 @@
1
+ # 5.2. Local Storage
2
+
3
+ *Notice: Only the LocalStorage and SessionStorage is implemented, we plan to implement CookieStorage with the same interface.*
4
+
5
+ This utility is for storing things in [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) you can see the API documentation [here](http://www.rubydoc.info/github/digitalnatives/fron/master/Fron/Storage/LocalStorage).
6
+
7
+ It implements the following class methods:
8
+ * **all** - Returns all keys and their values in a hash
9
+ * **clear** - Clears all keys
10
+ * **get(key)** - Get the value of the given key
11
+ * **remove(key)** - Removes the given key
12
+ * **set(key, value)** - Sets the given value to the given key
13
+
14
+ As an example the contents of this textarea persists between reloads:
15
+
16
+ <%= example 'TextArea' %>
@@ -0,0 +1,12 @@
1
+ # 5.1. Request
2
+
3
+ *Warning: Not all features are available yet, but it's usable.*
4
+
5
+ This class is a wrapper for the [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). You can see the API documentation [here](http://www.rubydoc.info/github/digitalnatives/fron/master/Fron/Request).
6
+
7
+ As an example we want to display the first there lines of the source of this page in a component:
8
+
9
+ <%= example 'SourceReader' %>
10
+
11
+ As you can see we instantiated a new [**Fron::Request**](http://www.rubydoc.info/github/digitalnatives/fron/master/Fron/Request) object and called
12
+ the **get** method to get the source which returned a [**Fron::Response**](http://www.rubydoc.info/github/digitalnatives/fron/master/Fron/Response) object.
@@ -0,0 +1,162 @@
1
+ require 'fron'
2
+ require 'vendor/highlight'
3
+ require 'vendor/highlight.ruby'
4
+ require 'vendor/marked.min'
5
+
6
+ require 'examples/my_button'
7
+ require 'examples/my_box'
8
+ require_tree './examples'
9
+
10
+ %x{
11
+ marked.setOptions({
12
+ highlight: function(code) {
13
+ return hljs.highlightAuto(code).value;
14
+ }
15
+ });
16
+ }
17
+
18
+ class Sidebar < Fron::Component
19
+ class Item < Fron::Component
20
+ tag 'header-item'
21
+ end
22
+
23
+ component :title, 'sidebar-title', target: :home, text: 'Fron'
24
+ component :about, 'sidebar-item', target: 'intro', text: 'Introduction'
25
+ component :dom, 'sidebar-item', target: 'getting-started', text: 'Getting Started'
26
+ component :setup, 'sidebar-item', target: 'the-dom', text: 'The DOM'
27
+ component :components, 'sidebar-item', target: 'components', text: 'Components'
28
+ component :components, 'sidebar-sub-item', target: 'components/inheritance', text: 'Inheritance'
29
+ component :components, 'sidebar-sub-item', target: 'components/composition', text: 'Composition'
30
+ component :components, 'sidebar-sub-item', target: 'components/events', text: 'Events'
31
+ component :components, 'sidebar-sub-item', target: 'components/routes', text: 'Routes'
32
+ component :components, 'sidebar-sub-item', target: 'components/styles', text: 'Styles'
33
+ component :behaviors, 'sidebar-item', target: 'utilities', text: 'Utilities'
34
+ component :behaviors, 'sidebar-sub-item', target: 'utilities/request', text: 'Request'
35
+ component :behaviors, 'sidebar-sub-item', target: 'utilities/local-storage', text: 'Local Storage'
36
+
37
+ style counterReset: :items,
38
+ padding: 20.px,
39
+ 'sidebar-title' => {
40
+ borderBottom: '2px solid #EEE',
41
+ paddingBottom: 5.px,
42
+ marginBottom: 10.px,
43
+ display: :block,
44
+ fontWeight: 600,
45
+ fontSize: 24.px
46
+ },
47
+ '[target]' => {
48
+ cursor: :pointer
49
+ },
50
+ 'sidebar-item' => {
51
+ borderBottom: '1px solid #EEE',
52
+ counterIncrement: :items,
53
+ counterReset: :subitems,
54
+ padding: '10px 0',
55
+ display: :block,
56
+ '&:before' => {
57
+ content: 'counter(items) ". "'
58
+ }
59
+ },
60
+ 'sidebar-sub-item' => {
61
+ borderBottom: '1px solid #EEE',
62
+ counterIncrement: :subitems,
63
+ padding: '10px 0',
64
+ paddingLeft: 10.px,
65
+ display: :block,
66
+ '&:before' => {
67
+ content: 'counter(items) "." counter(subitems) ". "'
68
+ }
69
+ }
70
+
71
+ on :click, '[target]', :navigate
72
+
73
+ def navigate(event)
74
+ DOM::Window.state = "/#{event.target[:target]}"
75
+ end
76
+ end
77
+
78
+ class Main < Fron::Component
79
+ include Fron::Behaviors::Routes
80
+
81
+ component :sidebar, Sidebar
82
+ component :wrapper, :wrapper do
83
+ component :container, :container
84
+ end
85
+
86
+ style fontFamily: 'Open Sans',
87
+ width: 1200.px,
88
+ margin: '0 auto',
89
+ color: '#444',
90
+ a: {
91
+ textDecoration: :none,
92
+ color: '#00ACE6'
93
+ },
94
+ sidebar: {
95
+ width: 240.px,
96
+ float: :left
97
+ },
98
+ pre: {
99
+ background: '#F9F9F9',
100
+ borderRadius: 5.px,
101
+ padding: 20.px
102
+ },
103
+ wrapper: {
104
+ overflow: :auto,
105
+ padding: 40.px,
106
+ paddingBottom: 60.px,
107
+ marginLeft: 240.px,
108
+ display: :block
109
+ },
110
+ h1: {
111
+ lineHeight: '1em',
112
+ marginTop: 0
113
+ }
114
+
115
+ route '(.*)', :page
116
+
117
+ def initialize
118
+ super
119
+ @pages = {}
120
+ Fron::Sheet.render_style_tag
121
+ Fron::Behaviors::Routes.listen
122
+ end
123
+
124
+ def home
125
+ DOM::Window.state = '/home'
126
+ end
127
+
128
+ def page(page)
129
+ return home if page == '/'
130
+ load page do |html|
131
+ @wrapper.container.html = html
132
+ @wrapper.container.find_all('example').each do |example|
133
+ example << Module.const_get(example['class']).new
134
+ end
135
+ end
136
+ end
137
+
138
+ def load(page)
139
+ return yield @pages[page] if @pages[page]
140
+ Fron::Request.new("/assets/pages#{page}.md").get do |response|
141
+ @pages[page] = `marked(#{response.body})`
142
+ yield @pages[page]
143
+ end
144
+ end
145
+ end
146
+
147
+ Fron::Behaviors::Style::Sheet.add_rule '*', { boxSizing: 'border-box' }, '0'
148
+ Fron::Behaviors::Style::Sheet.add_rule 'body', { margin: 0,
149
+ overflowY: :scroll,
150
+ fontSize: 18.px,
151
+ lineHeight: 26.px }, '1'
152
+
153
+ ['//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css',
154
+ '//fonts.googleapis.com/css?family=Open+Sans:400,600,700',
155
+ '//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/styles/tomorrow.min.css'
156
+ ].each do |url|
157
+ link = DOM::Element.new 'link'
158
+ link[:rel] = :stylesheet
159
+ link[:type] = 'text/css'
160
+ link[:href] = url
161
+ link >> DOM::Document.head
162
+ end