From d4fbac654152fc61ce4b6da777b8de2350dc6e17 Mon Sep 17 00:00:00 2001 From: Joe Stein Date: Mon, 8 Feb 2021 16:56:49 -0500 Subject: [PATCH 01/45] Switch to OpenAPI v3 + redoc --- CHANGELOG.md | 117 - Gemfile | 12 - Gemfile.lock | 136 - LICENSE | 13 - README.md | 17 +- Vagrantfile | 41 - config.rb | 60 - deploy.sh | 203 - docs/index.html | 1092 ++ font-selection.json | 148 - lib/multilang.rb | 16 - lib/toc_data.rb | 30 - lib/unique_head.rb | 17 - paths/add_user_to_group.yaml | 74 + paths/create_comment.yaml | 45 + paths/create_expense.yaml | 42 + paths/create_friend.yaml | 38 + paths/create_friends.yaml | 67 + paths/create_group.yaml | 63 + paths/delete_comment.yaml | 37 + paths/delete_expense.yaml | 37 + paths/delete_friend.yaml | 47 + paths/delete_group.yaml | 29 + paths/get_categories.yaml | 22 + paths/get_comments.yaml | 30 + paths/get_currencies.yaml | 29 + paths/get_current_user.yaml | 14 + paths/get_expense.yaml | 27 + paths/get_expenses.yaml | 66 + paths/get_friend.yaml | 28 + paths/get_friends.yaml | 22 + paths/get_group.yaml | 27 + paths/get_groups.yaml | 21 + paths/get_notifications.yaml | 63 + paths/get_user.yaml | 24 + paths/index.yaml | 57 + paths/parse_sentence.yaml | 77 + paths/remove_user_from_group.yaml | 50 + paths/undelete_expense.yaml | 24 + paths/undelete_group.yaml | 40 + paths/update_expense.yaml | 43 + paths/update_user.yaml | 40 + responses/forbidden.yaml | 5 + responses/not_found.yaml | 5 + responses/unauthorized.yaml | 5 + schemas/balance.yaml | 8 + schemas/category.yaml | 33 + schemas/comment.yaml | 27 + schemas/comment_user.yaml | 17 + schemas/current_user.yaml | 21 + schemas/debt.yaml | 16 + schemas/errors/forbidden.yaml | 11 + schemas/errors/not_found.yaml | 11 + schemas/errors/unauthorized.yaml | 5 + schemas/expense.yaml | 144 + schemas/expense/by_shares.yaml | 38 + schemas/expense/common.yaml | 33 + schemas/expense/equal_group_split.yaml | 11 + schemas/friend.yaml | 23 + schemas/group.yaml | 80 + schemas/notification.yaml | 38 + schemas/notification_settings.yaml | 6 + schemas/parent_category.yaml | 13 + schemas/share.yaml | 16 + schemas/user.yaml | 25 + source/CNAME | 1 - source/fonts/slate.eot | Bin 1876 -> 0 bytes source/fonts/slate.svg | 14 - source/fonts/slate.ttf | Bin 1720 -> 0 bytes source/fonts/slate.woff | Bin 1796 -> 0 bytes source/fonts/slate.woff2 | Bin 796 -> 0 bytes source/images/favicon.ico | Bin 5430 -> 0 bytes source/images/logo.svg | 27 - source/images/navbar.png | Bin 96 -> 0 bytes source/includes/_comments.md | 57 - source/includes/_errors.md | 16 - source/includes/_expenses.md | 179 - source/includes/_friends.md | 187 - source/includes/_groups.md | 254 - source/includes/_notifications.md | 58 - source/includes/_other.md | 229 - source/includes/_terms_of_use.md | 222 - source/includes/_users.md | 89 - source/index.html.md | 284 - source/javascripts/all.js | 2 - source/javascripts/all_nosearch.js | 16 - source/javascripts/app/_lang.js | 164 - source/javascripts/app/_search.js | 98 - source/javascripts/app/_toc.js | 117 - source/javascripts/lib/_energize.js | 169 - source/javascripts/lib/_imagesloaded.min.js | 7 - source/javascripts/lib/_jquery.highlight.js | 108 - source/javascripts/lib/_jquery.js | 9831 ------------------- source/javascripts/lib/_lunr.js | 1910 ---- source/layouts/layout.erb | 118 - source/stylesheets/_icon-font.scss | 38 - source/stylesheets/_normalize.scss | 427 - source/stylesheets/_variables.scss | 103 - source/stylesheets/print.css.scss | 147 - source/stylesheets/screen.css.scss | 614 -- splitwise.yaml | 343 + 101 files changed, 3224 insertions(+), 16281 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 LICENSE delete mode 100644 Vagrantfile delete mode 100644 config.rb delete mode 100755 deploy.sh create mode 100644 docs/index.html delete mode 100755 font-selection.json delete mode 100644 lib/multilang.rb delete mode 100644 lib/toc_data.rb delete mode 100644 lib/unique_head.rb create mode 100644 paths/add_user_to_group.yaml create mode 100644 paths/create_comment.yaml create mode 100644 paths/create_expense.yaml create mode 100644 paths/create_friend.yaml create mode 100644 paths/create_friends.yaml create mode 100644 paths/create_group.yaml create mode 100644 paths/delete_comment.yaml create mode 100644 paths/delete_expense.yaml create mode 100644 paths/delete_friend.yaml create mode 100644 paths/delete_group.yaml create mode 100644 paths/get_categories.yaml create mode 100644 paths/get_comments.yaml create mode 100644 paths/get_currencies.yaml create mode 100644 paths/get_current_user.yaml create mode 100644 paths/get_expense.yaml create mode 100644 paths/get_expenses.yaml create mode 100644 paths/get_friend.yaml create mode 100644 paths/get_friends.yaml create mode 100644 paths/get_group.yaml create mode 100644 paths/get_groups.yaml create mode 100644 paths/get_notifications.yaml create mode 100644 paths/get_user.yaml create mode 100644 paths/index.yaml create mode 100644 paths/parse_sentence.yaml create mode 100644 paths/remove_user_from_group.yaml create mode 100644 paths/undelete_expense.yaml create mode 100644 paths/undelete_group.yaml create mode 100644 paths/update_expense.yaml create mode 100644 paths/update_user.yaml create mode 100644 responses/forbidden.yaml create mode 100644 responses/not_found.yaml create mode 100644 responses/unauthorized.yaml create mode 100644 schemas/balance.yaml create mode 100644 schemas/category.yaml create mode 100644 schemas/comment.yaml create mode 100644 schemas/comment_user.yaml create mode 100644 schemas/current_user.yaml create mode 100644 schemas/debt.yaml create mode 100644 schemas/errors/forbidden.yaml create mode 100644 schemas/errors/not_found.yaml create mode 100644 schemas/errors/unauthorized.yaml create mode 100644 schemas/expense.yaml create mode 100644 schemas/expense/by_shares.yaml create mode 100644 schemas/expense/common.yaml create mode 100644 schemas/expense/equal_group_split.yaml create mode 100644 schemas/friend.yaml create mode 100644 schemas/group.yaml create mode 100644 schemas/notification.yaml create mode 100644 schemas/notification_settings.yaml create mode 100644 schemas/parent_category.yaml create mode 100644 schemas/share.yaml create mode 100644 schemas/user.yaml delete mode 100644 source/CNAME delete mode 100755 source/fonts/slate.eot delete mode 100755 source/fonts/slate.svg delete mode 100755 source/fonts/slate.ttf delete mode 100755 source/fonts/slate.woff delete mode 100755 source/fonts/slate.woff2 delete mode 100644 source/images/favicon.ico delete mode 100644 source/images/logo.svg delete mode 100644 source/images/navbar.png delete mode 100644 source/includes/_comments.md delete mode 100644 source/includes/_errors.md delete mode 100644 source/includes/_expenses.md delete mode 100644 source/includes/_friends.md delete mode 100644 source/includes/_groups.md delete mode 100644 source/includes/_notifications.md delete mode 100644 source/includes/_other.md delete mode 100644 source/includes/_terms_of_use.md delete mode 100644 source/includes/_users.md delete mode 100644 source/index.html.md delete mode 100644 source/javascripts/all.js delete mode 100644 source/javascripts/all_nosearch.js delete mode 100644 source/javascripts/app/_lang.js delete mode 100644 source/javascripts/app/_search.js delete mode 100644 source/javascripts/app/_toc.js delete mode 100644 source/javascripts/lib/_energize.js delete mode 100644 source/javascripts/lib/_imagesloaded.min.js delete mode 100644 source/javascripts/lib/_jquery.highlight.js delete mode 100644 source/javascripts/lib/_jquery.js delete mode 100644 source/javascripts/lib/_lunr.js delete mode 100644 source/layouts/layout.erb delete mode 100644 source/stylesheets/_icon-font.scss delete mode 100644 source/stylesheets/_normalize.scss delete mode 100644 source/stylesheets/_variables.scss delete mode 100644 source/stylesheets/print.css.scss delete mode 100644 source/stylesheets/screen.css.scss create mode 100644 splitwise.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 3b897a2..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,117 +0,0 @@ -# Changelog - -## Version 2.0.0 - -*July 17, 2017* - -- All-new statically generated table of contents - - Should be much faster loading and scrolling for large pages - - Smaller Javascript file sizes - - Avoids the problem with the last link in the ToC not ever highlighting if the section was shorter than the page - - Fixes control-click not opening in a new page - - Automatically updates the HTML title as you scroll -- Updated design - - New default colors! - - New spacings and sizes! - - System-default typefaces, just like GitHub -- Added search input delay on large corpuses to reduce lag -- We even bumped the major version cause hey, why not? -- Various small bug fixes - -Thanks to everyone who helped debug or wrote code for this version! It was a serious community effort, and I couldn't have done it alone. - -## Version 1.5.0 - -*February 23, 2017* - -- Add [multiple tabs per programming language](https://github.com/lord/slate/wiki/Multiple-language-tabs-per-programming-language) feature -- Upgrade Middleman to add Ruby 1.4.0 compatibility -- Switch default code highlighting color scheme to better highlight JSON -- Various small typo and bug fixes - -## Version 1.4.0 - -*November 24, 2016* - -- Upgrade Middleman and Rouge gems, should hopefully solve a number of bugs -- Update some links in README -- Fix broken Vagrant startup script -- Fix some problems with deploy.sh help message -- Fix bug with language tabs not hiding properly if no error -- Add `!default` to SASS variables -- Fix bug with logo margin -- Bump tested Ruby versions in .travis.yml - -## Version 1.3.3 - -*June 11, 2016* - -Documentation and example changes. - -## Version 1.3.2 - -*February 3, 2016* - -A small bugfix for slightly incorrect background colors on code samples in some cases. - -## Version 1.3.1 - -*January 31, 2016* - -A small bugfix for incorrect whitespace in code blocks. - -## Version 1.3 - -*January 27, 2016* - -We've upgraded Middleman and a number of other dependencies, which should fix quite a few bugs. - -Instead of `rake build` and `rake deploy`, you should now run `bundle exec middleman build --clean` to build your server, and `./deploy.sh` to deploy it to Github Pages. - -## 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 27, 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/Gemfile b/Gemfile deleted file mode 100644 index 25296a7..0000000 --- a/Gemfile +++ /dev/null @@ -1,12 +0,0 @@ -ruby '>=2.3.1' -source 'https://rubygems.org' - -# Middleman -gem 'middleman', '~> 4.3' -gem 'middleman-syntax', '~> 3.2' -gem 'middleman-autoprefixer', '~> 2.7' -gem "middleman-sprockets", "~> 4.1" -gem 'rouge', '~> 3.21' -gem 'redcarpet', '~> 3.5.0' -gem 'nokogiri', '~> 1.10.8' -gem 'sass' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index bcb1fac..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,136 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (5.2.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - autoprefixer-rails (8.6.5) - execjs - backports (3.20.1) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - concurrent-ruby (1.1.7) - contracts (0.13.0) - dotenv (2.7.6) - erubis (2.7.0) - execjs (2.7.0) - fast_blank (1.0.0) - fastimage (2.2.1) - ffi (1.14.2) - haml (5.2.1) - temple (>= 0.8.0) - tilt - hamster (3.0.0) - concurrent-ruby (~> 1.0) - hashie (3.6.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - kramdown (2.3.0) - rexml - listen (3.0.8) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - memoist (0.16.2) - middleman (4.3.11) - coffee-script (~> 2.2) - haml (>= 4.0.5) - kramdown (>= 2.3.0) - middleman-cli (= 4.3.11) - middleman-core (= 4.3.11) - middleman-autoprefixer (2.9.0) - autoprefixer-rails (~> 8.0) - middleman-core (>= 3.3.3) - middleman-cli (4.3.11) - thor (>= 0.17.0, < 2.0) - middleman-core (4.3.11) - activesupport (>= 4.2, < 6.0) - addressable (~> 2.3) - backports (~> 3.6) - bundler - contracts (~> 0.13.0) - dotenv - erubis - execjs (~> 2.0) - fast_blank - fastimage (~> 2.0) - hamster (~> 3.0) - hashie (~> 3.4) - i18n (~> 0.9.0) - listen (~> 3.0.0) - memoist (~> 0.14) - padrino-helpers (~> 0.13.0) - parallel - rack (>= 1.4.5, < 3) - sassc (~> 2.0) - servolux - tilt (~> 2.0.9) - uglifier (~> 3.0) - middleman-sprockets (4.1.1) - middleman-core (~> 4.0) - sprockets (>= 3.0) - middleman-syntax (3.2.0) - middleman-core (>= 3.2) - rouge (~> 3.2) - mini_portile2 (2.4.0) - minitest (5.14.3) - nokogiri (1.10.8) - mini_portile2 (~> 2.4.0) - padrino-helpers (0.13.3.4) - i18n (~> 0.6, >= 0.6.7) - padrino-support (= 0.13.3.4) - tilt (>= 1.4.1, < 3) - padrino-support (0.13.3.4) - activesupport (>= 3.1) - parallel (1.20.1) - public_suffix (4.0.6) - rack (2.2.3) - rb-fsevent (0.10.4) - rb-inotify (0.10.1) - ffi (~> 1.0) - redcarpet (3.5.1) - rexml (3.2.4) - rouge (3.26.0) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sassc (2.4.0) - ffi (~> 1.9) - servolux (0.13.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - temple (0.8.2) - thor (1.0.1) - thread_safe (0.3.6) - tilt (2.0.10) - tzinfo (1.2.9) - thread_safe (~> 0.1) - uglifier (3.2.0) - execjs (>= 0.3.0, < 3) - -PLATFORMS - ruby - -DEPENDENCIES - middleman (~> 4.3) - middleman-autoprefixer (~> 2.7) - middleman-sprockets (~> 4.1) - middleman-syntax (~> 3.2) - nokogiri (~> 1.10.8) - redcarpet (~> 3.5.0) - rouge (~> 3.21) - sass - -RUBY VERSION - ruby 2.3.3p222 - -BUNDLED WITH - 2.1.4 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5ceddf5..0000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -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/README.md b/README.md index 7eecd9c..96e819a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,19 @@ If something in the API is confusing you, you can open an [issue](https://github If you spot an issue in our API documentation itself, feel free to open a [pull request](https://github.com/splitwise/api-docs/pulls) to update this repo! Most of the relevant files live in the [/source/includes](https://github.com/splitwise/api-docs/tree/master/source/includes) directory. -## Powered by Slate +## Powered by OpenAPI + redocly -These API docs were built with [Slate](https://github.com/lord/slate). If you're looking to build an API documentation page for your own app, we'd highly recommend it 🙂 +These API docs follow the [OpenAPI v3](https://swagger.io/specification/) specification. The website is built with [redoc](https://github.com/Redocly/redoc). + +### Developing +``` +$ npm install -g redoc-cli +$ redoc-cli serve splitwise.yaml --watch +``` + +### Compiling +This will create a zero-dependency HTML file at docs/index.html. + +``` +$ redoc-cli bundle splitwise.yaml -o docs/ +``` diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 2fccb7c..0000000 --- a/Vagrantfile +++ /dev/null @@ -1,41 +0,0 @@ -Vagrant.configure(2) do |config| - config.vm.box = "ubuntu/trusty64" - config.vm.network :forwarded_port, guest: 4567, host: 4567 - - config.vm.provision "bootstrap", - type: "shell", - inline: <<-SHELL - sudo apt-add-repository ppa:brightbox/ruby-ng - sudo apt-get update - sudo apt-get install -yq ruby2.4 ruby2.4-dev - sudo apt-get install -yq pkg-config build-essential nodejs git libxml2-dev libxslt-dev - sudo apt-get autoremove -yq - gem2.4 install --no-ri --no-rdoc bundler - SHELL - - # add the local user git config to the vm - config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" - - config.vm.provision "install", - type: "shell", - privileged: false, - inline: <<-SHELL - echo "==============================================" - echo "Installing app dependencies" - cd /vagrant - bundle config build.nokogiri --use-system-libraries - bundle install - SHELL - - config.vm.provision "run", - type: "shell", - privileged: false, - run: "always", - inline: <<-SHELL - echo "==============================================" - echo "Starting up middleman at http://localhost:4567" - echo "If it does not come up, check the ~/middleman.log file for any error messages" - cd /vagrant - bundle exec middleman server --watcher-force-polling --watcher-latency=1 &> ~/middleman.log & - SHELL -end \ No newline at end of file diff --git a/config.rb b/config.rb deleted file mode 100644 index 45ae0c9..0000000 --- a/config.rb +++ /dev/null @@ -1,60 +0,0 @@ -# Unique header generation -require './lib/unique_head.rb' - -# 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, - renderer: UniqueHeadCounter - -# handle the "CNAME" file for GitHub Pages -page "CNAME", layout: false - -# Assets -set :css_dir, 'stylesheets' -set :js_dir, 'javascripts' -set :images_dir, 'images' -set :fonts_dir, 'fonts' - -# Activate the syntax highlighter -activate :syntax -ready do - require './lib/multilang.rb' -end - -activate :sprockets - -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 -configure :build do - # If you're having trouble with Middleman hanging, commenting - # out the following two lines has been known to help - activate :minify_css - activate :minify_javascript - # activate :relative_assets - # activate :asset_hash - # activate :gzip -end - -# Deploy Configuration -# If you want Middleman to listen on a different port, you can set that below -set :port, 4567 - -helpers do - require './lib/toc_data.rb' -end diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 909a9d9..0000000 --- a/deploy.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env bash -set -o errexit #abort if any command fails -me=$(basename "$0") - -help_message="\ -Usage: $me [-c FILE] [] -Deploy generated files to a git branch. - -Options: - - -h, --help Show this help information. - -v, --verbose Increase verbosity. Useful for debugging. - -e, --allow-empty Allow deployment of an empty directory. - -m, --message MESSAGE Specify the message used when committing on the - deploy branch. - -n, --no-hash Don't append the source commit's hash to the deploy - commit's message. -" - -bundle exec middleman build --clean - -parse_args() { - # Set args from a local environment file. - if [ -e ".env" ]; then - source .env - fi - - # Parse arg flags - # If something is exposed as an environment variable, set/overwrite it - # here. Otherwise, set/overwrite the internal variable instead. - while : ; do - if [[ $1 = "-h" || $1 = "--help" ]]; then - echo "$help_message" - return 0 - elif [[ $1 = "-v" || $1 = "--verbose" ]]; then - verbose=true - shift - elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then - allow_empty=true - shift - elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then - commit_message=$2 - shift 2 - elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then - GIT_DEPLOY_APPEND_HASH=false - shift - else - break - fi - done - - # Set internal option vars from the environment and arg flags. All internal - # vars should be declared here, with sane defaults if applicable. - - # Source directory & target branch. - deploy_directory=build - deploy_branch=gh-pages - - #if no user identity is already set in the current git environment, use this: - default_username=${GIT_DEPLOY_USERNAME:-deploy.sh} - default_email=${GIT_DEPLOY_EMAIL:-} - - #repository to deploy to. must be readable and writable. - repo=origin - - #append commit hash to the end of message by default - append_hash=${GIT_DEPLOY_APPEND_HASH:-true} -} - -main() { - parse_args "$@" - - enable_expanded_output - - if ! git diff --exit-code --quiet --cached; then - echo Aborting due to uncommitted changes in the index >&2 - return 1 - fi - - commit_title=`git log -n 1 --format="%s" HEAD` - commit_hash=` git log -n 1 --format="%H" HEAD` - - #default commit message uses last title if a custom one is not supplied - if [[ -z $commit_message ]]; then - commit_message="publish: $commit_title" - fi - - #append hash to commit message unless no hash flag was found - if [ $append_hash = true ]; then - commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash" - fi - - previous_branch=`git rev-parse --abbrev-ref HEAD` - - if [ ! -d "$deploy_directory" ]; then - echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2 - return 1 - fi - - # must use short form of flag in ls for compatibility with OS X and BSD - if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then - echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2 - return 1 - fi - - if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then - # deploy_branch exists in $repo; make sure we have the latest version - - disable_expanded_output - git fetch --force $repo $deploy_branch:$deploy_branch - enable_expanded_output - fi - - # check if deploy_branch exists locally - if git show-ref --verify --quiet "refs/heads/$deploy_branch" - then incremental_deploy - else initial_deploy - fi - - restore_head -} - -initial_deploy() { - git --work-tree "$deploy_directory" checkout --orphan $deploy_branch - git --work-tree "$deploy_directory" add --all - commit+push -} - -incremental_deploy() { - #make deploy_branch the current branch - git symbolic-ref HEAD refs/heads/$deploy_branch - #put the previously committed contents of deploy_branch into the index - git --work-tree "$deploy_directory" reset --mixed --quiet - git --work-tree "$deploy_directory" add --all - - set +o errexit - diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$? - set -o errexit - case $diff in - 0) echo No changes to files in $deploy_directory. Skipping commit.;; - 1) commit+push;; - *) - echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to master, use: git symbolic-ref HEAD refs/heads/master && git reset --mixed >&2 - return $diff - ;; - esac -} - -commit+push() { - set_user_id - git --work-tree "$deploy_directory" commit -m "$commit_message" - - disable_expanded_output - #--quiet is important here to avoid outputting the repo URL, which may contain a secret token - git push --quiet $repo $deploy_branch - enable_expanded_output -} - -#echo expanded commands as they are executed (for debugging) -enable_expanded_output() { - if [ $verbose ]; then - set -o xtrace - set +o verbose - fi -} - -#this is used to avoid outputting the repo URL, which may contain a secret token -disable_expanded_output() { - if [ $verbose ]; then - set +o xtrace - set -o verbose - fi -} - -set_user_id() { - if [[ -z `git config user.name` ]]; then - git config user.name "$default_username" - fi - if [[ -z `git config user.email` ]]; then - git config user.email "$default_email" - fi -} - -restore_head() { - if [[ $previous_branch = "HEAD" ]]; then - #we weren't on any branch before, so just set HEAD back to the commit it was on - git update-ref --no-deref HEAD $commit_hash $deploy_branch - else - git symbolic-ref HEAD refs/heads/$previous_branch - fi - - git reset --mixed -} - -filter() { - sed -e "s|$repo|\$repo|g" -} - -sanitize() { - "$@" 2> >(filter 1>&2) | filter -} - -[[ $1 = --source-only ]] || main "$@" diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8540869 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,1092 @@ + + + + + + Splitwise API + + + + + + + + + +

