local_documentation 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/app/assets/images/documentation/link.svg +28 -0
  4. data/app/assets/images/documentation/logo-black.svg +23 -0
  5. data/app/assets/images/documentation/logo-white.svg +23 -0
  6. data/app/assets/images/documentation/page.svg +16 -0
  7. data/app/assets/images/documentation/pin.svg +15 -0
  8. data/app/assets/images/documentation/recommendation.svg +11 -0
  9. data/app/assets/images/documentation/search.svg +10 -0
  10. data/app/assets/images/documentation/unhappy.svg +15 -0
  11. data/app/assets/images/documentation/warning.svg +16 -0
  12. data/app/assets/javascripts/documentation/application.coffee +78 -0
  13. data/app/assets/javascripts/documentation/jquery-ui.js +4146 -0
  14. data/app/assets/javascripts/documentation/jquery.autosize.js +263 -0
  15. data/app/assets/stylesheets/documentation/application.scss +322 -0
  16. data/app/assets/stylesheets/documentation/markdown.scss +168 -0
  17. data/app/assets/stylesheets/documentation/page_form.scss +39 -0
  18. data/app/assets/stylesheets/documentation/reset.scss +101 -0
  19. data/app/controllers/documentation/application_controller.rb +27 -0
  20. data/app/controllers/documentation/pages_controller.rb +93 -0
  21. data/app/helpers/documentation/application_helper.rb +13 -0
  22. data/app/models/documentation/page.rb +214 -0
  23. data/app/models/documentation/screenshot.rb +15 -0
  24. data/app/views/documentation/pages/_admin_buttons.html.haml +18 -0
  25. data/app/views/documentation/pages/form.html.haml +16 -0
  26. data/app/views/documentation/pages/index.html.haml +13 -0
  27. data/app/views/documentation/pages/positioning.html.haml +22 -0
  28. data/app/views/documentation/pages/screenshot.html.haml +15 -0
  29. data/app/views/documentation/pages/search.html.haml +12 -0
  30. data/app/views/documentation/pages/show.html.haml +14 -0
  31. data/app/views/documentation/shared/access_denied.html.haml +10 -0
  32. data/app/views/documentation/shared/not_found.html.haml +10 -0
  33. data/app/views/layouts/documentation/_footer.html.haml +0 -0
  34. data/app/views/layouts/documentation/_head.html.haml +0 -0
  35. data/app/views/layouts/documentation/_header.html.haml +3 -0
  36. data/app/views/layouts/documentation/_search.html.haml +5 -0
  37. data/app/views/layouts/documentation/application.html.haml +22 -0
  38. data/config/locales/en.yml +62 -0
  39. data/config/routes.rb +11 -0
  40. data/db/migrate/20140711185212_create_documentation_pages.rb +10 -0
  41. data/db/migrate/20140724111844_create_nifty_attachments_table.rb +16 -0
  42. data/db/migrate/20140724114255_create_documentation_screenshots.rb +7 -0
  43. data/db/seeds.rb +15 -0
  44. data/doc/developers-guide/authorization.md +37 -0
  45. data/doc/developers-guide/building-views/accessing-pages.md +38 -0
  46. data/doc/developers-guide/building-views/helpers.md +105 -0
  47. data/doc/developers-guide/building-views/overview.md +3 -0
  48. data/doc/developers-guide/customization.md +9 -0
  49. data/doc/developers-guide/overview.md +20 -0
  50. data/doc/developers-guide/search-backends.md +17 -0
  51. data/doc/markdown/overview.md +37 -0
  52. data/lib/documentation.rb +20 -0
  53. data/lib/documentation/authorizer.rb +60 -0
  54. data/lib/documentation/config.rb +31 -0
  55. data/lib/documentation/engine.rb +29 -0
  56. data/lib/documentation/errors.rb +7 -0
  57. data/lib/documentation/generators/setup_generator.rb +17 -0
  58. data/lib/documentation/markdown_renderer.rb +62 -0
  59. data/lib/documentation/search_result.rb +84 -0
  60. data/lib/documentation/searchers/abstract.rb +47 -0
  61. data/lib/documentation/searchers/simple.rb +40 -0
  62. data/lib/documentation/version.rb +3 -0
  63. data/lib/documentation/view_helpers.rb +159 -0
  64. data/lib/tasks/documentation.rake +44 -0
  65. 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