patricia 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +77 -0
  6. data/Rakefile +2 -0
  7. data/bin/patricia +48 -0
  8. data/lib/patricia/app.rb +258 -0
  9. data/lib/patricia/assets/javascripts/app.js +170 -0
  10. data/lib/patricia/assets/javascripts/bootstrap.min.js +7 -0
  11. data/lib/patricia/assets/javascripts/jquery-1.11.0.min.js +4 -0
  12. data/lib/patricia/assets/javascripts/tooltips.js +10 -0
  13. data/lib/patricia/assets/stylesheets/app.css +22 -0
  14. data/lib/patricia/assets/stylesheets/bootstrap.min.css +7 -0
  15. data/lib/patricia/cli.rb +66 -0
  16. data/lib/patricia/patricia.rb +248 -0
  17. data/lib/patricia/version.rb +3 -0
  18. data/lib/patricia/views/404.haml +12 -0
  19. data/lib/patricia/views/application.haml +18 -0
  20. data/lib/patricia/views/search.haml +62 -0
  21. data/lib/patricia/views/wiki/page.haml +31 -0
  22. data/lib/patricia/views/wiki/welcome.md +101 -0
  23. data/lib/patricia.rb +3 -0
  24. data/patricia/.gitignore +22 -0
  25. data/patricia.gemspec +25 -0
  26. data/spec/app_spec.rb +195 -0
  27. data/spec/assets/javascripts/one.js +1 -0
  28. data/spec/assets/javascripts/two.js +1 -0
  29. data/spec/assets/stylesheets/green.css +3 -0
  30. data/spec/assets/stylesheets/red.css +3 -0
  31. data/spec/patricia_wiki_spec.rb +123 -0
  32. data/spec/random-test-wiki/amazing-animals/index.md +3 -0
  33. data/spec/random-test-wiki/amazing-animals/small/hamster.md +7 -0
  34. data/spec/random-test-wiki/amazing-animals/small/mouse.md +13 -0
  35. data/spec/random-test-wiki/amazing-animals/small/rat.md +7 -0
  36. data/spec/random-test-wiki/amazing-animals/tall/elephant.md +13 -0
  37. data/spec/random-test-wiki/amazing-animals/tall/giraffe.md +7 -0
  38. data/spec/random-test-wiki/colors/blue.md +9 -0
  39. data/spec/random-test-wiki/colors/bright-orange.org +19 -0
  40. data/spec/random-test-wiki/colors/dark-yellow.textile +14 -0
  41. data/spec/random-test-wiki/colors/file.txt +3 -0
  42. data/spec/random-test-wiki/colors/green.markdown +3 -0
  43. data/spec/random-test-wiki/colors/image.png +0 -0
  44. data/spec/random-test-wiki/colors/light-pink.rst +74 -0
  45. data/spec/random-test-wiki/colors/red.md +7 -0
  46. data/spec/random-test-wiki/overview.md +15 -0
  47. data/spec/test_helpers.rb +11 -0
  48. metadata +143 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ef479d8eff067d6a70b52846c246763987cb72e8
