├── .gitignore
├── .travis.yml
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── config.rb
├── meta.json
└── source
├── fonts
├── icomoon.eot
├── icomoon.svg
├── icomoon.ttf
└── icomoon.woff
├── images
├── apod.jpg
├── earth.png
├── logo.png
├── mars.jpg
└── navbar.png
├── includes
└── _errors.md
├── index.md
├── javascripts
├── all.js
├── all_nosearch.js
├── app
│ ├── lang.js
│ ├── search.js
│ └── toc.js
└── lib
│ ├── energize.js
│ ├── jquery.highlight.js
│ ├── jquery.tocify.js
│ ├── jquery_ui.js
│ └── lunr.js
├── layouts
└── layout.erb
└── stylesheets
├── icon-font.scss
├── normalize.css
├── print.css.scss
├── screen.css.scss
├── syntax.css.scss.erb
└── variables.scss
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | coverage
6 | InstalledFiles
7 | lib/bundler/man
8 | pkg
9 | rdoc
10 | spec/reports
11 | test/tmp
12 | test/version_tmp
13 | tmp
14 | *.DS_STORE
15 | build/
16 | .cache
17 |
18 | # YARD artifacts
19 | .yardoc
20 | _yardoc
21 | doc/
22 | .idea/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 2.0.0
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:trusty
2 |
3 | RUN apt-get update
4 | RUN apt-get install -yq ruby ruby-dev build-essential
5 | RUN gem install --no-ri --no-rdoc bundler
6 | ADD Gemfile /app/Gemfile
7 | ADD Gemfile.lock /app/Gemfile.lock
8 | RUN cd /app; bundle install
9 | ADD . /app
10 | EXPOSE 4567
11 | WORKDIR /app
12 | CMD ["bundle", "exec", "middleman", "server"]
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # If you have OpenSSL installed, we recommend updating
2 | # the following line to use "https"
3 | source 'http://rubygems.org'
4 |
5 | gem "rouge", "1.7.2"
6 |
7 | gem "middleman", "~>3.3.0"
8 |
9 | # For syntax highlighting
10 | gem "middleman-syntax"
11 |
12 | # Plugin for middleman to generate Github pages
13 | gem 'middleman-gh-pages'
14 |
15 | # Live-reloading plugin
16 | gem "middleman-livereload", "~> 3.3.0"
17 |
18 | gem 'redcarpet', '~> 3.2.1'
19 |
20 | # For faster file watcher updates on Windows:
21 | gem "wdm", "~> 0.1.0", :platforms => [:mswin, :mingw]
22 |
23 | # Cross-templating language block fix for Ruby 1.8
24 | platforms :mri_18 do
25 | gem "ruby18_source_location"
26 | end
27 |
28 | gem "rake", "~> 10.4.0"
29 |
30 | gem 'therubyracer', :platforms => :ruby
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | activesupport (4.1.9)
5 | i18n (~> 0.6, >= 0.6.9)
6 | json (~> 1.7, >= 1.7.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.1)
9 | tzinfo (~> 1.1)
10 | celluloid (0.16.0)
11 | timers (~> 4.0.0)
12 | chunky_png (1.3.3)
13 | coffee-script (2.3.0)
14 | coffee-script-source
15 | execjs
16 | coffee-script-source (1.8.0)
17 | compass (1.0.1)
18 | chunky_png (~> 1.2)
19 | compass-core (~> 1.0.1)
20 | compass-import-once (~> 1.0.5)
21 | rb-fsevent (>= 0.9.3)
22 | rb-inotify (>= 0.9)
23 | sass (>= 3.3.13, < 3.5)
24 | compass-core (1.0.1)
25 | multi_json (~> 1.0)
26 | sass (>= 3.3.0, < 3.5)
27 | compass-import-once (1.0.5)
28 | sass (>= 3.2, < 3.5)
29 | em-websocket (0.5.1)
30 | eventmachine (>= 0.12.9)
31 | http_parser.rb (~> 0.6.0)
32 | erubis (2.7.0)
33 | eventmachine (1.0.4)
34 | execjs (2.2.2)
35 | ffi (1.9.6)
36 | haml (4.0.6)
37 | tilt
38 | hike (1.2.3)
39 | hitimes (1.2.2)
40 | hooks (0.4.0)
41 | uber (~> 0.0.4)
42 | http_parser.rb (0.6.0)
43 | i18n (0.6.11)
44 | json (1.8.2)
45 | kramdown (1.5.0)
46 | libv8 (3.16.14.7)
47 | listen (2.8.5)
48 | celluloid (>= 0.15.2)
49 | rb-fsevent (>= 0.9.3)
50 | rb-inotify (>= 0.9)
51 | middleman (3.3.7)
52 | coffee-script (~> 2.2)
53 | compass (>= 1.0.0, < 2.0.0)
54 | compass-import-once (= 1.0.5)
55 | execjs (~> 2.0)
56 | haml (>= 4.0.5)
57 | kramdown (~> 1.2)
58 | middleman-core (= 3.3.7)
59 | middleman-sprockets (>= 3.1.2)
60 | sass (>= 3.4.0, < 4.0)
61 | uglifier (~> 2.5)
62 | middleman-core (3.3.7)
63 | activesupport (~> 4.1.0)
64 | bundler (~> 1.1)
65 | erubis
66 | hooks (~> 0.3)
67 | i18n (~> 0.6.9)
68 | listen (>= 2.7.9, < 3.0)
69 | padrino-helpers (~> 0.12.3)
70 | rack (>= 1.4.5, < 2.0)
71 | rack-test (~> 0.6.2)
72 | thor (>= 0.15.2, < 2.0)
73 | tilt (~> 1.4.1, < 2.0)
74 | middleman-gh-pages (0.0.3)
75 | rake (> 0.9.3)
76 | middleman-livereload (3.3.4)
77 | em-websocket (~> 0.5.1)
78 | middleman-core (~> 3.2)
79 | rack-livereload (~> 0.3.15)
80 | middleman-sprockets (3.4.1)
81 | middleman-core (>= 3.3)
82 | sprockets (~> 2.12.1)
83 | sprockets-helpers (~> 1.1.0)
84 | sprockets-sass (~> 1.3.0)
85 | middleman-syntax (2.0.0)
86 | middleman-core (~> 3.2)
87 | rouge (~> 1.0)
88 | minitest (5.5.1)
89 | multi_json (1.10.1)
90 | padrino-helpers (0.12.4)
91 | i18n (~> 0.6, >= 0.6.7)
92 | padrino-support (= 0.12.4)
93 | tilt (~> 1.4.1)
94 | padrino-support (0.12.4)
95 | activesupport (>= 3.1)
96 | rack (1.6.0)
97 | rack-livereload (0.3.15)
98 | rack
99 | rack-test (0.6.3)
100 | rack (>= 1.0)
101 | rake (10.4.2)
102 | rb-fsevent (0.9.4)
103 | rb-inotify (0.9.5)
104 | ffi (>= 0.5.0)
105 | redcarpet (3.2.2)
106 | ref (1.0.5)
107 | rouge (1.7.2)
108 | ruby18_source_location (0.2)
109 | sass (3.4.9)
110 | sprockets (2.12.3)
111 | hike (~> 1.2)
112 | multi_json (~> 1.0)
113 | rack (~> 1.0)
114 | tilt (~> 1.1, != 1.3.0)
115 | sprockets-helpers (1.1.0)
116 | sprockets (~> 2.0)
117 | sprockets-sass (1.3.1)
118 | sprockets (~> 2.0)
119 | tilt (~> 1.1)
120 | therubyracer (0.12.1)
121 | libv8 (~> 3.16.14.0)
122 | ref
123 | thor (0.19.1)
124 | thread_safe (0.3.4)
125 | tilt (1.4.1)
126 | timers (4.0.1)
127 | hitimes
128 | tzinfo (1.2.2)
129 | thread_safe (~> 0.1)
130 | uber (0.0.13)
131 | uglifier (2.7.0)
132 | execjs (>= 0.3.0)
133 | json (>= 1.8.0)
134 |
135 | PLATFORMS
136 | ruby
137 |
138 | DEPENDENCIES
139 | middleman (~> 3.3.0)
140 | middleman-gh-pages
141 | middleman-livereload (~> 3.3.0)
142 | middleman-syntax
143 | rake (~> 10.4.0)
144 | redcarpet (~> 3.2.1)
145 | rouge (= 1.7.2)
146 | ruby18_source_location
147 | therubyracer
148 | wdm (~> 0.1.0)
149 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2008-2013 Concur Technologies, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may
4 | not use this file except in compliance with the License. You may obtain
5 | a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | License for the specific language governing permissions and limitations
13 | under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # api-docs
2 |
3 | nasa/api-docs repository contains the front-end for http://nasa.github.io/api-docs/. This site will be CNAME mapped to
4 | api.nasa.gov
5 |
6 | This project will contain API developer pages for available NASA public APIs and will additionally house web components to allow
7 | NASA branded API pages to be integrated with the GSA api.data.gov system.
8 |
9 | POC:
10 | Jason Duley jason.duley@nasa.gov
11 | Dan Hammer daniel.s.hammer@nasa.gov
12 |
13 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'middleman-gh-pages'
2 |
3 | task :default => [:build]
4 |
--------------------------------------------------------------------------------
/config.rb:
--------------------------------------------------------------------------------
1 | set :css_dir, 'stylesheets'
2 |
3 | set :js_dir, 'javascripts'
4 |
5 | set :images_dir, 'images'
6 |
7 | set :fonts_dir, 'fonts'
8 |
9 | set :markdown_engine, :redcarpet
10 |
11 | set :markdown, :fenced_code_blocks => true, :smartypants => true, :disable_indented_code_blocks => true, :prettify => true, :tables => true, :with_toc_data => true, :no_intra_emphasis => true
12 |
13 | # Activate the syntax highlighter
14 | activate :syntax
15 |
16 | # This is needed for Github pages, since they're hosted on a subdomain
17 | activate :relative_assets
18 | set :relative_links, true
19 |
20 | # Build-specific configuration
21 | configure :build do
22 | # For example, change the Compass output style for deployment
23 | activate :minify_css
24 |
25 | # Minify Javascript on build
26 | activate :minify_javascript
27 |
28 | # Enable cache buster
29 | # activate :asset_hash
30 |
31 | # Use relative URLs
32 | # activate :relative_assets
33 |
34 | # Or use a different image path
35 | # set :http_prefix, "/Content/images/"
36 | end
37 |
38 |
--------------------------------------------------------------------------------
/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "api-docs",
3 | "visibility": "public",
4 | "type": "GIT",
5 | "description": "repository to host api.nasa.gov website and associated API documentation.",
6 | "url": "https://github.com/nasa/api-docs",
7 | "repo": "nasa/api-docs",
8 | "missions":["OCIO", "GSA"],
9 | "categories": ["CTOF2F", "NASA"],
10 | "languages": ["Javascript", "HTML", "CSS", "Python"],
11 | "operating_systems": ["Linux", "Windows", "Unix", "Mac"],
12 | "poc": "Jason Duley",
13 | "poc_mbox": "jason.j.duley@nasa.gov",
14 | "license": "NASA Open Source Agreement Version 1.3",
15 | "software_types": ["website", "front-end", "gh-pages", "API"]
16 | }
17 |
--------------------------------------------------------------------------------
/source/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/fonts/icomoon.eot
--------------------------------------------------------------------------------
/source/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/source/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/source/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/fonts/icomoon.woff
--------------------------------------------------------------------------------
/source/images/apod.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/images/apod.jpg
--------------------------------------------------------------------------------
/source/images/earth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/images/earth.png
--------------------------------------------------------------------------------
/source/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/images/logo.png
--------------------------------------------------------------------------------
/source/images/mars.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/images/mars.jpg
--------------------------------------------------------------------------------
/source/images/navbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpaceRocks/api-docs/dd8e393f0e201199ff00dd3c02ae8a1839822cbc/source/images/navbar.png
--------------------------------------------------------------------------------
/source/includes/_errors.md:
--------------------------------------------------------------------------------
1 | # Errors
2 |
3 |
4 |
5 | The Kittn API uses the following error codes:
6 |
7 |
8 | Error Code | Meaning
9 | ---------- | -------
10 | 400 | Bad Request -- Your request sucks
11 | 401 | Unauthorized -- Your API key is wrong
12 | 403 | Forbidden -- The kitten requested is hidden for administrators only
13 | 404 | Not Found -- The specified kitten could not be found
14 | 405 | Method Not Allowed -- You tried to access a kitten with an invalid method
15 | 406 | Not Acceptable -- You requested a format that isn't json
16 | 410 | Gone -- The kitten requested has been removed from our servers
17 | 418 | I'm a teapot
18 | 429 | Too Many Requests -- You're requesting too many kittens! Slown down!
19 | 500 | Internal Server Error -- We had a problem with our server. Try again later.
20 | 503 | Service Unavailable -- We're temporarially offline for maintanance. Please try again later.
--------------------------------------------------------------------------------
/source/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: API Reference
3 |
4 | toc_footers:
5 | - Sign Up for a Developer Key
6 |
7 | search: true
8 | ---
9 |
10 | # Introduction
11 |
12 | NASA has **the best** data. Way better than NOAA. Most visitors to [nasa.gov](https://www.nasa.gov) websites are looking for images and videos. The objective of this API is to make NASA data, especially imagery, emminently accessible to application developers. The [api.nasa.gov] index is growing. These endpoints are just a sample of the endpoints that will soon be available. Stay tuned. In the meantime, if you have any suggestions (either about the APIs or documentation) please submit an issue at the [open repository for this documentation site](https://github.com/nasa/api-docs).
13 |
14 | # Apply for an API Key
15 |
16 | # Authentication
17 |
18 | **You do not need to authenticate** in order to explore the NASA data. However, if you will be intensively using the APIs to, say, support a mobile application, then you should sign up for an [api.data.gov developer key](https://api.data.gov/signup).
19 |
20 | ## Web Service Rate Limits
21 |
22 | Limits are placed on the number of API requests you may make using your API key. Rate limits may vary by service, but the defaults are:
23 |
24 | - Hourly Limit: 1,000 requests per hour
25 |
26 | For each API key, these limits are applied across all api.data.gov API requests. Exceeding these limits will lead to your API key being temporarily blocked from making further requests. The block will automatically be lifted by waiting an hour. If you need higher rate limits, contact us.
27 |
28 | ## DEMO_KEY Rate Limits
29 | In documentation examples, the special DEMO_KEY api key is used. This API key can be used for initially exploring APIs prior to signing up, but it has much lower rate limits, so you're encouraged to signup for your own API key if you plan to use the API (signup is quick and easy). The rate limits for the DEMO_KEY are:
30 |
31 | - Hourly Limit: 30 requests per IP address per hour
32 | - Daily Limit: 50 requests per IP address per day
33 |
34 | ## How Do I See My Current Usage?
35 |
36 | Your can check your current rate limit and usage details by inspecting the `X-RateLimit-Limit` and `X-RateLimit-Remaining` HTTP headers that are returned on every API response. For example, if an API has the default hourly limit of 1,000 request, after making 2 requests, you will receive this HTTP header in the response of the second request:
37 |
38 | `X-RateLimit-Remaining: 998`
39 |
40 | The hourly counters for your API key reset on a rolling basis.
41 |
42 | **Example**: If you made 500 requests at 10:15AM and 500 requests at 10:25AM, your API key would become temporarily blocked. This temporary block of your API key would cease at 11:15AM, at which point you could make 500 requests. At 11:25AM, you could then make another 500 requests.
43 |
44 |
45 |
48 |
49 | # Earth
50 |
51 | A recent industry [report](https://www.fgdc.gov/ngac/meetings/december-2014/ngac-landsat-economic-value-paper-2014-update.pdf) estimates that total annual value of $2.19 billion, far exceeding the multi-year total cost of building, launching, and managing Landsat satellites and sensors. The value is derived from consumer *use* of the data. There is no inherent value in idle data. The objective of this endpoint is to unlock the significant public investment in earth observation data. This open and documented API should dramatically reduce the transaction costs to engage with the imagery.The API is powered by Google Earth Engine, and currently only supports pan-sharpened Landsat 8 imagery.
52 |
53 | **Example image:**
54 |
55 | 
56 |
57 | Imagine the possibilities associated with this imagery, like [monitoring deforestation at 15-meter resolution](https://www.globalforestwatch.org) or assessing the damage from natural disasters. If you discover any cool applications, [please let us know](mailto:daniel.s.hammer@nasa.gov) so that we can showcase them on [open.nasa.gov](https://open.nasa.gov).
58 |
59 | ## Imagery
60 |
61 | This endpoint retrieves the Landsat 8 image for the supplied location and
62 | date. The response will include the date and URL to the image that is closest to the supplied date. The requested resource may not be available for the *exact* date in the request. You can retrieve a list of available resources through the [assets endpoint](/#assets).
63 |
64 | The cloud score is an optional calculation that returns the percentage of the queried image that is covered by clouds. If `False` is supplied to the `cloud_score` parameter, then no keypair is returned. If `True` is supplied, then a keypair will always be returned, even if the backend algorithm is not able to calculate a score. Note that this is a rough calculation, mainly used to filter out exceedingly cloudy images.
65 |
66 | ### HTTP Request
67 |
68 | `GET https://api.data.gov/nasa/planetary/earth/imagery`
69 |
70 | ### Query Parameters
71 |
72 | Parameter | Type | Default | Description
73 | --------- | --------- | ------- | -----------
74 | lat | float | n/a | Latitude
75 | lon | float | n/a | Longitude
76 | dim | float | 0.025 | width and height of image in degrees
77 | date | YYYY-MM-DD | *today* | date of image; if not supplied, then the most recent image (i.e., closest to today) is returned
78 | cloud_score | bool | False | calculate the percentage of the image covered by clouds
79 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
80 |
81 | ### Example query
82 | [`https://api.data.gov/nasa/planetary/earth/imagery?lon=100.75&lat=1.5&date=2014-02-01&cloud_score=True&api_key=DEMO_KEY`](https://api.data.gov/nasa/planetary/earth/imagery?lon=100.75&lat=1.5&date=2014-02-01&cloud_score=True&api_key=DEMO_KEY)
83 |
84 |
87 |
88 | ## Assets
89 |
90 | Hey, Charlie, when was the last time a NASA image was taken of my house? This endpoint retrieves the date-times and asset names for available imagery for a supplied location. The satellite passes over each point on earth roughly once every sixteen days. [This is an amazing visualization](http://earthobservatory.nasa.gov/Features/LandsatBigData/) of the acquisition pattern for Landsat 8 imagery. The objective of this endpoint is primarily to support the use of the [imagery endpoint](/#imagery).
91 |
92 | ### HTTP Request
93 |
94 | `GET https://api.data.gov/nasa/planetary/earth/assets`
95 |
96 | ### Query Parameters
97 |
98 | Parameter | Type | Default | Description
99 | --------- | --------- | ------- | -----------
100 | lat | float | n/a | Latitude
101 | lon | float | n/a | Longitude
102 | begin | YYYY-MM-DD | n/a | beginning of date range
103 | end | YYYY-MM-DD | *today* | end of date range
104 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
105 |
106 | ### Example query
107 | [`https://api.data.gov/nasa/planetary/earth/assets?lon=100.75&lat=1.5&begin=2014-02-01&api_key=DEMO_KEY`](https://api.data.gov/nasa/planetary/earth/assets?lon=100.75&lat=1.5&begin=2014-02-01&api_key=DEMO_KEY)
108 |
109 |
110 | # APOD
111 |
112 | One of the most popular websites at NASA is the [Astronomy Picture of the Day](http://apod.nasa.gov/apod/astropix.html). In fact, this website is one of the [most popular websites](https://analytics.usa.gov/demo/) across all federal agencies. It has the popular appeal of a Justin Bieber video. This endpoint structures the APOD imagery and associated metadata so that it can be repurposed for other applications. In addition, if the `concept_tags` parameter is set to `True`, then keywords derived from the image explanation are returned. These keywords could be used as auto-generated hashtags for twitter or instagram feeds; but generally help with discoverability of relevant imagery.
113 |
114 | **Example image:**
115 |
116 | 
117 |
118 | ### HTTP Request
119 |
120 | `GET https://api.data.gov/nasa/planetary/apod`
121 |
122 | ### Query Parameters
123 |
124 | Parameter | Type | Default | Description
125 | --------- | --------- | ------- | -----------
126 | date | YYYY-MM-DD | *today* | The date of the APOD image to retrieve
127 | concept_tags | bool | False | Return an ordered dictionary of concepts from the APOD explanation
128 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
129 |
130 | ### Example query
131 | [`https://api.data.gov/nasa/planetary/apod?concept_tags=True&api_key=DEMO_KEY`](https://api.data.gov/nasa/planetary/apod?concept_tags=True&api_key=DEMO_KEY)
132 |
133 | # Earth temperature anomalies
134 |
135 | There is no doubt that, on average, the earth is warming. However, the warming is spatially heterogenous. How much warmer (or cooler) is your hometown? This endpoint reports local temperature anomalies from the [Goddard Institute for Space Studies Surface Temperature Analysis](http://data.giss.nasa.gov/gistemp/) via the [New Scientist](http://www.newscientist.com/) web application to [explore global temperature anomalies](http://warmingworld.newscientistapps.com/). This endpoint restructures the query and response to correspond to other APIs on api.nasa.gov. The developer supplies a location and date range, and the returned object is a list of dictionaries that is ready for visualization in the [d3 framework](http://d3js.org/).
136 |
137 | ## Address
138 |
139 | The location can be specified as an address string.
140 |
141 | ### HTTP Request
142 |
143 | `GET https://api.data.gov/nasa/planetary/earth/temperature/address`
144 |
145 | ### Query Parameters
146 |
147 | Parameter | Type | Default | Description
148 | --------- | --------- | ------- | -----------
149 | text | string | n/a | Address string
150 | begin | int | 1880 | beginning year for date range, inclusive
151 | end | int | 2014 | end year for date range, inclusive
152 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
153 |
154 | ### Example query
155 | [`https://api.data.gov/nasa/planetary/earth/temperature/address?text=1800 F Street, NW, Washington DC&begin=1990`](https://api.data.gov/nasa/planetary/earth/temperature/address?text=1800 F Street, NW, Washington DC&begin=1990&api_key=DEMO_KEY)
156 |
157 | ## Coordinates
158 |
159 | Alternatively, you can supply the precise latitude and longitude as the location.
160 |
161 | ### HTTP Request
162 |
163 | `GET https://api.data.gov/planetary/earth/temperature/coords`
164 |
165 | ### Query Parameters
166 |
167 | Parameter | Type | Default | Description
168 | --------- | --------- | ------- | -----------
169 | lat | float | n/a | Latitude
170 | lon | float | n/a | Longitude
171 | begin | int | 1880 | beginning year for date range, inclusive
172 | end | int | 2014 | end year for date range, inclusive
173 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
174 |
175 | ### Example query
176 | [`https://api.data.gov/nasa/planetary/earth/temperature/coords?lat=100.3&lon=1.6&begin=1990&end=2005&api_key=DEMO_KEY`](https://api.data.gov/nasa/planetary/earth/temperature/coords?lat=100.3&lon=1.6&begin=1990&end=2005&api_key=DEMO_KEY)
177 |
178 | # Patents
179 |
180 | The NASA patent portfolio is available to benefit US citizens. Through partnerships and licensing agreements with industry, these patents ensure that NASA's investments in pioneering research find secondary uses that benefit the economy, create jobs, and improve quality of life. This endpoint provides structured, searchable developer access to NASA's patents that have been curated to support technology transfer.
181 |
182 | ### HTTP Request
183 |
184 | `GET https://api.data.gov/patents`
185 |
186 | ### Query Parameters
187 |
188 | Parameter | Type | Default | Description
189 | --------- | --------- | ------- | -----------
190 | query | string | None | Search text to filter results
191 | concept_tags | bool | False | Return an ordered dictionary of concepts from the patent abstract
192 | limit | int | *all* | number of patents to return
193 | api_key | string | DEMO_KEY | api.data.gov key for expanded usage
194 |
195 | ### Example query
196 | [`https://api.data.gov/nasa/patents/content?query=temperature&limit=5&api_key=DEMO_KEY`](https://api.data.gov/nasa/patents/content?query=temperature&limit=5&api_key=DEMO_KEY)
197 |
198 |
--------------------------------------------------------------------------------
/source/javascripts/all.js:
--------------------------------------------------------------------------------
1 | //= require_tree ./lib
2 | //= require_tree ./app
3 |
--------------------------------------------------------------------------------
/source/javascripts/all_nosearch.js:
--------------------------------------------------------------------------------
1 | //= require_tree ./lib
2 | //= require_tree ./app
3 | //= stub ./app/search.js
4 | //= stub ./lib/lunr.js
5 |
--------------------------------------------------------------------------------
/source/javascripts/app/lang.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | */
16 | (function (global) {
17 | var languages = [];
18 |
19 | global.setupLanguages = setupLanguages;
20 | global.activateLanguage = activateLanguage;
21 |
22 | function activateLanguage(language) {
23 | if (!language) return;
24 | if (language === "") return;
25 |
26 | $(".lang-selector a").removeClass('active');
27 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active');
28 | for (var i=0; i < languages.length; i++) {
29 | $(".highlight." + languages[i]).parent().hide();
30 | }
31 | $(".highlight." + language).parent().show();
32 |
33 | global.toc.calculateHeights();
34 |
35 | // scroll to the new location of the position
36 | if ($(window.location.hash).get(0)) {
37 | $(window.location.hash).get(0).scrollIntoView(true);
38 | }
39 | }
40 |
41 | // if a button is clicked, add the state to the history
42 | function pushURL(language) {
43 | if (!history) { return; }
44 | var hash = window.location.hash;
45 | if (hash) {
46 | hash = hash.replace(/^#+/, '');
47 | }
48 | history.pushState({}, '', '?' + language + '#' + hash);
49 |
50 | // save language as next default
51 | localStorage.setItem("language", language);
52 | }
53 |
54 | function setupLanguages(l) {
55 | var currentLanguage = l[0];
56 | var defaultLanguage = localStorage.getItem("language");
57 |
58 | languages = l;
59 |
60 | if ((location.search.substr(1) !== "") && (jQuery.inArray(location.search.substr(1), languages)) != -1) {
61 | // the language is in the URL, so use that language!
62 | activateLanguage(location.search.substr(1));
63 |
64 | localStorage.setItem("language", location.search.substr(1));
65 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
66 | // the language was the last selected one saved in localstorage, so use that language!
67 | activateLanguage(defaultLanguage);
68 | } else {
69 | // no language selected, so use the default
70 | activateLanguage(languages[0]);
71 | }
72 | }
73 |
74 | // if we click on a language tab, activate that language
75 | $(function() {
76 | $(".lang-selector a").on("click", function() {
77 | var language = $(this).data("language-name");
78 | pushURL(language);
79 | activateLanguage(language);
80 | return false;
81 | });
82 | window.onpopstate = function(event) {
83 | activateLanguage(window.location.search.substr(1));
84 | };
85 | });
86 | })(window);
87 |
--------------------------------------------------------------------------------
/source/javascripts/app/search.js:
--------------------------------------------------------------------------------
1 | (function (global) {
2 |
3 | var $global = $(global);
4 | var content, darkBox, searchResults;
5 | var highlightOpts = { element: 'span', className: 'search-highlight' };
6 |
7 | var index = new lunr.Index();
8 |
9 | index.ref('id');
10 | index.field('title', { boost: 10 });
11 | index.field('body');
12 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
13 |
14 | $(populate);
15 | $(bind);
16 |
17 | function populate() {
18 | $('h1, h2').each(function() {
19 | var title = $(this);
20 | var body = title.nextUntil('h1, h2');
21 | index.add({
22 | id: title.prop('id'),
23 | title: title.text(),
24 | body: body.text()
25 | });
26 | });
27 | }
28 |
29 | function bind() {
30 | content = $('.content');
31 | darkBox = $('.dark-box');
32 | searchResults = $('.search-results');
33 |
34 | $('#input-search').on('keyup', search);
35 | }
36 |
37 | function search(event) {
38 | unhighlight();
39 | searchResults.addClass('visible');
40 |
41 | // ESC clears the field
42 | if (event.keyCode === 27) this.value = '';
43 |
44 | if (this.value) {
45 | var results = index.search(this.value).filter(function(r) {
46 | return r.score > 0.0001;
47 | });
48 |
49 | if (results.length) {
50 | searchResults.empty();
51 | $.each(results, function (index, result) {
52 | searchResults.append("
");
53 | });
54 | highlight.call(this);
55 | } else {
56 | searchResults.html('');
57 | $('.search-results li').text('No Results Found for "' + this.value + '"');
58 | }
59 | } else {
60 | unhighlight();
61 | searchResults.removeClass('visible');
62 | }
63 | }
64 |
65 | function highlight() {
66 | if (this.value) content.highlight(this.value, highlightOpts);
67 | }
68 |
69 | function unhighlight() {
70 | content.unhighlight(highlightOpts);
71 | }
72 |
73 | })(window);
74 |
--------------------------------------------------------------------------------
/source/javascripts/app/toc.js:
--------------------------------------------------------------------------------
1 | (function (global) {
2 |
3 | var closeToc = function() {
4 | $(".tocify-wrapper").removeClass('open');
5 | $("#nav-button").removeClass('open');
6 | };
7 |
8 | var makeToc = function() {
9 | global.toc = $("#toc").tocify({
10 | selectors: 'h1, h2',
11 | extendPage: false,
12 | theme: 'none',
13 | smoothScroll: false,
14 | showEffectSpeed: 0,
15 | hideEffectSpeed: 180,
16 | ignoreSelector: '.toc-ignore',
17 | highlightOffset: 60,
18 | scrollTo: -1,
19 | scrollHistory: true,
20 | hashGenerator: function (text, element) {
21 | return element.prop('id');
22 | }
23 | }).data('toc-tocify');
24 |
25 | $("#nav-button").click(function() {
26 | $(".tocify-wrapper").toggleClass('open');
27 | $("#nav-button").toggleClass('open');
28 | return false;
29 | });
30 |
31 | $(".page-wrapper").click(closeToc);
32 | $(".tocify-item").click(closeToc);
33 | };
34 |
35 | // Hack to make already open sections to start opened,
36 | // instead of displaying an ugly animation
37 | function animate () {
38 | setTimeout(function() {
39 | toc.setOption('showEffectSpeed', 180);
40 | }, 50);
41 | }
42 |
43 | $(makeToc);
44 | $(animate);
45 |
46 | })(window);
47 |
48 |
--------------------------------------------------------------------------------
/source/javascripts/lib/energize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * energize.js v0.1.0
3 | *
4 | * Speeds up click events on mobile devices.
5 | * https://github.com/davidcalhoun/energize.js
6 | */
7 |
8 | (function() { // Sandbox
9 | /**
10 | * Don't add to non-touch devices, which don't need to be sped up
11 | */
12 | if(!('ontouchstart' in window)) return;
13 |
14 | var lastClick = {},
15 | isThresholdReached, touchstart, touchmove, touchend,
16 | click, closest;
17 |
18 | /**
19 | * isThresholdReached
20 | *
21 | * Compare touchstart with touchend xy coordinates,
22 | * and only fire simulated click event if the coordinates
23 | * are nearby. (don't want clicking to be confused with a swipe)
24 | */
25 | isThresholdReached = function(startXY, xy) {
26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
27 | };
28 |
29 | /**
30 | * touchstart
31 | *
32 | * Save xy coordinates when the user starts touching the screen
33 | */
34 | touchstart = function(e) {
35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
36 | this.threshold = false;
37 | };
38 |
39 | /**
40 | * touchmove
41 | *
42 | * Check if the user is scrolling past the threshold.
43 | * Have to check here because touchend will not always fire
44 | * on some tested devices (Kindle Fire?)
45 | */
46 | touchmove = function(e) {
47 | // NOOP if the threshold has already been reached
48 | if(this.threshold) return false;
49 |
50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
51 | };
52 |
53 | /**
54 | * touchend
55 | *
56 | * If the user didn't scroll past the threshold between
57 | * touchstart and touchend, fire a simulated click.
58 | *
59 | * (This will fire before a native click)
60 | */
61 | touchend = function(e) {
62 | // Don't fire a click if the user scrolled past the threshold
63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
64 | return;
65 | }
66 |
67 | /**
68 | * Create and fire a click event on the target element
69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent
70 | */
71 | var touch = e.changedTouches[0],
72 | evt = document.createEvent('MouseEvents');
73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click
75 | e.target.dispatchEvent(evt);
76 | };
77 |
78 | /**
79 | * click
80 | *
81 | * Because we've already fired a click event in touchend,
82 | * we need to listed for all native click events here
83 | * and suppress them as necessary.
84 | */
85 | click = function(e) {
86 | /**
87 | * Prevent ghost clicks by only allowing clicks we created
88 | * in the click event we fired (look for e.simulated)
89 | */
90 | var time = Date.now(),
91 | timeDiff = time - lastClick.time,
92 | x = e.clientX,
93 | y = e.clientY,
94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps
96 | nodeName = target.nodeName,
97 | isLink = nodeName === 'A',
98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
99 |
100 | lastClick.time = time;
101 | lastClick.x = x;
102 | lastClick.y = y;
103 |
104 | /**
105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
106 | * so we have to add more logic to determine the time of the last click. Not perfect...
107 | *
108 | * Older, simpler check: if((!e.simulated) || standAlone)
109 | */
110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
111 | e.preventDefault();
112 | e.stopPropagation();
113 | if(!standAlone) return false;
114 | }
115 |
116 | /**
117 | * Special logic for standalone web apps
118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
119 | */
120 | if(standAlone) {
121 | window.location = target.getAttribute("href");
122 | }
123 |
124 | /**
125 | * Add an energize-focus class to the targeted link (mimics :focus behavior)
126 | * TODO: test and/or remove? Does this work?
127 | */
128 | if(!target || !target.classList) return;
129 | target.classList.add("energize-focus");
130 | window.setTimeout(function(){
131 | target.classList.remove("energize-focus");
132 | }, 150);
133 | };
134 |
135 | /**
136 | * closest
137 | * @param {HTMLElement} node current node to start searching from.
138 | * @param {string} tagName the (uppercase) name of the tag you're looking for.
139 | *
140 | * Find the closest ancestor tag of a given node.
141 | *
142 | * Starts at node and goes up the DOM tree looking for a
143 | * matching nodeName, continuing until hitting document.body
144 | */
145 | closest = function(node, tagName){
146 | var curNode = node;
147 |
148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after
149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
150 | curNode = curNode.parentNode; // not found, so keep going up
151 | }
152 |
153 | return null; // not found
154 | };
155 |
156 | /**
157 | * Add all delegated event listeners
158 | *
159 | * All the events we care about bubble up to document,
160 | * so we can take advantage of event delegation.
161 | *
162 | * Note: no need to wait for DOMContentLoaded here
163 | */
164 | document.addEventListener('touchstart', touchstart, false);
165 | document.addEventListener('touchmove', touchmove, false);
166 | document.addEventListener('touchend', touchend, false);
167 | document.addEventListener('click', click, true); // TODO: why does this use capture?
168 |
169 | })();
--------------------------------------------------------------------------------
/source/javascripts/lib/jquery.highlight.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Highlight plugin
3 | *
4 | * Based on highlight v3 by Johann Burkard
5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
6 | *
7 | * Code a little bit refactored and cleaned (in my humble opinion).
8 | * Most important changes:
9 | * - has an option to highlight only entire words (wordsOnly - false by default),
10 | * - has an option to be case sensitive (caseSensitive - false by default)
11 | * - highlight element tag and class names can be specified in options
12 | *
13 | * Usage:
14 | * // wrap every occurrance of text 'lorem' in content
15 | * // with (default options)
16 | * $('#content').highlight('lorem');
17 | *
18 | * // search for and highlight more terms at once
19 | * // so you can save some time on traversing DOM
20 | * $('#content').highlight(['lorem', 'ipsum']);
21 | * $('#content').highlight('lorem ipsum');
22 | *
23 | * // search only for entire word 'lorem'
24 | * $('#content').highlight('lorem', { wordsOnly: true });
25 | *
26 | * // don't ignore case during search of term 'lorem'
27 | * $('#content').highlight('lorem', { caseSensitive: true });
28 | *
29 | * // wrap every occurrance of term 'ipsum' in content
30 | * // with
31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' });
32 | *
33 | * // remove default highlight
34 | * $('#content').unhighlight();
35 | *
36 | * // remove custom highlight
37 | * $('#content').unhighlight({ element: 'em', className: 'important' });
38 | *
39 | *
40 | * Copyright (c) 2009 Bartek Szopka
41 | *
42 | * Licensed under MIT license.
43 | *
44 | */
45 |
46 | jQuery.extend({
47 | highlight: function (node, re, nodeName, className) {
48 | if (node.nodeType === 3) {
49 | var match = node.data.match(re);
50 | if (match) {
51 | var highlight = document.createElement(nodeName || 'span');
52 | highlight.className = className || 'highlight';
53 | var wordNode = node.splitText(match.index);
54 | wordNode.splitText(match[0].length);
55 | var wordClone = wordNode.cloneNode(true);
56 | highlight.appendChild(wordClone);
57 | wordNode.parentNode.replaceChild(highlight, wordNode);
58 | return 1; //skip added node in parent
59 | }
60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
63 | for (var i = 0; i < node.childNodes.length; i++) {
64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
65 | }
66 | }
67 | return 0;
68 | }
69 | });
70 |
71 | jQuery.fn.unhighlight = function (options) {
72 | var settings = { className: 'highlight', element: 'span' };
73 | jQuery.extend(settings, options);
74 |
75 | return this.find(settings.element + "." + settings.className).each(function () {
76 | var parent = this.parentNode;
77 | parent.replaceChild(this.firstChild, this);
78 | parent.normalize();
79 | }).end();
80 | };
81 |
82 | jQuery.fn.highlight = function (words, options) {
83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
84 | jQuery.extend(settings, options);
85 |
86 | if (words.constructor === String) {
87 | words = [words];
88 | }
89 | words = jQuery.grep(words, function(word, i){
90 | return word != '';
91 | });
92 | words = jQuery.map(words, function(word, i) {
93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
94 | });
95 | if (words.length == 0) { return this; };
96 |
97 | var flag = settings.caseSensitive ? "" : "i";
98 | var pattern = "(" + words.join("|") + ")";
99 | if (settings.wordsOnly) {
100 | pattern = "\\b" + pattern + "\\b";
101 | }
102 | var re = new RegExp(pattern, flag);
103 |
104 | return this.each(function () {
105 | jQuery.highlight(this, re, settings.element, settings.className);
106 | });
107 | };
108 |
109 |
--------------------------------------------------------------------------------
/source/javascripts/lib/jquery.tocify.js:
--------------------------------------------------------------------------------
1 | //= require ./jquery_ui
2 | /* jquery Tocify - v1.8.0 - 2013-09-16
3 | * http://www.gregfranko.com/jquery.tocify.js/
4 | * Copyright (c) 2013 Greg Franko; Licensed MIT
5 | * Modified lightly by Robert Lord to fix a bug I found,
6 | * and also so it adds ids to headers
7 | * also because I want height caching, since the
8 | * height lookup for h1s and h2s was causing serious
9 | * lag spikes below 30 fps */
10 |
11 | // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE.
12 | (function(tocify) {
13 |
14 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
15 | "use strict";
16 |
17 | // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
18 | tocify(window.jQuery, window, document);
19 |
20 | }
21 |
22 | // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript.
23 | (function($, window, document, undefined) {
24 |
25 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
26 | "use strict";
27 |
28 | var tocClassName = "tocify",
29 | tocClass = "." + tocClassName,
30 | tocFocusClassName = "tocify-focus",
31 | tocHoverClassName = "tocify-hover",
32 | hideTocClassName = "tocify-hide",
33 | hideTocClass = "." + hideTocClassName,
34 | headerClassName = "tocify-header",
35 | headerClass = "." + headerClassName,
36 | subheaderClassName = "tocify-subheader",
37 | subheaderClass = "." + subheaderClassName,
38 | itemClassName = "tocify-item",
39 | itemClass = "." + itemClassName,
40 | extendPageClassName = "tocify-extend-page",
41 | extendPageClass = "." + extendPageClassName;
42 |
43 | // Calling the jQueryUI Widget Factory Method
44 | $.widget("toc.tocify", {
45 |
46 | //Plugin version
47 | version: "1.8.0",
48 |
49 | // These options will be used as defaults
50 | options: {
51 |
52 | // **context**: Accepts String: Any jQuery selector
53 | // The container element that holds all of the elements used to generate the table of contents
54 | context: "body",
55 |
56 | // **ignoreSelector**: Accepts String: Any jQuery selector
57 | // A selector to any element that would be matched by selectors that you wish to be ignored
58 | ignoreSelector: null,
59 |
60 | // **selectors**: Accepts an Array of Strings: Any jQuery selectors
61 | // The element's used to generate the table of contents. The order is very important since it will determine the table of content's nesting structure
62 | selectors: "h1, h2, h3",
63 |
64 | // **showAndHide**: Accepts a boolean: true or false
65 | // Used to determine if elements should be shown and hidden
66 | showAndHide: true,
67 |
68 | // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown"
69 | // Used to display any of the table of contents nested items
70 | showEffect: "slideDown",
71 |
72 | // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
73 | // The time duration of the show animation
74 | showEffectSpeed: "medium",
75 |
76 | // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp"
77 | // Used to hide any of the table of contents nested items
78 | hideEffect: "slideUp",
79 |
80 | // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
81 | // The time duration of the hide animation
82 | hideEffectSpeed: "medium",
83 |
84 | // **smoothScroll**: Accepts a boolean: true or false
85 | // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page
86 | smoothScroll: true,
87 |
88 | // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
89 | // The time duration of the smoothScroll animation
90 | smoothScrollSpeed: "medium",
91 |
92 | // **scrollTo**: Accepts Number (pixels)
93 | // The amount of space between the top of page and the selected table of contents item after the page has been scrolled
94 | scrollTo: 0,
95 |
96 | // **showAndHideOnScroll**: Accepts a boolean: true or false
97 | // Determines if table of contents nested items should be shown and hidden while scrolling
98 | showAndHideOnScroll: true,
99 |
100 | // **highlightOnScroll**: Accepts a boolean: true or false
101 | // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling
102 | highlightOnScroll: true,
103 |
104 | // **highlightOffset**: Accepts a number
105 | // The offset distance in pixels to trigger the next active table of contents item
106 | highlightOffset: 40,
107 |
108 | // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none"
109 | // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents
110 | theme: "bootstrap",
111 |
112 | // **extendPage**: Accepts a boolean: true or false
113 | // If a user scrolls to the bottom of the page and the page is not tall enough to scroll to the last table of contents item, then the page height is increased
114 | extendPage: true,
115 |
116 | // **extendPageOffset**: Accepts a number: pixels
117 | // How close to the bottom of the page a user must scroll before the page is extended
118 | extendPageOffset: 100,
119 |
120 | // **history**: Accepts a boolean: true or false
121 | // Adds a hash to the page url to maintain history
122 | history: true,
123 |
124 | // **scrollHistory**: Accepts a boolean: true or false
125 | // Adds a hash to the page url, to maintain history, when scrolling to a TOC item
126 | scrollHistory: false,
127 |
128 | // **hashGenerator**: How the hash value (the anchor segment of the URL, following the
129 | // # character) will be generated.
130 | //
131 | // "compact" (default) - #CompressesEverythingTogether
132 | // "pretty" - #looks-like-a-nice-url-and-is-easily-readable
133 | // function(text, element){} - Your own hash generation function that accepts the text as an
134 | // argument, and returns the hash value.
135 | hashGenerator: "compact",
136 |
137 | // **highlightDefault**: Accepts a boolean: true or false
138 | // Set's the first TOC item as active if no other TOC item is active.
139 | highlightDefault: true
140 |
141 | },
142 |
143 | // _Create
144 | // -------
145 | // Constructs the plugin. Only called once.
146 | _create: function() {
147 |
148 | var self = this;
149 |
150 | self.tocifyWrapper = $('.tocify-wrapper');
151 | self.extendPageScroll = true;
152 |
153 | // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings)
154 | self.items = [];
155 |
156 | // Generates the HTML for the dynamic table of contents
157 | self._generateToc();
158 |
159 | // Caches heights and anchors
160 | self.cachedHeights = [],
161 | self.cachedAnchors = [];
162 |
163 | // Adds CSS classes to the newly generated table of contents HTML
164 | self._addCSSClasses();
165 |
166 | self.webkit = (function() {
167 |
168 | for(var prop in window) {
169 |
170 | if(prop) {
171 |
172 | if(prop.toLowerCase().indexOf("webkit") !== -1) {
173 |
174 | return true;
175 |
176 | }
177 |
178 | }
179 |
180 | }
181 |
182 | return false;
183 |
184 | }());
185 |
186 | // Adds jQuery event handlers to the newly generated table of contents
187 | self._setEventHandlers();
188 |
189 | // Binding to the Window load event to make sure the correct scrollTop is calculated
190 | $(window).load(function() {
191 |
192 | // Sets the active TOC item
193 | self._setActiveElement(true);
194 |
195 | // Once all animations on the page are complete, this callback function will be called
196 | $("html, body").promise().done(function() {
197 |
198 | setTimeout(function() {
199 |
200 | self.extendPageScroll = false;
201 |
202 | },0);
203 |
204 | });
205 |
206 | });
207 |
208 | },
209 |
210 | // _generateToc
211 | // ------------
212 | // Generates the HTML for the dynamic table of contents
213 | _generateToc: function() {
214 |
215 | // _Local variables_
216 |
217 | // Stores the plugin context in the self variable
218 | var self = this,
219 |
220 | // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above
221 | firstElem,
222 |
223 | // Instantiated variable that will store the top level newly created unordered list DOM element
224 | ul,
225 | ignoreSelector = self.options.ignoreSelector;
226 |
227 | // If the selectors option has a comma within the string
228 | if(this.options.selectors.indexOf(",") !== -1) {
229 |
230 | // Grabs the first selector from the string
231 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(",")));
232 |
233 | }
234 |
235 | // If the selectors option does not have a comman within the string
236 | else {
237 |
238 | // Grabs the first selector from the string and makes sure there are no spaces
239 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,""));
240 |
241 | }
242 |
243 | if(!firstElem.length) {
244 |
245 | self.element.addClass(hideTocClassName);
246 |
247 | return;
248 |
249 | }
250 |
251 | self.element.addClass(tocClassName);
252 |
253 | // Loops through each top level selector
254 | firstElem.each(function(index) {
255 |
256 | //If the element matches the ignoreSelector then we skip it
257 | if($(this).is(ignoreSelector)) {
258 | return;
259 | }
260 |
261 | // Creates an unordered list HTML element and adds a dynamic ID and standard class name
262 | ul = $("
", {
263 | "id": headerClassName + index,
264 | "class": headerClassName
265 | }).
266 |
267 | // Appends a top level list item HTML element to the previously created HTML header
268 | append(self._nestElements($(this), index));
269 |
270 | // Add the created unordered list element to the HTML element calling the plugin
271 | self.element.append(ul);
272 |
273 | // Finds all of the HTML tags between the header and subheader elements
274 | $(this).nextUntil(this.nodeName.toLowerCase()).each(function() {
275 |
276 | // If there are no nested subheader elemements
277 | if($(this).find(self.options.selectors).length === 0) {
278 |
279 | // Loops through all of the subheader elements
280 | $(this).filter(self.options.selectors).each(function() {
281 |
282 | //If the element matches the ignoreSelector then we skip it
283 | if($(this).is(ignoreSelector)) {
284 | return;
285 | }
286 |
287 | self._appendSubheaders.call(this, self, ul);
288 |
289 | });
290 |
291 | }
292 |
293 | // If there are nested subheader elements
294 | else {
295 |
296 | // Loops through all of the subheader elements
297 | $(this).find(self.options.selectors).each(function() {
298 |
299 | //If the element matches the ignoreSelector then we skip it
300 | if($(this).is(ignoreSelector)) {
301 | return;
302 | }
303 |
304 | self._appendSubheaders.call(this, self, ul);
305 |
306 | });
307 |
308 | }
309 |
310 | });
311 |
312 | });
313 |
314 | },
315 |
316 | _setActiveElement: function(pageload) {
317 |
318 | var self = this,
319 |
320 | hash = window.location.hash.substring(1),
321 |
322 | elem = self.element.find("li[data-unique='" + hash + "']");
323 |
324 | if(hash.length) {
325 |
326 | // Removes highlighting from all of the list item's
327 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
328 |
329 | // Highlights the current list item that was clicked
330 | elem.addClass(self.focusClass);
331 |
332 | // If the showAndHide option is true
333 | if(self.options.showAndHide) {
334 |
335 | // Triggers the click event on the currently focused TOC item
336 | elem.click();
337 |
338 | }
339 |
340 | }
341 |
342 | else {
343 |
344 | // Removes highlighting from all of the list item's
345 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
346 |
347 | if(!hash.length && pageload && self.options.highlightDefault) {
348 |
349 | // Highlights the first TOC item if no other items are highlighted
350 | self.element.find(itemClass).first().addClass(self.focusClass);
351 |
352 | }
353 |
354 | }
355 |
356 | return self;
357 |
358 | },
359 |
360 | // _nestElements
361 | // -------------
362 | // Helps create the table of contents list by appending nested list items
363 | _nestElements: function(self, index) {
364 |
365 | var arr, item, hashValue;
366 |
367 | arr = $.grep(this.items, function (item) {
368 |
369 | return item === self.text();
370 |
371 | });
372 |
373 | // If there is already a duplicate TOC item
374 | if(arr.length) {
375 |
376 | // Adds the current TOC item text and index (for slight randomization) to the internal array
377 | this.items.push(self.text() + index);
378 |
379 | }
380 |
381 | // If there not a duplicate TOC item
382 | else {
383 |
384 | // Adds the current TOC item text to the internal array
385 | this.items.push(self.text());
386 |
387 | }
388 |
389 | hashValue = this._generateHashValue(arr, self, index);
390 |
391 | // ADDED BY ROBERT
392 | // actually add the hash value to the element's id
393 | // self.attr("id", "link-" + hashValue);
394 |
395 | // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
396 | item = $("", {
397 |
398 | // Sets a common class name to the list item
399 | "class": itemClassName,
400 |
401 | "data-unique": hashValue
402 |
403 | }).append($("", {
404 |
405 | "text": self.text()
406 |
407 | }));
408 |
409 | // Adds an HTML anchor tag before the currently traversed HTML element
410 | self.before($("", {
411 |
412 | // Sets a name attribute on the anchor tag to the text of the currently traversed HTML element (also making sure that all whitespace is replaced with an underscore)
413 | "name": hashValue,
414 |
415 | "data-unique": hashValue
416 |
417 | }));
418 |
419 | return item;
420 |
421 | },
422 |
423 | // _generateHashValue
424 | // ------------------
425 | // Generates the hash value that will be used to refer to each item.
426 | _generateHashValue: function(arr, self, index) {
427 |
428 | var hashValue = "",
429 | hashGeneratorOption = this.options.hashGenerator;
430 |
431 | if (hashGeneratorOption === "pretty") {
432 | // remove weird characters
433 |
434 |
435 | // prettify the text
436 | hashValue = self.text().toLowerCase().replace(/\s/g, "-");
437 |
438 | // ADDED BY ROBERT
439 | // remove weird characters
440 | hashValue = hashValue.replace(/[^\x00-\x7F]/g, "");
441 |
442 | // fix double hyphens
443 | while (hashValue.indexOf("--") > -1) {
444 | hashValue = hashValue.replace(/--/g, "-");
445 | }
446 |
447 | // fix colon-space instances
448 | while (hashValue.indexOf(":-") > -1) {
449 | hashValue = hashValue.replace(/:-/g, "-");
450 | }
451 |
452 | } else if (typeof hashGeneratorOption === "function") {
453 |
454 | // call the function
455 | hashValue = hashGeneratorOption(self.text(), self);
456 |
457 | } else {
458 |
459 | // compact - the default
460 | hashValue = self.text().replace(/\s/g, "");
461 |
462 | }
463 |
464 | // add the index if we need to
465 | if (arr.length) { hashValue += ""+index; }
466 |
467 | // return the value
468 | return hashValue;
469 |
470 | },
471 |
472 | // _appendElements
473 | // ---------------
474 | // Helps create the table of contents list by appending subheader elements
475 |
476 | _appendSubheaders: function(self, ul) {
477 |
478 | // The current element index
479 | var index = $(this).index(self.options.selectors),
480 |
481 | // Finds the previous header DOM element
482 | previousHeader = $(self.options.selectors).eq(index - 1),
483 |
484 | currentTagName = +$(this).prop("tagName").charAt(1),
485 |
486 | previousTagName = +previousHeader.prop("tagName").charAt(1),
487 |
488 | lastSubheader;
489 |
490 | // If the current header DOM element is smaller than the previous header DOM element or the first subheader
491 | if(currentTagName < previousTagName) {
492 |
493 | // Selects the last unordered list HTML found within the HTML element calling the plugin
494 | self.element.find(subheaderClass + "[data-tag=" + currentTagName + "]").last().append(self._nestElements($(this), index));
495 |
496 | }
497 |
498 | // If the current header DOM element is the same type of header(eg. h4) as the previous header DOM element
499 | else if(currentTagName === previousTagName) {
500 |
501 | ul.find(itemClass).last().after(self._nestElements($(this), index));
502 |
503 | }
504 |
505 | else {
506 |
507 | // Selects the last unordered list HTML found within the HTML element calling the plugin
508 | ul.find(itemClass).last().
509 |
510 | // Appends an unorderedList HTML element to the dynamic `unorderedList` variable and sets a common class name
511 | after($("
", {
512 |
513 | "class": subheaderClassName,
514 |
515 | "data-tag": currentTagName
516 |
517 | })).next(subheaderClass).
518 |
519 | // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
520 | append(self._nestElements($(this), index));
521 | }
522 |
523 | },
524 |
525 | // _setEventHandlers
526 | // ----------------
527 | // Adds jQuery event handlers to the newly generated table of contents
528 | _setEventHandlers: function() {
529 |
530 | // _Local variables_
531 |
532 | // Stores the plugin context in the self variable
533 | var self = this,
534 |
535 | // Instantiates a new variable that will be used to hold a specific element's context
536 | $self,
537 |
538 | // Instantiates a new variable that will be used to determine the smoothScroll animation time duration
539 | duration;
540 |
541 | // Event delegation that looks for any clicks on list item elements inside of the HTML element calling the plugin
542 | this.element.on("click.tocify", "li", function(event) {
543 |
544 | if(self.options.history) {
545 |
546 | window.location.hash = $(this).attr("data-unique");
547 |
548 | }
549 |
550 | // Removes highlighting from all of the list item's
551 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
552 |
553 | // Highlights the current list item that was clicked
554 | $(this).addClass(self.focusClass);
555 |
556 | // If the showAndHide option is true
557 | if(self.options.showAndHide) {
558 |
559 | var elem = $('li[data-unique="' + $(this).attr("data-unique") + '"]');
560 |
561 | self._triggerShow(elem);
562 |
563 | }
564 |
565 | self._scrollTo($(this));
566 |
567 | });
568 |
569 | // Mouseenter and Mouseleave event handlers for the list item's within the HTML element calling the plugin
570 | this.element.find("li").on({
571 |
572 | // Mouseenter event handler
573 | "mouseenter.tocify": function() {
574 |
575 | // Adds a hover CSS class to the current list item
576 | $(this).addClass(self.hoverClass);
577 |
578 | // Makes sure the cursor is set to the pointer icon
579 | $(this).css("cursor", "pointer");
580 |
581 | },
582 |
583 | // Mouseleave event handler
584 | "mouseleave.tocify": function() {
585 |
586 | if(self.options.theme !== "bootstrap") {
587 |
588 | // Removes the hover CSS class from the current list item
589 | $(this).removeClass(self.hoverClass);
590 |
591 | }
592 |
593 | }
594 | });
595 |
596 | // Reset height cache on scroll
597 |
598 | $(window).on('resize', function() {
599 | self.calculateHeights();
600 | });
601 |
602 | // Window scroll event handler
603 | $(window).on("scroll.tocify", function() {
604 |
605 | // Once all animations on the page are complete, this callback function will be called
606 | $("html, body").promise().done(function() {
607 |
608 | // Local variables
609 |
610 | // Stores how far the user has scrolled
611 | var winScrollTop = $(window).scrollTop(),
612 |
613 | // Stores the height of the window
614 | winHeight = $(window).height(),
615 |
616 | // Stores the height of the document
617 | docHeight = $(document).height(),
618 |
619 | scrollHeight = $("body")[0].scrollHeight,
620 |
621 | // Instantiates a variable that will be used to hold a selected HTML element
622 | elem,
623 |
624 | lastElem,
625 |
626 | lastElemOffset,
627 |
628 | currentElem;
629 |
630 | if(self.options.extendPage) {
631 |
632 | // If the user has scrolled to the bottom of the page and the last toc item is not focused
633 | if((self.webkit && winScrollTop >= scrollHeight - winHeight - self.options.extendPageOffset) || (!self.webkit && winHeight + winScrollTop > docHeight - self.options.extendPageOffset)) {
634 |
635 | if(!$(extendPageClass).length) {
636 |
637 | lastElem = $('div[data-unique="' + $(itemClass).last().attr("data-unique") + '"]');
638 |
639 | if(!lastElem.length) return;
640 |
641 | // Gets the top offset of the page header that is linked to the last toc item
642 | lastElemOffset = lastElem.offset().top;
643 |
644 | // Appends a div to the bottom of the page and sets the height to the difference of the window scrollTop and the last element's position top offset
645 | $(self.options.context).append($("", {
646 |
647 | "class": extendPageClassName,
648 |
649 | "height": Math.abs(lastElemOffset - winScrollTop) + "px",
650 |
651 | "data-unique": extendPageClassName
652 |
653 | }));
654 |
655 | if(self.extendPageScroll) {
656 |
657 | currentElem = self.element.find('li.active');
658 |
659 | self._scrollTo($("div[data-unique=" + currentElem.attr("data-unique") + "]"));
660 |
661 | }
662 |
663 | }
664 |
665 | }
666 |
667 | }
668 |
669 | // The zero timeout ensures the following code is run after the scroll events
670 | setTimeout(function() {
671 |
672 | // _Local variables_
673 |
674 | // Stores the distance to the closest anchor
675 | var // Stores the index of the closest anchor
676 | closestAnchorIdx = null,
677 | anchorText;
678 |
679 | // if never calculated before, calculate and cache the heights
680 | if (self.cachedHeights.length == 0) {
681 | self.calculateHeights();
682 | }
683 |
684 | var scrollTop = $(window).scrollTop();
685 |
686 | // Determines the index of the closest anchor
687 | self.cachedAnchors.each(function(idx) {
688 | if (self.cachedHeights[idx] - scrollTop < 0) {
689 | closestAnchorIdx = idx;
690 | } else {
691 | return false;
692 | }
693 | });
694 |
695 | anchorText = $(self.cachedAnchors[closestAnchorIdx]).attr("data-unique");
696 |
697 | // Stores the list item HTML element that corresponds to the currently traversed anchor tag
698 | elem = $('li[data-unique="' + anchorText + '"]');
699 |
700 | // If the `highlightOnScroll` option is true and a next element is found
701 | if(self.options.highlightOnScroll && elem.length && !elem.hasClass(self.focusClass)) {
702 |
703 | // Removes highlighting from all of the list item's
704 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
705 |
706 | // Highlights the corresponding list item
707 | elem.addClass(self.focusClass);
708 |
709 | // Scroll to highlighted element's header
710 | var tocifyWrapper = self.tocifyWrapper;
711 | var scrollToElem = $(elem).closest('.tocify-header');
712 |
713 | var elementOffset = scrollToElem.offset().top,
714 | wrapperOffset = tocifyWrapper.offset().top;
715 | var offset = elementOffset - wrapperOffset;
716 |
717 | if (offset >= $(window).height()) {
718 | var scrollPosition = offset + tocifyWrapper.scrollTop();
719 | tocifyWrapper.scrollTop(scrollPosition);
720 | } else if (offset < 0) {
721 | tocifyWrapper.scrollTop(0);
722 | }
723 | }
724 |
725 | if(self.options.scrollHistory) {
726 |
727 | // IF STATEMENT ADDED BY ROBERT
728 |
729 | if(window.location.hash !== "#" + anchorText && anchorText !== undefined) {
730 |
731 | if(history.replaceState) {
732 | history.replaceState({}, "", "#" + anchorText);
733 | // provide a fallback
734 | } else {
735 | scrollV = document.body.scrollTop;
736 | scrollH = document.body.scrollLeft;
737 | location.hash = "#" + anchorText;
738 | document.body.scrollTop = scrollV;
739 | document.body.scrollLeft = scrollH;
740 | }
741 |
742 | }
743 |
744 | }
745 |
746 | // If the `showAndHideOnScroll` option is true
747 | if(self.options.showAndHideOnScroll && self.options.showAndHide) {
748 |
749 | self._triggerShow(elem, true);
750 |
751 | }
752 |
753 | }, 0);
754 |
755 | });
756 |
757 | });
758 |
759 | },
760 |
761 | // calculateHeights
762 | // ----
763 | // ADDED BY ROBERT
764 | calculateHeights: function() {
765 | var self = this;
766 | self.cachedHeights = [];
767 | self.cachedAnchors = [];
768 | var anchors = $(self.options.context).find("div[data-unique]");
769 | anchors.each(function(idx) {
770 | var distance = (($(this).next().length ? $(this).next() : $(this)).offset().top - self.options.highlightOffset);
771 | self.cachedHeights[idx] = distance;
772 | });
773 | self.cachedAnchors = anchors;
774 | },
775 |
776 | // Show
777 | // ----
778 | // Opens the current sub-header
779 | show: function(elem, scroll) {
780 |
781 | // Stores the plugin context in the `self` variable
782 | var self = this,
783 | element = elem;
784 |
785 | // If the sub-header is not already visible
786 | if (!elem.is(":visible")) {
787 |
788 | // If the current element does not have any nested subheaders, is not a header, and its parent is not visible
789 | if(!elem.find(subheaderClass).length && !elem.parent().is(headerClass) && !elem.parent().is(":visible")) {
790 |
791 | // Sets the current element to all of the subheaders within the current header
792 | elem = elem.parents(subheaderClass).add(elem);
793 |
794 | }
795 |
796 | // If the current element does not have any nested subheaders and is not a header
797 | else if(!elem.children(subheaderClass).length && !elem.parent().is(headerClass)) {
798 |
799 | // Sets the current element to the closest subheader
800 | elem = elem.closest(subheaderClass);
801 |
802 | }
803 |
804 | //Determines what jQuery effect to use
805 | switch (self.options.showEffect) {
806 |
807 | //Uses `no effect`
808 | case "none":
809 |
810 | elem.show();
811 |
812 | break;
813 |
814 | //Uses the jQuery `show` special effect
815 | case "show":
816 |
817 | elem.show(self.options.showEffectSpeed);
818 |
819 | break;
820 |
821 | //Uses the jQuery `slideDown` special effect
822 | case "slideDown":
823 |
824 | elem.slideDown(self.options.showEffectSpeed);
825 |
826 | break;
827 |
828 | //Uses the jQuery `fadeIn` special effect
829 | case "fadeIn":
830 |
831 | elem.fadeIn(self.options.showEffectSpeed);
832 |
833 | break;
834 |
835 | //If none of the above options were passed, then a `jQueryUI show effect` is expected
836 | default:
837 |
838 | elem.show();
839 |
840 | break;
841 |
842 | }
843 |
844 | }
845 |
846 | // If the current subheader parent element is a header
847 | if(elem.parent().is(headerClass)) {
848 |
849 | // Hides all non-active sub-headers
850 | self.hide($(subheaderClass).not(elem));
851 |
852 | }
853 |
854 | // If the current subheader parent element is not a header
855 | else {
856 |
857 | // Hides all non-active sub-headers
858 | self.hide($(subheaderClass).not(elem.closest(headerClass).find(subheaderClass).not(elem.siblings())));
859 |
860 | }
861 |
862 | // Maintains chainablity
863 | return self;
864 |
865 | },
866 |
867 | // Hide
868 | // ----
869 | // Closes the current sub-header
870 | hide: function(elem) {
871 |
872 | // Stores the plugin context in the `self` variable
873 | var self = this;
874 |
875 | //Determines what jQuery effect to use
876 | switch (self.options.hideEffect) {
877 |
878 | // Uses `no effect`
879 | case "none":
880 |
881 | elem.hide();
882 |
883 | break;
884 |
885 | // Uses the jQuery `hide` special effect
886 | case "hide":
887 |
888 | elem.hide(self.options.hideEffectSpeed);
889 |
890 | break;
891 |
892 | // Uses the jQuery `slideUp` special effect
893 | case "slideUp":
894 |
895 | elem.slideUp(self.options.hideEffectSpeed);
896 |
897 | break;
898 |
899 | // Uses the jQuery `fadeOut` special effect
900 | case "fadeOut":
901 |
902 | elem.fadeOut(self.options.hideEffectSpeed);
903 |
904 | break;
905 |
906 | // If none of the above options were passed, then a `jqueryUI hide effect` is expected
907 | default:
908 |
909 | elem.hide();
910 |
911 | break;
912 |
913 | }
914 |
915 | // Maintains chainablity
916 | return self;
917 | },
918 |
919 | // _triggerShow
920 | // ------------
921 | // Determines what elements get shown on scroll and click
922 | _triggerShow: function(elem, scroll) {
923 |
924 | var self = this;
925 |
926 | // If the current element's parent is a header element or the next element is a nested subheader element
927 | if(elem.parent().is(headerClass) || elem.next().is(subheaderClass)) {
928 |
929 | // Shows the next sub-header element
930 | self.show(elem.next(subheaderClass), scroll);
931 |
932 | }
933 |
934 | // If the current element's parent is a subheader element
935 | else if(elem.parent().is(subheaderClass)) {
936 |
937 | // Shows the parent sub-header element
938 | self.show(elem.parent(), scroll);
939 |
940 | }
941 |
942 | // Maintains chainability
943 | return self;
944 |
945 | },
946 |
947 | // _addCSSClasses
948 | // --------------
949 | // Adds CSS classes to the newly generated table of contents HTML
950 | _addCSSClasses: function() {
951 |
952 | // If the user wants a jqueryUI theme
953 | if(this.options.theme === "jqueryui") {
954 |
955 | this.focusClass = "ui-state-default";
956 |
957 | this.hoverClass = "ui-state-hover";
958 |
959 | //Adds the default styling to the dropdown list
960 | this.element.addClass("ui-widget").find(".toc-title").addClass("ui-widget-header").end().find("li").addClass("ui-widget-content");
961 |
962 | }
963 |
964 | // If the user wants a twitterBootstrap theme
965 | else if(this.options.theme === "bootstrap") {
966 |
967 | this.element.find(headerClass + "," + subheaderClass).addClass("nav nav-list");
968 |
969 | this.focusClass = "active";
970 |
971 | }
972 |
973 | // If a user does not want a prebuilt theme
974 | else {
975 |
976 | // Adds more neutral classes (instead of jqueryui)
977 |
978 | this.focusClass = tocFocusClassName;
979 |
980 | this.hoverClass = tocHoverClassName;
981 |
982 | }
983 |
984 | //Maintains chainability
985 | return this;
986 |
987 | },
988 |
989 | // setOption
990 | // ---------
991 | // Sets a single Tocify option after the plugin is invoked
992 | setOption: function() {
993 |
994 | // Calls the jQueryUI Widget Factory setOption method
995 | $.Widget.prototype._setOption.apply(this, arguments);
996 |
997 | },
998 |
999 | // setOptions
1000 | // ----------
1001 | // Sets a single or multiple Tocify options after the plugin is invoked
1002 | setOptions: function() {
1003 |
1004 | // Calls the jQueryUI Widget Factory setOptions method
1005 | $.Widget.prototype._setOptions.apply(this, arguments);
1006 |
1007 | },
1008 |
1009 | // _scrollTo
1010 | // ---------
1011 | // Scrolls to a specific element
1012 | _scrollTo: function(elem) {
1013 |
1014 | var self = this,
1015 | duration = self.options.smoothScroll || 0,
1016 | scrollTo = self.options.scrollTo;
1017 |
1018 | // Once all animations on the page are complete, this callback function will be called
1019 | $("html, body").promise().done(function() {
1020 |
1021 | // Animates the html and body element scrolltops
1022 | $("html, body").animate({
1023 |
1024 | // Sets the jQuery `scrollTop` to the top offset of the HTML div tag that matches the current list item's `data-unique` tag
1025 | "scrollTop": $('div[data-unique="' + elem.attr("data-unique") + '"]').next().offset().top - ($.isFunction(scrollTo) ? scrollTo.call() : scrollTo) + "px"
1026 |
1027 | }, {
1028 |
1029 | // Sets the smoothScroll animation time duration to the smoothScrollSpeed option
1030 | "duration": duration
1031 |
1032 | });
1033 |
1034 | });
1035 |
1036 | // Maintains chainability
1037 | return self;
1038 |
1039 | }
1040 |
1041 | });
1042 |
1043 | })); //end of plugin
--------------------------------------------------------------------------------
/source/javascripts/lib/jquery_ui.js:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.10.3 - 2013-09-16
2 | * http://jqueryui.com
3 | * Includes: jquery.ui.widget.js
4 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
5 |
6 | (function(e,t){var i=0,s=Array.prototype.slice,a=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(n){}a(t)},e.widget=function(i,s,a){var n,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],n=u+"-"+i,a||(a=s,s=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:a.version,_proto:e.extend({},a),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(a,function(i,a){return e.isFunction(a)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,n=this._superApply;return this._super=e,this._superApply=t,i=a.apply(this,arguments),this._super=s,this._superApply=n,i}}(),t):(l[i]=a,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:n}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var a,n,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(a in r[o])n=r[o][a],r[o].hasOwnProperty(a)&&n!==t&&(i[a]=e.isPlainObject(n)?e.isPlainObject(i[a])?e.widget.extend({},i[a],n):e.widget.extend({},n):n);return i},e.widget.bridge=function(i,a){var n=a.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,a=e.data(this,n);return a?e.isFunction(a[r])&&"_"!==r.charAt(0)?(s=a[r].apply(a,h),s!==a&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,n);t?t.option(r||{})._init():e.data(this,n,new a(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var a,n,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},a=i.split("."),i=a.shift(),a.length){for(n=o[i]=e.widget.extend({},this.options[i]),r=0;a.length-1>r;r++)n[a[r]]=n[a[r]]||{},n=n[a[r]];if(i=a.pop(),s===t)return n[i]===t?null:n[i];n[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,a){var n,r=this;"boolean"!=typeof i&&(a=s,s=i,i=!1),a?(s=n=e(s),this.bindings=this.bindings.add(s)):(a=s,s=this.element,n=this.widget()),e.each(a,function(a,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=a.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?n.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var a,n,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(a in n)a in i||(i[a]=n[a]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,a,n){"string"==typeof a&&(a={effect:a});var r,o=a?a===!0||"number"==typeof a?i:a.effect||i:t;a=a||{},"number"==typeof a&&(a={duration:a}),r=!e.isEmptyObject(a),a.complete=n,a.delay&&s.delay(a.delay),r&&e.effects&&e.effects.effect[o]?s[t](a):o!==t&&s[o]?s[o](a.duration,a.easing,n):s.queue(function(i){e(this)[t](),n&&n.call(s[0]),i()})}})})(jQuery);
--------------------------------------------------------------------------------
/source/javascripts/lib/lunr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.2
3 | * Copyright (C) 2014 Oliver Nightingale
4 | * MIT Licensed
5 | * @license
6 | */
7 |
8 | (function(){
9 |
10 | /**
11 | * Convenience function for instantiating a new lunr index and configuring it
12 | * with the default pipeline functions and the passed config function.
13 | *
14 | * When using this convenience function a new index will be created with the
15 | * following functions already in the pipeline:
16 | *
17 | * lunr.StopWordFilter - filters out any stop words before they enter the
18 | * index
19 | *
20 | * lunr.stemmer - stems the tokens before entering the index.
21 | *
22 | * Example:
23 | *
24 | * var idx = lunr(function () {
25 | * this.field('title', 10)
26 | * this.field('tags', 100)
27 | * this.field('body')
28 | *
29 | * this.ref('cid')
30 | *
31 | * this.pipeline.add(function () {
32 | * // some custom pipeline function
33 | * })
34 | *
35 | * })
36 | *
37 | * @param {Function} config A function that will be called with the new instance
38 | * of the lunr.Index as both its context and first parameter. It can be used to
39 | * customize the instance of new lunr.Index.
40 | * @namespace
41 | * @module
42 | * @returns {lunr.Index}
43 | *
44 | */
45 | var lunr = function (config) {
46 | var idx = new lunr.Index
47 |
48 | idx.pipeline.add(
49 | lunr.trimmer,
50 | lunr.stopWordFilter,
51 | lunr.stemmer
52 | )
53 |
54 | if (config) config.call(idx, idx)
55 |
56 | return idx
57 | }
58 |
59 | lunr.version = "0.5.2"
60 | /*!
61 | * lunr.utils
62 | * Copyright (C) 2014 Oliver Nightingale
63 | */
64 |
65 | /**
66 | * A namespace containing utils for the rest of the lunr library
67 | */
68 | lunr.utils = {}
69 |
70 | /**
71 | * Print a warning message to the console.
72 | *
73 | * @param {String} message The message to be printed.
74 | * @memberOf Utils
75 | */
76 | lunr.utils.warn = (function (global) {
77 | return function (message) {
78 | if (global.console && console.warn) {
79 | console.warn(message)
80 | }
81 | }
82 | })(this)
83 |
84 | /*!
85 | * lunr.EventEmitter
86 | * Copyright (C) 2014 Oliver Nightingale
87 | */
88 |
89 | /**
90 | * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers.
91 | *
92 | * @constructor
93 | */
94 | lunr.EventEmitter = function () {
95 | this.events = {}
96 | }
97 |
98 | /**
99 | * Binds a handler function to a specific event(s).
100 | *
101 | * Can bind a single function to many different events in one call.
102 | *
103 | * @param {String} [eventName] The name(s) of events to bind this function to.
104 | * @param {Function} handler The function to call when an event is fired.
105 | * @memberOf EventEmitter
106 | */
107 | lunr.EventEmitter.prototype.addListener = function () {
108 | var args = Array.prototype.slice.call(arguments),
109 | fn = args.pop(),
110 | names = args
111 |
112 | if (typeof fn !== "function") throw new TypeError ("last argument must be a function")
113 |
114 | names.forEach(function (name) {
115 | if (!this.hasHandler(name)) this.events[name] = []
116 | this.events[name].push(fn)
117 | }, this)
118 | }
119 |
120 | /**
121 | * Removes a handler function from a specific event.
122 | *
123 | * @param {String} eventName The name of the event to remove this function from.
124 | * @param {Function} handler The function to remove from an event.
125 | * @memberOf EventEmitter
126 | */
127 | lunr.EventEmitter.prototype.removeListener = function (name, fn) {
128 | if (!this.hasHandler(name)) return
129 |
130 | var fnIndex = this.events[name].indexOf(fn)
131 | this.events[name].splice(fnIndex, 1)
132 |
133 | if (!this.events[name].length) delete this.events[name]
134 | }
135 |
136 | /**
137 | * Calls all functions bound to the given event.
138 | *
139 | * Additional data can be passed to the event handler as arguments to `emit`
140 | * after the event name.
141 | *
142 | * @param {String} eventName The name of the event to emit.
143 | * @memberOf EventEmitter
144 | */
145 | lunr.EventEmitter.prototype.emit = function (name) {
146 | if (!this.hasHandler(name)) return
147 |
148 | var args = Array.prototype.slice.call(arguments, 1)
149 |
150 | this.events[name].forEach(function (fn) {
151 | fn.apply(undefined, args)
152 | })
153 | }
154 |
155 | /**
156 | * Checks whether a handler has ever been stored against an event.
157 | *
158 | * @param {String} eventName The name of the event to check.
159 | * @private
160 | * @memberOf EventEmitter
161 | */
162 | lunr.EventEmitter.prototype.hasHandler = function (name) {
163 | return name in this.events
164 | }
165 |
166 | /*!
167 | * lunr.tokenizer
168 | * Copyright (C) 2014 Oliver Nightingale
169 | */
170 |
171 | /**
172 | * A function for splitting a string into tokens ready to be inserted into
173 | * the search index.
174 | *
175 | * @module
176 | * @param {String} obj The string to convert into tokens
177 | * @returns {Array}
178 | */
179 | lunr.tokenizer = function (obj) {
180 | if (!arguments.length || obj == null || obj == undefined) return []
181 | if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() })
182 |
183 | var str = obj.toString().replace(/^\s+/, '')
184 |
185 | for (var i = str.length - 1; i >= 0; i--) {
186 | if (/\S/.test(str.charAt(i))) {
187 | str = str.substring(0, i + 1)
188 | break
189 | }
190 | }
191 |
192 | return str
193 | .split(/\s+/)
194 | .map(function (token) {
195 | return token.toLowerCase()
196 | })
197 | }
198 | /*!
199 | * lunr.Pipeline
200 | * Copyright (C) 2014 Oliver Nightingale
201 | */
202 |
203 | /**
204 | * lunr.Pipelines maintain an ordered list of functions to be applied to all
205 | * tokens in documents entering the search index and queries being ran against
206 | * the index.
207 | *
208 | * An instance of lunr.Index created with the lunr shortcut will contain a
209 | * pipeline with a stop word filter and an English language stemmer. Extra
210 | * functions can be added before or after either of these functions or these
211 | * default functions can be removed.
212 | *
213 | * When run the pipeline will call each function in turn, passing a token, the
214 | * index of that token in the original list of all tokens and finally a list of
215 | * all the original tokens.
216 | *
217 | * The output of functions in the pipeline will be passed to the next function
218 | * in the pipeline. To exclude a token from entering the index the function
219 | * should return undefined, the rest of the pipeline will not be called with
220 | * this token.
221 | *
222 | * For serialisation of pipelines to work, all functions used in an instance of
223 | * a pipeline should be registered with lunr.Pipeline. Registered functions can
224 | * then be loaded. If trying to load a serialised pipeline that uses functions
225 | * that are not registered an error will be thrown.
226 | *
227 | * If not planning on serialising the pipeline then registering pipeline functions
228 | * is not necessary.
229 | *
230 | * @constructor
231 | */
232 | lunr.Pipeline = function () {
233 | this._stack = []
234 | }
235 |
236 | lunr.Pipeline.registeredFunctions = {}
237 |
238 | /**
239 | * Register a function with the pipeline.
240 | *
241 | * Functions that are used in the pipeline should be registered if the pipeline
242 | * needs to be serialised, or a serialised pipeline needs to be loaded.
243 | *
244 | * Registering a function does not add it to a pipeline, functions must still be
245 | * added to instances of the pipeline for them to be used when running a pipeline.
246 | *
247 | * @param {Function} fn The function to check for.
248 | * @param {String} label The label to register this function with
249 | * @memberOf Pipeline
250 | */
251 | lunr.Pipeline.registerFunction = function (fn, label) {
252 | if (label in this.registeredFunctions) {
253 | lunr.utils.warn('Overwriting existing registered function: ' + label)
254 | }
255 |
256 | fn.label = label
257 | lunr.Pipeline.registeredFunctions[fn.label] = fn
258 | }
259 |
260 | /**
261 | * Warns if the function is not registered as a Pipeline function.
262 | *
263 | * @param {Function} fn The function to check for.
264 | * @private
265 | * @memberOf Pipeline
266 | */
267 | lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
268 | var isRegistered = fn.label && (fn.label in this.registeredFunctions)
269 |
270 | if (!isRegistered) {
271 | lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
272 | }
273 | }
274 |
275 | /**
276 | * Loads a previously serialised pipeline.
277 | *
278 | * All functions to be loaded must already be registered with lunr.Pipeline.
279 | * If any function from the serialised data has not been registered then an
280 | * error will be thrown.
281 | *
282 | * @param {Object} serialised The serialised pipeline to load.
283 | * @returns {lunr.Pipeline}
284 | * @memberOf Pipeline
285 | */
286 | lunr.Pipeline.load = function (serialised) {
287 | var pipeline = new lunr.Pipeline
288 |
289 | serialised.forEach(function (fnName) {
290 | var fn = lunr.Pipeline.registeredFunctions[fnName]
291 |
292 | if (fn) {
293 | pipeline.add(fn)
294 | } else {
295 | throw new Error ('Cannot load un-registered function: ' + fnName)
296 | }
297 | })
298 |
299 | return pipeline
300 | }
301 |
302 | /**
303 | * Adds new functions to the end of the pipeline.
304 | *
305 | * Logs a warning if the function has not been registered.
306 | *
307 | * @param {Function} functions Any number of functions to add to the pipeline.
308 | * @memberOf Pipeline
309 | */
310 | lunr.Pipeline.prototype.add = function () {
311 | var fns = Array.prototype.slice.call(arguments)
312 |
313 | fns.forEach(function (fn) {
314 | lunr.Pipeline.warnIfFunctionNotRegistered(fn)
315 | this._stack.push(fn)
316 | }, this)
317 | }
318 |
319 | /**
320 | * Adds a single function after a function that already exists in the
321 | * pipeline.
322 | *
323 | * Logs a warning if the function has not been registered.
324 | *
325 | * @param {Function} existingFn A function that already exists in the pipeline.
326 | * @param {Function} newFn The new function to add to the pipeline.
327 | * @memberOf Pipeline
328 | */
329 | lunr.Pipeline.prototype.after = function (existingFn, newFn) {
330 | lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
331 |
332 | var pos = this._stack.indexOf(existingFn) + 1
333 | this._stack.splice(pos, 0, newFn)
334 | }
335 |
336 | /**
337 | * Adds a single function before a function that already exists in the
338 | * pipeline.
339 | *
340 | * Logs a warning if the function has not been registered.
341 | *
342 | * @param {Function} existingFn A function that already exists in the pipeline.
343 | * @param {Function} newFn The new function to add to the pipeline.
344 | * @memberOf Pipeline
345 | */
346 | lunr.Pipeline.prototype.before = function (existingFn, newFn) {
347 | lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
348 |
349 | var pos = this._stack.indexOf(existingFn)
350 | this._stack.splice(pos, 0, newFn)
351 | }
352 |
353 | /**
354 | * Removes a function from the pipeline.
355 | *
356 | * @param {Function} fn The function to remove from the pipeline.
357 | * @memberOf Pipeline
358 | */
359 | lunr.Pipeline.prototype.remove = function (fn) {
360 | var pos = this._stack.indexOf(fn)
361 | this._stack.splice(pos, 1)
362 | }
363 |
364 | /**
365 | * Runs the current list of functions that make up the pipeline against the
366 | * passed tokens.
367 | *
368 | * @param {Array} tokens The tokens to run through the pipeline.
369 | * @returns {Array}
370 | * @memberOf Pipeline
371 | */
372 | lunr.Pipeline.prototype.run = function (tokens) {
373 | var out = [],
374 | tokenLength = tokens.length,
375 | stackLength = this._stack.length
376 |
377 | for (var i = 0; i < tokenLength; i++) {
378 | var token = tokens[i]
379 |
380 | for (var j = 0; j < stackLength; j++) {
381 | token = this._stack[j](token, i, tokens)
382 | if (token === void 0) break
383 | };
384 |
385 | if (token !== void 0) out.push(token)
386 | };
387 |
388 | return out
389 | }
390 |
391 | /**
392 | * Resets the pipeline by removing any existing processors.
393 | *
394 | * @memberOf Pipeline
395 | */
396 | lunr.Pipeline.prototype.reset = function () {
397 | this._stack = []
398 | }
399 |
400 | /**
401 | * Returns a representation of the pipeline ready for serialisation.
402 | *
403 | * Logs a warning if the function has not been registered.
404 | *
405 | * @returns {Array}
406 | * @memberOf Pipeline
407 | */
408 | lunr.Pipeline.prototype.toJSON = function () {
409 | return this._stack.map(function (fn) {
410 | lunr.Pipeline.warnIfFunctionNotRegistered(fn)
411 |
412 | return fn.label
413 | })
414 | }
415 | /*!
416 | * lunr.Vector
417 | * Copyright (C) 2014 Oliver Nightingale
418 | */
419 |
420 | /**
421 | * lunr.Vectors implement vector related operations for
422 | * a series of elements.
423 | *
424 | * @constructor
425 | */
426 | lunr.Vector = function () {
427 | this._magnitude = null
428 | this.list = undefined
429 | this.length = 0
430 | }
431 |
432 | /**
433 | * lunr.Vector.Node is a simple struct for each node
434 | * in a lunr.Vector.
435 | *
436 | * @private
437 | * @param {Number} The index of the node in the vector.
438 | * @param {Object} The data at this node in the vector.
439 | * @param {lunr.Vector.Node} The node directly after this node in the vector.
440 | * @constructor
441 | * @memberOf Vector
442 | */
443 | lunr.Vector.Node = function (idx, val, next) {
444 | this.idx = idx
445 | this.val = val
446 | this.next = next
447 | }
448 |
449 | /**
450 | * Inserts a new value at a position in a vector.
451 | *
452 | * @param {Number} The index at which to insert a value.
453 | * @param {Object} The object to insert in the vector.
454 | * @memberOf Vector.
455 | */
456 | lunr.Vector.prototype.insert = function (idx, val) {
457 | var list = this.list
458 |
459 | if (!list) {
460 | this.list = new lunr.Vector.Node (idx, val, list)
461 | return this.length++
462 | }
463 |
464 | var prev = list,
465 | next = list.next
466 |
467 | while (next != undefined) {
468 | if (idx < next.idx) {
469 | prev.next = new lunr.Vector.Node (idx, val, next)
470 | return this.length++
471 | }
472 |
473 | prev = next, next = next.next
474 | }
475 |
476 | prev.next = new lunr.Vector.Node (idx, val, next)
477 | return this.length++
478 | }
479 |
480 | /**
481 | * Calculates the magnitude of this vector.
482 | *
483 | * @returns {Number}
484 | * @memberOf Vector
485 | */
486 | lunr.Vector.prototype.magnitude = function () {
487 | if (this._magniture) return this._magnitude
488 | var node = this.list,
489 | sumOfSquares = 0,
490 | val
491 |
492 | while (node) {
493 | val = node.val
494 | sumOfSquares += val * val
495 | node = node.next
496 | }
497 |
498 | return this._magnitude = Math.sqrt(sumOfSquares)
499 | }
500 |
501 | /**
502 | * Calculates the dot product of this vector and another vector.
503 | *
504 | * @param {lunr.Vector} otherVector The vector to compute the dot product with.
505 | * @returns {Number}
506 | * @memberOf Vector
507 | */
508 | lunr.Vector.prototype.dot = function (otherVector) {
509 | var node = this.list,
510 | otherNode = otherVector.list,
511 | dotProduct = 0
512 |
513 | while (node && otherNode) {
514 | if (node.idx < otherNode.idx) {
515 | node = node.next
516 | } else if (node.idx > otherNode.idx) {
517 | otherNode = otherNode.next
518 | } else {
519 | dotProduct += node.val * otherNode.val
520 | node = node.next
521 | otherNode = otherNode.next
522 | }
523 | }
524 |
525 | return dotProduct
526 | }
527 |
528 | /**
529 | * Calculates the cosine similarity between this vector and another
530 | * vector.
531 | *
532 | * @param {lunr.Vector} otherVector The other vector to calculate the
533 | * similarity with.
534 | * @returns {Number}
535 | * @memberOf Vector
536 | */
537 | lunr.Vector.prototype.similarity = function (otherVector) {
538 | return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
539 | }
540 | /*!
541 | * lunr.SortedSet
542 | * Copyright (C) 2014 Oliver Nightingale
543 | */
544 |
545 | /**
546 | * lunr.SortedSets are used to maintain an array of uniq values in a sorted
547 | * order.
548 | *
549 | * @constructor
550 | */
551 | lunr.SortedSet = function () {
552 | this.length = 0
553 | this.elements = []
554 | }
555 |
556 | /**
557 | * Loads a previously serialised sorted set.
558 | *
559 | * @param {Array} serialisedData The serialised set to load.
560 | * @returns {lunr.SortedSet}
561 | * @memberOf SortedSet
562 | */
563 | lunr.SortedSet.load = function (serialisedData) {
564 | var set = new this
565 |
566 | set.elements = serialisedData
567 | set.length = serialisedData.length
568 |
569 | return set
570 | }
571 |
572 | /**
573 | * Inserts new items into the set in the correct position to maintain the
574 | * order.
575 | *
576 | * @param {Object} The objects to add to this set.
577 | * @memberOf SortedSet
578 | */
579 | lunr.SortedSet.prototype.add = function () {
580 | Array.prototype.slice.call(arguments).forEach(function (element) {
581 | if (~this.indexOf(element)) return
582 | this.elements.splice(this.locationFor(element), 0, element)
583 | }, this)
584 |
585 | this.length = this.elements.length
586 | }
587 |
588 | /**
589 | * Converts this sorted set into an array.
590 | *
591 | * @returns {Array}
592 | * @memberOf SortedSet
593 | */
594 | lunr.SortedSet.prototype.toArray = function () {
595 | return this.elements.slice()
596 | }
597 |
598 | /**
599 | * Creates a new array with the results of calling a provided function on every
600 | * element in this sorted set.
601 | *
602 | * Delegates to Array.prototype.map and has the same signature.
603 | *
604 | * @param {Function} fn The function that is called on each element of the
605 | * set.
606 | * @param {Object} ctx An optional object that can be used as the context
607 | * for the function fn.
608 | * @returns {Array}
609 | * @memberOf SortedSet
610 | */
611 | lunr.SortedSet.prototype.map = function (fn, ctx) {
612 | return this.elements.map(fn, ctx)
613 | }
614 |
615 | /**
616 | * Executes a provided function once per sorted set element.
617 | *
618 | * Delegates to Array.prototype.forEach and has the same signature.
619 | *
620 | * @param {Function} fn The function that is called on each element of the
621 | * set.
622 | * @param {Object} ctx An optional object that can be used as the context
623 | * @memberOf SortedSet
624 | * for the function fn.
625 | */
626 | lunr.SortedSet.prototype.forEach = function (fn, ctx) {
627 | return this.elements.forEach(fn, ctx)
628 | }
629 |
630 | /**
631 | * Returns the index at which a given element can be found in the
632 | * sorted set, or -1 if it is not present.
633 | *
634 | * @param {Object} elem The object to locate in the sorted set.
635 | * @param {Number} start An optional index at which to start searching from
636 | * within the set.
637 | * @param {Number} end An optional index at which to stop search from within
638 | * the set.
639 | * @returns {Number}
640 | * @memberOf SortedSet
641 | */
642 | lunr.SortedSet.prototype.indexOf = function (elem, start, end) {
643 | var start = start || 0,
644 | end = end || this.elements.length,
645 | sectionLength = end - start,
646 | pivot = start + Math.floor(sectionLength / 2),
647 | pivotElem = this.elements[pivot]
648 |
649 | if (sectionLength <= 1) {
650 | if (pivotElem === elem) {
651 | return pivot
652 | } else {
653 | return -1
654 | }
655 | }
656 |
657 | if (pivotElem < elem) return this.indexOf(elem, pivot, end)
658 | if (pivotElem > elem) return this.indexOf(elem, start, pivot)
659 | if (pivotElem === elem) return pivot
660 | }
661 |
662 | /**
663 | * Returns the position within the sorted set that an element should be
664 | * inserted at to maintain the current order of the set.
665 | *
666 | * This function assumes that the element to search for does not already exist
667 | * in the sorted set.
668 | *
669 | * @param {Object} elem The elem to find the position for in the set
670 | * @param {Number} start An optional index at which to start searching from
671 | * within the set.
672 | * @param {Number} end An optional index at which to stop search from within
673 | * the set.
674 | * @returns {Number}
675 | * @memberOf SortedSet
676 | */
677 | lunr.SortedSet.prototype.locationFor = function (elem, start, end) {
678 | var start = start || 0,
679 | end = end || this.elements.length,
680 | sectionLength = end - start,
681 | pivot = start + Math.floor(sectionLength / 2),
682 | pivotElem = this.elements[pivot]
683 |
684 | if (sectionLength <= 1) {
685 | if (pivotElem > elem) return pivot
686 | if (pivotElem < elem) return pivot + 1
687 | }
688 |
689 | if (pivotElem < elem) return this.locationFor(elem, pivot, end)
690 | if (pivotElem > elem) return this.locationFor(elem, start, pivot)
691 | }
692 |
693 | /**
694 | * Creates a new lunr.SortedSet that contains the elements in the intersection
695 | * of this set and the passed set.
696 | *
697 | * @param {lunr.SortedSet} otherSet The set to intersect with this set.
698 | * @returns {lunr.SortedSet}
699 | * @memberOf SortedSet
700 | */
701 | lunr.SortedSet.prototype.intersect = function (otherSet) {
702 | var intersectSet = new lunr.SortedSet,
703 | i = 0, j = 0,
704 | a_len = this.length, b_len = otherSet.length,
705 | a = this.elements, b = otherSet.elements
706 |
707 | while (true) {
708 | if (i > a_len - 1 || j > b_len - 1) break
709 |
710 | if (a[i] === b[j]) {
711 | intersectSet.add(a[i])
712 | i++, j++
713 | continue
714 | }
715 |
716 | if (a[i] < b[j]) {
717 | i++
718 | continue
719 | }
720 |
721 | if (a[i] > b[j]) {
722 | j++
723 | continue
724 | }
725 | };
726 |
727 | return intersectSet
728 | }
729 |
730 | /**
731 | * Makes a copy of this set
732 | *
733 | * @returns {lunr.SortedSet}
734 | * @memberOf SortedSet
735 | */
736 | lunr.SortedSet.prototype.clone = function () {
737 | var clone = new lunr.SortedSet
738 |
739 | clone.elements = this.toArray()
740 | clone.length = clone.elements.length
741 |
742 | return clone
743 | }
744 |
745 | /**
746 | * Creates a new lunr.SortedSet that contains the elements in the union
747 | * of this set and the passed set.
748 | *
749 | * @param {lunr.SortedSet} otherSet The set to union with this set.
750 | * @returns {lunr.SortedSet}
751 | * @memberOf SortedSet
752 | */
753 | lunr.SortedSet.prototype.union = function (otherSet) {
754 | var longSet, shortSet, unionSet
755 |
756 | if (this.length >= otherSet.length) {
757 | longSet = this, shortSet = otherSet
758 | } else {
759 | longSet = otherSet, shortSet = this
760 | }
761 |
762 | unionSet = longSet.clone()
763 |
764 | unionSet.add.apply(unionSet, shortSet.toArray())
765 |
766 | return unionSet
767 | }
768 |
769 | /**
770 | * Returns a representation of the sorted set ready for serialisation.
771 | *
772 | * @returns {Array}
773 | * @memberOf SortedSet
774 | */
775 | lunr.SortedSet.prototype.toJSON = function () {
776 | return this.toArray()
777 | }
778 | /*!
779 | * lunr.Index
780 | * Copyright (C) 2014 Oliver Nightingale
781 | */
782 |
783 | /**
784 | * lunr.Index is object that manages a search index. It contains the indexes
785 | * and stores all the tokens and document lookups. It also provides the main
786 | * user facing API for the library.
787 | *
788 | * @constructor
789 | */
790 | lunr.Index = function () {
791 | this._fields = []
792 | this._ref = 'id'
793 | this.pipeline = new lunr.Pipeline
794 | this.documentStore = new lunr.Store
795 | this.tokenStore = new lunr.TokenStore
796 | this.corpusTokens = new lunr.SortedSet
797 | this.eventEmitter = new lunr.EventEmitter
798 |
799 | this._idfCache = {}
800 |
801 | this.on('add', 'remove', 'update', (function () {
802 | this._idfCache = {}
803 | }).bind(this))
804 | }
805 |
806 | /**
807 | * Bind a handler to events being emitted by the index.
808 | *
809 | * The handler can be bound to many events at the same time.
810 | *
811 | * @param {String} [eventName] The name(s) of events to bind the function to.
812 | * @param {Function} handler The serialised set to load.
813 | * @memberOf Index
814 | */
815 | lunr.Index.prototype.on = function () {
816 | var args = Array.prototype.slice.call(arguments)
817 | return this.eventEmitter.addListener.apply(this.eventEmitter, args)
818 | }
819 |
820 | /**
821 | * Removes a handler from an event being emitted by the index.
822 | *
823 | * @param {String} eventName The name of events to remove the function from.
824 | * @param {Function} handler The serialised set to load.
825 | * @memberOf Index
826 | */
827 | lunr.Index.prototype.off = function (name, fn) {
828 | return this.eventEmitter.removeListener(name, fn)
829 | }
830 |
831 | /**
832 | * Loads a previously serialised index.
833 | *
834 | * Issues a warning if the index being imported was serialised
835 | * by a different version of lunr.
836 | *
837 | * @param {Object} serialisedData The serialised set to load.
838 | * @returns {lunr.Index}
839 | * @memberOf Index
840 | */
841 | lunr.Index.load = function (serialisedData) {
842 | if (serialisedData.version !== lunr.version) {
843 | lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version)
844 | }
845 |
846 | var idx = new this
847 |
848 | idx._fields = serialisedData.fields
849 | idx._ref = serialisedData.ref
850 |
851 | idx.documentStore = lunr.Store.load(serialisedData.documentStore)
852 | idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore)
853 | idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens)
854 | idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline)
855 |
856 | return idx
857 | }
858 |
859 | /**
860 | * Adds a field to the list of fields that will be searchable within documents
861 | * in the index.
862 | *
863 | * An optional boost param can be passed to affect how much tokens in this field
864 | * rank in search results, by default the boost value is 1.
865 | *
866 | * Fields should be added before any documents are added to the index, fields
867 | * that are added after documents are added to the index will only apply to new
868 | * documents added to the index.
869 | *
870 | * @param {String} fieldName The name of the field within the document that
871 | * should be indexed
872 | * @param {Number} boost An optional boost that can be applied to terms in this
873 | * field.
874 | * @returns {lunr.Index}
875 | * @memberOf Index
876 | */
877 | lunr.Index.prototype.field = function (fieldName, opts) {
878 | var opts = opts || {},
879 | field = { name: fieldName, boost: opts.boost || 1 }
880 |
881 | this._fields.push(field)
882 | return this
883 | }
884 |
885 | /**
886 | * Sets the property used to uniquely identify documents added to the index,
887 | * by default this property is 'id'.
888 | *
889 | * This should only be changed before adding documents to the index, changing
890 | * the ref property without resetting the index can lead to unexpected results.
891 | *
892 | * @param {String} refName The property to use to uniquely identify the
893 | * documents in the index.
894 | * @param {Boolean} emitEvent Whether to emit add events, defaults to true
895 | * @returns {lunr.Index}
896 | * @memberOf Index
897 | */
898 | lunr.Index.prototype.ref = function (refName) {
899 | this._ref = refName
900 | return this
901 | }
902 |
903 | /**
904 | * Add a document to the index.
905 | *
906 | * This is the way new documents enter the index, this function will run the
907 | * fields from the document through the index's pipeline and then add it to
908 | * the index, it will then show up in search results.
909 | *
910 | * An 'add' event is emitted with the document that has been added and the index
911 | * the document has been added to. This event can be silenced by passing false
912 | * as the second argument to add.
913 | *
914 | * @param {Object} doc The document to add to the index.
915 | * @param {Boolean} emitEvent Whether or not to emit events, default true.
916 | * @memberOf Index
917 | */
918 | lunr.Index.prototype.add = function (doc, emitEvent) {
919 | var docTokens = {},
920 | allDocumentTokens = new lunr.SortedSet,
921 | docRef = doc[this._ref],
922 | emitEvent = emitEvent === undefined ? true : emitEvent
923 |
924 | this._fields.forEach(function (field) {
925 | var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name]))
926 |
927 | docTokens[field.name] = fieldTokens
928 | lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens)
929 | }, this)
930 |
931 | this.documentStore.set(docRef, allDocumentTokens)
932 | lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray())
933 |
934 | for (var i = 0; i < allDocumentTokens.length; i++) {
935 | var token = allDocumentTokens.elements[i]
936 | var tf = this._fields.reduce(function (memo, field) {
937 | var fieldLength = docTokens[field.name].length
938 |
939 | if (!fieldLength) return memo
940 |
941 | var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length
942 |
943 | return memo + (tokenCount / fieldLength * field.boost)
944 | }, 0)
945 |
946 | this.tokenStore.add(token, { ref: docRef, tf: tf })
947 | };
948 |
949 | if (emitEvent) this.eventEmitter.emit('add', doc, this)
950 | }
951 |
952 | /**
953 | * Removes a document from the index.
954 | *
955 | * To make sure documents no longer show up in search results they can be
956 | * removed from the index using this method.
957 | *
958 | * The document passed only needs to have the same ref property value as the
959 | * document that was added to the index, they could be completely different
960 | * objects.
961 | *
962 | * A 'remove' event is emitted with the document that has been removed and the index
963 | * the document has been removed from. This event can be silenced by passing false
964 | * as the second argument to remove.
965 | *
966 | * @param {Object} doc The document to remove from the index.
967 | * @param {Boolean} emitEvent Whether to emit remove events, defaults to true
968 | * @memberOf Index
969 | */
970 | lunr.Index.prototype.remove = function (doc, emitEvent) {
971 | var docRef = doc[this._ref],
972 | emitEvent = emitEvent === undefined ? true : emitEvent
973 |
974 | if (!this.documentStore.has(docRef)) return
975 |
976 | var docTokens = this.documentStore.get(docRef)
977 |
978 | this.documentStore.remove(docRef)
979 |
980 | docTokens.forEach(function (token) {
981 | this.tokenStore.remove(token, docRef)
982 | }, this)
983 |
984 | if (emitEvent) this.eventEmitter.emit('remove', doc, this)
985 | }
986 |
987 | /**
988 | * Updates a document in the index.
989 | *
990 | * When a document contained within the index gets updated, fields changed,
991 | * added or removed, to make sure it correctly matched against search queries,
992 | * it should be updated in the index.
993 | *
994 | * This method is just a wrapper around `remove` and `add`
995 | *
996 | * An 'update' event is emitted with the document that has been updated and the index.
997 | * This event can be silenced by passing false as the second argument to update. Only
998 | * an update event will be fired, the 'add' and 'remove' events of the underlying calls
999 | * are silenced.
1000 | *
1001 | * @param {Object} doc The document to update in the index.
1002 | * @param {Boolean} emitEvent Whether to emit update events, defaults to true
1003 | * @see Index.prototype.remove
1004 | * @see Index.prototype.add
1005 | * @memberOf Index
1006 | */
1007 | lunr.Index.prototype.update = function (doc, emitEvent) {
1008 | var emitEvent = emitEvent === undefined ? true : emitEvent
1009 |
1010 | this.remove(doc, false)
1011 | this.add(doc, false)
1012 |
1013 | if (emitEvent) this.eventEmitter.emit('update', doc, this)
1014 | }
1015 |
1016 | /**
1017 | * Calculates the inverse document frequency for a token within the index.
1018 | *
1019 | * @param {String} token The token to calculate the idf of.
1020 | * @see Index.prototype.idf
1021 | * @private
1022 | * @memberOf Index
1023 | */
1024 | lunr.Index.prototype.idf = function (term) {
1025 | var cacheKey = "@" + term
1026 | if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey]
1027 |
1028 | var documentFrequency = this.tokenStore.count(term),
1029 | idf = 1
1030 |
1031 | if (documentFrequency > 0) {
1032 | idf = 1 + Math.log(this.tokenStore.length / documentFrequency)
1033 | }
1034 |
1035 | return this._idfCache[cacheKey] = idf
1036 | }
1037 |
1038 | /**
1039 | * Searches the index using the passed query.
1040 | *
1041 | * Queries should be a string, multiple words are allowed and will lead to an
1042 | * AND based query, e.g. `idx.search('foo bar')` will run a search for
1043 | * documents containing both 'foo' and 'bar'.
1044 | *
1045 | * All query tokens are passed through the same pipeline that document tokens
1046 | * are passed through, so any language processing involved will be run on every
1047 | * query term.
1048 | *
1049 | * Each query term is expanded, so that the term 'he' might be expanded to
1050 | * 'hello' and 'help' if those terms were already included in the index.
1051 | *
1052 | * Matching documents are returned as an array of objects, each object contains
1053 | * the matching document ref, as set for this index, and the similarity score
1054 | * for this document against the query.
1055 | *
1056 | * @param {String} query The query to search the index with.
1057 | * @returns {Object}
1058 | * @see Index.prototype.idf
1059 | * @see Index.prototype.documentVector
1060 | * @memberOf Index
1061 | */
1062 | lunr.Index.prototype.search = function (query) {
1063 | var queryTokens = this.pipeline.run(lunr.tokenizer(query)),
1064 | queryVector = new lunr.Vector,
1065 | documentSets = [],
1066 | fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0)
1067 |
1068 | var hasSomeToken = queryTokens.some(function (token) {
1069 | return this.tokenStore.has(token)
1070 | }, this)
1071 |
1072 | if (!hasSomeToken) return []
1073 |
1074 | queryTokens
1075 | .forEach(function (token, i, tokens) {
1076 | var tf = 1 / tokens.length * this._fields.length * fieldBoosts,
1077 | self = this
1078 |
1079 | var set = this.tokenStore.expand(token).reduce(function (memo, key) {
1080 | var pos = self.corpusTokens.indexOf(key),
1081 | idf = self.idf(key),
1082 | similarityBoost = 1,
1083 | set = new lunr.SortedSet
1084 |
1085 | // if the expanded key is not an exact match to the token then
1086 | // penalise the score for this key by how different the key is
1087 | // to the token.
1088 | if (key !== token) {
1089 | var diff = Math.max(3, key.length - token.length)
1090 | similarityBoost = 1 / Math.log(diff)
1091 | }
1092 |
1093 | // calculate the query tf-idf score for this token
1094 | // applying an similarityBoost to ensure exact matches
1095 | // these rank higher than expanded terms
1096 | if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost)
1097 |
1098 | // add all the documents that have this key into a set
1099 | Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) })
1100 |
1101 | return memo.union(set)
1102 | }, new lunr.SortedSet)
1103 |
1104 | documentSets.push(set)
1105 | }, this)
1106 |
1107 | var documentSet = documentSets.reduce(function (memo, set) {
1108 | return memo.intersect(set)
1109 | })
1110 |
1111 | return documentSet
1112 | .map(function (ref) {
1113 | return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) }
1114 | }, this)
1115 | .sort(function (a, b) {
1116 | return b.score - a.score
1117 | })
1118 | }
1119 |
1120 | /**
1121 | * Generates a vector containing all the tokens in the document matching the
1122 | * passed documentRef.
1123 | *
1124 | * The vector contains the tf-idf score for each token contained in the
1125 | * document with the passed documentRef. The vector will contain an element
1126 | * for every token in the indexes corpus, if the document does not contain that
1127 | * token the element will be 0.
1128 | *
1129 | * @param {Object} documentRef The ref to find the document with.
1130 | * @returns {lunr.Vector}
1131 | * @private
1132 | * @memberOf Index
1133 | */
1134 | lunr.Index.prototype.documentVector = function (documentRef) {
1135 | var documentTokens = this.documentStore.get(documentRef),
1136 | documentTokensLength = documentTokens.length,
1137 | documentVector = new lunr.Vector
1138 |
1139 | for (var i = 0; i < documentTokensLength; i++) {
1140 | var token = documentTokens.elements[i],
1141 | tf = this.tokenStore.get(token)[documentRef].tf,
1142 | idf = this.idf(token)
1143 |
1144 | documentVector.insert(this.corpusTokens.indexOf(token), tf * idf)
1145 | };
1146 |
1147 | return documentVector
1148 | }
1149 |
1150 | /**
1151 | * Returns a representation of the index ready for serialisation.
1152 | *
1153 | * @returns {Object}
1154 | * @memberOf Index
1155 | */
1156 | lunr.Index.prototype.toJSON = function () {
1157 | return {
1158 | version: lunr.version,
1159 | fields: this._fields,
1160 | ref: this._ref,
1161 | documentStore: this.documentStore.toJSON(),
1162 | tokenStore: this.tokenStore.toJSON(),
1163 | corpusTokens: this.corpusTokens.toJSON(),
1164 | pipeline: this.pipeline.toJSON()
1165 | }
1166 | }
1167 |
1168 | /**
1169 | * Applies a plugin to the current index.
1170 | *
1171 | * A plugin is a function that is called with the index as its context.
1172 | * Plugins can be used to customise or extend the behaviour the index
1173 | * in some way. A plugin is just a function, that encapsulated the custom
1174 | * behaviour that should be applied to the index.
1175 | *
1176 | * The plugin function will be called with the index as its argument, additional
1177 | * arguments can also be passed when calling use. The function will be called
1178 | * with the index as its context.
1179 | *
1180 | * Example:
1181 | *
1182 | * var myPlugin = function (idx, arg1, arg2) {
1183 | * // `this` is the index to be extended
1184 | * // apply any extensions etc here.
1185 | * }
1186 | *
1187 | * var idx = lunr(function () {
1188 | * this.use(myPlugin, 'arg1', 'arg2')
1189 | * })
1190 | *
1191 | * @param {Function} plugin The plugin to apply.
1192 | * @memberOf Index
1193 | */
1194 | lunr.Index.prototype.use = function (plugin) {
1195 | var args = Array.prototype.slice.call(arguments, 1)
1196 | args.unshift(this)
1197 | plugin.apply(this, args)
1198 | }
1199 | /*!
1200 | * lunr.Store
1201 | * Copyright (C) 2014 Oliver Nightingale
1202 | */
1203 |
1204 | /**
1205 | * lunr.Store is a simple key-value store used for storing sets of tokens for
1206 | * documents stored in index.
1207 | *
1208 | * @constructor
1209 | * @module
1210 | */
1211 | lunr.Store = function () {
1212 | this.store = {}
1213 | this.length = 0
1214 | }
1215 |
1216 | /**
1217 | * Loads a previously serialised store
1218 | *
1219 | * @param {Object} serialisedData The serialised store to load.
1220 | * @returns {lunr.Store}
1221 | * @memberOf Store
1222 | */
1223 | lunr.Store.load = function (serialisedData) {
1224 | var store = new this
1225 |
1226 | store.length = serialisedData.length
1227 | store.store = Object.keys(serialisedData.store).reduce(function (memo, key) {
1228 | memo[key] = lunr.SortedSet.load(serialisedData.store[key])
1229 | return memo
1230 | }, {})
1231 |
1232 | return store
1233 | }
1234 |
1235 | /**
1236 | * Stores the given tokens in the store against the given id.
1237 | *
1238 | * @param {Object} id The key used to store the tokens against.
1239 | * @param {Object} tokens The tokens to store against the key.
1240 | * @memberOf Store
1241 | */
1242 | lunr.Store.prototype.set = function (id, tokens) {
1243 | this.store[id] = tokens
1244 | this.length = Object.keys(this.store).length
1245 | }
1246 |
1247 | /**
1248 | * Retrieves the tokens from the store for a given key.
1249 | *
1250 | * @param {Object} id The key to lookup and retrieve from the store.
1251 | * @returns {Object}
1252 | * @memberOf Store
1253 | */
1254 | lunr.Store.prototype.get = function (id) {
1255 | return this.store[id]
1256 | }
1257 |
1258 | /**
1259 | * Checks whether the store contains a key.
1260 | *
1261 | * @param {Object} id The id to look up in the store.
1262 | * @returns {Boolean}
1263 | * @memberOf Store
1264 | */
1265 | lunr.Store.prototype.has = function (id) {
1266 | return id in this.store
1267 | }
1268 |
1269 | /**
1270 | * Removes the value for a key in the store.
1271 | *
1272 | * @param {Object} id The id to remove from the store.
1273 | * @memberOf Store
1274 | */
1275 | lunr.Store.prototype.remove = function (id) {
1276 | if (!this.has(id)) return
1277 |
1278 | delete this.store[id]
1279 | this.length--
1280 | }
1281 |
1282 | /**
1283 | * Returns a representation of the store ready for serialisation.
1284 | *
1285 | * @returns {Object}
1286 | * @memberOf Store
1287 | */
1288 | lunr.Store.prototype.toJSON = function () {
1289 | return {
1290 | store: this.store,
1291 | length: this.length
1292 | }
1293 | }
1294 |
1295 | /*!
1296 | * lunr.stemmer
1297 | * Copyright (C) 2014 Oliver Nightingale
1298 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
1299 | */
1300 |
1301 | /**
1302 | * lunr.stemmer is an english language stemmer, this is a JavaScript
1303 | * implementation of the PorterStemmer taken from http://tartaurs.org/~martin
1304 | *
1305 | * @module
1306 | * @param {String} str The string to stem
1307 | * @returns {String}
1308 | * @see lunr.Pipeline
1309 | */
1310 | lunr.stemmer = (function(){
1311 | var step2list = {
1312 | "ational" : "ate",
1313 | "tional" : "tion",
1314 | "enci" : "ence",
1315 | "anci" : "ance",
1316 | "izer" : "ize",
1317 | "bli" : "ble",
1318 | "alli" : "al",
1319 | "entli" : "ent",
1320 | "eli" : "e",
1321 | "ousli" : "ous",
1322 | "ization" : "ize",
1323 | "ation" : "ate",
1324 | "ator" : "ate",
1325 | "alism" : "al",
1326 | "iveness" : "ive",
1327 | "fulness" : "ful",
1328 | "ousness" : "ous",
1329 | "aliti" : "al",
1330 | "iviti" : "ive",
1331 | "biliti" : "ble",
1332 | "logi" : "log"
1333 | },
1334 |
1335 | step3list = {
1336 | "icate" : "ic",
1337 | "ative" : "",
1338 | "alize" : "al",
1339 | "iciti" : "ic",
1340 | "ical" : "ic",
1341 | "ful" : "",
1342 | "ness" : ""
1343 | },
1344 |
1345 | c = "[^aeiou]", // consonant
1346 | v = "[aeiouy]", // vowel
1347 | C = c + "[^aeiouy]*", // consonant sequence
1348 | V = v + "[aeiou]*", // vowel sequence
1349 |
1350 | mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
1351 | meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
1352 | mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
1353 | s_v = "^(" + C + ")?" + v; // vowel in stem
1354 |
1355 | return function (w) {
1356 | var stem,
1357 | suffix,
1358 | firstch,
1359 | re,
1360 | re2,
1361 | re3,
1362 | re4;
1363 |
1364 | if (w.length < 3) { return w; }
1365 |
1366 | firstch = w.substr(0,1);
1367 | if (firstch == "y") {
1368 | w = firstch.toUpperCase() + w.substr(1);
1369 | }
1370 |
1371 | // Step 1a
1372 | re = /^(.+?)(ss|i)es$/;
1373 | re2 = /^(.+?)([^s])s$/;
1374 |
1375 | if (re.test(w)) { w = w.replace(re,"$1$2"); }
1376 | else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
1377 |
1378 | // Step 1b
1379 | re = /^(.+?)eed$/;
1380 | re2 = /^(.+?)(ed|ing)$/;
1381 | if (re.test(w)) {
1382 | var fp = re.exec(w);
1383 | re = new RegExp(mgr0);
1384 | if (re.test(fp[1])) {
1385 | re = /.$/;
1386 | w = w.replace(re,"");
1387 | }
1388 | } else if (re2.test(w)) {
1389 | var fp = re2.exec(w);
1390 | stem = fp[1];
1391 | re2 = new RegExp(s_v);
1392 | if (re2.test(stem)) {
1393 | w = stem;
1394 | re2 = /(at|bl|iz)$/;
1395 | re3 = new RegExp("([^aeiouylsz])\\1$");
1396 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1397 | if (re2.test(w)) { w = w + "e"; }
1398 | else if (re3.test(w)) { re = /.$/; w = w.replace(re,""); }
1399 | else if (re4.test(w)) { w = w + "e"; }
1400 | }
1401 | }
1402 |
1403 | // Step 1c
1404 | re = /^(.+?)y$/;
1405 | if (re.test(w)) {
1406 | var fp = re.exec(w);
1407 | stem = fp[1];
1408 | re = new RegExp(s_v);
1409 | if (re.test(stem)) { w = stem + "i"; }
1410 | }
1411 |
1412 | // Step 2
1413 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
1414 | if (re.test(w)) {
1415 | var fp = re.exec(w);
1416 | stem = fp[1];
1417 | suffix = fp[2];
1418 | re = new RegExp(mgr0);
1419 | if (re.test(stem)) {
1420 | w = stem + step2list[suffix];
1421 | }
1422 | }
1423 |
1424 | // Step 3
1425 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
1426 | if (re.test(w)) {
1427 | var fp = re.exec(w);
1428 | stem = fp[1];
1429 | suffix = fp[2];
1430 | re = new RegExp(mgr0);
1431 | if (re.test(stem)) {
1432 | w = stem + step3list[suffix];
1433 | }
1434 | }
1435 |
1436 | // Step 4
1437 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
1438 | re2 = /^(.+?)(s|t)(ion)$/;
1439 | if (re.test(w)) {
1440 | var fp = re.exec(w);
1441 | stem = fp[1];
1442 | re = new RegExp(mgr1);
1443 | if (re.test(stem)) {
1444 | w = stem;
1445 | }
1446 | } else if (re2.test(w)) {
1447 | var fp = re2.exec(w);
1448 | stem = fp[1] + fp[2];
1449 | re2 = new RegExp(mgr1);
1450 | if (re2.test(stem)) {
1451 | w = stem;
1452 | }
1453 | }
1454 |
1455 | // Step 5
1456 | re = /^(.+?)e$/;
1457 | if (re.test(w)) {
1458 | var fp = re.exec(w);
1459 | stem = fp[1];
1460 | re = new RegExp(mgr1);
1461 | re2 = new RegExp(meq1);
1462 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
1463 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
1464 | w = stem;
1465 | }
1466 | }
1467 |
1468 | re = /ll$/;
1469 | re2 = new RegExp(mgr1);
1470 | if (re.test(w) && re2.test(w)) {
1471 | re = /.$/;
1472 | w = w.replace(re,"");
1473 | }
1474 |
1475 | // and turn initial Y back to y
1476 |
1477 | if (firstch == "y") {
1478 | w = firstch.toLowerCase() + w.substr(1);
1479 | }
1480 |
1481 | return w;
1482 | }
1483 | })();
1484 |
1485 | lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
1486 | /*!
1487 | * lunr.stopWordFilter
1488 | * Copyright (C) 2014 Oliver Nightingale
1489 | */
1490 |
1491 | /**
1492 | * lunr.stopWordFilter is an English language stop word list filter, any words
1493 | * contained in the list will not be passed through the filter.
1494 | *
1495 | * This is intended to be used in the Pipeline. If the token does not pass the
1496 | * filter then undefined will be returned.
1497 | *
1498 | * @module
1499 | * @param {String} token The token to pass through the filter
1500 | * @returns {String}
1501 | * @see lunr.Pipeline
1502 | */
1503 | lunr.stopWordFilter = function (token) {
1504 | if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token
1505 | }
1506 |
1507 | lunr.stopWordFilter.stopWords = new lunr.SortedSet
1508 | lunr.stopWordFilter.stopWords.length = 119
1509 | lunr.stopWordFilter.stopWords.elements = [
1510 | "",
1511 | "a",
1512 | "able",
1513 | "about",
1514 | "across",
1515 | "after",
1516 | "all",
1517 | "almost",
1518 | "also",
1519 | "am",
1520 | "among",
1521 | "an",
1522 | "and",
1523 | "any",
1524 | "are",
1525 | "as",
1526 | "at",
1527 | "be",
1528 | "because",
1529 | "been",
1530 | "but",
1531 | "by",
1532 | "can",
1533 | "cannot",
1534 | "could",
1535 | "dear",
1536 | "did",
1537 | "do",
1538 | "does",
1539 | "either",
1540 | "else",
1541 | "ever",
1542 | "every",
1543 | "for",
1544 | "from",
1545 | "get",
1546 | "got",
1547 | "had",
1548 | "has",
1549 | "have",
1550 | "he",
1551 | "her",
1552 | "hers",
1553 | "him",
1554 | "his",
1555 | "how",
1556 | "however",
1557 | "i",
1558 | "if",
1559 | "in",
1560 | "into",
1561 | "is",
1562 | "it",
1563 | "its",
1564 | "just",
1565 | "least",
1566 | "let",
1567 | "like",
1568 | "likely",
1569 | "may",
1570 | "me",
1571 | "might",
1572 | "most",
1573 | "must",
1574 | "my",
1575 | "neither",
1576 | "no",
1577 | "nor",
1578 | "not",
1579 | "of",
1580 | "off",
1581 | "often",
1582 | "on",
1583 | "only",
1584 | "or",
1585 | "other",
1586 | "our",
1587 | "own",
1588 | "rather",
1589 | "said",
1590 | "say",
1591 | "says",
1592 | "she",
1593 | "should",
1594 | "since",
1595 | "so",
1596 | "some",
1597 | "than",
1598 | "that",
1599 | "the",
1600 | "their",
1601 | "them",
1602 | "then",
1603 | "there",
1604 | "these",
1605 | "they",
1606 | "this",
1607 | "tis",
1608 | "to",
1609 | "too",
1610 | "twas",
1611 | "us",
1612 | "wants",
1613 | "was",
1614 | "we",
1615 | "were",
1616 | "what",
1617 | "when",
1618 | "where",
1619 | "which",
1620 | "while",
1621 | "who",
1622 | "whom",
1623 | "why",
1624 | "will",
1625 | "with",
1626 | "would",
1627 | "yet",
1628 | "you",
1629 | "your"
1630 | ]
1631 |
1632 | lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
1633 | /*!
1634 | * lunr.trimmer
1635 | * Copyright (C) 2014 Oliver Nightingale
1636 | */
1637 |
1638 | /**
1639 | * lunr.trimmer is a pipeline function for trimming non word
1640 | * characters from the begining and end of tokens before they
1641 | * enter the index.
1642 | *
1643 | * This implementation may not work correctly for non latin
1644 | * characters and should either be removed or adapted for use
1645 | * with languages with non-latin characters.
1646 | *
1647 | * @module
1648 | * @param {String} token The token to pass through the filter
1649 | * @returns {String}
1650 | * @see lunr.Pipeline
1651 | */
1652 | lunr.trimmer = function (token) {
1653 | return token
1654 | .replace(/^\W+/, '')
1655 | .replace(/\W+$/, '')
1656 | }
1657 |
1658 | lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
1659 | /*!
1660 | * lunr.stemmer
1661 | * Copyright (C) 2014 Oliver Nightingale
1662 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
1663 | */
1664 |
1665 | /**
1666 | * lunr.TokenStore is used for efficient storing and lookup of the reverse
1667 | * index of token to document ref.
1668 | *
1669 | * @constructor
1670 | */
1671 | lunr.TokenStore = function () {
1672 | this.root = { docs: {} }
1673 | this.length = 0
1674 | }
1675 |
1676 | /**
1677 | * Loads a previously serialised token store
1678 | *
1679 | * @param {Object} serialisedData The serialised token store to load.
1680 | * @returns {lunr.TokenStore}
1681 | * @memberOf TokenStore
1682 | */
1683 | lunr.TokenStore.load = function (serialisedData) {
1684 | var store = new this
1685 |
1686 | store.root = serialisedData.root
1687 | store.length = serialisedData.length
1688 |
1689 | return store
1690 | }
1691 |
1692 | /**
1693 | * Adds a new token doc pair to the store.
1694 | *
1695 | * By default this function starts at the root of the current store, however
1696 | * it can start at any node of any token store if required.
1697 | *
1698 | * @param {String} token The token to store the doc under
1699 | * @param {Object} doc The doc to store against the token
1700 | * @param {Object} root An optional node at which to start looking for the
1701 | * correct place to enter the doc, by default the root of this lunr.TokenStore
1702 | * is used.
1703 | * @memberOf TokenStore
1704 | */
1705 | lunr.TokenStore.prototype.add = function (token, doc, root) {
1706 | var root = root || this.root,
1707 | key = token[0],
1708 | rest = token.slice(1)
1709 |
1710 | if (!(key in root)) root[key] = {docs: {}}
1711 |
1712 | if (rest.length === 0) {
1713 | root[key].docs[doc.ref] = doc
1714 | this.length += 1
1715 | return
1716 | } else {
1717 | return this.add(rest, doc, root[key])
1718 | }
1719 | }
1720 |
1721 | /**
1722 | * Checks whether this key is contained within this lunr.TokenStore.
1723 | *
1724 | * By default this function starts at the root of the current store, however
1725 | * it can start at any node of any token store if required.
1726 | *
1727 | * @param {String} token The token to check for
1728 | * @param {Object} root An optional node at which to start
1729 | * @memberOf TokenStore
1730 | */
1731 | lunr.TokenStore.prototype.has = function (token) {
1732 | if (!token) return false
1733 |
1734 | var node = this.root
1735 |
1736 | for (var i = 0; i < token.length; i++) {
1737 | if (!node[token[i]]) return false
1738 |
1739 | node = node[token[i]]
1740 | }
1741 |
1742 | return true
1743 | }
1744 |
1745 | /**
1746 | * Retrieve a node from the token store for a given token.
1747 | *
1748 | * By default this function starts at the root of the current store, however
1749 | * it can start at any node of any token store if required.
1750 | *
1751 | * @param {String} token The token to get the node for.
1752 | * @param {Object} root An optional node at which to start.
1753 | * @returns {Object}
1754 | * @see TokenStore.prototype.get
1755 | * @memberOf TokenStore
1756 | */
1757 | lunr.TokenStore.prototype.getNode = function (token) {
1758 | if (!token) return {}
1759 |
1760 | var node = this.root
1761 |
1762 | for (var i = 0; i < token.length; i++) {
1763 | if (!node[token[i]]) return {}
1764 |
1765 | node = node[token[i]]
1766 | }
1767 |
1768 | return node
1769 | }
1770 |
1771 | /**
1772 | * Retrieve the documents for a node for the given token.
1773 | *
1774 | * By default this function starts at the root of the current store, however
1775 | * it can start at any node of any token store if required.
1776 | *
1777 | * @param {String} token The token to get the documents for.
1778 | * @param {Object} root An optional node at which to start.
1779 | * @returns {Object}
1780 | * @memberOf TokenStore
1781 | */
1782 | lunr.TokenStore.prototype.get = function (token, root) {
1783 | return this.getNode(token, root).docs || {}
1784 | }
1785 |
1786 | lunr.TokenStore.prototype.count = function (token, root) {
1787 | return Object.keys(this.get(token, root)).length
1788 | }
1789 |
1790 | /**
1791 | * Remove the document identified by ref from the token in the store.
1792 | *
1793 | * By default this function starts at the root of the current store, however
1794 | * it can start at any node of any token store if required.
1795 | *
1796 | * @param {String} token The token to get the documents for.
1797 | * @param {String} ref The ref of the document to remove from this token.
1798 | * @param {Object} root An optional node at which to start.
1799 | * @returns {Object}
1800 | * @memberOf TokenStore
1801 | */
1802 | lunr.TokenStore.prototype.remove = function (token, ref) {
1803 | if (!token) return
1804 | var node = this.root
1805 |
1806 | for (var i = 0; i < token.length; i++) {
1807 | if (!(token[i] in node)) return
1808 | node = node[token[i]]
1809 | }
1810 |
1811 | delete node.docs[ref]
1812 | }
1813 |
1814 | /**
1815 | * Find all the possible suffixes of the passed token using tokens
1816 | * currently in the store.
1817 | *
1818 | * @param {String} token The token to expand.
1819 | * @returns {Array}
1820 | * @memberOf TokenStore
1821 | */
1822 | lunr.TokenStore.prototype.expand = function (token, memo) {
1823 | var root = this.getNode(token),
1824 | docs = root.docs || {},
1825 | memo = memo || []
1826 |
1827 | if (Object.keys(docs).length) memo.push(token)
1828 |
1829 | Object.keys(root)
1830 | .forEach(function (key) {
1831 | if (key === 'docs') return
1832 |
1833 | memo.concat(this.expand(token + key, memo))
1834 | }, this)
1835 |
1836 | return memo
1837 | }
1838 |
1839 | /**
1840 | * Returns a representation of the token store ready for serialisation.
1841 | *
1842 | * @returns {Object}
1843 | * @memberOf TokenStore
1844 | */
1845 | lunr.TokenStore.prototype.toJSON = function () {
1846 | return {
1847 | root: this.root,
1848 | length: this.length
1849 | }
1850 | }
1851 |
1852 |
1853 | /**
1854 | * export the module via AMD, CommonnJS or as a browser global
1855 | * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
1856 | */
1857 | ;(function (root, factory) {
1858 | if (typeof define === 'function' && define.amd) {
1859 | // AMD. Register as an anonymous module.
1860 | define(factory)
1861 | } else if (typeof exports === 'object') {
1862 | /**
1863 | * Node. Does not work with strict CommonJS, but
1864 | * only CommonJS-like enviroments that support module.exports,
1865 | * like Node.
1866 | */
1867 | module.exports = factory()
1868 | } else {
1869 | // Browser globals (root is window)
1870 | root.lunr = factory()
1871 | }
1872 | }(this, function () {
1873 | /**
1874 | * Just return a value to define the module export.
1875 | * This example returns an object, but the module
1876 | * can return a function as the exported value.
1877 | */
1878 | return lunr
1879 | }))
1880 | })()
1881 |
--------------------------------------------------------------------------------
/source/layouts/layout.erb:
--------------------------------------------------------------------------------
1 | <%#
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | %>
16 | <% language_tabs = current_page.data.language_tabs %>
17 |
18 |
19 |
20 |
21 |
22 |
23 | <%= current_page.data.title || "API Documentation" %>
24 |
25 | <%= stylesheet_link_tag :screen, media: :screen %>
26 | <%= stylesheet_link_tag :print, media: :print %>
27 |
28 | <% if current_page.data.search %>
29 | <%= javascript_include_tag "all" %>
30 | <% else %>
31 | <%= javascript_include_tag "all_nosearch" %>
32 | <% end %>
33 |
34 | <% if language_tabs %>
35 |
40 | <% end %>
41 |
42 |
43 |
44 |
45 |
46 | NAV
47 | <%= image_tag('navbar.png') %>
48 |
49 |
50 |