Commit 2975dbf5f756dcdb2b37c9cc7d38dead7a5a11f8

Authored by Braulio Bhavamitra
1 parent 5b05e582

Add pjax plugin

plugins/pjax/lib/pjax_plugin.rb 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +class PjaxPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + I18n.t('pjax_plugin.lib.plugin.name')
  5 + end
  6 +
  7 + def self.plugin_description
  8 + I18n.t('pjax_plugin.lib.plugin.description')
  9 + end
  10 +
  11 + def stylesheet?
  12 + true
  13 + end
  14 +
  15 + def js_files
  16 + ['jquery.pjax.js', 'patchwork.js', 'loading-overlay', 'pjax', ].map{ |j| "javascripts/#{j}" }
  17 + end
  18 +
  19 + def head_ending
  20 + #TODO: add pjax meta
  21 + end
  22 +
  23 + def body_beginning
  24 + lambda{ render 'pjax_layouts/load_state_script' }
  25 + end
  26 +
  27 + PjaxCheck = lambda do
  28 + return unless request.headers['X-PJAX']
  29 + # raise makes pjax fallback to a regular request
  30 + raise "Pjax can't be used here" if params[:controller] == 'account'
  31 +
  32 + @pjax = true
  33 + @pjax_loaded_themes = request.headers['X-PJAX-Themes'].to_s.split(',') || []
  34 +
  35 + unless self.respond_to? :get_layout_with_pjax
  36 + self.class.send :define_method, :get_layout_with_pjax do
  37 + if @pjax then 'pjax' else get_layout_without_pjax end
  38 + end
  39 + self.class.alias_method_chain :get_layout, :pjax
  40 + end
  41 + end
  42 +
  43 + def application_controller_filters
  44 + [{
  45 + :type => 'before_filter', :method_name => 'pjax_check',
  46 + :options => {}, :block => PjaxCheck,
  47 + }]
  48 + end
  49 +
  50 + protected
  51 +
  52 +end
... ...
plugins/pjax/locales/en.yml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +
  2 +"en": &en-US
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Use pjax for page's links"
  8 +
  9 +'en_US':
  10 + <<: *en-US
  11 +'en-US':
  12 + <<: *en-US
... ...
plugins/pjax/locales/pt.yml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +
  2 +"pt": &pt-BR
  3 + pjax_plugin:
  4 + lib:
  5 + plugin:
  6 + name: "pjax plugin"
  7 + description: "Usa o pjax para os links da página"
  8 +
  9 +'pt_BR':
  10 + <<: *pt-BR
  11 +'pt-BR':
  12 + <<: *pt-BR
  13 +
... ...
plugins/pjax/public/images/loading-gears.gif 0 → 100644

617 KB

plugins/pjax/public/images/loading-overlay.gif 0 → 100644

3.4 KB

