diff --git a/README.md b/README.md index efb90011e2..1488d8a097 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,12 @@ docker run --name devdocs -d -p 9292:9292 thibaut/devdocs DevDocs aims to make reading and searching reference documentation fast, easy and enjoyable. -The app's main goals are to: +The app's main goals are to: * Keep load times as short as possible * Improve the quality, speed, and order of search results * Maximize the use of caching and other performance optimizations -* Maintain a clean and readable user interface +* Maintain a clean and readable user interface * Be fully functional offline * Support full keyboard navigation * Reduce “context switch” by using a consistent typography and design across all documentations @@ -70,7 +70,7 @@ The app's main goals are to: The web app is all client-side JavaScript, written in [CoffeeScript](http://coffeescript.org), and powered by a small [Sinatra](http://www.sinatrarb.com)/[Sprockets](https://github.com/rails/sprockets) application. It relies on files generated by the [scraper](#scraper). -Many of the code's design decisions were driven by the fact that the app uses XHR to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts. +Many of the code's design decisions were driven by the fact that the app uses Fetch to load content directly into the main frame. This includes stripping the original documents of most of their HTML markup (e.g. scripts and stylesheets) to avoid polluting the main frame, and prefixing all CSS class names with an underscore to prevent conflicts. Another driving factor is performance and the fact that everything happens in the browser. A service worker (which comes with its own set of constraints) and `localStorage` are used to speed up the boot time, while memory consumption is kept in check by allowing the user to pick his/her own set of documentations. The search algorithm is kept simple because it needs to be fast even searching through 100,000 strings. @@ -126,7 +126,7 @@ thor docs:clean # Delete documentation packages thor console # Start a REPL thor console:docs # Start a REPL in the "Docs" module -# Tests can be run quickly from within the console using the "test" command. +# Tests can be run quickly from within the console using the "test" command. # Run "help test" for usage instructions. thor test:all # Run all tests thor test:docs # Run "Docs" tests diff --git a/assets/javascripts/lib/ajax.coffee b/assets/javascripts/lib/ajax.coffee index 4138ce7b0f..93d0bdb0d3 100644 --- a/assets/javascripts/lib/ajax.coffee +++ b/assets/javascripts/lib/ajax.coffee @@ -6,18 +6,32 @@ MIME_TYPES = applyDefaults(options) serializeData(options) - xhr = new XMLHttpRequest() - xhr.open(options.type, options.url, options.async) - - applyCallbacks(xhr, options) - applyHeaders(xhr, options) - - xhr.send(options.data) - - if options.async - abort: abort.bind(undefined, xhr) - else - parseResponse(xhr, options) + abortController = new AbortController() + + timer = setTimeout => + abortController.abort() + , options.timeout * 1000 + + fetch( + options.url, + headers: processHeaders(options) + method: options.type + contentType: options.dataType + signal: abortController.signal + ).then((response) -> + if options.dataType == 'json' + response.json() + else + response.text() + ).then((data) -> + if data? + onSuccess data, options + else + onError 'invalid', '', options + ).catch((error) -> + onError 'error', error, options + ).finally -> + clearTimeout timer ajax.defaults = async: true @@ -51,19 +65,7 @@ serializeData = (options) -> serializeParams = (params) -> ("#{encodeURIComponent key}=#{encodeURIComponent value}" for key, value of params).join '&' -applyCallbacks = (xhr, options) -> - return unless options.async - - xhr.timer = setTimeout onTimeout.bind(undefined, xhr, options), options.timeout * 1000 - xhr.onprogress = options.progress if options.progress - xhr.onreadystatechange = -> - if xhr.readyState is 4 - clearTimeout(xhr.timer) - onComplete(xhr, options) - return - return - -applyHeaders = (xhr, options) -> +processHeaders = (options) -> options.headers or= {} if options.contentType @@ -74,45 +76,13 @@ applyHeaders = (xhr, options) -> if options.dataType options.headers['Accept'] = MIME_TYPES[options.dataType] or options.dataType + return options.headers - for key, value of options.headers - xhr.setRequestHeader(key, value) +onSuccess = (data, options) -> + options.success?.call options.context, data, options return -onComplete = (xhr, options) -> - if 200 <= xhr.status < 300 - if (response = parseResponse(xhr, options))? - onSuccess response, xhr, options - else - onError 'invalid', xhr, options - else - onError 'error', xhr, options +onError = (type, error, options) -> + options.error?.call options.context, type, error, options return -onSuccess = (response, xhr, options) -> - options.success?.call options.context, response, xhr, options - return - -onError = (type, xhr, options) -> - options.error?.call options.context, type, xhr, options - return - -onTimeout = (xhr, options) -> - xhr.abort() - onError 'timeout', xhr, options - return - -abort = (xhr) -> - clearTimeout(xhr.timer) - xhr.onreadystatechange = null - xhr.abort() - return - -parseResponse = (xhr, options) -> - if options.dataType is 'json' - parseJSON(xhr.responseText) - else - xhr.responseText - -parseJSON = (json) -> - try JSON.parse(json) catch