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.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/Rakefile +2 -0
- data/bin/patricia +48 -0
- data/lib/patricia/app.rb +258 -0
- data/lib/patricia/assets/javascripts/app.js +170 -0
- data/lib/patricia/assets/javascripts/bootstrap.min.js +7 -0
- data/lib/patricia/assets/javascripts/jquery-1.11.0.min.js +4 -0
- data/lib/patricia/assets/javascripts/tooltips.js +10 -0
- data/lib/patricia/assets/stylesheets/app.css +22 -0
- data/lib/patricia/assets/stylesheets/bootstrap.min.css +7 -0
- data/lib/patricia/cli.rb +66 -0
- data/lib/patricia/patricia.rb +248 -0
- data/lib/patricia/version.rb +3 -0
- data/lib/patricia/views/404.haml +12 -0
- data/lib/patricia/views/application.haml +18 -0
- data/lib/patricia/views/search.haml +62 -0
- data/lib/patricia/views/wiki/page.haml +31 -0
- data/lib/patricia/views/wiki/welcome.md +101 -0
- data/lib/patricia.rb +3 -0
- data/patricia/.gitignore +22 -0
- data/patricia.gemspec +25 -0
- data/spec/app_spec.rb +195 -0
- data/spec/assets/javascripts/one.js +1 -0
- data/spec/assets/javascripts/two.js +1 -0
- data/spec/assets/stylesheets/green.css +3 -0
- data/spec/assets/stylesheets/red.css +3 -0
- data/spec/patricia_wiki_spec.rb +123 -0
- data/spec/random-test-wiki/amazing-animals/index.md +3 -0
- data/spec/random-test-wiki/amazing-animals/small/hamster.md +7 -0
- data/spec/random-test-wiki/amazing-animals/small/mouse.md +13 -0
- data/spec/random-test-wiki/amazing-animals/small/rat.md +7 -0
- data/spec/random-test-wiki/amazing-animals/tall/elephant.md +13 -0
- data/spec/random-test-wiki/amazing-animals/tall/giraffe.md +7 -0
- data/spec/random-test-wiki/colors/blue.md +9 -0
- data/spec/random-test-wiki/colors/bright-orange.org +19 -0
- data/spec/random-test-wiki/colors/dark-yellow.textile +14 -0
- data/spec/random-test-wiki/colors/file.txt +3 -0
- data/spec/random-test-wiki/colors/green.markdown +3 -0
- data/spec/random-test-wiki/colors/image.png +0 -0
- data/spec/random-test-wiki/colors/light-pink.rst +74 -0
- data/spec/random-test-wiki/colors/red.md +7 -0
- data/spec/random-test-wiki/overview.md +15 -0
- data/spec/test_helpers.rb +11 -0
- 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
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
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
|
data/lib/patricia/app.rb
ADDED
@@ -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
|
+
});
|