plugins/pjax/public/javascripts/jquery.pjax.js 0 → 100644
... ... @@ -0,0 +1,838 @@
  1 +// jquery.pjax.js
  2 +// copyright chris wanstrath
  3 +// https://github.com/defunkt/jquery-pjax
  4 +
  5 +(function($){
  6 +
  7 +// When called on a container with a selector, fetches the href with
  8 +// ajax into the container or with the data-pjax attribute on the link
  9 +// itself.
  10 +//
  11 +// Tries to make sure the back button and ctrl+click work the way
  12 +// you'd expect.
  13 +//
  14 +// Exported as $.fn.pjax
  15 +//
  16 +// Accepts a jQuery ajax options object that may include these
  17 +// pjax specific options:
  18 +//
  19 +//
  20 +// container - Where to stick the response body. Usually a String selector.
  21 +// $(container).html(xhr.responseBody)
  22 +// (default: current jquery context)
  23 +// push - Whether to pushState the URL. Defaults to true (of course).
  24 +// replace - Want to use replaceState instead? That's cool.
  25 +//
  26 +// For convenience the second parameter can be either the container or
  27 +// the options object.
  28 +//
  29 +// Returns the jQuery object
  30 +function fnPjax(selector, container, options) {
  31 + var context = this
  32 + return this.on('click.pjax', selector, function(event) {
  33 + var opts = $.extend({}, optionsFor(container, options))
  34 + if (!opts.container)
  35 + opts.container = $(this).attr('data-pjax') || context
  36 + handleClick(event, opts)
  37 + })
  38 +}
  39 +
  40 +// Public: pjax on click handler
  41 +//
  42 +// Exported as $.pjax.click.
  43 +//
  44 +// event - "click" jQuery.Event
  45 +// options - pjax options
  46 +//
  47 +// Examples
  48 +//
  49 +// $(document).on('click', 'a', $.pjax.click)
  50 +// // is the same as
  51 +// $(document).pjax('a')
  52 +//
  53 +// $(document).on('click', 'a', function(event) {
  54 +// var container = $(this).closest('[data-pjax-container]')
  55 +// $.pjax.click(event, container)
  56 +// })
  57 +//
  58 +// Returns nothing.
  59 +function handleClick(event, container, options) {
  60 + options = optionsFor(container, options)
  61 +
  62 + var link = event.currentTarget
  63 +
  64 + if (link.tagName.toUpperCase() !== 'A')
  65 + throw "$.fn.pjax or $.pjax.click requires an anchor element"
  66 +
  67 + // Middle click, cmd click, and ctrl click should open
  68 + // links in a new tab as normal.
  69 + if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
  70 + return
  71 +
  72 + // Ignore cross origin links
  73 + if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
  74 + return
  75 +
  76 + // Ignore anchors on the same page
  77 + if (link.hash && link.href.replace(link.hash, '') ===
  78 + location.href.replace(location.hash, ''))
  79 + return
  80 +
  81 + // Ignore empty anchor "foo.html#"
  82 + if (link.href === location.href + '#')
  83 + return
  84 +
  85 + var defaults = {
  86 + url: link.href,
  87 + container: $(link).attr('data-pjax'),
  88 + target: link
  89 + }
  90 +
  91 + var opts = $.extend({}, defaults, options)
  92 + var clickEvent = $.Event('pjax:click')
  93 + $(link).trigger(clickEvent, [opts])
  94 +
  95 + if (!clickEvent.isDefaultPrevented()) {
  96 + pjax(opts)
  97 + event.preventDefault()
  98 + }
  99 +}
  100 +
  101 +// Public: pjax on form submit handler
  102 +//
  103 +// Exported as $.pjax.submit
  104 +//
  105 +// event - "click" jQuery.Event
  106 +// options - pjax options
  107 +//
  108 +// Examples
  109 +//
  110 +// $(document).on('submit', 'form', function(event) {
  111 +// var container = $(this).closest('[data-pjax-container]')
  112 +// $.pjax.submit(event, container)
  113 +// })
  114 +//
  115 +// Returns nothing.
  116 +function handleSubmit(event, container, options) {
  117 + options = optionsFor(container, options)
  118 +
  119 + var form = event.currentTarget
  120 +
  121 + if (form.tagName.toUpperCase() !== 'FORM')
  122 + throw "$.pjax.submit requires a form element"
  123 +
  124 + var defaults = {
  125 + type: form.method.toUpperCase(),
  126 + url: form.action,
  127 + data: $(form).serializeArray(),
  128 + container: $(form).attr('data-pjax'),
  129 + target: form
  130 + }
  131 +
  132 + pjax($.extend({}, defaults, options))
  133 +
  134 + event.preventDefault()
  135 +}
  136 +
  137 +// Loads a URL with ajax, puts the response body inside a container,
  138 +// then pushState()'s the loaded URL.
  139 +//
  140 +// Works just like $.ajax in that it accepts a jQuery ajax
  141 +// settings object (with keys like url, type, data, etc).
  142 +//
  143 +// Accepts these extra keys:
  144 +//
  145 +// container - Where to stick the response body.
  146 +// $(container).html(xhr.responseBody)
  147 +// push - Whether to pushState the URL. Defaults to true (of course).
  148 +// replace - Want to use replaceState instead? That's cool.
  149 +//
  150 +// Use it just like $.ajax:
  151 +//
  152 +// var xhr = $.pjax({ url: this.href, container: '#main' })
  153 +// console.log( xhr.readyState )
  154 +//
  155 +// Returns whatever $.ajax returns.
  156 +function pjax(options) {
  157 + options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
  158 +
  159 + if ($.isFunction(options.url)) {
  160 + options.url = options.url()
  161 + }
  162 +
  163 + var target = options.target
  164 +
  165 + var hash = parseURL(options.url).hash
  166 +
  167 + var context = options.context = findContainerFor(options.container)
  168 +
  169 + // We want the browser to maintain two separate internal caches: one
  170 + // for pjax'd partial page loads and one for normal page loads.
  171 + // Without adding this secret parameter, some browsers will often
  172 + // confuse the two.
  173 + if (!options.data) options.data = {}
  174 + options.data._pjax = context.selector
  175 +
  176 + function fire(type, args) {
  177 + var event = $.Event(type, { relatedTarget: target })
  178 + context.trigger(event, args)
  179 + return !event.isDefaultPrevented()
  180 + }
  181 +
  182 + var timeoutTimer
  183 +
  184 + options.beforeSend = function(xhr, settings) {
  185 + // No timeout for non-GET requests
  186 + // Its not safe to request the resource again with a fallback method.
  187 + if (settings.type !== 'GET') {
  188 + settings.timeout = 0
  189 + }
  190 +
  191 + xhr.setRequestHeader('X-PJAX', 'true')
  192 + xhr.setRequestHeader('X-PJAX-Container', context.selector)
  193 +
  194 + if (!fire('pjax:beforeSend', [xhr, settings]))
  195 + return false
  196 +
  197 + if (settings.timeout > 0) {
  198 + timeoutTimer = setTimeout(function() {
  199 + if (fire('pjax:timeout', [xhr, options]))
  200 + xhr.abort('timeout')
  201 + }, settings.timeout)
  202 +
  203 + // Clear timeout setting so jquerys internal timeout isn't invoked
  204 + settings.timeout = 0
  205 + }
  206 +
  207 + options.requestUrl = parseURL(settings.url).href
  208 + }
  209 +
  210 + options.complete = function(xhr, textStatus) {
  211 + if (timeoutTimer)
  212 + clearTimeout(timeoutTimer)
  213 +
  214 + fire('pjax:complete', [xhr, textStatus, options])
  215 +
  216 + fire('pjax:end', [xhr, options])
  217 + }
  218 +
  219 + options.error = function(xhr, textStatus, errorThrown) {
  220 + var container = extractContainer("", xhr, options)
  221 +
  222 + var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
  223 + if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
  224 + locationReplace(container.url)
  225 + }
  226 + }
  227 +
  228 + options.success = function(data, status, xhr) {
  229 + // If $.pjax.defaults.version is a function, invoke it first.
  230 + // Otherwise it can be a static string.
  231 + var currentVersion = (typeof $.pjax.defaults.version === 'function') ?
  232 + $.pjax.defaults.version() :
  233 + $.pjax.defaults.version
  234 +
  235 + var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
  236 +
  237 + var container = extractContainer(data, xhr, options)
  238 +
  239 + // If there is a layout version mismatch, hard load the new url
  240 + if (currentVersion && latestVersion && currentVersion !== latestVersion) {
  241 + locationReplace(container.url)
  242 + return
  243 + }
  244 +
  245 + // If the new response is missing a body, hard load the page
  246 + if (!container.contents) {
  247 + locationReplace(container.url)
  248 + return
  249 + }
  250 +
  251 + pjax.state = {
  252 + id: options.id || uniqueId(),
  253 + url: container.url,
  254 + title: container.title,
  255 + container: context.selector,
  256 + fragment: options.fragment,
  257 + timeout: options.timeout
  258 + }
  259 +
  260 + if (options.push || options.replace) {
  261 + window.history.replaceState(pjax.state, container.title, container.url)
  262 + }
  263 +
  264 + // Clear out any focused controls before inserting new page contents.
  265 + document.activeElement.blur()
  266 +
  267 + if (container.title) document.title = container.title
  268 + context.html(container.contents)
  269 +
  270 + // FF bug: Won't autofocus fields that are inserted via JS.
  271 + // This behavior is incorrect. So if theres no current focus, autofocus
  272 + // the last field.
  273 + //
  274 + // http://www.w3.org/html/wg/drafts/html/master/forms.html
  275 + var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
  276 + if (autofocusEl && document.activeElement !== autofocusEl) {
  277 + autofocusEl.focus();
  278 + }
  279 +
  280 + executeScriptTags(container.scripts)
  281 +
  282 + // Scroll to top by default
  283 + if (typeof options.scrollTo === 'number')
  284 + $(window).scrollTop(options.scrollTo)
  285 +
  286 + // If the URL has a hash in it, make sure the browser
  287 + // knows to navigate to the hash.
  288 + if ( hash !== '' ) {
  289 + // Avoid using simple hash set here. Will add another history
  290 + // entry. Replace the url with replaceState and scroll to target
  291 + // by hand.
  292 + //
  293 + // window.location.hash = hash
  294 + var url = parseURL(container.url)
  295 + url.hash = hash
  296 +
  297 + pjax.state.url = url.href
  298 + window.history.replaceState(pjax.state, container.title, url.href)
  299 +
  300 + var target = $(url.hash)
  301 + if (target.length) $(window).scrollTop(target.offset().top)
  302 + }
  303 +
  304 + fire('pjax:success', [data, status, xhr, options])
  305 + }
  306 +
  307 +
  308 + // Initialize pjax.state for the initial page load. Assume we're
  309 + // using the container and options of the link we're loading for the
  310 + // back button to the initial page. This ensures good back button
  311 + // behavior.
  312 + if (!pjax.state) {
  313 + pjax.state = {
  314 + id: uniqueId(),
  315 + url: window.location.href,
  316 + title: document.title,
  317 + container: context.selector,
  318 + fragment: options.fragment,
  319 + timeout: options.timeout
  320 + }
  321 + window.history.replaceState(pjax.state, document.title)
  322 + }
  323 +
  324 + // Cancel the current request if we're already pjaxing
  325 + var xhr = pjax.xhr
  326 + if ( xhr && xhr.readyState < 4) {
  327 + xhr.onreadystatechange = $.noop
  328 + xhr.abort()
  329 + }
  330 +
  331 + pjax.options = options
  332 + var xhr = pjax.xhr = $.ajax(options)
  333 +
  334 + if (xhr.readyState > 0) {
  335 + if (options.push && !options.replace) {
  336 + // Cache current container element before replacing it
  337 + cachePush(pjax.state.id, context.clone().contents())
  338 +
  339 + window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
  340 + }
  341 +
  342 + fire('pjax:start', [xhr, options])
  343 + fire('pjax:send', [xhr, options])
  344 + }
  345 +
  346 + return pjax.xhr
  347 +}
  348 +
  349 +// Public: Reload current page with pjax.
  350 +//
  351 +// Returns whatever $.pjax returns.
  352 +function pjaxReload(container, options) {
  353 + var defaults = {
  354 + url: window.location.href,
  355 + push: false,
  356 + replace: true,
  357 + scrollTo: false
  358 + }
  359 +
  360 + return pjax($.extend(defaults, optionsFor(container, options)))
  361 +}
  362 +
  363 +// Internal: Hard replace current state with url.
  364 +//
  365 +// Work for around WebKit
  366 +// https://bugs.webkit.org/show_bug.cgi?id=93506
  367 +//
  368 +// Returns nothing.
  369 +function locationReplace(url) {
  370 + window.history.replaceState(null, "", "#")
  371 + window.location.replace(url)
  372 +}
  373 +
  374 +
  375 +var initialPop = true
  376 +var initialURL = window.location.href
  377 +var initialState = window.history.state
  378 +
  379 +// Initialize $.pjax.state if possible
  380 +// Happens when reloading a page and coming forward from a different
  381 +// session history.
  382 +if (initialState && initialState.container) {
  383 + pjax.state = initialState
  384 +}
  385 +
  386 +// Non-webkit browsers don't fire an initial popstate event
  387 +if ('state' in window.history) {
  388 + initialPop = false
  389 +}
  390 +
  391 +// popstate handler takes care of the back and forward buttons
  392 +//
  393 +// You probably shouldn't use pjax on pages with other pushState
  394 +// stuff yet.
  395 +function onPjaxPopstate(event) {
  396 + var state = event.state
  397 +
  398 + if (state && state.container) {
  399 + // When coming forward from a separate history session, will get an
  400 + // initial pop with a state we are already at. Skip reloading the current
  401 + // page.
  402 + if (initialPop && initialURL == state.url) return
  403 +
  404 + // If popping back to the same state, just skip.
  405 + // Could be clicking back from hashchange rather than a pushState.
  406 + if (pjax.state.id === state.id) return
  407 +
  408 + var container = $(state.container)
  409 + if (container.length) {
  410 + var direction, contents = cacheMapping[state.id]
  411 +
  412 + if (pjax.state) {
  413 + // Since state ids always increase, we can deduce the history
  414 + // direction from the previous state.
  415 + direction = pjax.state.id < state.id ? 'forward' : 'back'
  416 +
  417 + // Cache current container before replacement and inform the
  418 + // cache which direction the history shifted.
  419 + cachePop(direction, pjax.state.id, container.clone().contents())
  420 + }
  421 +
  422 + var popstateEvent = $.Event('pjax:popstate', {
  423 + state: state,
  424 + direction: direction
  425 + })
  426 + container.trigger(popstateEvent)
  427 +
  428 + var options = {
  429 + id: state.id,
  430 + url: state.url,
  431 + container: container,
  432 + push: false,
  433 + fragment: state.fragment,
  434 + timeout: state.timeout,
  435 + scrollTo: false
  436 + }
  437 +
  438 + if (contents) {
  439 + container.trigger('pjax:start', [null, options])
  440 +
  441 + if (state.title) document.title = state.title
  442 + container.html(contents)
  443 + pjax.state = state
  444 +
  445 + container.trigger('pjax:end', [null, options])
  446 + } else {
  447 + pjax(options)
  448 + }
  449 +
  450 + // Force reflow/relayout before the browser tries to restore the
  451 + // scroll position.
  452 + container[0].offsetHeight
  453 + } else {
  454 + locationReplace(location.href)
  455 + }
  456 + }
  457 + initialPop = false
  458 +}
  459 +
  460 +// Fallback version of main pjax function for browsers that don't
  461 +// support pushState.
  462 +//
  463 +// Returns nothing since it retriggers a hard form submission.
  464 +function fallbackPjax(options) {
  465 + var url = $.isFunction(options.url) ? options.url() : options.url,
  466 + method = options.type ? options.type.toUpperCase() : 'GET'
  467 +
  468 + var form = $('<form>', {
  469 + method: method === 'GET' ? 'GET' : 'POST',
  470 + action: url,
  471 + style: 'display:none'
  472 + })
  473 +
  474 + if (method !== 'GET' && method !== 'POST') {
  475 + form.append($('<input>', {
  476 + type: 'hidden',
  477 + name: '_method',
  478 + value: method.toLowerCase()
  479 + }))
  480 + }
  481 +
  482 + var data = options.data
  483 + if (typeof data === 'string') {
  484 + $.each(data.split('&'), function(index, value) {
  485 + var pair = value.split('=')
  486 + form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
  487 + })
  488 + } else if (typeof data === 'object') {
  489 + for (key in data)
  490 + form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
  491 + }
  492 +
  493 + $(document.body).append(form)
  494 + form.submit()
  495 +}
  496 +
  497 +// Internal: Generate unique id for state object.
  498 +//
  499 +// Use a timestamp instead of a counter since ids should still be
  500 +// unique across page loads.
  501 +//
  502 +// Returns Number.
  503 +function uniqueId() {
  504 + return (new Date).getTime()
  505 +}
  506 +
  507 +// Internal: Strips _pjax param from url
  508 +//
  509 +// url - String
  510 +//
  511 +// Returns String.
  512 +function stripPjaxParam(url) {
  513 + return url
  514 + .replace(/\?_pjax=[^&]+&?/, '?')
  515 + .replace(/_pjax=[^&]+&?/, '')
  516 + .replace(/[\?&]$/, '')
  517 +}
  518 +
  519 +// Internal: Parse URL components and returns a Locationish object.
  520 +//
  521 +// url - String URL
  522 +//
  523 +// Returns HTMLAnchorElement that acts like Location.
  524 +function parseURL(url) {
  525 + var a = document.createElement('a')
  526 + a.href = url
  527 + return a
  528 +}
  529 +
  530 +// Internal: Build options Object for arguments.
  531 +//
  532 +// For convenience the first parameter can be either the container or
  533 +// the options object.
  534 +//
  535 +// Examples
  536 +//
  537 +// optionsFor('#container')
  538 +// // => {container: '#container'}
  539 +//
  540 +// optionsFor('#container', {push: true})
  541 +// // => {container: '#container', push: true}
  542 +//
  543 +// optionsFor({container: '#container', push: true})
  544 +// // => {container: '#container', push: true}
  545 +//
  546 +// Returns options Object.
  547 +function optionsFor(container, options) {
  548 + // Both container and options
  549 + if ( container && options )
  550 + options.container = container
  551 +
  552 + // First argument is options Object
  553 + else if ( $.isPlainObject(container) )
  554 + options = container
  555 +
  556 + // Only container
  557 + else
  558 + options = {container: container}
  559 +
  560 + // Find and validate container
  561 + if (options.container)
  562 + options.container = findContainerFor(options.container)
  563 +
  564 + return options
  565 +}
  566 +
  567 +// Internal: Find container element for a variety of inputs.
  568 +//
  569 +// Because we can't persist elements using the history API, we must be
  570 +// able to find a String selector that will consistently find the Element.
  571 +//
  572 +// container - A selector String, jQuery object, or DOM Element.
  573 +//
  574 +// Returns a jQuery object whose context is `document` and has a selector.
  575 +function findContainerFor(container) {
  576 + container = $(container)
  577 +
  578 + if ( !container.length ) {
  579 + throw "no pjax container for " + container.selector
  580 + } else if ( container.selector !== '' && container.context === document ) {
  581 + return container
  582 + } else if ( container.attr('id') ) {
  583 + return $('#' + container.attr('id'))
  584 + } else {
  585 + throw "cant get selector for pjax container!"
  586 + }
  587 +}
  588 +
  589 +// Internal: Filter and find all elements matching the selector.
  590 +//
  591 +// Where $.fn.find only matches descendants, findAll will test all the
  592 +// top level elements in the jQuery object as well.
  593 +//
  594 +// elems - jQuery object of Elements
  595 +// selector - String selector to match
  596 +//
  597 +// Returns a jQuery object.
  598 +function findAll(elems, selector) {
  599 + return elems.filter(selector).add(elems.find(selector));
  600 +}
  601 +
  602 +function parseHTML(html) {
  603 + return $.parseHTML(html, document, true)
  604 +}
  605 +
  606 +// Internal: Extracts container and metadata from response.
  607 +//
  608 +// 1. Extracts X-PJAX-URL header if set
  609 +// 2. Extracts inline <title> tags
  610 +// 3. Builds response Element and extracts fragment if set
  611 +//
  612 +// data - String response data
  613 +// xhr - XHR response
  614 +// options - pjax options Object
  615 +//
  616 +// Returns an Object with url, title, and contents keys.
  617 +function extractContainer(data, xhr, options) {
  618 + var obj = {}
  619 +
  620 + // Prefer X-PJAX-URL header if it was set, otherwise fallback to
  621 + // using the original requested url.
  622 + obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl)
  623 +
  624 + // Attempt to parse response html into elements
  625 + if (/<html/i.test(data)) {
  626 + var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))
  627 + var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
  628 + } else {
  629 + var $head = $body = $(parseHTML(data))
  630 + }
  631 +
  632 + // If response data is empty, return fast
  633 + if ($body.length === 0)
  634 + return obj
  635 +
  636 + // If there's a <title> tag in the header, use it as
  637 + // the page's title.
  638 + obj.title = findAll($head, 'title').last().text()
  639 +
  640 + if (options.fragment) {
  641 + // If they specified a fragment, look for it in the response
  642 + // and pull it out.
  643 + if (options.fragment === 'body') {
  644 + var $fragment = $body
  645 + } else {
  646 + var $fragment = findAll($body, options.fragment).first()
  647 + }
  648 +
  649 + if ($fragment.length) {
  650 + obj.contents = $fragment.contents()
  651 +
  652 + // If there's no title, look for data-title and title attributes
  653 + // on the fragment
  654 + if (!obj.title)
  655 + obj.title = $fragment.attr('title') || $fragment.data('title')
  656 + }
  657 +
  658 + } else if (!/<html/i.test(data)) {
  659 + obj.contents = $body
  660 + }
  661 +
  662 + // Clean up any <title> tags
  663 + if (obj.contents) {
  664 + // Remove any parent title elements
  665 + obj.contents = obj.contents.not(function() { return $(this).is('title') })
  666 +
  667 + // Then scrub any titles from their descendants
  668 + obj.contents.find('title').remove()
  669 +
  670 + // Gather all script[src] elements
  671 + //obj.scripts = findAll(obj.contents, 'script[src]').remove()
  672 + obj.contents = obj.contents.not(obj.scripts)
  673 + }
  674 +
  675 + // Trim any whitespace off the title
  676 + if (obj.title) obj.title = $.trim(obj.title)
  677 +
  678 + return obj
  679 +}
  680 +
  681 +// Load an execute scripts using standard script request.
  682 +//
  683 +// Avoids jQuery's traditional $.getScript which does a XHR request and
  684 +// globalEval.
  685 +//
  686 +// scripts - jQuery object of script Elements
  687 +//
  688 +// Returns nothing.
  689 +function executeScriptTags(scripts) {
  690 + if (!scripts) return
  691 +
  692 + var existingScripts = $('script[src]')
  693 +
  694 + scripts.each(function() {
  695 + var src = this.src
  696 + var matchedScripts = existingScripts.filter(function() {
  697 + return this.src === src
  698 + })
  699 + if (matchedScripts.length) return
  700 +
  701 + var script = document.createElement('script')
  702 + script.type = $(this).attr('type')
  703 + script.src = $(this).attr('src')
  704 + document.head.appendChild(script)
  705 + })
  706 +}
  707 +
  708 +// Internal: History DOM caching class.
  709 +var cacheMapping = {}
  710 +var cacheForwardStack = []
  711 +var cacheBackStack = []
  712 +
  713 +// Push previous state id and container contents into the history
  714 +// cache. Should be called in conjunction with `pushState` to save the
  715 +// previous container contents.
  716 +//
  717 +// id - State ID Number
  718 +// value - DOM Element to cache
  719 +//
  720 +// Returns nothing.
  721 +function cachePush(id, value) {
  722 + cacheMapping[id] = value
  723 + cacheBackStack.push(id)
  724 +
  725 + // Remove all entires in forward history stack after pushing
  726 + // a new page.
  727 + while (cacheForwardStack.length)
  728 + delete cacheMapping[cacheForwardStack.shift()]
  729 +
  730 + // Trim back history stack to max cache length.
  731 + while (cacheBackStack.length > pjax.defaults.maxCacheLength)
  732 + delete cacheMapping[cacheBackStack.shift()]
  733 +}
  734 +
  735 +// Shifts cache from directional history cache. Should be
  736 +// called on `popstate` with the previous state id and container
  737 +// contents.
  738 +//
  739 +// direction - "forward" or "back" String
  740 +// id - State ID Number
  741 +// value - DOM Element to cache
  742 +//
  743 +// Returns nothing.
  744 +function cachePop(direction, id, value) {
  745 + var pushStack, popStack
  746 + cacheMapping[id] = value
  747 +
  748 + if (direction === 'forward') {
  749 + pushStack = cacheBackStack
  750 + popStack = cacheForwardStack
  751 + } else {
  752 + pushStack = cacheForwardStack
  753 + popStack = cacheBackStack
  754 + }
  755 +
  756 + pushStack.push(id)
  757 + if (id = popStack.pop())
  758 + delete cacheMapping[id]
  759 +}
  760 +
  761 +// Public: Find version identifier for the initial page load.
  762 +//
  763 +// Returns String version or undefined.
  764 +function findVersion() {
  765 + return $('meta').filter(function() {
  766 + var name = $(this).attr('http-equiv')
  767 + return name && name.toUpperCase() === 'X-PJAX-VERSION'
  768 + }).attr('content')
  769 +}
  770 +
  771 +// Install pjax functions on $.pjax to enable pushState behavior.
  772 +//
  773 +// Does nothing if already enabled.
  774 +//
  775 +// Examples
  776 +//
  777 +// $.pjax.enable()
  778 +//
  779 +// Returns nothing.
  780 +function enable() {
  781 + $.fn.pjax = fnPjax
  782 + $.pjax = pjax
  783 + $.pjax.enable = $.noop
  784 + $.pjax.disable = disable
  785 + $.pjax.click = handleClick
  786 + $.pjax.submit = handleSubmit
  787 + $.pjax.reload = pjaxReload
  788 + $.pjax.defaults = {
  789 + timeout: 650,
  790 + push: true,
  791 + replace: false,
  792 + type: 'GET',
  793 + dataType: 'html',
  794 + scrollTo: 0,
  795 + maxCacheLength: 20,
  796 + version: findVersion
  797 + }
  798 + $(window).on('popstate.pjax', onPjaxPopstate)
  799 +}
  800 +
  801 +// Disable pushState behavior.
  802 +//
  803 +// This is the case when a browser doesn't support pushState. It is
  804 +// sometimes useful to disable pushState for debugging on a modern
  805 +// browser.
  806 +//
  807 +// Examples
  808 +//
  809 +// $.pjax.disable()
  810 +//
  811 +// Returns nothing.
  812 +function disable() {
  813 + $.fn.pjax = function() { return this }
  814 + $.pjax = fallbackPjax
  815 + $.pjax.enable = enable
  816 + $.pjax.disable = $.noop
  817 + $.pjax.click = $.noop
  818 + $.pjax.submit = $.noop
  819 + $.pjax.reload = function() { window.location.reload() }
  820 +
  821 + $(window).off('popstate.pjax', onPjaxPopstate)
  822 +}
  823 +
  824 +
  825 +// Add the state property to jQuery's event object so we can use it in
  826 +// $(window).bind('popstate')
  827 +if ( $.inArray('state', $.event.props) < 0 )
  828 + $.event.props.push('state')
  829 +
  830 +// Is pjax supported by this browser?
  831 +$.support.pjax =
  832 + window.history && window.history.pushState && window.history.replaceState &&
  833 + // pushState isn't reliable on iOS until 5.
  834 + !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
  835 +
  836 +$.support.pjax ? enable() : disable()
  837 +
  838 +})(jQuery);
