apipony 0.0.4 → 0.0.7
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 +4 -4
- data/README.md +205 -3
- data/Rakefile +3 -7
- data/app/assets/stylesheets/apipony/styles.scss +153 -27
- data/app/views/apipony/application/_attribute.html.erb +42 -0
- data/app/views/apipony/application/_footer.html.erb +8 -0
- data/app/views/apipony/application/_header.html.erb +8 -0
- data/app/views/apipony/application/_method.html.erb +1 -0
- data/app/views/apipony/application/_request.html.erb +37 -0
- data/app/views/apipony/application/_response.html.erb +30 -0
- data/app/views/apipony/application/_sidebar.html.erb +16 -0
- data/app/views/apipony/application/index.html.erb +60 -0
- data/app/views/layouts/apipony/application.html.erb +22 -0
- data/lib/apipony.rb +19 -0
- data/lib/apipony/documentation.rb +24 -0
- data/lib/apipony/endpoint.rb +24 -3
- data/lib/apipony/engine.rb +2 -0
- data/lib/apipony/example_response.rb +9 -0
- data/lib/apipony/parameter.rb +3 -2
- data/lib/apipony/request.rb +17 -4
- data/lib/apipony/response.rb +47 -5
- data/lib/apipony/response_attribute.rb +130 -0
- data/lib/apipony/section.rb +11 -2
- data/lib/apipony/version.rb +3 -1
- metadata +42 -19
- data/app/views/apipony/application/index.html.haml +0 -74
- data/app/views/layouts/apipony/application.html.haml +0 -12
- data/config/initializers/haml.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c3166ea73b31da19b4bda3071ae02dcfa4d4ca9
|
4
|
+
data.tar.gz: 5a6d604772716180f128ef341ff9df54e0a03eac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e734f9f86495b7ee3d9f10b16bd3148f43264133ca7a388f3c4e6067ce2af65c9bbe1ebd480697bc88c97bba20fc760c1f883ee3770948b8f055a6fad120f3b8
|
7
|
+
data.tar.gz: 52c6232bf579dc49170f7c8699e8f183449af9e66a63488e721f6502cc16d9652341f29f01036ef98d1530f51c9f55047848df1c95ab570522e6c3afc32a79dc
|
data/README.md
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
# Apipony
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/apipony)
|
4
|
+
[](https://travis-ci.org/droptheplot/apipony)
|
4
5
|
[](https://codeclimate.com/github/droptheplot/apipony)
|
5
6
|
|
6
7
|
Ruby DSL to create Rails API documentation from your application.
|
7
8
|
|
8
|
-
## Getting started
|
9
9
|
|
10
|
+
## Getting started
|
10
11
|
* Add `gem 'apipony'` to Gemfile
|
11
12
|
* `bundle install`
|
12
13
|
* `rails g apipony:install`
|
13
14
|
* Now you can edit your documentation in `config/initializers/apipony.rb`
|
14
15
|
|
15
|
-
## How it works
|
16
16
|
|
17
|
+
## How it works
|
17
18
|
DSL example:
|
18
19
|
|
19
20
|
```ruby
|
@@ -28,7 +29,7 @@ Apipony::Documentation.define do
|
|
28
29
|
e.description = 'Find ponies'
|
29
30
|
|
30
31
|
request_with do
|
31
|
-
param :name, example: 'applejack', required: true
|
32
|
+
param :name, example: 'applejack', required: true
|
32
33
|
end
|
33
34
|
|
34
35
|
response_with 200 do
|
@@ -44,6 +45,207 @@ Apipony::Documentation.define do
|
|
44
45
|
end
|
45
46
|
```
|
46
47
|
|
48
|
+
|
49
|
+
## Features
|
50
|
+
|
51
|
+
### Response Attribute Documentation
|
52
|
+
Apipony lets you provide further documentation about the attributes in your
|
53
|
+
API's responses. Simply create a new `attribute` inside a `respond_with` block.
|
54
|
+
These attributes can even have nested sub-attributes, for documentation of
|
55
|
+
more complex object graphs.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Apipony::Documentation.define do
|
59
|
+
section "Species" do
|
60
|
+
endpoint 'get', '/species/:id' do |e|
|
61
|
+
e.description = "Get information about a species"
|
62
|
+
respond_with 200 do
|
63
|
+
example do
|
64
|
+
set :body, {
|
65
|
+
name: "Unicorn",
|
66
|
+
is_pony: true,
|
67
|
+
biology: {
|
68
|
+
has_horn: true,
|
69
|
+
has_wings: false
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
attribute :name, type: :string
|
74
|
+
attribute :is_pony, type: :bool,
|
75
|
+
description: "Is this species a type of pony?"
|
76
|
+
attribute :biology do
|
77
|
+
attribute :has_horn, type: :bool
|
78
|
+
attribute :has_wings, type: :bool
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### Enum Attributes
|
87
|
+
Your API may have some fields that can be picked from a pre-defined set of
|
88
|
+
values. You can document those values by using an enum attribute.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
Apipony::Documentation.define do
|
92
|
+
section "Ponies" do
|
93
|
+
endpoint "get", "/ponies/:id" do |e|
|
94
|
+
e.description = "Information about a pony"
|
95
|
+
example do
|
96
|
+
set :body, {
|
97
|
+
name: "Applejack",
|
98
|
+
sex: "female",
|
99
|
+
kind: :earth,
|
100
|
+
occupation: :farmer
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
attribute :kind, type: :enum do
|
105
|
+
choice :earth, description: "A pony with no wings or horn"
|
106
|
+
choice :unicorn, description: "A pony with a horn"
|
107
|
+
choice :pegasus, description: "A pony with wings"
|
108
|
+
choice :alicorn,
|
109
|
+
description: "A pony with wings and a horn. Indicates royalty."
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
### Example Generation
|
117
|
+
When describing attributes, you can provide an optional `example:` parameter.
|
118
|
+
If included, this will be used to generate the example response in the
|
119
|
+
documentation.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Apipony::Documentation.define do
|
123
|
+
section "Ponies" do
|
124
|
+
endpoint "get", "/ponies/:id" do |e|
|
125
|
+
respond_with 200 do
|
126
|
+
attribute :name, type: :string, example: "Applejack"
|
127
|
+
# Enum members automatically select the first choice
|
128
|
+
attribute :kind, type: :enum do
|
129
|
+
choice :earth
|
130
|
+
choice :pegasus
|
131
|
+
choice :unicorn
|
132
|
+
choice :alicorn
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
endpoint "get", "/ponies/" do |e|
|
138
|
+
# Automatic serialization of arrays is supported
|
139
|
+
respond_with 200, array: true do
|
140
|
+
attribute :name, type: :string, example: "Applejack"
|
141
|
+
attribute :id, type: :integer, example: 10
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
`GET /ponies/:id` will now have the example of:
|
149
|
+
|
150
|
+
```json
|
151
|
+
{
|
152
|
+
"name": "Applejack",
|
153
|
+
"kind": "Earth"
|
154
|
+
}
|
155
|
+
```
|
156
|
+
|
157
|
+
`GET /ponies/` will have the example of:
|
158
|
+
|
159
|
+
```json
|
160
|
+
[
|
161
|
+
{
|
162
|
+
"name": "Applejack",
|
163
|
+
"id": 10
|
164
|
+
}
|
165
|
+
]
|
166
|
+
```
|
167
|
+
|
168
|
+
### Predefined Subtypes
|
169
|
+
Sometimes, when building an API, it can be useful to store data in a common
|
170
|
+
format. Apipony lets you define this common format once, then use it multiple
|
171
|
+
times. Check it out:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
Apipony::Documentation.define do
|
175
|
+
subtype :pony_stub do
|
176
|
+
attribute :name, type: :string
|
177
|
+
attribute :id, type: :integer
|
178
|
+
end
|
179
|
+
|
180
|
+
section "Ponies" do
|
181
|
+
endpoint 'get', '/ponies/:id' do |e|
|
182
|
+
e.description = "Find a pony with a given name"
|
183
|
+
request_with do
|
184
|
+
params :id, example: 10, required: true
|
185
|
+
end
|
186
|
+
|
187
|
+
response_with 200 do
|
188
|
+
example do
|
189
|
+
set :body, {
|
190
|
+
:name => :applejack,
|
191
|
+
:type => :earth,
|
192
|
+
:sex => :female,
|
193
|
+
:occupation => :farmer,
|
194
|
+
:friends => [
|
195
|
+
{name: "Twilight Sparkle", id: 1},
|
196
|
+
{name: "Pinkie Pie", id: 2},
|
197
|
+
{name: "Rainbow Dash", id: 3},
|
198
|
+
{name: "Rarity", id: 4},
|
199
|
+
{name: "Fluttershy", id: 5}
|
200
|
+
]
|
201
|
+
}
|
202
|
+
end
|
203
|
+
attribute :name, type: :string
|
204
|
+
attribute :kind, type: :enum do
|
205
|
+
choice :alicorn
|
206
|
+
choice :earth
|
207
|
+
choice :unicorn
|
208
|
+
choice :pegasus
|
209
|
+
end
|
210
|
+
attribute :friends, type: :pony_stub, array: true
|
211
|
+
attribute :occupation, type: :string
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
section "Locations" do
|
216
|
+
endpoint 'get', '/locations/:id' do |e|
|
217
|
+
e.description = "Information about a location"
|
218
|
+
response_with 200 do
|
219
|
+
example do
|
220
|
+
set :body, {
|
221
|
+
:name => "Crystal Empire",
|
222
|
+
:population => 107770,
|
223
|
+
:rulers => [
|
224
|
+
{
|
225
|
+
name: "Shining Armor",
|
226
|
+
id: 50
|
227
|
+
},
|
228
|
+
{
|
229
|
+
name: "Princess Cadence",
|
230
|
+
id: 90001
|
231
|
+
}
|
232
|
+
]
|
233
|
+
}
|
234
|
+
end
|
235
|
+
attribute :name, type: :string
|
236
|
+
attribute :population, type: :integer
|
237
|
+
attribute :rulers, type: :pony_stub, array: true
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
Now, the `friends` attribute of `GET /ponies/:id` and the `rulers` attribute of
|
245
|
+
`GET /locations/:id` will reference a common subtype on the generated
|
246
|
+
documentation.
|
247
|
+
|
248
|
+
|
47
249
|
Generated documentation example:
|
48
250
|
|
49
251
|

|
data/Rakefile
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
begin
|
2
2
|
require 'bundler/setup'
|
3
|
+
require 'yard'
|
3
4
|
rescue LoadError
|
4
5
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
6
|
end
|
6
7
|
|
7
8
|
require 'rdoc/task'
|
8
9
|
|
9
|
-
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
11
|
-
rdoc.title = 'Apipony'
|
12
|
-
rdoc.options << '--line-numbers'
|
13
|
-
rdoc.rdoc_files.include('README.rdoc')
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
10
|
+
YARD::Rake::YardocTask.new(:doc) do |doc|
|
15
11
|
end
|
16
12
|
|
17
|
-
APP_RAKEFILE = File.expand_path("../
|
13
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
14
|
load 'rails/tasks/engine.rake'
|
19
15
|
|
20
16
|
|
@@ -1,13 +1,25 @@
|
|
1
|
+
// Color scheme
|
1
2
|
$dark_color: #111;
|
2
3
|
$medium_color: #888;
|
3
4
|
$light_color: #eee;
|
4
|
-
$brand_color: #
|
5
|
+
$brand_color: #5e147d;
|
5
6
|
|
6
|
-
|
7
|
+
// Method colors
|
8
|
+
$get-color: #1abc9c;
|
9
|
+
$post-color: #3498db;
|
10
|
+
$put-color: #2980d9;
|
11
|
+
$delete-color: #c0392b;
|
7
12
|
|
8
|
-
|
13
|
+
// Misc. values
|
14
|
+
$transition-time: 200ms;
|
15
|
+
$mobile-width: 900px;
|
16
|
+
|
17
|
+
|
18
|
+
*, *::before, *::after {
|
9
19
|
margin: 0;
|
10
20
|
padding: 0;
|
21
|
+
box-sizing: border-box;
|
22
|
+
|
11
23
|
&:focus {
|
12
24
|
outline: none;
|
13
25
|
}
|
@@ -19,8 +31,8 @@ html, body {
|
|
19
31
|
}
|
20
32
|
|
21
33
|
body {
|
22
|
-
font-family: 'Arial';
|
23
|
-
font-size:
|
34
|
+
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
35
|
+
font-size: 12px;
|
24
36
|
color: $dark_color;
|
25
37
|
-webkit-font-smoothing: antialiased;
|
26
38
|
}
|
@@ -45,8 +57,9 @@ h2 {
|
|
45
57
|
}
|
46
58
|
|
47
59
|
h3 {
|
60
|
+
padding-top: 10px;
|
48
61
|
font-size: 17px;
|
49
|
-
line-height:
|
62
|
+
line-height: 16px;
|
50
63
|
}
|
51
64
|
|
52
65
|
h4 {
|
@@ -57,17 +70,22 @@ h4 {
|
|
57
70
|
|
58
71
|
h5 {
|
59
72
|
font-weight: normal;
|
73
|
+
font-size: 12px;
|
74
|
+
line-height: 17px;
|
60
75
|
}
|
61
76
|
|
62
77
|
ul {
|
63
78
|
margin-bottom: 20px;
|
79
|
+
|
64
80
|
li {
|
65
81
|
margin-bottom: 10px;
|
66
82
|
}
|
67
83
|
}
|
68
84
|
|
69
85
|
.container {
|
70
|
-
width:
|
86
|
+
max-width: 900px;
|
87
|
+
width: 100%;
|
88
|
+
padding: 0 20px;
|
71
89
|
margin: 0 auto;
|
72
90
|
position: relative;
|
73
91
|
}
|
@@ -75,20 +93,28 @@ ul {
|
|
75
93
|
.row {
|
76
94
|
overflow: hidden;
|
77
95
|
margin: 0 -10px;
|
96
|
+
display: flex;
|
97
|
+
flex-flow: row nowrap;
|
98
|
+
|
99
|
+
@media all and (max-width: $mobile-width) {
|
100
|
+
flex-flow: row wrap;
|
101
|
+
}
|
78
102
|
}
|
79
103
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
104
|
+
.sidebar {
|
105
|
+
flex: 1 120px;
|
106
|
+
|
107
|
+
@media all and (max-width: $mobile-width) {
|
108
|
+
flex: 1 100%;
|
109
|
+
margin-bottom: 30px;
|
86
110
|
}
|
87
111
|
}
|
88
112
|
|
89
|
-
|
90
|
-
|
91
|
-
|
113
|
+
.main-col {
|
114
|
+
flex: 8;
|
115
|
+
|
116
|
+
@media all and (max-width: $mobile-width) {
|
117
|
+
flex: 1 100%;
|
92
118
|
}
|
93
119
|
}
|
94
120
|
|
@@ -96,17 +122,23 @@ header {
|
|
96
122
|
background: $brand_color;
|
97
123
|
height: 60px;
|
98
124
|
line-height: 60px;
|
125
|
+
|
99
126
|
.title {
|
100
|
-
font-family: 'Cuprum';
|
101
127
|
font-weight: bold;
|
102
128
|
font-size: 20px;
|
103
129
|
color: #fff;
|
104
130
|
}
|
131
|
+
|
105
132
|
.back {
|
106
133
|
float: right;
|
107
134
|
color: #fff;
|
108
|
-
opacity: .7;
|
135
|
+
opacity: 0.7;
|
109
136
|
font-size: 12px;
|
137
|
+
transition: $transition-time opacity;
|
138
|
+
|
139
|
+
&:hover {
|
140
|
+
opacity: 1;
|
141
|
+
}
|
110
142
|
}
|
111
143
|
}
|
112
144
|
|
@@ -120,8 +152,15 @@ footer {
|
|
120
152
|
border-top: 1px solid $light_color;
|
121
153
|
text-align: right;
|
122
154
|
font-size: 12px;
|
155
|
+
color: $medium_color;
|
156
|
+
|
123
157
|
a {
|
124
158
|
color: $medium_color;
|
159
|
+
transition: $transition-time color;
|
160
|
+
|
161
|
+
&:hover {
|
162
|
+
color: $dark-color;
|
163
|
+
}
|
125
164
|
}
|
126
165
|
}
|
127
166
|
|
@@ -131,22 +170,27 @@ footer {
|
|
131
170
|
margin-bottom: 40px;
|
132
171
|
border-bottom: 1px solid $light_color;
|
133
172
|
}
|
173
|
+
|
134
174
|
.endpoint {
|
135
175
|
position: relative;
|
176
|
+
|
136
177
|
&:not(:last-child) {
|
137
178
|
margin-bottom: 40px;
|
138
179
|
}
|
180
|
+
|
139
181
|
.description {
|
140
182
|
font-size: 12px;
|
141
183
|
color: $medium_color;
|
142
184
|
padding-top: 20px;
|
143
185
|
font-weight: normal;
|
144
186
|
}
|
187
|
+
|
145
188
|
.response, .request {
|
146
189
|
padding-top: 20px;
|
190
|
+
|
147
191
|
.code {
|
148
192
|
padding: 8px 10px 10px;
|
149
|
-
font-size:
|
193
|
+
font-size: 14px;
|
150
194
|
border-radius: 0 0 3px 3px;
|
151
195
|
}
|
152
196
|
}
|
@@ -157,48 +201,60 @@ footer {
|
|
157
201
|
border: 1px solid darken($light_color, 5%);
|
158
202
|
border-radius: 3px;
|
159
203
|
margin-top: 20px;
|
204
|
+
overflow-x: auto;
|
205
|
+
|
160
206
|
.title {
|
161
207
|
border-bottom: 1px solid darken($light_color, 5%);
|
162
208
|
padding: 10px;
|
163
|
-
font-size:
|
209
|
+
font-size: 12px;
|
164
210
|
font-weight: bold;
|
165
211
|
}
|
166
212
|
}
|
167
213
|
|
168
214
|
.method {
|
169
215
|
text-transform: uppercase;
|
216
|
+
vertical-align: bottom;
|
170
217
|
color: #fff;
|
171
218
|
font-size: 9px;
|
172
219
|
padding: 4px 5px;
|
173
220
|
border-radius: 3px;
|
174
221
|
margin-right: 5px;
|
175
222
|
font-weight: bold;
|
176
|
-
background: $medium_color;
|
223
|
+
background-color: $medium_color;
|
224
|
+
|
177
225
|
&.get {
|
178
|
-
background:
|
226
|
+
background-color: $get-color;
|
179
227
|
}
|
228
|
+
|
180
229
|
&.post {
|
181
|
-
background:
|
230
|
+
background-color: $post-color;
|
182
231
|
}
|
232
|
+
|
183
233
|
&.put {
|
184
|
-
background:
|
234
|
+
background-color: $put-color;
|
185
235
|
}
|
236
|
+
|
186
237
|
&.delete {
|
187
|
-
background:
|
238
|
+
background-color: $delete-color;
|
188
239
|
}
|
189
240
|
}
|
190
241
|
|
191
|
-
.
|
242
|
+
.parameters, .attributes {
|
243
|
+
font-size: 12px;
|
244
|
+
|
192
245
|
.name {
|
193
246
|
color: $brand_color;
|
194
247
|
font-weight: bold;
|
195
248
|
}
|
249
|
+
|
196
250
|
.type {
|
197
251
|
color: $medium_color;
|
198
252
|
font-style: italic;
|
199
253
|
}
|
254
|
+
|
200
255
|
.required {
|
201
256
|
text-align: center;
|
257
|
+
|
202
258
|
.fa {
|
203
259
|
color: $medium_color;
|
204
260
|
}
|
@@ -207,9 +263,79 @@ footer {
|
|
207
263
|
|
208
264
|
.table {
|
209
265
|
padding: 8px 0;
|
266
|
+
|
210
267
|
tr {
|
211
268
|
td {
|
212
|
-
padding:
|
269
|
+
padding: 4px 10px;
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
.attribute-container {
|
275
|
+
display: flex;
|
276
|
+
flex-flow: column nowrap;
|
277
|
+
|
278
|
+
.attribute {
|
279
|
+
display: flex;
|
280
|
+
flex-flow: row nowrap;
|
281
|
+
align-items: inherit;
|
282
|
+
justify-content: flex-start;
|
283
|
+
|
284
|
+
> div {
|
285
|
+
padding: 4px 10px;
|
286
|
+
}
|
287
|
+
|
288
|
+
.attribute-type {
|
289
|
+
flex: 1 1 60px;
|
290
|
+
}
|
291
|
+
|
292
|
+
.attribute-name {
|
293
|
+
flex: 2 1 0;
|
294
|
+
min-width: 90px;
|
295
|
+
}
|
296
|
+
|
297
|
+
.attribute-description {
|
298
|
+
flex: 8 1 0;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
ul {
|
303
|
+
margin: 8px 10px;
|
304
|
+
border: 1px solid #e1e1e1;
|
305
|
+
border-radius: 3px;
|
306
|
+
|
307
|
+
li {
|
308
|
+
list-style: none;
|
309
|
+
margin: 10px 0;
|
310
|
+
padding: 0 12px;
|
213
311
|
}
|
214
312
|
}
|
215
313
|
}
|
314
|
+
|
315
|
+
.attribute-enum-member {
|
316
|
+
display: flex;
|
317
|
+
flex-flow: row nowrap;
|
318
|
+
|
319
|
+
.title {
|
320
|
+
border-bottom: 1px solid darken($light_color, 5%);
|
321
|
+
padding: 10px;
|
322
|
+
font-size: 11px;
|
323
|
+
font-weight: bold;
|
324
|
+
}
|
325
|
+
|
326
|
+
.attribute-enum-member-name {
|
327
|
+
font-weight: 400;
|
328
|
+
margin-right: 10px;
|
329
|
+
min-width: 60px;
|
330
|
+
flex: 1 1 0;
|
331
|
+
color: $brand_color;
|
332
|
+
}
|
333
|
+
|
334
|
+
.attribute-enum-member-description {
|
335
|
+
flex: 7 1 0;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
339
|
+
.subtype {
|
340
|
+
margin-bottom: 25px;
|
341
|
+
}
|