diff --git a/build.gradle b/build.gradle
index 2439c1cc..67a98120 100644
--- a/build.gradle
+++ b/build.gradle
@@ -216,6 +216,10 @@ task docsZip(type: Zip, dependsOn: [':docs:asciidoctor', ':api', ':buildSamples'
from(file('samples/rest-notes-spring-hateoas/build/asciidoc/html5')) {
into 'samples/restful-notes'
}
+
+ from(file('samples/rest-notes-slate/build/docs')) {
+ into 'samples/slate'
+ }
}
configurations {
diff --git a/buildSrc/src/main/groovy/org/springframework/restdocs/build/SampleBuildConfigurer.groovy b/buildSrc/src/main/groovy/org/springframework/restdocs/build/SampleBuildConfigurer.groovy
index 5ddd17ab..c960ee1e 100644
--- a/buildSrc/src/main/groovy/org/springframework/restdocs/build/SampleBuildConfigurer.groovy
+++ b/buildSrc/src/main/groovy/org/springframework/restdocs/build/SampleBuildConfigurer.groovy
@@ -29,6 +29,8 @@ public class SampleBuildConfigurer {
private String workingDir
+ private boolean verifyIncludes = true
+
SampleBuildConfigurer(String name) {
this.name = name
}
@@ -39,24 +41,34 @@ public class SampleBuildConfigurer {
Task createTask(Project project, Object... dependencies) {
File sampleDir = new File(this.workingDir).absoluteFile
- Task verifyIncludes
+ Task sampleBuild = project.tasks.create name
+ sampleBuild.description = "Builds the ${name} sample"
+ sampleBuild.group = "Build"
if (new File(sampleDir, 'build.gradle').isFile()) {
Task gradleBuild = createGradleBuild(project, dependencies)
- verifyIncludes = createVerifyIncludes(project, new File(sampleDir, 'build/asciidoc'))
- verifyIncludes.dependsOn gradleBuild
+ if (verifyIncludes) {
+ Task verifyIncludesTask = createVerifyIncludes(project, new File(sampleDir, 'build/asciidoc'))
+ verifyIncludesTask.dependsOn gradleBuild
+ sampleBuild.dependsOn verifyIncludesTask
+ }
+ else {
+ sampleBuild.dependsOn gradleBuild
+ }
}
else if (new File(sampleDir, 'pom.xml').isFile()) {
Task mavenBuild = createMavenBuild(project, sampleDir, dependencies)
- verifyIncludes = createVerifyIncludes(project, new File(sampleDir, 'target/generated-docs'))
- verifyIncludes.dependsOn(mavenBuild)
+ if (verifyIncludes) {
+ Task verifyIncludesTask = createVerifyIncludes(project, new File(sampleDir, 'target/generated-docs'))
+ verifyIncludesTask.dependsOn(mavenBuild)
+ sampleBuild.dependsOn verifyIncludesTask
+ }
+ else {
+ sampleBuild.dependsOn mavenBuild
+ }
}
else {
throw new IllegalStateException("No pom.xml or build.gradle was found in $sampleDir")
}
- Task sampleBuild = project.tasks.create name
- sampleBuild.description = "Builds the ${name} sample"
- sampleBuild.group = "Build"
- sampleBuild.dependsOn verifyIncludes
return sampleBuild
}
@@ -108,9 +120,9 @@ public class SampleBuildConfigurer {
}
private Task createVerifyIncludes(Project project, File buildDir) {
- Task verifyIncludes = project.tasks.create("${name}VerifyIncludes")
- verifyIncludes.description = "Verifies the includes in the ${name} sample"
- verifyIncludes << {
+ Task verifyIncludesTask = project.tasks.create("${name}VerifyIncludes")
+ verifyIncludesTask.description = "Verifies the includes in the ${name} sample"
+ verifyIncludesTask << {
Map unprocessedIncludes = [:]
buildDir.eachFileRecurse { file ->
if (file.name.endsWith('.html')) {
@@ -132,6 +144,6 @@ public class SampleBuildConfigurer {
throw new GradleException(message.toString())
}
}
- return verifyIncludes
+ return verifyIncludesTask
}
}
\ No newline at end of file
diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc
index 485dec89..67d7dc17 100644
--- a/docs/src/docs/asciidoc/getting-started.adoc
+++ b/docs/src/docs/asciidoc/getting-started.adoc
@@ -16,21 +16,26 @@ If you want to jump straight in, a number of sample applications are available:
| {samples}/rest-assured[REST Assured]
| Gradle
-| Demonstrates the use of Spring REST Docs with REST Assured.
+| Demonstrates the use of Spring REST Docs with http://rest-assured.io[REST Assured].
+
+| {samples}/rest-notes-slate[Slate]
+| Gradle
+| Demonstrates the use of Spring REST Docs with Markdown and
+ http://github.com/tripit/slate[Slate].
| {samples}/rest-notes-spring-data-rest[Spring Data REST]
| Maven
| Demonstrates the creation of a getting started guide and an API guide for a service
- implemented using Spring Data REST.
+ implemented using http://projects.spring.io/spring-data-rest/[Spring Data REST].
| {samples}/rest-notes-spring-hateoas[Spring HATEOAS]
| Gradle
| Demonstrates the creation of a getting started guide and an API guide for a service
- implemented using Spring HATEOAS.
+ implemented using http://projects.spring.io/spring-hateoas/[Spring HATEOAS].
| {samples}/testng[TestNG]
| Gradle
-| Demonstrates the use of Spring REST Docs with TestNG.
+| Demonstrates the use of Spring REST Docs with http://testng.org[TestNG].
|===
diff --git a/samples/rest-notes-slate/README.md b/samples/rest-notes-slate/README.md
new file mode 100644
index 00000000..bb4de692
--- /dev/null
+++ b/samples/rest-notes-slate/README.md
@@ -0,0 +1,24 @@
+# REST Notes Slate
+
+This sample shows how to document a RESTful API using Spring REST Docs and [Slate][1].
+The sample can be built with Gradle, but requires Ruby and the `bundler` gem to
+be installed.
+
+## Quickstart
+
+```
+./gradlew build
+open build/docs/api-guide.html
+```
+
+## Details
+
+The bulk of the documentation is written in Markdown in the file named
+[slate/api-guide.md.erb][2]. When the documentation is built, snippets generated by
+Spring REST Docs in the [ApiDocumentation][3] tests are incorporated into this
+documentation by [ERB][4]. The combined Markdown document is then turned into HTML.
+
+[1]: https://github.com/tripit/slate
+[2]: slate/api-guide.md.erb
+[3]: src/test/java/com/example/notes/ApiDocumentation.java
+[4]: http://ruby-doc.org/stdlib-2.2.3/libdoc/erb/rdoc/ERB.html
\ No newline at end of file
diff --git a/samples/rest-notes-slate/build.gradle b/samples/rest-notes-slate/build.gradle
new file mode 100644
index 00000000..79788c3d
--- /dev/null
+++ b/samples/rest-notes-slate/build.gradle
@@ -0,0 +1,64 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'spring-boot'
+apply plugin: 'eclipse'
+
+repositories {
+ mavenLocal()
+ maven { url 'https://repo.spring.io/libs-snapshot' }
+ mavenCentral()
+}
+
+group = 'com.example'
+
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+ext {
+ snippetsDir = file('build/generated-snippets')
+ springRestdocsVersion = '1.1.0.BUILD-SNAPSHOT'
+}
+
+dependencies {
+ compile 'org.springframework.boot:spring-boot-starter-data-jpa'
+ compile 'org.springframework.boot:spring-boot-starter-data-rest'
+
+ runtime 'com.h2database:h2'
+ runtime 'org.atteo:evo-inflector:1.2.1'
+
+ testCompile 'com.jayway.jsonpath:json-path'
+ testCompile 'org.springframework.boot:spring-boot-starter-test'
+ testCompile "org.springframework.restdocs:spring-restdocs-mockmvc:$springRestdocsVersion"
+}
+
+test {
+ outputs.dir snippetsDir
+}
+
+task(bundleInstall, type: Exec) {
+ workingDir file('slate')
+ executable 'bundle'
+ args 'install'
+}
+
+task(slate, type: Exec) {
+ dependsOn 'bundleInstall', 'test'
+ workingDir file('slate')
+ executable 'bundle'
+ args 'exec', 'middleman', 'build'
+}
+
+build {
+ dependsOn 'slate'
+}
+
+eclipseJdt.onlyIf { false }
+cleanEclipseJdt.onlyIf { false }
diff --git a/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.jar b/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e8c6bf7b
Binary files /dev/null and b/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.properties b/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..90a0f04c
--- /dev/null
+++ b/samples/rest-notes-slate/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Oct 15 14:25:16 BST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
diff --git a/samples/rest-notes-slate/gradlew b/samples/rest-notes-slate/gradlew
new file mode 100755
index 00000000..97fac783
--- /dev/null
+++ b/samples/rest-notes-slate/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples/rest-notes-slate/gradlew.bat b/samples/rest-notes-slate/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/samples/rest-notes-slate/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/rest-notes-slate/slate/.gitignore b/samples/rest-notes-slate/slate/.gitignore
new file mode 100644
index 00000000..f6fc8c00
--- /dev/null
+++ b/samples/rest-notes-slate/slate/.gitignore
@@ -0,0 +1,22 @@
+*.gem
+*.rbc
+.bundle
+.config
+coverage
+InstalledFiles
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+*.DS_STORE
+build/
+.cache
+
+# YARD artifacts
+.yardoc
+_yardoc
+doc/
+.idea/
\ No newline at end of file
diff --git a/samples/rest-notes-slate/slate/CHANGELOG.md b/samples/rest-notes-slate/slate/CHANGELOG.md
new file mode 100644
index 00000000..ee496db6
--- /dev/null
+++ b/samples/rest-notes-slate/slate/CHANGELOG.md
@@ -0,0 +1,49 @@
+# Changelog
+
+## Version 1.2
+
+*June 20, 2015*
+
+**Fixes:**
+
+- Remove crash on invalid languages
+- Update Tocify to scroll to the highlighted header in the Table of Contents
+- Fix variable leak and update search algorithms
+- Update Python examples to be valid Python
+- Update gems
+- More misc. bugfixes of Javascript errors
+- Add Dockerfile
+- Remove unused gems
+- Optimize images, fonts, and generated asset files
+- Add chinese font support
+- Remove RedCarpet header ID patch
+- Update language tabs to not disturb existing query strings
+
+## Version 1.1
+
+*July 27th, 2014*
+
+**Fixes:**
+
+- Finally, a fix for the redcarpet upgrade bug
+
+## Version 1.0
+
+*July 2, 2014*
+
+[View Issues](https://github.com/tripit/slate/issues?milestone=1&state=closed)
+
+**Features:**
+
+- Responsive designs for phones and tablets
+- Started tagging versions
+
+**Fixes:**
+
+- Fixed 'unrecognized expression' error
+- Fixed #undefined hash bug
+- Fixed bug where the current language tab would be unselected
+- Fixed bug where tocify wouldn't highlight the current section while searching
+- Fixed bug where ids of header tags would have special characters that caused problems
+- Updated layout so that pages with disabled search wouldn't load search.js
+- Cleaned up Javascript
diff --git a/samples/rest-notes-slate/slate/Gemfile b/samples/rest-notes-slate/slate/Gemfile
new file mode 100644
index 00000000..0933b9d6
--- /dev/null
+++ b/samples/rest-notes-slate/slate/Gemfile
@@ -0,0 +1,12 @@
+source 'https://rubygems.org'
+
+# Middleman
+gem 'middleman', '~>3.3.10'
+gem 'middleman-gh-pages', '~> 0.0.3'
+gem 'middleman-syntax', '~> 2.0.0'
+gem 'middleman-autoprefixer', '~> 2.4.4'
+gem 'rouge', '~> 1.9.0'
+gem 'redcarpet', '~> 3.3.2'
+
+gem 'rake', '~> 10.4.2'
+gem 'therubyracer', '~> 0.12.1', platforms: :ruby
diff --git a/samples/rest-notes-slate/slate/Gemfile.lock b/samples/rest-notes-slate/slate/Gemfile.lock
new file mode 100644
index 00000000..fff5ee10
--- /dev/null
+++ b/samples/rest-notes-slate/slate/Gemfile.lock
@@ -0,0 +1,140 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ activesupport (4.1.11)
+ i18n (~> 0.6, >= 0.6.9)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.1)
+ tzinfo (~> 1.1)
+ autoprefixer-rails (5.2.0.1)
+ execjs
+ json
+ celluloid (0.16.0)
+ timers (~> 4.0.0)
+ chunky_png (1.3.4)
+ coffee-script (2.4.1)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.9.1.1)
+ compass (1.0.3)
+ chunky_png (~> 1.2)
+ compass-core (~> 1.0.2)
+ compass-import-once (~> 1.0.5)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ sass (>= 3.3.13, < 3.5)
+ compass-core (1.0.3)
+ multi_json (~> 1.0)
+ sass (>= 3.3.0, < 3.5)
+ compass-import-once (1.0.5)
+ sass (>= 3.2, < 3.5)
+ erubis (2.7.0)
+ execjs (2.5.2)
+ ffi (1.9.8)
+ haml (4.0.6)
+ tilt
+ hike (1.2.3)
+ hitimes (1.2.2)
+ hooks (0.4.0)
+ uber (~> 0.0.4)
+ i18n (0.7.0)
+ json (1.8.3)
+ kramdown (1.7.0)
+ libv8 (3.16.14.7)
+ listen (2.10.1)
+ celluloid (~> 0.16.0)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ middleman (3.3.12)
+ coffee-script (~> 2.2)
+ compass (>= 1.0.0, < 2.0.0)
+ compass-import-once (= 1.0.5)
+ execjs (~> 2.0)
+ haml (>= 4.0.5)
+ kramdown (~> 1.2)
+ middleman-core (= 3.3.12)
+ middleman-sprockets (>= 3.1.2)
+ sass (>= 3.4.0, < 4.0)
+ uglifier (~> 2.5)
+ middleman-autoprefixer (2.4.4)
+ autoprefixer-rails (~> 5.2.0)
+ middleman-core (>= 3.3.3)
+ middleman-core (3.3.12)
+ activesupport (~> 4.1.0)
+ bundler (~> 1.1)
+ erubis
+ hooks (~> 0.3)
+ i18n (~> 0.7.0)
+ listen (>= 2.7.9, < 3.0)
+ padrino-helpers (~> 0.12.3)
+ rack (>= 1.4.5, < 2.0)
+ rack-test (~> 0.6.2)
+ thor (>= 0.15.2, < 2.0)
+ tilt (~> 1.4.1, < 2.0)
+ middleman-gh-pages (0.0.3)
+ rake (> 0.9.3)
+ middleman-sprockets (3.4.2)
+ middleman-core (>= 3.3)
+ sprockets (~> 2.12.1)
+ sprockets-helpers (~> 1.1.0)
+ sprockets-sass (~> 1.3.0)
+ middleman-syntax (2.0.0)
+ middleman-core (~> 3.2)
+ rouge (~> 1.0)
+ minitest (5.7.0)
+ multi_json (1.11.1)
+ padrino-helpers (0.12.5)
+ i18n (~> 0.6, >= 0.6.7)
+ padrino-support (= 0.12.5)
+ tilt (~> 1.4.1)
+ padrino-support (0.12.5)
+ activesupport (>= 3.1)
+ rack (1.6.4)
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rake (10.4.2)
+ rb-fsevent (0.9.5)
+ rb-inotify (0.9.5)
+ ffi (>= 0.5.0)
+ redcarpet (3.3.2)
+ ref (1.0.5)
+ rouge (1.9.0)
+ sass (3.4.14)
+ sprockets (2.12.3)
+ hike (~> 1.2)
+ multi_json (~> 1.0)
+ rack (~> 1.0)
+ tilt (~> 1.1, != 1.3.0)
+ sprockets-helpers (1.1.0)
+ sprockets (~> 2.0)
+ sprockets-sass (1.3.1)
+ sprockets (~> 2.0)
+ tilt (~> 1.1)
+ therubyracer (0.12.2)
+ libv8 (~> 3.16.14.0)
+ ref
+ thor (0.19.1)
+ thread_safe (0.3.5)
+ tilt (1.4.1)
+ timers (4.0.1)
+ hitimes
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+ uber (0.0.13)
+ uglifier (2.7.1)
+ execjs (>= 0.3.0)
+ json (>= 1.8.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ middleman (~> 3.3.10)
+ middleman-autoprefixer (~> 2.4.4)
+ middleman-gh-pages (~> 0.0.3)
+ middleman-syntax (~> 2.0.0)
+ rake (~> 10.4.2)
+ redcarpet (~> 3.3.2)
+ rouge (~> 1.9.0)
+ therubyracer (~> 0.12.1)
diff --git a/samples/rest-notes-slate/slate/LICENSE b/samples/rest-notes-slate/slate/LICENSE
new file mode 100644
index 00000000..5ceddf59
--- /dev/null
+++ b/samples/rest-notes-slate/slate/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2008-2013 Concur Technologies, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may
+not use this file except in compliance with the License. You may obtain
+a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
\ No newline at end of file
diff --git a/samples/rest-notes-slate/slate/README.md b/samples/rest-notes-slate/slate/README.md
new file mode 100644
index 00000000..2f042e7f
--- /dev/null
+++ b/samples/rest-notes-slate/slate/README.md
@@ -0,0 +1,128 @@
+Slate
+========
+
+[](https://travis-ci.org/tripit/slate) [](https://gemnasium.com/tripit/slate)
+
+Slate helps you create beautiful API documentation. Think of it as an intelligent, responsive documentation template for your API.
+
+
+
+*The example above was created with Slate. Check it out at [tripit.github.io/slate](http://tripit.github.io/slate).*
+
+Features
+------------
+
+* **Clean, intuitive design** — with Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [Paypal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even print.
+
+* **Everything on a single page** — gone are the days where your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy.
+
+* **Slate is just Markdown** — when you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks!
+
+* **Write code samples in multiple languages** — if your API has bindings in multiple programming languages, you easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with Github Flavored Markdown!
+
+* **Out-of-the-box syntax highlighting** for [almost 60 languages](http://rouge.jayferd.us/demo), no configuration required.
+
+* **Automatic, smoothly scrolling table of contents** on the far left of the page. As you scroll, it displays your current position in the document. It's fast, too. We're using Slate at TripIt to build documentation for our new API, where our table of contents has over 180 entries. We've made sure that the performance remains excellent, even for larger documents.
+
+* **Let your users update your documentation for you** — by default, your Slate-generated documentation is hosted in a public Github repository. Not only does this mean you get free hosting for your docs with Github Pages, but it also makes it's simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to, you're welcome to not use Github and host your docs elsewhere!
+
+Getting starting with Slate is super easy! Simply fork this repository, and then follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](http://tripit.github.io/slate).
+
+
+
+Getting Started with Slate
+------------------------------
+
+### Prerequisites
+
+You're going to need:
+
+ - **Linux or OS X** — Windows may work, but is unsupported.
+ - **Ruby, version 1.9.3 or newer**
+ - **Bundler** — If Ruby is already installed, but the `bundle` command doesn't work, just run `gem install bundler` in a terminal.
+
+### Getting Set Up
+
+ 1. Fork this repository on Github.
+ 2. Clone *your forked repository* (not our original one) to your hard drive with `git clone https://github.com/YOURUSERNAME/slate.git`
+ 3. `cd slate`
+ 4. Install all dependencies: `bundle install`
+ 5. Start the test server: `bundle exec middleman server`
+
+Or use the included Dockerfile! (must install Docker first)
+
+```shell
+docker build -t slate .
+docker run -d -p 4567:4567 --name slate -v $(pwd)/source:/app/source slate
+```
+
+You can now see the docs at . Whoa! That was fast!
+
+*Note: if you're using the Docker setup on OSX, the docs will be
+availalable at the output of `boot2docker ip` instead of `localhost:4567`.*
+
+Now that Slate is all set up your machine, you'll probably want to learn more about [editing Slate markdown](https://github.com/tripit/slate/wiki/Markdown-Syntax), or [how to publish your docs](https://github.com/tripit/slate/wiki/Deploying-Slate).
+
+Examples of Slate in the Wild
+---------------------------------
+
+* [Travis-CI's API docs](http://docs.travis-ci.com/api/)
+* [Mozilla's localForage docs](http://mozilla.github.io/localForage/)
+* [Mozilla Recroom](http://mozilla.github.io/recroom/)
+* [ChaiOne Gameplan API docs](http://chaione.github.io/gameplanb2b/#introduction)
+* [Drcaban's Build a Quine tutorial](http://drcabana.github.io/build-a-quine/#introduction)
+* [PricePlow API docs](https://www.priceplow.com/api/documentation)
+* [Emerging Threats API docs](http://apidocs.emergingthreats.net/)
+* [Appium docs](http://appium.io/slate/en/master)
+* [Golazon Developer](http://developer.golazon.com)
+* [Dwolla API docs](https://docs.dwolla.com/)
+* [RozpisyZapasu API docs](http://www.rozpisyzapasu.cz/dev/api/)
+* [Codestar Framework Docs](http://codestarframework.com/documentation/)
+* [Buddycloud API](http://buddycloud.com/api)
+* [Crafty Clicks API](https://craftyclicks.co.uk/api/)
+* [Paracel API Reference](http://paracel.io/docs/api_reference.html)
+* [Switch Payments Documentation](http://switchpayments.com/docs/) & [API](http://switchpayments.com/developers/)
+* [Coinbase API Reference](https://developers.coinbase.com/api)
+* [Whispir.io API](https://whispir.github.io/api)
+* [NASA API](https://data.nasa.gov/developer/external/planetary/)
+* [CardPay API](https://developers.cardpay.com/)
+* [IBM Cloudant](https://docs-testb.cloudant.com/content-review/_design/couchapp/index.html)
+* [Bitrix basis components](http://bbc.bitrix.expert/)
+* [viagogo API Documentation](http://developer.viagogo.net/)
+* [Fidor Bank API Documentation](http://docs.fidor.de/)
+* [Market Prophit API Documentation](http://developer.marketprophit.com/)
+* [OAuth.io API Documentation](http://docs.oauth.io/)
+* [Aircall for Developers](http://developer.aircall.io/)
+* [SupportKit API Docs](http://docs.supportkit.io/)
+* [SocialRadar's LocationKit Docs](https://docs.locationkit.io/)
+* [SafetyCulture API Documentation](https://developer.safetyculture.io/)
+* [hosting.de API Documentation](https://www.hosting.de/docs/api/)
+
+(Feel free to add your site to this list in a pull request!)
+
+Need Help? Found a bug?
+--------------------
+
+Just [submit a issue](https://github.com/tripit/slate/issues) to the Slate Github if you need any help. And, of course, feel free to submit pull requests with bug fixes or changes.
+
+
+Contributors
+--------------------
+
+Slate was built by [Robert Lord](https://lord.io) while at [TripIt](http://tripit.com).
+
+Thanks to the following people who have submitted major pull requests:
+
+- [@chrissrogers](https://github.com/chrissrogers)
+- [@bootstraponline](https://github.com/bootstraponline)
+- [@realityking](https://github.com/realityking)
+
+Also, thanks to [Sauce Labs](http://saucelabs.com) for helping sponsor the project.
+
+Special Thanks
+--------------------
+- [Middleman](https://github.com/middleman/middleman)
+- [jquery.tocify.js](https://github.com/gfranko/jquery.tocify.js)
+- [middleman-syntax](https://github.com/middleman/middleman-syntax)
+- [middleman-gh-pages](https://github.com/neo/middleman-gh-pages)
+- [Font Awesome](http://fortawesome.github.io/Font-Awesome/)
diff --git a/samples/rest-notes-slate/slate/Rakefile b/samples/rest-notes-slate/slate/Rakefile
new file mode 100644
index 00000000..6a952e1e
--- /dev/null
+++ b/samples/rest-notes-slate/slate/Rakefile
@@ -0,0 +1,6 @@
+require 'middleman-gh-pages'
+require 'rake/clean'
+
+CLOBBER.include('build')
+
+task :default => [:build]
diff --git a/samples/rest-notes-slate/slate/config.rb b/samples/rest-notes-slate/slate/config.rb
new file mode 100644
index 00000000..fdcb21f3
--- /dev/null
+++ b/samples/rest-notes-slate/slate/config.rb
@@ -0,0 +1,41 @@
+# Markdown
+set :markdown_engine, :redcarpet
+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
+
+# Assets
+set :css_dir, 'stylesheets'
+set :js_dir, 'javascripts'
+set :images_dir, 'images'
+set :fonts_dir, 'fonts'
+
+# Activate the syntax highlighter
+activate :syntax
+
+activate :autoprefixer do |config|
+ config.browsers = ['last 2 version', 'Firefox ESR']
+ config.cascade = false
+ config.inline = true
+end
+
+# Github pages require relative links
+activate :relative_assets
+set :relative_links, true
+
+# Build Configuration
+
+set :build_dir, '../build/docs'
+
+configure :build do
+ activate :minify_css
+ activate :minify_javascript
+ # activate :relative_assets
+ # activate :asset_hash
+ # activate :gzip
+end
diff --git a/samples/rest-notes-slate/slate/font-selection.json b/samples/rest-notes-slate/slate/font-selection.json
new file mode 100755
index 00000000..5e78f5d8
--- /dev/null
+++ b/samples/rest-notes-slate/slate/font-selection.json
@@ -0,0 +1,148 @@
+{
+ "IcoMoonType": "selection",
+ "icons": [
+ {
+ "icon": {
+ "paths": [
+ "M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z"
+ ],
+ "attrs": [],
+ "isMulticolor": false,
+ "tags": [
+ "exclamation-circle"
+ ],
+ "defaultCode": 61546,
+ "grid": 14
+ },
+ "attrs": [],
+ "properties": {
+ "id": 100,
+ "order": 4,
+ "prevSize": 28,
+ "code": 58880,
+ "name": "exclamation-sign",
+ "ligatures": ""
+ },
+ "setIdx": 0,
+ "iconIdx": 0
+ },
+ {
+ "icon": {
+ "paths": [
+ "M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
+ ],
+ "attrs": [],
+ "isMulticolor": false,
+ "tags": [
+ "info-circle"
+ ],
+ "defaultCode": 61530,
+ "grid": 14
+ },
+ "attrs": [],
+ "properties": {
+ "id": 85,
+ "order": 3,
+ "name": "info-sign",
+ "prevSize": 28,
+ "code": 58882
+ },
+ "setIdx": 0,
+ "iconIdx": 2
+ },
+ {
+ "icon": {
+ "paths": [
+ "M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
+ ],
+ "attrs": [],
+ "isMulticolor": false,
+ "tags": [
+ "check-circle"
+ ],
+ "defaultCode": 61528,
+ "grid": 14
+ },
+ "attrs": [],
+ "properties": {
+ "id": 83,
+ "order": 9,
+ "prevSize": 28,
+ "code": 58886,
+ "name": "ok-sign"
+ },
+ "setIdx": 0,
+ "iconIdx": 6
+ },
+ {
+ "icon": {
+ "paths": [
+ "M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z"
+ ],
+ "width": 951,
+ "attrs": [],
+ "isMulticolor": false,
+ "tags": [
+ "search"
+ ],
+ "defaultCode": 61442,
+ "grid": 14
+ },
+ "attrs": [],
+ "properties": {
+ "id": 2,
+ "order": 1,
+ "prevSize": 28,
+ "code": 58887,
+ "name": "icon-search"
+ },
+ "setIdx": 0,
+ "iconIdx": 7
+ }
+ ],
+ "height": 1024,
+ "metadata": {
+ "name": "slate",
+ "license": "SIL OFL 1.1"
+ },
+ "preferences": {
+ "showGlyphs": true,
+ "showQuickUse": true,
+ "showQuickUse2": true,
+ "showSVGs": true,
+ "fontPref": {
+ "prefix": "icon-",
+ "metadata": {
+ "fontFamily": "slate",
+ "majorVersion": 1,
+ "minorVersion": 0,
+ "description": "Based on FontAwesome",
+ "license": "SIL OFL 1.1"
+ },
+ "metrics": {
+ "emSize": 1024,
+ "baseline": 6.25,
+ "whitespace": 50
+ },
+ "resetPoint": 58880,
+ "showSelector": false,
+ "selector": "class",
+ "classSelector": ".icon",
+ "showMetrics": false,
+ "showMetadata": true,
+ "showVersion": true,
+ "ie7": false
+ },
+ "imagePref": {
+ "prefix": "icon-",
+ "png": true,
+ "useClassSelector": true,
+ "color": 4473924,
+ "bgColor": 16777215
+ },
+ "historySize": 100,
+ "showCodes": true,
+ "gridSize": 16,
+ "showLiga": false
+ }
+}
diff --git a/samples/rest-notes-slate/slate/source/api-guide.md.erb b/samples/rest-notes-slate/slate/source/api-guide.md.erb
new file mode 100644
index 00000000..11248a3e
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/api-guide.md.erb
@@ -0,0 +1,206 @@
+---
+title: REST Notes API Guide
+
+language_tabs:
+ - shell
+ - http
+
+search: true
+---
+
+# Overview
+
+
+## HTTP verbs
+
+RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
+use of HTTP verbs.
+
+
+Verb | Usage
+-------- | -----
+`GET` | Used to retrieve a resource
+`POST` | Used to create a new resource
+`PATCH` | Used to update an existing resource, including partial updates
+`DELETE` | Used to delete an existing resource
+
+## HTTP status codes
+
+RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
+use of HTTP status codes.
+
+Status code | Usage
+----------------- | -----
+`200 OK` | The request completed successfully
+`201 Created` | A new resource has been created successfully. The resource's URI is available from the response's `Location` header
+`204 No Content` | An update to an existing resource has been applied successfully
+`400 Bad Request` | The request was malformed. The response body will include an error providing further information
+`404 Not Found` | The requested resource did not exist
+
+## Errors
+
+<%= ERB.new(File.read("../build/generated-snippets/error-example/http-response.md")).result(binding) %>
+
+Whenever an error response (status code >= 400) is returned, the body will contain a JSON object
+that describes the problem. The error object has the following structure:
+
+<%= ERB.new(File.read("../build/generated-snippets/error-example/response-fields.md")).result(binding) %>
+
+## Hypermedia
+
+RESTful Notes uses hypermedia and resources include links to other resources in their
+responses. Responses are in [Hypertext Application Language (HAL)](http://stateless.co/hal_specification.html format).
+Links can be found beneath the `_links` key. Users of the API should not create URIs
+themselves, instead they should use the above-described links to navigate
+
+
+
+# Resources
+
+
+
+## Index
+
+<%= ERB.new(File.read("../build/generated-snippets/index-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/index-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/index-example/http-response.md")).result(binding) %>
+
+The index provides the entry point into the service. A `GET` request is used to access the index.
+
+### Response structure
+
+<%= ERB.new(File.read("../build/generated-snippets/index-example/response-fields.md")).result(binding) %>
+
+### Links
+
+<%= ERB.new(File.read("../build/generated-snippets/index-example/links.md")).result(binding) %>
+
+
+
+## Notes
+
+The Notes resources is used to create and list notes
+
+### Listing notes
+
+<%= ERB.new(File.read("../build/generated-snippets/notes-list-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/notes-list-example/http-response.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/notes-list-example/curl-request.md")).result(binding) %>
+
+A `GET` request will list all of the service's notes.
+
+#### Response structure
+
+<%= ERB.new(File.read("../build/generated-snippets/notes-list-example/response-fields.md")).result(binding) %>
+
+### Creating a note
+
+<%= ERB.new(File.read("../build/generated-snippets/notes-create-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/notes-create-example/http-response.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/notes-create-example/curl-request.md")).result(binding) %>
+
+A `POST` request is used to create a note
+
+#### Request structure
+
+<%= ERB.new(File.read("../build/generated-snippets/notes-create-example/request-fields.md")).result(binding) %>
+
+
+
+## Tags
+
+The Tags resource is used to create and list tags.
+
+### Listing tags
+
+<%= ERB.new(File.read("../build/generated-snippets/tags-list-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tags-list-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tags-list-example/http-response.md")).result(binding) %>
+
+A `GET` request will list all of the service's tags.
+
+#### Response structure
+
+<%= ERB.new(File.read("../build/generated-snippets/tags-list-example/response-fields.md")).result(binding) %>
+
+
+### Creating a tag
+
+<%= ERB.new(File.read("../build/generated-snippets/tags-create-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tags-create-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/notes-create-example/http-response.md")).result(binding) %>
+
+A `POST` request is used to create a tag
+
+#### Request structure
+
+<%= ERB.new(File.read("../build/generated-snippets/tags-create-example/request-fields.md")).result(binding) %>
+
+
+
+## Note
+
+The Note resource is used to retrieve, update, and delete individual notes
+
+### Retrieve a note
+
+<%= ERB.new(File.read("../build/generated-snippets/note-get-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/note-get-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/note-get-example/http-response.md")).result(binding) %>
+
+A `GET` request will retrieve the details of a note
+
+<%= ERB.new(File.read("../build/generated-snippets/note-get-example/response-fields.md")).result(binding) %>
+
+#### Links
+
+<%= ERB.new(File.read("../build/generated-snippets/note-get-example/links.md")).result(binding) %>
+
+### Update a note
+
+<%= ERB.new(File.read("../build/generated-snippets/note-update-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/note-update-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/note-update-example/http-response.md")).result(binding) %>
+
+A `PATCH` request is used to update a note
+
+#### Request structure
+
+<%= ERB.new(File.read("../build/generated-snippets/note-update-example/request-fields.md")).result(binding) %>
+
+To leave an attribute of a note unchanged, any of the above may be omitted from the
+request.
+
+
+
+## Tag
+
+The Tag resource is used to retrieve, update, and delete individual tags
+
+### Retrieve a tag
+
+<%= ERB.new(File.read("../build/generated-snippets/tag-get-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tag-get-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tag-get-example/http-response.md")).result(binding) %>
+
+A `GET` request will retrieve the details of a tag
+
+#### Response structure
+
+<%= ERB.new(File.read("../build/generated-snippets/tag-get-example/response-fields.md")).result(binding) %>
+
+#### Links
+
+<%= ERB.new(File.read("../build/generated-snippets/tag-get-example/links.md")).result(binding) %>
+
+### Update a tag
+
+<%= ERB.new(File.read("../build/generated-snippets/tag-update-example/curl-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tag-update-example/http-request.md")).result(binding) %>
+<%= ERB.new(File.read("../build/generated-snippets/tag-update-example/http-response.md")).result(binding) %>
+
+A `PATCH` request is used to update a tag
+
+#### Request structure
+
+<%= ERB.new(File.read("../build/generated-snippets/tag-update-example/request-fields.md")).result(binding) %>
\ No newline at end of file
diff --git a/samples/rest-notes-slate/slate/source/fonts/slate.eot b/samples/rest-notes-slate/slate/source/fonts/slate.eot
new file mode 100755
index 00000000..13c4839a
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/fonts/slate.eot differ
diff --git a/samples/rest-notes-slate/slate/source/fonts/slate.svg b/samples/rest-notes-slate/slate/source/fonts/slate.svg
new file mode 100755
index 00000000..5f349823
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/fonts/slate.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/samples/rest-notes-slate/slate/source/fonts/slate.ttf b/samples/rest-notes-slate/slate/source/fonts/slate.ttf
new file mode 100755
index 00000000..ace9a46a
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/fonts/slate.ttf differ
diff --git a/samples/rest-notes-slate/slate/source/fonts/slate.woff b/samples/rest-notes-slate/slate/source/fonts/slate.woff
new file mode 100755
index 00000000..1e72e0ee
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/fonts/slate.woff differ
diff --git a/samples/rest-notes-slate/slate/source/fonts/slate.woff2 b/samples/rest-notes-slate/slate/source/fonts/slate.woff2
new file mode 100755
index 00000000..7c585a72
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/fonts/slate.woff2 differ
diff --git a/samples/rest-notes-slate/slate/source/images/logo.png b/samples/rest-notes-slate/slate/source/images/logo.png
new file mode 100644
index 00000000..fa1f13da
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/images/logo.png differ
diff --git a/samples/rest-notes-slate/slate/source/images/navbar.png b/samples/rest-notes-slate/slate/source/images/navbar.png
new file mode 100644
index 00000000..df38e90d
Binary files /dev/null and b/samples/rest-notes-slate/slate/source/images/navbar.png differ
diff --git a/samples/rest-notes-slate/slate/source/javascripts/all.js b/samples/rest-notes-slate/slate/source/javascripts/all.js
new file mode 100644
index 00000000..ffaa9b01
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/all.js
@@ -0,0 +1,4 @@
+//= require ./lib/_energize
+//= require ./app/_lang
+//= require ./app/_search
+//= require ./app/_toc
diff --git a/samples/rest-notes-slate/slate/source/javascripts/all_nosearch.js b/samples/rest-notes-slate/slate/source/javascripts/all_nosearch.js
new file mode 100644
index 00000000..818bc4e5
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/all_nosearch.js
@@ -0,0 +1,3 @@
+//= require ./lib/_energize
+//= require ./app/_lang
+//= require ./app/_toc
diff --git a/samples/rest-notes-slate/slate/source/javascripts/app/_lang.js b/samples/rest-notes-slate/slate/source/javascripts/app/_lang.js
new file mode 100644
index 00000000..1a124bb6
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/app/_lang.js
@@ -0,0 +1,162 @@
+/*
+Copyright 2008-2013 Concur Technologies, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may
+not use this file except in compliance with the License. You may obtain
+a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+*/
+(function (global) {
+ 'use strict';
+
+ var languages = [];
+
+ global.setupLanguages = setupLanguages;
+ global.activateLanguage = activateLanguage;
+
+ function activateLanguage(language) {
+ if (!language) return;
+ if (language === "") return;
+
+ $(".lang-selector a").removeClass('active');
+ $(".lang-selector a[data-language-name='" + language + "']").addClass('active');
+ for (var i=0; i < languages.length; i++) {
+ $(".highlight." + languages[i]).hide();
+ }
+ $(".highlight." + language).show();
+
+ global.toc.calculateHeights();
+
+ // scroll to the new location of the position
+ if ($(window.location.hash).get(0)) {
+ $(window.location.hash).get(0).scrollIntoView(true);
+ }
+ }
+
+ // parseURL and stringifyURL are from https://github.com/sindresorhus/query-string
+ // MIT licensed
+ // https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license
+ function parseURL(str) {
+ if (typeof str !== 'string') {
+ return {};
+ }
+
+ str = str.trim().replace(/^(\?|#|&)/, '');
+
+ if (!str) {
+ return {};
+ }
+
+ return str.split('&').reduce(function (ret, param) {
+ var parts = param.replace(/\+/g, ' ').split('=');
+ var key = parts[0];
+ var val = parts[1];
+
+ key = decodeURIComponent(key);
+ // missing `=` should be `null`:
+ // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
+ val = val === undefined ? null : decodeURIComponent(val);
+
+ if (!ret.hasOwnProperty(key)) {
+ ret[key] = val;
+ } else if (Array.isArray(ret[key])) {
+ ret[key].push(val);
+ } else {
+ ret[key] = [ret[key], val];
+ }
+
+ return ret;
+ }, {});
+ };
+
+ function stringifyURL(obj) {
+ return obj ? Object.keys(obj).sort().map(function (key) {
+ var val = obj[key];
+
+ if (Array.isArray(val)) {
+ return val.sort().map(function (val2) {
+ return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
+ }).join('&');
+ }
+
+ return encodeURIComponent(key) + '=' + encodeURIComponent(val);
+ }).join('&') : '';
+ };
+
+ // gets the language set in the query string
+ function getLanguageFromQueryString() {
+ if (location.search.length >= 1) {
+ var language = parseURL(location.search).language
+ if (language) {
+ return language;
+ } else if (jQuery.inArray(location.search.substr(1), languages) != -1) {
+ return location.search.substr(1);
+ }
+ }
+
+ return false;
+ }
+
+ // returns a new query string with the new language in it
+ function generateNewQueryString(language) {
+ var url = parseURL(location.search);
+ if (url.language) {
+ url.language = language;
+ return stringifyURL(url);
+ }
+ return language;
+ }
+
+ // if a button is clicked, add the state to the history
+ function pushURL(language) {
+ if (!history) { return; }
+ var hash = window.location.hash;
+ if (hash) {
+ hash = hash.replace(/^#+/, '');
+ }
+ history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash);
+
+ // save language as next default
+ localStorage.setItem("language", language);
+ }
+
+ function setupLanguages(l) {
+ var defaultLanguage = localStorage.getItem("language");
+
+ languages = l;
+
+ var presetLanguage = getLanguageFromQueryString();
+ if (presetLanguage) {
+ // the language is in the URL, so use that language!
+ activateLanguage(presetLanguage);
+
+ localStorage.setItem("language", presetLanguage);
+ } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
+ // the language was the last selected one saved in localstorage, so use that language!
+ activateLanguage(defaultLanguage);
+ } else {
+ // no language selected, so use the default
+ activateLanguage(languages[0]);
+ }
+ }
+
+ // if we click on a language tab, activate that language
+ $(function() {
+ $(".lang-selector a").on("click", function() {
+ var language = $(this).data("language-name");
+ pushURL(language);
+ activateLanguage(language);
+ return false;
+ });
+ window.onpopstate = function() {
+ activateLanguage(getLanguageFromQueryString());
+ };
+ });
+})(window);
diff --git a/samples/rest-notes-slate/slate/source/javascripts/app/_search.js b/samples/rest-notes-slate/slate/source/javascripts/app/_search.js
new file mode 100644
index 00000000..91f38a04
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/app/_search.js
@@ -0,0 +1,74 @@
+//= require ../lib/_lunr
+//= require ../lib/_jquery.highlight
+(function () {
+ 'use strict';
+
+ var content, searchResults;
+ var highlightOpts = { element: 'span', className: 'search-highlight' };
+
+ var index = new lunr.Index();
+
+ index.ref('id');
+ index.field('title', { boost: 10 });
+ index.field('body');
+ index.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
+
+ $(populate);
+ $(bind);
+
+ function populate() {
+ $('h1, h2').each(function() {
+ var title = $(this);
+ var body = title.nextUntil('h1, h2');
+ index.add({
+ id: title.prop('id'),
+ title: title.text(),
+ body: body.text()
+ });
+ });
+ }
+
+ function bind() {
+ content = $('.content');
+ searchResults = $('.search-results');
+
+ $('#input-search').on('keyup', search);
+ }
+
+ function search(event) {
+ unhighlight();
+ searchResults.addClass('visible');
+
+ // ESC clears the field
+ if (event.keyCode === 27) this.value = '';
+
+ if (this.value) {
+ var results = index.search(this.value).filter(function(r) {
+ return r.score > 0.0001;
+ });
+
+ if (results.length) {
+ searchResults.empty();
+ $.each(results, function (index, result) {
+ var elem = document.getElementById(result.ref);
+ searchResults.append("
");
+ });
+ highlight.call(this);
+ } else {
+ searchResults.html('');
+ $('.search-results li').text('No Results Found for "' + this.value + '"');
+ }
+ } else {
+ unhighlight();
+ searchResults.removeClass('visible');
+ }
+ }
+
+ function highlight() {
+ if (this.value) content.highlight(this.value, highlightOpts);
+ }
+
+ function unhighlight() {
+ content.unhighlight(highlightOpts);
+ }
+})();
diff --git a/samples/rest-notes-slate/slate/source/javascripts/app/_toc.js b/samples/rest-notes-slate/slate/source/javascripts/app/_toc.js
new file mode 100644
index 00000000..bc2aa3e1
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/app/_toc.js
@@ -0,0 +1,55 @@
+//= require ../lib/_jquery_ui
+//= require ../lib/_jquery.tocify
+//= require ../lib/_imagesloaded.min
+(function (global) {
+ 'use strict';
+
+ var closeToc = function() {
+ $(".tocify-wrapper").removeClass('open');
+ $("#nav-button").removeClass('open');
+ };
+
+ var makeToc = function() {
+ global.toc = $("#toc").tocify({
+ selectors: 'h1, h2',
+ extendPage: false,
+ theme: 'none',
+ smoothScroll: false,
+ showEffectSpeed: 0,
+ hideEffectSpeed: 180,
+ ignoreSelector: '.toc-ignore',
+ highlightOffset: 60,
+ scrollTo: -1,
+ scrollHistory: true,
+ hashGenerator: function (text, element) {
+ return element.prop('id');
+ }
+ }).data('toc-tocify');
+
+ $("#nav-button").click(function() {
+ $(".tocify-wrapper").toggleClass('open');
+ $("#nav-button").toggleClass('open');
+ return false;
+ });
+
+ $(".page-wrapper").click(closeToc);
+ $(".tocify-item").click(closeToc);
+ };
+
+ // Hack to make already open sections to start opened,
+ // instead of displaying an ugly animation
+ function animate() {
+ setTimeout(function() {
+ toc.setOption('showEffectSpeed', 180);
+ }, 50);
+ }
+
+ $(function() {
+ makeToc();
+ animate();
+ $('.content').imagesLoaded( function() {
+ global.toc.calculateHeights();
+ });
+ });
+})(window);
+
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_energize.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_energize.js
new file mode 100644
index 00000000..6798f3c0
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_energize.js
@@ -0,0 +1,169 @@
+/**
+ * energize.js v0.1.0
+ *
+ * Speeds up click events on mobile devices.
+ * https://github.com/davidcalhoun/energize.js
+ */
+
+(function() { // Sandbox
+ /**
+ * Don't add to non-touch devices, which don't need to be sped up
+ */
+ if(!('ontouchstart' in window)) return;
+
+ var lastClick = {},
+ isThresholdReached, touchstart, touchmove, touchend,
+ click, closest;
+
+ /**
+ * isThresholdReached
+ *
+ * Compare touchstart with touchend xy coordinates,
+ * and only fire simulated click event if the coordinates
+ * are nearby. (don't want clicking to be confused with a swipe)
+ */
+ isThresholdReached = function(startXY, xy) {
+ return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
+ };
+
+ /**
+ * touchstart
+ *
+ * Save xy coordinates when the user starts touching the screen
+ */
+ touchstart = function(e) {
+ this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
+ this.threshold = false;
+ };
+
+ /**
+ * touchmove
+ *
+ * Check if the user is scrolling past the threshold.
+ * Have to check here because touchend will not always fire
+ * on some tested devices (Kindle Fire?)
+ */
+ touchmove = function(e) {
+ // NOOP if the threshold has already been reached
+ if(this.threshold) return false;
+
+ this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
+ };
+
+ /**
+ * touchend
+ *
+ * If the user didn't scroll past the threshold between
+ * touchstart and touchend, fire a simulated click.
+ *
+ * (This will fire before a native click)
+ */
+ touchend = function(e) {
+ // Don't fire a click if the user scrolled past the threshold
+ if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
+ return;
+ }
+
+ /**
+ * Create and fire a click event on the target element
+ * https://developer.mozilla.org/en/DOM/event.initMouseEvent
+ */
+ var touch = e.changedTouches[0],
+ evt = document.createEvent('MouseEvents');
+ evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
+ evt.simulated = true; // distinguish from a normal (nonsimulated) click
+ e.target.dispatchEvent(evt);
+ };
+
+ /**
+ * click
+ *
+ * Because we've already fired a click event in touchend,
+ * we need to listed for all native click events here
+ * and suppress them as necessary.
+ */
+ click = function(e) {
+ /**
+ * Prevent ghost clicks by only allowing clicks we created
+ * in the click event we fired (look for e.simulated)
+ */
+ var time = Date.now(),
+ timeDiff = time - lastClick.time,
+ x = e.clientX,
+ y = e.clientY,
+ xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
+ target = closest(e.target, 'A') || e.target, // needed for standalone apps
+ nodeName = target.nodeName,
+ isLink = nodeName === 'A',
+ standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
+
+ lastClick.time = time;
+ lastClick.x = x;
+ lastClick.y = y;
+
+ /**
+ * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
+ * so we have to add more logic to determine the time of the last click. Not perfect...
+ *
+ * Older, simpler check: if((!e.simulated) || standAlone)
+ */
+ if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
+ e.preventDefault();
+ e.stopPropagation();
+ if(!standAlone) return false;
+ }
+
+ /**
+ * Special logic for standalone web apps
+ * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
+ */
+ if(standAlone) {
+ window.location = target.getAttribute("href");
+ }
+
+ /**
+ * Add an energize-focus class to the targeted link (mimics :focus behavior)
+ * TODO: test and/or remove? Does this work?
+ */
+ if(!target || !target.classList) return;
+ target.classList.add("energize-focus");
+ window.setTimeout(function(){
+ target.classList.remove("energize-focus");
+ }, 150);
+ };
+
+ /**
+ * closest
+ * @param {HTMLElement} node current node to start searching from.
+ * @param {string} tagName the (uppercase) name of the tag you're looking for.
+ *
+ * Find the closest ancestor tag of a given node.
+ *
+ * Starts at node and goes up the DOM tree looking for a
+ * matching nodeName, continuing until hitting document.body
+ */
+ closest = function(node, tagName){
+ var curNode = node;
+
+ while(curNode !== document.body) { // go up the dom until we find the tag we're after
+ if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
+ curNode = curNode.parentNode; // not found, so keep going up
+ }
+
+ return null; // not found
+ };
+
+ /**
+ * Add all delegated event listeners
+ *
+ * All the events we care about bubble up to document,
+ * so we can take advantage of event delegation.
+ *
+ * Note: no need to wait for DOMContentLoaded here
+ */
+ document.addEventListener('touchstart', touchstart, false);
+ document.addEventListener('touchmove', touchmove, false);
+ document.addEventListener('touchend', touchend, false);
+ document.addEventListener('click', click, true); // TODO: why does this use capture?
+
+})();
\ No newline at end of file
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_imagesloaded.min.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_imagesloaded.min.js
new file mode 100644
index 00000000..d66f6589
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_imagesloaded.min.js
@@ -0,0 +1,7 @@
+/*!
+ * imagesLoaded PACKAGED v3.1.8
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+
+(function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s});
\ No newline at end of file
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.highlight.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.highlight.js
new file mode 100644
index 00000000..9dcf3c7a
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.highlight.js
@@ -0,0 +1,108 @@
+/*
+ * jQuery Highlight plugin
+ *
+ * Based on highlight v3 by Johann Burkard
+ * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
+ *
+ * Code a little bit refactored and cleaned (in my humble opinion).
+ * Most important changes:
+ * - has an option to highlight only entire words (wordsOnly - false by default),
+ * - has an option to be case sensitive (caseSensitive - false by default)
+ * - highlight element tag and class names can be specified in options
+ *
+ * Usage:
+ * // wrap every occurrance of text 'lorem' in content
+ * // with (default options)
+ * $('#content').highlight('lorem');
+ *
+ * // search for and highlight more terms at once
+ * // so you can save some time on traversing DOM
+ * $('#content').highlight(['lorem', 'ipsum']);
+ * $('#content').highlight('lorem ipsum');
+ *
+ * // search only for entire word 'lorem'
+ * $('#content').highlight('lorem', { wordsOnly: true });
+ *
+ * // don't ignore case during search of term 'lorem'
+ * $('#content').highlight('lorem', { caseSensitive: true });
+ *
+ * // wrap every occurrance of term 'ipsum' in content
+ * // with
+ * $('#content').highlight('ipsum', { element: 'em', className: 'important' });
+ *
+ * // remove default highlight
+ * $('#content').unhighlight();
+ *
+ * // remove custom highlight
+ * $('#content').unhighlight({ element: 'em', className: 'important' });
+ *
+ *
+ * Copyright (c) 2009 Bartek Szopka
+ *
+ * Licensed under MIT license.
+ *
+ */
+
+jQuery.extend({
+ highlight: function (node, re, nodeName, className) {
+ if (node.nodeType === 3) {
+ var match = node.data.match(re);
+ if (match) {
+ var highlight = document.createElement(nodeName || 'span');
+ highlight.className = className || 'highlight';
+ var wordNode = node.splitText(match.index);
+ wordNode.splitText(match[0].length);
+ var wordClone = wordNode.cloneNode(true);
+ highlight.appendChild(wordClone);
+ wordNode.parentNode.replaceChild(highlight, wordNode);
+ return 1; //skip added node in parent
+ }
+ } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
+ !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
+ !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
+ for (var i = 0; i < node.childNodes.length; i++) {
+ i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
+ }
+ }
+ return 0;
+ }
+});
+
+jQuery.fn.unhighlight = function (options) {
+ var settings = { className: 'highlight', element: 'span' };
+ jQuery.extend(settings, options);
+
+ return this.find(settings.element + "." + settings.className).each(function () {
+ var parent = this.parentNode;
+ parent.replaceChild(this.firstChild, this);
+ parent.normalize();
+ }).end();
+};
+
+jQuery.fn.highlight = function (words, options) {
+ var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
+ jQuery.extend(settings, options);
+
+ if (words.constructor === String) {
+ words = [words];
+ }
+ words = jQuery.grep(words, function(word, i){
+ return word != '';
+ });
+ words = jQuery.map(words, function(word, i) {
+ return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ });
+ if (words.length == 0) { return this; };
+
+ var flag = settings.caseSensitive ? "" : "i";
+ var pattern = "(" + words.join("|") + ")";
+ if (settings.wordsOnly) {
+ pattern = "\\b" + pattern + "\\b";
+ }
+ var re = new RegExp(pattern, flag);
+
+ return this.each(function () {
+ jQuery.highlight(this, re, settings.element, settings.className);
+ });
+};
+
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.tocify.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.tocify.js
new file mode 100644
index 00000000..91cf6379
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery.tocify.js
@@ -0,0 +1,1042 @@
+/* jquery Tocify - v1.8.0 - 2013-09-16
+* http://www.gregfranko.com/jquery.tocify.js/
+* Copyright (c) 2013 Greg Franko; Licensed MIT
+* Modified lightly by Robert Lord to fix a bug I found,
+* and also so it adds ids to headers
+* also because I want height caching, since the
+* height lookup for h1s and h2s was causing serious
+* lag spikes below 30 fps */
+
+// 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.
+(function(tocify) {
+
+ // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
+ "use strict";
+
+ // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
+ tocify(window.jQuery, window, document);
+
+}
+
+// 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.
+(function($, window, document, undefined) {
+
+ // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
+ "use strict";
+
+ var tocClassName = "tocify",
+ tocClass = "." + tocClassName,
+ tocFocusClassName = "tocify-focus",
+ tocHoverClassName = "tocify-hover",
+ hideTocClassName = "tocify-hide",
+ hideTocClass = "." + hideTocClassName,
+ headerClassName = "tocify-header",
+ headerClass = "." + headerClassName,
+ subheaderClassName = "tocify-subheader",
+ subheaderClass = "." + subheaderClassName,
+ itemClassName = "tocify-item",
+ itemClass = "." + itemClassName,
+ extendPageClassName = "tocify-extend-page",
+ extendPageClass = "." + extendPageClassName;
+
+ // Calling the jQueryUI Widget Factory Method
+ $.widget("toc.tocify", {
+
+ //Plugin version
+ version: "1.8.0",
+
+ // These options will be used as defaults
+ options: {
+
+ // **context**: Accepts String: Any jQuery selector
+ // The container element that holds all of the elements used to generate the table of contents
+ context: "body",
+
+ // **ignoreSelector**: Accepts String: Any jQuery selector
+ // A selector to any element that would be matched by selectors that you wish to be ignored
+ ignoreSelector: null,
+
+ // **selectors**: Accepts an Array of Strings: Any jQuery selectors
+ // 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
+ selectors: "h1, h2, h3",
+
+ // **showAndHide**: Accepts a boolean: true or false
+ // Used to determine if elements should be shown and hidden
+ showAndHide: true,
+
+ // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown"
+ // Used to display any of the table of contents nested items
+ showEffect: "slideDown",
+
+ // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
+ // The time duration of the show animation
+ showEffectSpeed: "medium",
+
+ // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp"
+ // Used to hide any of the table of contents nested items
+ hideEffect: "slideUp",
+
+ // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
+ // The time duration of the hide animation
+ hideEffectSpeed: "medium",
+
+ // **smoothScroll**: Accepts a boolean: true or false
+ // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page
+ smoothScroll: true,
+
+ // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
+ // The time duration of the smoothScroll animation
+ smoothScrollSpeed: "medium",
+
+ // **scrollTo**: Accepts Number (pixels)
+ // The amount of space between the top of page and the selected table of contents item after the page has been scrolled
+ scrollTo: 0,
+
+ // **showAndHideOnScroll**: Accepts a boolean: true or false
+ // Determines if table of contents nested items should be shown and hidden while scrolling
+ showAndHideOnScroll: true,
+
+ // **highlightOnScroll**: Accepts a boolean: true or false
+ // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling
+ highlightOnScroll: true,
+
+ // **highlightOffset**: Accepts a number
+ // The offset distance in pixels to trigger the next active table of contents item
+ highlightOffset: 40,
+
+ // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none"
+ // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents
+ theme: "bootstrap",
+
+ // **extendPage**: Accepts a boolean: true or false
+ // 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
+ extendPage: true,
+
+ // **extendPageOffset**: Accepts a number: pixels
+ // How close to the bottom of the page a user must scroll before the page is extended
+ extendPageOffset: 100,
+
+ // **history**: Accepts a boolean: true or false
+ // Adds a hash to the page url to maintain history
+ history: true,
+
+ // **scrollHistory**: Accepts a boolean: true or false
+ // Adds a hash to the page url, to maintain history, when scrolling to a TOC item
+ scrollHistory: false,
+
+ // **hashGenerator**: How the hash value (the anchor segment of the URL, following the
+ // # character) will be generated.
+ //
+ // "compact" (default) - #CompressesEverythingTogether
+ // "pretty" - #looks-like-a-nice-url-and-is-easily-readable
+ // function(text, element){} - Your own hash generation function that accepts the text as an
+ // argument, and returns the hash value.
+ hashGenerator: "compact",
+
+ // **highlightDefault**: Accepts a boolean: true or false
+ // Set's the first TOC item as active if no other TOC item is active.
+ highlightDefault: true
+
+ },
+
+ // _Create
+ // -------
+ // Constructs the plugin. Only called once.
+ _create: function() {
+
+ var self = this;
+
+ self.tocifyWrapper = $('.tocify-wrapper');
+ self.extendPageScroll = true;
+
+ // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings)
+ self.items = [];
+
+ // Generates the HTML for the dynamic table of contents
+ self._generateToc();
+
+ // Caches heights and anchors
+ self.cachedHeights = [],
+ self.cachedAnchors = [];
+
+ // Adds CSS classes to the newly generated table of contents HTML
+ self._addCSSClasses();
+
+ self.webkit = (function() {
+
+ for(var prop in window) {
+
+ if(prop) {
+
+ if(prop.toLowerCase().indexOf("webkit") !== -1) {
+
+ return true;
+
+ }
+
+ }
+
+ }
+
+ return false;
+
+ }());
+
+ // Adds jQuery event handlers to the newly generated table of contents
+ self._setEventHandlers();
+
+ // Binding to the Window load event to make sure the correct scrollTop is calculated
+ $(window).load(function() {
+
+ // Sets the active TOC item
+ self._setActiveElement(true);
+
+ // Once all animations on the page are complete, this callback function will be called
+ $("html, body").promise().done(function() {
+
+ setTimeout(function() {
+
+ self.extendPageScroll = false;
+
+ },0);
+
+ });
+
+ });
+
+ },
+
+ // _generateToc
+ // ------------
+ // Generates the HTML for the dynamic table of contents
+ _generateToc: function() {
+
+ // _Local variables_
+
+ // Stores the plugin context in the self variable
+ var self = this,
+
+ // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above
+ firstElem,
+
+ // Instantiated variable that will store the top level newly created unordered list DOM element
+ ul,
+ ignoreSelector = self.options.ignoreSelector;
+
+ // If the selectors option has a comma within the string
+ if(this.options.selectors.indexOf(",") !== -1) {
+
+ // Grabs the first selector from the string
+ firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(",")));
+
+ }
+
+ // If the selectors option does not have a comman within the string
+ else {
+
+ // Grabs the first selector from the string and makes sure there are no spaces
+ firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,""));
+
+ }
+
+ if(!firstElem.length) {
+
+ self.element.addClass(hideTocClassName);
+
+ return;
+
+ }
+
+ self.element.addClass(tocClassName);
+
+ // Loops through each top level selector
+ firstElem.each(function(index) {
+
+ //If the element matches the ignoreSelector then we skip it
+ if($(this).is(ignoreSelector)) {
+ return;
+ }
+
+ // Creates an unordered list HTML element and adds a dynamic ID and standard class name
+ ul = $("
", {
+ "id": headerClassName + index,
+ "class": headerClassName
+ }).
+
+ // Appends a top level list item HTML element to the previously created HTML header
+ append(self._nestElements($(this), index));
+
+ // Add the created unordered list element to the HTML element calling the plugin
+ self.element.append(ul);
+
+ // Finds all of the HTML tags between the header and subheader elements
+ $(this).nextUntil(this.nodeName.toLowerCase()).each(function() {
+
+ // If there are no nested subheader elemements
+ if($(this).find(self.options.selectors).length === 0) {
+
+ // Loops through all of the subheader elements
+ $(this).filter(self.options.selectors).each(function() {
+
+ //If the element matches the ignoreSelector then we skip it
+ if($(this).is(ignoreSelector)) {
+ return;
+ }
+
+ self._appendSubheaders.call(this, self, ul);
+
+ });
+
+ }
+
+ // If there are nested subheader elements
+ else {
+
+ // Loops through all of the subheader elements
+ $(this).find(self.options.selectors).each(function() {
+
+ //If the element matches the ignoreSelector then we skip it
+ if($(this).is(ignoreSelector)) {
+ return;
+ }
+
+ self._appendSubheaders.call(this, self, ul);
+
+ });
+
+ }
+
+ });
+
+ });
+
+ },
+
+ _setActiveElement: function(pageload) {
+
+ var self = this,
+
+ hash = window.location.hash.substring(1),
+
+ elem = self.element.find("li[data-unique='" + hash + "']");
+
+ if(hash.length) {
+
+ // Removes highlighting from all of the list item's
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
+
+ // Highlights the current list item that was clicked
+ elem.addClass(self.focusClass);
+
+ // If the showAndHide option is true
+ if(self.options.showAndHide) {
+
+ // Triggers the click event on the currently focused TOC item
+ elem.click();
+
+ }
+
+ }
+
+ else {
+
+ // Removes highlighting from all of the list item's
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
+
+ if(!hash.length && pageload && self.options.highlightDefault) {
+
+ // Highlights the first TOC item if no other items are highlighted
+ self.element.find(itemClass).first().addClass(self.focusClass);
+
+ }
+
+ }
+
+ return self;
+
+ },
+
+ // _nestElements
+ // -------------
+ // Helps create the table of contents list by appending nested list items
+ _nestElements: function(self, index) {
+
+ var arr, item, hashValue;
+
+ arr = $.grep(this.items, function (item) {
+
+ return item === self.text();
+
+ });
+
+ // If there is already a duplicate TOC item
+ if(arr.length) {
+
+ // Adds the current TOC item text and index (for slight randomization) to the internal array
+ this.items.push(self.text() + index);
+
+ }
+
+ // If there not a duplicate TOC item
+ else {
+
+ // Adds the current TOC item text to the internal array
+ this.items.push(self.text());
+
+ }
+
+ hashValue = this._generateHashValue(arr, self, index);
+
+ // ADDED BY ROBERT
+ // actually add the hash value to the element's id
+ // self.attr("id", "link-" + hashValue);
+
+ // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
+ item = $("", {
+
+ // Sets a common class name to the list item
+ "class": itemClassName,
+
+ "data-unique": hashValue
+
+ }).append($("", {
+
+ "text": self.text()
+
+ }));
+
+ // Adds an HTML anchor tag before the currently traversed HTML element
+ self.before($("", {
+
+ // 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)
+ "name": hashValue,
+
+ "data-unique": hashValue
+
+ }));
+
+ return item;
+
+ },
+
+ // _generateHashValue
+ // ------------------
+ // Generates the hash value that will be used to refer to each item.
+ _generateHashValue: function(arr, self, index) {
+
+ var hashValue = "",
+ hashGeneratorOption = this.options.hashGenerator;
+
+ if (hashGeneratorOption === "pretty") {
+ // remove weird characters
+
+
+ // prettify the text
+ hashValue = self.text().toLowerCase().replace(/\s/g, "-");
+
+ // ADDED BY ROBERT
+ // remove weird characters
+ hashValue = hashValue.replace(/[^\x00-\x7F]/g, "");
+
+ // fix double hyphens
+ while (hashValue.indexOf("--") > -1) {
+ hashValue = hashValue.replace(/--/g, "-");
+ }
+
+ // fix colon-space instances
+ while (hashValue.indexOf(":-") > -1) {
+ hashValue = hashValue.replace(/:-/g, "-");
+ }
+
+ } else if (typeof hashGeneratorOption === "function") {
+
+ // call the function
+ hashValue = hashGeneratorOption(self.text(), self);
+
+ } else {
+
+ // compact - the default
+ hashValue = self.text().replace(/\s/g, "");
+
+ }
+
+ // add the index if we need to
+ if (arr.length) { hashValue += ""+index; }
+
+ // return the value
+ return hashValue;
+
+ },
+
+ // _appendElements
+ // ---------------
+ // Helps create the table of contents list by appending subheader elements
+
+ _appendSubheaders: function(self, ul) {
+
+ // The current element index
+ var index = $(this).index(self.options.selectors),
+
+ // Finds the previous header DOM element
+ previousHeader = $(self.options.selectors).eq(index - 1),
+
+ currentTagName = +$(this).prop("tagName").charAt(1),
+
+ previousTagName = +previousHeader.prop("tagName").charAt(1),
+
+ lastSubheader;
+
+ // If the current header DOM element is smaller than the previous header DOM element or the first subheader
+ if(currentTagName < previousTagName) {
+
+ // Selects the last unordered list HTML found within the HTML element calling the plugin
+ self.element.find(subheaderClass + "[data-tag=" + currentTagName + "]").last().append(self._nestElements($(this), index));
+
+ }
+
+ // If the current header DOM element is the same type of header(eg. h4) as the previous header DOM element
+ else if(currentTagName === previousTagName) {
+
+ ul.find(itemClass).last().after(self._nestElements($(this), index));
+
+ }
+
+ else {
+
+ // Selects the last unordered list HTML found within the HTML element calling the plugin
+ ul.find(itemClass).last().
+
+ // Appends an unorderedList HTML element to the dynamic `unorderedList` variable and sets a common class name
+ after($("
", {
+
+ "class": subheaderClassName,
+
+ "data-tag": currentTagName
+
+ })).next(subheaderClass).
+
+ // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
+ append(self._nestElements($(this), index));
+ }
+
+ },
+
+ // _setEventHandlers
+ // ----------------
+ // Adds jQuery event handlers to the newly generated table of contents
+ _setEventHandlers: function() {
+
+ // _Local variables_
+
+ // Stores the plugin context in the self variable
+ var self = this,
+
+ // Instantiates a new variable that will be used to hold a specific element's context
+ $self,
+
+ // Instantiates a new variable that will be used to determine the smoothScroll animation time duration
+ duration;
+
+ // Event delegation that looks for any clicks on list item elements inside of the HTML element calling the plugin
+ this.element.on("click.tocify", "li", function(event) {
+
+ if(self.options.history) {
+
+ window.location.hash = $(this).attr("data-unique");
+
+ }
+
+ // Removes highlighting from all of the list item's
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
+
+ // Highlights the current list item that was clicked
+ $(this).addClass(self.focusClass);
+
+ // If the showAndHide option is true
+ if(self.options.showAndHide) {
+
+ var elem = $('li[data-unique="' + $(this).attr("data-unique") + '"]');
+
+ self._triggerShow(elem);
+
+ }
+
+ self._scrollTo($(this));
+
+ });
+
+ // Mouseenter and Mouseleave event handlers for the list item's within the HTML element calling the plugin
+ this.element.find("li").on({
+
+ // Mouseenter event handler
+ "mouseenter.tocify": function() {
+
+ // Adds a hover CSS class to the current list item
+ $(this).addClass(self.hoverClass);
+
+ // Makes sure the cursor is set to the pointer icon
+ $(this).css("cursor", "pointer");
+
+ },
+
+ // Mouseleave event handler
+ "mouseleave.tocify": function() {
+
+ if(self.options.theme !== "bootstrap") {
+
+ // Removes the hover CSS class from the current list item
+ $(this).removeClass(self.hoverClass);
+
+ }
+
+ }
+ });
+
+ // Reset height cache on scroll
+
+ $(window).on('resize', function() {
+ self.calculateHeights();
+ });
+
+ // Window scroll event handler
+ $(window).on("scroll.tocify", function() {
+
+ // Once all animations on the page are complete, this callback function will be called
+ $("html, body").promise().done(function() {
+
+ // Local variables
+
+ // Stores how far the user has scrolled
+ var winScrollTop = $(window).scrollTop(),
+
+ // Stores the height of the window
+ winHeight = $(window).height(),
+
+ // Stores the height of the document
+ docHeight = $(document).height(),
+
+ scrollHeight = $("body")[0].scrollHeight,
+
+ // Instantiates a variable that will be used to hold a selected HTML element
+ elem,
+
+ lastElem,
+
+ lastElemOffset,
+
+ currentElem;
+
+ if(self.options.extendPage) {
+
+ // If the user has scrolled to the bottom of the page and the last toc item is not focused
+ if((self.webkit && winScrollTop >= scrollHeight - winHeight - self.options.extendPageOffset) || (!self.webkit && winHeight + winScrollTop > docHeight - self.options.extendPageOffset)) {
+
+ if(!$(extendPageClass).length) {
+
+ lastElem = $('div[data-unique="' + $(itemClass).last().attr("data-unique") + '"]');
+
+ if(!lastElem.length) return;
+
+ // Gets the top offset of the page header that is linked to the last toc item
+ lastElemOffset = lastElem.offset().top;
+
+ // 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
+ $(self.options.context).append($("", {
+
+ "class": extendPageClassName,
+
+ "height": Math.abs(lastElemOffset - winScrollTop) + "px",
+
+ "data-unique": extendPageClassName
+
+ }));
+
+ if(self.extendPageScroll) {
+
+ currentElem = self.element.find('li.active');
+
+ self._scrollTo($("div[data-unique=" + currentElem.attr("data-unique") + "]"));
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // The zero timeout ensures the following code is run after the scroll events
+ setTimeout(function() {
+
+ // _Local variables_
+
+ // Stores the distance to the closest anchor
+ var // Stores the index of the closest anchor
+ closestAnchorIdx = null,
+ anchorText;
+
+ // if never calculated before, calculate and cache the heights
+ if (self.cachedHeights.length == 0) {
+ self.calculateHeights();
+ }
+
+ var scrollTop = $(window).scrollTop();
+
+ // Determines the index of the closest anchor
+ self.cachedAnchors.each(function(idx) {
+ if (self.cachedHeights[idx] - scrollTop < 0) {
+ closestAnchorIdx = idx;
+ } else {
+ return false;
+ }
+ });
+
+ anchorText = $(self.cachedAnchors[closestAnchorIdx]).attr("data-unique");
+
+ // Stores the list item HTML element that corresponds to the currently traversed anchor tag
+ elem = $('li[data-unique="' + anchorText + '"]');
+
+ // If the `highlightOnScroll` option is true and a next element is found
+ if(self.options.highlightOnScroll && elem.length && !elem.hasClass(self.focusClass)) {
+
+ // Removes highlighting from all of the list item's
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
+
+ // Highlights the corresponding list item
+ elem.addClass(self.focusClass);
+
+ // Scroll to highlighted element's header
+ var tocifyWrapper = self.tocifyWrapper;
+ var scrollToElem = $(elem).closest('.tocify-header');
+
+ var elementOffset = scrollToElem.offset().top,
+ wrapperOffset = tocifyWrapper.offset().top;
+ var offset = elementOffset - wrapperOffset;
+
+ if (offset >= $(window).height()) {
+ var scrollPosition = offset + tocifyWrapper.scrollTop();
+ tocifyWrapper.scrollTop(scrollPosition);
+ } else if (offset < 0) {
+ tocifyWrapper.scrollTop(0);
+ }
+ }
+
+ if(self.options.scrollHistory) {
+
+ // IF STATEMENT ADDED BY ROBERT
+
+ if(window.location.hash !== "#" + anchorText && anchorText !== undefined) {
+
+ if(history.replaceState) {
+ history.replaceState({}, "", "#" + anchorText);
+ // provide a fallback
+ } else {
+ scrollV = document.body.scrollTop;
+ scrollH = document.body.scrollLeft;
+ location.hash = "#" + anchorText;
+ document.body.scrollTop = scrollV;
+ document.body.scrollLeft = scrollH;
+ }
+
+ }
+
+ }
+
+ // If the `showAndHideOnScroll` option is true
+ if(self.options.showAndHideOnScroll && self.options.showAndHide) {
+
+ self._triggerShow(elem, true);
+
+ }
+
+ }, 0);
+
+ });
+
+ });
+
+ },
+
+ // calculateHeights
+ // ----
+ // ADDED BY ROBERT
+ calculateHeights: function() {
+ var self = this;
+ self.cachedHeights = [];
+ self.cachedAnchors = [];
+ var anchors = $(self.options.context).find("div[data-unique]");
+ anchors.each(function(idx) {
+ var distance = (($(this).next().length ? $(this).next() : $(this)).offset().top - self.options.highlightOffset);
+ self.cachedHeights[idx] = distance;
+ });
+ self.cachedAnchors = anchors;
+ },
+
+ // Show
+ // ----
+ // Opens the current sub-header
+ show: function(elem, scroll) {
+
+ // Stores the plugin context in the `self` variable
+ var self = this,
+ element = elem;
+
+ // If the sub-header is not already visible
+ if (!elem.is(":visible")) {
+
+ // If the current element does not have any nested subheaders, is not a header, and its parent is not visible
+ if(!elem.find(subheaderClass).length && !elem.parent().is(headerClass) && !elem.parent().is(":visible")) {
+
+ // Sets the current element to all of the subheaders within the current header
+ elem = elem.parents(subheaderClass).add(elem);
+
+ }
+
+ // If the current element does not have any nested subheaders and is not a header
+ else if(!elem.children(subheaderClass).length && !elem.parent().is(headerClass)) {
+
+ // Sets the current element to the closest subheader
+ elem = elem.closest(subheaderClass);
+
+ }
+
+ //Determines what jQuery effect to use
+ switch (self.options.showEffect) {
+
+ //Uses `no effect`
+ case "none":
+
+ elem.show();
+
+ break;
+
+ //Uses the jQuery `show` special effect
+ case "show":
+
+ elem.show(self.options.showEffectSpeed);
+
+ break;
+
+ //Uses the jQuery `slideDown` special effect
+ case "slideDown":
+
+ elem.slideDown(self.options.showEffectSpeed);
+
+ break;
+
+ //Uses the jQuery `fadeIn` special effect
+ case "fadeIn":
+
+ elem.fadeIn(self.options.showEffectSpeed);
+
+ break;
+
+ //If none of the above options were passed, then a `jQueryUI show effect` is expected
+ default:
+
+ elem.show();
+
+ break;
+
+ }
+
+ }
+
+ // If the current subheader parent element is a header
+ if(elem.parent().is(headerClass)) {
+
+ // Hides all non-active sub-headers
+ self.hide($(subheaderClass).not(elem));
+
+ }
+
+ // If the current subheader parent element is not a header
+ else {
+
+ // Hides all non-active sub-headers
+ self.hide($(subheaderClass).not(elem.closest(headerClass).find(subheaderClass).not(elem.siblings())));
+
+ }
+
+ // Maintains chainablity
+ return self;
+
+ },
+
+ // Hide
+ // ----
+ // Closes the current sub-header
+ hide: function(elem) {
+
+ // Stores the plugin context in the `self` variable
+ var self = this;
+
+ //Determines what jQuery effect to use
+ switch (self.options.hideEffect) {
+
+ // Uses `no effect`
+ case "none":
+
+ elem.hide();
+
+ break;
+
+ // Uses the jQuery `hide` special effect
+ case "hide":
+
+ elem.hide(self.options.hideEffectSpeed);
+
+ break;
+
+ // Uses the jQuery `slideUp` special effect
+ case "slideUp":
+
+ elem.slideUp(self.options.hideEffectSpeed);
+
+ break;
+
+ // Uses the jQuery `fadeOut` special effect
+ case "fadeOut":
+
+ elem.fadeOut(self.options.hideEffectSpeed);
+
+ break;
+
+ // If none of the above options were passed, then a `jqueryUI hide effect` is expected
+ default:
+
+ elem.hide();
+
+ break;
+
+ }
+
+ // Maintains chainablity
+ return self;
+ },
+
+ // _triggerShow
+ // ------------
+ // Determines what elements get shown on scroll and click
+ _triggerShow: function(elem, scroll) {
+
+ var self = this;
+
+ // If the current element's parent is a header element or the next element is a nested subheader element
+ if(elem.parent().is(headerClass) || elem.next().is(subheaderClass)) {
+
+ // Shows the next sub-header element
+ self.show(elem.next(subheaderClass), scroll);
+
+ }
+
+ // If the current element's parent is a subheader element
+ else if(elem.parent().is(subheaderClass)) {
+
+ // Shows the parent sub-header element
+ self.show(elem.parent(), scroll);
+
+ }
+
+ // Maintains chainability
+ return self;
+
+ },
+
+ // _addCSSClasses
+ // --------------
+ // Adds CSS classes to the newly generated table of contents HTML
+ _addCSSClasses: function() {
+
+ // If the user wants a jqueryUI theme
+ if(this.options.theme === "jqueryui") {
+
+ this.focusClass = "ui-state-default";
+
+ this.hoverClass = "ui-state-hover";
+
+ //Adds the default styling to the dropdown list
+ this.element.addClass("ui-widget").find(".toc-title").addClass("ui-widget-header").end().find("li").addClass("ui-widget-content");
+
+ }
+
+ // If the user wants a twitterBootstrap theme
+ else if(this.options.theme === "bootstrap") {
+
+ this.element.find(headerClass + "," + subheaderClass).addClass("nav nav-list");
+
+ this.focusClass = "active";
+
+ }
+
+ // If a user does not want a prebuilt theme
+ else {
+
+ // Adds more neutral classes (instead of jqueryui)
+
+ this.focusClass = tocFocusClassName;
+
+ this.hoverClass = tocHoverClassName;
+
+ }
+
+ //Maintains chainability
+ return this;
+
+ },
+
+ // setOption
+ // ---------
+ // Sets a single Tocify option after the plugin is invoked
+ setOption: function() {
+
+ // Calls the jQueryUI Widget Factory setOption method
+ $.Widget.prototype._setOption.apply(this, arguments);
+
+ },
+
+ // setOptions
+ // ----------
+ // Sets a single or multiple Tocify options after the plugin is invoked
+ setOptions: function() {
+
+ // Calls the jQueryUI Widget Factory setOptions method
+ $.Widget.prototype._setOptions.apply(this, arguments);
+
+ },
+
+ // _scrollTo
+ // ---------
+ // Scrolls to a specific element
+ _scrollTo: function(elem) {
+
+ var self = this,
+ duration = self.options.smoothScroll || 0,
+ scrollTo = self.options.scrollTo;
+
+ // Once all animations on the page are complete, this callback function will be called
+ $("html, body").promise().done(function() {
+
+ // Animates the html and body element scrolltops
+ $("html, body").animate({
+
+ // Sets the jQuery `scrollTop` to the top offset of the HTML div tag that matches the current list item's `data-unique` tag
+ "scrollTop": $('div[data-unique="' + elem.attr("data-unique") + '"]').next().offset().top - ($.isFunction(scrollTo) ? scrollTo.call() : scrollTo) + "px"
+
+ }, {
+
+ // Sets the smoothScroll animation time duration to the smoothScrollSpeed option
+ "duration": duration
+
+ });
+
+ });
+
+ // Maintains chainability
+ return self;
+
+ }
+
+ });
+
+})); //end of plugin
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery_ui.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery_ui.js
new file mode 100644
index 00000000..637e9c14
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_jquery_ui.js
@@ -0,0 +1,566 @@
+/*! jQuery UI - v1.11.3 - 2015-02-12
+ * http://jqueryui.com
+ * Includes: widget.js
+ * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
+ /*!
+ * jQuery UI Widget 1.11.3
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+
+
+ var widget_uuid = 0,
+ widget_slice = Array.prototype.slice;
+
+ $.cleanData = (function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // http://bugs.jquery.com/ticket/8235
+ } catch ( e ) {}
+ }
+ orig( elems );
+ };
+ })( $.cleanData );
+
+ $.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ // proxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ proxiedPrototype = {},
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( !$.isFunction( value ) ) {
+ proxiedPrototype[ prop ] = value;
+ return;
+ }
+ proxiedPrototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
+ }, proxiedPrototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+
+ return constructor;
+ };
+
+ $.widget.extend = function( target ) {
+ var input = widget_slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+ };
+
+ $.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = widget_slice.call( arguments, 1 ),
+ returnValue = this;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+
+ // Allow multiple hashes to be passed on init
+ if ( args.length ) {
+ options = $.widget.extend.apply( null, [ options ].concat(args) );
+ }
+
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+ };
+
+ $.Widget = function( /* options, element */ ) {};
+ $.Widget._childConstructors = [];
+
+ $.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "
",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = widget_uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( arguments.length === 1 ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( arguments.length === 1 ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled", !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOptions({ disabled: false });
+ },
+ disable: function() {
+ return this._setOptions({ disabled: true });
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
+ this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $( this.bindings.not( element ).get() );
+ this.focusable = $( this.focusable.not( element ).get() );
+ this.hoverable = $( this.hoverable.not( element ).get() );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+ };
+
+ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+ });
+
+ var widget = $.widget;
+
+
+
+}));
diff --git a/samples/rest-notes-slate/slate/source/javascripts/lib/_lunr.js b/samples/rest-notes-slate/slate/source/javascripts/lib/_lunr.js
new file mode 100644
index 00000000..54457dab
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/javascripts/lib/_lunr.js
@@ -0,0 +1,1910 @@
+/**
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.7
+ * Copyright (C) 2014 Oliver Nightingale
+ * MIT Licensed
+ * @license
+ */
+
+(function(){
+
+ /**
+ * Convenience function for instantiating a new lunr index and configuring it
+ * with the default pipeline functions and the passed config function.
+ *
+ * When using this convenience function a new index will be created with the
+ * following functions already in the pipeline:
+ *
+ * lunr.StopWordFilter - filters out any stop words before they enter the
+ * index
+ *
+ * lunr.stemmer - stems the tokens before entering the index.
+ *
+ * Example:
+ *
+ * var idx = lunr(function () {
+ * this.field('title', 10)
+ * this.field('tags', 100)
+ * this.field('body')
+ *
+ * this.ref('cid')
+ *
+ * this.pipeline.add(function () {
+ * // some custom pipeline function
+ * })
+ *
+ * })
+ *
+ * @param {Function} config A function that will be called with the new instance
+ * of the lunr.Index as both its context and first parameter. It can be used to
+ * customize the instance of new lunr.Index.
+ * @namespace
+ * @module
+ * @returns {lunr.Index}
+ *
+ */
+ var lunr = function (config) {
+ var idx = new lunr.Index
+
+ idx.pipeline.add(
+ lunr.trimmer,
+ lunr.stopWordFilter,
+ lunr.stemmer
+ )
+
+ if (config) config.call(idx, idx)
+
+ return idx
+ }
+
+ lunr.version = "0.5.7"
+ /*!
+ * lunr.utils
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * A namespace containing utils for the rest of the lunr library
+ */
+ lunr.utils = {}
+
+ /**
+ * Print a warning message to the console.
+ *
+ * @param {String} message The message to be printed.
+ * @memberOf Utils
+ */
+ lunr.utils.warn = (function (global) {
+ return function (message) {
+ if (global.console && console.warn) {
+ console.warn(message)
+ }
+ }
+ })(this)
+
+ /*!
+ * lunr.EventEmitter
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.EventEmitter is an event emitter for lunr. It manages adding and removing event handlers and triggering events and their handlers.
+ *
+ * @constructor
+ */
+ lunr.EventEmitter = function () {
+ this.events = {}
+ }
+
+ /**
+ * Binds a handler function to a specific event(s).
+ *
+ * Can bind a single function to many different events in one call.
+ *
+ * @param {String} [eventName] The name(s) of events to bind this function to.
+ * @param {Function} handler The function to call when an event is fired.
+ * @memberOf EventEmitter
+ */
+ lunr.EventEmitter.prototype.addListener = function () {
+ var args = Array.prototype.slice.call(arguments),
+ fn = args.pop(),
+ names = args
+
+ if (typeof fn !== "function") throw new TypeError ("last argument must be a function")
+
+ names.forEach(function (name) {
+ if (!this.hasHandler(name)) this.events[name] = []
+ this.events[name].push(fn)
+ }, this)
+ }
+
+ /**
+ * Removes a handler function from a specific event.
+ *
+ * @param {String} eventName The name of the event to remove this function from.
+ * @param {Function} handler The function to remove from an event.
+ * @memberOf EventEmitter
+ */
+ lunr.EventEmitter.prototype.removeListener = function (name, fn) {
+ if (!this.hasHandler(name)) return
+
+ var fnIndex = this.events[name].indexOf(fn)
+ this.events[name].splice(fnIndex, 1)
+
+ if (!this.events[name].length) delete this.events[name]
+ }
+
+ /**
+ * Calls all functions bound to the given event.
+ *
+ * Additional data can be passed to the event handler as arguments to `emit`
+ * after the event name.
+ *
+ * @param {String} eventName The name of the event to emit.
+ * @memberOf EventEmitter
+ */
+ lunr.EventEmitter.prototype.emit = function (name) {
+ if (!this.hasHandler(name)) return
+
+ var args = Array.prototype.slice.call(arguments, 1)
+
+ this.events[name].forEach(function (fn) {
+ fn.apply(undefined, args)
+ })
+ }
+
+ /**
+ * Checks whether a handler has ever been stored against an event.
+ *
+ * @param {String} eventName The name of the event to check.
+ * @private
+ * @memberOf EventEmitter
+ */
+ lunr.EventEmitter.prototype.hasHandler = function (name) {
+ return name in this.events
+ }
+
+ /*!
+ * lunr.tokenizer
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * A function for splitting a string into tokens ready to be inserted into
+ * the search index.
+ *
+ * @module
+ * @param {String} obj The string to convert into tokens
+ * @returns {Array}
+ */
+ lunr.tokenizer = function (obj) {
+ if (!arguments.length || obj == null || obj == undefined) return []
+ if (Array.isArray(obj)) return obj.map(function (t) { return t.toLowerCase() })
+
+ var str = obj.toString().replace(/^\s+/, '')
+
+ for (var i = str.length - 1; i >= 0; i--) {
+ if (/\S/.test(str.charAt(i))) {
+ str = str.substring(0, i + 1)
+ break
+ }
+ }
+
+ return str
+ .split(/(?:\s+|\-)/)
+ .filter(function (token) {
+ return !!token
+ })
+ .map(function (token) {
+ return token.toLowerCase()
+ })
+ }
+ /*!
+ * lunr.Pipeline
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.Pipelines maintain an ordered list of functions to be applied to all
+ * tokens in documents entering the search index and queries being ran against
+ * the index.
+ *
+ * An instance of lunr.Index created with the lunr shortcut will contain a
+ * pipeline with a stop word filter and an English language stemmer. Extra
+ * functions can be added before or after either of these functions or these
+ * default functions can be removed.
+ *
+ * When run the pipeline will call each function in turn, passing a token, the
+ * index of that token in the original list of all tokens and finally a list of
+ * all the original tokens.
+ *
+ * The output of functions in the pipeline will be passed to the next function
+ * in the pipeline. To exclude a token from entering the index the function
+ * should return undefined, the rest of the pipeline will not be called with
+ * this token.
+ *
+ * For serialisation of pipelines to work, all functions used in an instance of
+ * a pipeline should be registered with lunr.Pipeline. Registered functions can
+ * then be loaded. If trying to load a serialised pipeline that uses functions
+ * that are not registered an error will be thrown.
+ *
+ * If not planning on serialising the pipeline then registering pipeline functions
+ * is not necessary.
+ *
+ * @constructor
+ */
+ lunr.Pipeline = function () {
+ this._stack = []
+ }
+
+ lunr.Pipeline.registeredFunctions = {}
+
+ /**
+ * Register a function with the pipeline.
+ *
+ * Functions that are used in the pipeline should be registered if the pipeline
+ * needs to be serialised, or a serialised pipeline needs to be loaded.
+ *
+ * Registering a function does not add it to a pipeline, functions must still be
+ * added to instances of the pipeline for them to be used when running a pipeline.
+ *
+ * @param {Function} fn The function to check for.
+ * @param {String} label The label to register this function with
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.registerFunction = function (fn, label) {
+ if (label in this.registeredFunctions) {
+ lunr.utils.warn('Overwriting existing registered function: ' + label)
+ }
+
+ fn.label = label
+ lunr.Pipeline.registeredFunctions[fn.label] = fn
+ }
+
+ /**
+ * Warns if the function is not registered as a Pipeline function.
+ *
+ * @param {Function} fn The function to check for.
+ * @private
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
+ var isRegistered = fn.label && (fn.label in this.registeredFunctions)
+
+ if (!isRegistered) {
+ lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
+ }
+ }
+
+ /**
+ * Loads a previously serialised pipeline.
+ *
+ * All functions to be loaded must already be registered with lunr.Pipeline.
+ * If any function from the serialised data has not been registered then an
+ * error will be thrown.
+ *
+ * @param {Object} serialised The serialised pipeline to load.
+ * @returns {lunr.Pipeline}
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.load = function (serialised) {
+ var pipeline = new lunr.Pipeline
+
+ serialised.forEach(function (fnName) {
+ var fn = lunr.Pipeline.registeredFunctions[fnName]
+
+ if (fn) {
+ pipeline.add(fn)
+ } else {
+ throw new Error ('Cannot load un-registered function: ' + fnName)
+ }
+ })
+
+ return pipeline
+ }
+
+ /**
+ * Adds new functions to the end of the pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {Function} functions Any number of functions to add to the pipeline.
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.add = function () {
+ var fns = Array.prototype.slice.call(arguments)
+
+ fns.forEach(function (fn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
+ this._stack.push(fn)
+ }, this)
+ }
+
+ /**
+ * Adds a single function after a function that already exists in the
+ * pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {Function} existingFn A function that already exists in the pipeline.
+ * @param {Function} newFn The new function to add to the pipeline.
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.after = function (existingFn, newFn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
+
+ var pos = this._stack.indexOf(existingFn) + 1
+ this._stack.splice(pos, 0, newFn)
+ }
+
+ /**
+ * Adds a single function before a function that already exists in the
+ * pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {Function} existingFn A function that already exists in the pipeline.
+ * @param {Function} newFn The new function to add to the pipeline.
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.before = function (existingFn, newFn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
+
+ var pos = this._stack.indexOf(existingFn)
+ this._stack.splice(pos, 0, newFn)
+ }
+
+ /**
+ * Removes a function from the pipeline.
+ *
+ * @param {Function} fn The function to remove from the pipeline.
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.remove = function (fn) {
+ var pos = this._stack.indexOf(fn)
+ this._stack.splice(pos, 1)
+ }
+
+ /**
+ * Runs the current list of functions that make up the pipeline against the
+ * passed tokens.
+ *
+ * @param {Array} tokens The tokens to run through the pipeline.
+ * @returns {Array}
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.run = function (tokens) {
+ var out = [],
+ tokenLength = tokens.length,
+ stackLength = this._stack.length
+
+ for (var i = 0; i < tokenLength; i++) {
+ var token = tokens[i]
+
+ for (var j = 0; j < stackLength; j++) {
+ token = this._stack[j](token, i, tokens)
+ if (token === void 0) break
+ };
+
+ if (token !== void 0) out.push(token)
+ };
+
+ return out
+ }
+
+ /**
+ * Resets the pipeline by removing any existing processors.
+ *
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.reset = function () {
+ this._stack = []
+ }
+
+ /**
+ * Returns a representation of the pipeline ready for serialisation.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @returns {Array}
+ * @memberOf Pipeline
+ */
+ lunr.Pipeline.prototype.toJSON = function () {
+ return this._stack.map(function (fn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
+
+ return fn.label
+ })
+ }
+ /*!
+ * lunr.Vector
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.Vectors implement vector related operations for
+ * a series of elements.
+ *
+ * @constructor
+ */
+ lunr.Vector = function () {
+ this._magnitude = null
+ this.list = undefined
+ this.length = 0
+ }
+
+ /**
+ * lunr.Vector.Node is a simple struct for each node
+ * in a lunr.Vector.
+ *
+ * @private
+ * @param {Number} The index of the node in the vector.
+ * @param {Object} The data at this node in the vector.
+ * @param {lunr.Vector.Node} The node directly after this node in the vector.
+ * @constructor
+ * @memberOf Vector
+ */
+ lunr.Vector.Node = function (idx, val, next) {
+ this.idx = idx
+ this.val = val
+ this.next = next
+ }
+
+ /**
+ * Inserts a new value at a position in a vector.
+ *
+ * @param {Number} The index at which to insert a value.
+ * @param {Object} The object to insert in the vector.
+ * @memberOf Vector.
+ */
+ lunr.Vector.prototype.insert = function (idx, val) {
+ var list = this.list
+
+ if (!list) {
+ this.list = new lunr.Vector.Node (idx, val, list)
+ return this.length++
+ }
+
+ var prev = list,
+ next = list.next
+
+ while (next != undefined) {
+ if (idx < next.idx) {
+ prev.next = new lunr.Vector.Node (idx, val, next)
+ return this.length++
+ }
+
+ prev = next, next = next.next
+ }
+
+ prev.next = new lunr.Vector.Node (idx, val, next)
+ return this.length++
+ }
+
+ /**
+ * Calculates the magnitude of this vector.
+ *
+ * @returns {Number}
+ * @memberOf Vector
+ */
+ lunr.Vector.prototype.magnitude = function () {
+ if (this._magniture) return this._magnitude
+ var node = this.list,
+ sumOfSquares = 0,
+ val
+
+ while (node) {
+ val = node.val
+ sumOfSquares += val * val
+ node = node.next
+ }
+
+ return this._magnitude = Math.sqrt(sumOfSquares)
+ }
+
+ /**
+ * Calculates the dot product of this vector and another vector.
+ *
+ * @param {lunr.Vector} otherVector The vector to compute the dot product with.
+ * @returns {Number}
+ * @memberOf Vector
+ */
+ lunr.Vector.prototype.dot = function (otherVector) {
+ var node = this.list,
+ otherNode = otherVector.list,
+ dotProduct = 0
+
+ while (node && otherNode) {
+ if (node.idx < otherNode.idx) {
+ node = node.next
+ } else if (node.idx > otherNode.idx) {
+ otherNode = otherNode.next
+ } else {
+ dotProduct += node.val * otherNode.val
+ node = node.next
+ otherNode = otherNode.next
+ }
+ }
+
+ return dotProduct
+ }
+
+ /**
+ * Calculates the cosine similarity between this vector and another
+ * vector.
+ *
+ * @param {lunr.Vector} otherVector The other vector to calculate the
+ * similarity with.
+ * @returns {Number}
+ * @memberOf Vector
+ */
+ lunr.Vector.prototype.similarity = function (otherVector) {
+ return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
+ }
+ /*!
+ * lunr.SortedSet
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.SortedSets are used to maintain an array of uniq values in a sorted
+ * order.
+ *
+ * @constructor
+ */
+ lunr.SortedSet = function () {
+ this.length = 0
+ this.elements = []
+ }
+
+ /**
+ * Loads a previously serialised sorted set.
+ *
+ * @param {Array} serialisedData The serialised set to load.
+ * @returns {lunr.SortedSet}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.load = function (serialisedData) {
+ var set = new this
+
+ set.elements = serialisedData
+ set.length = serialisedData.length
+
+ return set
+ }
+
+ /**
+ * Inserts new items into the set in the correct position to maintain the
+ * order.
+ *
+ * @param {Object} The objects to add to this set.
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.add = function () {
+ Array.prototype.slice.call(arguments).forEach(function (element) {
+ if (~this.indexOf(element)) return
+ this.elements.splice(this.locationFor(element), 0, element)
+ }, this)
+
+ this.length = this.elements.length
+ }
+
+ /**
+ * Converts this sorted set into an array.
+ *
+ * @returns {Array}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.toArray = function () {
+ return this.elements.slice()
+ }
+
+ /**
+ * Creates a new array with the results of calling a provided function on every
+ * element in this sorted set.
+ *
+ * Delegates to Array.prototype.map and has the same signature.
+ *
+ * @param {Function} fn The function that is called on each element of the
+ * set.
+ * @param {Object} ctx An optional object that can be used as the context
+ * for the function fn.
+ * @returns {Array}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.map = function (fn, ctx) {
+ return this.elements.map(fn, ctx)
+ }
+
+ /**
+ * Executes a provided function once per sorted set element.
+ *
+ * Delegates to Array.prototype.forEach and has the same signature.
+ *
+ * @param {Function} fn The function that is called on each element of the
+ * set.
+ * @param {Object} ctx An optional object that can be used as the context
+ * @memberOf SortedSet
+ * for the function fn.
+ */
+ lunr.SortedSet.prototype.forEach = function (fn, ctx) {
+ return this.elements.forEach(fn, ctx)
+ }
+
+ /**
+ * Returns the index at which a given element can be found in the
+ * sorted set, or -1 if it is not present.
+ *
+ * @param {Object} elem The object to locate in the sorted set.
+ * @param {Number} start An optional index at which to start searching from
+ * within the set.
+ * @param {Number} end An optional index at which to stop search from within
+ * the set.
+ * @returns {Number}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.indexOf = function (elem, start, end) {
+ var start = start || 0,
+ end = end || this.elements.length,
+ sectionLength = end - start,
+ pivot = start + Math.floor(sectionLength / 2),
+ pivotElem = this.elements[pivot]
+
+ if (sectionLength <= 1) {
+ if (pivotElem === elem) {
+ return pivot
+ } else {
+ return -1
+ }
+ }
+
+ if (pivotElem < elem) return this.indexOf(elem, pivot, end)
+ if (pivotElem > elem) return this.indexOf(elem, start, pivot)
+ if (pivotElem === elem) return pivot
+ }
+
+ /**
+ * Returns the position within the sorted set that an element should be
+ * inserted at to maintain the current order of the set.
+ *
+ * This function assumes that the element to search for does not already exist
+ * in the sorted set.
+ *
+ * @param {Object} elem The elem to find the position for in the set
+ * @param {Number} start An optional index at which to start searching from
+ * within the set.
+ * @param {Number} end An optional index at which to stop search from within
+ * the set.
+ * @returns {Number}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.locationFor = function (elem, start, end) {
+ var start = start || 0,
+ end = end || this.elements.length,
+ sectionLength = end - start,
+ pivot = start + Math.floor(sectionLength / 2),
+ pivotElem = this.elements[pivot]
+
+ if (sectionLength <= 1) {
+ if (pivotElem > elem) return pivot
+ if (pivotElem < elem) return pivot + 1
+ }
+
+ if (pivotElem < elem) return this.locationFor(elem, pivot, end)
+ if (pivotElem > elem) return this.locationFor(elem, start, pivot)
+ }
+
+ /**
+ * Creates a new lunr.SortedSet that contains the elements in the intersection
+ * of this set and the passed set.
+ *
+ * @param {lunr.SortedSet} otherSet The set to intersect with this set.
+ * @returns {lunr.SortedSet}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.intersect = function (otherSet) {
+ var intersectSet = new lunr.SortedSet,
+ i = 0, j = 0,
+ a_len = this.length, b_len = otherSet.length,
+ a = this.elements, b = otherSet.elements
+
+ while (true) {
+ if (i > a_len - 1 || j > b_len - 1) break
+
+ if (a[i] === b[j]) {
+ intersectSet.add(a[i])
+ i++, j++
+ continue
+ }
+
+ if (a[i] < b[j]) {
+ i++
+ continue
+ }
+
+ if (a[i] > b[j]) {
+ j++
+ continue
+ }
+ };
+
+ return intersectSet
+ }
+
+ /**
+ * Makes a copy of this set
+ *
+ * @returns {lunr.SortedSet}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.clone = function () {
+ var clone = new lunr.SortedSet
+
+ clone.elements = this.toArray()
+ clone.length = clone.elements.length
+
+ return clone
+ }
+
+ /**
+ * Creates a new lunr.SortedSet that contains the elements in the union
+ * of this set and the passed set.
+ *
+ * @param {lunr.SortedSet} otherSet The set to union with this set.
+ * @returns {lunr.SortedSet}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.union = function (otherSet) {
+ var longSet, shortSet, unionSet
+
+ if (this.length >= otherSet.length) {
+ longSet = this, shortSet = otherSet
+ } else {
+ longSet = otherSet, shortSet = this
+ }
+
+ unionSet = longSet.clone()
+
+ unionSet.add.apply(unionSet, shortSet.toArray())
+
+ return unionSet
+ }
+
+ /**
+ * Returns a representation of the sorted set ready for serialisation.
+ *
+ * @returns {Array}
+ * @memberOf SortedSet
+ */
+ lunr.SortedSet.prototype.toJSON = function () {
+ return this.toArray()
+ }
+ /*!
+ * lunr.Index
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.Index is object that manages a search index. It contains the indexes
+ * and stores all the tokens and document lookups. It also provides the main
+ * user facing API for the library.
+ *
+ * @constructor
+ */
+ lunr.Index = function () {
+ this._fields = []
+ this._ref = 'id'
+ this.pipeline = new lunr.Pipeline
+ this.documentStore = new lunr.Store
+ this.tokenStore = new lunr.TokenStore
+ this.corpusTokens = new lunr.SortedSet
+ this.eventEmitter = new lunr.EventEmitter
+
+ this._idfCache = {}
+
+ this.on('add', 'remove', 'update', (function () {
+ this._idfCache = {}
+ }).bind(this))
+ }
+
+ /**
+ * Bind a handler to events being emitted by the index.
+ *
+ * The handler can be bound to many events at the same time.
+ *
+ * @param {String} [eventName] The name(s) of events to bind the function to.
+ * @param {Function} handler The serialised set to load.
+ * @memberOf Index
+ */
+ lunr.Index.prototype.on = function () {
+ var args = Array.prototype.slice.call(arguments)
+ return this.eventEmitter.addListener.apply(this.eventEmitter, args)
+ }
+
+ /**
+ * Removes a handler from an event being emitted by the index.
+ *
+ * @param {String} eventName The name of events to remove the function from.
+ * @param {Function} handler The serialised set to load.
+ * @memberOf Index
+ */
+ lunr.Index.prototype.off = function (name, fn) {
+ return this.eventEmitter.removeListener(name, fn)
+ }
+
+ /**
+ * Loads a previously serialised index.
+ *
+ * Issues a warning if the index being imported was serialised
+ * by a different version of lunr.
+ *
+ * @param {Object} serialisedData The serialised set to load.
+ * @returns {lunr.Index}
+ * @memberOf Index
+ */
+ lunr.Index.load = function (serialisedData) {
+ if (serialisedData.version !== lunr.version) {
+ lunr.utils.warn('version mismatch: current ' + lunr.version + ' importing ' + serialisedData.version)
+ }
+
+ var idx = new this
+
+ idx._fields = serialisedData.fields
+ idx._ref = serialisedData.ref
+
+ idx.documentStore = lunr.Store.load(serialisedData.documentStore)
+ idx.tokenStore = lunr.TokenStore.load(serialisedData.tokenStore)
+ idx.corpusTokens = lunr.SortedSet.load(serialisedData.corpusTokens)
+ idx.pipeline = lunr.Pipeline.load(serialisedData.pipeline)
+
+ return idx
+ }
+
+ /**
+ * Adds a field to the list of fields that will be searchable within documents
+ * in the index.
+ *
+ * An optional boost param can be passed to affect how much tokens in this field
+ * rank in search results, by default the boost value is 1.
+ *
+ * Fields should be added before any documents are added to the index, fields
+ * that are added after documents are added to the index will only apply to new
+ * documents added to the index.
+ *
+ * @param {String} fieldName The name of the field within the document that
+ * should be indexed
+ * @param {Number} boost An optional boost that can be applied to terms in this
+ * field.
+ * @returns {lunr.Index}
+ * @memberOf Index
+ */
+ lunr.Index.prototype.field = function (fieldName, opts) {
+ var opts = opts || {},
+ field = { name: fieldName, boost: opts.boost || 1 }
+
+ this._fields.push(field)
+ return this
+ }
+
+ /**
+ * Sets the property used to uniquely identify documents added to the index,
+ * by default this property is 'id'.
+ *
+ * This should only be changed before adding documents to the index, changing
+ * the ref property without resetting the index can lead to unexpected results.
+ *
+ * @param {String} refName The property to use to uniquely identify the
+ * documents in the index.
+ * @param {Boolean} emitEvent Whether to emit add events, defaults to true
+ * @returns {lunr.Index}
+ * @memberOf Index
+ */
+ lunr.Index.prototype.ref = function (refName) {
+ this._ref = refName
+ return this
+ }
+
+ /**
+ * Add a document to the index.
+ *
+ * This is the way new documents enter the index, this function will run the
+ * fields from the document through the index's pipeline and then add it to
+ * the index, it will then show up in search results.
+ *
+ * An 'add' event is emitted with the document that has been added and the index
+ * the document has been added to. This event can be silenced by passing false
+ * as the second argument to add.
+ *
+ * @param {Object} doc The document to add to the index.
+ * @param {Boolean} emitEvent Whether or not to emit events, default true.
+ * @memberOf Index
+ */
+ lunr.Index.prototype.add = function (doc, emitEvent) {
+ var docTokens = {},
+ allDocumentTokens = new lunr.SortedSet,
+ docRef = doc[this._ref],
+ emitEvent = emitEvent === undefined ? true : emitEvent
+
+ this._fields.forEach(function (field) {
+ var fieldTokens = this.pipeline.run(lunr.tokenizer(doc[field.name]))
+
+ docTokens[field.name] = fieldTokens
+ lunr.SortedSet.prototype.add.apply(allDocumentTokens, fieldTokens)
+ }, this)
+
+ this.documentStore.set(docRef, allDocumentTokens)
+ lunr.SortedSet.prototype.add.apply(this.corpusTokens, allDocumentTokens.toArray())
+
+ for (var i = 0; i < allDocumentTokens.length; i++) {
+ var token = allDocumentTokens.elements[i]
+ var tf = this._fields.reduce(function (memo, field) {
+ var fieldLength = docTokens[field.name].length
+
+ if (!fieldLength) return memo
+
+ var tokenCount = docTokens[field.name].filter(function (t) { return t === token }).length
+
+ return memo + (tokenCount / fieldLength * field.boost)
+ }, 0)
+
+ this.tokenStore.add(token, { ref: docRef, tf: tf })
+ };
+
+ if (emitEvent) this.eventEmitter.emit('add', doc, this)
+ }
+
+ /**
+ * Removes a document from the index.
+ *
+ * To make sure documents no longer show up in search results they can be
+ * removed from the index using this method.
+ *
+ * The document passed only needs to have the same ref property value as the
+ * document that was added to the index, they could be completely different
+ * objects.
+ *
+ * A 'remove' event is emitted with the document that has been removed and the index
+ * the document has been removed from. This event can be silenced by passing false
+ * as the second argument to remove.
+ *
+ * @param {Object} doc The document to remove from the index.
+ * @param {Boolean} emitEvent Whether to emit remove events, defaults to true
+ * @memberOf Index
+ */
+ lunr.Index.prototype.remove = function (doc, emitEvent) {
+ var docRef = doc[this._ref],
+ emitEvent = emitEvent === undefined ? true : emitEvent
+
+ if (!this.documentStore.has(docRef)) return
+
+ var docTokens = this.documentStore.get(docRef)
+
+ this.documentStore.remove(docRef)
+
+ docTokens.forEach(function (token) {
+ this.tokenStore.remove(token, docRef)
+ }, this)
+
+ if (emitEvent) this.eventEmitter.emit('remove', doc, this)
+ }
+
+ /**
+ * Updates a document in the index.
+ *
+ * When a document contained within the index gets updated, fields changed,
+ * added or removed, to make sure it correctly matched against search queries,
+ * it should be updated in the index.
+ *
+ * This method is just a wrapper around `remove` and `add`
+ *
+ * An 'update' event is emitted with the document that has been updated and the index.
+ * This event can be silenced by passing false as the second argument to update. Only
+ * an update event will be fired, the 'add' and 'remove' events of the underlying calls
+ * are silenced.
+ *
+ * @param {Object} doc The document to update in the index.
+ * @param {Boolean} emitEvent Whether to emit update events, defaults to true
+ * @see Index.prototype.remove
+ * @see Index.prototype.add
+ * @memberOf Index
+ */
+ lunr.Index.prototype.update = function (doc, emitEvent) {
+ var emitEvent = emitEvent === undefined ? true : emitEvent
+
+ this.remove(doc, false)
+ this.add(doc, false)
+
+ if (emitEvent) this.eventEmitter.emit('update', doc, this)
+ }
+
+ /**
+ * Calculates the inverse document frequency for a token within the index.
+ *
+ * @param {String} token The token to calculate the idf of.
+ * @see Index.prototype.idf
+ * @private
+ * @memberOf Index
+ */
+ lunr.Index.prototype.idf = function (term) {
+ var cacheKey = "@" + term
+ if (Object.prototype.hasOwnProperty.call(this._idfCache, cacheKey)) return this._idfCache[cacheKey]
+
+ var documentFrequency = this.tokenStore.count(term),
+ idf = 1
+
+ if (documentFrequency > 0) {
+ idf = 1 + Math.log(this.tokenStore.length / documentFrequency)
+ }
+
+ return this._idfCache[cacheKey] = idf
+ }
+
+ /**
+ * Searches the index using the passed query.
+ *
+ * Queries should be a string, multiple words are allowed and will lead to an
+ * AND based query, e.g. `idx.search('foo bar')` will run a search for
+ * documents containing both 'foo' and 'bar'.
+ *
+ * All query tokens are passed through the same pipeline that document tokens
+ * are passed through, so any language processing involved will be run on every
+ * query term.
+ *
+ * Each query term is expanded, so that the term 'he' might be expanded to
+ * 'hello' and 'help' if those terms were already included in the index.
+ *
+ * Matching documents are returned as an array of objects, each object contains
+ * the matching document ref, as set for this index, and the similarity score
+ * for this document against the query.
+ *
+ * @param {String} query The query to search the index with.
+ * @returns {Object}
+ * @see Index.prototype.idf
+ * @see Index.prototype.documentVector
+ * @memberOf Index
+ */
+ lunr.Index.prototype.search = function (query) {
+ var queryTokens = this.pipeline.run(lunr.tokenizer(query)),
+ queryVector = new lunr.Vector,
+ documentSets = [],
+ fieldBoosts = this._fields.reduce(function (memo, f) { return memo + f.boost }, 0)
+
+ var hasSomeToken = queryTokens.some(function (token) {
+ return this.tokenStore.has(token)
+ }, this)
+
+ if (!hasSomeToken) return []
+
+ queryTokens
+ .forEach(function (token, i, tokens) {
+ var tf = 1 / tokens.length * this._fields.length * fieldBoosts,
+ self = this
+
+ var set = this.tokenStore.expand(token).reduce(function (memo, key) {
+ var pos = self.corpusTokens.indexOf(key),
+ idf = self.idf(key),
+ similarityBoost = 1,
+ set = new lunr.SortedSet
+
+ // if the expanded key is not an exact match to the token then
+ // penalise the score for this key by how different the key is
+ // to the token.
+ if (key !== token) {
+ var diff = Math.max(3, key.length - token.length)
+ similarityBoost = 1 / Math.log(diff)
+ }
+
+ // calculate the query tf-idf score for this token
+ // applying an similarityBoost to ensure exact matches
+ // these rank higher than expanded terms
+ if (pos > -1) queryVector.insert(pos, tf * idf * similarityBoost)
+
+ // add all the documents that have this key into a set
+ Object.keys(self.tokenStore.get(key)).forEach(function (ref) { set.add(ref) })
+
+ return memo.union(set)
+ }, new lunr.SortedSet)
+
+ documentSets.push(set)
+ }, this)
+
+ var documentSet = documentSets.reduce(function (memo, set) {
+ return memo.intersect(set)
+ })
+
+ return documentSet
+ .map(function (ref) {
+ return { ref: ref, score: queryVector.similarity(this.documentVector(ref)) }
+ }, this)
+ .sort(function (a, b) {
+ return b.score - a.score
+ })
+ }
+
+ /**
+ * Generates a vector containing all the tokens in the document matching the
+ * passed documentRef.
+ *
+ * The vector contains the tf-idf score for each token contained in the
+ * document with the passed documentRef. The vector will contain an element
+ * for every token in the indexes corpus, if the document does not contain that
+ * token the element will be 0.
+ *
+ * @param {Object} documentRef The ref to find the document with.
+ * @returns {lunr.Vector}
+ * @private
+ * @memberOf Index
+ */
+ lunr.Index.prototype.documentVector = function (documentRef) {
+ var documentTokens = this.documentStore.get(documentRef),
+ documentTokensLength = documentTokens.length,
+ documentVector = new lunr.Vector
+
+ for (var i = 0; i < documentTokensLength; i++) {
+ var token = documentTokens.elements[i],
+ tf = this.tokenStore.get(token)[documentRef].tf,
+ idf = this.idf(token)
+
+ documentVector.insert(this.corpusTokens.indexOf(token), tf * idf)
+ };
+
+ return documentVector
+ }
+
+ /**
+ * Returns a representation of the index ready for serialisation.
+ *
+ * @returns {Object}
+ * @memberOf Index
+ */
+ lunr.Index.prototype.toJSON = function () {
+ return {
+ version: lunr.version,
+ fields: this._fields,
+ ref: this._ref,
+ documentStore: this.documentStore.toJSON(),
+ tokenStore: this.tokenStore.toJSON(),
+ corpusTokens: this.corpusTokens.toJSON(),
+ pipeline: this.pipeline.toJSON()
+ }
+ }
+
+ /**
+ * Applies a plugin to the current index.
+ *
+ * A plugin is a function that is called with the index as its context.
+ * Plugins can be used to customise or extend the behaviour the index
+ * in some way. A plugin is just a function, that encapsulated the custom
+ * behaviour that should be applied to the index.
+ *
+ * The plugin function will be called with the index as its argument, additional
+ * arguments can also be passed when calling use. The function will be called
+ * with the index as its context.
+ *
+ * Example:
+ *
+ * var myPlugin = function (idx, arg1, arg2) {
+ * // `this` is the index to be extended
+ * // apply any extensions etc here.
+ * }
+ *
+ * var idx = lunr(function () {
+ * this.use(myPlugin, 'arg1', 'arg2')
+ * })
+ *
+ * @param {Function} plugin The plugin to apply.
+ * @memberOf Index
+ */
+ lunr.Index.prototype.use = function (plugin) {
+ var args = Array.prototype.slice.call(arguments, 1)
+ args.unshift(this)
+ plugin.apply(this, args)
+ }
+ /*!
+ * lunr.Store
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.Store is a simple key-value store used for storing sets of tokens for
+ * documents stored in index.
+ *
+ * @constructor
+ * @module
+ */
+ lunr.Store = function () {
+ this.store = {}
+ this.length = 0
+ }
+
+ /**
+ * Loads a previously serialised store
+ *
+ * @param {Object} serialisedData The serialised store to load.
+ * @returns {lunr.Store}
+ * @memberOf Store
+ */
+ lunr.Store.load = function (serialisedData) {
+ var store = new this
+
+ store.length = serialisedData.length
+ store.store = Object.keys(serialisedData.store).reduce(function (memo, key) {
+ memo[key] = lunr.SortedSet.load(serialisedData.store[key])
+ return memo
+ }, {})
+
+ return store
+ }
+
+ /**
+ * Stores the given tokens in the store against the given id.
+ *
+ * @param {Object} id The key used to store the tokens against.
+ * @param {Object} tokens The tokens to store against the key.
+ * @memberOf Store
+ */
+ lunr.Store.prototype.set = function (id, tokens) {
+ if (!this.has(id)) this.length++
+ this.store[id] = tokens
+ }
+
+ /**
+ * Retrieves the tokens from the store for a given key.
+ *
+ * @param {Object} id The key to lookup and retrieve from the store.
+ * @returns {Object}
+ * @memberOf Store
+ */
+ lunr.Store.prototype.get = function (id) {
+ return this.store[id]
+ }
+
+ /**
+ * Checks whether the store contains a key.
+ *
+ * @param {Object} id The id to look up in the store.
+ * @returns {Boolean}
+ * @memberOf Store
+ */
+ lunr.Store.prototype.has = function (id) {
+ return id in this.store
+ }
+
+ /**
+ * Removes the value for a key in the store.
+ *
+ * @param {Object} id The id to remove from the store.
+ * @memberOf Store
+ */
+ lunr.Store.prototype.remove = function (id) {
+ if (!this.has(id)) return
+
+ delete this.store[id]
+ this.length--
+ }
+
+ /**
+ * Returns a representation of the store ready for serialisation.
+ *
+ * @returns {Object}
+ * @memberOf Store
+ */
+ lunr.Store.prototype.toJSON = function () {
+ return {
+ store: this.store,
+ length: this.length
+ }
+ }
+
+ /*!
+ * lunr.stemmer
+ * Copyright (C) 2014 Oliver Nightingale
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
+ */
+
+ /**
+ * lunr.stemmer is an english language stemmer, this is a JavaScript
+ * implementation of the PorterStemmer taken from http://tartaurs.org/~martin
+ *
+ * @module
+ * @param {String} str The string to stem
+ * @returns {String}
+ * @see lunr.Pipeline
+ */
+ lunr.stemmer = (function(){
+ var step2list = {
+ "ational" : "ate",
+ "tional" : "tion",
+ "enci" : "ence",
+ "anci" : "ance",
+ "izer" : "ize",
+ "bli" : "ble",
+ "alli" : "al",
+ "entli" : "ent",
+ "eli" : "e",
+ "ousli" : "ous",
+ "ization" : "ize",
+ "ation" : "ate",
+ "ator" : "ate",
+ "alism" : "al",
+ "iveness" : "ive",
+ "fulness" : "ful",
+ "ousness" : "ous",
+ "aliti" : "al",
+ "iviti" : "ive",
+ "biliti" : "ble",
+ "logi" : "log"
+ },
+
+ step3list = {
+ "icate" : "ic",
+ "ative" : "",
+ "alize" : "al",
+ "iciti" : "ic",
+ "ical" : "ic",
+ "ful" : "",
+ "ness" : ""
+ },
+
+ c = "[^aeiou]", // consonant
+ v = "[aeiouy]", // vowel
+ C = c + "[^aeiouy]*", // consonant sequence
+ V = v + "[aeiou]*", // vowel sequence
+
+ mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
+ meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
+ mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
+ s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ var re_mgr0 = new RegExp(mgr0);
+ var re_mgr1 = new RegExp(mgr1);
+ var re_meq1 = new RegExp(meq1);
+ var re_s_v = new RegExp(s_v);
+
+ var re_1a = /^(.+?)(ss|i)es$/;
+ var re2_1a = /^(.+?)([^s])s$/;
+ var re_1b = /^(.+?)eed$/;
+ var re2_1b = /^(.+?)(ed|ing)$/;
+ var re_1b_2 = /.$/;
+ var re2_1b_2 = /(at|bl|iz)$/;
+ var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
+ var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+
+ var re_1c = /^(.+?[^aeiou])y$/;
+ var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+
+ var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+
+ var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ var re2_4 = /^(.+?)(s|t)(ion)$/;
+
+ var re_5 = /^(.+?)e$/;
+ var re_5_1 = /ll$/;
+ var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+
+ var porterStemmer = function porterStemmer(w) {
+ var stem,
+ suffix,
+ firstch,
+ re,
+ re2,
+ re3,
+ re4;
+
+ if (w.length < 3) { return w; }
+
+ firstch = w.substr(0,1);
+ if (firstch == "y") {
+ w = firstch.toUpperCase() + w.substr(1);
+ }
+
+ // Step 1a
+ re = re_1a
+ re2 = re2_1a;
+
+ if (re.test(w)) { w = w.replace(re,"$1$2"); }
+ else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
+
+ // Step 1b
+ re = re_1b;
+ re2 = re2_1b;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = re_mgr0;
+ if (re.test(fp[1])) {
+ re = re_1b_2;
+ w = w.replace(re,"");
+ }
+ } else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = re_s_v;
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = re2_1b_2;
+ re3 = re3_1b_2;
+ re4 = re4_1b_2;
+ if (re2.test(w)) { w = w + "e"; }
+ else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
+ else if (re4.test(w)) { w = w + "e"; }
+ }
+ }
+
+ // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)
+ re = re_1c;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = re_2;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = re_mgr0;
+ if (re.test(stem)) {
+ w = stem + step2list[suffix];
+ }
+ }
+
+ // Step 3
+ re = re_3;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = re_mgr0;
+ if (re.test(stem)) {
+ w = stem + step3list[suffix];
+ }
+ }
+
+ // Step 4
+ re = re_4;
+ re2 = re2_4;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = re_mgr1;
+ if (re.test(stem)) {
+ w = stem;
+ }
+ } else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = re_mgr1;
+ if (re2.test(stem)) {
+ w = stem;
+ }
+ }
+
+ // Step 5
+ re = re_5;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = re_mgr1;
+ re2 = re_meq1;
+ re3 = re3_5;
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
+ w = stem;
+ }
+ }
+
+ re = re_5_1;
+ re2 = re_mgr1;
+ if (re.test(w) && re2.test(w)) {
+ re = re_1b_2;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+
+ if (firstch == "y") {
+ w = firstch.toLowerCase() + w.substr(1);
+ }
+
+ return w;
+ };
+
+ return porterStemmer;
+ })();
+
+ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
+ /*!
+ * lunr.stopWordFilter
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.stopWordFilter is an English language stop word list filter, any words
+ * contained in the list will not be passed through the filter.
+ *
+ * This is intended to be used in the Pipeline. If the token does not pass the
+ * filter then undefined will be returned.
+ *
+ * @module
+ * @param {String} token The token to pass through the filter
+ * @returns {String}
+ * @see lunr.Pipeline
+ */
+ lunr.stopWordFilter = function (token) {
+ if (lunr.stopWordFilter.stopWords.indexOf(token) === -1) return token
+ }
+
+ lunr.stopWordFilter.stopWords = new lunr.SortedSet
+ lunr.stopWordFilter.stopWords.length = 119
+ lunr.stopWordFilter.stopWords.elements = [
+ "",
+ "a",
+ "able",
+ "about",
+ "across",
+ "after",
+ "all",
+ "almost",
+ "also",
+ "am",
+ "among",
+ "an",
+ "and",
+ "any",
+ "are",
+ "as",
+ "at",
+ "be",
+ "because",
+ "been",
+ "but",
+ "by",
+ "can",
+ "cannot",
+ "could",
+ "dear",
+ "did",
+ "do",
+ "does",
+ "either",
+ "else",
+ "ever",
+ "every",
+ "for",
+ "from",
+ "get",
+ "got",
+ "had",
+ "has",
+ "have",
+ "he",
+ "her",
+ "hers",
+ "him",
+ "his",
+ "how",
+ "however",
+ "i",
+ "if",
+ "in",
+ "into",
+ "is",
+ "it",
+ "its",
+ "just",
+ "least",
+ "let",
+ "like",
+ "likely",
+ "may",
+ "me",
+ "might",
+ "most",
+ "must",
+ "my",
+ "neither",
+ "no",
+ "nor",
+ "not",
+ "of",
+ "off",
+ "often",
+ "on",
+ "only",
+ "or",
+ "other",
+ "our",
+ "own",
+ "rather",
+ "said",
+ "say",
+ "says",
+ "she",
+ "should",
+ "since",
+ "so",
+ "some",
+ "than",
+ "that",
+ "the",
+ "their",
+ "them",
+ "then",
+ "there",
+ "these",
+ "they",
+ "this",
+ "tis",
+ "to",
+ "too",
+ "twas",
+ "us",
+ "wants",
+ "was",
+ "we",
+ "were",
+ "what",
+ "when",
+ "where",
+ "which",
+ "while",
+ "who",
+ "whom",
+ "why",
+ "will",
+ "with",
+ "would",
+ "yet",
+ "you",
+ "your"
+ ]
+
+ lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
+ /*!
+ * lunr.trimmer
+ * Copyright (C) 2014 Oliver Nightingale
+ */
+
+ /**
+ * lunr.trimmer is a pipeline function for trimming non word
+ * characters from the begining and end of tokens before they
+ * enter the index.
+ *
+ * This implementation may not work correctly for non latin
+ * characters and should either be removed or adapted for use
+ * with languages with non-latin characters.
+ *
+ * @module
+ * @param {String} token The token to pass through the filter
+ * @returns {String}
+ * @see lunr.Pipeline
+ */
+ lunr.trimmer = function (token) {
+ return token
+ .replace(/^\W+/, '')
+ .replace(/\W+$/, '')
+ }
+
+ lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
+ /*!
+ * lunr.stemmer
+ * Copyright (C) 2014 Oliver Nightingale
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
+ */
+
+ /**
+ * lunr.TokenStore is used for efficient storing and lookup of the reverse
+ * index of token to document ref.
+ *
+ * @constructor
+ */
+ lunr.TokenStore = function () {
+ this.root = { docs: {} }
+ this.length = 0
+ }
+
+ /**
+ * Loads a previously serialised token store
+ *
+ * @param {Object} serialisedData The serialised token store to load.
+ * @returns {lunr.TokenStore}
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.load = function (serialisedData) {
+ var store = new this
+
+ store.root = serialisedData.root
+ store.length = serialisedData.length
+
+ return store
+ }
+
+ /**
+ * Adds a new token doc pair to the store.
+ *
+ * By default this function starts at the root of the current store, however
+ * it can start at any node of any token store if required.
+ *
+ * @param {String} token The token to store the doc under
+ * @param {Object} doc The doc to store against the token
+ * @param {Object} root An optional node at which to start looking for the
+ * correct place to enter the doc, by default the root of this lunr.TokenStore
+ * is used.
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.add = function (token, doc, root) {
+ var root = root || this.root,
+ key = token[0],
+ rest = token.slice(1)
+
+ if (!(key in root)) root[key] = {docs: {}}
+
+ if (rest.length === 0) {
+ root[key].docs[doc.ref] = doc
+ this.length += 1
+ return
+ } else {
+ return this.add(rest, doc, root[key])
+ }
+ }
+
+ /**
+ * Checks whether this key is contained within this lunr.TokenStore.
+ *
+ * By default this function starts at the root of the current store, however
+ * it can start at any node of any token store if required.
+ *
+ * @param {String} token The token to check for
+ * @param {Object} root An optional node at which to start
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.has = function (token) {
+ if (!token) return false
+
+ var node = this.root
+
+ for (var i = 0; i < token.length; i++) {
+ if (!node[token[i]]) return false
+
+ node = node[token[i]]
+ }
+
+ return true
+ }
+
+ /**
+ * Retrieve a node from the token store for a given token.
+ *
+ * By default this function starts at the root of the current store, however
+ * it can start at any node of any token store if required.
+ *
+ * @param {String} token The token to get the node for.
+ * @param {Object} root An optional node at which to start.
+ * @returns {Object}
+ * @see TokenStore.prototype.get
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.getNode = function (token) {
+ if (!token) return {}
+
+ var node = this.root
+
+ for (var i = 0; i < token.length; i++) {
+ if (!node[token[i]]) return {}
+
+ node = node[token[i]]
+ }
+
+ return node
+ }
+
+ /**
+ * Retrieve the documents for a node for the given token.
+ *
+ * By default this function starts at the root of the current store, however
+ * it can start at any node of any token store if required.
+ *
+ * @param {String} token The token to get the documents for.
+ * @param {Object} root An optional node at which to start.
+ * @returns {Object}
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.get = function (token, root) {
+ return this.getNode(token, root).docs || {}
+ }
+
+ lunr.TokenStore.prototype.count = function (token, root) {
+ return Object.keys(this.get(token, root)).length
+ }
+
+ /**
+ * Remove the document identified by ref from the token in the store.
+ *
+ * By default this function starts at the root of the current store, however
+ * it can start at any node of any token store if required.
+ *
+ * @param {String} token The token to get the documents for.
+ * @param {String} ref The ref of the document to remove from this token.
+ * @param {Object} root An optional node at which to start.
+ * @returns {Object}
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.remove = function (token, ref) {
+ if (!token) return
+ var node = this.root
+
+ for (var i = 0; i < token.length; i++) {
+ if (!(token[i] in node)) return
+ node = node[token[i]]
+ }
+
+ delete node.docs[ref]
+ }
+
+ /**
+ * Find all the possible suffixes of the passed token using tokens
+ * currently in the store.
+ *
+ * @param {String} token The token to expand.
+ * @returns {Array}
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.expand = function (token, memo) {
+ var root = this.getNode(token),
+ docs = root.docs || {},
+ memo = memo || []
+
+ if (Object.keys(docs).length) memo.push(token)
+
+ Object.keys(root)
+ .forEach(function (key) {
+ if (key === 'docs') return
+
+ memo.concat(this.expand(token + key, memo))
+ }, this)
+
+ return memo
+ }
+
+ /**
+ * Returns a representation of the token store ready for serialisation.
+ *
+ * @returns {Object}
+ * @memberOf TokenStore
+ */
+ lunr.TokenStore.prototype.toJSON = function () {
+ return {
+ root: this.root,
+ length: this.length
+ }
+ }
+
+
+ /**
+ * export the module via AMD, CommonJS or as a browser global
+ * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
+ */
+ ;(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory)
+ } else if (typeof exports === 'object') {
+ /**
+ * Node. Does not work with strict CommonJS, but
+ * only CommonJS-like enviroments that support module.exports,
+ * like Node.
+ */
+ module.exports = factory()
+ } else {
+ // Browser globals (root is window)
+ root.lunr = factory()
+ }
+ }(this, function () {
+ /**
+ * Just return a value to define the module export.
+ * This example returns an object, but the module
+ * can return a function as the exported value.
+ */
+ return lunr
+ }))
+})()
diff --git a/samples/rest-notes-slate/slate/source/layouts/layout.erb b/samples/rest-notes-slate/slate/source/layouts/layout.erb
new file mode 100644
index 00000000..36ae0f95
--- /dev/null
+++ b/samples/rest-notes-slate/slate/source/layouts/layout.erb
@@ -0,0 +1,102 @@
+<%#
+Copyright 2008-2013 Concur Technologies, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may
+not use this file except in compliance with the License. You may obtain
+a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations
+under the License.
+%>
+<% language_tabs = current_page.data.language_tabs %>
+
+
+
+
+
+
+ <%= current_page.data.title || "API Documentation" %>
+
+ <%= stylesheet_link_tag :screen, media: :screen %>
+ <%= stylesheet_link_tag :print, media: :print %>
+
+ <% if current_page.data.search %>
+ <%= javascript_include_tag "all" %>
+ <% else %>
+ <%= javascript_include_tag "all_nosearch" %>
+ <% end %>
+
+ <% if language_tabs %>
+
+ <% end %>
+
+
+
+
+
+ NAV
+ <%= image_tag('navbar.png') %>
+
+
+