... ...
plugins/pjax/public/javascripts/loading-overlay.js 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +if (typeof loading_overlay === 'undefined') {
  2 +
  3 +// block user actions while making a post. Also indicate the network transaction
  4 +loading_overlay = {
  5 +
  6 + show: function (selector) {
  7 + var element = jQuery(selector);
  8 + var overlay = jQuery('<div>', {
  9 + class: 'loading-overlay',
  10 + css: {
  11 + width: element.outerWidth(),
  12 + height: element.outerHeight(),
  13 + left: element.position().left,
  14 + top: element.position().top,
  15 + marginLeft: parseFloat(element.css('margin-left')),
  16 + marginTop: parseFloat(element.css('margin-top')),
  17 + marginRight: parseFloat(element.css('margin-right')),
  18 + marginBottom: parseFloat(element.css('margin-bottom')),
  19 + },
  20 + }).appendTo(element).get(0);
  21 +
  22 + overlay.dest = element;
  23 + element.loading_overlay = overlay;
  24 + },
  25 +
  26 + hide: function (selector) {
  27 + var element = jQuery(selector);
  28 + var overlay = element.find('.loading-overlay');
  29 + overlay.remove();
  30 + },
  31 +
  32 +};
  33 +
  34 +}
... ...
plugins/pjax/public/javascripts/patchwork.js 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +var patch = (function () {
  2 + /*jshint evil: true */
  3 +
  4 + "use strict";
  5 +
  6 + var global = new Function("return this;")(), // Get a reference to the global object
  7 + fnProps = Object.getOwnPropertyNames(Function); // Get the own ("static") properties of the Function constructor
  8 +
  9 + return function (original, originalRef, patches) {
  10 +
  11 + var ref = global[originalRef] = original, // Maintain a reference to the original constructor as a new property on the global object
  12 + args = [],
  13 + newRef, // This will be the new patched constructor
  14 + i;
  15 +
  16 + patches.called = patches.called || originalRef; // If we are not patching static calls just pass them through to the original function
  17 +
  18 + for (i = 0; i < original.length; i++) { // Match the arity of the original constructor
  19 + args[i] = "a" + i; // Give the arguments a name (native constructors don't care, but user-defined ones will break otherwise)
  20 + }
  21 +
  22 + if (patches.constructed) { // This string is evaluated to create the patched constructor body in the case that we are patching newed calls
  23 + args.push("'use strict'; return (!!this ? " + patches.constructed + " : " + patches.called + ").apply(null, arguments);");
  24 + } else { // This string is evaluated to create the patched constructor body in the case that we are only patching static calls
  25 + args.push("'use strict'; return (!!this ? new (Function.prototype.bind.apply(" + originalRef + ", [{}].concat([].slice.call(arguments))))() : " + patches.called + ".apply(null, arguments));");
  26 + }
  27 +
  28 + newRef = new (Function.prototype.bind.apply(Function, [{}].concat(args)))(); // Create a new function to wrap the patched constructor
  29 + newRef.prototype = original.prototype; // Keep a reference to the original prototype to ensure instances of the patch appear as instances of the original
  30 + newRef.prototype.constructor = newRef; // Ensure the constructor of patched instances is the patched constructor
  31 +
  32 + Object.getOwnPropertyNames(ref).forEach(function (property) { // Add any "static" properties of the original constructor to the patched one
  33 + if (fnProps.indexOf(property) < 0) { // Don't include static properties of Function since the patched constructor will already have them
  34 + newRef[property] = ref[property];
  35 + }
  36 + });
  37 +
  38 + return newRef; // Return the patched constructor
  39 + };
  40 +
  41 +}());