Splitwise API (3.0.0)

Download OpenAPI specification:Download

Introduction

Hey there! We're glad you're interested in the Splitwise API. This documentation will help you to fetch information +on users, expenses, groups, and much more.

+

If something in the API is confusing you, you can open an issue about it on GitHub. +We're a small team, so we may not have an instant fix, but we'll get back to you as soon as we're able. +(If you spot an issue in our API documentation itself, feel free to open a pull request to update this website!)

+

Third-Party SDKs

The development community has built a number of unofficial, third-party SDKs for Splitwise in a variety of different languages.

+ +

If you've built a third-party SDK for Splitwise and you'd like to see it included in this list, then please open a pull request to update this section and add a new link. Thank you for your work!

+

Note: These links are provided for convenience. These libraries have not been reviewed or endorsed by Splitwise, and Splitwise +cannot vouch for their quality. If you have questions or bug reports, please direct your feedback to the authors of these libraries.

+

Terms of Use

Overview

Splitwise provides this Self-Serve API to facilitate integrations with third-party applications, as well as open-up functionality for hobbyists and power users to programmatically interact with their own Splitwise account and build plugins or other tools.

+

If you’re interested in integrating your commercial application with Splitwise, we strongly encourage you to contact developers@splitwise.com so our development team can help discuss your use case, provide private APIs and Enterprise support, and offer an appropriate commercial license for the integration. The Self-Serve API documented here may be suitable for internal prototyping and other exploratory work.

+

If you are developing a non-commercial plugin application or personal project, we recommend you make use of the Self-Serve API documented here under the API Terms Of Use. Please be aware that our Self-Serve API has conservative rate and access limits, which are subject to change at any time and not well suited to commercial projects. If this is a problem for your use case, please contact us at developers@splitwise.com to discuss your needs.

+

All Self-Service API users are subject to the API Terms of Use below.

+

TERMS OF USE

These API Terms of Use describe your rights and responsibilities when accessing our publicly available Application Programming Interface (API) and related API documentation. Please review them carefully.

+

Splitwise may modify this Agreement at any time by posting a revised version on our website. The revised version will be effective at the time that it is posted.

+

These API terms form a binding contract between you and us. In these terms "you," and "your," refers to the individual, company or legal entity and/or entities that you represent while accessing the API. “We”, “us”, “our” and “Splitwise” refers to Splitwise Inc. By accepting these API terms, either by accessing or using the API, or authorizing or permitting any individual to access or use the API, you agree to be bound by this contract.

+
    +
  1. + API License: +
      +
    1. + Subject to the restrictions in these terms, we grant you a non-exclusive, revocable, worldwide, non-transferable, non-sublicensable, limited license to access and use (i) our APIs (ii) related API documentation, packages, sample code, software, or materials made available by Splitwise (“API Documentation”), and (iii) any and all access keys or data derived or obtained from Splitwise API responses (“Splitwise Data”). The Splitwise API, Splitwise Data, and API Documentation will be together referred to as the “Splitwise Materials.” You will use Splitwise Materials solely as necessary to develop, test and support a Self-Service integration of your software application (an "Application" or "App") with Splitwise in accordance with this Agreement and any other agreements between You and Splitwise. +
    2. +
    +
  2. +
  3. + API License Restrictions +
      +
    1. + You agree that will you will not, and will not allow any of your partners, subsidiaries and/or affiliates and each of their respective directors, officers, employees, agents, partners, suppliers, service providers, contractors or end users (collectively, “Your Affiliates”) to engage in any Prohibited Activities set forth in section 2f. +
    2. +
    3. + Splitwise reserves the right to block or revoke, with or without notice, your access to any or all of the Splitwise Materials if Splitwise determines in its sole discretion that you are engaging in any of the Prohibited Activities. +
    4. +
    5. + Splitwise may monitor your use of Splitwise Materials to improve our services and ensure compliance with this agreement, and may suspend your access to Splitwise Materials if we believe you are in violation. +
    6. +
    7. + Your use of the Splitwise API is subject to usage limits and other functional restrictions in the sole discretion of Splitwise. You will not use the API in a manner that exceeds rate limits, or constitutes excessive or abusive usage. +
    8. +
    9. + Your use of Splitwise Materials must respect Splitwise user’s privacy choices and settings and the Privacy portion of this agreement. You will obtain explicit consent from end users as a basis for any processing of Splitwise Materials. Your use of Splitwise Materials must comply with all Applicable Data Protection Laws applicable to you, including but not limited to GDPR and CCPA compliance. +
    10. +
    11. + Prohibited Activities: +
        +
      1. + You will not use Splitwise Materials or any part thereof in any manner or for any purpose that violates any law or regulation, or any right of any person, including but not limited to intellectual property rights, rights of privacy and/or publicity, or which otherwise results in liability to Splitwise, or its officers, employees, or end users. +
      2. +
      3. + You will not use Splitwise Materials in a way that poses a security, operational or technical risk to our Services. +
      4. +
      5. + You may not Splitwise Materials to create an application that replicates existing Splitwise functionality or competes with Splitwise and our Services. +
      6. +
      7. + You will not use Splitwise Materials to create an application that encourages or creates functionality for users to violate our Terms of Service. +
      8. +
      9. + You will not use Splitwise Materials to create an application that can be used by anyone under the age of 13. You will not knowingly collect or enable the collection of any personal information from children under the age of 13. +
      10. +
      11. + You will not reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code or underlying ideas, trade secrets, algorithms or structure of the Splitwise Materials, or Splitwise software applications. +
      12. +
      13. + You will not attempt to defeat, avoid, bypass, remove, deactivate or otherwise circumvent any software protection mechanisms in the Splitwise Materials or Application or any part thereof, including without limitation, any such mechanism used to restrict or control the functionality of the API. +
      14. +
      15. + You will not use Splitwise’s name to endorse or promote any product, including a product derived from Splitwise Materials. +
      16. +
      17. + You will not sell, lease, rent, sublicense or in any way otherwise commercialize any Splitwise Data, or dataset derived from Splitwise Data and/or Splitwise Materials. +
      18. +
      19. + You will not use Splitwise Materials in applications that send unsolicited communications to users or include any malware, adware, potentially unwanted programs, or similar applications that could damage or disparage Splitwise’s reputation or services. +
      20. +
      +
    +
  4. +
  5. + Privacy +
      +
    1. + Your Application shall have a lawful privacy policy, accessible with reasonably prominent hyperlinks that does not conflict with or supersede the Splitwise Privacy Policy and that explains how you collect, store, use, and/or transfer any Personal Data via your Applications. Personal Data is data that may be used, either alone or together with other information, to identify an individual user, including, without limitation, a user’s name, address, telephone number, username, email address, city and country, geolocation, unique identifiers, picture, or other similar information and includes personal data as defined in the GDPR. +
    2. +
    3. + You are responsible for maintaining an appropriate legal basis to process any data under all applicable data protection laws (including but not limited to the GDPR, and the CCPA). +
    4. +
    5. + You will use industry standard security measures to protect against and prevent security breaches and any unauthorized disclosure of any personal information you process, including administrative, physical and technical safeguards for protection of the security, confidentiality and integrity of that personal information. +
    6. +
    7. + You must promptly notify us in writing via email to security@splitwise.com of any security deficiencies in, or intrusions to, your Applications or systems that you discover, and of any breaches of your user agreement or privacy policy that impact or may impact Splitwise customers. Please review our Privacy Policy for more information on how we collect and use data relating to the use and performance of our Service. +
    8. +
    9. + You will delete Splitwise Data as requested within a reasonable time, if so requested by either a Splitwise User or Splitwise Inc. +
    10. +
    11. + Any data submitted to Splitwise through your use of the Splitwise API will be governed by the Splitwise Privacy Policy. +
    12. +
    13. + You agree that Splitwise may collect certain use data and information related to your use of the Splitwise Materials, and the Splitwise API in connection with your Application (“Usage Data”), and that Splitwise may use such Usage Data for any business purpose, internal or external, including, without limitation, providing enhancements to the Splitwise Materials or Splitwise Platform, providing developer of user support, or otherwise. You agree to include a statement to this effect in your Application’s Privacy Policy. +
    14. +
    +
  6. + +
  7. + Conditions Of Use +
      +
    1. + Splitwise reserves the right to modify our API at any time, for any reason, without notice. +
    2. +
    3. + Splitwise may use your name, and other contact details to contact you regarding your use of our API or, if we believe you are in violation of this contract. +
    4. +
    5. + You are solely responsible for your use of the Splitwise API and any application you create that uses Splitwise Materials, including but not limited to Customer Support. +
    6. +
    7. + Splitwise reserves the right to develop and extend its products and capabilities without regard to whether those products compete with or invalidate your Splitwise integration or products offered by you. +
    8. +
    9. + Splitwise may limit (i) the number of network calls that your App may make via the API; and (ii) the maximum number of Splitwise users that may connect your Application, or (iii) anything else about the Splitwise API as Splitwise deems appropriate, at Splitwise’s sole discretion. +
    10. +
    11. + Splitwise may impose or modify these limitations without notice. Splitwise may utilize technical measures to prevent over-usage and stop usage of the API by your App after any usage limitations are exceeded or suspend your access to the API with or without notice to you in the event you exceed such limitations. +
    12. +
    13. + You will not issue any press release or other announcement regarding your Application that makes any reference to Splitwise without our prior written consent. +
    14. +
    15. + You will not use our API to distribute unsolicited advertising or promotions, or to send messages, make comments, or initiate any other unsolicited direct communication or contact with Splitwise users or partners. +
    16. +
    +
  8. + +
  9. + Use of Splitwise Marks +
      +
    1. + The rights granted in this Agreement do not include any general right to use the Splitwise name or any Splitwise trademarks, service marks or logos (the “Splitwise Marks”) with respect to your Applications. Subject to your continued compliance with this Agreement, you may use Splitwise Marks for limited purposes related to your Applications only as described in Splitwise Branding Guidelines and/or as provided in written communications with the Splitwise team. +
    2. +
    3. + These rights apply on a non-exclusive, non-transferable, worldwide, royalty-free basis, without any right to sub-license, and may be revoked by Splitwise at any time. +
    4. +
    5. + If Splitwise updates Branding Guidelines or any Splitwise Marks that you are using, you agree to update such Splitwise Marks to reflect the most current versions. You must not use any Splitwise Marks or trade dress, or any confusingly similar mark or trade dress, as the name or part of the name, user interface, or icon of your Applications, or as part of any logo or branding for your Applications. +
    6. +
    +
  10. + +
  11. + Reservation Of Rights. The Splitwise Materials as well as the trademarks, copyrights, trade secrets, patents or other intellectual property (collectively, “Intellectual Property”) contained therein will remain the sole and exclusive property of Splitwise, and you will reasonably assist Splitwise in protecting such ownership. Splitwise reserves to itself all rights to the Splitwise Materials not expressly granted to You. Except as expressly provided in this Agreement, You do not acquire any rights to or interest in the Intellectual Property. You will not utilize Splitwise Intellectual Property except as expressly authorized under this Agreement. +
  12. + +
  13. + Feedback. Splitwise welcomes feedback from developers to improve our API, documentation and Services, and may provide feedback to you as well. We will review any feedback received, however we make no guarantee that suggestions will be implemented. If you choose to provide feedback, suggestions or comments regarding the Splitwise API, documentation, or services, you acknowledge that Splitwise will be free to use your feedback in any way it sees fit. This includes the freedom to copy, modify, create derivative works, distribute, publicly display, publicly perform, grant sublicenses to, and otherwise exploit in any manner such feedback, suggestions or comments, for any and all purposes, with no obligation of any kind to you, in perpetuity. +
  14. + +
  15. + Confidentiality. Any information not generally available to the public that is made available to you should be considered Confidential. You agree to: +
      +
    1. + Protect this information from unauthorized use, access, or disclosure, +
    2. +
    3. + Use this information only as necessary, +
    4. +
    5. + Destroy any copies, or return this information to us when this Contract is terminated, or at any time as requested by Splitwise +
    6. +
    +
  16. + +
  17. + Termination. This Contract shall remain effective until terminated by either party. You may terminate this Contract at any time, by discontinuing your use of our APIs. Splitwise may terminate this Contract at any time with or without cause and without advanced notice to you. Upon termination, all rights and licenses granted under this Contract shall immediately terminate. You must immediately discontinue any use, and destroy any copies of the Splitwise Materials and Confidential Information in your possession. +
  18. +
  19. + Representations and Warranties. You represent and warrant that you have validly entered into the Contract, and that you have the legal power to do so, and that doing so will not violate any law, government regulation, or breach agreement with another third party. +

    THE SPLITWISE API AND DOCUMENTATION IS BEING PROVIDED TO YOU ‘AS IS’ AND ‘AS AVAILABLE’ WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. YOU ACKNOWLEDGE THAT WE DO NOT WARRANT THAT THE APIS WILL BE UNINTERRUPTED, TIMELY, SECURE, OR ERROR-FREE. +
  20. +
  21. + Limitation of Liability. +

    TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL SPLITWISE, ITS AFFILIATES, OFFICERS, DIRECTORS, EMPLOYEES, AGENTS, LICENSORS, LICENSEES, ASSIGNS OR SUCCESSORS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE OR CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO ANY LOSS OF DATA, SERVICE INTERRUPTION, COMPUTER FAILURE, OR PECUNIARY LOSS) HOWEVER CAUSED, WHETHER IN CONTRACT, TORT OR UNDER ANY OTHER THEORY OF LIABILITY, AND WHETHER OR NOT YOU OR THE THIRD PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. YOUR ONLY RIGHT WITH RESPECT TO ANY PROBLEMS OR DISSATISFACTION WITH THE SPLITWISE SERVICES IS TO STOP USING THE SPLITWISE SERVICES. +

    SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OR EXCLUSION OF LIABILITY FOR CERTAIN TYPES OF DAMAGES REFERRED TO ABOVE (INCLUDING INCIDENTAL OR CONSEQUENTIAL DAMAGES). ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU. YOU AGREE THAT SPLITWISE’S AGGREGATE LIABILITY UNDER THIS AGREEMENT IS LIMITED TO ONE HUNDRED DOLLARS ($100). +
  22. + +
  23. + Indemnification: You agree to defend, indemnify, and hold harmless Splitwise and its affiliates, directors, and customers, from and against any and all third-party claims, actions, suits, and proceedings (including, but not limited to legal, or investigative fees), arising out of, or related to your use of the Splitwise Services, your violation of this Contract, your violation of your user agreement or privacy policy, or your violation of any laws, regulations, or third party rights. +
  24. + +
  25. + Miscellaneous +
      +
    1. + Applicable Law, Jurisdiction, and Venue: Any dispute arising out of this Agreement shall be governed by Massachusetts law and controlling U.S. federal law, without regard to conflict of law provisions thereof. Any claim or dispute between you and Splitwise that arises in whole or in part from this Contract or your use of the API or our Services shall be decided exclusively by a court of competent jurisdiction located in Massachusetts, and you hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to venue and jurisdiction in the state and federal courts of Massachusetts. +
    2. +
    3. + Assignment: You may not assign or delegate any of your rights or obligations hereunder, whether by operation of law or otherwise, without Splitwise’s prior written consent. Splitwise retains the right to assign the Contract in its entirety, without consent of the other party, to a corporate affiliate or in connection with a merger, acquisition, corporate reorganization, or sale of all or substantially all of its assets. Any purported assignment in violation of this section is void. +
    4. +
    5. + Language: This contact was drafted in English. In the event that this contract, or any part thereof, is translated to a language other than English, the English-language version shall control in the event of a conflict. +
    6. +
    7. + Relationship: You and Splitwise are independent contractors. This Contract does not create or imply any partnership, agency, joint venture, fiduciary or employment relationship between the parties. There are no third party beneficiaries to the Contract. +
    8. +
    9. + Severability: The Contract will be enforced to the fullest extent permitted under applicable law. If any provision of the Contract is found to be invalid or unenforceable by a court of competent jurisdiction, the provision will be modified by the court and interpreted so as best to accomplish the objectives of the original provision to the fullest extent permitted by law, and the remaining provisions of the Contract will remain in effect. +
    10. +
    11. + Force Majeure: Neither we nor you will be responsible for any failure to perform obligations under this Contract if such failure is caused by events beyond the reasonable control of a party, which may include denial-of-service attacks, a failure by a third party hosting provider, acts of God, war, strikes, revolutions, lack or failure of transportation facilities, laws or governmental regulations. +
    12. +
    13. + Entire Agreement: These Terms comprise the entire agreement between you and Splitwise with respect to the above subject matter and supersedes and merges all prior proposals, understandings and contemporaneous communications. +
    14. +
    +
  26. +

Authentication

OAuth

Splitwise uses OAuth 2 with the authorization code flow. To connect via OAuth 2, you'll need to register your app. When you register, you'll be given a key and secret.

+

Note: OAuth can be a very confusing protocol to implement correctly, and we strongly recommend +that you use an existing OAuth library to connect to Splitwise. You can find a list of OAuth client libraries at the +OAuth community site.

+

For more information on using OAuth, check out the following resources:

