Skip to content

Integrate version compare API info into footer_html endpoint #1499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 30, 2015
Merged
35 changes: 8 additions & 27 deletions readthedocs/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
from builds.models import Build, Version
from core.utils import trigger_build
from projects.models import Project, ImportedFile
from projects.version_handling import highest_version
from projects.version_handling import parse_version_failsafe
from restapi.views.footer_views import get_version_compare_data

from .utils import SearchMixin, PostAuthentication

Expand Down Expand Up @@ -144,34 +143,16 @@ def get_object_list(self, request):
self._meta.queryset = Version.objects.api(user=request.user, only_active=False)
return super(VersionResource, self).get_object_list(request)

def version_compare(self, request, **kwargs):
project = get_object_or_404(Project, slug=kwargs['project_slug'])
highest_version_obj, highest_version_comparable = highest_version(
project.versions.filter(active=True))
base = kwargs.get('base', None)
ret_val = {
'project': highest_version_obj,
'version': highest_version_comparable,
'is_highest': True,
}
if highest_version_obj:
ret_val['url'] = highest_version_obj.get_absolute_url()
ret_val['slug'] = highest_version_obj.slug,
def version_compare(self, request, project_slug, base=None, **kwargs):
project = get_object_or_404(Project, slug=project_slug)
if base and base != LATEST:
try:
base_version_obj = project.versions.get(slug=base)
base_version_comparable = parse_version_failsafe(
base_version_obj.verbose_name)
if base_version_comparable:
# This is only place where is_highest can get set. All
# error cases will be set to True, for non- standard
# versions.
ret_val['is_highest'] = (
base_version_comparable >= highest_version_comparable)
else:
ret_val['is_highest'] = True
base_version = project.versions.get(slug=base)
except (Version.DoesNotExist, TypeError):
ret_val['is_highest'] = True
base_version = None
else:
base_version = None
ret_val = get_version_compare_data(project, base_version)
return self.create_response(request, ret_val)

def build_version(self, request, **kwargs):
Expand Down
97 changes: 97 additions & 0 deletions readthedocs/core/static-src/core/js/doc-embed/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
var rtd = require('./rtd-data');
var versionCompare = require('./version-compare');


function init(build) {
var get_data = {
project: rtd['project'],
version: rtd['version'],
page: rtd['page'],
theme: rtd['theme'],
format: "jsonp",
};

// Crappy heuristic, but people change the theme name on us.
// So we have to do some duck typing.
if ("docroot" in rtd) {
get_data['docroot'] = rtd['docroot'];
}

if ("source_suffix" in rtd) {
get_data['source_suffix'] = rtd['source_suffix'];
}

if (window.location.pathname.indexOf('/projects/') === 0) {
get_data['subproject'] = true;
}

// Get footer HTML from API and inject it into the page.
$.ajax({
url: rtd.api_host + "/api/v2/footer_html/",
crossDomain: true,
xhrFields: {
withCredentials: true,
},
dataType: "jsonp",
data: get_data,
success: function (data) {
versionCompare.init(data.version_compare);
injectFooter(data);
setupBookmarkCSRFToken();
},
error: function () {
console.error('Error loading Read the Docs footer');
}
});
}


function injectFooter(data) {
// If the theme looks like ours, update the existing badge
// otherwise throw a a full one into the page.
if (build.is_rtd_theme()) {
$("div.rst-other-versions").html(data['html']);
} else {
$("body").append(data['html']);
}

if (!data['version_active']) {
$('.rst-current-version').addClass('rst-out-of-date');
} else if (!data['version_supported']) {
//$('.rst-current-version').addClass('rst-active-old-version')
}

// Show promo selectively
if (data.promo && build.show_promo()) {
var promo = new sponsorship.Promo(
data.promo_data.id,
data.promo_data.text,
data.promo_data.link,
data.promo_data.image
)
if (promo) {
promo.display();
}
}
}


function setupBookmarkCSRFToken() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably kill this for now -- we have killed the Bookmarking feature (it was half done, and probably not the most reasonable from a product perspective). However, this code does do some interesting things that we'll likely need to do when we actually look seriously at doing CNAME-supported javascript clients.

function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", $('a.bookmark[token]').attr('token'));
}
}
});
}