0 42 \ No newline at end of file
... ...
plugins/pjax/public/javascripts/pjax.js 0 → 100644
... ... @@ -0,0 +1,179 @@
  1 +
  2 +pjax = {
  3 +
  4 + states: {},
  5 + current_state: null,
  6 + initial_state: null,
  7 +
  8 + themes: {},
  9 +
  10 + load: function() {
  11 + var target = jQuery('#content');
  12 + var content = jQuery('#content-inner');
  13 + var loadingTarget = jQuery('#content');
  14 +
  15 + var container = '.pjax-container';
  16 + target.addClass('pjax-container');
  17 +
  18 + jQuery(document).pjax('a', container);
  19 +
  20 + jQuery(document).on('pjax:beforeSend', function(event, xhr, settings) {
  21 + var themes = jQuery.map(pjax.themes, function(theme) { return theme.id }).join(',');
  22 + xhr.setRequestHeader('X-PJAX-Themes', themes);
  23 + });
  24 +
  25 + jQuery(document).on('pjax:send', function(event) {
  26 + /* initial state is only initialized after the first navigation,
  27 + * so we do associate it here */
  28 + if (!pjax.states[jQuery.pjax.state.id])
  29 + pjax.states[jQuery.pjax.state.id] = pjax.initial_state;
  30 +
  31 + loading_overlay.show(loadingTarget);
  32 + });
  33 + jQuery(document).on('pjax:complete', function(event) {
  34 + loading_overlay.hide(loadingTarget);
  35 + });
  36 +
  37 + jQuery(document).on('pjax:popstate', function(event) {
  38 + pjax.popstate(event.state, event.direction);
  39 + });
  40 +
  41 + jQuery(document).on('pjax:timeout', function(event) {
  42 + // Prevent default timeout redirection behavior
  43 + event.preventDefault();
  44 + });
  45 +
  46 + pjax.patch.document_write();
  47 + //pjax.patch.xhr();
  48 + },
  49 +
  50 + update: function(state, from_state) {
  51 + if (!from_state)
  52 + from_state = this.current_state || this.initial_state;
  53 +
  54 + if (state.layout_template != from_state.layout_template) {
  55 + var lt_css = jQuery('head link[href^="/designs/templates"]');
  56 + lt_css.attr('href', lt_css.attr('href').replace(/templates\/.+\/stylesheets/, 'templates/'+state.layout_template+'/stylesheets'));
  57 + }
  58 +
  59 + if (state.theme.id != from_state.theme.id)
  60 + this.update_theme(state, from_state);
  61 +
  62 + document.body.className = state.body_classes;
  63 +
  64 + render_all_jquery_ui_widgets();
  65 +
  66 + userDataCallback(noosfero.user_data);
  67 +
  68 + // theme's update dependent on content. must be last thing to run
  69 + if (state.theme_update_js)
  70 + jQuery.globalEval(state.theme_update_js);
  71 +
  72 + pjax.current_state = state;
  73 + },
  74 +
  75 + update_theme: function(state, from_state) {
  76 + // wait for the new theme css load
  77 + this.loading.show(function() {
  78 + return !pjax.css_loaded('/designs/themes/'+state.theme.id+'/style.css');
  79 + });
  80 +
  81 + var css = jQuery('head link[href^="/designs/themes/'+from_state.theme.id+'/style"]');
  82 + css.attr('href', css.attr('href').replace(/themes\/.+\/style/, 'themes/'+state.theme.id+'/style'));
  83 +
  84 + jQuery('head link[rel="shortcut icon"]').attr('href', state.theme.favicon);
  85 +
  86 + jQuery('#theme-header').html(state.theme.header);
  87 + jQuery('#site-title').html(state.theme.site_title);
  88 + jQuery('#navigation ul').html(state.theme.extra_navigation);
  89 + jQuery('#theme-footer').html(state.theme.footer);
  90 +
  91 + jQuery('head script[src^="/designs/themes/'+from_state.theme.id+'/theme.js"]').remove();
  92 + if (state.theme.js_src) {
  93 + var script = document.createElement('script');
  94 + script.type = 'text/javascript', script.src = state.theme.js_src;
  95 + document.head.appendChild(script);
  96 + }
  97 + },
  98 +
  99 + popstate: function(state, direction) {
  100 + state = pjax.states[state.id];
  101 + var from_state = pjax.states[jQuery.pjax.state.id];
  102 + pjax.update(state, from_state);
  103 + },
  104 +
  105 + loading: {
  106 + repeatCallback: null,
  107 +
  108 + show: function(repeatCallback) {
  109 + this.repeatCallback = repeatCallback;
  110 + this.gears().show();
  111 + this.pool();
  112 + },
  113 +
  114 + pool: function() {
  115 + setTimeout(this.timeout, 50);
  116 + },
  117 +
  118 + timeout: function() {
  119 + var repeat = pjax.loading.repeatCallback();
  120 + if (repeat)
  121 + pjax.loading.pool();
  122 + else
  123 + pjax.loading.gears().hide();
  124 + },
  125 +
  126 + gears: function() {
  127 + var gears = jQuery('#pjax-loading-gears');
  128 + if (!gears.length) {
  129 + gears = jQuery('<div>', {
  130 + id: 'pjax-loading-gears',
  131 + });
  132 + gears.appendTo(document.body);
  133 + }
  134 +
  135 + return gears;
  136 + },
  137 + },
  138 +
  139 + css_loaded: function(path) {
  140 + var found = false;
  141 + for (index in document.styleSheets) {
  142 + var stylesheet = document.styleSheets[index];
  143 + if (!stylesheet.href)
  144 + continue;
  145 +
  146 + found = stylesheet.href.indexOf(path) != -1;
  147 + if (found)
  148 + break;
  149 + }
  150 + return found;
  151 + },
  152 +
  153 + patch: {
  154 +
  155 + document_write: function () {
  156 + // document.write doesn't work after ready state
  157 + document._write = document.write;
  158 + document.write = function (data) {
  159 + if (document.readyState != 'loading')
  160 + content.append(data);
  161 + else
  162 + document._write(data);
  163 + };
  164 + },
  165 +
  166 + xhr: function () {
  167 + XMLHttpRequest = patch(XMLHttpRequest, '_XMLHttpRequest', {
  168 + constructed: function() {
  169 + console.log('here')
  170 +
  171 + var args = [].slice.call(arguments);
  172 + return new (Function.prototype.bind.apply(_XMLHttpRequest, [{}].concat(args)));
  173 + },
  174 + });
  175 + },
  176 +
  177 + },
  178 +};
  179 +