4
+ data.tar.gz: 538feff310e1df73d5f52c79d6da00e054bd268a
5
+ SHA512:
6
+ metadata.gz: e22e0b7aa71306f9e4015a98acedc315adf3bf21a38141b1f0f073918c0db23aa202e5fc43fcb38e0731e79755068a5fc86a44d290bc521c5d15bafa74fb952e
7
+ data.tar.gz: d704365bc2c6e17f7966870f16f07905232899e4c185f4eeca0c7bfe623a49db1ba187fbbff1c4dd51960ff63261fc6dbcc4218ff46f11b9758419c0c248ad89
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ bin/app_config.yml
24
+ output
25
+ spec/output
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in patricia.gemspec
4
+ gemspec
5
+
6
+ gem 'kramdown'
7
+ gem 'org-ruby'
8
+ gem 'redcloth'
9
+ gem 'pandoc-ruby'
10
+ gem 'rack'
11
+ gem 'fileutils'
12
+ gem 'sinatra'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 nounch
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Patricia
2
+
3
+ Patricia is a simple markup-based Wiki. You give it a directory full of
4
+ markup files and it either
5
+
6
+ - serves the markup files as dynamic HTML pages with a hierarchical list of
7
+ all pages in a handy sidebar (including a search box) or
8
+ - generates a directory full of static HTML pages that you can serve
9
+ yourself.
10
+
11
+ The first option is great for writing/previewing your markup files. You
12
+ simply refresh the page and Patricia picks up the current state of the
13
+ file. However, it is also perfectly fine to use this built-in server to
14
+ serve your Wiki as a real website.
15
+
16
+ Files of different supported markup languages can be wildly mixed in the
17
+ markup directory, Patricia is able to figure it out as long as you use the
18
+ right file extension. Static files (images/PDFs/video/...) in the markup
19
+ directory are served as is, so you can link to them from any page and the
20
+ links will not break.
21
+
22
+ ## Installation
23
+
24
+ gem install patricia
25
+
26
+ ## Quick Start
27
+
28
+ Run
29
+
30
+ patricia /path/to/markup/dir -p 4321
31
+
32
+ and point your browser to `http://localhost:4321/`. Patricia will tell you
33
+ what to do from that point on.
34
+
35
+ ## Usage
36
+
37
+ To serve a directory full of markup files (`dir`), run this:
38
+
39
+ patricia /path/to/dir
40
+
41
+ To generate static HTML files in an output directory (`outputdir`), run
42
+ this:
43
+
44
+ patricia /path/to/dir /path/to/outputdir
45
+
46
+ To use your own stylesheets use the `--css` command line switch, to include
47
+ your own JavaScript, use `--js`. If you want to use a custom port, the
48
+ `--port` comes in handy.
49
+
50
+ Run `patricia --help` to see a list of all options.
51
+
52
+ ## Markup Languages
53
+
54
+ Patricia supports the following markup languages (supported file
55
+ extensions in parentheses):
56
+
57
+ - Markdown (`.md`, `.markdown`) via `kramdown`
58
+ - Org (`.org`) via `org-ruby`
59
+ - Textile (`.textile`) via `redcloth`
60
+ - reStructuredText (`.rst`, `.rest`, `restx`) via `pandoc-ruby`
61
+
62
+ To add support for a new language do this:
63
+
64
+ 0. *Write tests*
65
+ 1. Add a method with the markup language name to `Patricia::Converter`
66
+ (This function takes a markup string as input and returns a HTML string
67
+ as output)
68
+ 2. Associate this new method with the desired file extensions for the new
69
+ markup language by adding mappings to the
70
+ `Patricia::Converter.converters` hash.
71
+ 3. Add those file extensions to `PatriciaApp::App`'s
72
+ `settings.app_markdown_glob` in its `configure` block.
73
+ 4. Add the file extensions to the `settings.app_content_types` hash in
74
+ `PatriciaApp::App` and map them to the `text/plain` content type, if
75
+ possible, so that browsers will display them on a page instead of
76
+ prompting to download the file.
77
+ 5. *Make sure the tests pass*
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/patricia ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require 'rack'
5
+ require 'yaml'
6
+ require_relative '../lib/patricia'
7
+
8
+
9
+ config = {
10
+ :css_dir => CLI.options[:css],
11
+ :js_dir => CLI.options[:js],
12
+ :markup_dir => CLI.options[:markup_dir],
13
+ :tooltips => CLI.options[:tooltips],
14
+ }
15
+ config_file = File.dirname(__FILE__) + '/app_config.yml'
16
+ # The thread ensures that the config file is written before the Sinatra app
17
+ # starts.
18
+ config_writer = Thread.new do
19
+ begin
20
+ File.delete(config_file)
21
+ rescue
22
+ # Ignore.
23
+ end
24
+ File.open(config_file, 'w') do |f|
25
+ f.write(config.to_yaml)
26
+ end
27
+ end
28
+
29
+ # Wait for the config file to be written.
30
+ config_writer.join
31
+
32
+ if CLI.options[:output_dir]
33
+ css_dir = CLI.options[:css]
34
+ js_dir = CLI.options[:js]
35
+ css = Dir[css_dir + '/**/*.css'].collect { |x| x.sub(/#{css_dir}/, '') }
36
+ js = Dir[js_dir + '/**/*.js'].collect { |x| x.sub(/#{js_dir}/, '') }
37
+ patricia = Patricia::Wiki.new(CLI.options[:markup_dir], :output_dir =>
38
+ CLI.options[:output_dir], :css =>
39
+ css, :js => js)
40
+ patricia.render
41
+ else
42
+ require_relative '../lib/patricia/app'
43
+ app = Rack::Builder.new do
44
+ use PatriciaApp::App
45
+ run Proc.new { |env| [404, {'Content-Type' => 'text/html'}, ['404']] }
46
+ end
47
+ Rack::Handler::WEBrick.run app, :Port => CLI.options[:port] || 4321
48
+ end
@@ -0,0 +1,258 @@
1
+ require 'sinatra/base'
2
+ require 'yaml'
3
+
4
+
5
+ module PatriciaApp
6
+ class App < Sinatra::Base
7
+
8
+ def self.generate_routes
9
+ # CSS
10
+ settings.app_css.each do |dir|
11
+ get dir do
12
+ content_type 'text/css'
13
+ File.new(File.join(settings.app_css_dir, dir)).readlines
14
+ end
15
+ end
16
+
17
+ # JS
18
+ settings.app_js.each do |dir|
19
+ get dir do
20
+ content_type 'text/js'
21
+ File.new(File.join(settings.app_js_dir, dir)).readlines
22
+ end
23
+ end
24
+ end
25
+
26
+ configure do
27
+ begin
28
+ config_file = File.expand_path('../../../bin/app_config.yml',
29
+ __FILE__)
30
+ app_configs = YAML.load_file(config_file)
31
+ markup_dir = app_configs[:markup_dir]
32
+ set :environment, 'production'
33
+ set :app_markup_dir, markup_dir
34
+ set :app_markdown_glob, '.{md,markdown,org,textile,rst,rest,restx}'
35
+ set :app_css_path, '/patricia.css'
36
+ set :app_js_path, '/patricia.js'
37
+ set :app_public_folder, app_configs[:public_folder]
38
+ set :app_tooltips, app_configs[:tooltips]
39
+ # CSS
40
+ if app_configs[:css_dir] != nil
41
+ set :app_css_dir, app_configs[:css_dir]
42
+ css_paths = Dir[app_configs[:css_dir] + '/**/*.css']
43
+ .collect do |path|
44
+ path.gsub(/#{app_css_dir}/, '')
45
+ end
46
+ set :app_css, css_paths
47
+ else
48
+ set :app_css_dir, ''
49
+ set :app_css, []
50
+ end
51
+ # JS
52
+ if app_configs[:js_dir] != nil
53
+ set :app_js_dir, app_configs[:js_dir]
54
+ js_paths = Dir[app_configs[:js_dir] + '/**/*.js']
55
+ .collect do |path|
56
+ path.gsub(/#{app_js_dir}/, '')
57
+ end
58
+ set :app_js, js_paths
59
+ else
60
+ set :app_js_dir, ''
61
+ set :app_js, []
62
+ end
63
+ # Content types
64
+ set :app_content_types, {
65
+ 'png' => 'image/png',
66
+ 'jpg' => 'image/jpeg',
67
+ 'jpeg' => 'image/jpeg',
68
+ 'gif' => 'image/gif',
69
+ 'bmp' => 'image/bmp',
70
+ 'torrent' => 'image/x-bittorrent',
71
+ 'svg' => 'image/svg+xml',
72
+ 'xml' => 'application/xml',
73
+ 'atom' => 'application/atom-xml',
74
+ 'zip' => 'application/zip',
75
+ 'bz' => 'application/x-bzip',
76
+ 'bz2' => 'application/x-bzip2',
77
+ 'gzip' => 'application/gzip',
78
+ 'pdf' => 'application/pdf',
79
+ 'sh' => 'application/x-sh',
80
+ '7z' => 'application/x-7z-compressed',
81
+ 'swf' => 'application/x-shockwave-flash',
82
+ 'avi' => 'video/x-msvideo',
83
+ 'txt' => 'text/plain',
84
+ 'css' => 'text/css',
85
+ 'csv' => 'text/csv',
86
+ # Send markup source files
87
+ #
88
+ # One can argue if this is semantically correct (alternative:
89
+ # `plain/x-markdown;charset=UTF-8'), but as there is no official
90
+ # final specification and this solution comes with the highest
91
+ # browser-compatibility, it is preferred.
92
+ 'md' => 'text/plain',
93
+ 'markdown' => 'text/plain',
94
+ 'org' => 'text/plain',
95
+ 'textile' => 'text/plain',
96
+ 'rst' => 'text/plain',
97
+ 'rest' => 'text/plain',
98
+ 'rstx' => 'text/plain',
99
+ }
100
+ self.generate_routes
101
+ rescue
102
+ # Ignore.
103
+ end
104
+ end
105
+
106
+ helpers do
107
+ def build_toc
108
+ Patricia::Wiki.new(settings.app_markup_dir).build_toc
109
+ end
110
+
111
+ def generate_page_title
112
+ capitalize_all(File.basename(settings.app_markup_dir).gsub(/-/,
113
+ ' '))
114
+ end
115
+
116
+ def capitalize_all(string)
117
+ string.split.inject([]) do |result, token|
118
+ result << token[0].capitalize + token[1..-1]
119
+ end.join(' ')
120
+ end
121
+ end
122
+
123
+ get '/' do
124
+ @html = Patricia::Converter .to_html(File.dirname(__FILE__) +
125
+ '/views/wiki/welcome.md')
126
+ @toc = build_toc
127
+ @title = generate_page_title
128
+ @page_title = ''
129
+ @breadcrumb = ''
130
+ @stylesheets = settings.app_css
131
+ @javascripts = settings.app_js
132
+ haml 'wiki/page'.to_sym, :layout => :application
133
+ end
134
+
135
+ get %r{/404/?} do
136
+ haml '404'.to_sym, :layout => :application
137
+ end
138
+
139
+ get %r{/patricia/search/?} do
140
+ haml :search, :layout => :application
141
+ end
142
+
143
+ post %r{/patricia/search/?} do
144
+ @previous_search_query = params[:search_query] || ''
145
+ if params[:case_sensitive]
146
+ search_query = %r{#{params[:search_query]}}
147
+ @previous_search_query_was_sensitive = true
148
+ else
149
+ search_query = %r{#{params[:search_query]}}i
150
+ @previous_search_query_was_sensitive = false
151
+ end
152
+ # Search all markup files
153
+ @results = []
154
+ paths = Dir[File.join(settings.app_markup_dir, '/**/*' +
155
+ settings.app_markdown_glob)]
156
+ paths.each do |path|
157
+ if File.read(path)
158
+ p = path.gsub(/#{settings.app_markup_dir}/, '')
159
+ file_name = File.basename(p, '.*')
160
+ no_ext = File.join(File.dirname(p), file_name).sub(/^\.\//, '')
161
+ beautiful_file_name = capitalize_all(file_name.gsub(/-/, ' '))
162
+ beautiful_path = no_ext.split('/').collect do |s|
163
+ capitalize_all(s.gsub(/-/, ' '))
164
+ end.join(' > ')
165
+ content = File.read(path)
166
+ lines = content.split("\n").length
167
+ if content =~ search_query
168
+ @results << [beautiful_file_name, '/' + no_ext, beautiful_path,
169
+ lines]
170
+ end
171
+ end
172
+ end
173
+ haml :search, :layout => :application
174
+ end
175
+
176
+ get settings.app_css_path do
177
+ pwd = File.dirname(__FILE__)
178
+ css = ''
179
+ [
180
+ '/assets/stylesheets/bootstrap.min.css',
181
+ '/assets/stylesheets/app.css',
182
+ ].each do |path|
183
+ css << File.read(pwd + path) + "\n\n"
184
+ end
185
+ content_type 'text/css'
186
+ css
187
+ end
188
+
189
+ get settings.app_js_path do
190
+ pwd = File.dirname(__FILE__)
191
+ js = ''
192
+ files =
193
+ [
194
+ '/assets/javascripts/jquery-1.11.0.min.js',
195
+ '/assets/javascripts/bootstrap.min.js',
196
+ '/assets/javascripts/app.js',
197
+ ]
198
+ files << '/assets/javascripts/tooltips.js' if settings.app_tooltips
199
+ files.each do |path|
200
+ js << File.read(pwd + path) + "\n\n"
201
+ end
202
+ content_type 'text/javascript'
203
+ js
204
+ end
205
+
206
+ get %r{/(.*)} do |path|
207
+ # This assumes that there are not two or more files with the same
208
+ # file name, but different extensions (e.g. `/smart/green/frog.md'
209
+ # and `/smart/green/frog.markdown'). If so, it will chose the one it
210
+ # finds first.
211
+ # If this becomes a problem in the future, one could send a
212
+ # disambiguation page with links to each of the files for this
213
+ # request.
214
+ begin
215
+ file_path = Dir[settings.app_markup_dir + '/' + path +
216
+ settings.app_markdown_glob].first
217
+ @markup_url = file_path.gsub(/#{settings.app_markup_dir}/, '')
218
+ @html = Patricia::Converter.to_html(file_path)
219
+ @toc = build_toc
220
+ arrow = ' > '
221
+ @title = generate_page_title
222
+ @breadcrumb = capitalize_all(File.dirname(path).gsub(/\//, ' '))
223
+ breadcrumb_array = @breadcrumb.split
224
+ if breadcrumb_array.length >= 1 && breadcrumb_array.first !~ /\./
225
+ @breadcrumb = breadcrumb_array.inject([]) do |result, token|
226
+ result << capitalize_all(token.gsub(/-/, ' '))
227
+ end.join(arrow) + arrow
228
+ else
229
+ @breadcrumb = ''
230
+ end
231
+ @stylesheets = settings.app_css
232
+ @javascripts = settings.app_js
233
+ @page_title = capitalize_all(File.basename(path).gsub(/-/, ' '))
234
+ .split.join(' ')
235
+ haml 'wiki/page'.to_sym, :layout => :application
236
+ rescue
237
+ file_path = File.join(settings.app_markup_dir, path)
238
+ if File.exists?(file_path)
239
+ ext = File.extname(path).gsub(/\./, '')
240
+ # Try to guess the correct content type for popular content
241
+ # types.
242
+ content_type settings.app_content_types[ext] ||
243
+ 'application/octet-stream'
244
+ send_file(file_path)
245
+ else
246
+ redirect to('/404')
247
+ end
248
+ end
249
+ end
250
+
251
+ # `patricia.rb' already defines this, but redefining it here decouples
252
+ # the web app from the static file generator.
253
+ def _without_extension(path)
254
+ path.sub(/\..*$/, '')
255
+ end
256
+
257
+ end
258
+ end
@@ -0,0 +1,170 @@
1
+ $(document).ready(function() {
2
+
3
+ // Helpers
4
+
5
+ $.fn.elementText = function() {
6
+ var str = '';
7
+ $(this).contents().each(function() {
8
+ if ($(this).nodeType == 3) {
9
+ str += $(this).textContent || $(this).innerText || '';
10
+ }
11
+ });
12
+ return str;
13
+ };
14
+
15
+
16
+ // Sidebar expanding
17
+
18
+ var self = $(this);
19
+ self.sidebarExpanded = false;
20
+ self.originalSidebarStyle = {
21
+ 'width': '',
22
+ 'position': '',
23
+ 'box-shadow': '',
24
+ };
25
+ self.originalSidebarLeftMargin = parseInt($('#p-sidebar').css(
26
+ 'margin-left'));
27
+ self.sidebarLeftMarginAnimationWidth = 10;
28
+ self.sidebarExpandedStyle = {
29
+ 'width': '80%',
30
+ 'right': '20px',
31
+ 'position': 'absolute',
32
+ 'box-shadow': '0 0 0 9999px rgba(0, 0, 0, 0.3)',
33
+ };
34
+ self.sidebarWidthToggleOriginalText =
35
+ $('#p-sidebar-width-toggle').text();
36
+ self.sidebarWidthToggleExpandedText = 'Narrow sidebar';
37
+ self.previousScrollPosition = null;
38
+
39
+ self.toggleSidebarWidth = function() {
40
+ if (self.sidebarExpanded) {
41
+ self.sidebarExpanded = false;
42
+ $('#p-sidebar-width-toggle')
43
+ .text(self.sidebarWidthToggleOriginalText);
44
+ $('#p-sidebar').css(self.originalSidebarStyle);
45
+ // Scroll to the previous scroll position.
46
+ $('html').scrollTop(self.previousScrollPosition);
47
+ } else {
48
+ self.sidebarExpanded = true;
49
+ $('#p-sidebar-width-toggle')
50
+ .text(self.sidebarWidthToggleExpandedText);
51
+ $('#p-sidebar').css(self.sidebarExpandedStyle);
52
+ // Scroll to thetop of the page.
53
+ self.previousScrollPosition = $('html').scrollTop();
54
+ $('html').scrollTop(0);
55
+ }
56
+ };
57
+
58
+ $('#p-sidebar-width-toggle').click(function(e) {
59
+ e.preventDefault();
60
+ self.toggleSidebarWidth();
61
+ });
62
+
63
+ // Collapse the sidebar when clicking on a link while the sidebar is
64
+ // expanded. So when hitting the back button the sidebar will be
65
+ // collapsed. This is moreintuitive.
66
+ $('#toc a').click(function() {
67
+ if (self.sidebarExpanded) {
68
+ self.toggleSidebarWidth();
69
+ }
70
+ });
71
+
72
+ // Keybinding
73
+ $('body').append(
74
+ '\
75
+ <div id="help-box">\
76
+ <p class="text-right"><a class="btn btn-default btn-xs help-box-toggle"\
77
+ href="/">Close</a></p>\
78
+ <h2 class="text-center">Key bindings</h2>\
79
+ <br/>\
80
+ <p><code>?</code><span>: Toggle key bindings help</span></p>\
81
+ <p><code>w</code><span>: Toggle sidebar width</span></p>\
82
+ <p><code>s</code><span>: Select sidebar search box</span></p>\
83
+ <p><code>Esc</code><span>: Unselect sidebar search box</span></p>\
84
+ <p><code>p</code><span>: Go to page search page</span></p>\
85
+ </div>\
86
+ '
87
+ );
88
+ self.helpBox = $('#help-box');
89
+ self.helpBox.hide();
90
+ self.helpBox.css({
91
+ 'position': 'fixed',
92
+ 'background-color': '#FAFAFA',
93
+ 'color': '#686868',
94
+ 'top': '50%',
95
+ 'left': '50%',
96
+ 'width': '500px',
97
+ 'height': '400px',
98
+ 'margin-left': '-200px',
99
+ 'margin-top': '-300px',
100
+ 'z-index': '9999',
101
+ 'padding': '10px',
102
+ 'box-shadow': '0 0 0 9999px rgba(0, 0, 0, 0.2)',
103
+ 'border': '1px solid #ACACAC',
104
+ });
105
+ $('#p-sidebar-search-box').parent().find('a:first').parent().prepend('\
106
+ <a href="/" class="text-muted btn-xs help-box-toggle">Key bindings\
107
+ </a>');
108
+ $('.help-box-toggle').click(function(e) {
109
+ e.preventDefault();
110
+ self.helpBox.toggle();
111
+ })
112
+ $(document).keypress(function(e) {
113
+ // 119: w - Toggle sidebar width
114
+ // 115: s - Select sidebar search box
115
+ // 0: Esc - Unselect sidebar search box
116
+ // 112: s - Go to page search page
117
+ // 63: ? - Toggle key bindings help
118
+ if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA') {
119
+ if (e.which == 119) {
120
+ self.toggleSidebarWidth();
121
+ } else if (e.which == 115) {
122
+ $('#p-sidebar-search-box').select();
123
+ } else if (e.which == 112) {
124
+ window.location = $('#p-page-search-link').attr('href');
125
+ } else if (e.which == 63) {
126
+ $('#help-box').toggle();
127
+ }
128
+ } else if (e.target.id == 'sidebar-search-box') {
129
+ if (e.which == 0) {
130
+ $('#p-sidebar-search-box').blur();
131
+ }
132
+ }
133
+ });
134
+
135
+
136
+ // Skip `Key bindings' and `Widen sidebar' links when navigation using
137
+ // TAB.
138
+ $('#p-sidebar-search-box').parent().find('a').slice(0,2)
139
+ .each(function(index) {
140
+ $(this).attr('tabindex', '9999');
141
+ // Add tooltips
142
+ var title = '';
143
+ if (index == 0) {
144
+ title = 'Shortcut: "?"';
145
+ } else if (index == 1) {
146
+ title = 'Shortcut: "w"';
147
+ }
148
+ $(this).attr('title', title);
149
+ $(this).attr('data-toggle', 'tooltip');
150
+ $(this).attr('data-placement', 'top');
151
+ });
152
+
153
+
154
+ // Search
155
+
156
+ $('#p-sidebar-search-box').keyup(function() {
157
+ var filter = $(this).val().toLowerCase();
158
+
159
+ $('#p-sidebar li').each(function() {
160
+ var text = $(this).text().toLowerCase();
161
+
162
+ if (text.match(filter)) {
163
+ $(this).show()
164
+ } else {
165
+ $(this).hide();
166
+ }
167
+ });
168
+ });
169
+
170
+ });