+ +
Security Scheme Type OAuth2
authorizationCode OAuth Flow
Authorization URL: /oauth/authorize
Token URL: /oauth/token
Scopes:

    ApiKeyAuth

    For speed and ease of prototyping, you can generate a personal API key on your app's details page. You should present this key to the server via the Authorization header as a Bearer token. The API key is an access token for your personal account, so keep it as safe as you would a password. +If your key becomes compromised or you want to invalidate your existing key for any other reason, you can do so on the app details page by generating a new key.

    +
    Security Scheme Type HTTP
    HTTP Authorization Scheme bearer
    Bearer format "API key"

    Users

    Resources to access and modify user information.

    +

    Get information about the current user

    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    {
    • "id": 0,
    • "first_name": "Ada",
    • "last_name": "Lovelace",
    • "email": "ada@example.com",
    • "registration_status": "confirmed",
    • "picture": {
      },
    • "notifications_read": "2017-06-02T20:21:57Z",
    • "notifications_count": 12,
    • "notifications": {
      },
    • "default_currency": "USD",
    • "locale": "en"
    }

    Get information about another user

    Authorizations:
    path Parameters
    id
    required
    integer

    Responses

    Response samples

    Content type
    application/json
    {
    • "id": 0,
    • "first_name": "Ada",
    • "last_name": "Lovelace",
    • "email": "ada@example.com",
    • "registration_status": "confirmed",
    • "picture": {
      }
    }

    Update a user

    Authorizations:
    path Parameters
    id
    required
    integer
    Request Body schema: application/json
    first_name
    string
    last_name
    string
    email
    string
    password
    string
    locale
    string
    default_currency
    string

    Responses

    Request samples

    Content type
    application/json
    {
    • "first_name": "string",
    • "last_name": "string",
    • "email": "string",
    • "password": "string",
    • "locale": "string",
    • "default_currency": "string"
    }

    Response samples

    Content type
    application/json
    {
    • "id": 0,
    • "first_name": "Ada",
    • "last_name": "Lovelace",
    • "email": "ada@example.com",
    • "registration_status": "confirmed",
    • "picture": {
      }
    }

    Groups

    A Group represents a collection of users who share expenses together. For example, some users use a Group to aggregate expenses related to an apartment. Others use it to represent a trip. Expenses assigned to a group are split among the users of that group. Importantly, two users in a Group can also have expenses with one another outside of the Group.

    +

    List the current user's groups

    Note: Expenses that are not associated with a group are listed in a group with ID 0.

    +
    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    {}

    Get information about a group

    Authorizations:
    path Parameters
    id
    required
    integer

    Responses

    Response samples

    Content type
    application/json
    {}

    Create a group

    Creates a new group. Adds the current user to the group by default.

    +

    Note: group user parameters must be flattened into the format users__{index}__{property}, where +property is user_id, first_name, last_name, or email. +The user's email or ID must be provided.

    +
    Authorizations:
    Request Body schema: application/json
    name
    required
    string
    group_type
    string
    Enum: "apartment" "house" "trip" "other"

    What is the group used for?

    +
    simplify_by_default
    boolean

    Turn on simplify debts?

    +
    users__{index}__{property}*
    string

    Responses

    Request samples

    Content type
    application/json
    {
    • "name": "The Brain Trust",
    • "group_type": "trip",
    • "users__0__first_name": "Alan",
    • "users__0__last_name": "Turing",
    • "users__0__email": "alan@example.org",
    • "users__1__id": 5823
    }

    Response samples

    Content type
    application/json
    {}

    Delete a group

    Delete an existing group. Destroys all associated records (expenses, etc.)

    +
    Authorizations:
    path Parameters
    id
    required
    integer

    Responses

    Response samples

    Content type
    application/json
    {
    • "success": true
    }

    Restore a group

    Restores a deleted group.

    +

    Note: 200 OK does not indicate a successful response. You must check the success value of the response.

    +
    Authorizations:
    path Parameters
    id
    required
    integer

    Responses

    Response samples

    Content type
    application/json
    Example
    {
    • "success": true
    }

    Add a user to a group

    Note: 200 OK does not indicate a successful response. You must check the success value of the response.

    +
    Authorizations:
    Request Body schema: application/json
    One of
    group_id
    required
    integer
    user_id
    required
    integer

    Responses

    Request samples

    Content type
    application/json
    Example
    {
    • "group_id": 49012,
    • "user_id": 7999632
    }

    Response samples

    Content type
    application/json
    Example
    {
    • "success": true,
    • "user": { },
    • "errors": [ ]
    }

    Remove a user from a group

    Remove a user from a group. Does not succeed if the user has a non-zero balance.

    +

    Note: 200 OK does not indicate a successful response. You must check the success value of the response.

    +
    Authorizations:
    Request Body schema: application/json
    group_id
    required
    integer
    user_id
    required
    integer

    Responses

    Request samples

    Content type
    application/json
    {
    • "group_id": 4012,
    • "user_id": 940142
    }

    Response samples

    Content type
    application/json
    Example
    {
    • "success": true,
    • "errors": [ ]
    }

    Friends

    List current user's friends

    Note: group objects only include group balances with that friend.

    +
    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    {
    • "friends": [
      ]
    }

    Get details about a friend

    Authorizations:
    path Parameters
    id
    required
    integer

    User ID of the friend

    +

    Responses

    Response samples

    Content type
    application/json
    {
    • "friend": {
      }
    }

    Add a friend

    Adds a friend. If the other user does not exist, you must supply user_first_name. +If the other user exists, user_first_name and user_last_name will be ignored.

    +
    Authorizations:
    Request Body schema: application/json
    email
    required
    string
    user_first_name
    string
    user_last_name
    string

    Responses

    Request samples

    Content type
    application/json
    {
    • "email": "ada@example.com",
    • "user_first_name": "Ada",
    • "user_last_name": "Lovelace"
    }

    Response samples

    Content type
    application/json
    {
    • "friend": {
      }
    }

    Add friends

    Add multiple friends at once.

    +

    For each user, if the other user does not exist, you must supply friends__{index}__first_name.

    +

    Note: user parameters must be flattened into the format friends__{index}__{property}, where +property is first_name, last_name, or email.

    +
    Authorizations:
    Request Body schema: application/json
    friends__{index}__{property}*
    string

    Responses

    Request samples

    Content type
    application/json
    {
    • "friends__0__first_name": "Alan",
    • "friends__0__last_name": "Turing",
    • "friends__0__email": "alan@example.org",
    • "friends__1__email": "existing_user@example.com"
    }

    Response samples

    Content type
    application/json
    {
    • "users": [
      ],
    • "errors": [ ]
    }

    Delete friendship

    Given a friend ID, break off the friendship between the current user and the specified user.

    +

    Note: 200 OK does not indicate a successful response. You must check the success value of the response.

    +
    Authorizations:
    path Parameters
    id
    required
    integer

    User ID of the friend

    +

    Responses

    Response samples

    Content type
    application/json
    Example
    {
    • "success": true,
    • "errors": [ ]
    }

    Expenses

    Get expense information

    Authorizations:
    path Parameters
    id
    required
    integer

    Responses

    Response samples

    Content type
    application/json
    {
    • "expense": {
      }
    }

    List the current user's expenses

    Authorizations:
    query Parameters
    group_id
    integer

    If provided, only expenses in that group will be returned, and friend_id will be ignored.

    +
    friend_id
    integer

    ID of another user. If provided, only expenses between the current and provided user will be returned.

    +
    dated_after
    string <date-time>
    dated_before
    string <date-time>
    updated_after
    string <update-time>
    updated_before
    string <date-time>
    limit
    integer
    Default: 20
    offset
    integer
    Default: 0

    Responses

    Response samples

    Content type
    application/json
    {
    • "expenses": [
      ]
    }

    Create an expense

    Creates an expense. You may either split an expense equally (only with group_id provided), +or supply a list of shares.

    +

    If providing a list of shares, each share must include paid_share and owed_share, and must be identified by one of the following:

    +
      +
    • email, first_name, and last_name
    • +
    • user_id
    • +
    +

    Note: 200 OK does not indicate a successful response. The operation was successful only if errors is empty.

    +
    Authorizations:
    Request Body schema: application/json
    One of
    cost
    string

    A string representation of a decimal value, limited to 2 decimal places

    +
    group_id
    required
    integer

    The group to put this expense in

    +
    description
    string

    A short description of the expense

    +
    details
    string Nullable

    Also known as "notes."

    +
    date
    string <date-time>

    The date and time the expense took place. May differ from created_at

    +
    repeat_interval
    string
    Enum: "never" "weekly" "fortnightly" "monthly" "yearly"
    currency_code
    string

    A currency code. Must be in the list from get_currencies

    +
    category_id
    integer

    A category id from get_categories

    +
    split_equally
    required
    boolean
    Value: true

    Responses

    Request samples

    Content type
    application/json
    Example
    {
    • "cost": "25",
    • "group_id": 0,
    • "description": "Grocery run",
    • "details": "string",
    • "date": "2012-05-02T13:00:00Z",
    • "repeat_interval": "never",
    • "currency_code": "USD",
    • "category_id": 15,
    • "split_equally": true
    }

    Response samples

    Content type
    application/json
    {
    • "expenses": [
      ],
    • "errors": { }
    }

    Update an expense

    Updates an expense. Parameters are the same as in create_expense, but you only need to include parameters +that are changing from the previous values. If any values is supplied for users__{index}__{property}, all +shares for the expense will be overwritten with the provided values.

    +

    Note: 200 OK does not indicate a successful response. The operation was successful only if errors is empty.

    +
    Authorizations:
    Request Body schema: application/json
    One of
    cost
    string

    A string representation of a decimal value, limited to 2 decimal places

    +
    group_id
    required
    integer

    The group to put this expense in

    +
    description
    string

    A short description of the expense

    +
    details
    string Nullable

    Also known as "notes."

    +
    date
    string <date-time>

    The date and time the expense took place. May differ from created_at

    +
    repeat_interval
    string
    Enum: "never" "weekly" "fortnightly" "monthly" "yearly"
    currency_code
    string

    A currency code. Must be in the list from get_currencies

    +
    category_id
    integer

    A category id from get_categories

    +
    split_equally
    required
    boolean
    Value: true

    Responses

    Request samples

    Content type
    application/json
    Example
    {
    • "cost": "25",
    • "group_id": 0,
    • "description": "Grocery run",
    • "details": "string",
    • "date": "2012-05-02T13:00:00Z",
    • "repeat_interval": "never",
    • "currency_code": "USD",
    • "category_id": 15,
    • "split_equally": true
    }

    Response samples

    Content type
    application/json
    {
    • "expenses": [
      ],
    • "errors": { }
    }

    Delete an expense

    Note: 200 OK does not indicate a successful response. The operation was successful only if success is true.

    +
    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    Example
    {
    • "success": true
    }

    Restore an expense

    Note: 200 OK does not indicate a successful response. The operation was successful only if success is true.

    +
    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    {
    • "success": true
    }

    Comments

    Get expense comments

    Authorizations:
    query Parameters
    expense_id
    required
    integer
    Example: expense_id=4193

    Responses

    Response samples

    Content type
    application/json
    {
    • "comments": [
      ]
    }

    Create a comment

    Authorizations:
    Request Body schema: application/json
    expense_id
    integer
    content
    string

    Responses

    Request samples

    Content type
    application/json
    {
    • "expense_id": 5123,
    • "content": "Does this include the delivery fee?"
    }

    Response samples

    Content type
    application/json
    {
    • "comment": {
      }
    }

    Delete a comment

    Deletes a comment. Returns the deleted comment.

    +
    Authorizations:

    Responses

    Response samples

    Content type
    application/json
    {
    • "comment": {
      }
    }

    Notifications

    Get notifications

    Return a list of recent activity on the users account with the most recent items first. +content will be suitable for display in HTML and uses only the <strong>, <strike>, <small>, +<br> and <font color="#FFEE44"> tags.

    +

    The type value indicates what the notification is about. Notification types may be added in the future +without warning. Below is an incomplete list of notification types.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeMeaning
    0Expense added
    1Expense updated
    2Expense deleted
    3Comment added
    4Added to group
    5Removed from group
    6Group deleted
    7Group settings changed
    8Added as friend
    9Removed as friend
    10News (a URL should be included)
    11Debt simplification
    12Group undeleted
    13Expense undeleted
    14Group currency conversion
    15Friend currency conversion
    +

    Note: While all parameters are optional, the server sets arbitrary (but large) limits +on the number of notifications returned if you set a very old updated_after value or limit of 0 for a +user with many notifications.

    +
    Authorizations:
    query Parameters
    updated_after
    string <date-time>
    Example: updated_after=2020-07-28T20:46:00Z

    If provided, returns only notifications after this time.

    +
    limit
    integer
    Default: 0

    Omit (or provide 0) to get the maximum number of notifications.

    +

    Responses

    Response samples

    Content type
    application/json
    {
    • "notifications": [
      ]
    }

    Other

    Supported currencies

    Returns a list of all currencies allowed by the system. These are mostly ISO 4217 codes, but we do +sometimes use pending codes or unofficial, colloquial codes (like BTC instead of XBT for Bitcoin).

    +

    Responses

    Response samples

    Content type
    application/json
    {
    • "currencies": [
      ]
    }

    Supported categories

    Returns a list of all categories Splitwise allows for expenses. There are parent categories that represent groups of categories with subcategories for more specific categorization. +When creating expenses, you must use a subcategory, not a parent category. +If you intend for an expense to be represented by the parent category and nothing more specific, please use the "Other" subcategory.

    +

    Responses

    Response samples

    Content type
    application/json
    {}

    Parse sentence into an expense

    Attempts to create an expense from the input as an English natural language phrase like "groceries $20" or "Jon paid me $50". +If valid is true, the expense value will be a complete and valid expense. +If it is false, the expense value may be missing some values.

    +

    Note: By default, the expense is only parsed, not persisted. See the autosave parameter.

    +
    Authorizations:
    Request Body schema: application/json
    One of
    input
    required
    string

    A natural language sentence describing an expense.

    +
    autosave
    boolean
    Default: false

    If true, the resulting expense will be saved if valid.

    +

    Responses

    Request samples

    Content type
    application/json
    Example
    {
    • "input": "I owe Ada 5 bucks",
    • "autosave": false
    }

    Response samples

    Content type
    application/json
    {
    • "expense": {
      },
    • "valid": true,
    • "confidence": 0.5,
    • "error": "string"
    }
    + + + + \ No newline at end of file diff --git a/font-selection.json b/font-selection.json deleted file mode 100755 index 5e78f5d..0000000 --- a/font-selection.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "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/lib/multilang.rb b/lib/multilang.rb deleted file mode 100644 index 36fbe5b..0000000 --- a/lib/multilang.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Multilang - def block_code(code, full_lang_name) - if full_lang_name - parts = full_lang_name.split('--') - rouge_lang_name = (parts) ? parts[0] : "" # just parts[0] here causes null ref exception when no language specified - super(code, rouge_lang_name).sub("highlight #{rouge_lang_name}") do |match| - match + " tab-" + full_lang_name - end - else - super(code, full_lang_name) - end - end -end - -require 'middleman-core/renderers/redcarpet' -Middleman::Renderers::MiddlemanRedcarpetHTML.send :include, Multilang diff --git a/lib/toc_data.rb b/lib/toc_data.rb deleted file mode 100644 index f4663cd..0000000 --- a/lib/toc_data.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'nokogiri' - -def toc_data(page_content) - html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content) - - # get a flat list of headers - headers = [] - html_doc.css('h1, h2, h3').each do |header| - headers.push({ - id: header.attribute('id').to_s, - content: header.children, - level: header.name[1].to_i, - children: [] - }) - end - - [3,2].each do |header_level| - header_to_nest = nil - headers = headers.reject do |header| - if header[:level] == header_level - header_to_nest[:children].push header if header_to_nest - true - else - header_to_nest = header if header[:level] < header_level - false - end - end - end - headers -end \ No newline at end of file diff --git a/lib/unique_head.rb b/lib/unique_head.rb deleted file mode 100644 index c5dc279..0000000 --- a/lib/unique_head.rb +++ /dev/null @@ -1,17 +0,0 @@ -# Unique header generation -require 'middleman-core/renderers/redcarpet' -class UniqueHeadCounter < Middleman::Renderers::MiddlemanRedcarpetHTML - def initialize - super - @head_count = {} - end - def header(text, header_level) - friendly_text = text.parameterize - @head_count[friendly_text] ||= 0 - @head_count[friendly_text] += 1 - if @head_count[friendly_text] > 1 - friendly_text += "-#{@head_count[friendly_text]}" - end - return "#{text}" - end -end diff --git a/paths/add_user_to_group.yaml b/paths/add_user_to_group.yaml new file mode 100644 index 0000000..acf6bdd --- /dev/null +++ b/paths/add_user_to_group.yaml @@ -0,0 +1,74 @@ +--- +post: + tags: + - groups + summary: Add a user to a group + description: "**Note**: 200 OK does not indicate a successful response. You must + check the `success` value of the response.\n" + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - type: object + title: User ID + properties: + group_id: + type: integer + example: 49012 + user_id: + type: integer + example: 7999632 + required: + - user_id + - type: object + title: User info + properties: + group_id: + type: integer + example: 49012 + first_name: + type: string + example: Grace + last_name: + type: string + example: Hopper + email: + type: string + example: gracehopper@example.com + required: + - first_name + - last_name + - email + required: + - group_id + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + user: + "$ref": "../schemas/user.yaml" + errors: + type: object + additionalProperties: + type: array + examples: + Success: + value: + success: true + user: {} + errors: [] + Failure: + value: + success: false + user: + errors: + base: + - That user cannot be a member of this group diff --git a/paths/create_comment.yaml b/paths/create_comment.yaml new file mode 100644 index 0000000..621bd31 --- /dev/null +++ b/paths/create_comment.yaml @@ -0,0 +1,45 @@ +--- +post: + tags: + - comments + summary: Create a comment + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + expense_id: + type: integer + example: 5123 + content: + type: string + example: "Does this include the delivery fee?" + responses: + '200': + description: 'OK' + content: + application/json: + schema: + type: object + properties: + comment: + allOf: + - $ref: ../schemas/comment.yaml + - type: object + properties: + relation_id: + example: 5123 + comment_type: + example: "User" + content: + example: "Does this include the delivery fee?" + user: + $ref: ../schemas/comment_user.yaml + '401': + $ref: ../responses/unauthorized.yaml + '403': + $ref: ../responses/forbidden.yaml + '404': + $ref: ../responses/not_found.yaml diff --git a/paths/create_expense.yaml b/paths/create_expense.yaml new file mode 100644 index 0000000..628f6f3 --- /dev/null +++ b/paths/create_expense.yaml @@ -0,0 +1,42 @@ +--- +post: + tags: + - expenses + summary: Create an expense + description: | + Creates an expense. You may either split an expense equally (only with `group_id` provided), + or supply a list of shares. + + If providing a list of shares, each share must include `paid_share` and `owed_share`, and must be identified by one of the following: + - `email`, `first_name`, and `last_name` + - `user_id` + + **Note**: 200 OK does not indicate a successful response. The operation was successful only if `errors` is empty. + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - "$ref": "../schemas/expense/equal_group_split.yaml" + title: Equal group split + - "$ref": "../schemas/expense/by_shares.yaml" + title: Split by shares + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + expenses: + type: array + items: + "$ref": "../schemas/expense.yaml" + errors: + type: object + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/paths/create_friend.yaml b/paths/create_friend.yaml new file mode 100644 index 0000000..b3dd811 --- /dev/null +++ b/paths/create_friend.yaml @@ -0,0 +1,38 @@ +--- +post: + tags: + - friends + summary: Add a friend + description: | + Adds a friend. If the other user does not exist, you must supply `user_first_name`. + If the other user exists, `user_first_name` and `user_last_name` will be ignored. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + example: ada@example.com + user_first_name: + type: string + example: Ada + user_last_name: + type: string + example: Lovelace + required: + - email + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + friend: + "$ref": "../schemas/friend.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" diff --git a/paths/create_friends.yaml b/paths/create_friends.yaml new file mode 100644 index 0000000..f878107 --- /dev/null +++ b/paths/create_friends.yaml @@ -0,0 +1,67 @@ +--- +post: + tags: + - friends + summary: Add friends + description: | + Add multiple friends at once. + + For each user, if the other user does not exist, you must supply `friends__{index}__first_name`. + + **Note**: user parameters must be flattened into the format `friends__{index}__{property}`, where + `property` is `first_name`, `last_name`, or `email`. + requestBody: + required: true + content: + application/json: + schema: + type: object + additionalProperties: + type: string + x-additionalPropertiesName: friends__{index}__{property} + example: + friends__0__first_name: Alan + friends__0__last_name: Turing + friends__0__email: alan@example.org + friends__1__email: existing_user@example.com + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + users: + type: array + items: + allOf: + - "$ref": "../schemas/friend.yaml" + - title: User + errors: + type: array + example: [] + '400': + description: Bad Request + content: + application/json: + schema: + type: object + properties: + users: + type: array + example: [] + errors: + type: object + additionalProperties: + type: array + example: + base: + - Please supply a name for this user + example: + users: [] + errors: + base: + - That user cannot be a member of this group + '401': + "$ref": "../responses/unauthorized.yaml" diff --git a/paths/create_group.yaml b/paths/create_group.yaml new file mode 100644 index 0000000..522fdc6 --- /dev/null +++ b/paths/create_group.yaml @@ -0,0 +1,63 @@ +--- +post: + tags: + - groups + summary: Create a group + description: | + Creates a new group. Adds the current user to the group by default. + + **Note**: group user parameters must be flattened into the format `users__{index}__{property}`, where + `property` is `user_id`, `first_name`, `last_name`, or `email`. + The user's email or ID must be provided. + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string + group_type: + type: string + description: What is the group used for? + enum: [apartment, house, trip, other] + simplify_by_default: + type: boolean + description: Turn on simplify debts? + additionalProperties: + type: string + x-additionalPropertiesName: users__{index}__{property} + example: + name: The Brain Trust + group_type: trip + users__0__first_name: Alan + users__0__last_name: Turing + users__0__email: alan@example.org + users__1__id: 5823 + required: + - name + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + group: + "$ref": "../schemas/group.yaml" + '400': + description: Bad Request + content: + application/json: + schema: + type: object + properties: + errors: + type: object + properties: + base: + type: array + items: + type: string + example: You cannot add unknown users to a group by user_id diff --git a/paths/delete_comment.yaml b/paths/delete_comment.yaml new file mode 100644 index 0000000..4ff2dd0 --- /dev/null +++ b/paths/delete_comment.yaml @@ -0,0 +1,37 @@ +--- +post: + params: + - in: path + name: id + schema: + type: integer + required: true + tags: + - comments + summary: Delete a comment + description: Deletes a comment. Returns the deleted comment. + responses: + '200': + description: 'OK' + content: + application/json: + schema: + type: object + properties: + comment: + allOf: + - $ref: ../schemas/comment.yaml + - type: object + properties: + comment_type: + example: "User" + content: + example: "Does this include the delivery fee?" + user: + $ref: ../schemas/comment_user.yaml + '401': + $ref: ../responses/unauthorized.yaml + '403': + $ref: ../responses/forbidden.yaml + '404': + $ref: ../responses/not_found.yaml diff --git a/paths/delete_expense.yaml b/paths/delete_expense.yaml new file mode 100644 index 0000000..3965fb3 --- /dev/null +++ b/paths/delete_expense.yaml @@ -0,0 +1,37 @@ +--- +params: +- in: query + name: id + description: ID of the expense to delete +post: + tags: + - expenses + summary: Delete an expense + description: "**Note**: 200 OK does not indicate a successful response. The operation + was successful only if `success` is true.\n" + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + required: true + errors: + type: object + examples: + Success: + value: + success: true + Failure: + value: + success: false + errors: + expense: + - does not exist, or has already been deleted + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/paths/delete_friend.yaml b/paths/delete_friend.yaml new file mode 100644 index 0000000..62beeae --- /dev/null +++ b/paths/delete_friend.yaml @@ -0,0 +1,47 @@ +--- +parameters: +- in: path + name: id + description: User ID of the friend + schema: + type: integer + required: true +post: + tags: + - friends + summary: Delete friendship + description: | + Given a friend ID, break off the friendship between the current user and the specified user. + + **Note**: 200 OK does not indicate a successful response. You must check the `success` value of the response. + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + errors: + type: object + additionalProperties: + type: array + examples: + Success: + value: + success: true + errors: [] + Failure: + value: + success: false + errors: + base: + - There was an issue deleting that friendship + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/delete_group.yaml b/paths/delete_group.yaml new file mode 100644 index 0000000..bc9d1a9 --- /dev/null +++ b/paths/delete_group.yaml @@ -0,0 +1,29 @@ +--- +post: + tags: + - groups + summary: Delete a group + description: Delete an existing group. Destroys all associated records (expenses, + etc.) + parameters: + - in: path + name: id + schema: + type: integer + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/get_categories.yaml b/paths/get_categories.yaml new file mode 100644 index 0000000..b0fd501 --- /dev/null +++ b/paths/get_categories.yaml @@ -0,0 +1,22 @@ +--- +get: + tags: + - other + summary: Supported categories + security: [] + description: | + Returns a list of all categories Splitwise allows for expenses. There are parent categories that represent groups of categories with subcategories for more specific categorization. + When creating expenses, you must use a subcategory, not a parent category. + If you intend for an expense to be represented by the parent category and nothing more specific, please use the "Other" subcategory. + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + categories: + type: array + items: + $ref: ../schemas/parent_category.yaml diff --git a/paths/get_comments.yaml b/paths/get_comments.yaml new file mode 100644 index 0000000..3d509df --- /dev/null +++ b/paths/get_comments.yaml @@ -0,0 +1,30 @@ +--- +get: + tags: + - comments + summary: Get expense comments + parameters: + - in: query + name: expense_id + schema: + type: integer + required: true + example: 4193 + responses: + '200': + description: 'OK' + content: + application/json: + schema: + type: object + properties: + comments: + type: array + items: + $ref: ../schemas/comment.yaml + '401': + $ref: ../responses/unauthorized.yaml + '403': + $ref: ../responses/forbidden.yaml + '404': + $ref: ../responses/not_found.yaml diff --git a/paths/get_currencies.yaml b/paths/get_currencies.yaml new file mode 100644 index 0000000..a79f4aa --- /dev/null +++ b/paths/get_currencies.yaml @@ -0,0 +1,29 @@ +--- +get: + tags: + - other + summary: Supported currencies + security: [] + description: | + Returns a list of all currencies allowed by the system. These are mostly ISO 4217 codes, but we do + sometimes use pending codes or unofficial, colloquial codes (like BTC instead of XBT for Bitcoin). + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + currencies: + type: array + items: + type: object + title: Currency + properties: + currency_code: + type: string + example: BRL + unit: + type: string + example: R$ diff --git a/paths/get_current_user.yaml b/paths/get_current_user.yaml new file mode 100644 index 0000000..074553a --- /dev/null +++ b/paths/get_current_user.yaml @@ -0,0 +1,14 @@ +--- +get: + tags: + - users + summary: Get information about the current user + responses: + '200': + description: OK + content: + application/json: + schema: + "$ref": "../schemas/current_user.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" diff --git a/paths/get_expense.yaml b/paths/get_expense.yaml new file mode 100644 index 0000000..2bd226e --- /dev/null +++ b/paths/get_expense.yaml @@ -0,0 +1,27 @@ +--- +parameters: +- in: path + name: id + schema: + type: integer + required: true +get: + tags: + - expenses + summary: Get expense information + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + expense: + "$ref": "../schemas/expense.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/get_expenses.yaml b/paths/get_expenses.yaml new file mode 100644 index 0000000..75e9141 --- /dev/null +++ b/paths/get_expenses.yaml @@ -0,0 +1,66 @@ +--- +get: + tags: + - expenses + summary: List the current user's expenses + parameters: + - in: query + name: group_id + schema: + type: integer + description: If provided, only expenses in that group will be returned, and `friend_id` + will be ignored. + - in: query + name: friend_id + schema: + type: integer + description: ID of another user. If provided, only expenses between the current + and provided user will be returned. + - in: query + name: dated_after + schema: + type: string + format: date-time + - in: query + name: dated_before + schema: + type: string + format: date-time + - in: query + name: updated_after + schema: + type: string + format: update-time + - in: query + name: updated_before + schema: + type: string + format: date-time + - in: query + name: limit + schema: + type: integer + default: 20 + - in: query + name: offset + schema: + type: integer + default: 0 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + expenses: + type: array + items: + "$ref": "../schemas/expense.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/get_friend.yaml b/paths/get_friend.yaml new file mode 100644 index 0000000..e8970d2 --- /dev/null +++ b/paths/get_friend.yaml @@ -0,0 +1,28 @@ +--- +parameters: +- in: path + name: id + description: User ID of the friend + schema: + type: integer + required: true +get: + tags: + - friends + summary: Get details about a friend + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + friend: + "$ref": "../schemas/friend.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/get_friends.yaml b/paths/get_friends.yaml new file mode 100644 index 0000000..70c634d --- /dev/null +++ b/paths/get_friends.yaml @@ -0,0 +1,22 @@ +--- +get: + tags: + - friends + summary: List current user's friends + description: "**Note**: `group` objects only include group balances with that friend.\n" + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + friends: + type: array + items: + allOf: + - "$ref": "../schemas/friend.yaml" + - title: User + '401': + "$ref": "../responses/unauthorized.yaml" diff --git a/paths/get_group.yaml b/paths/get_group.yaml new file mode 100644 index 0000000..ef7cdbf --- /dev/null +++ b/paths/get_group.yaml @@ -0,0 +1,27 @@ +--- +parameters: +- in: path + name: id + schema: + type: integer + required: true +get: + tags: + - groups + summary: Get information about a group + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + group: + "$ref": "../schemas/group.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/get_groups.yaml b/paths/get_groups.yaml new file mode 100644 index 0000000..cb83797 --- /dev/null +++ b/paths/get_groups.yaml @@ -0,0 +1,21 @@ +--- +get: + tags: + - groups + summary: List the current user's groups + description: "**Note**: Expenses that are not associated with a group are listed + in a group with ID 0.\n" + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + groups: + type: array + items: + "$ref": "../schemas/group.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" diff --git a/paths/get_notifications.yaml b/paths/get_notifications.yaml new file mode 100644 index 0000000..247c040 --- /dev/null +++ b/paths/get_notifications.yaml @@ -0,0 +1,63 @@ +--- +get: + tags: + - notifications + summary: Get notifications + description: | + Return a list of recent activity on the users account with the most recent items first. + `content` will be suitable for display in HTML and uses only the ``, ``, ``, + `
    ` and `` tags. + + The `type` value indicates what the notification is about. Notification types may be added in the future + without warning. Below is an incomplete list of notification types. + + | Type | Meaning | + | ---- | ------- | + | 0 | Expense added | + | 1 | Expense updated | + | 2 | Expense deleted | + | 3 | Comment added | + | 4 | Added to group | + | 5 | Removed from group | + | 6 | Group deleted | + | 7 | Group settings changed | + | 8 | Added as friend | + | 9 | Removed as friend | + | 10 | News (a URL should be included) | + | 11 | Debt simplification | + | 12 | Group undeleted | + | 13 | Expense undeleted | + | 14 | Group currency conversion | + | 15 | Friend currency conversion | + + **Note**: While all parameters are optional, the server sets arbitrary (but large) limits + on the number of notifications returned if you set a very old `updated_after` value or `limit` of `0` for a + user with many notifications. + parameters: + - in: query + name: updated_after + schema: + type: string + format: date-time + example: "2020-07-28T20:46:00Z" + description: If provided, returns only notifications after this time. + - in: query + name: limit + schema: + type: integer + default: 0 + description: Omit (or provide `0`) to get the maximum number of notifications. + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + notifications: + type: array + items: + $ref: ../schemas/notification.yaml + '401': + $ref: ../responses/unauthorized.yaml diff --git a/paths/get_user.yaml b/paths/get_user.yaml new file mode 100644 index 0000000..616212c --- /dev/null +++ b/paths/get_user.yaml @@ -0,0 +1,24 @@ +--- +parameters: +- in: path + name: id + schema: + type: integer + required: true +get: + tags: + - users + summary: Get information about another user + responses: + '200': + description: OK + content: + application/json: + schema: + "$ref": "../schemas/user.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" + '404': + "$ref": "../responses/not_found.yaml" diff --git a/paths/index.yaml b/paths/index.yaml new file mode 100644 index 0000000..bd3e2f2 --- /dev/null +++ b/paths/index.yaml @@ -0,0 +1,57 @@ +--- +/get_current_user: + $ref: ./get_current_user.yaml +/get_user/{id}: + $ref: ./get_user.yaml +/update_user/{id}: + $ref: ./update_user.yaml +/get_groups: + $ref: ./get_groups.yaml +/get_group/{id}: + $ref: ./get_group.yaml +/create_group: + $ref: ./create_group.yaml +/delete_group/{id}: + $ref: ./delete_group.yaml +/undelete_group/{id}: + $ref: ./undelete_group.yaml +/add_user_to_group: + $ref: ./add_user_to_group.yaml +/remove_user_from_group: + $ref: ./remove_user_from_group.yaml +/get_friends: + $ref: ./get_friends.yaml +/get_friend/{id}: + $ref: ./get_friend.yaml +/create_friend: + $ref: ./create_friend.yaml +/create_friends: + $ref: ./create_friends.yaml +/delete_friend/{id}: + $ref: ./delete_friend.yaml +/get_currencies: + $ref: ./get_currencies.yaml +/get_expense/{id}: + $ref: ./get_expense.yaml +/get_expenses: + $ref: ./get_expenses.yaml +/create_expense: + $ref: ./create_expense.yaml +/update_expense/{id}: + $ref: ./update_expense.yaml +/delete_expense/{id}: + $ref: ./delete_expense.yaml +/undelete_expense/{id}: + $ref: ./undelete_expense.yaml +/get_comments: + $ref: ./get_comments.yaml +/create_comment: + $ref: ./create_comment.yaml +/delete_comment: + $ref: ./delete_comment.yaml +/get_notifications: + $ref: ./get_notifications.yaml +/get_categories: + $ref: ./get_categories.yaml +/parse_sentence: + $ref: ./parse_sentence.yaml diff --git a/paths/parse_sentence.yaml b/paths/parse_sentence.yaml new file mode 100644 index 0000000..7d815fd --- /dev/null +++ b/paths/parse_sentence.yaml @@ -0,0 +1,77 @@ +--- +post: + tags: + - other + summary: Parse sentence into an expense + description: | + Attempts to create an expense from the input as an English natural language phrase like "groceries $20" or "Jon paid me $50". + If `valid` is `true`, the `expense` value will be a complete and valid expense. + If it is `false`, the `expense` value may be missing some values. + + **Note**: By default, the expense is only parsed, not persisted. See the `autosave` parameter. + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - type: object + title: Freeform + properties: + input: + type: string + required: true + example: "I owe Ada 5 bucks" + description: A natural language sentence describing an expense. + autosave: + type: boolean + description: If true, the resulting expense will be saved if valid. + default: false + - type: object + title: With friend + properties: + input: + type: string + required: true + example: "I owe Ada 5 bucks" + description: A natural language sentence describing an expense. + friend_id: + type: integer + autosave: + type: boolean + description: If true, the resulting expense will be saved if valid. + default: false + - type: object + title: With group + properties: + input: + type: string + required: true + example: "I owe Ada 5 bucks" + description: A natural language sentence describing an expense. + group_id: + type: integer + autosave: + type: boolean + description: If true, the resulting expense will be saved if valid. + default: false + required: + - input + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + expense: + $ref: ../schemas/expense.yaml + valid: + type: boolean + confidence: + type: number + format: double + example: 0.5 + error: + type: string diff --git a/paths/remove_user_from_group.yaml b/paths/remove_user_from_group.yaml new file mode 100644 index 0000000..42ae787 --- /dev/null +++ b/paths/remove_user_from_group.yaml @@ -0,0 +1,50 @@ +--- +post: + tags: + - groups + summary: Remove a user from a group + description: | + Remove a user from a group. Does not succeed if the user has a non-zero balance. + + **Note:** 200 OK does not indicate a successful response. You must check the success value of the response. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + group_id: + type: integer + example: 4012 + user_id: + type: integer + example: 940142 + required: + - user_id + - group_id + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + errors: + type: object + additionalProperties: + type: array + examples: + Success: + value: + success: true + errors: [] + Failure: + value: + success: false + errors: + base: + - The user has a non-zero balance diff --git a/paths/undelete_expense.yaml b/paths/undelete_expense.yaml new file mode 100644 index 0000000..08cb8d5 --- /dev/null +++ b/paths/undelete_expense.yaml @@ -0,0 +1,24 @@ +--- +params: +- in: query + name: id + description: ID of the expense to restore +post: + tags: + - expenses + summary: Restore an expense + description: "**Note**: 200 OK does not indicate a successful response. The operation + was successful only if `success` is true.\n" + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/paths/undelete_group.yaml b/paths/undelete_group.yaml new file mode 100644 index 0000000..2192833 --- /dev/null +++ b/paths/undelete_group.yaml @@ -0,0 +1,40 @@ +--- +parameters: +- in: path + name: id + schema: + type: integer + required: true +post: + tags: + - groups + summary: Restore a group + description: | + Restores a deleted group. + + **Note**: 200 OK does not indicate a successful response. You must check the `success` value of the response. + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + errors: + type: array + examples: + Success: + value: + success: true + Failure: + value: + success: false + errors: + - You do not have permission to undelete this group. + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/paths/update_expense.yaml b/paths/update_expense.yaml new file mode 100644 index 0000000..2e67b2b --- /dev/null +++ b/paths/update_expense.yaml @@ -0,0 +1,43 @@ +--- +params: +- in: query + name: id + description: ID of the expense to update +post: + tags: + - expenses + summary: Update an expense + description: | + Updates an expense. Parameters are the same as in `create_expense`, but you only need to include parameters + that are changing from the previous values. If any values is supplied for `users__{index}__{property}`, _all_ + shares for the expense will be overwritten with the provided values. + + **Note**: 200 OK does not indicate a successful response. The operation was successful only if `errors` is empty. + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - "$ref": "../schemas/expense/equal_group_split.yaml" + title: Equal group split + - "$ref": "../schemas/expense/by_shares.yaml" + title: Split by shares + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + expenses: + type: array + items: + "$ref": "../schemas/expense.yaml" + errors: + type: object + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/paths/update_user.yaml b/paths/update_user.yaml new file mode 100644 index 0000000..eb2c0a3 --- /dev/null +++ b/paths/update_user.yaml @@ -0,0 +1,40 @@ +--- +parameters: +- in: path + name: id + schema: + type: integer + required: true +post: + tags: + - users + summary: Update a user + requestBody: + required: true + content: + application/json: + schema: + properties: + first_name: + type: string + last_name: + type: string + email: + type: string + password: + type: string + locale: + type: string + default_currency: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + "$ref": "../schemas/user.yaml" + '401': + "$ref": "../responses/unauthorized.yaml" + '403': + "$ref": "../responses/forbidden.yaml" diff --git a/responses/forbidden.yaml b/responses/forbidden.yaml new file mode 100644 index 0000000..8169620 --- /dev/null +++ b/responses/forbidden.yaml @@ -0,0 +1,5 @@ +description: "Forbidden" +content: + application/json: + schema: + $ref: ../schemas/errors/forbidden.yaml diff --git a/responses/not_found.yaml b/responses/not_found.yaml new file mode 100644 index 0000000..8c7a66c --- /dev/null +++ b/responses/not_found.yaml @@ -0,0 +1,5 @@ +description: "Not Found" +content: + application/json: + schema: + $ref: ../schemas/errors/not_found.yaml diff --git a/responses/unauthorized.yaml b/responses/unauthorized.yaml new file mode 100644 index 0000000..d35e397 --- /dev/null +++ b/responses/unauthorized.yaml @@ -0,0 +1,5 @@ +description: "Invalid API key or OAuth access token" +content: + application/json: + schema: + $ref: ../schemas/errors/unauthorized.yaml diff --git a/schemas/balance.yaml b/schemas/balance.yaml new file mode 100644 index 0000000..fc2ad66 --- /dev/null +++ b/schemas/balance.yaml @@ -0,0 +1,8 @@ +type: object +properties: + currency_code: + type: string + example: "USD" + amount: + type: string + example: "414.5" diff --git a/schemas/category.yaml b/schemas/category.yaml new file mode 100644 index 0000000..df19382 --- /dev/null +++ b/schemas/category.yaml @@ -0,0 +1,33 @@ +--- +type: object +properties: + id: + type: integer + example: 48 + name: + type: string + example: Cleaning + icon: + type: string + example: https://s3.amazonaws.com/splitwise/uploads/category/icon/square/utilities/cleaning.png + icon_types: + type: object + properties: + slim: + type: object + properties: + small: + type: string + format: uri + large: + type: string + format: uri + square: + type: object + properties: + large: + type: string + format: uri + xlarge: + type: string + format: uri diff --git a/schemas/comment.yaml b/schemas/comment.yaml new file mode 100644 index 0000000..654574d --- /dev/null +++ b/schemas/comment.yaml @@ -0,0 +1,27 @@ +type: object +properties: + id: + type: integer + example: 79800950 + content: + type: string + example: "John D. updated this transaction: - The cost changed from $6.99 to $8.99" + comment_type: + type: string + enum: [System, User] + relation_type: + type: string + enum: [ExpenseComment] + relation_id: + type: integer + example: 855870953 + description: ID of the subject of the comment + created_at: + type: string + format: date-time + deleted_at: + type: string + format: date-time + nullable: true + user: + - $ref: ./comment_user.yaml diff --git a/schemas/comment_user.yaml b/schemas/comment_user.yaml new file mode 100644 index 0000000..73e9484 --- /dev/null +++ b/schemas/comment_user.yaml @@ -0,0 +1,17 @@ +type: object +properties: + id: + type: integer + example: 491923 + first_name: + type: string + example: Jane + last_name: + type: string + example: Doe + picture: + type: object + properties: + medium: + type: string + example: image_url diff --git a/schemas/current_user.yaml b/schemas/current_user.yaml new file mode 100644 index 0000000..a055f6a --- /dev/null +++ b/schemas/current_user.yaml @@ -0,0 +1,21 @@ +allOf: +- $ref: ./user.yaml +- type: object + properties: + notifications_read: + type: string + example: "2017-06-02T20:21:57Z" + description: "ISO 8601 date/time indicating the last time notifications were read" + notifications_count: + type: integer + example: 12 + description: "Number of unread notifications since notifiations_read" + notifications: + $ref: ./notification_settings.yaml + default_currency: + type: string + example: "USD" + locale: + type: string + example: "en" + description: "ISO_639-1 2-letter locale code" diff --git a/schemas/debt.yaml b/schemas/debt.yaml new file mode 100644 index 0000000..df6cb92 --- /dev/null +++ b/schemas/debt.yaml @@ -0,0 +1,16 @@ +type: object +properties: + from: + type: integer + example: 18523 + description: User ID + to: + type: integer + example: 90261 + description: User ID + amount: + type: string + example: "414.5" + currency_code: + type: string + example: "USD" diff --git a/schemas/errors/forbidden.yaml b/schemas/errors/forbidden.yaml new file mode 100644 index 0000000..921074f --- /dev/null +++ b/schemas/errors/forbidden.yaml @@ -0,0 +1,11 @@ +type: object +properties: + errors: + type: object + properties: + base: + type: array + items: + type: string + example: + "Invalid API request: you do not have permission to perform that action" diff --git a/schemas/errors/not_found.yaml b/schemas/errors/not_found.yaml new file mode 100644 index 0000000..a51239b --- /dev/null +++ b/schemas/errors/not_found.yaml @@ -0,0 +1,11 @@ +type: object +properties: + errors: + type: object + properties: + base: + type: array + items: + type: string + example: + "Invalid API Request: record not found" diff --git a/schemas/errors/unauthorized.yaml b/schemas/errors/unauthorized.yaml new file mode 100644 index 0000000..f3b173c --- /dev/null +++ b/schemas/errors/unauthorized.yaml @@ -0,0 +1,5 @@ +type: object +properties: + error: + type: string + example: "Invalid API request: you are not logged in" diff --git a/schemas/expense.yaml b/schemas/expense.yaml new file mode 100644 index 0000000..0119a40 --- /dev/null +++ b/schemas/expense.yaml @@ -0,0 +1,144 @@ +allOf: + - $ref: ./expense/common.yaml + - type: object + properties: + id: + type: integer + example: 51023 + group_id: + type: integer + example: 391 + nullable: true + description: Null if the expense is not associated with a group. + friendship_id: + type: integer + example: 4818 + nullable: true + description: Null if the expense is not associated with a friendship. + expense_bundle_id: + type: integer + example: 491030 + nullable: true + description: + type: string + example: "Brunch" + repeats: + type: boolean + description: Whether the expense recurs automatically + repeat_interval: + type: string + enum: [never, weekly, fortnightly, monthly, yearly] + email_reminder: + type: boolean + description: | + Whether a reminder will be sent to involved users in advance of the next occurrence of a recurring expense. + Only applicable if the expense recurs. + email_reminder_in_advance: + type: integer + description: | + Number of days in advance to remind involved users about the next occurrence of a new expense. + Only applicable if the expense recurs. + enum: [null, -1, 0, 1, 2, 3, 4, 5, 6, 7, 14] + nullable: true + next_repeat: + type: string + nullable: true + description: + The date of the next occurrence of a recurring expense. + Only applicable if the expense recurs. + details: + type: string + description: Also known as "notes." + nullable: true + comments_count: + type: integer + payment: + type: boolean + description: Whether this was a payment between users + transaction_confirmed: + type: boolean + description: If a payment was made via an integrated third party service, whether it was confirmed by that service. + cost: + type: string + example: "25.0" + currency_code: + type: string + example: "USD" + repayments: + type: array + items: + type: object + properties: + from: + type: integer + description: ID of the owing user + example: 6788709 + to: + type: integer + description: ID of the owed user + example: 270896089 + amount: + type: string + example: "25.0" + date: + type: string + format: date-time + description: The date and time the expense took place. May differ from `created_at` + example: "2012-05-02T13:00:00Z" + created_at: + type: string + format: date-time + description: The date and time the expense was created on Splitwise + example: "2012-07-27T06:17:09Z" + created_by: + allOf: + - $ref: ./user.yaml + - nullable: true + updated_at: + type: string + description: The last time the expense was updated. + format: date-time + example: "2012-12-23T05:47:02Z" + updated_by: + allOf: + - $ref: ./user.yaml + - nullable: true + deleted_at: + type: string + description: If the expense was deleted, when it was deleted. + format: date-time + example: "2012-12-23T05:47:02Z" + nullable: true + deleted_by: + allOf: + - $ref: ./user.yaml + - nullable: true + category: + type: object + properties: + id: + type: integer + example: 5 + name: + type: string + example: Electricity + description: Translated to the current user's locale + receipt: + type: object + properties: + large: + type: string + nullable: true + example: https://splitwise.s3.amazonaws.com/uploads/expense/receipt/3678899/large_95f8ecd1-536b-44ce-ad9b-0a9498bb7cf0.png + original: + type: string + nullable: true + example: https://splitwise.s3.amazonaws.com/uploads/expense/receipt/3678899/95f8ecd1-536b-44ce-ad9b-0a9498bb7cf0.png + users: + type: array + items: + $ref: ./share.yaml + comments: + type: array + items: + $ref: ./comment.yaml diff --git a/schemas/expense/by_shares.yaml b/schemas/expense/by_shares.yaml new file mode 100644 index 0000000..39cf17b --- /dev/null +++ b/schemas/expense/by_shares.yaml @@ -0,0 +1,38 @@ +allOf: +- $ref: ./common.yaml +- type: object + properties: + users__0__user_id: + type: integer + example: 54123 + users__0__paid_share: + type: string + example: "25" + description: Decimal amount as a string with 2 decimal places. The amount this user paid for the expense + users__0__owed_share: + type: string + example: "13.55" + description: Decimal amount as a string with 2 decimal places. The amount this user owes for the expense + users__1__first_name: + type: string + example: "Neu" + users__1__last_name: + type: string + example: "Yewzer" + users__1__email: + type: string + example: "neuyewxyz@example.com" + users__1__paid_share: + type: string + example: "0" + description: Decimal amount as a string with 2 decimal places. The amount this user paid for the expense + users__1__owed_share: + type: string + example: "11.45" + description: Decimal amount as a string with 2 decimal places. The amount this user owes for the expense + additionalProperties: + x-additionalPropertiesName: users__{index}__{property} + type: string +- required: + - group_id + diff --git a/schemas/expense/common.yaml b/schemas/expense/common.yaml new file mode 100644 index 0000000..980f16c --- /dev/null +++ b/schemas/expense/common.yaml @@ -0,0 +1,33 @@ +type: object +properties: + cost: + type: string + example: "25" + description: A string representation of a decimal value, limited to 2 decimal places + group_id: + type: integer + description: The group to put this expense in + description: + type: string + description: A short description of the expense + example: Grocery run + details: + type: string + description: Also known as "notes." + nullable: true + date: + type: string + description: The date and time the expense took place. May differ from `created_at` + format: date-time + example: "2012-05-02T13:00:00Z" + repeat_interval: + type: string + enum: [never, weekly, fortnightly, monthly, yearly] + currency_code: + type: string + example: "USD" + description: A currency code. Must be in the list from `get_currencies` + category_id: + type: integer + description: A category id from `get_categories` + example: 15 diff --git a/schemas/expense/equal_group_split.yaml b/schemas/expense/equal_group_split.yaml new file mode 100644 index 0000000..2a79d97 --- /dev/null +++ b/schemas/expense/equal_group_split.yaml @@ -0,0 +1,11 @@ +allOf: +- $ref: ./common.yaml +- type: object + properties: + split_equally: + type: boolean + enum: [true] +- required: + - group_id + - split_equally + diff --git a/schemas/friend.yaml b/schemas/friend.yaml new file mode 100644 index 0000000..6a2dfc1 --- /dev/null +++ b/schemas/friend.yaml @@ -0,0 +1,23 @@ +allOf: +- $ref: ./user.yaml +- type: object + properties: + groups: + type: array + items: + type: object + properties: + group_id: + type: integer + example: 571 + balance: + type: array + items: + $ref: ./balance.yaml + balance: + type: array + items: + $ref: ./balance.yaml + updated_at: + type: string + format: date-time diff --git a/schemas/group.yaml b/schemas/group.yaml new file mode 100644 index 0000000..dfb1216 --- /dev/null +++ b/schemas/group.yaml @@ -0,0 +1,80 @@ +type: object +properties: + id: + type: integer + example: 321 + name: + type: string + example: "Housemates 2020" + group_type: + type: string + enum: [apartment, house, trip, other] + example: "apartment" + updated_at: + type: string + format: date-time + simplify_by_default: + type: boolean + members: + type: array + items: + allOf: + - $ref: ./user.yaml + - type: object + properties: + balance: + type: array + items: + type: object + properties: + currency_code: + type: string + example: "USD" + amount: + type: string + example: "-5.02" + original_debts: + type: array + items: + $ref: ./debt.yaml + simplified_debts: + type: array + items: + $ref: ./debt.yaml + avatar: + type: object + properties: + original: + type: string + nullable: true + example: null + xxlarge: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_avatars/avatar-ruby2-house-1000px.png" + xlarge: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_avatars/avatar-ruby2-house-500px.png" + large: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_avatars/avatar-ruby2-house-200px.png" + medium: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_avatars/avatar-ruby2-house-100px.png" + small: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_avatars/avatar-ruby2-house-50px.png" + custom_avatar: + type: boolean + cover_photo: + type: object + properties: + xxlarge: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_cover_photos/coverphoto-ruby-1000px.png" + xlarge: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/group/default_cover_photos/coverphoto-ruby-500px.png" + invite_link: + type: string + example: "https://www.splitwise.com/join/abQwErTyuI+12" + description: "A link the user can send to a friend to join the group directly" diff --git a/schemas/notification.yaml b/schemas/notification.yaml new file mode 100644 index 0000000..edb1d2b --- /dev/null +++ b/schemas/notification.yaml @@ -0,0 +1,38 @@ +--- +type: object +properties: + id: + type: integer + example: 32514315 + type: + type: integer + created_at: + type: string + format: date-time + created_by: + type: integer + example: 2 + source: + type: object + nullable: true + properties: + type: + type: string + example: "Expense" + id: + type: integer + example: 865077 + url: + type: string + nullable: true + image_url: + type: string + example: "https://s3.amazonaws.com/splitwise/uploads/notifications/v2/0-venmo.png" + image_shape: + type: string + enum: + - "square" + - "circle" + content: + type: string + example: You paid Jon H..
    You paid $23.45 diff --git a/schemas/notification_settings.yaml b/schemas/notification_settings.yaml new file mode 100644 index 0000000..f8113e6 --- /dev/null +++ b/schemas/notification_settings.yaml @@ -0,0 +1,6 @@ +type: object +description: "User's notification preferences" +additionalProperties: + type: boolean +example: + added_as_friend: true diff --git a/schemas/parent_category.yaml b/schemas/parent_category.yaml new file mode 100644 index 0000000..1a12cab --- /dev/null +++ b/schemas/parent_category.yaml @@ -0,0 +1,13 @@ +--- +allOf: +- $ref: ./category.yaml +- type: object + properties: + id: + example: 1 + name: + example: Utilities + subcategories: + type: array + items: + $ref: ./category.yaml diff --git a/schemas/share.yaml b/schemas/share.yaml new file mode 100644 index 0000000..1e31458 --- /dev/null +++ b/schemas/share.yaml @@ -0,0 +1,16 @@ +type: object +properties: + user: + $ref: ./comment_user.yaml + user_id: + type: integer + example: 491923 + paid_share: + type: string + example: "8.99" + owed_share: + type: string + example: "4.5" + net_balance: + type: string + example: "4.49" diff --git a/schemas/user.yaml b/schemas/user.yaml new file mode 100644 index 0000000..57410ba --- /dev/null +++ b/schemas/user.yaml @@ -0,0 +1,25 @@ +type: object +properties: + id: + type: integer + first_name: + type: string + example: "Ada" + last_name: + type: string + example: "Lovelace" + email: + type: string + example: "ada@example.com" + registration_status: + type: string + enum: [confirmed, dummy, invited] + picture: + type: object + properties: + small: + type: string + medium: + type: string + large: + type: string diff --git a/source/CNAME b/source/CNAME deleted file mode 100644 index 960f5ee..0000000 --- a/source/CNAME +++ /dev/null @@ -1 +0,0 @@ -dev.splitwise.com \ No newline at end of file diff --git a/source/fonts/slate.eot b/source/fonts/slate.eot deleted file mode 100755 index 13c4839a1975d4c92d66753d75553f922743c6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1876 zcmaJ?&2Jl35TEDWeRg&sP8)xqh$Kr)Yzaiu#`dnNaHtS!Xx9i53LF}#nAmmP`Xfq= z;s{Zte*qCEK;i~h4hYFPaA-x4sD#vVrBXQ{MIaD@1Ke8kn|8tnQ^fdI9N^x@? zd<=RUJW{D`U;b-v<0kYSA}zO8E|&DaCqF0BzlPme0}$TUZbAP8`m<~GR{K~75*dg= zcCEQu4DE@ZpmS=+>&5muJwb0nf0^x#V!izR7X1vpggLV7&CM3_1j&!tPMS_)mkgrN zC!rsJe5kniow8zt{RT+zltXle=pd}!=-!|+8X9a|iyqm&z#GOh#?Z4hMmoI$K1va6 zrUYgm&_U=R+`ZrJ0!LQ9E`42ef0@uHvC8U zo}w4%B?O&MCX$JGEG)w^HIqqa()pb0xK4IFpUb457c*fwDPqaQf|z%md}h1{#>ac0 zD>_@{@&c$_-fEYWRBE3yj7U8q4MTz%hA^cOxdwei%19MlXpM(P_)+e=@Rm}B>tXap zwoqO*DogaoKF+^`!zvX>{f10 zq*F6dk@0&Ok4=k2cHR|EKKaOn+_Q3KG-~~J-9n!;&D+d{W9=SQ|IqlDm9?y2uUl{( zi(0o$Q@Cby<g+B7U zE7gMM6{=S}Ps}C~lhg72%h#4X&vB-0d)je4Z)w>(?>M%-xE~jH;l@L}Lcyy(n4O9z z65lS`w&R>XbW_^$2bKN!lz(S%N3N!tcP>R={D&-^3rjyfS-aWi!AkH>sk+00G5&qW zC1%n(1Gmpd;5$IPUF_Lw@K%&WbbxEX?LgKcF9x!K$J`8LiMQ^#KsG5yOZ=|rBS1K&l2uG4tC&hwF_o-hDp_Le zTrgI}?0+pse - - -Generated by IcoMoon - - - - - - - - - - diff --git a/source/fonts/slate.ttf b/source/fonts/slate.ttf deleted file mode 100755 index ace9a46a7e1ed6b6ab3de2f30ef3e27c572f88d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1720 zcmaJ>-D_KA7=PaL@t*V?ZMro%kz(6pV-rwZyCyk@Q7?wb>UJv9`gO6wj7^qiCTUqB zO%X@;7dX69!8^V6LWJzXiw!F%45qgVdLa}=5xvm6E&iVKo-9$jJ@CHI_viaTf`}a2 zC!H2wcyVDVd0amU$>&(FZ8pn0bm7yth{U7dH)`ef4)6r{E^wmO*t_`0^~*QG?-S|8 zt!lYq{5ky*k?|Sy{uTt*p8hrX-@re<)$DYS^+1t{800m!H_O^}@g4X@@W-3w?hZXf zuY!M;^{sNV`qeJ|2)=?Gg`Mqo2XzAEd#oqjAaRXMBJF+c79{T|EPkbe7-PE;5S;Q~ zaGL1Q(r@%{&}khDI-bPX!~GUGz-0@x9Aaiik?BxrHq?#(PnWnI%nYaReOv*$ZSm>?)ctla|1hAG;T1^YPnfOv{N&_J8ekcvoG^Cm?MLpzb znO-8ASEU{s)D{<<&JKz%JjTWAM|EWWzjHMajT;ECdZ?bsKw&|^tZONrkvOuIkI%De zUYTtG_26}0jYK0>4*B1QgBuPLXU?}t^*TiboK|r`R>P0_HD+(cdi{Ze{FKYDLBs0R~?v6B%Rx~Edo2aasT@IJ-vtfG(iE^ z$Awv_E5l{^C4s84a|;3+->t#z!u^V-9Nj!@+Ph(RslFP9tMyA^DCS*vdNzG<@yc2l z`u?ov&H8>AuC0gXeBbj{4$|U#n6XQ^x*FE+&d;P>_lp(J^Zj%8%oMl&cI_ZN6TKO{ zkvFp2-&{yO{TDd~50<`txN&oc<4*8TskuV~pXj~g5i{t$k=GYVU^@bQTx>a5uvcK? zADE$i`dge4A3((KH9;?{zv*7K*f>Jt^humckR5yQeHf=xv0R7Ti)jP&N=%#Ng5wPM z_VCv|5z{smX^sCCV+<0Gsc02b(JH2*RZK;zn2MJ0U5I^E%r-TsOdYDyD_EDQO?BF) z7OFc*Cuk9TtZz5Uo$8RKb(q)a%}C8|gD__z_YMNoV9|<#sst6tUZ*|mXK52w&tq|_ J6Wq-M;U6NW`v(93 diff --git a/source/fonts/slate.woff b/source/fonts/slate.woff deleted file mode 100755 index 1e72e0ee0018119d7c814c2097cc60c0bcd05d84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1796 zcmaJ>&2Jl35TCanZ=GF;-Nqj%Qj#SmwgjS~vAs(b4i$%lc7v*fuR|jhlemtH?Nqf< z5&;$c3y8RZ#0{p83^QtGf$~^ZU#vdPL3b z4fcBr_DN>rKz!G#?&4f~pM4ZRM6a}~ts1aTadwIM%N_dh>UO7#K7YRFXF@YA68l_? z@xFm7>0K?wZ&VUvut!OxMlIIQ5*<0_&Hha~Yl49Y@PK@!7+CqFG*;eClSR)#j$=Xw zNnrjF9T`VX|4zRT99<||DqHk_nzSa(NzO5voBad{L?lOWoE4r?ZbRP(V_X@TZL>{} z(3A8mk}l-3xojrwNJr*pi-lsLQVxSKC{0w##ljO}){#>poy#tYg)pcTCk9|L<3To?f*omEO$b9ODUa}gVj!a zAvUB1l6OCpmTg;7PgnO)phbF-Xik@UVo+OLa3((}zVs*;Zywi?{r}GOL=0{q1ou!q ztD%;HAbGE?Z5HC#RzAMSTXWiN9ioS*i+Usm@#fI}eK@$`F!8DQHtAj`iEnm!UKH}P zNl{d*%%o>TwzLq6ppv_9BR_a$H<|Q)z2RXkyY6k4BJlQ)o4+xU@=Bif%MA~%sib$? zbw%hV*Y96nzi0MvpHdWZeO#D>x^i4rP!XsqKRYk5@ZB2RF5E9QWp(qg81F^VmBvaG ztu(Ggk(kS7r)DyTm#?ozQ4q}d{!9==(dt@sJ_vk&`7k5ChZ~1PD=Sgs?%Z@HoBe#* z_k-a4JvVKwyWWZ=q2@1#9uk~9EfrHBtA=!8%MC` zIGu@c6SyDi7WCym@ z2-w*rE$c_UfviKEL=Vy>={0bo+(i5#P1q3$6VB``hVMrCzU@B6vbqKcPy}m-BtQn- zkylU>byNe6DE>18g5d9bm%bZqa|qF=7&3(|U!AY)%Yg-vOsqKIq$w0D$r*3}ks4=+ z>U6?w2xMd=B$;zQ9v)F1K7KwfQ9|)<9nl&i?6-rchs{I8LC1eK^@#(|^QwB-{VeQz zoHLn@cwG?Yo(X!F(Q8iBOqSNs${h}C*?qJYLuZAnVXqx{ww|}Ux0`ca^V#hz?c7{r z7ldYbmk!M1m3G+IYC6(!VcCzqp&pJkZ#1@kw2W<^EJMqOM;^&dq6ELC*|pK!(cC0< zbZtF%BzLsuTw5O*+eg;dX0a+hcfI=gj8a~qWYJoMamHAo)!Qq|6&~9S8`{d0T?L&% z@$R=P6H0rTp6V+-Dlb>EB67JEMQ>kk%`y}{hvBC(RNEw)*%|U|4>SL+B=3rE@y*rw zR_bLZr&~XN?C;+yx2IilcDnrb?M)}An;DsTe@^rq$9L-UWNqY_oTyWyYisN3CMIh; z*C)b12exYgK^2c4S}J#2YJ+00rUlj%+LqQ7E%lldEc`K|1Bky)iLkwF{Wltvj!{A+PBEzhE3BX) zFBgqmj@&z?N1*=OK?z54T7is8FiA(N66oe`X+cKl>`n&&000000HC;3 zk^ug-#56-JW5JAtMV6Rgj#+|9A(7;@^`+^dWy@Alt7D86w|;R>QMs>d+47HJVfKhH aD;vc&%m&dlj4($-ipx}q&#}A+00017%3vV? diff --git a/source/images/favicon.ico b/source/images/favicon.ico deleted file mode 100644 index 84672b6d654cf9c8833a4bda75cd54bcaa6b83e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmcgwdr*{B6#r7knx;voKbj^@scEdn{~GhnLqtSTQN%<^e9felX(d`2lcb}Kh7OgI z(rgC+777kWZwu*=#!Il8i>9 zuDQ9nQzD%TmcMH##Hr%>IC3chx0~}p?Kpbm5CTUG>68e=WU-obyRu(HTJA8MEsaBq zk*5G0J9?y3T^^hDt+o!GzO7tf3Pt*xIxjSuyhdvL%o zL{1!w*yt&UoiW8lpXY6{SYWkUxrioxJ>nAlF*;~4f<_KS#P~6oHJ$Y$9VGGDf7fbv zkl!z$PM?D-)myMXcPM_!?1!|B7$k07hPg3Q9eQ#Msc*Kl>QXcMA~n-VzZHa|pduM1 z)#-w=E~a43+9hJnv!f&JT(#e>-$&@D=LI3Jd;>~!=_n>$cRHE91@T`j^w8gzGZ^Q} zR*`&~15;wVbCx-L;yc8BJWu%FX0x7cXemQ!?O_z_(gc;L+*h4t?-|N?&-_j9?m>K> z#cHm)(_D;eHNVmr>fGHp_puDg)|1X6((Ne^71f_6T5h(qH`X;>Bp)1b+CpV+>#!IN zB$GgT&q_UaPJPdj%z9IYp}o4{JgOUVaKFP~rnXJg_kz2;uR6*1Bl#4fy;OeHm)yg) zpXshB`X?2|)L&6-EA4T{j|K@M;&|li+Vf2{vSxOqff{`frB)zv;Ssb|_i zzz%$}8}<5J6x{p<*=5UdxiSf@rW%*IvEKBk$s&FQatso&Xt15jxTVz+S#?Q`TtL)<#t;M%m%{X*%JVd z?OA>(s@m*8tM=1)0-wj#e%|VBZEg5*OEO0J4|}Aw4+$EHX_LcYFc=*6Yq4dS=RkP|vNAJ} zxc+NQ2nzwvU)mtcU9I!oJ#|u;xbOImYax949Q$A5R##VH^Y`Ck&a9cjR`+##*gE@b za(Jk?-^CrN-jm+&o0=LiZp-X?olf0WEScugg#`&iVqW!X=B;YU z!M4*cAt7lb=FOfaaHVg%!g>E|@j3qed~P64m%JzLoRXSU%5}El0%0C2oTlRPyzARIy06>-R=B);Ac?YD(AwSa^@qKqHy`vR%h;}=D_ZGXwH=#NZhat zb7oF;*?TT~z$-pux)J&NV15|o+?!N8DG#O`);qB;oP8M=wqf`|}l&$#64z2^rZyL6>J=a=@duVtCJctbdC z6>HE*KfetQ9!tU}OXfQEf;Rq${hvJ$XRgH&?ml&m?Kt-Jm1XAIvi%+8+UC3eYUO_H zNneK#7QEwf{y6`sY0M=X52!xo_lnlv_VLuyl0D`!|;2OfAP6&O`WJ zA>L^&_;N1pxUyYx-~BwMPP|!KvX$ za~t=u3{eH?%p{_JydKY|&Lf(}E@upS&!e`n0JTj8+PnJ>$t@;&>hYj@jzVMiB>Y80 z^;8xgm0O8cQN`!~2c9E6S_@+lW&%+%m0x_c(f%9l1p4dYmZEgmD@remq6C6N&6(~L OWqQ%c>QA9&CH?_1>`S5m diff --git a/source/images/logo.svg b/source/images/logo.svg deleted file mode 100644 index 0d084f6..0000000 --- a/source/images/logo.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - Artboard Copy 2 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/source/images/navbar.png b/source/images/navbar.png deleted file mode 100644 index df38e90d87e1a215371b4977e18cde90f8832537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVQ%R6tFatx` */ } - } - ] -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_comments?expense_id=:id` - -## create_comment - -```json -{ - "comment": { /* */ }, - "errors": {} -} -``` - -`POST https://secure.splitwise.com/api/v3.0/create_comment` - -### Query parameters - -Parameter | Type | Description ---------- | ---- | ----------- -expense_id | Integer | The expense the comment is for -content | String | The comment contents - -## delete_comment - -```json -{ - "comment": { /* */ }, - "errors": {} -} -``` - -`POST https://secure.splitwise.com/api/v3.0/delete_comment/:id` - -### Query parameters - -Parameter | Type | Description ---------- | ---- | ----------- -id | Integer | The comment id diff --git a/source/includes/_errors.md b/source/includes/_errors.md deleted file mode 100644 index 843adf5..0000000 --- a/source/includes/_errors.md +++ /dev/null @@ -1,16 +0,0 @@ -# Errors - -In general, the Splitwise API returns the following error codes: - -Error Code | Meaning ----------- | ------- -400 | Bad Request. Something about the request was invalid, and you will probably need to change your request before trying again. -401 | Unauthorized. You are not logged in — your OAuth authentication may not be configured correctly. -403 | Forbidden. The current user is not allowed to perform this action. -404 | Not Found. The endpoint that you were trying to call does not exist. -500 | Internal Server Error. Our server had an unexpected error while trying to process your request. This may be a temporary problem, or there may be a problem with your request that is causing the server to crash. -503 | Service Unavailable. We're temporarily offline for maintenance. Please try again later. - -In addition, even when a call is successful and returns a `200 OK` HTTP response, the body of the response may include a key called `error` or `errors`. This is usually used to communicate validation errors. For example, if you submit an expense without any cost, we may return an `errors` key as part of the JSON response. - -The format of these errors is somewhat inconsistent, unfortunately. We're working on standardizing it, but it's a work in progress. \ No newline at end of file diff --git a/source/includes/_expenses.md b/source/includes/_expenses.md deleted file mode 100644 index 5078aae..0000000 --- a/source/includes/_expenses.md +++ /dev/null @@ -1,179 +0,0 @@ -# Expenses - -## get_expense/:id - -Return full details on an expense involving the current user. There are some additional values included in the expense object than shown here but they should be ignored. - -```json -{ - "expense": { - "id": 368887, - "group_id": 18417, //or null - "description": "Grocery run", - "repeats": false, - "repeat_interval": "never", //or "weekly", "fortnightly", "monthly", "yearly" - "email_reminder": false, - "email_reminder_in_advance": -1, // or 0, 1, 3, 5, 7, 14 - "next_repeat": null, - "details": "Additional notes about the expense", - "comments_count": 0, - "payment": false, - "transaction_confirmed": false, - "cost": "25.0", - "currency_code": "USD", - "repayments": [ - { - "from": 6788709, - "to": 270896089, - "amount": "25.0" - } - ], - "date": "2012-07-27T06:17:09Z", - "created_at": "2012-07-27T06:17:09Z", - "created_by": { /* */ }, - "updated_at": "2012-12-23T05:47:02Z", - "updated_by": { /* */ }, - "deleted_at": "2012-12-23T05:47:02Z", - "deleted_by": { /* */ }, - "category": { - "id": 18, - "name": "General" - }, - "receipt": { - "large": "https://splitwise.s3.amazonaws.com/uploads/expense/receipt/3678899/large_95f8ecd1-536b-44ce-ad9b-0a9498bb7cf0.png", - "original": "https://splitwise.s3.amazonaws.com/uploads/expense/receipt/3678899/95f8ecd1-536b-44ce-ad9b-0a9498bb7cf0.png" - }, - "users": [ - { - "user": { /* */ }, - "user_id": 270896089, - "paid_share": "25.0", - "owed_share": "0.0", - "net_balance": "25.0" - }, - { - "user": { /* */ }, - "user_id": 6788709, - "paid_share": "0.0", - "owed_share": "25.0", - "net_balance": "-25.0" - } - ], - "comments": [ /* , ,... */ ] - } -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_expense/:id` - -## get_expenses - -Return expenses involving the current user, in reverse chronological order - -```json -{ - "expenses": [ /* , , ... */ ], -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_expenses` - -### Query parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -group_id | Integer | Return expenses for specific group -friend_id | Integer | Return expenses for a specific friend that are not in any group -dated_after | Time | ISO 8601 Date time. Return expenses later than this date -dated_before | Time | ISO 8601 Date time. Return expenses earlier than this date -updated_after | Time | ISO 8601 Date time. Return expenses updated after this date -updated_before | Time | ISO 8601 Date time. Return expenses updated before this date -limit | Integer | How many expenses to fetch. Defaults to 20; set to 0 to fetch all -offset | Integer | Return expenses starting at limit * offset - -## create_expense - -```json -{ - "expense": { /* */ }, - "errors": { } -} -``` - -`POST https://secure.splitwise.com/api/v3.0/create_expense` - -### Query parameters - -#### Required - -Parameter | Type | Description ---------- | ---- | ----------- -cost | String | A string representation of a decimal value, limited to 2 decimal places -description | String | A short description of the expense -payment | Boolean | `true` if this is a payment, `false` otherwise - -#### Split configuration - - - -Parameter | Type | Description ---------- | ---- | ----------- -group_id | Integer | The group to put this expense in. -split_equally | Boolean | Set this to `true` if using it -users\__0\__user_id | Integer | The user id of a friend for this share -users\__0\__paid_share | String | Decimal amount as a string with 2 decimal places. The amount this user paid for the expense -users\__0\__owed_share | String | Decimal amount as a string with 2 decimal places. The amount this user owes on the expense -users\__1\__first_name | String | -users\__1\__last_name | String | -users\__1\__email | String | Valid email address for this user -users\__1\__paid_share | String | Decimal amount as a string with 2 decimal places. The amount this user paid for the expense -users\__1\__owed_share | String | Decimal amount as a string with 2 decimal places. The amount this user owes on the expense -users\__*\__key_value | String | Add additional user shares with indexes 2,3,4,5,... - -#### Optional parameters - -Parameter | Type | Description ---------- | ---- | ----------- -group_id | Integer | The group to put the expense in -details | String | More detailed notes -date | Time | ISO 8601 date time -repeat_interval | String | One of: never, weekly, fortnightly, monthly, yearly -currency_code | String | ISO 4217 currency code. Must be in the list from `get_currencies` -category_id | Integer | A category id from `get_categories` - -## update_expense/:id - -```json -{ - "expense": { /* */ }, - "errors": { } -} -``` - -`POST https://secure.splitwise.com/api/v3.0/update_expense/:id` - -### Query parameters - -These are the same as for `create_expense` except you only need to include parameters that are changing from the previous values. - -## delete_expense/:id - -```json -{ - "success": true, //or false -} -``` - -`POST https://secure.splitwise.com/api/v3.0/delete_expense/:id` - -## undelete_expense/:id - -```json -{ - "success": true, //or false -} -``` - -`POST https://secure.splitwise.com/api/v3.0/undelete_expense/:id` diff --git a/source/includes/_friends.md b/source/includes/_friends.md deleted file mode 100644 index e0d88f1..0000000 --- a/source/includes/_friends.md +++ /dev/null @@ -1,187 +0,0 @@ -# Friends - -Friends of a user are other users with whom the user splits expenses. To split expenses with one another, users must be friends. Users in a group together are automatically made friends. Many of the calls containing the word “friend” return objects representing friends of the current user. In addition to containing the user data, these objects contain information about the current user's balance with each friend. - -## get_friends - -> Example Response: - -```json -{ - "friends":[ - { - "id": 1, - "first_name": "Ada", - "last_name": "Lovelace", - "picture": { - "small": "image_url", - "medium": "image_url", - "large": "image_url" - }, - "balance":[ - { - "currency_code":"USD", - "amount":"-1794.5" - }, - { - "currency_code":"AED", - "amount":"7.5" - } - ], - "groups":[ // group objects only include group balances with that friend - { - "group_id":3018312, - "balance":[ - { - "currency_code":"USD", - "amount":"414.5" - } - ] - }, - { - "group_id":2830896, - "balance":[ - ] - }, - { - "group_id":0, - "balance":[ - { - "currency_code":"USD", - "amount":"-2209.0" - }, - { - "currency_code":"AED", - "amount":"7.5" - } - ] - } - ], - "updated_at":"2017-11-30T09:41:09Z" - } // , ... - ] -} -``` - -`GET https://www.splitwise.com/api/v3.0/get_friends` - -Returns a list of the current user's friends. - -## get_friend/:id - -> Example Response: - -```json -{ - "friend": - { - "id": 1, - "first_name": "Ada", - "last_name": "Lovelace", - "picture": { - "small": "image_url", - "medium": "image_url", - "large": "image_url" - }, - "registration_status": "confirmed", // or 'dummy' or 'invited' - "balance":[ - { - "currency_code":"USD", - "amount":"-1794.5" - }, - { - "currency_code":"AED", - "amount":"7.5" - } - ], - "groups":[ - { - "group_id":3018312, - "balance":[ - { - "currency_code":"USD", - "amount":"414.5" - } - ] - }, - { - "group_id":2830896, - "balance":[ - ] - }, - { - "group_id":0, - "balance":[ - { - "currency_code":"USD", - "amount":"-2209.0" - }, - { - "currency_code":"AED", - "amount":"7.5" - } - ] - } - ], - "updated_at":"2017-11-30T09:41:09Z" - } - } -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_friend/:id` - -Get detailed info on one friend of current_user. - -## create_friend - -> Example Response: same as [get_friend response](#get_friend-id) - -`POST https://secure.splitwise.com/api/v3.0/create_friend` - -Makes the current user a friend of a user specified with the url parameters user_email, user_first_name, and, optionally, user_last_name. - -### Query Parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -user_first_name | String | Add a user's first name -user_last_name | String | Add a user's last name -user_email | String | Add a user's email (or find an existing user by email) - -## create_friends - -> Example Response: same as [get_friends response](#get_friends) - - -`POST https://secure.splitwise.com/api/v3.0/create_friends` - -Make the current user a friend of the specified users. - -### Query Parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -friends\__0\__user_first_name | String | Add a user's first name -friends\__0\__user_last_name | String | Add a user's last name -friends\__0\__user_email | String | Add a user's email (or find an existing user by email) -friends\__1\__user_email | String | Find an existing user by email) - -## delete_friend/:id - -> Example Response: - -```json -{ - "success": true, //or false - "errors": ["any errors"] -} -``` - -`POST https://secure.splitwise.com/api/v3.0/delete_friend/:id` - -Given a friend ID, break off the friendship between the current user and the specified user. diff --git a/source/includes/_groups.md b/source/includes/_groups.md deleted file mode 100644 index 8b547ca..0000000 --- a/source/includes/_groups.md +++ /dev/null @@ -1,254 +0,0 @@ -# Groups - -A Group represents a collection of users who share expenses together. For example, some users use a Group to aggregate expenses related to an apartment. Others use it to represent a trip. Expenses assigned to a group are split among the users of that group. Importantly, two users in a Group can also have expenses with one another outside of the Group. - -## get_groups - -> Example Response: - -```json -{ - "groups":[ - // Non-group expenses are listed in a group with id 0 - { - "id":0, - "name":"Non-group expenses", - "updated_at": "2017-08-30T20:31:51Z", // - "members":[ - { - "id": 1, - "first_name": "Ada", - "last_name": "Lovelace", - "picture": { - "small": "image_url", - "medium": "image_url", - "large": "image_url" - }, - "email": "ada@example.com", - "registration_status": "confirmed", //'dummy', 'invited', or 'confirmed' - "balance":[ - { - "currency_code":"AED", - "amount":"0.0" - }, - { - "currency_code":"ALL", - "amount":"0.0" - }, - { - "currency_code":"EUR", - "amount":"-5.0" - }, - { - "currency_code":"USD", - "amount":"3730.5" - } //, ... - ] - } // , ... - ], - "simplify_by_default":false, - "original_debts":[ - { - "from": 12345, // user_id - "to": 54321, // user_id - "amount":"414.5", // amount as a decimal string - "currency_code":"USD" // three-letter currency code - } // , ... - ] - }, - { - "id":3018312, - "name":"a test group", - "updated_at":"2017-08-30T20:31:51Z", - "members":[ /* , , ... */ ], - "simplify_by_default":false, - "original_debts":[ - { - "from": 12345, // user_id - "to": 54321, // user_id - "amount":"414.5", // amount as a decimal string - "currency_code":"USD" // three-letter currency code - } // , ... - ], - "simplified_debts":[ - { - "from": 12345, // user_id - "to": 54321, // user_id - "amount":"414.5", // amount as a decimal string - "currency_code":"USD" // three-letter currency code - } // , ... - ], - "whiteboard":"a message!", - "group_type":"apartment", - "invite_link":"https://www.splitwise.com/join/abcdef1232456" - } // , ... -} -``` - -`GET https://www.splitwise.com/api/v3.0/get_groups` - -Returns list of all groups that the current_user belongs to - -## get_group/:id - -> Example Response: - -```json -{ - "group": - { - "id":3018312, - "name":"a test group", - "updated_at":"2017-08-30T20:31:51Z", - "members":[ /* , , ... */ ], - "simplify_by_default":false, - "original_debts":[ - { - "from": 12345, // user_id - "to": 54321, // user_id - "amount":"414.5", // amount as a decimal string - "currency_code":"USD" // three-letter currency code - } // , ... - ], - "simplified_debts":[ - { - "from": 12345, // user_id - "to": 54321, // user_id - "amount":"414.5", // amount as a decimal string - "currency_code":"USD" // three-letter currency code - } // , ... - ], - "whiteboard":"a message!", - "group_type":"apartment", - "invite_link":"https://www.splitwise.com/join/abcdef1232456" - } -} -``` - -`GET https://www.splitwise.com/api/v3.0/get_group/:id` - -Returns information about the specified group (as long as the current user has access) - -## create_group - -> Example Response: - -```json -{ - "group": - { - "id":3018312, - "name":"a test group", - "updated_at":"2017-08-30T20:31:51Z", - "members":[ /* , , ... */ ], - "simplify_by_default":false, - "original_debts":[], - "simplified_debts":[], - "whiteboard":"a message!", - "group_type":"apartment", - "invite_link":"https://www.splitwise.com/join/abcdef1232456", - // or if create failed - "errors": ["something went wrong", "with your group"] - } -} -``` - -`POST https://secure.splitwise.com/api/v3.0/create_group` - -Create a new group. Adds the current user to the group by default. - -### Query Parameters - - - - - - -Parameter | Type | Description ---------- | ---- | ----------- -name | String | Group name -whiteboard | String | Text to display on the group whiteboard -group_type | String | What the group is being used for. Must be one of: `apartment`, `house`, `trip`, `other`. -simplify_by_default | Boolean | Turn on simplify debts? -users\__0\__first_name | String | Add a user's first name -users\__0\__last_name | String | Add a user's last name -users\__0\__email | String | Add a user's email -users\__1\__user_id | Integer | Add an existing user by id - -## delete_group/:id - -> Example Response: - -```json -{ - "success": true, // or false - "errors": ["any errors"] -} -``` - -`POST https://secure.splitwise.com/api/v3.0/delete_group/:id` - -Delete an existing group. Destroys all associated records (expenses, etc.) - - -## undelete_group/:id - -> Example Response: - -```json -{ - "success": true, //or false - "errors": ["any errors"] -} -``` - -`POST https://secure.splitwise.com/api/v3.0/undelete_group/:id` - -## add_user_to_group - -> Example Response: - -```json -{ - "success": true, //or false - "errors": ["any errors"] -} -``` - -`POST https://secure.splitwise.com/api/v3.0/add_user_to_group` - -Add a user to a group - -### Query Parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -group_id | Integer | Existing group to add the user to -first_name | String | Add a user's first name -last_name | String | Add a user's last name -email | String | Add a user's email -user_id | Integer | Add an existing user by id - -## remove_user_from_group - -> Example Response: - -```json -{ - "success": true, //or false - "errors": ["any errors"] -} -``` - -`POST https://secure.splitwise.com/api/v3.0/remove_user_from_group` - -Remove a user from a group if their balance is 0 - -### Query Parameters - -Parameter | Type | Description ---------- | ---- | ----------- -group_id | Integer | Group to remove the user from -user_id | Integer | Id of user to remove diff --git a/source/includes/_notifications.md b/source/includes/_notifications.md deleted file mode 100644 index b25780a..0000000 --- a/source/includes/_notifications.md +++ /dev/null @@ -1,58 +0,0 @@ -# Notifications - -## get_notifications - -```json -{ - "notifications": [ - { - "id": 32514315, - "type": 0, - "created_at": "2020-05-13T20:58:17Z", - "created_by": 2, - "source": { - "type": "Expense", - "id": 865077, - "url": null - }, - "image_url": "https://s3.amazonaws.com/splitwise/uploads/notifications/v2/0-venmo.png", - "image_shape": "square", - "content": "You paid Jon H..
    You paid $23.45" - } //, ... - ] -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_notifications` - -Return a list of recent activity on the users account with the most recent items first. `content` will be suitable for display in HTML and uses only the ``, ``, ``, `
    ` and `` tags. - -The `type` value indicates what the notification is about. Notification `type`s may be added in the future without warning. Below is an incomplete list of notification types. - -Type | Meaning ----- | ------- -0 | Expense added -1 | Expense updated -2 | Expense deleted -3 | Comment added -4 | Added to group -5 | Removed from group -6 | Group deleted -7 | Group settings changed -8 | Added as friend -9 | Removed as friend -10 | News (a URL should be included) -11 | Debt simplification -12 | Group undeleted -13 | Expense undeleted -14 | Group currency conversion -15 | Friend currency conversion - -### Query parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -updated_after | Time | ISO 8601 Date and time string with timezone offset. Return notifications after this time. -limit | Integer | How many notifications to fetch. Defaults to 20. 0 for all. diff --git a/source/includes/_other.md b/source/includes/_other.md deleted file mode 100644 index f099a6c..0000000 --- a/source/includes/_other.md +++ /dev/null @@ -1,229 +0,0 @@ -# Other API calls - -## get_currencies - -```json -{ - "currencies":[ - { "currency_code":"USD", "unit":"$" }, - { "currency_code":"ARS", "unit":"$" }, - { "currency_code":"AUD", "unit":"$" }, - { "currency_code":"EUR", "unit":"€" }, - { "currency_code":"BRL", "unit":"R$" }, - { "currency_code":"CAD", "unit":"$" }, - { "currency_code":"CNY", "unit":"¥" }, - { "currency_code":"DKK", "unit":"kr" }, - { "currency_code":"GBP", "unit":"£" }, - { "currency_code":"INR", "unit":"₹" }, - { "currency_code":"ILS", "unit":"₪" }, - { "currency_code":"JPY", "unit":"¥" }, - { "currency_code":"MXN", "unit":"$" }, - { "currency_code":"NZD", "unit":"$" }, - { "currency_code":"PHP", "unit":"₱" }, - { "currency_code":"RUB", "unit":"₽" }, - { "currency_code":"SGD", "unit":"$" }, - { "currency_code":"SEK", "unit":"kr" }, - { "currency_code":"CHF", "unit":"Fr." }, - { "currency_code":"MYR", "unit":"RM" }, - { "currency_code":"RON", "unit":"RON" }, - { "currency_code":"ZAR", "unit":"R" }, - { "currency_code":"LKR", "unit":"Rs. " }, - { "currency_code":"NAD", "unit":"$" }, - { "currency_code":"SAR", "unit":"SR" }, - { "currency_code":"AED", "unit":"DH" }, - { "currency_code":"PLN", "unit":"PLN" }, - { "currency_code":"HRK", "unit":"HRK" }, - { "currency_code":"PKR", "unit":"Rs" }, - { "currency_code":"TWD", "unit":"NT$" }, - { "currency_code":"VEF", "unit":"Bs" }, - { "currency_code":"HUF", "unit":"Ft" }, - { "currency_code":"CLP", "unit":"$" }, - { "currency_code":"BDT", "unit":"Tk" }, - { "currency_code":"CZK", "unit":"Kč" }, - { "currency_code":"COP", "unit":"$" }, - { "currency_code":"TRY", "unit":"TL" }, - { "currency_code":"KRW", "unit":"₩" }, - { "currency_code":"BOB", "unit":"Bs." }, - { "currency_code":"VND", "unit":"₫" }, - { "currency_code":"NOK", "unit":"kr" }, - { "currency_code":"EGP", "unit":"E£" }, - { "currency_code":"HKD", "unit":"$" }, - { "currency_code":"THB", "unit":"฿" }, - { "currency_code":"KES", "unit":"KSh" }, - { "currency_code":"IDR", "unit":"Rp " }, - { "currency_code":"ISK", "unit":"kr" }, - { "currency_code":"BTC", "unit":"฿" }, - { "currency_code":"UAH", "unit":"₴" }, - { "currency_code":"MVR", "unit":"MVR" }, - { "currency_code":"OMR", "unit":"OMR" }, - { "currency_code":"YER", "unit":"YER" }, - { "currency_code":"IRR", "unit":"IRR" }, - { "currency_code":"QAR", "unit":"QR" }, - { "currency_code":"BHD", "unit":"BD" }, - { "currency_code":"TZS", "unit":"TZS" }, - { "currency_code":"RSD", "unit":"RSD" }, - { "currency_code":"ETB", "unit":"Br" }, - { "currency_code":"BGN", "unit":"BGN" }, - { "currency_code":"FJD", "unit":"$" }, - { "currency_code":"JMD", "unit":"J$" }, - { "currency_code":"UYU", "unit":"$" }, - { "currency_code":"GTQ", "unit":"Q" }, - { "currency_code":"NPR", "unit":"Rs. " }, - { "currency_code":"PEN", "unit":"S/. " }, - { "currency_code":"DJF", "unit":"Fdj " }, - { "currency_code":"LTL", "unit":"Lt " }, - { "currency_code":"MKW", "unit":"MK" }, - { "currency_code":"KWD", "unit":"KWD" }, - { "currency_code":"CRC", "unit":"₡" }, - { "currency_code":"DOP", "unit":"$" }, - { "currency_code":"NGN", "unit":"₦" }, - { "currency_code":"JOD", "unit":"JOD" }, - { "currency_code":"MAD", "unit":"MAD" }, - { "currency_code":"RWF", "unit":"FRw" }, - { "currency_code":"UGX", "unit":"USh" }, - { "currency_code":"AOA", "unit":"Kz" }, - { "currency_code":"XAF", "unit":"CFA" }, - { "currency_code":"XOF", "unit":"CFA" }, - { "currency_code":"CMG", "unit":"CMg" }, - { "currency_code":"ANG", "unit":"NAf" }, - { "currency_code":"ALL", "unit":"L" }, - { "currency_code":"PYG", "unit":"₲" }, - { "currency_code":"KYD", "unit":"CI$" }, - { "currency_code":"KZT", "unit":"₸" }, - { "currency_code":"BAM", "unit":"KM" }, - { "currency_code":"AWG", "unit":"Afl." }, - { "currency_code":"BIF", "unit":"FBu" }, - { "currency_code":"MKD", "unit":"ден" }, - { "currency_code":"XPF", "unit":"F" }, - { "currency_code":"GEL", "unit":"GEL" }, - { "currency_code":"TND", "unit":"DT" }, - { "currency_code":"MZN", "unit":"MT" }, - { "currency_code":"BYR", "unit":"BYR" }, - { "currency_code":"TTD", "unit":"TT$" }, - { "currency_code":"XCD", "unit":"EC$" }, - { "currency_code":"LBP", "unit":"ل.ل" }, - { "currency_code":"LAK", "unit":"₭" }, - { "currency_code":"MOP", "unit":"MOP$" }, - { "currency_code":"GHS", "unit":"GH₵" }, - { "currency_code":"UZS", "unit":"UZS" }, - { "currency_code":"NIO", "unit":"C$" }, - { "currency_code":"AZN", "unit":"m." }, - { "currency_code":"ZMW", "unit":"ZMW" }, - { "currency_code":"SZL", "unit":"E" }, - { "currency_code":"BWP", "unit":"P" }, - { "currency_code":"MMK", "unit":"K" }, - { "currency_code":"CVE", "unit":"$" }, - { "currency_code":"MUR", "unit":"₨" }, - { "currency_code":"SCR", "unit":"SR" }, - { "currency_code":"KHR", "unit":"៛" }, - { "currency_code":"CUP", "unit":"$" }, - { "currency_code":"CUC", "unit":"CUC$" }, - { "currency_code":"STD", "unit":"Db" }, - { "currency_code":"HNL", "unit":"L" }, - { "currency_code":"AMD", "unit":"AMD" }, - { "currency_code":"MDL", "unit":"MDL" }, - { "currency_code":"MNT", "unit":"₮" }, - { "currency_code":"BYN", "unit":"Br" }, - { "currency_code":"MGA", "unit":"Ar" }, - { "currency_code":"BBD", "unit":"$" }, - { "currency_code":"KMF", "unit":"CF" }, - { "currency_code":"IQD", "unit":"IQD" }, - { "currency_code":"BZD", "unit":"BZ$" }, - { "currency_code":"GYD", "unit":"G$" }, - { "currency_code":"SRD", "unit":"$" }, - { "currency_code":"KGS", "unit":"KGS" }, - { "currency_code":"TJS", "unit":"TJS" }, - { "currency_code":"VUV", "unit":"Vt" }, - { "currency_code":"BTN", "unit":"Nu." }, - { "currency_code":"WST", "unit":"WS$" } - ] } -``` - -`GET https://secure.splitwise.com/api/v3.0/get_currencies` - -Returns a list of all currencies allowed by the system. These are mostly ISO 4217 codes, but we do sometimes use pending codes or unofficial, colloquial codes (like BTC instead of XBT for Bitcoin) - -## get_categories - -```json -{ - "categories": [ - { - "id": 19, - "name": "Entertainment", - "icon": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square/entertainment/other.png", - "icon_types": { - "slim": { - "small": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/other.png", - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/other@2x.png" - }, - "square": { - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/other@2x.png", - "xlarge": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/other@3x.png" - } - }, - "subcategories": [ - { - "id": 20, - "name": "Games", - "icon": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square/entertainment/games.png", - "icon_types": { - "slim": { - "small": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/games.png", - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/games@2x.png" - }, - "square": { - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/games@2x.png", - "xlarge": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/games@3x.png" - } - } - }, - { - "id": 21, - "name": "Movies", - "icon": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square/entertainment/movies.png", - "icon_types": { - "slim": { - "small": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/movies.png", - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/slim/entertainment/movies@2x.png" - }, - "square": { - "large": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/movies@2x.png", - "xlarge": "https://s3.amazonaws.com/splitwise/uploads/category/icon/square_v2/entertainment/movies@3x.png" - } - } - } //, ... - ] - } //, ... - ] -} -``` - -`GET https://secure.splitwise.com/api/v3.0/get_categories` - -Returns a list of all categories Splitwise allows for expenses. There are parent categories that represent groups of categories with subcategories for more specific categorization. You may not use the parent categories when creating expenses. If you intend for an expense to be represented by the parent category and nothing more specific, please use the `"Other"` subcategory. - -## parse_sentence - -```json -{ - "expense": { /* */ }, - "valid": true, //or false - "error": "an error message" -} -``` - -`POST https://secure.splitwise.com/api/v3.0/parse_sentence` - -Attempts to create an expense from the `input` as an English natural language phrase like `"groceries $20"` or `"Jon paid me $50"`. If `valid` is `true`, the `expense` value will be a complete and valid expense. If it is `false`, the `expense` value may be missing some values. - -### Query Parameters - - - -Parameter | Type | Description ---------- | ---- | ----------- -input | String | A natural language sentence describing an expense -group_id | Integer | A group id -friend_id | Integer | A friend id -autosave | Boolean | If true, will save the resulting expense if valid. Defaults to false. diff --git a/source/includes/_terms_of_use.md b/source/includes/_terms_of_use.md deleted file mode 100644 index 3fee90a..0000000 --- a/source/includes/_terms_of_use.md +++ /dev/null @@ -1,222 +0,0 @@ -# Terms of Use - -## Overview - -Splitwise provides this Self-Serve API to facilitate integrations with third-party applications, as well as open-up functionality for hobbyists and power users to programmatically interact with their own Splitwise account and build plugins or other tools. - -If you’re interested in integrating your commercial application with Splitwise, we strongly encourage you to contact developers@splitwise.com so our development team can help discuss your use case, provide private APIs and Enterprise support, and offer an appropriate commercial license for the integration. The Self-Serve API documented here may be suitable for internal prototyping and other exploratory work. - -If you are developing a non-commercial plugin application or personal project, we recommend you make use of the Self-Serve API documented here under the API Terms Of Use. Please be aware that our Self-Serve API has conservative rate and access limits, which are subject to change at any time and not well suited to commercial projects. If this is a problem for your use case, please contact us at developers@splitwise.com to discuss your needs. - -All Self-Service API users are subject to the API Terms of Use below. - -## TERMS OF USE - -These API Terms of Use describe your rights and responsibilities when accessing our publicly available Application Programming Interface (API) and related API documentation. Please review them carefully. - -Splitwise may modify this Agreement at any time by posting a revised version on our website. The revised version will be effective at the time that it is posted. - -These API terms form a binding contract between you and us. In these terms "you," and "your," refers to the individual, company or legal entity and/or entities that you represent while accessing the API. “We”, “us”, “our” and “Splitwise” refers to Splitwise Inc. By accepting these API terms, either by accessing or using the API, or authorizing or permitting any individual to access or use the API, you agree to be bound by this contract. - -
      -
    1. - API License: -
        -
      1. - Subject to the restrictions in these terms, we grant you a non-exclusive, revocable, worldwide, non-transferable, non-sublicensable, limited license to access and use (i) our APIs (ii) related API documentation, packages, sample code, software, or materials made available by Splitwise (“API Documentation”), and (iii) any and all access keys or data derived or obtained from Splitwise API responses (“Splitwise Data”). The Splitwise API, Splitwise Data, and API Documentation will be together referred to as the “Splitwise Materials.” You will use Splitwise Materials solely as necessary to develop, test and support a Self-Service integration of your software application (an "Application" or "App") with Splitwise in accordance with this Agreement and any other agreements between You and Splitwise. -
      2. -
      -
    2. -
    3. - API License Restrictions -
        -
      1. - You agree that will you will not, and will not allow any of your partners, subsidiaries and/or affiliates and each of their respective directors, officers, employees, agents, partners, suppliers, service providers, contractors or end users (collectively, “Your Affiliates”) to engage in any Prohibited Activities set forth in section 2f. -
      2. -
      3. - Splitwise reserves the right to block or revoke, with or without notice, your access to any or all of the Splitwise Materials if Splitwise determines in its sole discretion that you are engaging in any of the Prohibited Activities. -
      4. -
      5. - Splitwise may monitor your use of Splitwise Materials to improve our services and ensure compliance with this agreement, and may suspend your access to Splitwise Materials if we believe you are in violation. -
      6. -
      7. - Your use of the Splitwise API is subject to usage limits and other functional restrictions in the sole discretion of Splitwise. You will not use the API in a manner that exceeds rate limits, or constitutes excessive or abusive usage. -
      8. -
      9. - Your use of Splitwise Materials must respect Splitwise user’s privacy choices and settings and the Privacy portion of this agreement. You will obtain explicit consent from end users as a basis for any processing of Splitwise Materials. Your use of Splitwise Materials must comply with all Applicable Data Protection Laws applicable to you, including but not limited to GDPR and CCPA compliance. -
      10. -
      11. - Prohibited Activities: -
          -
        1. - You will not use Splitwise Materials or any part thereof in any manner or for any purpose that violates any law or regulation, or any right of any person, including but not limited to intellectual property rights, rights of privacy and/or publicity, or which otherwise results in liability to Splitwise, or its officers, employees, or end users. -
        2. -
        3. - You will not use Splitwise Materials in a way that poses a security, operational or technical risk to our Services. -
        4. -
        5. - You may not Splitwise Materials to create an application that replicates existing Splitwise functionality or competes with Splitwise and our Services. -
        6. -
        7. - You will not use Splitwise Materials to create an application that encourages or creates functionality for users to violate our Terms of Service. -
        8. -
        9. - You will not use Splitwise Materials to create an application that can be used by anyone under the age of 13. You will not knowingly collect or enable the collection of any personal information from children under the age of 13. -
        10. -
        11. - You will not reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code or underlying ideas, trade secrets, algorithms or structure of the Splitwise Materials, or Splitwise software applications. -
        12. -
        13. - You will not attempt to defeat, avoid, bypass, remove, deactivate or otherwise circumvent any software protection mechanisms in the Splitwise Materials or Application or any part thereof, including without limitation, any such mechanism used to restrict or control the functionality of the API. -
        14. -
        15. - You will not use Splitwise’s name to endorse or promote any product, including a product derived from Splitwise Materials. -
        16. -
        17. - You will not sell, lease, rent, sublicense or in any way otherwise commercialize any Splitwise Data, or dataset derived from Splitwise Data and/or Splitwise Materials. -
        18. -
        19. - You will not use Splitwise Materials in applications that send unsolicited communications to users or include any malware, adware, potentially unwanted programs, or similar applications that could damage or disparage Splitwise’s reputation or services. -
        20. -
        -
      -
    4. -
    5. - Privacy -
        -
      1. - Your Application shall have a lawful privacy policy, accessible with reasonably prominent hyperlinks that does not conflict with or supersede the Splitwise Privacy Policy and that explains how you collect, store, use, and/or transfer any Personal Data via your Applications. Personal Data is data that may be used, either alone or together with other information, to identify an individual user, including, without limitation, a user’s name, address, telephone number, username, email address, city and country, geolocation, unique identifiers, picture, or other similar information and includes personal data as defined in the GDPR. -
      2. -
      3. - You are responsible for maintaining an appropriate legal basis to process any data under all applicable data protection laws (including but not limited to the GDPR, and the CCPA). -
      4. -
      5. - You will use industry standard security measures to protect against and prevent security breaches and any unauthorized disclosure of any personal information you process, including administrative, physical and technical safeguards for protection of the security, confidentiality and integrity of that personal information. -
      6. -
      7. - You must promptly notify us in writing via email to security@splitwise.com of any security deficiencies in, or intrusions to, your Applications or systems that you discover, and of any breaches of your user agreement or privacy policy that impact or may impact Splitwise customers. Please review our Privacy Policy for more information on how we collect and use data relating to the use and performance of our Service. -
      8. -
      9. - You will delete Splitwise Data as requested within a reasonable time, if so requested by either a Splitwise User or Splitwise Inc. -
      10. -
      11. - Any data submitted to Splitwise through your use of the Splitwise API will be governed by the Splitwise Privacy Policy. -
      12. -
      13. - You agree that Splitwise may collect certain use data and information related to your use of the Splitwise Materials, and the Splitwise API in connection with your Application (“Usage Data”), and that Splitwise may use such Usage Data for any business purpose, internal or external, including, without limitation, providing enhancements to the Splitwise Materials or Splitwise Platform, providing developer of user support, or otherwise. You agree to include a statement to this effect in your Application’s Privacy Policy. -
      14. -
      -
    6. - -
    7. - Conditions Of Use -
        -
      1. - Splitwise reserves the right to modify our API at any time, for any reason, without notice. -
      2. -
      3. - Splitwise may use your name, and other contact details to contact you regarding your use of our API or, if we believe you are in violation of this contract. -
      4. -
      5. - You are solely responsible for your use of the Splitwise API and any application you create that uses Splitwise Materials, including but not limited to Customer Support. -
      6. -
      7. - Splitwise reserves the right to develop and extend its products and capabilities without regard to whether those products compete with or invalidate your Splitwise integration or products offered by you. -
      8. -
      9. - Splitwise may limit (i) the number of network calls that your App may make via the API; and (ii) the maximum number of Splitwise users that may connect your Application, or (iii) anything else about the Splitwise API as Splitwise deems appropriate, at Splitwise’s sole discretion. -
      10. -
      11. - Splitwise may impose or modify these limitations without notice. Splitwise may utilize technical measures to prevent over-usage and stop usage of the API by your App after any usage limitations are exceeded or suspend your access to the API with or without notice to you in the event you exceed such limitations. -
      12. -
      13. - You will not issue any press release or other announcement regarding your Application that makes any reference to Splitwise without our prior written consent. -
      14. -
      15. - You will not use our API to distribute unsolicited advertising or promotions, or to send messages, make comments, or initiate any other unsolicited direct communication or contact with Splitwise users or partners. -
      16. -
      -
    8. - -
    9. - Use of Splitwise Marks -
        -
      1. - The rights granted in this Agreement do not include any general right to use the Splitwise name or any Splitwise trademarks, service marks or logos (the “Splitwise Marks”) with respect to your Applications. Subject to your continued compliance with this Agreement, you may use Splitwise Marks for limited purposes related to your Applications only as described in Splitwise Branding Guidelines and/or as provided in written communications with the Splitwise team. -
      2. -
      3. - These rights apply on a non-exclusive, non-transferable, worldwide, royalty-free basis, without any right to sub-license, and may be revoked by Splitwise at any time. -
      4. -
      5. - If Splitwise updates Branding Guidelines or any Splitwise Marks that you are using, you agree to update such Splitwise Marks to reflect the most current versions. You must not use any Splitwise Marks or trade dress, or any confusingly similar mark or trade dress, as the name or part of the name, user interface, or icon of your Applications, or as part of any logo or branding for your Applications. -
      6. -
      -
    10. - -
    11. - Reservation Of Rights. The Splitwise Materials as well as the trademarks, copyrights, trade secrets, patents or other intellectual property (collectively, “Intellectual Property”) contained therein will remain the sole and exclusive property of Splitwise, and you will reasonably assist Splitwise in protecting such ownership. Splitwise reserves to itself all rights to the Splitwise Materials not expressly granted to You. Except as expressly provided in this Agreement, You do not acquire any rights to or interest in the Intellectual Property. You will not utilize Splitwise Intellectual Property except as expressly authorized under this Agreement. -
    12. - -
    13. - Feedback. Splitwise welcomes feedback from developers to improve our API, documentation and Services, and may provide feedback to you as well. We will review any feedback received, however we make no guarantee that suggestions will be implemented. If you choose to provide feedback, suggestions or comments regarding the Splitwise API, documentation, or services, you acknowledge that Splitwise will be free to use your feedback in any way it sees fit. This includes the freedom to copy, modify, create derivative works, distribute, publicly display, publicly perform, grant sublicenses to, and otherwise exploit in any manner such feedback, suggestions or comments, for any and all purposes, with no obligation of any kind to you, in perpetuity. -
    14. - -
    15. - Confidentiality. Any information not generally available to the public that is made available to you should be considered Confidential. You agree to: -
        -
      1. - Protect this information from unauthorized use, access, or disclosure, -
      2. -
      3. - Use this information only as necessary, -
      4. -
      5. - Destroy any copies, or return this information to us when this Contract is terminated, or at any time as requested by Splitwise -
      6. -
      -
    16. - -
    17. - Termination. This Contract shall remain effective until terminated by either party. You may terminate this Contract at any time, by discontinuing your use of our APIs. Splitwise may terminate this Contract at any time with or without cause and without advanced notice to you. Upon termination, all rights and licenses granted under this Contract shall immediately terminate. You must immediately discontinue any use, and destroy any copies of the Splitwise Materials and Confidential Information in your possession. -
    18. -
    19. - Representations and Warranties. You represent and warrant that you have validly entered into the Contract, and that you have the legal power to do so, and that doing so will not violate any law, government regulation, or breach agreement with another third party. -

      THE SPLITWISE API AND DOCUMENTATION IS BEING PROVIDED TO YOU ‘AS IS’ AND ‘AS AVAILABLE’ WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY WARRANTIES OF MERCHANTABILITY, TITLE, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. YOU ACKNOWLEDGE THAT WE DO NOT WARRANT THAT THE APIS WILL BE UNINTERRUPTED, TIMELY, SECURE, OR ERROR-FREE. -
    20. -
    21. - Limitation of Liability. -

      TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL SPLITWISE, ITS AFFILIATES, OFFICERS, DIRECTORS, EMPLOYEES, AGENTS, LICENSORS, LICENSEES, ASSIGNS OR SUCCESSORS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE OR CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO ANY LOSS OF DATA, SERVICE INTERRUPTION, COMPUTER FAILURE, OR PECUNIARY LOSS) HOWEVER CAUSED, WHETHER IN CONTRACT, TORT OR UNDER ANY OTHER THEORY OF LIABILITY, AND WHETHER OR NOT YOU OR THE THIRD PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. YOUR ONLY RIGHT WITH RESPECT TO ANY PROBLEMS OR DISSATISFACTION WITH THE SPLITWISE SERVICES IS TO STOP USING THE SPLITWISE SERVICES. -

      SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OR EXCLUSION OF LIABILITY FOR CERTAIN TYPES OF DAMAGES REFERRED TO ABOVE (INCLUDING INCIDENTAL OR CONSEQUENTIAL DAMAGES). ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU. YOU AGREE THAT SPLITWISE’S AGGREGATE LIABILITY UNDER THIS AGREEMENT IS LIMITED TO ONE HUNDRED DOLLARS ($100). -
    22. - -
    23. - Indemnification: You agree to defend, indemnify, and hold harmless Splitwise and its affiliates, directors, and customers, from and against any and all third-party claims, actions, suits, and proceedings (including, but not limited to legal, or investigative fees), arising out of, or related to your use of the Splitwise Services, your violation of this Contract, your violation of your user agreement or privacy policy, or your violation of any laws, regulations, or third party rights. -
    24. - -
    25. - Miscellaneous -
        -
      1. - Applicable Law, Jurisdiction, and Venue: Any dispute arising out of this Agreement shall be governed by Massachusetts law and controlling U.S. federal law, without regard to conflict of law provisions thereof. Any claim or dispute between you and Splitwise that arises in whole or in part from this Contract or your use of the API or our Services shall be decided exclusively by a court of competent jurisdiction located in Massachusetts, and you hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to venue and jurisdiction in the state and federal courts of Massachusetts. -
      2. -
      3. - Assignment: You may not assign or delegate any of your rights or obligations hereunder, whether by operation of law or otherwise, without Splitwise’s prior written consent. Splitwise retains the right to assign the Contract in its entirety, without consent of the other party, to a corporate affiliate or in connection with a merger, acquisition, corporate reorganization, or sale of all or substantially all of its assets. Any purported assignment in violation of this section is void. -
      4. -
      5. - Language: This contact was drafted in English. In the event that this contract, or any part thereof, is translated to a language other than English, the English-language version shall control in the event of a conflict. -
      6. -
      7. - Relationship: You and Splitwise are independent contractors. This Contract does not create or imply any partnership, agency, joint venture, fiduciary or employment relationship between the parties. There are no third party beneficiaries to the Contract. -
      8. -
      9. - Severability: The Contract will be enforced to the fullest extent permitted under applicable law. If any provision of the Contract is found to be invalid or unenforceable by a court of competent jurisdiction, the provision will be modified by the court and interpreted so as best to accomplish the objectives of the original provision to the fullest extent permitted by law, and the remaining provisions of the Contract will remain in effect. -
      10. -
      11. - Force Majeure: Neither we nor you will be responsible for any failure to perform obligations under this Contract if such failure is caused by events beyond the reasonable control of a party, which may include denial-of-service attacks, a failure by a third party hosting provider, acts of God, war, strikes, revolutions, lack or failure of transportation facilities, laws or governmental regulations. -
      12. -
      13. - Entire Agreement: These Terms comprise the entire agreement between you and Splitwise with respect to the above subject matter and supersedes and merges all prior proposals, understandings and contemporaneous communications. -
      14. -
      -
    26. -
    diff --git a/source/includes/_users.md b/source/includes/_users.md deleted file mode 100644 index 78956ad..0000000 --- a/source/includes/_users.md +++ /dev/null @@ -1,89 +0,0 @@ -# Users - - - -## get_current_user - -> Example Response: - -```json -{ - "user": { - "id": 1, - "first_name": "Ada", - "last_name": "Lovelace", - "picture": { - "small": "image_url", - "medium": "image_url", - "large": "image_url" - }, - "email": "ada@example.com", - "registration_status": "confirmed", //'dummy', 'invited', or 'confirmed' - "default_currency": "USD", - "locale": "en", - "notifications_read": "2017-06-02T20:21:57Z", // the last time notifications were marked as read - "notifications_count": 12, // the number of unread notifications - "notifications": { // notification preferences - "added_as_friend": true, - // ... - } - } -} -``` - -`GET https://www.splitwise.com/api/v3.0/get_current_user` - -Retrieve info about the user who is currently logged in. - -## get_user/:id - -> Example Response: - -```json -{ - "user": { - "id": 1, - "first_name": "Ada", - "last_name": "Lovelace", - "picture": { - "small": "image_url", - "medium": "image_url", - "large": "image_url" - }, - "email": "ada@example.com", - "registration_status": "confirmed" //'dummy', 'invited', or 'confirmed' - } - } -} -``` - -`GET https://www.splitwise.com/api/v3.0/get_user/:id` - -Retrieve info about another user that the current user is acquainted with (e.g. they are friends, or they both belong to the same group). - - -## update_user/:id - -`POST https://www.splitwise.com/api/v3.0/update_user/:id` - -Update a specific user. A user can edit anything about their own account, and may edit the `first_name`, `last_name`, and `email` for any acquaintances who have not logged in yet. - -### Query Parameters - -Parameter | Type | Description ---------- | ---- | ----------- -first_name | String | User's first name -last_name | String | User's last name -email | String | User's email address -password | String | User's password -locale | String | User's locale (ISO 639-1) -date_format | String | Preferred Date Format (e.g. MM/DD/YYYY or -default_currency | String | User's default currency (ISO 4217) -default_group_id | String | Default Group ID (set -1 for none) -notification_settings | Object
    `{ notification_type: bool, ... }` | Set notification types on or off: added_as_friend, added_to_group, expense_added, expense_updated, bills, payments, monthly_summary, announcements,} - - - - - - diff --git a/source/index.html.md b/source/index.html.md deleted file mode 100644 index 5dbe4de..0000000 --- a/source/index.html.md +++ /dev/null @@ -1,284 +0,0 @@ ---- -title: Splitwise API Reference - -language_tabs: # must be one of https://git.io/vQNgJ - - ruby - - javascript - -toc_footers: - - Sign up for an API key - - Documentation powered by Slate - -includes: - - users - - groups - - friends - - expenses - - comments - - notifications - - other - - errors - - terms_of_use - -search: true ---- - -# Introduction - -Hey there! We're glad you're interested in the Splitwise API. This documentation will help you to fetch information on users, expenses, groups, and much more. - -If something in the API is confusing you, you can open an [issue](https://github.com/splitwise/api-docs/issues) about it on GitHub. We're a small team, so we may not have an instant fix, but we'll get back to you as soon as we're able. (If you spot an issue in our API documentation itself, feel free to open a [pull request](https://github.com/splitwise/api-docs/pulls) to update this website!) - -# Third-party SDKs - -The development community has built a number of unofficial, third-party SDKs for Splitwise in a variety of different languages. - -- Javascript - - [https://github.com/keriwarr/splitwise](https://github.com/keriwarr/splitwise) - - [https://github.com/Bearer/Pizzly](https://github.com/Bearer/Pizzly) -- Ruby - - [https://github.com/divyum/splitwise-ruby](https://github.com/divyum/splitwise-ruby) -- Python - - [https://github.com/namaggarwal/splitwise](https://github.com/namaggarwal/splitwise) -- Elixir - - [https://github.com/matiasdelgado/ex_splitwise](https://github.com/matiasdelgado/ex_splitwise) -- Java - - [https://github.com/sritejakv/splitwise-java](https://github.com/sritejakv/splitwise-java) -- Dart - - [https://github.com/srthkpthk/splitwise_api](https://github.com/srthkpthk/splitwise_api) - -If you've built a third-party SDK for Splitwise and you'd like to see it included in this list, then please open a [pull request](https://github.com/splitwise/api-docs/pulls) to update this section and add a new link. Thank you for your work! - - - -# Authentication - -```ruby -################### -# OAuth 2 example # -################### - -#!/usr/bin/env ruby -require 'oauth2' # gem 'oauth2' -require 'pp' - -CONSUMER_KEY = -CONSUMER_SECRET = -TOKEN_URL = 'https://secure.splitwise.com/oauth/token' -AUTHORIZE_URL = 'https://secure.splitwise.com/oauth/authorize' -MY_CALLBACK_URL = 'http://localhost:8080/callback' # Make sure to set the redirect URL that you registered with Splitwise when creating your app so that it matches this URL -BASE_SITE = 'https://secure.splitwise.com/' - -client = OAuth2::Client.new(CONSUMER_KEY, CONSUMER_SECRET, site: BASE_SITE) -authorize_url = client.auth_code.authorize_url(redirect_uri: MY_CALLBACK_URL) -# => "https://www.splitwise.com/oauth/authorize?response_type=code&client_id=#{CONSUMER_KEY}&redirect_uri=#{MY_CALLBACK_URL} - -require 'webrick' -require 'cgi' - -server = WEBrick::HTTPServer.new( - Port: 8080, - StartCallback: proc { puts "Opening 'localhost:8080'"; `open 'http://localhost:8080/'` }) - -server.mount_proc "/" do |req, res| - res.body = " Get Code " -end - -server.mount_proc "/callback" do |req, res| - authorization_code = CGI.parse(req.query_string)['code'] - access_token = client.auth_code.get_token( - authorization_code, - redirect_uri: MY_CALLBACK_URL - ) - - # This is your actual bearer token! Your bearer token will be printed out to the console. - # You can then use that bearer token to make additional API requests to Splitwise. For example: - # curl -XGET "http://secure.splitwise.com/api/v3.0/get_current_user" -H "Authorization: Bearer YOUR_TOKEN" - puts "***" - puts "Here is your OAuth Bearer token!" - pp access_token.to_hash - puts "***" - - response = access_token.get('/api/v3.0/get_current_user') - res.body = response.body -end - -# Triggered by ^C on os x; run `$ stty -a |grep intr` to find appropriate key combination -trap('INT') { server.stop } -server.start - - -################### -# OAuth 1 example # -################### - -#!/usr/bin/env ruby -require 'oauth' # gem oauth - -CONSUMER_KEY = -CONSUMER_SECRET = -REQUEST_TOKEN_URL ='https://secure.splitwise.com/oauth/request_token' -ACCESS_TOKEN_URL = 'https://secure.splitwise.com/oauth/access_token' -AUTHORIZE_URL = 'https://secure.splitwise.com/oauth/authorize' -MY_CALLBACK_URL = 'http://localhost:8080/callback' - -consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, site: 'https://www.splitwise.com') -request_token = consumer.get_request_token(oauth_callback: MY_CALLBACK_URL) -# => "https://www.splitwise.com/oauth/authorize?oauth_token="#{request_token}" - -require 'webrick' -require 'cgi' -server = WEBrick::HTTPServer.new( - Port: 8080, - StartCallback: proc { puts "Opening 'localhost:8080'"; `open 'http://localhost:8080/'` }) - -server.mount_proc "/" do |req, res| - res.body = " Get Code " -end - -server.mount_proc "/callback" do |req, res| - oauth_verifier = CGI.parse(req.query_string)['oauth_verifier'].first - access_token = request_token.get_access_token(oauth_verifier: oauth_verifier) - access_token.params.each do |k, v| - puts " #{k}: #{v}" unless k.is_a?(Symbol) - end - response = access_token.request(:get, '/api/v3.0/get_current_user') - res.body = response.body -end - -# Triggered by ^C on os x; run `$ stty -a |grep intr` to find appropriate key combination -trap('INT') { server.stop } -server.start -``` - -```javascript -################### -# OAuth 2 example # -################### - -#!/usr/bin/env node -'use strict'; - -const OAuth = require('oauth'); -const {exec} = require('child_process'); -const qs = require('querystring'); -const http = require('http'); - -const CONSUMER_KEY = ; -const CONSUMER_SECRET = ; -const TOKEN_URL = '/oauth/token'; -const AUTHORIZE_URL = '/oauth/authorize'; -const MY_CALLBACK_URL = 'http://localhost:8080/callback'; -const BASE_SITE = 'https://www.splitwise.com'; - -var authURL; -const client = new OAuth.OAuth2( - CONSUMER_KEY, - CONSUMER_SECRET, - BASE_SITE, - AUTHORIZE_URL, - TOKEN_URL, - null); - -const server = http.createServer(function(req, res) { - console.log(req.url); - var p = req.url.split('/'); - console.log(p); - - var pLen = p.length; - - authURL = client.getAuthorizeUrl({ - redirect_uri: MY_CALLBACK_URL, - response_type: 'code' - }); - - /** - * Creating an anchor with authURL as href and sending as response - */ - var body = ' Get Code '; - if (pLen === 2 && p[1] === '') { - res.writeHead(200, { - 'Content-Length': body.length, - 'Content-Type': 'text/html' - }); - res.end(body); - } else if (pLen === 2 && p[1].indexOf('callback') === 0) { - /** To obtain and parse code='...' from code?code='...' */ - var qsObj = qs.parse(p[1].split('?')[1]); - console.log(qsObj.code); - /** Obtaining access_token */ - client.getOAuthAccessToken( - qsObj.code, - { - 'redirect_uri': MY_CALLBACK_URL, - 'grant_type': 'authorization_code' - }, - function(e, access_token, refresh_token, results) { - if (e) { - console.log(e); - res.end(JSON.stringify(e)); - } else if (results.error) { - console.log(results); - res.end(JSON.stringify(results)); - } - else { - console.log('Obtained access_token: ', access_token); - client.get('https://secure.splitwise.com/api/v3.0/get_current_user', access_token, function(e, data, response) { - if (e) console.error(e); - res.end(data); - }); - } - }); - - } else { - // Unhandled url - } -}); -server.listen({port: 8080}, serverReady); - -function serverReady() { - console.log(`Server on port ${server.address().port} is now up`); - exec(`open http://localhost:8080/`, (err, stdout, stderr) => { - if (err) { - // node couldn't execute the command - return; - } - - // the *entire* stdout and stderr (buffered) - console.log(`stdout: ${stdout}`); - console.log(`stderr: ${stderr}`); - }); -} - -module.exports = client; -``` - -Splitwise uses OAuth for authentication. To connect via OAuth, you'll need to [register your app](https://secure.splitwise.com/apps) on Splitwise. When you register, you'll be given a consumer key and a consumer secret, which can be used by your application to make requests to the Splitwise server. - - - -For more information on using OAuth, check out the following resources: - -- The OAuth community [getting started](http://oauth.net/documentation/getting-started/) guide -- The term.ie [OAuth test server](http://term.ie/oauth/example/) (great for debugging authorization issues) -- This old [Splitwise blog post](https://blog.splitwise.com/2013/07/15/setting-up-oauth-for-the-splitwise-api/) about OAuth - -## API keys - -```http -GET /api/v3.0/get_current_user HTTP/1.1 -Host: www.splitwise.com -Authorization: Bearer -``` - -For speed and ease of prototyping, you can generate a personal API key on your app's details page. You should present this key to the server via the `Authorization` header as a Bearer token. The API key is an access token for your personal account, so keep it as safe as you would a password. - -If your key becomes compromised or you want to invalidate your existing key for any other reason, you can do so on the app details page by generating a new key. - -# An important note about nested parameters - -Due to a quirk in Splitwise's servers, nested parameters (e.g. `users[1][first_name]`) cannot currently be used when submitting a request. Instead, to indicate nested parameters, use double underscores (e.g. `users__1__first_name`). We hope to support proper nested parameters in future API versions. diff --git a/source/javascripts/all.js b/source/javascripts/all.js deleted file mode 100644 index 5f5d406..0000000 --- a/source/javascripts/all.js +++ /dev/null @@ -1,2 +0,0 @@ -//= require ./all_nosearch -//= require ./app/_search diff --git a/source/javascripts/all_nosearch.js b/source/javascripts/all_nosearch.js deleted file mode 100644 index b18c1d8..0000000 --- a/source/javascripts/all_nosearch.js +++ /dev/null @@ -1,16 +0,0 @@ -//= require ./lib/_energize -//= require ./app/_toc -//= require ./app/_lang - -$(function() { - loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10); - setupLanguages($('body').data('languages')); - $('.content').imagesLoaded( function() { - window.recacheHeights(); - window.refreshToc(); - }); -}); - -window.onpopstate = function() { - activateLanguage(getLanguageFromQueryString()); -}; diff --git a/source/javascripts/app/_lang.js b/source/javascripts/app/_lang.js deleted file mode 100644 index 208f4e0..0000000 --- a/source/javascripts/app/_lang.js +++ /dev/null @@ -1,164 +0,0 @@ -//= require ../lib/_jquery - -/* -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 () { - 'use strict'; - - var languages = []; - - window.setupLanguages = setupLanguages; - window.activateLanguage = activateLanguage; - window.getLanguageFromQueryString = getLanguageFromQueryString; - - 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.tab-" + languages[i]).hide(); - $(".lang-specific." + languages[i]).hide(); - } - $(".highlight.tab-" + language).show(); - $(".lang-specific." + language).show(); - - window.recacheHeights(); - - // 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; - }); - }); -})(); diff --git a/source/javascripts/app/_search.js b/source/javascripts/app/_search.js deleted file mode 100644 index 9ff4233..0000000 --- a/source/javascripts/app/_search.js +++ /dev/null @@ -1,98 +0,0 @@ -//= require ../lib/_lunr -//= require ../lib/_jquery -//= require ../lib/_jquery.highlight -;(function () { - 'use strict'; - - var content, searchResults; - var highlightOpts = { element: 'span', className: 'search-highlight' }; - var searchDelay = 0; - var timeoutHandle = 0; - - 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() - }); - }); - - determineSearchDelay(); - } - function determineSearchDelay() { - if(index.tokenStore.length>5000) { - searchDelay = 300; - } - } - - function bind() { - content = $('.content'); - searchResults = $('.search-results'); - - $('#input-search').on('keyup',function(e) { - var wait = function() { - return function(executingFunction, waitTime){ - clearTimeout(timeoutHandle); - timeoutHandle = setTimeout(executingFunction, waitTime); - }; - }(); - wait(function(){ - search(e); - }, searchDelay ); - }); - } - - function search(event) { - - var searchInput = $('#input-search')[0]; - - unhighlight(); - searchResults.addClass('visible'); - - // ESC clears the field - if (event.keyCode === 27) searchInput.value = ''; - - if (searchInput.value) { - var results = index.search(searchInput.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("
  • " + $(elem).text() + "
  • "); - }); - highlight.call(searchInput); - } else { - searchResults.html('
  • '); - $('.search-results li').text('No Results Found for "' + searchInput.value + '"'); - } - } else { - unhighlight(); - searchResults.removeClass('visible'); - } - } - - function highlight() { - if (this.value) content.highlight(this.value, highlightOpts); - } - - function unhighlight() { - content.unhighlight(highlightOpts); - } -})(); - diff --git a/source/javascripts/app/_toc.js b/source/javascripts/app/_toc.js deleted file mode 100644 index bb651b0..0000000 --- a/source/javascripts/app/_toc.js +++ /dev/null @@ -1,117 +0,0 @@ -//= require ../lib/_jquery -//= require ../lib/_imagesloaded.min -;(function () { - 'use strict'; - - var loaded = false; - - var debounce = function(func, waitTime) { - var timeout = false; - return function() { - if (timeout === false) { - setTimeout(function() { - func(); - timeout = false; - }, waitTime); - timeout = true; - } - }; - }; - - var closeToc = function() { - $(".toc-wrapper").removeClass('open'); - $("#nav-button").removeClass('open'); - }; - - function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) { - var headerHeights = {}; - var pageHeight = 0; - var windowHeight = 0; - var originalTitle = document.title; - - var recacheHeights = function() { - headerHeights = {}; - pageHeight = $(document).height(); - windowHeight = $(window).height(); - - $toc.find(tocLinkSelector).each(function() { - var targetId = $(this).attr('href'); - if (targetId[0] === "#") { - headerHeights[targetId] = $(targetId).offset().top; - } - }); - }; - - var refreshToc = function() { - var currentTop = $(document).scrollTop() + scrollOffset; - - if (currentTop + windowHeight >= pageHeight) { - // at bottom of page, so just select last header by making currentTop very large - // this fixes the problem where the last header won't ever show as active if its content - // is shorter than the window height - currentTop = pageHeight + 1000; - } - - var best = null; - for (var name in headerHeights) { - if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) { - best = name; - } - } - - // Catch the initial load case - if (currentTop == scrollOffset && !loaded) { - best = window.location.hash; - loaded = true; - } - - var $best = $toc.find("[href='" + best + "']").first(); - if (!$best.hasClass("active")) { - // .active is applied to the ToC link we're currently on, and its parent
      s selected by tocListSelector - // .active-expanded is applied to the ToC links that are parents of this one - $toc.find(".active").removeClass("active"); - $toc.find(".active-parent").removeClass("active-parent"); - $best.addClass("active"); - $best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent'); - $best.siblings(tocListSelector).addClass("active"); - $toc.find(tocListSelector).filter(":not(.active)").slideUp(150); - $toc.find(tocListSelector).filter(".active").slideDown(150); - if (window.history.pushState) { - window.history.pushState(null, "", best); - } - // TODO remove classnames - document.title = $best.data("title") + " – " + originalTitle; - } - }; - - var makeToc = function() { - recacheHeights(); - refreshToc(); - - $("#nav-button").click(function() { - $(".toc-wrapper").toggleClass('open'); - $("#nav-button").toggleClass('open'); - return false; - }); - $(".page-wrapper").click(closeToc); - $(".toc-link").click(closeToc); - - // reload immediately after scrolling on toc click - $toc.find(tocLinkSelector).click(function() { - setTimeout(function() { - refreshToc(); - }, 0); - }); - - $(window).scroll(debounce(refreshToc, 200)); - $(window).resize(debounce(recacheHeights, 200)); - }; - - makeToc(); - - window.recacheHeights = recacheHeights; - window.refreshToc = refreshToc; - } - - window.loadToc = loadToc; -})(); diff --git a/source/javascripts/lib/_energize.js b/source/javascripts/lib/_energize.js deleted file mode 100644 index 6798f3c..0000000 --- a/source/javascripts/lib/_energize.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * 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/source/javascripts/lib/_imagesloaded.min.js b/source/javascripts/lib/_imagesloaded.min.js deleted file mode 100644 index d66f658..0000000 --- a/source/javascripts/lib/_imagesloaded.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * 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/source/javascripts/lib/_jquery.highlight.js b/source/javascripts/lib/_jquery.highlight.js deleted file mode 100644 index 9dcf3c7..0000000 --- a/source/javascripts/lib/_jquery.highlight.js +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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/source/javascripts/lib/_jquery.js b/source/javascripts/lib/_jquery.js deleted file mode 100644 index b78120e..0000000 --- a/source/javascripts/lib/_jquery.js +++ /dev/null @@ -1,9831 +0,0 @@ -/*! - * jQuery JavaScript Library v2.2.0 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-01-08T20:02Z - */ - -(function( global, factory ) { - - if ( typeof module === "object" && typeof module.exports === "object" ) { - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Support: Firefox 18+ -// Can't be in strict mode, several libs including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -//"use strict"; -var arr = []; - -var document = window.document; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var support = {}; - - - -var - version = "2.2.0", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android<4.1 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // parseFloat NaNs numeric-cast false positives (null|true|false|"") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - // adding 1 corrects loss of precision from parseFloat (#15100) - var realStringObj = obj && obj.toString(); - return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0; - }, - - isPlainObject: function( obj ) { - - // Not plain objects: - // - Any object or value whose internal [[Class]] property is not "[object Object]" - // - DOM nodes - // - window - if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.constructor && - !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { - return false; - } - - // If the function hasn't returned already, we're confident that - // |obj| is a plain object, created by {} or constructed with new Object - return true; - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android<4.0, iOS<6 (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - var script, - indirect = eval; - - code = jQuery.trim( code ); - - if ( code ) { - - // If the code includes a valid, prologue position - // strict mode pragma, execute code by injecting a - // script tag into the document. - if ( code.indexOf( "use strict" ) === 1 ) { - script = document.createElement( "script" ); - script.text = code; - document.head.appendChild( script ).parentNode.removeChild( script ); - } else { - - // Otherwise, avoid the DOM node creation, insertion - // and removal by using an indirect global eval - - indirect( code ); - } - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE9-11+ - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android<4.1 - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -// JSHint would error on this code due to the Symbol not being defined in ES5. -// Defining this global in .jshintrc would create a danger of using the global -// unguarded in another place, it seems safer to just disable JSHint for these -// three lines. -/* jshint ignore: start */ -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} -/* jshint ignore: end */ - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: iOS 8.2 (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.2.1 - * http://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-10-17 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // General-purpose constants - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // http://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, nidselect, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']"; - while ( i-- ) { - groups[i] = nidselect + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); - } - // release memory in IE - div = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, parent, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( (parent = document.defaultView) && parent.top !== parent ) { - // Support: IE 11 - if ( parent.addEventListener ) { - parent.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( document.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID find and filter - if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var m = context.getElementById( id ); - return m ? [ m ] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - docElem.appendChild( div ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibing-combinator selector` fails - if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( div ) { - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( (oldCache = uniqueCache[ dir ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ dir ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ ); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - } ); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - - } - - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, - len = this.length, - ret = [], - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - // Support: Blackberry 4.6 - // gEBID returns nodes no longer in the document (#6963) - if ( elem && elem.parentNode ) { - - // Inject the element directly into the jQuery object - this.length = 1; - this[ 0 ] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( pos ? - pos.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - return elem.contentDocument || jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnotwhite = ( /\S+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ], - [ "notify", "progress", jQuery.Callbacks( "memory" ) ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this === promise ? newDefer.promise() : this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( function() { - - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || - ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. - // If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // Add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .progress( updateFunc( i, progressContexts, progressValues ) ) - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ); - } else { - --remaining; - } - } - } - - // If we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -} ); - - -// The deferred used on DOM ready -var readyList; - -jQuery.fn.ready = function( fn ) { - - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } - } -} ); - -/** - * The ready event handler and self cleanup method - */ -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called - // after the browser event has already occurred. - // Support: IE9-10 only - // Older IE sometimes signals "interactive" too soon - if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - - } else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); - } - } - return readyList.promise( obj ); -}; - -// Kick off the DOM ready check even if the user does not -jQuery.ready.promise(); - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - len ? fn( elems[ 0 ], key ) : emptyGet; -}; -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - /* jshint -W018 */ - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - register: function( owner, initial ) { - var value = initial || {}; - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable, non-writable property - // configurability must be true to allow the property to be - // deleted with the delete operator - } else { - Object.defineProperty( owner, this.expando, { - value: value, - writable: true, - configurable: true - } ); - } - return owner[ this.expando ]; - }, - cache: function( owner ) { - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( !acceptData( owner ) ) { - return {}; - } - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - if ( typeof data === "string" ) { - cache[ data ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ prop ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - owner[ this.expando ] && owner[ this.expando ][ key ]; - }, - access: function( owner, key, value ) { - var stored; - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - stored = this.get( owner, key ); - - return stored !== undefined ? - stored : this.get( owner, jQuery.camelCase( key ) ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, name, camel, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key === undefined ) { - this.register( owner ); - - } else { - - // Support array or space separated string of keys - if ( jQuery.isArray( key ) ) { - - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = key.concat( key.map( jQuery.camelCase ) ); - } else { - camel = jQuery.camelCase( key ); - - // Try the string as a key before any manipulation - if ( key in cache ) { - name = [ key, camel ]; - } else { - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - name = camel; - name = name in cache ? - [ name ] : ( name.match( rnotwhite ) || [] ); - } - } - - i = name.length; - - while ( i-- ) { - delete cache[ name[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <= 35-45+ - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://code.google.com/p/chromium/issues/detail?id=378607 - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE11+ - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data, camelKey; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // with the key as-is - data = dataUser.get( elem, key ) || - - // Try to find dashed key if it exists (gh-2779) - // This is for 2.2.x only - dataUser.get( elem, key.replace( rmultiDash, "-$&" ).toLowerCase() ); - - if ( data !== undefined ) { - return data; - } - - camelKey = jQuery.camelCase( key ); - - // Attempt to get data from the cache - // with the key camelized - data = dataUser.get( elem, camelKey ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, camelKey, undefined ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - camelKey = jQuery.camelCase( key ); - this.each( function() { - - // First, attempt to store a copy or reference of any - // data that might've been store with a camelCased key. - var data = dataUser.get( this, camelKey ); - - // For HTML5 data-* attribute interop, we have to - // store property names with dashes in a camelCase form. - // This might not apply to all properties...* - dataUser.set( this, camelKey, value ); - - // *... In the case of properties that might _actually_ - // have dashes, we need to also store a copy of that - // unchanged property. - if ( key.indexOf( "-" ) > -1 && data !== undefined ) { - dataUser.set( this, key, value ); - } - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHidden = function( elem, el ) { - - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || - !jQuery.contains( elem.ownerDocument, elem ); - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, - maxIterations = 20, - currentValue = tween ? - function() { return tween.cur(); } : - function() { return jQuery.css( elem, prop, "" ); }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - initialInUnit = initialInUnit / scale; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([\w:-]+)/ ); - -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE9 - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
      " ], - col: [ 2, "", "
      " ], - tr: [ 2, "", "
      " ], - td: [ 3, "", "
      " ], - - _default: [ 0, "", "" ] -}; - -// Support: IE9 -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE9-11+ - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== "undefined" ? - context.querySelectorAll( tag || "*" ) : - []; - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], ret ) : - ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0-4.3, Safari<=5.1 - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Safari<=5.1, Android<4.2 - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE<=11+ - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE9 -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, matches, sel, handleObj, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Support (at least): Chrome, IE9 - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // - // Support: Firefox<=42+ - // Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343) - if ( delegateCount && cur.nodeType && - ( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push( { elem: cur, handlers: matches } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " + - "metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split( " " ), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: ( "button buttons clientX clientY offsetX offsetY pageX pageY " + - "screenX screenY toElement" ).split( " " ), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + - ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + - ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: Cordova 2.5 (WebKit) (#13255) - // All events should have a target; Cordova deviceready doesn't - if ( !event.target ) { - event.target = document; - } - - // Support: Safari 6.0+, Chrome<28 - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android<4.0 - src.returnValue === false ? - returnTrue : - returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://code.google.com/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, - - // Support: IE 10-11, Edge 10240+ - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -function manipulationTarget( elem, content ) { - if ( jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <= 35-45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <= 35-45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - - // Keep domManip exposed until 3.0 (gh-2225) - domManip: domManip, - - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: QtWebKit - // .get() because push.apply(_, arraylike) throws - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); - - -var iframe, - elemdisplay = { - - // Support: Firefox - // We have to pre-define these values for FF (#10227) - HTML: "block", - BODY: "block" - }; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ - -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - display = jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = ( iframe || jQuery( "