... ...
plugins/pjax/public/style.css 0 → 120000
... ... @@ -0,0 +1 @@
  1 +stylesheets/pjax.css
0 2 \ No newline at end of file
... ...
plugins/pjax/public/stylesheets/_loading-overlay.scss 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +.loading-overlay {
  3 + position: absolute;
  4 + background-image: url(/plugins/pjax/images/loading-overlay.gif);
  5 + opacity: 0.1;
  6 + z-index: 10;
  7 +}
... ...
plugins/pjax/public/stylesheets/pjax.css 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +.loading-overlay {
  2 + position: absolute;
  3 + background-image: url(/plugins/pjax/images/loading-overlay.gif);
  4 + opacity: 0.1;
  5 + z-index: 10; }
  6 +
  7 +#pjax-loading-gears {
  8 + display: none;
  9 + position: fixed;
  10 + top: 0;
  11 + left: 0;
  12 + right: 0;
  13 + bottom: 0;
  14 + z-index: 9999;
  15 + background: white url(/plugins/pjax/images/loading-gears.gif) no-repeat center center;
  16 + background-size: 5%; }
... ...
plugins/pjax/public/stylesheets/pjax.scss 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +@import 'loading-overlay';
  2 +
  3 +#pjax-loading-gears {
  4 + display: none; //default
  5 + position: fixed;
  6 + top: 0;
  7 + left: 0;
  8 + right: 0;
  9 + bottom: 0;
  10 + z-index: 9999;
  11 + background: white url(/plugins/pjax/images/loading-gears.gif) no-repeat center center;
  12 + background-size: 5%;
  13 +}
  14 +
