local_documentation 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- 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 +78 -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 +322 -0
- data/app/assets/stylesheets/documentation/markdown.scss +168 -0
- data/app/assets/stylesheets/documentation/page_form.scss +39 -0
- data/app/assets/stylesheets/documentation/reset.scss +101 -0
- data/app/controllers/documentation/application_controller.rb +27 -0
- data/app/controllers/documentation/pages_controller.rb +93 -0
- data/app/helpers/documentation/application_helper.rb +13 -0
- data/app/models/documentation/page.rb +214 -0
- data/app/models/documentation/screenshot.rb +15 -0
- data/app/views/documentation/pages/_admin_buttons.html.haml +18 -0
- data/app/views/documentation/pages/form.html.haml +16 -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/screenshot.html.haml +15 -0
- data/app/views/documentation/pages/search.html.haml +12 -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/_footer.html.haml +0 -0
- data/app/views/layouts/documentation/_head.html.haml +0 -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 +22 -0
- data/config/locales/en.yml +62 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20140711185212_create_documentation_pages.rb +10 -0
- data/db/migrate/20140724111844_create_nifty_attachments_table.rb +16 -0
- data/db/migrate/20140724114255_create_documentation_screenshots.rb +7 -0
- data/db/seeds.rb +15 -0
- data/doc/developers-guide/authorization.md +37 -0
- data/doc/developers-guide/building-views/accessing-pages.md +38 -0
- data/doc/developers-guide/building-views/helpers.md +105 -0
- data/doc/developers-guide/building-views/overview.md +3 -0
- data/doc/developers-guide/customization.md +9 -0
- data/doc/developers-guide/overview.md +20 -0
- data/doc/developers-guide/search-backends.md +17 -0
- data/doc/markdown/overview.md +37 -0
- data/lib/documentation.rb +20 -0
- data/lib/documentation/authorizer.rb +60 -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 +17 -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 +3 -0
- data/lib/documentation/view_helpers.rb +159 -0
- data/lib/tasks/documentation.rake +44 -0
- metadata +307 -0
@@ -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,39 @@
|
|
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
|
+
dd.padded { padding-top: 6px; }
|
34
|
+
}
|
35
|
+
|
36
|
+
p.submit {
|
37
|
+
text-align:right;
|
38
|
+
}
|
39
|
+
}
|
@@ -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,27 @@
|
|
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
|
+
before_filter do
|
13
|
+
unless authorizer.can_use_ui?
|
14
|
+
render :template => 'documentation/shared/not_found', :layout => false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def authorizer
|
21
|
+
@authorizer ||= Documentation.config.authorizer.new(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
helper_method :authorizer
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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 screenshot
|
48
|
+
authorizer.check! :upload, @page
|
49
|
+
if request.post?
|
50
|
+
@screenshot = Screenshot.new(screenshot_params)
|
51
|
+
if @screenshot.save
|
52
|
+
render :json => { :id => @screenshot.id, :title => @screenshot.alt_text, :path => @screenshot.upload.path }, :status => :created
|
53
|
+
else
|
54
|
+
render :json => { :errors => @screenshot.errors }, :status => :unprocessible_entity
|
55
|
+
end
|
56
|
+
else
|
57
|
+
@screenshot = Screenshot.new
|
58
|
+
render 'screenshot', :layout => false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def positioning
|
63
|
+
authorizer.check! :reposition_page, @page
|
64
|
+
@pages = @page ? @page.children : Page.roots
|
65
|
+
if request.post?
|
66
|
+
Page.reorder(@page, params[:order])
|
67
|
+
render :json => {:status => 'ok'}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def search
|
72
|
+
authorizer.check! :search
|
73
|
+
@result = Documentation::Page.search(params[:query], :page => params[:page].blank? ? 1 : params[:page].to_i)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def find_page
|
79
|
+
if params[:path]
|
80
|
+
@page = Page.find_from_path(params[:path])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def safe_params
|
85
|
+
params.require(:page).permit(:title, :permalink, :content, :favourite)
|
86
|
+
end
|
87
|
+
|
88
|
+
def screenshot_params
|
89
|
+
params.require(:screenshot).permit(:upload_file, :alt_text)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
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,214 @@
|
|
1
|
+
module Documentation
|
2
|
+
class Page < ActiveRecord::Base
|
3
|
+
establish_connection :documentation
|
4
|
+
|
5
|
+
validates :title, :presence => true
|
6
|
+
validates :position, :presence => true
|
7
|
+
validates :permalink, :presence => true, :uniqueness => {:scope => :parent_id}
|
8
|
+
|
9
|
+
default_scope -> { order(:position) }
|
10
|
+
scope :roots, -> { where(:parent_id => nil) }
|
11
|
+
|
12
|
+
belongs_to :parent, :class_name => 'Documentation::Page', :foreign_key => 'parent_id'
|
13
|
+
|
14
|
+
before_validation do
|
15
|
+
if self.position.blank?
|
16
|
+
last_position = self.class.unscoped.where(:parent_id => self.parent_id).order(:position => :desc).first
|
17
|
+
self.position = last_position ? last_position.position + 1 : 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
before_validation :set_permalink
|
22
|
+
before_save :compile_content
|
23
|
+
|
24
|
+
#
|
25
|
+
# Ensure the page is updated in the index after saving/destruction
|
26
|
+
#
|
27
|
+
after_commit :index, :on => [:create, :update]
|
28
|
+
after_commit :delete_from_index, :on => :destroy
|
29
|
+
|
30
|
+
#
|
31
|
+
# Store all the parents of this object. THis is automatically populated when it is loaded
|
32
|
+
# from a path
|
33
|
+
#
|
34
|
+
attr_accessor :parents
|
35
|
+
|
36
|
+
#
|
37
|
+
# Set the permalink for this page
|
38
|
+
#
|
39
|
+
def set_permalink
|
40
|
+
proposed_permalink = self.title.parameterize
|
41
|
+
index = 1
|
42
|
+
while self.permalink.blank?
|
43
|
+
if self.class.where(:permalink => proposed_permalink, :parent_id => self.parent_id).exists?
|
44
|
+
index += 1
|
45
|
+
proposed_permalink = self.title.parameterize + "-#{index}"
|
46
|
+
else
|
47
|
+
self.permalink = proposed_permalink
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Return a default empty array for parents
|
54
|
+
#
|
55
|
+
def parents
|
56
|
+
@parents ||= self.parent ? [self.parent.parents, self.parent].flatten : []
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Return a full breadcrumb to this page (as it has been loaded)
|
61
|
+
#
|
62
|
+
def breadcrumb
|
63
|
+
@breadcrumb ||= [parents, new_record? ? nil : self].flatten.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Return the path where this page can be viewed in the site
|
68
|
+
#
|
69
|
+
def preview_path
|
70
|
+
if path = Documentation.config.preview_path_prefix
|
71
|
+
"#{path}#{full_permalink}"
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Return a full permalink tot his page
|
79
|
+
#
|
80
|
+
def full_permalink
|
81
|
+
@full_permalink ||= begin
|
82
|
+
if parents.empty?
|
83
|
+
self.permalink
|
84
|
+
else
|
85
|
+
previous = breadcrumb.compact.map(&:permalink).compact
|
86
|
+
previous.empty? ? self.permalink : previous.join('/')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Return all child pages
|
93
|
+
#
|
94
|
+
def children
|
95
|
+
@children ||= begin
|
96
|
+
if self.new_record?
|
97
|
+
[]
|
98
|
+
else
|
99
|
+
children = self.class.where(:parent_id => self.id)
|
100
|
+
children.each { |c| c.parents = [parents, self].flatten }
|
101
|
+
children
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# Does this page have children?
|
108
|
+
#
|
109
|
+
def has_children?
|
110
|
+
!children.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Return pages which should be included in the navigation
|
115
|
+
#
|
116
|
+
def navigation
|
117
|
+
if has_children?
|
118
|
+
root_parent = parents[-1]
|
119
|
+
if root_parent.nil?
|
120
|
+
pages = self.class.roots
|
121
|
+
else
|
122
|
+
pages = (root_parent || self).children
|
123
|
+
end
|
124
|
+
else
|
125
|
+
root_parent = parents[-2] || parents[-1]
|
126
|
+
if root_parent.nil? || (root_parent.parent.nil? && parents.size <= 1)
|
127
|
+
pages = self.class.roots
|
128
|
+
else
|
129
|
+
pages = (root_parent || self).children
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
pages.map do |c|
|
135
|
+
child_pages = []
|
136
|
+
child_pages = c.children if breadcrumb.include?(c)
|
137
|
+
[c, child_pages]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Create the compiled content
|
143
|
+
#
|
144
|
+
def compile_content
|
145
|
+
mr = Documentation::MarkdownRenderer.new
|
146
|
+
mr.page = self
|
147
|
+
rc = Redcarpet::Markdown.new(mr, :space_after_headers => true, :fenced_code_blocks => true, :no_intra_emphasis => true, :highlight => true)
|
148
|
+
self.compiled_content = rc.render(self.content.to_s).html_safe
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Index this page
|
153
|
+
#
|
154
|
+
def index
|
155
|
+
if searcher = Documentation.config.searcher
|
156
|
+
searcher.index(self)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Delete this page from the index
|
162
|
+
#
|
163
|
+
def delete_from_index
|
164
|
+
if searcher = Documentation.config.searcher
|
165
|
+
searcher.delete(self)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# Find a page using the searcher if one exists otherwise just fall back to AR
|
171
|
+
# searching. Returns a Documentation::SearchResult object.
|
172
|
+
#
|
173
|
+
def self.search(query, options = {})
|
174
|
+
if searcher = Documentation.config.searcher
|
175
|
+
searcher.search(query, options)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Find a page by passing a path to the page from the root of the
|
181
|
+
# site
|
182
|
+
#
|
183
|
+
def self.find_from_path(path_string)
|
184
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find page without a path" if path_string.blank?
|
185
|
+
path_parts = path_string.split('/')
|
186
|
+
path = []
|
187
|
+
path_parts.each_with_index do |p, i|
|
188
|
+
page = self.where(:parent_id => (path.last ? path.last.id : nil)).find_by_permalink(p)
|
189
|
+
if page
|
190
|
+
page.parents = path.dup
|
191
|
+
page.parent = path.last
|
192
|
+
path << page
|
193
|
+
else
|
194
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find page at #{path_string}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
path.last
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Reorder pgaes
|
202
|
+
#
|
203
|
+
def self.reorder(parent, order = [])
|
204
|
+
order = order.map(&:to_i)
|
205
|
+
order = self.where(:parent_id => parent.id).map(&:id) if order.empty?
|
206
|
+
order.each_with_index do |id, index|
|
207
|
+
command = self.find_by_id!(id)
|
208
|
+
command.position = index + 1
|
209
|
+
command.save
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|