fix: resolve 3 security audit issues
#3: Stored XSS in stats dashboard - escape p[path] with html.escape() #4: Caddy timeout race - increase read/write_timeout 30s -> 60s #5: Missing CSP header - add Content-Security-Policy to Caddyfile
This commit is contained in:
641
cache/4853d7f210093728ff9ad1ca4d54f9e4
vendored
Normal file
641
cache/4853d7f210093728ff9ad1ca4d54f9e4
vendored
Normal file
@@ -0,0 +1,641 @@
|
||||
// Place your application-specific JavaScript functions and classes here
|
||||
// This file is automatically included by javascript_include_tag :defaults
|
||||
|
||||
//things to do when the page loads
|
||||
$j(document).ready(function() {
|
||||
setupToggled();
|
||||
if ($j('form#work-form')) { hideFormFields(); }
|
||||
hideHideMe();
|
||||
showShowMe();
|
||||
handlePopUps();
|
||||
attachCharacterCounters();
|
||||
setupAccordion();
|
||||
setupDropdown();
|
||||
updateCachedTokens();
|
||||
|
||||
// add clear to items on the splash page in older browsers
|
||||
$j('.splash').children('div:nth-of-type(odd)').addClass('odd');
|
||||
|
||||
// make Share buttons on works and own bookmarks visible
|
||||
$j('.actions').children('.share').removeClass('hidden');
|
||||
|
||||
// make Approve buttons on inbox items visible
|
||||
$j('#inbox-form, .messages').find('.unreviewed').find('.review').find('a').removeClass('hidden');
|
||||
|
||||
prepareDeleteLinks();
|
||||
thermometer();
|
||||
$j('body').addClass('javascript');
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Autocomplete
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
function get_token_input_options(self) {
|
||||
return {
|
||||
searchingText: self.data('autocomplete-searching-text'),
|
||||
hintText: self.data('autocomplete-hint-text'),
|
||||
noResultsText: self.data('autocomplete-no-results-text'),
|
||||
minChars: self.data('autocomplete-min-chars'),
|
||||
queryParam: "term",
|
||||
preventDuplicates: true,
|
||||
tokenLimit: self.data('autocomplete-token-limit'),
|
||||
liveParams: self.data('autocomplete-live-params'),
|
||||
makeSortable: self.data('autocomplete-sortable')
|
||||
};
|
||||
}
|
||||
|
||||
// Look for autocomplete_options in application helper and throughout the views to
|
||||
// see how to use this!
|
||||
var input = $j('input.autocomplete');
|
||||
if (input.livequery) {
|
||||
jQuery(function($) {
|
||||
$('input.autocomplete').livequery(function(){
|
||||
var self = $(this);
|
||||
var token_input_options = get_token_input_options(self);
|
||||
var method;
|
||||
try {
|
||||
method = $.parseJSON(self.data('autocomplete-method'));
|
||||
} catch (err) {
|
||||
method = self.data('autocomplete-method');
|
||||
}
|
||||
self.tokenInput(method, token_input_options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// expand, contract, shuffle
|
||||
jQuery(function($){
|
||||
$(".expand").each(function(){
|
||||
// start by hiding the list in the page
|
||||
list = $($(this).data("action-target"));
|
||||
if (!list.data("force-expand") || list.children().size() > 25 || list.data("force-contract")) {
|
||||
list.hide();
|
||||
$(this).show();
|
||||
} else {
|
||||
// show the shuffle and contract button only
|
||||
$(this).nextAll(".shuffle").show();
|
||||
$(this).next(".contract").show();
|
||||
}
|
||||
|
||||
// set up click event to expand the list
|
||||
$(this).click(function(event){
|
||||
list = $($(this).data("action-target"));
|
||||
list.show();
|
||||
|
||||
// show the contract & shuffle buttons and hide us
|
||||
$(this).next(".contract").show();
|
||||
$(this).nextAll(".shuffle").show();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
|
||||
$(".contract").each(function(){
|
||||
$(this).click(function(event){
|
||||
// hide the list when clicked
|
||||
list = $($(this).data("action-target"));
|
||||
list.hide();
|
||||
|
||||
// show the expand and shuffle buttons and hide us
|
||||
$(this).prev(".expand").show();
|
||||
$(this).nextAll(".shuffle").hide();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
||||
|
||||
$(".shuffle").each(function(){
|
||||
// shuffle the list's children when clicked
|
||||
$(this).click(function(event){
|
||||
list = $($(this).data("action-target"));
|
||||
list.children().shuffle();
|
||||
});
|
||||
});
|
||||
|
||||
$(".expand_all").each(function(){
|
||||
target = "." + $(this).data("target-class");
|
||||
$(this).click(function(event) {
|
||||
$(this).closest(target).find(".expand").click();
|
||||
});
|
||||
});
|
||||
|
||||
$(".contract_all").each(function(){
|
||||
target = "." + $(this).data("target-class");
|
||||
$(this).click(function(event) {
|
||||
$(this).closest(target).find(".contract").click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// check all or none within the parent fieldset, optionally with a string to match on the id attribute of the checkboxes
|
||||
// stored in the "data-checkbox-id-filter" attribute on the all/none links.
|
||||
// allow for some flexibility by checking the next and previous fieldset if the checkboxes aren't in this one
|
||||
jQuery(function($){
|
||||
$('.check_all').each(function(){
|
||||
$(this).click(function(event){
|
||||
var filter = $(this).data('checkbox-id-filter');
|
||||
var checkboxes;
|
||||
if (filter) {
|
||||
checkboxes = $(this).closest('fieldset').find('input[id*="' + filter + '"][type="checkbox"]');
|
||||
} else {
|
||||
checkboxes = $(this).closest("fieldset").find(':checkbox');
|
||||
if (checkboxes.length == 0) {
|
||||
checkboxes = $(this).closest("fieldset").next().find(':checkbox');
|
||||
if (checkboxes.length == 0) {
|
||||
checkboxes = $(this).closest("fieldset").prev().find(':checkbox');
|
||||
}
|
||||
}
|
||||
}
|
||||
checkboxes.prop('checked', true);
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
$('.check_none').each(function(){
|
||||
$(this).click(function(event){
|
||||
var filter = $(this).data('checkbox-id-filter');
|
||||
var checkboxes;
|
||||
if (filter) {
|
||||
checkboxes = $(this).closest('fieldset').find('input[id*="' + filter + '"][type="checkbox"]');
|
||||
} else {
|
||||
checkboxes = $(this).closest("fieldset").find(':checkbox');
|
||||
if (checkboxes.length == 0) {
|
||||
checkboxes = $(this).closest("fieldset").next().find(':checkbox');
|
||||
if (checkboxes.length == 0) {
|
||||
checkboxes = $(this).closest("fieldset").prev().find(':checkbox');
|
||||
}
|
||||
}
|
||||
}
|
||||
checkboxes.prop('checked', false);
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Set up open and close toggles for a given object
|
||||
// Typical setup (this will leave the toggled item open for users without javascript but hide the controls from them):
|
||||
// <a class="foo_open hidden">Open Foo</a>
|
||||
// <div id="foo" class="toggled">
|
||||
// foo!
|
||||
// <a class="foo_close hidden">Close</a>
|
||||
// </div>
|
||||
//
|
||||
// Notes:
|
||||
// - The open button CANNOT be inside the toggled div, the close button can be (but doesn't have to be)
|
||||
// - You can have multiple open and close buttons for the same div since those are labeled with classes
|
||||
// - You don't have to use div and a, those are just examples. Anything you put the toggled and _open/_close classes on will work.
|
||||
// - If you want the toggled item not to be visible to users without JavaScript by default, add the class "hidden" to the toggled item as well.
|
||||
// (and you can then add an alternative link for them using <noscript>)
|
||||
// - Generally reserved for toggling complex elements like bookmark forms and challenge sign-ups; for simple elements like lists use setupAccordion.
|
||||
function setupToggled(){
|
||||
$j('.toggled').filter(function(){
|
||||
return $j(this).closest('.userstuff').length === 0;
|
||||
}).each(function(){
|
||||
var node = $j(this);
|
||||
var open_toggles = $j('.' + node.attr('id') + "_open");
|
||||
var close_toggles = $j('.' + node.attr('id') + "_close");
|
||||
|
||||
if (node.hasClass('open')) {
|
||||
close_toggles.each(function(){$j(this).show();});
|
||||
open_toggles.each(function(){$j(this).hide();});
|
||||
} else {
|
||||
node.hide();
|
||||
close_toggles.each(function(){$j(this).hide();});
|
||||
open_toggles.each(function(){$j(this).show();});
|
||||
}
|
||||
|
||||
open_toggles.each(function(){
|
||||
$j(this).click(function(e){
|
||||
if ($j(this).attr('href') == '#') {e.preventDefault();}
|
||||
node.show();
|
||||
open_toggles.each(function(){$j(this).hide();});
|
||||
close_toggles.each(function(){$j(this).show();});
|
||||
});
|
||||
});
|
||||
|
||||
close_toggles.each(function(){
|
||||
$j(this).click(function(e){
|
||||
if ($j(this).attr('href') == '#') {e.preventDefault();}
|
||||
node.hide();
|
||||
close_toggles.each(function(){$j(this).hide();});
|
||||
open_toggles.each(function(){$j(this).show();});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hideHideMe() {
|
||||
$j('.hideme').each(function() { $j(this).hide(); });
|
||||
}
|
||||
|
||||
function showShowMe() {
|
||||
$j('.showme').each(function() { $j(this).show(); });
|
||||
}
|
||||
|
||||
function handlePopUps() {
|
||||
$j("a[data_popup]").click(function(event, element) {
|
||||
if (event.stopped) return;
|
||||
window.open($j(element).attr('href'));
|
||||
event.stop();
|
||||
});
|
||||
}
|
||||
|
||||
// used in nested form fields for deleting a nested resource
|
||||
// see prompt form for example
|
||||
function remove_section(link, class_of_section_to_remove) {
|
||||
$j(link).siblings(":input[type=hidden]").val("1"); // relies on the "_destroy" field being the nearest hidden field
|
||||
var section = $j(link).closest("." + class_of_section_to_remove);
|
||||
section.find(".required input, .required textarea").each(function(index) {
|
||||
var element = eval('validation_for_' + $j(this).attr('id'));
|
||||
element.disable();
|
||||
});
|
||||
section.hide();
|
||||
}
|
||||
|
||||
// used with nested form fields for dynamically stuffing in an extra partial
|
||||
// see challenge signup form and prompt form for an example
|
||||
function add_section(link, nested_model_name, content) {
|
||||
// get the right new_id which should be in a div with class "last_id" at the bottom of
|
||||
// the nearest section
|
||||
var last_id = parseInt($j(link).parent().siblings('.last_id').last().html());
|
||||
var new_id = last_id + 1;
|
||||
var regexp = new RegExp("new_" + nested_model_name, "g");
|
||||
content = content.replace(regexp, new_id);
|
||||
// kludgy: show the hidden remove_section link (we don't want it showing for non-js users)
|
||||
content = content.replace('class="hidden showme"', '');
|
||||
$j(link).parent().before(content);
|
||||
}
|
||||
|
||||
// An attempt to replace the various work form toggle methods with a more generic one
|
||||
function toggleFormField(element_id) {
|
||||
var ticky = $j('#' + element_id + '-show');
|
||||
if (ticky.is(':checked')) {
|
||||
$j('#' + element_id).removeClass('hidden');
|
||||
}
|
||||
else {
|
||||
$j('#' + element_id).addClass('hidden');
|
||||
if (element_id != 'chapters-options' && element_id != 'backdate-options') {
|
||||
$j('#' + element_id).find(':input[type!="hidden"]').each(function(index, d) {
|
||||
if ($j(d).attr('type') == "checkbox") {$j(d).attr('checked', false);}
|
||||
else {$j(d).val('');}
|
||||
});
|
||||
}
|
||||
}
|
||||
// We want to check this whether the ticky is checked or not
|
||||
if (element_id == 'chapters-options') {
|
||||
var item = document.getElementById('work_wip_length');
|
||||
if (item.value == 1 || item.value == '1') {item.value = '?';}
|
||||
else {item.value = 1;}
|
||||
}
|
||||
}
|
||||
|
||||
// Hides expandable form field options if Javascript is enabled
|
||||
function hideFormFields() {
|
||||
if ($j('form#work-form') != null) {
|
||||
var toHide = ['#co-authors-options', '#front-notes-options', '#end-notes-options', '#chapters-options',
|
||||
'#parent-options', '#series-options', '#backdate-options', '#override_tags-options'];
|
||||
|
||||
$j.each(toHide, function(index, name) {
|
||||
if ($j(name)) {
|
||||
if (!($j(name + '-show').is(':checked'))) { $j(name).addClass('hidden'); }
|
||||
}
|
||||
});
|
||||
$j('form#work-form').className = $j('form#work-form').className;
|
||||
}
|
||||
}
|
||||
|
||||
// Hides the extra checkbox fields in prompt form
|
||||
function hideField(id) {
|
||||
$j('#' + id).toggle();
|
||||
}
|
||||
|
||||
function attachCharacterCounters() {
|
||||
var countFn = function() {
|
||||
var counter = (function(input) {
|
||||
/* Character-counted inputs do not always have the same hierarchical relationship
|
||||
to their associated counter elements in the DOM, and some cc-inputs have
|
||||
duplicate ids. So search for the input's associated counter element first by id,
|
||||
then by checking the input's siblings, then by checking its cousins. */
|
||||
var cc = $j('.character_counter [id='+input.attr('id')+'_counter]');
|
||||
if (cc.length === 1) { return cc; } // id search, use attribute selector rather
|
||||
// than # to check for duplicate ids
|
||||
|
||||
cc = input.nextAll('.character_counter').first().find('.value'); // sibling search
|
||||
if (cc.length) { return cc; }
|
||||
|
||||
var parent = input.parent(); // 2 level cousin search
|
||||
for (var i = 0; i < 2; i++) {
|
||||
cc = parent.nextAll('.character_counter').find('.value');
|
||||
if (cc.length) { return cc; }
|
||||
parent = parent.parent();
|
||||
}
|
||||
|
||||
return $j(); // return empty jquery element if search found nothing
|
||||
})($j(this)),
|
||||
max = parseInt(counter.attr('data-maxlength'), 10),
|
||||
val = $j(this).val().replace(/\r\n/g,'\n').replace(/\r|\n/g,'\r\n'),
|
||||
remaining = max - val.length;
|
||||
|
||||
counter.html(remaining).attr("aria-valuenow", remaining);
|
||||
};
|
||||
|
||||
$j(document).on('keyup keydown mouseup mousedown change', '.observe_textlength', countFn);
|
||||
$j('.observe_textlength').each(countFn);
|
||||
}
|
||||
|
||||
// add attributes that are only needed in the primary menus and when JavaScript is enabled
|
||||
function setupDropdown(){
|
||||
$j('#header').find('.dropdown').attr("aria-haspopup", true);
|
||||
$j('#header').find('.dropdown, .dropdown .actions').children('a').attr({
|
||||
'class': 'dropdown-toggle',
|
||||
'data-toggle': 'dropdown',
|
||||
'data-target': '#'
|
||||
});
|
||||
$j('.dropdown').find('.menu').addClass("dropdown-menu");
|
||||
}
|
||||
|
||||
// Accordion-style collapsible widgets
|
||||
// The pane element can be shown or hidden using the expander (link)
|
||||
// Apply hidden to the pane element if it shouldn't be visible when JavaScript is disabled
|
||||
// Typical set up:
|
||||
// <li aria-haspopup="true">
|
||||
// <a href="#">Expander</a>
|
||||
// <div class="expandable">
|
||||
// foo!
|
||||
// </div>
|
||||
// </li>
|
||||
function setupAccordion() {
|
||||
$j(".expandable").filter(function() {
|
||||
return $j(this).closest(".userstuff").length === 0;
|
||||
}).each(function() {
|
||||
var pane = $j(this);
|
||||
// hide the pane element if it's not hidden by default
|
||||
if ( !pane.hasClass("hidden") ) {
|
||||
pane.addClass("hidden");
|
||||
};
|
||||
|
||||
// make the expander visible
|
||||
// add the default collapsed state
|
||||
// make it do the expanding and collapsing
|
||||
pane.prev().removeClass("hidden").addClass("collapsed").click(function(e) {
|
||||
var expander = $j(this);
|
||||
if (expander.attr('href') == '#') {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// change the classes upon clicking the expander
|
||||
expander.toggleClass("collapsed").toggleClass("expanded").next().toggleClass("hidden");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the /confirm_delete portion of delete links so user who have JS enabled will
|
||||
// be able to delete items via hyperlink (per rails/jquery-ujs) rather than a dedicated
|
||||
// form page.
|
||||
function prepareDeleteLinks() {
|
||||
$j('a[href$="/confirm_delete"][data-confirm]').each(function(){
|
||||
this.href = this.href.replace(/\/confirm_delete$/, "");
|
||||
$j(this).attr("data-method", "delete");
|
||||
});
|
||||
|
||||
// Removing non-default orphan_account pseuds from works
|
||||
$j('a[href$="/confirm_remove_pseud"][data-confirm]').each(function() {
|
||||
this.href = this.href.replace(/\/confirm_remove_pseud$/, "/remove_pseud");
|
||||
$j(this).attr("data-method", "put");
|
||||
});
|
||||
|
||||
// For purging assignments in gift exchanges. This is only on one page and easy to
|
||||
// check, so don't worry about adding a fallback data-confirm message.
|
||||
$j('a[href$="/confirm_purge"][data-confirm]').each(function() {
|
||||
this.href = this.href.replace(/\/confirm_purge$/, "/purge");
|
||||
$j(this).attr("data-method", "post");
|
||||
});
|
||||
}
|
||||
|
||||
/// Kudos
|
||||
$j(document).ready(function() {
|
||||
$j('input#kudo_submit').on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$j.ajax({
|
||||
type: 'POST',
|
||||
url: '/kudos.js',
|
||||
data: jQuery('#new_kudo').serialize(),
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var msg = 'Sorry, we were unable to save your kudos';
|
||||
|
||||
// When we hit the rate limit, the response from Rack::Attack is a plain text 429.
|
||||
if (jqXHR.status == "429") {
|
||||
msg = "Sorry, you can't leave more kudos right now. Please try again in a few minutes.";
|
||||
} else {
|
||||
var data = $j.parseJSON(jqXHR.responseText);
|
||||
if (data.error_message) {
|
||||
msg = data.error_message;
|
||||
}
|
||||
}
|
||||
|
||||
$j('#kudos_message').addClass('kudos_error').text(msg);
|
||||
},
|
||||
success: function(data) {
|
||||
$j('#kudos_message').addClass('notice').text('Thank you for leaving kudos!');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Scroll to the top of the comments section when loading additional pages via Ajax in comment pagination.
|
||||
$j('#comments_placeholder').on('click.rails', '.pagination a[data-remote]', function(e){
|
||||
$j.scrollTo('#comments_placeholder');
|
||||
});
|
||||
|
||||
// Scroll to the top of the comments section when loading comments via AJAX
|
||||
$j("#show_comments_link_top").on('click.rails', 'a[href*="show_comments"]', function(e){
|
||||
$j.scrollTo('#comments');
|
||||
});
|
||||
});
|
||||
|
||||
// For simple forms that appear to toggle between creating and destroying records
|
||||
// e.g. favorite tags, subscriptions
|
||||
// <form> needs ajax-create-destroy class, data-create-value, data-destroy-value
|
||||
// data-create-value: text of the button for creating, e.g. Favorite, Subscribe
|
||||
// data-destroy-value: text of button for destroying, e.g. Unfavorite, Unsubscribe
|
||||
// controller needs item_id and item_success_message for save success and
|
||||
// item_success_message for destroy success
|
||||
$j(document).ready(function() {
|
||||
$j('form.ajax-create-destroy').on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var form = $j(this);
|
||||
var formAction = form.attr('action');
|
||||
var formSubmit = form.find('[type="submit"]');
|
||||
var createValue = form.data('create-value');
|
||||
var destroyValue = form.data('destroy-value');
|
||||
var flashContainer = $j('.flash');
|
||||
|
||||
$j.ajax({
|
||||
type: 'POST',
|
||||
url: formAction,
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
flashContainer.removeClass('error').empty();
|
||||
if (data.item_id) {
|
||||
flashContainer.addClass('notice').html(data.item_success_message);
|
||||
formSubmit.val(destroyValue);
|
||||
form.append('<input name="_method" type="hidden" value="delete">');
|
||||
form.attr('action', formAction + '/' + data.item_id);
|
||||
} else {
|
||||
flashContainer.addClass('notice').html(data.item_success_message);
|
||||
formSubmit.val(createValue);
|
||||
form.find('input[name="_method"]').remove();
|
||||
form.attr('action', formAction.replace(/\/\d+/, ''));
|
||||
}
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown) {
|
||||
flashContainer.empty();
|
||||
flashContainer.addClass('error notice');
|
||||
try {
|
||||
jQuery.parseJSON(xhr.responseText);
|
||||
} catch (e) {
|
||||
flashContainer.append("We're sorry! Something went wrong.");
|
||||
return;
|
||||
}
|
||||
$j.each(jQuery.parseJSON(xhr.responseText).errors, function(index, error) {
|
||||
flashContainer.append(error + " ");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// For simple forms that update or destroy records and remove them from a listing
|
||||
// e.g. delete from history, mark as read, delete invitation request
|
||||
// <form> needs ajax-remove class
|
||||
// controller needs item_success_message
|
||||
$j(document).ready(function() {
|
||||
$j('form.ajax-remove').on("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var form = $j(this);
|
||||
var formAction = form.attr('action');
|
||||
// The record we're removing is probably in a list, but might be in a table
|
||||
if (form.closest('li.group').length !== 0) {
|
||||
formParent = form.closest('li.group');
|
||||
} else { formParent = form.closest('tr'); };
|
||||
// The admin div does not hold a flash container
|
||||
var parentContainer = formParent.closest('div:not(.admin)');
|
||||
var flashContainer = parentContainer.find('.flash');
|
||||
|
||||
$j.ajax({
|
||||
type: 'POST',
|
||||
url: formAction,
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
flashContainer.removeClass('error').empty();
|
||||
flashContainer.addClass('notice').html(data.item_success_message);
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown) {
|
||||
flashContainer.empty();
|
||||
flashContainer.addClass('error notice');
|
||||
try {
|
||||
jQuery.parseJSON(xhr.responseText);
|
||||
} catch (e) {
|
||||
flashContainer.append("We're sorry! Something went wrong.");
|
||||
return;
|
||||
}
|
||||
$j.each(jQuery.parseJSON(xhr.responseText).errors, function(index, error) {
|
||||
flashContainer.append(error + " ");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$j(document).ajaxSuccess(function() {
|
||||
formParent.slideUp(function() {
|
||||
$j(this).remove();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FUNDRAISING THERMOMETER adapted from http://jsfiddle.net/GeekyJohn/vQ4Xn/
|
||||
function thermometer() {
|
||||
var banners = $j('.announcement').filter(function(){
|
||||
return $j(this).closest('.userstuff').length === 0;
|
||||
});
|
||||
|
||||
banners.has('.goal').each(function(){
|
||||
var banner_content = $j(this).find('.userstuff');
|
||||
banner_goal_text = banner_content.find('span.goal').html();
|
||||
banner_progress_text = banner_content.find('span.progress').html();
|
||||
if ($j(this).find('span.goal').hasClass('stretch')){
|
||||
stretch = true
|
||||
} else { stretch = false }
|
||||
|
||||
goal_amount = parseFloat(banner_goal_text.replace(/\.(?![0-9])|[^\.0-9]/g, ''));
|
||||
progress_amount = parseFloat(banner_progress_text.replace(/\.(?![0-9])|[^\.0-9]/g, ''));
|
||||
percentage_amount = Math.min( Math.round(progress_amount / goal_amount * 1000) / 10, 100);
|
||||
|
||||
// add thermometer markup (with amounts)
|
||||
banner_content.append('<div class="thermometer-content"><div class="thermometer"><div class="track"><div class="goal"><span class="amount">' + banner_goal_text +'</span></div><div class="progress"><span class="amount">' + banner_progress_text + '</span></div></div></div></div>');
|
||||
|
||||
// set the progress indicator
|
||||
// darker green for over 100% stretch goals
|
||||
// green for 100%
|
||||
// yellow-green for 85-99%
|
||||
// yellow for 30-84%
|
||||
// orange for 0-29%
|
||||
if ( stretch == true ) {
|
||||
banner_content.find('div.track').css({
|
||||
'background': '#8eb92a',
|
||||
'background-image': 'linear-gradient(to bottom, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%)'
|
||||
});
|
||||
banner_content.find('div.progress').css({
|
||||
'width': percentage_amount + '%',
|
||||
'background': '#4d7c10',
|
||||
'background-image': 'linear-gradient(to bottom, #6e992f 0%, #4d7c10 50%, #3b7000 51%, #5d8e13 100%)'
|
||||
});
|
||||
} else if (percentage_amount >= 100) {
|
||||
banner_content.find('div.progress').css({
|
||||
'width': '100%',
|
||||
'background': '#8eb92a',
|
||||
'background-image': 'linear-gradient(to bottom, #bfd255 0%, #8eb92a 50%, #72aa00 51%, #9ecb2d 100%)'
|
||||
});
|
||||
} else if (percentage_amount >= 85) {
|
||||
banner_content.find('div.progress').css({
|
||||
'width': percentage_amount + '%',
|
||||
'background': '#d2e638',
|
||||
'background-image': 'linear-gradient(to bottom, #e6f0a3 0%, #d2e638 50%, #c3d825 51%, #dbf043 100%)'
|
||||
});
|
||||
} else if (percentage_amount >= 30) {
|
||||
banner_content.find('div.progress').css({
|
||||
'width': percentage_amount + '%',
|
||||
'background': '#fccd4d',
|
||||
'background-image': 'linear-gradient(to bottom, #fceabb 0%, #fccd4d 50%, #f8b500 51%, #fbdf93 100%)'
|
||||
});
|
||||
} else {
|
||||
banner_content.find('div.progress').css({
|
||||
'width': percentage_amount + '%',
|
||||
'background': '#f17432',
|
||||
'background-image': 'linear-gradient(to bottom, #feccb1 0%, #f17432 50%, #ea5507 51%, #fb955e 100%)'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateCachedTokens() {
|
||||
// we only do full page caching when users are logged out
|
||||
if ($j('#small_login').length > 0) {
|
||||
$j.getJSON("/token_dispenser.json", function( data ) {
|
||||
var token = data.token;
|
||||
// set token on fields
|
||||
$j('input[name=authenticity_token]').each(function(){
|
||||
$j(this).attr('value', token);
|
||||
});
|
||||
$j('meta[name=csrf-token]').attr('content', token);
|
||||
$j.event.trigger({ type: "loadedCSRF" });
|
||||
});
|
||||
} else {
|
||||
$j.event.trigger({ type: "loadedCSRF" });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user