module.exports = {
init: init
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function init() {
// Add Grok the Docs Client
$.ajax({
url: "https://api.grokthedocs.com/static/javascript/bundle-client.js",
crossDomain: true,
dataType: "script",
});
}


module.exports = {
init: init
};
51 changes: 51 additions & 0 deletions readthedocs/core/static-src/core/js/doc-embed/mkdocs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Mkdocs specific JS code.
*/


var rtd = require('./rtd-data');


function init() {

// Override MkDocs styles
if ("builder" in rtd && rtd["builder"] == "mkdocs") {
$('<input>').attr({
type: 'hidden',
name: 'project',
value: rtd["project"]
}).appendTo('#rtd-search-form');
$('<input>').attr({
type: 'hidden',
name: 'version',
value: rtd["version"]
}).appendTo('#rtd-search-form');
$('<input>').attr({
type: 'hidden',
name: 'type',
value: 'file'
}).appendTo('#rtd-search-form');

$("#rtd-search-form").prop("action", rtd.api_host + "/elasticsearch/");

// Apply stickynav to mkdocs builds
var nav_bar = $('nav.wy-nav-side:first'),
win = $(window),
sticky_nav_class = 'stickynav',
apply_stickynav = function () {
if (nav_bar.height() <= win.height()) {
nav_bar.addClass(sticky_nav_class);
} else {
nav_bar.removeClass(sticky_nav_class);
}
};
win.on('resize', apply_stickynav);
apply_stickynav();
}

}


module.exports = {
init: init
};
16 changes: 16 additions & 0 deletions readthedocs/core/static-src/core/js/doc-embed/rtd-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* This exposes data injected during the RTD build into the template. It's
* provided via the global READTHEDOCS_DATA variable and is exposed here as a
* module for cleaner usage.
*/


var data = READTHEDOCS_DATA;


if (data.api_host === undefined) {
data.api_host = 'https://readthedocs.org';
}


module.exports = data;
131 changes: 131 additions & 0 deletions readthedocs/core/static-src/core/js/doc-embed/sphinx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Sphinx builder specific JS code.
*/


var rtd = require('./rtd-data');


function init() {

/// Read the Docs Sphinx theme code
if (!("builder" in rtd) || "builder" in rtd && rtd["builder"] != "mkdocs") {
function toggleCurrent (elem) {
var parent_li = elem.closest('li');
parent_li.siblings('li.current').removeClass('current');
parent_li.siblings().find('li.current').removeClass('current');
parent_li.find('> ul li.current').removeClass('current');
parent_li.toggleClass('current');
}

// Shift nav in mobile when clicking the menu.
$(document).on('click', "[data-toggle='wy-nav-top']", function() {
$("[data-toggle='wy-nav-shift']").toggleClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift");
});
// Nav menu link click operations
$(document).on('click', ".wy-menu-vertical .current ul li a", function() {
var target = $(this);
// Close menu when you click a link.
$("[data-toggle='wy-nav-shift']").removeClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift");
// Handle dynamic display of l3 and l4 nav lists
toggleCurrent(target);
if (typeof(window.SphinxRtdTheme) != 'undefined') {
window.SphinxRtdTheme.StickyNav.hashChange();
}
});
$(document).on('click', "[data-toggle='rst-current-version']", function() {
$("[data-toggle='rst-versions']").toggleClass("shift-up");
});
// Make tables responsive
$("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>");

// Add expand links to all parents of nested ul
$('.wy-menu-vertical ul').siblings('a').each(function () {
var link = $(this);
expand = $('<span class="toctree-expand"></span>');
expand.on('click', function (ev) {
toggleCurrent(link);
ev.stopPropagation();
return false;
});
link.prepend(expand);
});

// Sphinx theme state
window.SphinxRtdTheme = (function (jquery) {
var stickyNav = (function () {
var navBar,
win,
winScroll = false,
linkScroll = false,
winPosition = 0,
enable = function () {
init();
reset();
win.on('hashchange', reset);

// Set scrolling
win.on('scroll', function () {
if (!linkScroll) {
winScroll = true;
}
});
setInterval(function () {
if (winScroll) {
winScroll = false;
var newWinPosition = win.scrollTop(),
navPosition = navBar.scrollTop(),
newNavPosition = navPosition + (newWinPosition - winPosition);
navBar.scrollTop(newNavPosition);
winPosition = newWinPosition;
}
}, 25);
},
init = function () {
navBar = jquery('nav.wy-nav-side:first');
win = jquery(window);
},
reset = function () {
// Get anchor from URL and open up nested nav
var anchor = encodeURI(window.location.hash);
if (anchor) {
try {
var link = $('.wy-menu-vertical')
.find('[href="' + anchor + '"]');
$('.wy-menu-vertical li.toctree-l1 li.current')
.removeClass('current');
link.closest('li.toctree-l2').addClass('current');
link.closest('li.toctree-l3').addClass('current');
link.closest('li.toctree-l4').addClass('current');
}
catch (err) {
console.log("Error expanding nav for anchor", err);
}
}
},
hashChange = function () {
linkScroll = true;
win.one('hashchange', function () {
linkScroll = false;
});
};
jquery(init);
return {
enable: enable,
hashChange: hashChange
};
}());
return {
StickyNav: stickyNav
};
}($));
}

}


module.exports = {
init: init
};
Loading