... ...
plugins/pjax/vendor/plugins/xhr_status_except_pjax/init.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class ActionDispatch::Request
  2 +
  3 + def xml_http_request_with_pjax?
  4 + xml_http_request_without_pjax? and @env['HTTP_X_PJAX'].blank?
  5 + end
  6 +
  7 +end
  8 +
  9 + ActionDispatch::Request.send :alias_method_chain, :xml_http_request?, :pjax
  10 + ActionDispatch::Request.send :alias_method, :xhr?, :xml_http_request?
  11 +
... ...
plugins/pjax/views/layouts/pjax.html.erb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +<%
  2 + update_js = render(:file => "#{Rails.root}/public/designs/themes/#{current_theme}/pjax_update.js.erb").to_json rescue nil
  3 +%>
  4 +
  5 +<title><%= h page_title %></title>
  6 +
  7 +<%= render :file => "#{Rails.root}/public/designs/themes/#{current_theme}/layouts/_content.html.erb" rescue
  8 + render "layouts/content" %>
  9 +
  10 +<%= javascript_tag do %>
  11 + <%= render 'pjax_shared/load_state.js' %>
  12 +
  13 + <% if update_js %>
  14 + state.theme_update_js = <%= update_js %>;
  15 + <% end %>
  16 +
  17 + pjax.update(state);
  18 +<% end %>
  19 +
