documentation 0.0.1 → 1.0.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 +4 -4
- data/app/assets/images/documentation/link.svg +28 -0
- data/app/assets/images/documentation/logo-black.svg +23 -0
- data/app/assets/images/documentation/logo-white.svg +23 -0
- data/app/assets/images/documentation/page.svg +16 -0
- data/app/assets/images/documentation/pin.svg +15 -0
- data/app/assets/images/documentation/recommendation.svg +11 -0
- data/app/assets/images/documentation/search.svg +10 -0
- data/app/assets/images/documentation/unhappy.svg +15 -0
- data/app/assets/images/documentation/warning.svg +16 -0
- data/app/assets/javascripts/documentation/application.coffee +14 -0
- data/app/assets/javascripts/documentation/jquery-ui.js +4146 -0
- data/app/assets/javascripts/documentation/jquery.autosize.js +263 -0
- data/app/assets/stylesheets/documentation/application.scss +320 -0
- data/app/assets/stylesheets/documentation/markdown.scss +168 -0
- data/app/assets/stylesheets/documentation/page_form.scss +38 -0
- data/app/assets/stylesheets/documentation/reset.scss +101 -0
- data/app/controllers/documentation/application_controller.rb +21 -0
- data/app/controllers/documentation/pages_controller.rb +74 -0
- data/app/helpers/documentation/application_helper.rb +13 -0
- data/app/models/documentation/page.rb +213 -0
- data/app/views/documentation/pages/_admin_buttons.html.haml +18 -0
- data/app/views/documentation/pages/form.html.haml +12 -0
- data/app/views/documentation/pages/index.html.haml +13 -0
- data/app/views/documentation/pages/positioning.html.haml +22 -0
- data/app/views/documentation/pages/search.html.haml +26 -0
- data/app/views/documentation/pages/show.html.haml +14 -0
- data/app/views/documentation/shared/access_denied.html.haml +10 -0
- data/app/views/documentation/shared/not_found.html.haml +10 -0
- data/app/views/layouts/documentation/_header.html.haml +3 -0
- data/app/views/layouts/documentation/_search.html.haml +5 -0
- data/app/views/layouts/documentation/application.html.haml +18 -0
- data/config/locales/en.yml +56 -0
- data/config/routes.rb +10 -0
- data/db/migrate/20140711185212_create_documentation_pages.rb +10 -0
- data/db/seeds.rb +15 -0
- data/lib/documentation.rb +16 -0
- data/lib/documentation/authorizer.rb +52 -0
- data/lib/documentation/config.rb +31 -0
- data/lib/documentation/engine.rb +29 -0
- data/lib/documentation/errors.rb +7 -0
- data/lib/documentation/generators/setup_generator.rb +13 -0
- data/lib/documentation/markdown_renderer.rb +62 -0
- data/lib/documentation/search_result.rb +84 -0
- data/lib/documentation/searchers/abstract.rb +47 -0
- data/lib/documentation/searchers/simple.rb +40 -0
- data/lib/documentation/version.rb +1 -1
- data/lib/documentation/view_helpers.rb +113 -0
- data/lib/tasks/documentation.rake +6 -0
- metadata +219 -3
@@ -0,0 +1,168 @@
|
|
1
|
+
div.documentationMarkdown {
|
2
|
+
a { color:#35A4D4; text-decoration:none; border-bottom:1px solid #35A4D4; font-weight:600;}
|
3
|
+
|
4
|
+
h1, h2 { font-size:1.6em; font-weight:300; margin:20px 0; border-bottom:1px solid #aecdda; padding-bottom:2px; color:#35A4D4;}
|
5
|
+
h3 { font-size:1.3em; font-weight:500; margin:20px 0; }
|
6
|
+
h4 { font-size:1.1em; font-weight:500; margin:20px 0;}
|
7
|
+
h5 { font-size:1.1em; font-weight:500; margin:20px 0;}
|
8
|
+
|
9
|
+
p { margin:20px 0; line-height:1.8;}
|
10
|
+
|
11
|
+
ul, ol { line-height:1.8em; margin:20px 0; margin-left:40px; }
|
12
|
+
ul li { background:image-url('documentation/pin.svg') no-repeat 0 5px; background-size:13px; padding-left:25px;}
|
13
|
+
ol { margin-left:65px;}
|
14
|
+
ol li { margin-bottom:20px;padding-left:15px;}
|
15
|
+
ol li:last-child { margin-bottom:0; }
|
16
|
+
ul.pages {
|
17
|
+
overflow:hidden;
|
18
|
+
li { background:image-url('documentation/page.svg') no-repeat 0 5px; background-size:13px; padding-left:25px; width:45%; float:left; margin-bottom:5px;}
|
19
|
+
}
|
20
|
+
|
21
|
+
h2, h3, h4, h5 {
|
22
|
+
&:hover a.anchor { display:inline-block;}
|
23
|
+
}
|
24
|
+
|
25
|
+
a.anchor { display:none; float:right; border:0; color:#999; width:16px; height:16px; background:image-url('documentation/link.svg'); opacity:0.6; background-size:16px; text-indent:-40000px;}
|
26
|
+
|
27
|
+
p.recommendation {
|
28
|
+
background:image-url('documentation/recommendation.svg') #F5F7FB no-repeat 20px 16px;
|
29
|
+
background-size:20px;
|
30
|
+
border:1px solid #E4E7ED;
|
31
|
+
padding:15px;
|
32
|
+
margin-left:0;
|
33
|
+
margin-right:0;
|
34
|
+
padding-left:55px;
|
35
|
+
}
|
36
|
+
|
37
|
+
p.warning {
|
38
|
+
background:image-url('documentation/warning.svg') #fffbec no-repeat 20px 18px;
|
39
|
+
background-size:20px;
|
40
|
+
border:1px solid #edda8c;
|
41
|
+
padding:15px;
|
42
|
+
color:#97894f;
|
43
|
+
margin-left:0;
|
44
|
+
margin-right:0;
|
45
|
+
padding-left:55px;
|
46
|
+
}
|
47
|
+
|
48
|
+
p.codeTitle {
|
49
|
+
background:#f7f7f7;
|
50
|
+
margin-bottom:0;
|
51
|
+
line-height:1;
|
52
|
+
border-top-left-radius:6px;
|
53
|
+
border-top-right-radius:6px;
|
54
|
+
padding:15px 20px;
|
55
|
+
font-size:0.9em;
|
56
|
+
font-weight:500;
|
57
|
+
color:#666;
|
58
|
+
border:1px solid #ddd;
|
59
|
+
border-bottom:0;
|
60
|
+
}
|
61
|
+
|
62
|
+
p.codeTitle + div.highlight pre {
|
63
|
+
margin-top:0 !important;
|
64
|
+
border-top-left-radius:0;
|
65
|
+
border-top-right-radius:0;
|
66
|
+
}
|
67
|
+
|
68
|
+
pre {
|
69
|
+
margin:25px 0;
|
70
|
+
line-height:1.4;
|
71
|
+
padding:20px;
|
72
|
+
border-radius:6px;
|
73
|
+
overflow-x:auto;
|
74
|
+
word-wrap: normal;
|
75
|
+
white-space: pre;
|
76
|
+
}
|
77
|
+
|
78
|
+
code { background:#F8F8F8; border:1px solid #ddd; border-radius:4px; padding:0px 5px; margin:0 2px; font-size:0.85em; white-space:pre; word-wrap:none;}
|
79
|
+
|
80
|
+
.imgcontainer.center {
|
81
|
+
display:block;
|
82
|
+
margin:0 35px;
|
83
|
+
text-align:center;
|
84
|
+
img {
|
85
|
+
max-width:90%;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
table {
|
90
|
+
margin:25px 0;
|
91
|
+
min-width:100%;
|
92
|
+
td, th { border:1px solid #E4E7ED; padding:10px;}
|
93
|
+
th { background:#F5F7FB; color:#8E9BB4; font-weight:300; text-align:left;}
|
94
|
+
}
|
95
|
+
|
96
|
+
.highlight {
|
97
|
+
pre {
|
98
|
+
font-family:Consolas, "Liberation Mono", Courier, monospace;
|
99
|
+
font-size:15px;
|
100
|
+
background: #1d1f21; color: #c5c8c6;
|
101
|
+
.hll { background-color: #373b41 }
|
102
|
+
.c { color: #969896 } /* Comment */
|
103
|
+
.err { color: #cc6666 } /* Error */
|
104
|
+
.k { color: #b294bb } /* Keyword */
|
105
|
+
.l { color: #de935f } /* Literal */
|
106
|
+
.n { color: #c5c8c6 } /* Name */
|
107
|
+
.o { color: #8abeb7 } /* Operator */
|
108
|
+
.p { color: #c5c8c6 } /* Punctuation */
|
109
|
+
.cm { color: #969896 } /* Comment.Multiline */
|
110
|
+
.cp { color: #969896 } /* Comment.Preproc */
|
111
|
+
.c1 { color: #969896 } /* Comment.Single */
|
112
|
+
.cs { color: #969896 } /* Comment.Special */
|
113
|
+
.gd { color: #cc6666 } /* Generic.Deleted */
|
114
|
+
.ge { font-style: italic } /* Generic.Emph */
|
115
|
+
.gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */
|
116
|
+
.gi { color: #b5bd68 } /* Generic.Inserted */
|
117
|
+
.gp { color: #969896; font-weight: bold } /* Generic.Prompt */
|
118
|
+
.gs { font-weight: bold } /* Generic.Strong */
|
119
|
+
.gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */
|
120
|
+
.kc { color: #b294bb } /* Keyword.Constant */
|
121
|
+
.kd { color: #b294bb } /* Keyword.Declaration */
|
122
|
+
.kn { color: #8abeb7 } /* Keyword.Namespace */
|
123
|
+
.kp { color: #b294bb } /* Keyword.Pseudo */
|
124
|
+
.kr { color: #b294bb } /* Keyword.Reserved */
|
125
|
+
.kt { color: #f0c674 } /* Keyword.Type */
|
126
|
+
.ld { color: #b5bd68 } /* Literal.Date */
|
127
|
+
.m { color: #de935f } /* Literal.Number */
|
128
|
+
.s { color: #b5bd68 } /* Literal.String */
|
129
|
+
.na { color: #81a2be } /* Name.Attribute */
|
130
|
+
.nb { color: #c5c8c6 } /* Name.Builtin */
|
131
|
+
.nc { color: #f0c674 } /* Name.Class */
|
132
|
+
.no { color: #cc6666 } /* Name.Constant */
|
133
|
+
.nd { color: #8abeb7 } /* Name.Decorator */
|
134
|
+
.ni { color: #c5c8c6 } /* Name.Entity */
|
135
|
+
.ne { color: #cc6666 } /* Name.Exception */
|
136
|
+
.nf { color: #81a2be } /* Name.Function */
|
137
|
+
.nl { color: #c5c8c6 } /* Name.Label */
|
138
|
+
.nn { color: #f0c674 } /* Name.Namespace */
|
139
|
+
.nx { color: #81a2be } /* Name.Other */
|
140
|
+
.py { color: #c5c8c6 } /* Name.Property */
|
141
|
+
.nt { color: #8abeb7 } /* Name.Tag */
|
142
|
+
.nv { color: #cc6666 } /* Name.Variable */
|
143
|
+
.ow { color: #8abeb7 } /* Operator.Word */
|
144
|
+
.w { color: #c5c8c6 } /* Text.Whitespace */
|
145
|
+
.mf { color: #de935f } /* Literal.Number.Float */
|
146
|
+
.mh { color: #de935f } /* Literal.Number.Hex */
|
147
|
+
.mi { color: #de935f } /* Literal.Number.Integer */
|
148
|
+
.mo { color: #de935f } /* Literal.Number.Oct */
|
149
|
+
.sb { color: #b5bd68 } /* Literal.String.Backtick */
|
150
|
+
.sc { color: #c5c8c6 } /* Literal.String.Char */
|
151
|
+
.sd { color: #969896 } /* Literal.String.Doc */
|
152
|
+
.s2 { color: #b5bd68 } /* Literal.String.Double */
|
153
|
+
.se { color: #de935f } /* Literal.String.Escape */
|
154
|
+
.sh { color: #b5bd68 } /* Literal.String.Heredoc */
|
155
|
+
.si { color: #de935f } /* Literal.String.Interpol */
|
156
|
+
.sx { color: #b5bd68 } /* Literal.String.Other */
|
157
|
+
.sr { color: #b5bd68 } /* Literal.String.Regex */
|
158
|
+
.s1 { color: #b5bd68 } /* Literal.String.Single */
|
159
|
+
.ss { color: #b5bd68 } /* Literal.String.Symbol */
|
160
|
+
.bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */
|
161
|
+
.vc { color: #cc6666 } /* Name.Variable.Class */
|
162
|
+
.vg { color: #cc6666 } /* Name.Variable.Global */
|
163
|
+
.vi { color: #cc6666 } /* Name.Variable.Instance */
|
164
|
+
.il { color: #de935f } /* Literal.Number.Integer.Long */
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
form.pageForm {
|
2
|
+
padding:15px 35px 25px 35px;
|
3
|
+
margin-top:10px;
|
4
|
+
input[type=text], textarea {
|
5
|
+
width:100%;
|
6
|
+
border:1px solid #c7cfde;
|
7
|
+
padding:6px;
|
8
|
+
background:#F5F7FB;
|
9
|
+
}
|
10
|
+
|
11
|
+
p.title {
|
12
|
+
margin-bottom:15px;
|
13
|
+
input { font-size:1.3em; font-weight:normal; padding:10px;}
|
14
|
+
}
|
15
|
+
|
16
|
+
p.content {
|
17
|
+
textarea {
|
18
|
+
width:100%;
|
19
|
+
min-height:300px;
|
20
|
+
font-size:0.9em;
|
21
|
+
line-height:1.4;
|
22
|
+
padding:10px;
|
23
|
+
font-family:'Consolas', Monaco, 'Courier New', Courier, fixed;
|
24
|
+
resize:none;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
dl {
|
29
|
+
margin:15px 0;
|
30
|
+
dt { width:120px; float:left; color:#999;padding-top:6px;}
|
31
|
+
dd { margin-left:160px; }
|
32
|
+
dd input { width:100%; font-size:1.1em;}
|
33
|
+
}
|
34
|
+
|
35
|
+
p.submit {
|
36
|
+
text-align:right;
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,101 @@
|
|
1
|
+
html, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, hgroup, menu, nav, section, time, mark, audio, video {
|
2
|
+
margin: 0;
|
3
|
+
padding: 0;
|
4
|
+
border: 0;
|
5
|
+
outline: 0;
|
6
|
+
font-size: 100%;
|
7
|
+
letter-spacing:0;
|
8
|
+
vertical-align: baseline;
|
9
|
+
background: transparent;
|
10
|
+
font-weight:normal;
|
11
|
+
}
|
12
|
+
|
13
|
+
article, aside, figure, footer, header, hgroup, nav, section {display: block;}
|
14
|
+
|
15
|
+
img,object,embed {max-width: 100%;}
|
16
|
+
ul {list-style: none;}
|
17
|
+
blockquote, q {quotes: none;}
|
18
|
+
b,strong { font-weight: bold;}
|
19
|
+
blockquote:before, blockquote:after, q:before, q:after {content: ''; content: none;}
|
20
|
+
|
21
|
+
a {margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent;}
|
22
|
+
|
23
|
+
del {text-decoration: line-through;}
|
24
|
+
|
25
|
+
abbr[title], dfn[title] {border-bottom: 1px dotted #000; cursor: help;}
|
26
|
+
|
27
|
+
/* tables still need cellspacing="0" in the markup */
|
28
|
+
table {border-collapse: collapse; border-spacing: 0;}
|
29
|
+
th {font-weight: bold; vertical-align: bottom;}
|
30
|
+
td {font-weight: normal; vertical-align: top;}
|
31
|
+
|
32
|
+
hr {display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0;}
|
33
|
+
|
34
|
+
input, select {vertical-align: middle;}
|
35
|
+
|
36
|
+
pre {
|
37
|
+
white-space: pre; /* CSS2 */
|
38
|
+
white-space: pre-wrap; /* CSS 2.1 */
|
39
|
+
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
|
40
|
+
word-wrap: break-word; /* IE */
|
41
|
+
}
|
42
|
+
|
43
|
+
input[type="radio"] {vertical-align: text-bottom;}
|
44
|
+
input[type="checkbox"] {vertical-align: bottom; *vertical-align: baseline;}
|
45
|
+
.ie6 input {vertical-align: text-bottom;}
|
46
|
+
|
47
|
+
select, input, textarea {font: 99% sans-serif;}
|
48
|
+
|
49
|
+
table {font-size: inherit; font: 100%;}
|
50
|
+
|
51
|
+
/* Accessible focus treatment
|
52
|
+
people.opera.com/patrickl/experiments/keyboard/test */
|
53
|
+
a:hover, a:active {outline: none;}
|
54
|
+
|
55
|
+
small {font-size: 85%;}
|
56
|
+
|
57
|
+
strong, th {font-weight: bold;}
|
58
|
+
|
59
|
+
td, td img {vertical-align: top;}
|
60
|
+
|
61
|
+
/* Make sure sup and sub don't screw with your line-heights
|
62
|
+
gist.github.com/413930 */
|
63
|
+
sub, sup {font-size: 75%; line-height: 0; position: relative;}
|
64
|
+
sup {top: -0.5em;}
|
65
|
+
sub {bottom: -0.25em;}
|
66
|
+
|
67
|
+
/* standardize any monospaced elements */
|
68
|
+
pre, code, kbd, samp {font-family: monospace, sans-serif;}
|
69
|
+
|
70
|
+
/* hand cursor on clickable elements */
|
71
|
+
.clickable,
|
72
|
+
label,
|
73
|
+
input[type=button],
|
74
|
+
input[type=submit],
|
75
|
+
button {cursor: pointer;}
|
76
|
+
|
77
|
+
/* Webkit browsers add a 2px margin outside the chrome of form elements */
|
78
|
+
button, input, select, textarea {margin: 0;}
|
79
|
+
|
80
|
+
/* make buttons play nice in IE */
|
81
|
+
button {width: auto; overflow: visible;}
|
82
|
+
|
83
|
+
/* scale images in IE7 more attractively */
|
84
|
+
.ie7 img {-ms-interpolation-mode: bicubic;}
|
85
|
+
|
86
|
+
/* prevent BG image flicker upon hover */
|
87
|
+
.ie6 html {filter: expression(document.execCommand("BackgroundImageCache", false, true));}
|
88
|
+
|
89
|
+
/* let's clear some floats */
|
90
|
+
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
|
91
|
+
.clearfix:after { clear: both; }
|
92
|
+
.clearfix { zoom: 1; }
|
93
|
+
|
94
|
+
select, input, textarea, a { outline: none;}
|
95
|
+
|
96
|
+
|
97
|
+
input, textarea {
|
98
|
+
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
99
|
+
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
100
|
+
box-sizing: border-box; /* Opera/IE 8+ */
|
101
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Documentation
|
2
|
+
class ApplicationController < ActionController::Base
|
3
|
+
|
4
|
+
rescue_from Documentation::AccessDeniedError do |e|
|
5
|
+
render :template => 'documentation/shared/access_denied', :layout => false
|
6
|
+
end
|
7
|
+
|
8
|
+
rescue_from ActiveRecord::RecordNotFound do |e|
|
9
|
+
render :template => 'documentation/shared/not_found', :layout => false
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def authorizer
|
15
|
+
@authorizer ||= Documentation.config.authorizer.new(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
helper_method :authorizer
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Documentation
|
2
|
+
class PagesController < Documentation::ApplicationController
|
3
|
+
|
4
|
+
before_filter :find_page, :only => [:show, :edit, :new, :destroy, :positioning]
|
5
|
+
|
6
|
+
def show
|
7
|
+
authorizer.check! :view_page, @page
|
8
|
+
end
|
9
|
+
|
10
|
+
def edit
|
11
|
+
authorizer.check! :edit_page, @page
|
12
|
+
|
13
|
+
if request.patch?
|
14
|
+
if @page.update_attributes(safe_params)
|
15
|
+
redirect_to page_path(@page.full_permalink), :notice => "Page has been saved successfully."
|
16
|
+
return
|
17
|
+
end
|
18
|
+
end
|
19
|
+
render :action => "form"
|
20
|
+
end
|
21
|
+
|
22
|
+
def new
|
23
|
+
authorizer.check! :add_page, @page
|
24
|
+
|
25
|
+
parent = @page
|
26
|
+
@page = Page.new(:title => "Untitled Page")
|
27
|
+
if @page.parent = parent
|
28
|
+
@page.parents = parent.breadcrumb
|
29
|
+
end
|
30
|
+
|
31
|
+
if request.post?
|
32
|
+
@page.attributes = safe_params
|
33
|
+
if @page.save
|
34
|
+
redirect_to page_path(@page.full_permalink), :notice => "Page created successfully"
|
35
|
+
return
|
36
|
+
end
|
37
|
+
end
|
38
|
+
render :action => "form"
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy
|
42
|
+
authorizer.check! :delete_page, @page
|
43
|
+
@page.destroy
|
44
|
+
redirect_to @page.parent ? page_path(@page.parent.full_permalink) : root_path, :notice => "Page has been removed successfully."
|
45
|
+
end
|
46
|
+
|
47
|
+
def positioning
|
48
|
+
authorizer.check! :reposition_page, @page
|
49
|
+
@pages = @page ? @page.children : Page.roots
|
50
|
+
if request.post?
|
51
|
+
Page.reorder(@page, params[:order])
|
52
|
+
render :json => {:status => 'ok'}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def search
|
57
|
+
authorizer.check! :search
|
58
|
+
@result = Documentation::Page.search(params[:query], :page => params[:page].blank? ? 1 : params[:page].to_i)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def find_page
|
64
|
+
if params[:path]
|
65
|
+
@page = Page.find_from_path(params[:path])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def safe_params
|
70
|
+
params.require(:page).permit(:title, :permalink, :content, :favourite)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Documentation
|
2
|
+
module ApplicationHelper
|
3
|
+
|
4
|
+
include Documentation::ViewHelpers
|
5
|
+
|
6
|
+
def flash_messages
|
7
|
+
flashes = flash.collect do |key,msg|
|
8
|
+
content_tag :div, content_tag(:p, h(msg)), :id => "flash-#{key}"
|
9
|
+
end.join.html_safe
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module Documentation
|
2
|
+
class Page < ActiveRecord::Base
|
3
|
+
|
4
|
+
validates :title, :presence => true
|
5
|
+
validates :position, :presence => true
|
6
|
+
validates :permalink, :presence => true, :uniqueness => {:scope => :parent_id}
|
7
|
+
|
8
|
+
default_scope -> { order(:position) }
|
9
|
+
scope :roots, -> { where(:parent_id => nil) }
|
10
|
+
|
11
|
+
belongs_to :parent, :class_name => 'Documentation::Page', :foreign_key => 'parent_id'
|
12
|
+
|
13
|
+
before_validation do
|
14
|
+
if self.position.blank?
|
15
|
+
last_position = self.class.unscoped.where(:parent_id => self.parent_id).order(:position => :desc).first
|
16
|
+
self.position = last_position ? last_position.position + 1 : 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
before_validation :set_permalink
|
21
|
+
before_save :compile_content
|
22
|
+
|
23
|
+
#
|
24
|
+
# Ensure the page is updated in the index after saving/destruction
|
25
|
+
#
|
26
|
+
after_commit :index, :on => [:create, :update]
|
27
|
+
after_commit :delete_from_index, :on => :destroy
|
28
|
+
|
29
|
+
#
|
30
|
+
# Store all the parents of this object. THis is automatically populated when it is loaded
|
31
|
+
# from a path
|
32
|
+
#
|
33
|
+
attr_accessor :parents
|
34
|
+
|
35
|
+
#
|
36
|
+
# Set the permalink for this page
|
37
|
+
#
|
38
|
+
def set_permalink
|
39
|
+
proposed_permalink = self.title.parameterize
|
40
|
+
index = 1
|
41
|
+
while self.permalink.blank?
|
42
|
+
if self.class.where(:permalink => proposed_permalink, :parent_id => self.parent_id).exists?
|
43
|
+
index += 1
|
44
|
+
proposed_permalink = self.title.parameterize + "-#{index}"
|
45
|
+
else
|
46
|
+
self.permalink = proposed_permalink
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Return a default empty array for parents
|
53
|
+
#
|
54
|
+
def parents
|
55
|
+
@parents ||= self.parent ? [self.parent.parents, self.parent].flatten : []
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Return a full breadcrumb to this page (as it has been loaded)
|
60
|
+
#
|
61
|
+
def breadcrumb
|
62
|
+
@breadcrumb ||= [parents, new_record? ? nil : self].flatten.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Return the path where this page can be viewed in the site
|
67
|
+
#
|
68
|
+
def preview_path
|
69
|
+
if path = Documentation.config.preview_path_prefix
|
70
|
+
"#{path}#{full_permalink}"
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Return a full permalink tot his page
|
78
|
+
#
|
79
|
+
def full_permalink
|
80
|
+
@full_permalink ||= begin
|
81
|
+
if parents.empty?
|
82
|
+
self.permalink
|
83
|
+
else
|
84
|
+
previous = breadcrumb.compact.map(&:permalink).compact
|
85
|
+
previous.empty? ? self.permalink : previous.join('/')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Return all child pages
|
92
|
+
#
|
93
|
+
def children
|
94
|
+
@children ||= begin
|
95
|
+
if self.new_record?
|
96
|
+
[]
|
97
|
+
else
|
98
|
+
children = self.class.where(:parent_id => self.id)
|
99
|
+
children.each { |c| c.parents = [parents, self].flatten }
|
100
|
+
children
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Does this page have children?
|
107
|
+
#
|
108
|
+
def has_children?
|
109
|
+
!children.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Return pages which should be included in the navigation
|
114
|
+
#
|
115
|
+
def navigation
|
116
|
+
if has_children?
|
117
|
+
root_parent = parents[-1]
|
118
|
+
if root_parent.nil?
|
119
|
+
pages = self.class.roots
|
120
|
+
else
|
121
|
+
pages = (root_parent || self).children
|
122
|
+
end
|
123
|
+
else
|
124
|
+
root_parent = parents[-2] || parents[-1]
|
125
|
+
if root_parent.nil? || (root_parent.parent.nil? && parents.size <= 1)
|
126
|
+
pages = self.class.roots
|
127
|
+
else
|
128
|
+
pages = (root_parent || self).children
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
pages.map do |c|
|
134
|
+
child_pages = []
|
135
|
+
child_pages = c.children if breadcrumb.include?(c)
|
136
|
+
[c, child_pages]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Create the compiled content
|
142
|
+
#
|
143
|
+
def compile_content
|
144
|
+
mr = Documentation::MarkdownRenderer.new
|
145
|
+
mr.page = self
|
146
|
+
rc = Redcarpet::Markdown.new(mr, :space_after_headers => true, :fenced_code_blocks => true, :no_intra_emphasis => true, :highlight => true)
|
147
|
+
self.compiled_content = rc.render(self.content.to_s).html_safe
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Index this page
|
152
|
+
#
|
153
|
+
def index
|
154
|
+
if searcher = Documentation.config.searcher
|
155
|
+
searcher.index(self)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Delete this page from the index
|
161
|
+
#
|
162
|
+
def delete_from_index
|
163
|
+
if searcher = Documentation.config.searcher
|
164
|
+
searcher.delete(self)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Find a page using the searcher if one exists otherwise just fall back to AR
|
170
|
+
# searching. Returns a Documentation::SearchResult object.
|
171
|
+
#
|
172
|
+
def self.search(query, options = {})
|
173
|
+
if searcher = Documentation.config.searcher
|
174
|
+
searcher.search(query, options)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Find a page by passing a path to the page from the root of the
|
180
|
+
# site
|
181
|
+
#
|
182
|
+
def self.find_from_path(path_string)
|
183
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find page without a path" if path_string.blank?
|
184
|
+
path_parts = path_string.split('/')
|
185
|
+
path = []
|
186
|
+
path_parts.each_with_index do |p, i|
|
187
|
+
page = self.where(:parent_id => (path.last ? path.last.id : nil)).find_by_permalink(p)
|
188
|
+
if page
|
189
|
+
page.parents = path.dup
|
190
|
+
page.parent = path.last
|
191
|
+
path << page
|
192
|
+
else
|
193
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find page at #{path_string}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
path.last
|
197
|
+
end
|
198
|
+
|
199
|
+
#
|
200
|
+
# Reorder pgaes
|
201
|
+
#
|
202
|
+
def self.reorder(parent, order = [])
|
203
|
+
order = order.map(&:to_i)
|
204
|
+
order = self.where(:parent_id => parent.id).map(&:id) if order.empty?
|
205
|
+
order.each_with_index do |id, index|
|
206
|
+
command = self.find_by_id!(id)
|
207
|
+
command.position = index + 1
|
208
|
+
command.save
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|