patricia 0.0.1

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.
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
+ });