... ...
plugins/pjax/views/pjax_layouts/_load_state_script.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<script type="text/javascript">
  2 + jQuery(function($) {
  3 + pjax.load();
  4 +
  5 + <%= render 'pjax_shared/load_state.js' %>
  6 + });
  7 +</script>
... ...
plugins/pjax/views/pjax_shared/_load_state.js.erb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +var theme_id = <%= current_theme.to_json %>;
  2 +
  3 +<% if @pjax_loaded_themes.blank? or not @pjax_loaded_themes.include? current_theme %>
  4 + pjax.themes[theme_id] = {
  5 + id: theme_id,
  6 + favicon: '<%= image_path theme_favicon %>',
  7 + header: '<%= escape_javascript theme_header %>',
  8 + site_title: '<%= escape_javascript theme_site_title %>',
  9 + extra_navigation: '<%= escape_javascript theme_extra_navigation %>',
  10 + footer: '<%= escape_javascript theme_footer %>',
  11 + js_src: <%= theme_javascript_src.to_json %>,
  12 + };
  13 +<% end %>
  14 +
  15 +var state = {
  16 + body_classes: <%= body_classes.to_json %>,
  17 + layout_template: <%= layout_template.to_json %>,
  18 + theme: pjax.themes[theme_id],
  19 +};
  20 +
  21 +if (jQuery.pjax.state)
  22 + pjax.states[jQuery.pjax.state.id] = state;
  23 +
  24 +if (!pjax.initial_state)
  25 + pjax.initial_state = state;
  26 +
... ...