├── .gitignore ├── .hg ├── 00changelog.i ├── cur-message.txt ├── hgrc ├── requires └── thgstatus ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── config.rb ├── font-selection.json ├── source ├── fonts │ ├── lato │ │ ├── lato-bol-webfont-g.eot │ │ ├── lato-bol-webfont-g.ttf │ │ ├── lato-bol-webfont-g.woff │ │ ├── lato-bol-webfont.svg │ │ ├── lato-bolita-webfont-g.eot │ │ ├── lato-bolita-webfont-g.ttf │ │ ├── lato-bolita-webfont-g.woff │ │ ├── lato-bolita-webfont.svg │ │ ├── lato-lig-webfont-g.eot │ │ ├── lato-lig-webfont-g.ttf │ │ ├── lato-lig-webfont-g.woff │ │ ├── lato-lig-webfont.svg │ │ ├── lato-ligita-webfont-g.eot │ │ ├── lato-ligita-webfont-g.ttf │ │ ├── lato-ligita-webfont-g.woff │ │ ├── lato-ligita-webfont.svg │ │ ├── lato-reg-webfont-g.eot │ │ ├── lato-reg-webfont-g.ttf │ │ ├── lato-reg-webfont-g.woff │ │ ├── lato-reg-webfont.svg │ │ ├── lato-regita-webfont-g.eot │ │ ├── lato-regita-webfont-g.ttf │ │ ├── lato-regita-webfont-g.woff │ │ └── lato-regita-webfont.svg │ ├── slate.eot │ ├── slate.svg │ ├── slate.ttf │ ├── slate.woff │ └── slate.woff2 ├── images │ ├── logo.bak.png │ ├── logo.png │ └── navbar.png ├── includes │ └── _errors.md ├── index.md ├── javascripts │ ├── all.js │ ├── all_nosearch.js │ ├── app │ │ ├── _lang.js │ │ ├── _search.js │ │ └── _toc.js │ └── lib │ │ ├── _energize.js │ │ ├── _imagesloaded.min.js │ │ ├── _jquery.highlight.js │ │ ├── _jquery.tocify.js │ │ ├── _jquery_ui.js │ │ └── _lunr.js ├── layouts │ └── layout.erb └── stylesheets │ ├── _icon-font.scss │ ├── _normalize.css │ ├── _syntax.scss.erb │ ├── _variables.scss │ ├── print.css.scss │ └── screen.css.scss └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | coverage 6 | InstalledFiles 7 | lib/bundler/man 8 | pkg 9 | rdoc 10 | spec/reports 11 | test/tmp 12 | test/version_tmp 13 | tmp 14 | *.DS_STORE 15 | build/ 16 | .cache 17 | 18 | # YARD artifacts 19 | .yardoc 20 | _yardoc 21 | doc/ 22 | .idea/ -------------------------------------------------------------------------------- /.hg/00changelog.i: -------------------------------------------------------------------------------- 1 |  dummy changelog to prevent using the old repo layout -------------------------------------------------------------------------------- /.hg/cur-message.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/.hg/cur-message.txt -------------------------------------------------------------------------------- /.hg/hgrc: -------------------------------------------------------------------------------- 1 | # Generated by TortoiseHg 2 | -------------------------------------------------------------------------------- /.hg/requires: -------------------------------------------------------------------------------- 1 | dotencode 2 | fncache 3 | revlogv1 4 | store 5 | -------------------------------------------------------------------------------- /.hg/thgstatus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/.hg/thgstatus -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: ruby 4 | 5 | rvm: 6 | - 1.9.3 7 | - 2.0.0 8 | 9 | cache: bundler 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 1.2 4 | 5 | *June 20, 2015* 6 | 7 | **Fixes:** 8 | 9 | - Remove crash on invalid languages 10 | - Update Tocify to scroll to the highlighted header in the Table of Contents 11 | - Fix variable leak and update search algorithms 12 | - Update Python examples to be valid Python 13 | - Update gems 14 | - More misc. bugfixes of Javascript errors 15 | - Add Dockerfile 16 | - Remove unused gems 17 | - Optimize images, fonts, and generated asset files 18 | - Add chinese font support 19 | - Remove RedCarpet header ID patch 20 | - Update language tabs to not disturb existing query strings 21 | 22 | ## Version 1.1 23 | 24 | *July 27th, 2014* 25 | 26 | **Fixes:** 27 | 28 | - Finally, a fix for the redcarpet upgrade bug 29 | 30 | ## Version 1.0 31 | 32 | *July 2, 2014* 33 | 34 | [View Issues](https://github.com/tripit/slate/issues?milestone=1&state=closed) 35 | 36 | **Features:** 37 | 38 | - Responsive designs for phones and tablets 39 | - Started tagging versions 40 | 41 | **Fixes:** 42 | 43 | - Fixed 'unrecognized expression' error 44 | - Fixed #undefined hash bug 45 | - Fixed bug where the current language tab would be unselected 46 | - Fixed bug where tocify wouldn't highlight the current section while searching 47 | - Fixed bug where ids of header tags would have special characters that caused problems 48 | - Updated layout so that pages with disabled search wouldn't load search.js 49 | - Cleaned up Javascript 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Slate 2 | 3 | Thanks for contributing to Slate! A couple of quick guidelines for submitting PRs: 4 | 5 | - Please point your pull requests at the `dev` branch, and keep your commit messages clear and informative. 6 | - Please make sure your contributions work in the most recent version of Chrome, Firefox, and IE. 7 | - If you're implementing a new feature, even if it's relatively small, it's nice to open an issue before you start so that others know what you're working on and can help make sure you're on the right track. 8 | 9 | Thanks again! Happy coding. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | RUN apt-get update 4 | RUN apt-get install -yq ruby ruby-dev build-essential git 5 | RUN gem install --no-ri --no-rdoc bundler 6 | ADD Gemfile /app/Gemfile 7 | ADD Gemfile.lock /app/Gemfile.lock 8 | RUN cd /app; bundle install 9 | ADD . /app 10 | EXPOSE 4567 11 | WORKDIR /app 12 | CMD ["bundle", "exec", "middleman", "server"] 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Middleman 4 | gem 'middleman', '~>3.3.10' 5 | gem 'middleman-gh-pages', '~> 0.0.3' 6 | gem 'middleman-syntax', '~> 2.0.0' 7 | gem 'middleman-autoprefixer', '~> 2.4.4' 8 | gem 'rouge', '~> 1.9.0' 9 | gem 'redcarpet', '~> 3.3.3' 10 | 11 | gem 'rake', '~> 10.4.2' 12 | gem 'therubyracer', '~> 0.12.1', platforms: :ruby 13 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.1.11) 5 | i18n (~> 0.6, >= 0.6.9) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.1) 9 | tzinfo (~> 1.1) 10 | autoprefixer-rails (5.2.0.1) 11 | execjs 12 | json 13 | celluloid (0.16.0) 14 | timers (~> 4.0.0) 15 | chunky_png (1.3.4) 16 | coffee-script (2.4.1) 17 | coffee-script-source 18 | execjs 19 | coffee-script-source (1.9.1.1) 20 | compass (1.0.3) 21 | chunky_png (~> 1.2) 22 | compass-core (~> 1.0.2) 23 | compass-import-once (~> 1.0.5) 24 | rb-fsevent (>= 0.9.3) 25 | rb-inotify (>= 0.9) 26 | sass (>= 3.3.13, < 3.5) 27 | compass-core (1.0.3) 28 | multi_json (~> 1.0) 29 | sass (>= 3.3.0, < 3.5) 30 | compass-import-once (1.0.5) 31 | sass (>= 3.2, < 3.5) 32 | erubis (2.7.0) 33 | execjs (2.5.2) 34 | ffi (1.9.8) 35 | ffi (1.9.8-x86-mingw32) 36 | haml (4.0.6) 37 | tilt 38 | hike (1.2.3) 39 | hitimes (1.2.3) 40 | hitimes (1.2.3-x86-mingw32) 41 | hooks (0.4.0) 42 | uber (~> 0.0.4) 43 | i18n (0.7.0) 44 | json (1.8.3) 45 | kramdown (1.7.0) 46 | libv8 (3.16.14.7) 47 | listen (2.10.1) 48 | celluloid (~> 0.16.0) 49 | rb-fsevent (>= 0.9.3) 50 | rb-inotify (>= 0.9) 51 | middleman (3.3.12) 52 | coffee-script (~> 2.2) 53 | compass (>= 1.0.0, < 2.0.0) 54 | compass-import-once (= 1.0.5) 55 | execjs (~> 2.0) 56 | haml (>= 4.0.5) 57 | kramdown (~> 1.2) 58 | middleman-core (= 3.3.12) 59 | middleman-sprockets (>= 3.1.2) 60 | sass (>= 3.4.0, < 4.0) 61 | uglifier (~> 2.5) 62 | middleman-autoprefixer (2.4.4) 63 | autoprefixer-rails (~> 5.2.0) 64 | middleman-core (>= 3.3.3) 65 | middleman-core (3.3.12) 66 | activesupport (~> 4.1.0) 67 | bundler (~> 1.1) 68 | erubis 69 | hooks (~> 0.3) 70 | i18n (~> 0.7.0) 71 | listen (>= 2.7.9, < 3.0) 72 | padrino-helpers (~> 0.12.3) 73 | rack (>= 1.4.5, < 2.0) 74 | rack-test (~> 0.6.2) 75 | thor (>= 0.15.2, < 2.0) 76 | tilt (~> 1.4.1, < 2.0) 77 | middleman-gh-pages (0.0.3) 78 | rake (> 0.9.3) 79 | middleman-sprockets (3.4.2) 80 | middleman-core (>= 3.3) 81 | sprockets (~> 2.12.1) 82 | sprockets-helpers (~> 1.1.0) 83 | sprockets-sass (~> 1.3.0) 84 | middleman-syntax (2.0.0) 85 | middleman-core (~> 3.2) 86 | rouge (~> 1.0) 87 | minitest (5.7.0) 88 | multi_json (1.11.1) 89 | padrino-helpers (0.12.5) 90 | i18n (~> 0.6, >= 0.6.7) 91 | padrino-support (= 0.12.5) 92 | tilt (~> 1.4.1) 93 | padrino-support (0.12.5) 94 | activesupport (>= 3.1) 95 | rack (1.6.4) 96 | rack-test (0.6.3) 97 | rack (>= 1.0) 98 | rake (10.4.2) 99 | rb-fsevent (0.9.5) 100 | rb-inotify (0.9.5) 101 | ffi (>= 0.5.0) 102 | redcarpet (3.3.3) 103 | ref (1.0.5) 104 | rouge (1.9.0) 105 | sass (3.4.14) 106 | sprockets (2.12.3) 107 | hike (~> 1.2) 108 | multi_json (~> 1.0) 109 | rack (~> 1.0) 110 | tilt (~> 1.1, != 1.3.0) 111 | sprockets-helpers (1.1.0) 112 | sprockets (~> 2.0) 113 | sprockets-sass (1.3.1) 114 | sprockets (~> 2.0) 115 | tilt (~> 1.1) 116 | therubyracer (0.12.2) 117 | libv8 (~> 3.16.14.0) 118 | ref 119 | thor (0.19.1) 120 | thread_safe (0.3.5) 121 | tilt (1.4.1) 122 | timers (4.0.1) 123 | hitimes 124 | tzinfo (1.2.2) 125 | thread_safe (~> 0.1) 126 | uber (0.0.13) 127 | uglifier (2.7.1) 128 | execjs (>= 0.3.0) 129 | json (>= 1.8.0) 130 | 131 | PLATFORMS 132 | ruby 133 | x86-mingw32 134 | 135 | DEPENDENCIES 136 | middleman (~> 3.3.10) 137 | middleman-autoprefixer (~> 2.4.4) 138 | middleman-gh-pages (~> 0.0.3) 139 | middleman-syntax (~> 2.0.0) 140 | rake (~> 10.4.2) 141 | redcarpet (~> 3.3.3) 142 | rouge (~> 1.9.0) 143 | therubyracer (~> 0.12.1) 144 | 145 | BUNDLED WITH 146 | 1.10.6 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2013 Concur Technologies, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | not use this file except in compliance with the License. You may obtain 5 | a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations 13 | under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Slate 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/tripit/slate.svg?branch=master)](https://travis-ci.org/tripit/slate) [![Dependency Status](https://gemnasium.com/tripit/slate.png)](https://gemnasium.com/tripit/slate) 5 | 6 | Slate helps you create beautiful API documentation. Think of it as an intelligent, responsive documentation template for your API. 7 | 8 | Screenshot of Example Documentation created with Slate 9 | 10 | *The example above was created with Slate. Check it out at [tripit.github.io/slate](http://tripit.github.io/slate).* 11 | 12 | Features 13 | ------------ 14 | 15 | * **Clean, intuitive design** — with Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [Paypal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even print. 16 | 17 | * **Everything on a single page** — gone are the days where your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy. 18 | 19 | * **Slate is just Markdown** — when you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks! 20 | 21 | * **Write code samples in multiple languages** — if your API has bindings in multiple programming languages, you easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with Github Flavored Markdown! 22 | 23 | * **Out-of-the-box syntax highlighting** for [almost 60 languages](http://rouge.jayferd.us/demo), no configuration required. 24 | 25 | * **Automatic, smoothly scrolling table of contents** on the far left of the page. As you scroll, it displays your current position in the document. It's fast, too. We're using Slate at TripIt to build documentation for our new API, where our table of contents has over 180 entries. We've made sure that the performance remains excellent, even for larger documents. 26 | 27 | * **Let your users update your documentation for you** — by default, your Slate-generated documentation is hosted in a public Github repository. Not only does this mean you get free hosting for your docs with Github Pages, but it also makes it's simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to, you're welcome to not use Github and host your docs elsewhere! 28 | 29 | Getting starting with Slate is super easy! Simply fork this repository, and then follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](http://tripit.github.io/slate). 30 | 31 | 32 | 33 | Getting Started with Slate 34 | ------------------------------ 35 | 36 | ### Prerequisites 37 | 38 | You're going to need: 39 | 40 | - **Linux or OS X** — Windows may work, but is unsupported. 41 | - **Ruby, version 1.9.3 or newer** 42 | - **Bundler** — If Ruby is already installed, but the `bundle` command doesn't work, just run `gem install bundler` in a terminal. 43 | 44 | ### Getting Set Up 45 | 46 | 1. Fork this repository on Github. 47 | 2. Clone *your forked repository* (not our original one) to your hard drive with `git clone https://github.com/YOURUSERNAME/slate.git` 48 | 3. `cd slate` 49 | 4. Install all dependencies: `bundle install` 50 | 5. Start the test server: `bundle exec middleman server` 51 | 52 | Or use the included Dockerfile! (must install Docker first) 53 | 54 | ```shell 55 | docker build -t slate . 56 | docker run -d -p 4567:4567 slate 57 | ``` 58 | 59 | You can now see the docs at . Whoa! That was fast! 60 | 61 | *Note: if you're using the Docker setup on OSX, the docs will be 62 | availalable at the output of `boot2docker ip` instead of `localhost:4567`.* 63 | 64 | Now that Slate is all set up your machine, you'll probably want to learn more about [editing Slate markdown](https://github.com/tripit/slate/wiki/Markdown-Syntax), or [how to publish your docs](https://github.com/tripit/slate/wiki/Deploying-Slate). 65 | 66 | Examples of Slate in the Wild 67 | --------------------------------- 68 | 69 | * [Travis-CI's API docs](http://docs.travis-ci.com/api/) 70 | * [Mozilla's localForage docs](http://mozilla.github.io/localForage/) 71 | * [Mozilla Recroom](http://mozilla.github.io/recroom/) 72 | * [ChaiOne Gameplan API docs](http://chaione.github.io/gameplanb2b/#introduction) 73 | * [Drcaban's Build a Quine tutorial](http://drcabana.github.io/build-a-quine/#introduction) 74 | * [PricePlow API docs](https://www.priceplow.com/api/documentation) 75 | * [Emerging Threats API docs](http://apidocs.emergingthreats.net/) 76 | * [Appium docs](http://appium.io/slate/en/master) 77 | * [Golazon Developer](http://developer.golazon.com) 78 | * [Dwolla API docs](https://docs.dwolla.com/) 79 | * [RozpisyZapasu API docs](http://www.rozpisyzapasu.cz/dev/api/) 80 | * [Codestar Framework Docs](http://codestarframework.com/documentation/) 81 | * [Buddycloud API](http://buddycloud.com/api) 82 | * [Crafty Clicks API](https://craftyclicks.co.uk/api/) 83 | * [Paracel API Reference](http://paracel.io/docs/api_reference.html) 84 | * [Switch Payments Documentation](http://switchpayments.com/docs/) & [API](http://switchpayments.com/developers/) 85 | * [Coinbase API Reference](https://developers.coinbase.com/api) 86 | * [Whispir.io API](https://whispir.github.io/api) 87 | * [NASA API](https://data.nasa.gov/developer/external/planetary/) 88 | * [CardPay API](https://developers.cardpay.com/) 89 | * [IBM Cloudant](https://docs-testb.cloudant.com/content-review/_design/couchapp/index.html) 90 | * [Bitrix basis components](http://bbc.bitrix.expert/) 91 | * [viagogo API Documentation](http://developer.viagogo.net/) 92 | * [Fidor Bank API Documentation](http://docs.fidor.de/) 93 | * [Market Prophit API Documentation](http://developer.marketprophit.com/) 94 | * [OAuth.io API Documentation](http://docs.oauth.io/) 95 | * [Aircall for Developers](http://developer.aircall.io/) 96 | * [SupportKit API Docs](http://docs.supportkit.io/) 97 | * [SocialRadar's LocationKit Docs](https://docs.locationkit.io/) 98 | * [SafetyCulture API Documentation](https://developer.safetyculture.io/) 99 | 100 | (Feel free to add your site to this list in a pull request!) 101 | 102 | Need Help? Found a bug? 103 | -------------------- 104 | 105 | Just [submit a issue](https://github.com/tripit/slate/issues) to the Slate Github if you need any help. And, of course, feel free to submit pull requests with bug fixes or changes. 106 | 107 | 108 | Contributors 109 | -------------------- 110 | 111 | Slate was built by [Robert Lord](https://lord.io) while at [TripIt](http://tripit.com). 112 | 113 | Thanks to the following people who have submitted major pull requests: 114 | 115 | - [@chrissrogers](https://github.com/chrissrogers) 116 | - [@bootstraponline](https://github.com/bootstraponline) 117 | - [@realityking](https://github.com/realityking) 118 | 119 | Also, thanks to [Sauce Labs](http://saucelabs.com) for helping sponsor the project. 120 | 121 | Special Thanks 122 | -------------------- 123 | - [Middleman](https://github.com/middleman/middleman) 124 | - [jquery.tocify.js](https://github.com/gfranko/jquery.tocify.js) 125 | - [middleman-syntax](https://github.com/middleman/middleman-syntax) 126 | - [middleman-gh-pages](https://github.com/neo/middleman-gh-pages) 127 | - [Font Awesome](http://fortawesome.github.io/Font-Awesome/) 128 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'middleman-gh-pages' 2 | require 'rake/clean' 3 | 4 | CLOBBER.include('build') 5 | 6 | task :default => [:build] 7 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | # Markdown 2 | # set :markdown_engine, :redcarpet 3 | set :markdown_engine, :kramdown 4 | set :markdown, 5 | fenced_code_blocks: true, 6 | smartypants: true, 7 | disable_indented_code_blocks: true, 8 | prettify: true, 9 | tables: true, 10 | with_toc_data: true, 11 | no_intra_emphasis: true 12 | 13 | # Assets 14 | set :css_dir, 'stylesheets' 15 | set :js_dir, 'javascripts' 16 | set :images_dir, 'images' 17 | set :fonts_dir, 'fonts' 18 | 19 | # Activate the syntax highlighter 20 | activate :syntax 21 | 22 | activate :autoprefixer do |config| 23 | config.browsers = ['last 2 version', 'Firefox ESR'] 24 | config.cascade = false 25 | config.inline = true 26 | end 27 | 28 | # Github pages require relative links 29 | activate :relative_assets 30 | set :relative_links, true 31 | 32 | # Build Configuration 33 | configure :build do 34 | activate :minify_css 35 | activate :minify_javascript 36 | # activate :relative_assets 37 | # activate :asset_hash 38 | # activate :gzip 39 | end 40 | -------------------------------------------------------------------------------- /font-selection.json: -------------------------------------------------------------------------------- 1 | { 2 | "IcoMoonType": "selection", 3 | "icons": [ 4 | { 5 | "icon": { 6 | "paths": [ 7 | "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" 8 | ], 9 | "attrs": [], 10 | "isMulticolor": false, 11 | "tags": [ 12 | "exclamation-circle" 13 | ], 14 | "defaultCode": 61546, 15 | "grid": 14 16 | }, 17 | "attrs": [], 18 | "properties": { 19 | "id": 100, 20 | "order": 4, 21 | "prevSize": 28, 22 | "code": 58880, 23 | "name": "exclamation-sign", 24 | "ligatures": "" 25 | }, 26 | "setIdx": 0, 27 | "iconIdx": 0 28 | }, 29 | { 30 | "icon": { 31 | "paths": [ 32 | "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" 33 | ], 34 | "attrs": [], 35 | "isMulticolor": false, 36 | "tags": [ 37 | "info-circle" 38 | ], 39 | "defaultCode": 61530, 40 | "grid": 14 41 | }, 42 | "attrs": [], 43 | "properties": { 44 | "id": 85, 45 | "order": 3, 46 | "name": "info-sign", 47 | "prevSize": 28, 48 | "code": 58882 49 | }, 50 | "setIdx": 0, 51 | "iconIdx": 2 52 | }, 53 | { 54 | "icon": { 55 | "paths": [ 56 | "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" 57 | ], 58 | "attrs": [], 59 | "isMulticolor": false, 60 | "tags": [ 61 | "check-circle" 62 | ], 63 | "defaultCode": 61528, 64 | "grid": 14 65 | }, 66 | "attrs": [], 67 | "properties": { 68 | "id": 83, 69 | "order": 9, 70 | "prevSize": 28, 71 | "code": 58886, 72 | "name": "ok-sign" 73 | }, 74 | "setIdx": 0, 75 | "iconIdx": 6 76 | }, 77 | { 78 | "icon": { 79 | "paths": [ 80 | "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" 81 | ], 82 | "width": 951, 83 | "attrs": [], 84 | "isMulticolor": false, 85 | "tags": [ 86 | "search" 87 | ], 88 | "defaultCode": 61442, 89 | "grid": 14 90 | }, 91 | "attrs": [], 92 | "properties": { 93 | "id": 2, 94 | "order": 1, 95 | "prevSize": 28, 96 | "code": 58887, 97 | "name": "icon-search" 98 | }, 99 | "setIdx": 0, 100 | "iconIdx": 7 101 | } 102 | ], 103 | "height": 1024, 104 | "metadata": { 105 | "name": "slate", 106 | "license": "SIL OFL 1.1" 107 | }, 108 | "preferences": { 109 | "showGlyphs": true, 110 | "showQuickUse": true, 111 | "showQuickUse2": true, 112 | "showSVGs": true, 113 | "fontPref": { 114 | "prefix": "icon-", 115 | "metadata": { 116 | "fontFamily": "slate", 117 | "majorVersion": 1, 118 | "minorVersion": 0, 119 | "description": "Based on FontAwesome", 120 | "license": "SIL OFL 1.1" 121 | }, 122 | "metrics": { 123 | "emSize": 1024, 124 | "baseline": 6.25, 125 | "whitespace": 50 126 | }, 127 | "resetPoint": 58880, 128 | "showSelector": false, 129 | "selector": "class", 130 | "classSelector": ".icon", 131 | "showMetrics": false, 132 | "showMetadata": true, 133 | "showVersion": true, 134 | "ie7": false 135 | }, 136 | "imagePref": { 137 | "prefix": "icon-", 138 | "png": true, 139 | "useClassSelector": true, 140 | "color": 4473924, 141 | "bgColor": 16777215 142 | }, 143 | "historySize": 100, 144 | "showCodes": true, 145 | "gridSize": 16, 146 | "showLiga": false 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /source/fonts/lato/lato-bol-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bol-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-bol-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bol-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-bol-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bol-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/lato/lato-bolita-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bolita-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-bolita-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bolita-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-bolita-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-bolita-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/lato/lato-lig-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-lig-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-lig-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-lig-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-lig-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-lig-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/lato/lato-ligita-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-ligita-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-ligita-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-ligita-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-ligita-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-ligita-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/lato/lato-reg-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-reg-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-reg-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-reg-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-reg-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-reg-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/lato/lato-regita-webfont-g.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-regita-webfont-g.eot -------------------------------------------------------------------------------- /source/fonts/lato/lato-regita-webfont-g.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-regita-webfont-g.ttf -------------------------------------------------------------------------------- /source/fonts/lato/lato-regita-webfont-g.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/lato/lato-regita-webfont-g.woff -------------------------------------------------------------------------------- /source/fonts/slate.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/slate.eot -------------------------------------------------------------------------------- /source/fonts/slate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /source/fonts/slate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/slate.ttf -------------------------------------------------------------------------------- /source/fonts/slate.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/slate.woff -------------------------------------------------------------------------------- /source/fonts/slate.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/fonts/slate.woff2 -------------------------------------------------------------------------------- /source/images/logo.bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/images/logo.bak.png -------------------------------------------------------------------------------- /source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/images/logo.png -------------------------------------------------------------------------------- /source/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ringcentral/slate/abf434f7ddabbd18b41cf91dda7f7476cf58e3b6/source/images/navbar.png -------------------------------------------------------------------------------- /source/includes/_errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | The RingCentral for Developers API uses the following error codes: 4 | 5 | 6 | Error Code | Meaning 7 | ---------- | ------- 8 | 400 | Bad Request -- Your request is incorrectly formatted 9 | 401 | Unauthorized -- Your API key is wrong 10 | 403 | Forbidden -- The resource requested is hidden for administrators only 11 | 404 | Not Found -- The specified resource could not be found 12 | 405 | Method Not Allowed -- You tried to access a resource with an invalid method 13 | 410 | Gone -- The resource requested has been removed from our servers 14 | 429 | Too Many Requests -- You're making too many requests! Slown down! 15 | 500 | Internal Server Error -- We had a problem with our server. Try again later. 16 | 503 | Service Unavailable -- We're temporarially offline for maintanance. Please try again later. -------------------------------------------------------------------------------- /source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RingCentral API Reference 3 | 4 | language_tabs: 5 | - http 6 | - javascript 7 | 8 | toc_footers: 9 | - Sign Up for RingCentral for Developers 10 | - Documentation Powered by Slate 11 | 12 | includes: 13 | - errors 14 | 15 | search: true 16 | --- 17 | 18 | # Introduction 19 | 20 | Welcome to the RingCentral CTI Developer Tutorial. This tutorial uses the [RingCentral JavaScript SDK](http://github.com/ringcentral/js-sdk). 21 | 22 | Call management integration typically includes monitoring of incoming calls, listing calls and performing of RingOuts, all of which are covered in this tutorial. 23 | 24 | # Quickstart 25 | 26 | ## Retrieve Call Logs 27 | 28 | ~~~ http 29 | GET https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~/call-log HTTP/1.1 30 | Authorization: Bearer U0pDMDFQMDFQQVMwMnxBQUFWZmY4ZXoxMlh 31 | Accept: application/json 32 | ~~~ 33 | 34 | ~~~ 35 | HTTP/1.1 200 OK 36 | Content-Language: en 37 | Content-Type: application/json; charset=UTF-8 38 | Content-Length: 4145 39 | 40 | { 41 | "uri": ".../account/401501489004/extension/401501489004/call-log?dateFrom=2013-01-01T00:00:00.000Z&page=1&perPage=100", 42 | "records": [ 43 | { 44 | "uri": ".../account/401501489004/extension/401501489004/call-log/IXtVRs-3B7A_PfE", 45 | "id": "IXtVRs-3B7A_PfE", 46 | "sessionId": "404769762004", 47 | "startTime": "2014-09-22T12:58:09.000Z", 48 | "duration": 4, 49 | "type": "Voice", 50 | "direction": "Inbound", 51 | "action": "Phone Call", 52 | "result": "Accepted", 53 | "to": { 54 | "phoneNumber": "18662130006", 55 | "name": "Rose White" 56 | }, 57 | "from": { 58 | "phoneNumber": "16504445567", 59 | "name": "Phillip Marco", 60 | "location": "Palo Alto, CA" 61 | } 62 | }, 63 | { 64 | "uri": ".../account/401501489004/extension/401501489004/call-log/IXtVPRjQvSF5Pe4", 65 | "id": "IXtVPRjQvSF5Pe4", 66 | "sessionId": "404769755004", 67 | "startTime": "2014-09-22T12:58:06.000Z", 68 | "duration": 10, 69 | "type": "Voice", 70 | "direction": "Outbound", 71 | "action": "VoIP Call", 72 | "result": "Call connected", 73 | "to": { 74 | "phoneNumber": "16504445567", 75 | "location": "Palo Alto, CA" 76 | }, 77 | "from": { 78 | "phoneNumber": "16504445567", 79 | "name": "Irene Fox" 80 | } 81 | }, 82 | { 83 | ... 84 | } 85 | ], 86 | "paging": { 87 | "page": 1, 88 | "perPage": 100, 89 | "pageStart": 0, 90 | "pageEnd": 6 91 | }, 92 | "navigation": { 93 | "firstPage": { 94 | "uri": ".../account/401501489004/extension/401501489004/call-log?dateFrom=2013-01-01T00:00:00.000Z&page=1&perPage=100" 95 | } 96 | } 97 | } 98 | ~~~ 99 | 100 | To access a Call Log resource you will require this URI: 101 | 102 | `GET /restapi/v1.0/account/{accountId}/extension/{extensionId}/call-log` 103 | 104 | Send the request having specified particular accountId and extensionId or using simplified syntax with tilde characters. Please note, that if dateFrom parameter is not specified, server will return records for last week only. 105 | 106 | ## Retrieve Filtered Call Logs 107 | 108 | ~~~ http 109 | GET https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~/call-log?dateFrom=2014-09-18&phoneNumber=16504445567&dateTo=2014-09-25&type=Voice HTTP/1.1 110 | Authorization: Bearer U0pDMDFQMDFQQVMwMnxBQUFWZmY4ZXoxMl 111 | Accept: application/json 112 | ~~~ 113 | 114 | ~~~ 115 | HTTP/1.1 200 OK 116 | Content-Language: en 117 | Content-Type: application/json; charset=UTF-8 118 | Content-Length: 1725 119 | 120 | { 121 | "uri": ".../account/401501489004/extension/401501489004/call-log?phoneNumber=16504445567&type=Voice&dateFrom=2014-09-18T00:00:00.000Z&dateTo=2014-09-25T00:00:00.000Z&page=1&perPage=100", 122 | "records": [ 123 | { 124 | "uri": ".../account/401501489004/extension/401501489004/call-log/IXtVRs-3B7A_PfE", 125 | "id": "IXtVRs-3B7A_PfE", 126 | "sessionId": "404769762004", 127 | "startTime": "2014-09-22T12:58:09.000Z", 128 | "duration": 4, 129 | "type": "Voice", 130 | "direction": "Inbound", 131 | "action": "Phone Call", 132 | "result": "Accepted", 133 | "to": { 134 | "phoneNumber": "18662130006", 135 | "name": "Steve Jones" 136 | }, 137 | "from": { 138 | "phoneNumber": "16504445567", 139 | "name": "Tony Black", 140 | "location": "Palo Alto, CA" 141 | } 142 | }, 143 | { 144 | "uri": ".../account/401501489004/extension/401501489004/call-log/IXtVPRjQvSF5Pe4", 145 | "id": "IXtVPRjQvSF5Pe4", 146 | "sessionId": "404769755004", 147 | "startTime": "2014-09-22T12:58:06.000Z", 148 | "duration": 10, 149 | "type": "Voice", 150 | "direction": "Outbound", 151 | "action": "VoIP Call", 152 | "result": "Call connected", 153 | "to": { 154 | "phoneNumber": "16504445567", 155 | "location": "Palo Alto, CA" 156 | }, 157 | "from": { 158 | "phoneNumber": "18662130006", 159 | "name": "Steve Jones" 160 | } 161 | } 162 | ], 163 | "paging": { 164 | "page": 1, 165 | "perPage": 100, 166 | "pageStart": 0, 167 | "pageEnd": 1 168 | }, 169 | "navigation": { 170 | "firstPage": { 171 | "uri": ".../account/401501489004/extension/401501489004/call-log?phoneNumber=16504445567&type=Voice&dateFrom=2014-09-18T00:00:00.000Z&dateTo=2014-09-25T00:00:00.000Z&page=1&perPage=100" 172 | } 173 | } 174 | } 175 | ~~~ 176 | 177 | You may get exactly the records you require by setting a few filters. For example if you need the records over a week (dateFrom, dateTo) of voice calls (type) to/from a certain number (phoneNumber), sned the request having set the following query parameters: 178 | 179 | * `dateFrom` = 2014-09-18 180 | * `dateTo` = 2014-09-25 181 | * `type` = Voice 182 | * `phoneNumber` = 16504445567 183 | 184 | ## Send SMS Message 185 | 186 | ~~~ http 187 | POST https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~/sms HTTP/1.1 188 | Authorization: Bearer U0pDMDFQMDFQQVMwMXxBQUFWZmY4ZXoxMlhvUFI5dmhYVzV 189 | Content-Type: application/json 190 | Accept: application/json 191 | Content-Length: 323 192 | 193 | { 194 | "to": [{"phoneNumber": "+18662130006"}], 195 | "from": {"phoneNumber": "+16504445567"}, 196 | "text": "Test SMS message from Platform server" 197 | } 198 | ~~~ 199 | 200 | ~~~ 201 | HTTP/1.1 200 OK 202 | Content-Language: en 203 | Content-Type: application/json; charset=UTF-8 204 | Content-Length: 926 205 | 206 | { 207 | "uri": ".../account/401474882008/extension/401474882008/message-store/402206716008", 208 | "id": 402206716008, 209 | "to": [ 210 | { 211 | "phoneNumber": "+16505393204", 212 | "location": "San Mateo, CA" 213 | } 214 | ], 215 | "from": { 216 | "phoneNumber": "+18004900003" 217 | }, 218 | "type": "SMS", 219 | "creationTime": "2014-09-24T07:04:26.000Z", 220 | "readStatus": "Read", 221 | "priority": "Normal", 222 | "attachments": [ 223 | { 224 | "id": 402206716008, 225 | "uri": ".../account/401474882008/extension/401474882008/message-store/402206716008/content/402206716008", 226 | "type": "Text", 227 | "contentType": "text/plain" 228 | } 229 | ], 230 | "direction": "Outbound", 231 | "availability": "Alive", 232 | "subject": "Test SMS message from Platform server", 233 | "messageStatus": "Sent", 234 | "smsSendingAttemptsCount": 1, 235 | "conversationId": 5578972751633835268, 236 | "lastModifiedTime": "2014-09-24T07:04:26.534Z" 237 | } 238 | ~~~ 239 | 240 | If you need to send a text message to somebody, you should use this URI: 241 | 242 | `POST /restapi/v1.0/account/{accountId}/extension/{extensionId}/sms` 243 | 244 | Send `POST` request: Specify `accountId` and `extensionId` in the request URL or use simplified syntax with tilde characters. Specify the recipient phone number in the `to` field and one of your valid RingCentral phone numbers in the `from` field. Enter your message in the `text` field. 245 | 246 | ## Send Pager Message 247 | 248 | ~~~ http 249 | POST /restapi/v1.0/account/~/extension/~/company-pager HTTP/1.1 250 | Content-Type: application/json 251 | Content-Length: ACTUAL_CONTENT_LENGTH_HERE 252 | 253 | { 254 | "to": [{"extensionNumber": "102"}, 255 | {"extensionNumber": "103"}], 256 | 257 | "from": {"extensionNumber": "101"}, 258 | "text": "Hello!" 259 | } 260 | 261 | HTTP/1.1 200 OK 262 | Content-Type: application/json 263 | 264 | { 265 | "uri": ".../account/1346632010/extension/1346632010/message-store/320272670010", 266 | "id": 320272670010, 267 | "to": [ 268 | {"extensionNumber": "101"}, 269 | {"extensionNumber": "102"}, 270 | {"extensionNumber": "103"} 271 | ], 272 | "from": {"extensionNumber": "101"}, 273 | "type": "Pager", 274 | "creationTime": "2012-10-18T13:18:24.000Z", 275 | "readStatus": "Unread", 276 | "priority": "Normal", 277 | "attachments": [ { 278 | "id": 1, 279 | "uri": "http:.../restapi/v1.0/account/1346632010/extension/1346632010/message-store/320272670010/content/1", 280 | "contentType": "text/plain" 281 | }], 282 | "direction": "Outbound", 283 | "availability": "Alive", 284 | "subject": "Hello!", 285 | "messageStatus": "Sent", 286 | "conversationId": 320272670010, 287 | "lastModifiedTime": "2012-10-18T13:18:24.000Z", 288 | "pgToDepartment": false 289 | } 290 | ~~~ 291 | 292 | Pager messages are RingCentral specific types of text messages which can be sent between extensions of one account. Unlike SMS, pager messages can be sent to multiple recipients, so the API allows several extension numbers in the **to** field. Another difference from SMS is that the pager message that is sent to the department extension is automatically forwarded to all department members. This allows setting up dedicated mailing lists within the organization. The endpoint **company-pager** is designed to handle pager messages. 293 | 294 | # Getting Started 295 | 296 | ## Installing the SDK 297 | 298 | To install the SDK, follow the online instructions posted at Github: 299 | 300 | [https://github.com/ringcentral/js-sdk#installation](https://github.com/ringcentral/js-sdk#installation) 301 | 302 | ## Instantiating the SDK 303 | 304 | ~~~ javascript 305 | var RC_SERVER_PRODUCTION = 'https://platform.ringcentral.com'; 306 | var RC_SERVER_SANDBOX = 'https://platform.devtest.ringcentral.com'; 307 | 308 | var rcsdk = new RCSDK({ 309 | server: RC_SERVER_SANDBOX, 310 | appKey: 'yourAppKey', 311 | appSecret: 'yourAppSecret' 312 | }); 313 | ~~~ 314 | 315 | The SDK is represented by the global RCSDK constructor. Your application must create an instance of this object. 316 | 317 | In order to bootstrap the RingCentral JavaScript SDK, you have to first get a reference to the Platform singleton and then configure it. Before you can do anything using the Platform singleton, you need to configure it with the server URL (this tells the SDK which server to connect to) and your unique API key (this is provided by RingCentral's developer relations team). 318 | 319 | 322 | 323 | ## Getting the Platform Singleton 324 | 325 | ~~~ javascript 326 | var platform = rcsdk.getPlatform(); 327 | ~~~ 328 | 329 | Now that you have your platform singleton and SDK has been configured with the correct server URL and API key, your application can log in so that it can access the features of the API. 330 | 331 | ## Login via 3-legged OAuth 332 | 333 | ~~~ javascript 334 | // Get user authorization URL 335 | var myRedirectUri = 'https://example.com/oauth'; 336 | var authorizeUrl = rcsdk.getPlatform().getAuthURL({ 337 | redirectUri: myRedirectUri 338 | }); 339 | // Open window for authorizeUrl 340 | 341 | // In redirect URL, retrieve `code` from query string and exhange for access token. 342 | // Get query string 343 | var myRedirectUri = 'https://example.com/oauth'; 344 | var qs = rcsdk.getPlatform().parseAuthRedirectUrl(window.location.href); 345 | qs.redirectUri = myRedirectUri; 346 | 347 | if ('code' in qs) { 348 | var res = rcsdk.getPlatform().authorize(qs) 349 | .then(function(response) { 350 | // process response and close window (if popup) 351 | window.open('', '_self', ''); 352 | window.close(); 353 | }).catch(function(e) { 354 | console.log("Error: Authorization") 355 | }); 356 | } else { 357 | console.log("Error: No Code") 358 | } 359 | ~~~ 360 | 361 | 3-legged OAuth is the standard login approach for user applications via the web where the user will be presented with a standard RingCentral login. This approach also supports RingCentral customers that have deployed single sign-on (SSO) via 3-rd party identity providers (IdPs). To implement 3-legged OAuth, implement the following steps: 362 | 363 | 1. Configure a redirect URI for your service in the RingCentral Developer portal which will be used to send the authorization code upon success login and authorization. 364 | 2. Then use the redirect URI with the SDK to retrieve an OAuth authorization URL that can be used to open a browser window. 365 | 3. Finally, in the web page at your redirect URI, extract the authoriation code from the URL string's `code` query parameter and exchange the authorization code for an access token. 366 | 367 | ## Login via 2-legged OAuth 368 | 369 | ~~~ javascript 370 | platform.authorize({ 371 | username: '+18001234567', // your phone number in E.164 format 372 | extension: '101', // leave blank if direct number is used 373 | password: 'yourpassword' 374 | }).then(function(ajax) { 375 | // your code here 376 | }).catch(function(e) { 377 | alert(e.message || 'Server cannot authorize user'); 378 | }); 379 | ~~~ 380 | 381 | Client-server applications can use the 2-legged OAuth approach which doesn't provide a user login page. 382 | 383 | To log in to RingCentral, get the Platform object and call its authorize method, providing valid username, extension, and password values. Enter your phone number in E.164 format for username. The `+` may be omitted. 384 | 385 | A Promise is returned, and you can use its then method to specify your continuation function, and its catch method to specify an error handling function. 386 | 387 | ## Handling Authorization Exceptions 388 | 389 | ~~~ javascript 390 | platform.on(platform.events.accessViolation, function(e){ 391 | // do something 392 | }); 393 | ~~~ 394 | 395 | To handle possible access or authentication exceptions that may occur while the application is running (after the user has successfully logged in), you can provide a handler for the `accessViolation` platform event. 396 | 397 | 400 | 401 | ## Determining Authorization Status 402 | 403 | ~~~ javascript 404 | // To check authentication status: 405 | platform.isAuthorized() 406 | .then(function(){ ... }) 407 | .catch(function(e){ ... }); 408 | 409 | // Checking authn status synchronously 410 | // without auto-refresh of the access token 411 | if (platform.isTokenValid()) {...} 412 | ~~~ 413 | 414 | The `isAuthorized` method will automatically perform a refresh of the access token, if needed. This method may be used in the login page of your application for automatic login. 415 | 416 | There is also a synchronous method for checking the authentication status that does not automatically perform a refresh of the access token. 417 | 418 | ## Manual Access Token Refresh 419 | 420 | ~~~ javascript 421 | // Refreshing the access token manually 422 | platform.refresh().then(...) 423 | ~~~ 424 | 425 | Access token refresh normally happens automatically for common use cases. On rare occasions, you may perform a refresh of the access token manually by calling the refresh method using the `platform.refresh()` method. 426 | 427 | ## Logout 428 | 429 | ~~~ javascript 430 | // without callback 431 | platform.logout() 432 | // with callback 433 | platform.logout().then(...) 434 | ~~~ 435 | 436 | Your application can log out the user by calling the `platform.logout()` method. 437 | 438 | # Making Calls (RingOut) 439 | 440 | Outbound calls using RingCentral can be made using the RingOut functionality. 441 | 442 | ## Two-Legged Calls 443 | 444 | When making a call, the RingCentral system establishes two calls, one for each of the two parties being connected, and then connects them. This results in events for two calls (2-legged calls) when initiated a single click-to-call? 445 | 446 | ## Making an Outbound Call 447 | 448 | ~~~ javascript 449 | // Phone numbers should be in E.164 format. 450 | platform 451 | .apiCall(rcsdk.getRingoutHelper().saveRequest({ 452 | from: {phoneNumber: '+16501111111'}, 453 | to: {phoneNumber: '+18882222222'}, 454 | callerId: {phoneNumber: '+18882222222'}, // optional, 455 | playPrompt: false // optional 456 | })) 457 | .then(function(ajax) { 458 | // here application can start polling 459 | // also save ajax.data as, for example, prevRingoutData 460 | }) 461 | .catch(handleError); 462 | ~~~ 463 | 464 | The application should stop polling the RingOut when its status changes to error or success because after that there will be no status updates. 465 | 466 | ## Polling Outbound Call Status 467 | 468 | ~~~ javascript 469 | // Poll for the status of an ongoing outbound call 470 | function update(next, delay) { 471 | 472 | if (!rcsdk.getRingoutHelper().isInProgress(ringout)) return; 473 | 474 | platform 475 | .apiCall(rcsdk.getRingoutHelper().loadRequest(prevRingoutData)) 476 | .then(function(ajax) { 477 | // also save ajax.data as, for example, prevRingoutData 478 | console.log(ajax.data); // updated status of ringout 479 | timeout = next(delay); // you can increase delay here 480 | }) 481 | .catch(handleError); 482 | 483 | } 484 | 485 | var timeout = rcsdk.getUtils().poll(update, 3000); // stay in RPS limits 486 | 487 | // To stop polling: 488 | 489 | rcsdk.getUtils().stopPolling(timeout); 490 | ~~~ 491 | 492 | Use polling to get the status of an ongoing outbound call. 493 | 494 | ## Outbound Call Control 495 | 496 | The RingCentral Connect Platform does not currently support control of outbound calls. However, you can cancel ringout call while callee party status is `InProgress`. To do that make a `DELETE` request to ringout URI. 497 | 498 | ## Outbound Call Statuses 499 | 500 | * A 2-legged RingOut call is represented in events as an outbound call between `from` and `to` numbers provided in RingOut API request. 501 | * There is the known issue: notification with `CallConnected` status comes after first leg is connected. So actually a call can be missed by callee but it won't be reflected in event flow; but it will be reflected in call log. 502 | * Phone numbers in notification (`from` and `to`) may be either E.164 phone numbers (with or without `+`) or short extension numbers (e.g. '101') for calls between extensions 503 | * For some RC phone system configurations when multiple devices are ringing for inbound call, you may get transitional notifications with `NoCall` status which will be immediately followed by `Ringing` or `CallConnected` (for the same `sessionId`). 504 | 505 | ## Polling & Events Notification 506 | 507 | All RingOut calls will appear in event notifications and active calls endpoint. The difference between what RingOut polling provides is more granular status updates (application can track status of both parties). Normally it should be represented by two independent views/flows in application. In general there is no point to match ringout calls with any of active calls, those process may happen concurrently. 508 | 509 | If the application needs to track outbound calls and save them somewhere, it is better to initiate the ringout and NOT poll it, but expect a notification and work only with notifications/active calls. 510 | 511 | # Making Calls (URI Scheme) 512 | 513 | In addition to making calls via the RingOut API, if the user has the RingCentral for Desktop softphone installed, it is possible to use a URI scheme to initiate a dial out from the application. 514 | 515 | RingCentral supports both a custom `rcmobile` URI scheme will resolve the issue of competing applications using the same URI scheme and a standard `tel` URI scheme which is more common but subject to competing uses. 516 | 517 | ## RingCentral URI Scheme 518 | 519 | ~~~ 520 | // HTML URI Scheme 521 | 1-650-111-2222 522 | ~~~ 523 | 524 | ~~~ javascript 525 | // Use the following for Google Chrome only 526 | var w = (window.parent)?window.parent:window; 527 | w.location.assign('rcmobile://call?number=16501112222'); 528 | // For more info, see http://stackoverflow.com/questions/2330545/ 529 | ~~~ 530 | 531 | The RingCentral `rcmobile` URI Scheme is specific to RingCentral and thus has a higher probability of workign as intended. 532 | 533 | ## Standard URI Scheme 534 | 535 | ~~~ 536 | // HTML URI Scheme 537 | 1-650-111-2222 538 | 1-650-111-2222 539 | ~~~ 540 | 541 | The standard `tel` URI Scheme is also supported but since multiple applications use this URI scheme, there may be competing applications resulting in a less desirable expeirence. 542 | 543 | # Call Management 544 | 545 | If you are integrating with a CRM or ERP system, use of the JavaScript SDK is highly recommended. Following is an example of a call management integration that includes monitoring of incoming calls and performing of RingOuts. 546 | 547 | A call management integration usually consists of the following tasks: 548 | 549 | * Track the telephony status 550 | * View the list of active calls 551 | * View the recent calls 552 | 553 | ## Call States 554 | 555 | ~~~ javascript 556 | 557 | // 1) Missed inbound call (Ringing -> No Call) 558 | { 559 | "body": { 560 | "telephonyStatus": "Ringing", 561 | "extensionId": 607457016, 562 | "activeCalls": [{"direction": "Inbound", "from": "13027430863", "telephonyStatus": "Ringing", "to": "18885434778", "sessionId": "67029198021", "id": "8ad4d5ea165f4e2aa00474a666368728"}], 563 | "sequence": 133702 564 | }, 565 | "timestamp": "2015-04-10T18:48:55.133+0000", 566 | "uuid": "2b5bb8c0-c36c-46af-bd93-fb41e330d87c", 567 | "event": "/restapi/v1.0/account/~/extension/607457016/presence?detailedTelephonyState=true" 568 | } 569 | 570 | { 571 | "body": { 572 | "telephonyStatus": "NoCall", 573 | "extensionId": 607457016, 574 | "activeCalls": [{"direction": "Inbound", "from": "13027430863", "telephonyStatus": "NoCall", "to": "18885434778", "sessionId": "67029198021", "id": "8ad4d5ea165f4e2aa00474a666368728"}], 575 | "sequence": 133857 576 | }, 577 | "timestamp": "2015-04-10T18:49:14.679+0000", 578 | "uuid": "a5a876ab-3356-41e5-b1fe-fff91da3a281", 579 | "event": "/restapi/v1.0/account/~/extension/607457016/presence?detailedTelephonyState=true" 580 | } 581 | 582 | // 2) Answered inbound call (Ringing -> CallConnected -> NoCall) 583 | 584 | { 585 | "body": { 586 | "telephonyStatus": "Ringing", 587 | "extensionId": 607457016, 588 | "activeCalls": [{"direction": "Inbound", "from": "13027430863", "telephonyStatus": "Ringing", "to": "18885434778", "sessionId": "67029198021", "id": "61803278cbf74d7490539a6174a6c094"}], 589 | "sequence": 133897 590 | }, 591 | "timestamp": "2015-04-10T18:49:20.858+0000", 592 | "uuid": "c394ccf6-85dd-4da4-a87e-d31fac7f1283", 593 | "event": "/restapi/v1.0/account/~/extension/607457016/presence?detailedTelephonyState=true" 594 | } 595 | 596 | { 597 | "body": { 598 | "telephonyStatus": "CallConnected", 599 | "extensionId": 607457016, 600 | "activeCalls": [{"direction": "Inbound", "from": "13027430863", "telephonyStatus": "CallConnected", "to": "18885434778", "sessionId": "67029198021", "id": "61803278cbf74d7490539a6174a6c094"}], 601 | "sequence": 133942 602 | }, 603 | "timestamp": "2015-04-10T18:49:26.687+0000", 604 | "uuid": "96df6fdd-a2a4-42fb-bc34-cffb5d637754", 605 | "event": "/restapi/v1.0/account/~/extension/607457016/presence?detailedTelephonyState=true" 606 | } 607 | 608 | { 609 | "body": { 610 | "telephonyStatus": "NoCall", 611 | "extensionId": 607457016, 612 | "activeCalls": [{"direction": "Inbound", "from": "13027430863", "telephonyStatus": "NoCall", "to": "", "sessionId": "67029198021", "id": "61803278cbf74d7490539a6174a6c094"}], 613 | "sequence": 134051 614 | }, 615 | "timestamp": "2015-04-10T18:49:41.828+0000", 616 | "uuid": "7fa7ac3c-5b7b-4127-88ec-9734f478d4a3", 617 | "event": "/restapi/v1.0/account/~/extension/607457016/presence?detailedTelephonyState=true" 618 | } 619 | 620 | // 3) 2-legged Ringout 621 | { 622 | "body": { 623 | "telephonyStatus": "Ringing", 624 | "extensionId": 255537016, 625 | "activeCalls": [{"to": "16502008440", "direction": "Outbound", "from": "16509540334", "sessionId": "915021981016", "telephonyStatus": "Ringing"}], 626 | "sequence": 46061598 627 | }, 628 | "timestamp": "2015-04-17T19:38:59.718+0000", 629 | "uuid": "305b8075-5dad-4686-b22a-d26744802566", "event": "/restapi/v1.0/account/~/extension/255537016/presence?detailedTelephonyState=true" 630 | } 631 | 632 | { 633 | "body": { 634 | "telephonyStatus": "CallConnected", 635 | "extensionId": 255537016, 636 | "activeCalls": [{"direction": "Outbound", "from": "16509540334", "telephonyStatus": "CallConnected", "to": "16502008440", "sessionId": "915021981016", "id": "b5ea44d61b6a4658aadf948348db069c"}], 637 | "sequence": 46062162 638 | }, 639 | "timestamp": "2015-04-17T19:39:12.950+0000", 640 | "uuid": "71673991-dad0-4c8e-9c22-78b0244c790c", 641 | "event": "/restapi/v1.0/account/~/extension/255537016/presence?detailedTelephonyState=true" 642 | } 643 | 644 | { 645 | "body": { 646 | "telephonyStatus": "NoCall", 647 | "extensionId": 255537016, 648 | "activeCalls": [{"direction": "Outbound", "from": "16509540334", "telephonyStatus": "NoCall", "to": "16502008440", "sessionId": "915021981016", "id": "b5ea44d61b6a4658aadf948348db069c"}], 649 | "sequence": 46063265 650 | }, 651 | "timestamp": "2015-04-17T19:39:40.172+0000", 652 | "uuid": "97b01949-1438-45f2-856a-54f89e83367f", 653 | "event": "/restapi/v1.0/account/~/extension/255537016/presence?detailedTelephonyState=true" 654 | } 655 | ~~~ 656 | 657 | * A call may consist of multiple call legs. Top-level telephonyStatus should be aggregated across these multiple call legs. 658 | * A call is identified by its sessionId. For tracking a call, application should match by sessionId across activeCall items from multiple notifications. 659 | * In some rare cases notifications can be delivered in incorrect order. Application should remember largest 'sequence' value from an event for a given call (identified by its sessionId) and ignore any events which come later with smaller 'sequence' 660 | 661 | Some typical event flows for inbound calls are listed for the following scenarios: 662 | 663 | 1. Missed inbound call (Ringing -> No Call) 664 | 1. Answered inbound call (Ringing -> CallConnected -> NoCall) 665 | 1. 2-legged Ringout 666 | 667 | ## Call Notification 668 | 669 | ~~~ javascript 670 | var subscription = rcsdk.getSubscription(); 671 | 672 | subscription 673 | .on(subscription.events.notification, function(msg) { 674 | console.log(msg, msg.body); 675 | }) 676 | .register({ 677 | events: [ 678 | '/account/~/extension/~/presence?detailedTelephonyState=true' 679 | ] 680 | }) 681 | .then(...); 682 | ~~~ 683 | 684 | To get notification of inbound and outbound call events, your application can receive push notifications from the RingCentral Connect Platform by subscribing to specific events, such as the telephony presence event. 685 | 686 | ## Caller ID & Called Number 687 | 688 | ~~~ javascript 689 | var subscription = rcsdk.getSubscription(); 690 | 691 | subscription 692 | .on(subscription.events.notification, function(msg) { 693 | console.log(msg.body.activeCalls[n].from); // activeCalls is array 694 | console.log(msg.body.activeCalls[n].to); 695 | }) 696 | .register({ 697 | events: [ 698 | '/account/~/extension/~/presence?detailedTelephonyState=true' 699 | ], 700 | }) 701 | .then(...); 702 | ~~~ 703 | 704 | Subscript to the account using the `detailedTelephonyState` to get caller info (caller id and called number) from telephony presence events. 705 | 706 | ## Call Information During Call 707 | 708 | ~~~ javascript 709 | platform.apiCall({ 710 | url: rcsdk.getCallHelper().createUrl({active: true}), 711 | get: { // this can be omitted 712 | page: 1, 713 | perPage: 10 714 | } 715 | }).then(function(ajax) { 716 | console.log(ajax.data.records); 717 | }).catch(function(e) { 718 | alert('Active Calls Error: ' + e.message); 719 | }); 720 | ~~~ 721 | 722 | To determine call duration or any other information about the call during the call, the application needs to remember discovered calls and save the time at which they were discovered (had Ringing or Connected status) and then duration can be calculated as difference between now and the saved time. 723 | 724 | The application may periodically (around 2-5 minutes) poll the active calls endpoint to make sure that even missed event notifications will not affect the application state. The application may also load a list of active calls after receiving event notifications. Please keep in mind that application may exceed RPS limits and will be throttled in this case, so application must handle this situation. 725 | 726 | ## Determining End of Call 727 | 728 | The application is responsible for caching active calls. A common method is to use a session ID to track a call. 729 | Your application may gather information about discovered calls and when it sees 'No Call' after 'Connected' status it means that the call has ended. Also the call will disappear from active calls list. See the section below on determining call duration of a recently ended call for more info. 730 | 731 | ## Call Duration Post-Call 732 | 733 | ~~~ javascript 734 | // Determining call duaration of a recently ended call. 735 | platform.apiCall({ 736 | url: rcsdk.getCallHelper().createUrl({active: true}), 737 | get: { // this can be omitted 738 | page: 1, 739 | perPage: 10 740 | } 741 | }).then(function(ajax) { 742 | console.log(ajax.data.records); 743 | }).catch(function(e) { 744 | alert('Active Calls Error: ' + e.message); 745 | }); 746 | ~~~ 747 | 748 | Completed calls will stay in Active Calls for a limited time (few minutes). Use the following code to determine the duration of a recently completed call. 749 | 750 | ## Call and Event Matching 751 | 752 | To match calls and events from the call log or active calls, use the `sessionId` property of calls from events' active calls array and call log or active calls response. 753 | 754 | ## Multiple Concurrent Calls 755 | 756 | To handle multiple concurrent calls, each call event will contain information about all calls, application must go through all calls in `msg.body.activeCalls` array. The application must remember `sequenceNumber` and ignore events with smaller sequence number than already received. 757 | 758 | ## Call Control 759 | 760 | The RingCentral Connect Platform does not currently support any call control functions. 761 | 762 | # Call Queries 763 | 764 | A call management integration usually consists of tracking the telephony call status per the above and the following call query tasks: 765 | 766 | * View active calls 767 | * View recently ended calls 768 | * View historical calls 769 | 770 | ## View Active Calls 771 | 772 | ~~~ javascript 773 | var activeCalls = [], 774 | Call = rcsdk.getCallHelper(); 775 | 776 | // This call may be repeated when needed, for example as a response to incoming Subscription 777 | platform.apiCall(Call.loadRequest(null, { 778 | url: Call.createUrl({active: true}), 779 | query: { // this can be omitted 780 | page: 1, 781 | perPage: 10 782 | } 783 | })).then(function(response) { 784 | activeCalls = Call.merge(activeCalls, response.data.records); // safely merge existing active calls with new ones 785 | }).catch(function(e) { 786 | alert('Active Calls Error: ' + e.message); 787 | }); 788 | ~~~ 789 | 790 | By default, the load request returns calls that were made during the last week. To alter the time frame, provide custom query.dateTo and query.dateFrom properties. 791 | 792 | ## View Recently Ended Calls 793 | 794 | ~~~ javascript 795 | // Getting historical call information 796 | platform.apiCall({ 797 | url: rcsdk.getCallHelper().createUrl(), 798 | get: { // this can be omitted 799 | page: 1, 800 | perPage: 10 801 | } 802 | }).then(function(ajax) { 803 | console.log(ajax.data.records); 804 | }).catch(function(e) { 805 | alert('Calls Error: ' + e.message); 806 | }); 807 | ~~~ 808 | 809 | Recently ended calls appear in Call Log with some delay (seconds to a minute or so) so the recommended way to retrieve them is to use Active Calls API. 810 | 811 | 814 | 815 | ## View Historical Calls 816 | 817 | ~~~ javascript 818 | var calls = [], 819 | Call = rcsdk.getCallHelper(); 820 | 821 | // This call may be repeated when needed, for example as a response to incoming Subscription 822 | platform.apiCall(Call.loadRequest(null, { 823 | query: { // this can be omitted 824 | page: 1, 825 | perPage: 10 826 | }, 827 | })).then(function(response) { 828 | calls = Call.merge(calls, response.data.records); // safely merge existing active calls with new ones 829 | }).catch(function(e) { 830 | alert('Recent Calls Error: ' + e.message); 831 | }); 832 | ~~~ 833 | 834 | By default, the load request returns calls that were made during the last week. To alter the time frame, provide custom query.dateTo and query.dateFrom properties. 835 | 836 | # Call Recordings 837 | 838 | Call log records with recordings will have a `recording` object property which includes information on the recording including the `contentUri` string property which can be used to retrieve the recording. 839 | 840 | ~~~ javascript 841 | var callLogRecord = { 842 | [...] 843 | "recording": { 844 | "uri": "https.../restapi/v1.0/account/401190149008/recording/401547458008", 845 | "id": "401547458008", 846 | "type": "OnDemand", 847 | "contentUri": "https.../restapi/v1.0/account/401190149008/recording/401547458008/content" 848 | } 849 | } 850 | 851 | var recordingUrl = callLogRecord['recording']['contentUri']; 852 | var recordingUrlWithToken = rcsdk.getPlatform().apiUrl(uri, {addToken: true}); 853 | ~~~ 854 | 855 | 856 | -------------------------------------------------------------------------------- /source/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require ./lib/_energize 2 | //= require ./app/_lang 3 | //= require ./app/_search 4 | //= require ./app/_toc 5 | -------------------------------------------------------------------------------- /source/javascripts/all_nosearch.js: -------------------------------------------------------------------------------- 1 | //= require ./lib/_energize 2 | //= require ./app/_lang 3 | //= require ./app/_toc 4 | -------------------------------------------------------------------------------- /source/javascripts/app/_lang.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | (function (global) { 17 | 'use strict'; 18 | 19 | var languages = []; 20 | 21 | global.setupLanguages = setupLanguages; 22 | global.activateLanguage = activateLanguage; 23 | 24 | function activateLanguage(language) { 25 | if (!language) return; 26 | if (language === "") return; 27 | 28 | $(".lang-selector a").removeClass('active'); 29 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active'); 30 | for (var i=0; i < languages.length; i++) { 31 | $(".highlight." + languages[i]).hide(); 32 | } 33 | $(".highlight." + language).show(); 34 | 35 | global.toc.calculateHeights(); 36 | 37 | // scroll to the new location of the position 38 | if ($(window.location.hash).get(0)) { 39 | $(window.location.hash).get(0).scrollIntoView(true); 40 | } 41 | } 42 | 43 | // parseURL and stringifyURL are from https://github.com/sindresorhus/query-string 44 | // MIT licensed 45 | // https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license 46 | function parseURL(str) { 47 | if (typeof str !== 'string') { 48 | return {}; 49 | } 50 | 51 | str = str.trim().replace(/^(\?|#|&)/, ''); 52 | 53 | if (!str) { 54 | return {}; 55 | } 56 | 57 | return str.split('&').reduce(function (ret, param) { 58 | var parts = param.replace(/\+/g, ' ').split('='); 59 | var key = parts[0]; 60 | var val = parts[1]; 61 | 62 | key = decodeURIComponent(key); 63 | // missing `=` should be `null`: 64 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters 65 | val = val === undefined ? null : decodeURIComponent(val); 66 | 67 | if (!ret.hasOwnProperty(key)) { 68 | ret[key] = val; 69 | } else if (Array.isArray(ret[key])) { 70 | ret[key].push(val); 71 | } else { 72 | ret[key] = [ret[key], val]; 73 | } 74 | 75 | return ret; 76 | }, {}); 77 | }; 78 | 79 | function stringifyURL(obj) { 80 | return obj ? Object.keys(obj).sort().map(function (key) { 81 | var val = obj[key]; 82 | 83 | if (Array.isArray(val)) { 84 | return val.sort().map(function (val2) { 85 | return encodeURIComponent(key) + '=' + encodeURIComponent(val2); 86 | }).join('&'); 87 | } 88 | 89 | return encodeURIComponent(key) + '=' + encodeURIComponent(val); 90 | }).join('&') : ''; 91 | }; 92 | 93 | // gets the language set in the query string 94 | function getLanguageFromQueryString() { 95 | if (location.search.length >= 1) { 96 | var language = parseURL(location.search).language 97 | if (language) { 98 | return language; 99 | } else if (jQuery.inArray(location.search.substr(1), languages) != -1) { 100 | return location.search.substr(1); 101 | } 102 | } 103 | 104 | return false; 105 | } 106 | 107 | // returns a new query string with the new language in it 108 | function generateNewQueryString(language) { 109 | var url = parseURL(location.search); 110 | if (url.language) { 111 | url.language = language; 112 | return stringifyURL(url); 113 | } 114 | return language; 115 | } 116 | 117 | // if a button is clicked, add the state to the history 118 | function pushURL(language) { 119 | if (!history) { return; } 120 | var hash = window.location.hash; 121 | if (hash) { 122 | hash = hash.replace(/^#+/, ''); 123 | } 124 | history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash); 125 | 126 | // save language as next default 127 | localStorage.setItem("language", language); 128 | } 129 | 130 | function setupLanguages(l) { 131 | var defaultLanguage = localStorage.getItem("language"); 132 | 133 | languages = l; 134 | 135 | var presetLanguage = getLanguageFromQueryString(); 136 | if (presetLanguage) { 137 | // the language is in the URL, so use that language! 138 | activateLanguage(presetLanguage); 139 | 140 | localStorage.setItem("language", presetLanguage); 141 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { 142 | // the language was the last selected one saved in localstorage, so use that language! 143 | activateLanguage(defaultLanguage); 144 | } else { 145 | // no language selected, so use the default 146 | activateLanguage(languages[0]); 147 | } 148 | } 149 | 150 | // if we click on a language tab, activate that language 151 | $(function() { 152 | $(".lang-selector a").on("click", function() { 153 | var language = $(this).data("language-name"); 154 | pushURL(language); 155 | activateLanguage(language); 156 | return false; 157 | }); 158 | window.onpopstate = function() { 159 | activateLanguage(getLanguageFromQueryString()); 160 | }; 161 | }); 162 | })(window); 163 | -------------------------------------------------------------------------------- /source/javascripts/app/_search.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_lunr 2 | //= require ../lib/_jquery.highlight 3 | (function () { 4 | 'use strict'; 5 | 6 | var content, searchResults; 7 | var highlightOpts = { element: 'span', className: 'search-highlight' }; 8 | 9 | var index = new lunr.Index(); 10 | 11 | index.ref('id'); 12 | index.field('title', { boost: 10 }); 13 | index.field('body'); 14 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); 15 | 16 | $(populate); 17 | $(bind); 18 | 19 | function populate() { 20 | $('h1, h2').each(function() { 21 | var title = $(this); 22 | var body = title.nextUntil('h1, h2'); 23 | index.add({ 24 | id: title.prop('id'), 25 | title: title.text(), 26 | body: body.text() 27 | }); 28 | }); 29 | } 30 | 31 | function bind() { 32 | content = $('.content'); 33 | searchResults = $('.search-results'); 34 | 35 | $('#input-search').on('keyup', search); 36 | } 37 | 38 | function search(event) { 39 | unhighlight(); 40 | searchResults.addClass('visible'); 41 | 42 | // ESC clears the field 43 | if (event.keyCode === 27) this.value = ''; 44 | 45 | if (this.value) { 46 | var results = index.search(this.value).filter(function(r) { 47 | return r.score > 0.0001; 48 | }); 49 | 50 | if (results.length) { 51 | searchResults.empty(); 52 | $.each(results, function (index, result) { 53 | var elem = document.getElementById(result.ref); 54 | searchResults.append("
  • " + $(elem).text() + "
  • "); 55 | }); 56 | highlight.call(this); 57 | } else { 58 | searchResults.html('
  • '); 59 | $('.search-results li').text('No Results Found for "' + this.value + '"'); 60 | } 61 | } else { 62 | unhighlight(); 63 | searchResults.removeClass('visible'); 64 | } 65 | } 66 | 67 | function highlight() { 68 | if (this.value) content.highlight(this.value, highlightOpts); 69 | } 70 | 71 | function unhighlight() { 72 | content.unhighlight(highlightOpts); 73 | } 74 | })(); 75 | -------------------------------------------------------------------------------- /source/javascripts/app/_toc.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_jquery_ui 2 | //= require ../lib/_jquery.tocify 3 | //= require ../lib/_imagesloaded.min 4 | (function (global) { 5 | 'use strict'; 6 | 7 | var closeToc = function() { 8 | $(".tocify-wrapper").removeClass('open'); 9 | $("#nav-button").removeClass('open'); 10 | }; 11 | 12 | var makeToc = function() { 13 | global.toc = $("#toc").tocify({ 14 | selectors: 'h1, h2', 15 | extendPage: false, 16 | theme: 'none', 17 | smoothScroll: false, 18 | showEffectSpeed: 0, 19 | hideEffectSpeed: 180, 20 | ignoreSelector: '.toc-ignore', 21 | highlightOffset: 60, 22 | scrollTo: -1, 23 | scrollHistory: true, 24 | hashGenerator: function (text, element) { 25 | return element.prop('id'); 26 | } 27 | }).data('toc-tocify'); 28 | 29 | $("#nav-button").click(function() { 30 | $(".tocify-wrapper").toggleClass('open'); 31 | $("#nav-button").toggleClass('open'); 32 | return false; 33 | }); 34 | 35 | $(".page-wrapper").click(closeToc); 36 | $(".tocify-item").click(closeToc); 37 | }; 38 | 39 | // Hack to make already open sections to start opened, 40 | // instead of displaying an ugly animation 41 | function animate() { 42 | setTimeout(function() { 43 | toc.setOption('showEffectSpeed', 180); 44 | }, 50); 45 | } 46 | 47 | $(function() { 48 | makeToc(); 49 | animate(); 50 | $('.content').imagesLoaded( function() { 51 | global.toc.calculateHeights(); 52 | }); 53 | }); 54 | })(window); 55 | 56 | -------------------------------------------------------------------------------- /source/javascripts/lib/_energize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * energize.js v0.1.0 3 | * 4 | * Speeds up click events on mobile devices. 5 | * https://github.com/davidcalhoun/energize.js 6 | */ 7 | 8 | (function() { // Sandbox 9 | /** 10 | * Don't add to non-touch devices, which don't need to be sped up 11 | */ 12 | if(!('ontouchstart' in window)) return; 13 | 14 | var lastClick = {}, 15 | isThresholdReached, touchstart, touchmove, touchend, 16 | click, closest; 17 | 18 | /** 19 | * isThresholdReached 20 | * 21 | * Compare touchstart with touchend xy coordinates, 22 | * and only fire simulated click event if the coordinates 23 | * are nearby. (don't want clicking to be confused with a swipe) 24 | */ 25 | isThresholdReached = function(startXY, xy) { 26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5; 27 | }; 28 | 29 | /** 30 | * touchstart 31 | * 32 | * Save xy coordinates when the user starts touching the screen 33 | */ 34 | touchstart = function(e) { 35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY]; 36 | this.threshold = false; 37 | }; 38 | 39 | /** 40 | * touchmove 41 | * 42 | * Check if the user is scrolling past the threshold. 43 | * Have to check here because touchend will not always fire 44 | * on some tested devices (Kindle Fire?) 45 | */ 46 | touchmove = function(e) { 47 | // NOOP if the threshold has already been reached 48 | if(this.threshold) return false; 49 | 50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]); 51 | }; 52 | 53 | /** 54 | * touchend 55 | * 56 | * If the user didn't scroll past the threshold between 57 | * touchstart and touchend, fire a simulated click. 58 | * 59 | * (This will fire before a native click) 60 | */ 61 | touchend = function(e) { 62 | // Don't fire a click if the user scrolled past the threshold 63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) { 64 | return; 65 | } 66 | 67 | /** 68 | * Create and fire a click event on the target element 69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent 70 | */ 71 | var touch = e.changedTouches[0], 72 | evt = document.createEvent('MouseEvents'); 73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click 75 | e.target.dispatchEvent(evt); 76 | }; 77 | 78 | /** 79 | * click 80 | * 81 | * Because we've already fired a click event in touchend, 82 | * we need to listed for all native click events here 83 | * and suppress them as necessary. 84 | */ 85 | click = function(e) { 86 | /** 87 | * Prevent ghost clicks by only allowing clicks we created 88 | * in the click event we fired (look for e.simulated) 89 | */ 90 | var time = Date.now(), 91 | timeDiff = time - lastClick.time, 92 | x = e.clientX, 93 | y = e.clientY, 94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)], 95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps 96 | nodeName = target.nodeName, 97 | isLink = nodeName === 'A', 98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href"); 99 | 100 | lastClick.time = time; 101 | lastClick.x = x; 102 | lastClick.y = y; 103 | 104 | /** 105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire), 106 | * so we have to add more logic to determine the time of the last click. Not perfect... 107 | * 108 | * Older, simpler check: if((!e.simulated) || standAlone) 109 | */ 110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) { 111 | e.preventDefault(); 112 | e.stopPropagation(); 113 | if(!standAlone) return false; 114 | } 115 | 116 | /** 117 | * Special logic for standalone web apps 118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window 119 | */ 120 | if(standAlone) { 121 | window.location = target.getAttribute("href"); 122 | } 123 | 124 | /** 125 | * Add an energize-focus class to the targeted link (mimics :focus behavior) 126 | * TODO: test and/or remove? Does this work? 127 | */ 128 | if(!target || !target.classList) return; 129 | target.classList.add("energize-focus"); 130 | window.setTimeout(function(){ 131 | target.classList.remove("energize-focus"); 132 | }, 150); 133 | }; 134 | 135 | /** 136 | * closest 137 | * @param {HTMLElement} node current node to start searching from. 138 | * @param {string} tagName the (uppercase) name of the tag you're looking for. 139 | * 140 | * Find the closest ancestor tag of a given node. 141 | * 142 | * Starts at node and goes up the DOM tree looking for a 143 | * matching nodeName, continuing until hitting document.body 144 | */ 145 | closest = function(node, tagName){ 146 | var curNode = node; 147 | 148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after 149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found 150 | curNode = curNode.parentNode; // not found, so keep going up 151 | } 152 | 153 | return null; // not found 154 | }; 155 | 156 | /** 157 | * Add all delegated event listeners 158 | * 159 | * All the events we care about bubble up to document, 160 | * so we can take advantage of event delegation. 161 | * 162 | * Note: no need to wait for DOMContentLoaded here 163 | */ 164 | document.addEventListener('touchstart', touchstart, false); 165 | document.addEventListener('touchmove', touchmove, false); 166 | document.addEventListener('touchend', touchend, false); 167 | document.addEventListener('click', click, true); // TODO: why does this use capture? 168 | 169 | })(); -------------------------------------------------------------------------------- /source/javascripts/lib/_imagesloaded.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v3.1.8 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | (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}); -------------------------------------------------------------------------------- /source/javascripts/lib/_jquery.highlight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Highlight plugin 3 | * 4 | * Based on highlight v3 by Johann Burkard 5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html 6 | * 7 | * Code a little bit refactored and cleaned (in my humble opinion). 8 | * Most important changes: 9 | * - has an option to highlight only entire words (wordsOnly - false by default), 10 | * - has an option to be case sensitive (caseSensitive - false by default) 11 | * - highlight element tag and class names can be specified in options 12 | * 13 | * Usage: 14 | * // wrap every occurrance of text 'lorem' in content 15 | * // with (default options) 16 | * $('#content').highlight('lorem'); 17 | * 18 | * // search for and highlight more terms at once 19 | * // so you can save some time on traversing DOM 20 | * $('#content').highlight(['lorem', 'ipsum']); 21 | * $('#content').highlight('lorem ipsum'); 22 | * 23 | * // search only for entire word 'lorem' 24 | * $('#content').highlight('lorem', { wordsOnly: true }); 25 | * 26 | * // don't ignore case during search of term 'lorem' 27 | * $('#content').highlight('lorem', { caseSensitive: true }); 28 | * 29 | * // wrap every occurrance of term 'ipsum' in content 30 | * // with 31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); 32 | * 33 | * // remove default highlight 34 | * $('#content').unhighlight(); 35 | * 36 | * // remove custom highlight 37 | * $('#content').unhighlight({ element: 'em', className: 'important' }); 38 | * 39 | * 40 | * Copyright (c) 2009 Bartek Szopka 41 | * 42 | * Licensed under MIT license. 43 | * 44 | */ 45 | 46 | jQuery.extend({ 47 | highlight: function (node, re, nodeName, className) { 48 | if (node.nodeType === 3) { 49 | var match = node.data.match(re); 50 | if (match) { 51 | var highlight = document.createElement(nodeName || 'span'); 52 | highlight.className = className || 'highlight'; 53 | var wordNode = node.splitText(match.index); 54 | wordNode.splitText(match[0].length); 55 | var wordClone = wordNode.cloneNode(true); 56 | highlight.appendChild(wordClone); 57 | wordNode.parentNode.replaceChild(highlight, wordNode); 58 | return 1; //skip added node in parent 59 | } 60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children 61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted 63 | for (var i = 0; i < node.childNodes.length; i++) { 64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className); 65 | } 66 | } 67 | return 0; 68 | } 69 | }); 70 | 71 | jQuery.fn.unhighlight = function (options) { 72 | var settings = { className: 'highlight', element: 'span' }; 73 | jQuery.extend(settings, options); 74 | 75 | return this.find(settings.element + "." + settings.className).each(function () { 76 | var parent = this.parentNode; 77 | parent.replaceChild(this.firstChild, this); 78 | parent.normalize(); 79 | }).end(); 80 | }; 81 | 82 | jQuery.fn.highlight = function (words, options) { 83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; 84 | jQuery.extend(settings, options); 85 | 86 | if (words.constructor === String) { 87 | words = [words]; 88 | } 89 | words = jQuery.grep(words, function(word, i){ 90 | return word != ''; 91 | }); 92 | words = jQuery.map(words, function(word, i) { 93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 94 | }); 95 | if (words.length == 0) { return this; }; 96 | 97 | var flag = settings.caseSensitive ? "" : "i"; 98 | var pattern = "(" + words.join("|") + ")"; 99 | if (settings.wordsOnly) { 100 | pattern = "\\b" + pattern + "\\b"; 101 | } 102 | var re = new RegExp(pattern, flag); 103 | 104 | return this.each(function () { 105 | jQuery.highlight(this, re, settings.element, settings.className); 106 | }); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /source/javascripts/lib/_jquery.tocify.js: -------------------------------------------------------------------------------- 1 | /* jquery Tocify - v1.8.0 - 2013-09-16 2 | * http://www.gregfranko.com/jquery.tocify.js/ 3 | * Copyright (c) 2013 Greg Franko; Licensed MIT 4 | * Modified lightly by Robert Lord to fix a bug I found, 5 | * and also so it adds ids to headers 6 | * also because I want height caching, since the 7 | * height lookup for h1s and h2s was causing serious 8 | * lag spikes below 30 fps */ 9 | 10 | // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE. 11 | (function(tocify) { 12 | 13 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/) 14 | "use strict"; 15 | 16 | // Calls the second IIFE and locally passes in the global jQuery, window, and document objects 17 | tocify(window.jQuery, window, document); 18 | 19 | } 20 | 21 | // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript. 22 | (function($, window, document, undefined) { 23 | 24 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/) 25 | "use strict"; 26 | 27 | var tocClassName = "tocify", 28 | tocClass = "." + tocClassName, 29 | tocFocusClassName = "tocify-focus", 30 | tocHoverClassName = "tocify-hover", 31 | hideTocClassName = "tocify-hide", 32 | hideTocClass = "." + hideTocClassName, 33 | headerClassName = "tocify-header", 34 | headerClass = "." + headerClassName, 35 | subheaderClassName = "tocify-subheader", 36 | subheaderClass = "." + subheaderClassName, 37 | itemClassName = "tocify-item", 38 | itemClass = "." + itemClassName, 39 | extendPageClassName = "tocify-extend-page", 40 | extendPageClass = "." + extendPageClassName; 41 | 42 | // Calling the jQueryUI Widget Factory Method 43 | $.widget("toc.tocify", { 44 | 45 | //Plugin version 46 | version: "1.8.0", 47 | 48 | // These options will be used as defaults 49 | options: { 50 | 51 | // **context**: Accepts String: Any jQuery selector 52 | // The container element that holds all of the elements used to generate the table of contents 53 | context: "body", 54 | 55 | // **ignoreSelector**: Accepts String: Any jQuery selector 56 | // A selector to any element that would be matched by selectors that you wish to be ignored 57 | ignoreSelector: null, 58 | 59 | // **selectors**: Accepts an Array of Strings: Any jQuery selectors 60 | // The element's used to generate the table of contents. The order is very important since it will determine the table of content's nesting structure 61 | selectors: "h1, h2, h3", 62 | 63 | // **showAndHide**: Accepts a boolean: true or false 64 | // Used to determine if elements should be shown and hidden 65 | showAndHide: true, 66 | 67 | // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown" 68 | // Used to display any of the table of contents nested items 69 | showEffect: "slideDown", 70 | 71 | // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast" 72 | // The time duration of the show animation 73 | showEffectSpeed: "medium", 74 | 75 | // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp" 76 | // Used to hide any of the table of contents nested items 77 | hideEffect: "slideUp", 78 | 79 | // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast" 80 | // The time duration of the hide animation 81 | hideEffectSpeed: "medium", 82 | 83 | // **smoothScroll**: Accepts a boolean: true or false 84 | // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page 85 | smoothScroll: true, 86 | 87 | // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast" 88 | // The time duration of the smoothScroll animation 89 | smoothScrollSpeed: "medium", 90 | 91 | // **scrollTo**: Accepts Number (pixels) 92 | // The amount of space between the top of page and the selected table of contents item after the page has been scrolled 93 | scrollTo: 0, 94 | 95 | // **showAndHideOnScroll**: Accepts a boolean: true or false 96 | // Determines if table of contents nested items should be shown and hidden while scrolling 97 | showAndHideOnScroll: true, 98 | 99 | // **highlightOnScroll**: Accepts a boolean: true or false 100 | // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling 101 | highlightOnScroll: true, 102 | 103 | // **highlightOffset**: Accepts a number 104 | // The offset distance in pixels to trigger the next active table of contents item 105 | highlightOffset: 40, 106 | 107 | // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none" 108 | // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents 109 | theme: "bootstrap", 110 | 111 | // **extendPage**: Accepts a boolean: true or false 112 | // If a user scrolls to the bottom of the page and the page is not tall enough to scroll to the last table of contents item, then the page height is increased 113 | extendPage: true, 114 | 115 | // **extendPageOffset**: Accepts a number: pixels 116 | // How close to the bottom of the page a user must scroll before the page is extended 117 | extendPageOffset: 100, 118 | 119 | // **history**: Accepts a boolean: true or false 120 | // Adds a hash to the page url to maintain history 121 | history: true, 122 | 123 | // **scrollHistory**: Accepts a boolean: true or false 124 | // Adds a hash to the page url, to maintain history, when scrolling to a TOC item 125 | scrollHistory: false, 126 | 127 | // **hashGenerator**: How the hash value (the anchor segment of the URL, following the 128 | // # character) will be generated. 129 | // 130 | // "compact" (default) - #CompressesEverythingTogether 131 | // "pretty" - #looks-like-a-nice-url-and-is-easily-readable 132 | // function(text, element){} - Your own hash generation function that accepts the text as an 133 | // argument, and returns the hash value. 134 | hashGenerator: "compact", 135 | 136 | // **highlightDefault**: Accepts a boolean: true or false 137 | // Set's the first TOC item as active if no other TOC item is active. 138 | highlightDefault: true 139 | 140 | }, 141 | 142 | // _Create 143 | // ------- 144 | // Constructs the plugin. Only called once. 145 | _create: function() { 146 | 147 | var self = this; 148 | 149 | self.tocifyWrapper = $('.tocify-wrapper'); 150 | self.extendPageScroll = true; 151 | 152 | // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings) 153 | self.items = []; 154 | 155 | // Generates the HTML for the dynamic table of contents 156 | self._generateToc(); 157 | 158 | // Caches heights and anchors 159 | self.cachedHeights = [], 160 | self.cachedAnchors = []; 161 | 162 | // Adds CSS classes to the newly generated table of contents HTML 163 | self._addCSSClasses(); 164 | 165 | self.webkit = (function() { 166 | 167 | for(var prop in window) { 168 | 169 | if(prop) { 170 | 171 | if(prop.toLowerCase().indexOf("webkit") !== -1) { 172 | 173 | return true; 174 | 175 | } 176 | 177 | } 178 | 179 | } 180 | 181 | return false; 182 | 183 | }()); 184 | 185 | // Adds jQuery event handlers to the newly generated table of contents 186 | self._setEventHandlers(); 187 | 188 | // Binding to the Window load event to make sure the correct scrollTop is calculated 189 | $(window).load(function() { 190 | 191 | // Sets the active TOC item 192 | self._setActiveElement(true); 193 | 194 | // Once all animations on the page are complete, this callback function will be called 195 | $("html, body").promise().done(function() { 196 | 197 | setTimeout(function() { 198 | 199 | self.extendPageScroll = false; 200 | 201 | },0); 202 | 203 | }); 204 | 205 | }); 206 | 207 | }, 208 | 209 | // _generateToc 210 | // ------------ 211 | // Generates the HTML for the dynamic table of contents 212 | _generateToc: function() { 213 | 214 | // _Local variables_ 215 | 216 | // Stores the plugin context in the self variable 217 | var self = this, 218 | 219 | // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above 220 | firstElem, 221 | 222 | // Instantiated variable that will store the top level newly created unordered list DOM element 223 | ul, 224 | ignoreSelector = self.options.ignoreSelector; 225 | 226 | // If the selectors option has a comma within the string 227 | if(this.options.selectors.indexOf(",") !== -1) { 228 | 229 | // Grabs the first selector from the string 230 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(","))); 231 | 232 | } 233 | 234 | // If the selectors option does not have a comman within the string 235 | else { 236 | 237 | // Grabs the first selector from the string and makes sure there are no spaces 238 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"")); 239 | 240 | } 241 | 242 | if(!firstElem.length) { 243 | 244 | self.element.addClass(hideTocClassName); 245 | 246 | return; 247 | 248 | } 249 | 250 | self.element.addClass(tocClassName); 251 | 252 | // Loops through each top level selector 253 | firstElem.each(function(index) { 254 | 255 | //If the element matches the ignoreSelector then we skip it 256 | if($(this).is(ignoreSelector)) { 257 | return; 258 | } 259 | 260 | // Creates an unordered list HTML element and adds a dynamic ID and standard class name 261 | ul = $("