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:
akiba
2026-06-30 14:03:48 +00:00
parent 085cc7a140
commit 122c408fff
32 changed files with 9066 additions and 859 deletions

View File

@@ -0,0 +1,8 @@
/*!
* JavaScript Cookie v2.2.1
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
!function(e){var n;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var t=window.Cookies,o=window.Cookies=e();o.noConflict=function(){return window.Cookies=t,o}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function t(o){function r(){}function i(n,t,i){if("undefined"!=typeof document){"number"==typeof(i=e({path:"/"},r.defaults,i)).expires&&(i.expires=new Date(1*new Date+864e5*i.expires)),i.expires=i.expires?i.expires.toUTCString():"";try{var c=JSON.stringify(t);/^[\{\[]/.test(c)&&(t=c)}catch(e){}t=o.write?o.write(t,n):encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[\(\)]/g,escape);var f="";for(var u in i)i[u]&&(f+="; "+u,!0!==i[u]&&(f+="="+i[u].split(";")[0]));return document.cookie=n+"="+t+f}}function c(e,t){if("undefined"!=typeof document){for(var r={},i=document.cookie?document.cookie.split("; "):[],c=0;c<i.length;c++){var f=i[c].split("="),u=f.slice(1).join("=");t||'"'!==u.charAt(0)||(u=u.slice(1,-1));try{var a=n(f[0]);if(u=(o.read||o)(u,a)||n(u),t)try{u=JSON.parse(u)}catch(e){}if(r[a]=u,e===a)break}catch(e){}}return e?r[e]:r}}return r.set=i,r.get=function(e){return c(e,!1)},r.getJSON=function(e){return c(e,!0)},r.remove=function(n,t){i(n,"",e(t,{expires:-1}))},r.defaults={},r.withConverter=t,r}(function(){})});

152
cache/0ff52d5b7b9f8180daa067cf312b4baf vendored Normal file
View File

@@ -0,0 +1,152 @@
/* MEDIA: only screen and (max-width: 62em), handheld ENDMEDIA */
/* 04 region dashboard */
#dashboard {
clear: both;
float: none;
margin: 1% 3.5%;
max-width: 100%;
padding: 0;
width: auto;
}
#dashboard, #dashboard.own {
border-bottom: 10px solid #900;
border-top: 10px solid #900;
padding: 0.5em 0;
border-radius: 0.25em;
}
#dashboard ul {
border: none;
display: inline;
padding: 0;
text-align: left;
}
#dashboard li {
display: inline;
}
#dashboard a, #dashboard span {
display: inline-block;
margin: 0.25em 0;
}
#dashboard .secondary {
background: #eee;
padding: 0.375em 0 0.625em;
box-shadow: inset 2px 2px 5px #bbb;
}
#dashboard .secondary a {
margin: 0.125em 0;
}
#dashboard .landmark {
clear: none;
float: left;
}
/* 05 region main */
#main, #main.dashboard {
float: none;
margin: auto;
padding-left: 3.5%;
padding-right: 3.5%;
width: auto;
}
/* 07 interactions */
form.single input[type="text"] {
width: 100%;
box-sizing: border-box;
}
form.single input[type="submit"] {
margin-top: 0.375em;
}
form.single span.submit {
display: block;
text-align: right;
}
form.single ul.autocomplete {
display: block;
}
form.single .autocomplete li.input {
margin-right: 0;
}
/* 08 actions */
.javascript .work.navigation .secondary {
width: 100%;
box-sizing: border-box;
}
/* If we use display without the .expanded qualifier, it will override .hidden's
display: none and secondary will always be open. */
.javascript .work.navigation .expanded + .secondary, .javascript .work.navigation .secondary li, .javascript .work.navigation .secondary p, .javascript .work.navigation .secondary a {
display: block;
}
.javascript .work.navigation .secondary p {
padding-block-start: 0.375em;
}
.javascript .work.navigation .secondary select {
max-width: 100%;
min-width: auto;
}
.javascript .work.navigation .secondary a {
height: 100%;
min-height: 1.286em;
padding-inline: 0.5em;
white-space: normal;
}
/* 16 zone system */
.logged-in .splash > .module {
width: 48.5%
}
.logged-in .splash > div:nth-of-type(odd) {
margin-left: 0;
margin-right: 1.5%;
}
.logged-in .splash > div:nth-of-type(even) {
margin-left: 1.5%;
margin-right: 0;
}
/* 18 zone searchbrowse */
form.filters {
width: auto;
min-width: 23%;
max-width: 24%;
}
.filters fieldset {
margin-right: 0;
}
form.filters dl {
margin-left: 0.25em;
margin-right: 0.25em;
}
/* 21 userstuff */
#workskin {
margin: auto 1.5%;
}

BIN
cache/17342e82684c267ba4f1e531738eb513 vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
cache/2f7d84701e69c2978451a4693dbf6b35 vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1 @@
function setupFilterToggles(){var e=$j(".filters").find("dt.filter-toggle");e.each(function(){var e=$j(this).next().attr("id");$j(this).wrapInner('<button type="button" class="expander" aria-expanded="false" aria-controls="'+e+'"></button>')}),$j("dt.tags button").on("click",function(){"false"==$j(this).attr("aria-expanded")?$j(this).attr("aria-expanded","true"):$j(this).attr("aria-expanded","false")})}function showFilters(){var e=$j(".filters").find("dd.expandable");e.each(function(e,t){var a=$j(t).find("input").filter('[value]:not([value=""])'),r=$j(t).attr("id"),n=$j("#toggle_"+r),i=$j('[aria-controls="'+r+'"]');a.each(function(e,a){$j(a).is(':checked, [type="text"]')&&($j(t).removeClass("hidden"),$j(n).removeClass("collapsed").addClass("expanded"),$j(i).attr("aria-expanded","true"))})})}function setupNarrowScreenFilters(){var e=$j("form.filters"),t=$j("#outer"),a=$j("#go_to_filters"),r=$j("#leave_filters");a.click(function(a){a.preventDefault(),e.removeClass("narrow-hidden"),t.addClass("filtering"),e.find(":focusable").first().focus(),e.trap()}),r.click(function(r){r.preventDefault(),t.removeClass("filtering"),e.addClass("narrow-hidden"),a.focus()})}$j(document).ready(function(){setupFilterToggles(),showFilters(),setupNarrowScreenFilters()});

76
cache/331a4a4d15181598c366618812c89376 vendored Normal file
View File

@@ -0,0 +1,76 @@
/* MEDIA: speech */
.landmark {
opacity: 1;
height: auto;
width: auto;
font-size: 100%;
line-height: 0;
color: black;
}
a.tag {
speak: no-punctuation;
}
em {
voice-stress: moderate;
}
strong, .warnings, .caution {
voice-stress: strong;
}
cite {
voice-rate: 95%;
}
acronym {
speak: spell-out;
}
abbr {
content: attr(title);
}
code, kbd, tt, samp {
voice-rate: 95%;
}
.userstuff {
speak-numeral: continuous;
}
.meta {
voice-rate: 110%;
}
.blurb {
voice-rate: 105%;
}
.userstuff h1, .userstuff h2.title {
voice-rate: 90%;
pause: 5ms 10ms;
}
.chapter {
pause: 10ms 10ms;
}
.userstuff dt {
voice-balance: leftwards;
}
.userstuff dd {
voice-balance: rightwards;
}
.navigation {
voice-stress: moderate;
speak: no-punctuation;
}
.splash .browse li a:before {
speak: none;
}

View File

@@ -0,0 +1 @@
!function(o){"use strict";var n="[data-toggle=dropdown]",t=function(n){var t=o(n).on("click.dropdown.data-api",this.toggle);o("html").on("click.dropdown.data-api",(function(){t.parent().removeClass("open")}))};function e(){o(n).each((function(){d(o(this)).removeClass("open")}))}function d(n){var t,e=n.attr("data-target");return e||(e=(e=n.attr("href"))&&/#/.test(e)&&e.replace(/.*(?=#[^\s]*$)/,"")),(t=e&&o(e))&&t.length||(t=n.parent()),t}t.prototype={constructor:t,toggle:function(n){var t,r,i=o(this);if(!i.is(".disabled, :disabled"))return r=(t=d(i)).hasClass("open"),e(),r?(t.children("ul").hide(),i.blur()):(t.toggleClass("open").children("ul").removeAttr("style"),i.focus()),i.focus(),!1},keydown:function(t){var e,r,i,a,s;if(/(38|40|27)/.test(t.keyCode)&&(e=o(this),t.preventDefault(),t.stopPropagation(),!e.is(".disabled, :disabled"))){if(!(a=(i=d(e)).hasClass("open"))||a&&27==t.keyCode)return 27==t.which&&i.find(n).focus(),e.click();(r=o("ul.menu li:not(.divider):visible a",i)).length&&(s=r.index(r.filter(":focus")),38==t.keyCode&&s>0&&s--,40==t.keyCode&&s<r.length-1&&s++,~s||(s=0),r.eq(s).focus())}}};var r=o.fn.dropdown;o.fn.dropdown=function(n){return this.each((function(){var e=o(this),d=e.data("dropdown");d||e.data("dropdown",d=new t(this)),"string"==typeof n&&d[n].call(e)}))},o.fn.dropdown.Constructor=t,o.fn.dropdown.noConflict=function(){return o.fn.dropdown=r,this},o(document).on("click.dropdown.data-api",e).on("click.dropdown.data-api",".dropdown form",(function(o){o.stopPropagation()})).on("click.dropdown-menu",(function(o){o.stopPropagation()})).on("click.dropdown.data-api",n,t.prototype.toggle).on("keydown.dropdown.data-api",n+", ul.menu",t.prototype.keydown).on("mouseenter",".dropdown",(function(n){var t=o(this);t.siblings(".open").length&&t.children("ul").hide()})).on("mouseleave",".dropdown",(function(n){o(this).children("ul").removeAttr("")}))}(window.jQuery);

View File

@@ -0,0 +1,8 @@
/*!
Copyright (c) 2011, 2012 Julien Wajsberg <felash@gmail.com>
All rights reserved.
Official repository: https://github.com/julienw/jquery-trap-input
License is there: https://github.com/julienw/jquery-trap-input/blob/master/LICENSE
This is version 1.2.0.
*/(function(e,t){function r(e){if(e.keyCode===9){var t=!!e.shiftKey;if(i(this,e.target,t)){e.preventDefault();e.stopPropagation()}}}function i(e,t,n){var r=a(e),i=t,s,o,u,f;do{s=r.index(i);o=s+1;u=s-1;f=r.length-1;switch(s){case-1:return false;case 0:u=f;break;case f:o=0;break}if(n){o=u}i=r.get(o);if(!i||i===t){return true}try{i.focus()}catch(l){return true}}while(r.length>1&&t===t.ownerDocument.activeElement);return true}function s(){return this.tabIndex>0}function o(){return!this.tabIndex}function u(e,t){return e.t-t.t||e.i-t.i}function a(t){var n=e(t);var r=[],i=0;h.enable&&h.enable();n.find("a[href], link[href], [draggable=true], [contenteditable=true], :input:enabled, [tabindex=0]").filter(":visible").filter(o).each(function(e,t){r.push({v:t,t:0,i:i++})});n.find("[tabindex]").filter(":visible").filter(s).each(function(e,t){r.push({v:t,t:t.tabIndex,i:i++})});h.disable&&h.disable();r=e.map(r.sort(u),function(e){return e.v});return e(r)}function f(){this.keydown(r);this.data(n,true);return this}function l(){this.unbind("keydown",r);this.removeData(n);return this}function c(){return!!this.data(n)}var n="trap.isTrapping";e.fn.extend({trap:f,untrap:l,isTrapping:c});var h={};if(e.find.find&&e.find.attr!==e.attr){(function(){function i(e){var r=e.getAttributeNode(n);return r&&r.specified?parseInt(r.value,10):t}function s(){r[n]=r.tabIndex=i}function o(){delete r[n];delete r.tabIndex}var n="tabindex";var r=e.expr.attrHandle;h={enable:s,disable:o}})()}})(jQuery);

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) 2007 Ariel Flesler - aflesler ○ gmail • com | https://github.com/flesler
* Licensed under MIT
* @author Ariel Flesler
* @version 2.1.2
*/
;(function(f){"use strict";"function"===typeof define&&define.amd?define(["jquery"],f):"undefined"!==typeof module&&module.exports?module.exports=f(require("jquery")):f(jQuery)})(function($){"use strict";function n(a){return!a.nodeName||-1!==$.inArray(a.nodeName.toLowerCase(),["iframe","#document","html","body"])}function h(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}var p=$.scrollTo=function(a,d,b){return $(window).scrollTo(a,d,b)};p.defaults={axis:"xy",duration:0,limit:!0};$.fn.scrollTo=function(a,d,b){"object"=== typeof d&&(b=d,d=0);"function"===typeof b&&(b={onAfter:b});"max"===a&&(a=9E9);b=$.extend({},p.defaults,b);d=d||b.duration;var u=b.queue&&1<b.axis.length;u&&(d/=2);b.offset=h(b.offset);b.over=h(b.over);return this.each(function(){function k(a){var k=$.extend({},b,{queue:!0,duration:d,complete:a&&function(){a.call(q,e,b)}});r.animate(f,k)}if(null!==a){var l=n(this),q=l?this.contentWindow||window:this,r=$(q),e=a,f={},t;switch(typeof e){case "number":case "string":if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(e)){e= h(e);break}e=l?$(e):$(e,q);case "object":if(e.length===0)return;if(e.is||e.style)t=(e=$(e)).offset()}var v=$.isFunction(b.offset)&&b.offset(q,e)||b.offset;$.each(b.axis.split(""),function(a,c){var d="x"===c?"Left":"Top",m=d.toLowerCase(),g="scroll"+d,h=r[g](),n=p.max(q,c);t?(f[g]=t[m]+(l?0:h-r.offset()[m]),b.margin&&(f[g]-=parseInt(e.css("margin"+d),10)||0,f[g]-=parseInt(e.css("border"+d+"Width"),10)||0),f[g]+=v[m]||0,b.over[m]&&(f[g]+=e["x"===c?"width":"height"]()*b.over[m])):(d=e[m],f[g]=d.slice&& "%"===d.slice(-1)?parseFloat(d)/100*n:d);b.limit&&/^\d+$/.test(f[g])&&(f[g]=0>=f[g]?0:Math.min(f[g],n));!a&&1<b.axis.length&&(h===f[g]?f={}:u&&(k(b.onAfterFirst),f={}))});k(b.onAfter)}})};p.max=function(a,d){var b="x"===d?"Width":"Height",h="scroll"+b;if(!n(a))return a[h]-$(a)[b.toLowerCase()]();var b="client"+b,k=a.ownerDocument||a.document,l=k.documentElement,k=k.body;return Math.max(l[h],k[h])-Math.min(l[b],k[b])};$.Tween.propHooks.scrollLeft=$.Tween.propHooks.scrollTop={get:function(a){return $(a.elem)[a.prop]()}, set:function(a){var d=this.get(a);if(a.options.interrupt&&a._last&&a._last!==d)return $(a.elem).stop();var b=Math.round(a.now);d!==b&&($(a.elem)[a.prop](b),a._last=this.get(a))}};return p});

641
cache/4853d7f210093728ff9ad1ca4d54f9e4 vendored Normal file
View 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" });
}
}

BIN
cache/648f0179eb5fdc03a5766870d2eb6550 vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

143
cache/7bddd8efbc2b1a767d0f063375e157ce vendored Normal file
View File

@@ -0,0 +1,143 @@
/*==SANDBOX: All temporary, development, and unreviewed css lives here.
Rules will be incorporated into the main cascade by Front End before deploy.
If you've just coded up a new feature and put in some layout, put that layout here,
with a comment, and the Front End Santa will make your wishes come true.
Or if you've put in a fix and you've not yet read the Front End Docs?
Put your fix in here.
Examples:
/*Fix for issue 996
.bookmark .header {float:left;}
/*Draft layout for views/shiny/new by astolat.
It has to be purple, see wiki, ADT meeting 25
.shiny-happy-people table {display:table-cell;
background:purple; color:#555;}
/*Some helpful notes
0.875em/1.286 line height = 14px with 18px leading
0.643em = 9px, so {margin: 0.643em auto;} gives you a single blank line between block elements */
/* AO3-6869 -- Workaround for a layout bug in WebKit, triggered by `float` and LiveValidation's placeholder span */
form#new_abuse_report .footnote {
float: none;
margin-right: 0;
width: fit-content;
}
/* styling for AO3-3359 to remove top padding from the new button
and preserve the balance/symmetry of the page */
#new_work_search fieldset:first-of-type .submit {
padding-top: 0;
}
/* Issue 3243 needs nested ul to be indented in external_authors/claim
*/
.edit_external_author ul ul {
margin-left: 2.75em;
}
/* While implementing AO3-5987 it was suggested we move to non-JS share buttons.
* These are statically styled with CSS instead.
*
* Sourced from: https://sharingbuttons.io/
* See: https://github.com/otwcode/otwarchive/pull/3874#pullrequestreview-460459176
*/
a.resp-sharing-button__link,
.resp-sharing-button__icon {
display: inline-block;
}
a.resp-sharing-button__link,
a.resp-sharing-button__link:hover {
text-decoration: none;
color: #fff;
border: none;
}
.resp-sharing-button {
border-radius: 5px;
transition: 25ms ease-out;
padding: 0.5em 0.75em;
}
.resp-sharing-button__icon svg {
width: 1.25em;
height: 1.25em;
margin-right: 0.25em;
vertical-align: text-bottom;
overflow: visible;
}
/* Non solid icons get a stroke */
.resp-sharing-button__icon {
stroke: #fff;
fill: none;
}
/* Solid icons get a fill */
.resp-sharing-button__icon--solid {
fill: #fff;
stroke: none;
}
.resp-sharing-button--twitter {
background-color: #55acee;
}
.resp-sharing-button--twitter:hover,
a:focus .resp-sharing-button--twitter {
background-color: #2795e9;
}
.resp-sharing-button--tumblr {
background-color: #35465C;
}
.resp-sharing-button--tumblr:hover,
a:focus .resp-sharing-button--tumblr {
background-color: #222d3c;
}
.resp-sharing-button--bluesky {
background-color: #1185fe;
}
.resp-sharing-button--bluesky:hover,
a:focus .resp-sharing-button--bluesky {
background-color: #0168d6;
}
.resp-sharing-button--twitter {
background-color: #55acee;
border-color: #55acee;
}
.resp-sharing-button--twitter:hover,
.resp-sharing-button--twitter:active {
background-color: #2795e9;
border-color: #2795e9;
}
.resp-sharing-button--tumblr {
background-color: #35465C;
border-color: #35465C;
}
.resp-sharing-button--tumblr:hover,
.resp-sharing-button--tumblr:active {
background-color: #222d3c;
border-color: #222d3c;
}
.resp-sharing-button--bluesky {
background-color: #1185fe;
border-color: #1185fe;
}
.resp-sharing-button--bluesky:hover,
.resp-sharing-button--bluesky:active {
background-color: #0168d6;
border-color: #0168d6;
}

4491
cache/92cf3bcddf1191c3c04ceea2383c8ed9 vendored Normal file

File diff suppressed because it is too large Load Diff

66
cache/9722736b7350e3b421dd1a82f33a13a5 vendored Normal file
View File

@@ -0,0 +1,66 @@
/* MEDIA: print */
html, body, #main {
font-family: serif;
}
html, body, #main, .works-show, dl.meta, .meta, .meta dd, .meta ul, .preface blockquote, .preface p, .blurb dd ul, .blurb h4, .blurb h5, .blurb .summary blockquote {
margin: auto;
padding: 2pt;
min-height: auto;
width: auto;
color: black;
background: transparent;
border: none;
position: static;
}
#main p, #main li, #main dd {
font: normal 11pt serif;
line-height: 1.2;
margin: auto;
text-indent: 22pt;
}
#header, #footer, #dashboard, img, #skiplinks, .navigation, .meta dt, #feedback, .meta .stats, .blurb dt, .filters, .pagination, .flash, input, .landmark {
display: none;
}
.meta dd, .meta ul, .blurb dd, .blurb .stats dt, .blurb dd ul, .blurb ul li, .blurb .datetime {
font-size: 9pt;
display: inline;
}
li.blurb {
border-bottom: 3pt double black;
margin: 6pt auto;
}
a, a:link, a:visited {
color: black;
text-decoration: underline;
border: 0;
}
.meta a, .meta a:link, .meta a:visited, .blurb a:link, .blurb a:visited {
text-decoration: none;
}
.docs .userstuff summary .heading {
display: inline;
}
/* CSS3 perks, print urls after links */
a:after {
content: " (" attr(href) ") ";
font-size: 9pt;
}
a[href^="/"]:after {
content: " (http://agento3.miscs.dev" attr(href) ") ";
}
.meta a:link:after, .meta a:visited:after, .blurb a:link:after, .blurb a:link:visited, .byline a:link:after, .byline a:visited:after {
content: " ";
}

View File

@@ -0,0 +1,8 @@
/*! jquery.livequery - v1.3.6 - 2013-08-26
* Copyright (c)
* (c) 2010, Brandon Aaron (http://brandonaaron.net)
* (c) 2012 - 2013, Alexander Zaytsev (http://hazzik.ru/en)
* Dual licensed under the MIT (MIT_LICENSE.txt)
* and GPL Version 2 (GPL_LICENSE.txt) licenses.
*/
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a,b){function c(a,b,c,d){return!(a.selector!=b.selector||a.context!=b.context||c&&c.$lqguid!=b.fn.$lqguid||d&&d.$lqguid!=b.fn2.$lqguid)}a.extend(a.fn,{livequery:function(b,e){var f,g=this;return a.each(d.queries,function(a,d){return c(g,d,b,e)?(f=d)&&!1:void 0}),f=f||new d(g.selector,g.context,b,e),f.stopped=!1,f.run(),g},expire:function(b,e){var f=this;return a.each(d.queries,function(a,g){c(f,g,b,e)&&!f.stopped&&d.stop(g.id)}),f}});var d=a.livequery=function(b,c,e,f){var g=this;return g.selector=b,g.context=c,g.fn=e,g.fn2=f,g.elements=a([]),g.stopped=!1,g.id=d.queries.push(g)-1,e.$lqguid=e.$lqguid||d.guid++,f&&(f.$lqguid=f.$lqguid||d.guid++),g};d.prototype={stop:function(){var b=this;b.stopped||(b.fn2&&b.elements.each(b.fn2),b.elements=a([]),b.stopped=!0)},run:function(){var b=this;if(!b.stopped){var c=b.elements,d=a(b.selector,b.context),e=d.not(c),f=c.not(d);b.elements=d,e.each(b.fn),b.fn2&&f.each(b.fn2)}}},a.extend(d,{guid:0,queries:[],queue:[],running:!1,timeout:null,registered:[],checkQueue:function(){if(d.running&&d.queue.length)for(var a=d.queue.length;a--;)d.queries[d.queue.shift()].run()},pause:function(){d.running=!1},play:function(){d.running=!0,d.run()},registerPlugin:function(){a.each(arguments,function(b,c){if(a.fn[c]&&!(a.inArray(c,d.registered)>0)){var e=a.fn[c];a.fn[c]=function(){var a=e.apply(this,arguments);return d.run(),a},d.registered.push(c)}})},run:function(c){c!==b?a.inArray(c,d.queue)<0&&d.queue.push(c):a.each(d.queries,function(b){a.inArray(b,d.queue)<0&&d.queue.push(b)}),d.timeout&&clearTimeout(d.timeout),d.timeout=setTimeout(d.checkQueue,20)},stop:function(c){c!==b?d.queries[c].stop():a.each(d.queries,d.prototype.stop)}}),d.registerPlugin("append","prepend","after","before","wrap","attr","removeAttr","addClass","removeClass","toggleClass","empty","remove","html","prop","removeProp"),a(function(){d.play()})});

924
cache/c7be43ce9c3c20e7bfed57686ab4f46e vendored Normal file
View File

@@ -0,0 +1,924 @@
// LiveValidation 1.3 (standalone version)
// Copyright (c) 2007-2008 Alec Hill (www.livevalidation.com)
// LiveValidation is licensed under the terms of the MIT License
/*********************************************** LiveValidation class ***********************************/
/**
* validates a form field in real-time based on validations you assign to it
*
* @var element {mixed} - either a dom element reference or the string id of the element to validate
* @var optionsObj {Object} - general options, see below for details
*
* optionsObj properties:
* validMessage {String} - the message to show when the field passes validation
* (DEFAULT: "Thankyou!")
* onValid {Function} - function to execute when field passes validation
* (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); } )
* onInvalid {Function} - function to execute when field fails validation
* (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); })
* insertAfterWhatNode {Int} - position to insert default message
* (DEFAULT: the field that is being validated)
* onlyOnBlur {Boolean} - whether you want it to validate as you type or only on blur
* (DEFAULT: false)
* wait {Integer} - the time you want it to pause from the last keystroke before it validates (ms)
* (DEFAULT: 0)
* onlyOnSubmit {Boolean} - whether should be validated only when the form it belongs to is submitted
* (DEFAULT: false)
*/
var LiveValidation = function(element, optionsObj){
this.initialize(element, optionsObj);
}
LiveValidation.VERSION = '1.3 standalone';
/** element types constants ****/
LiveValidation.TEXTAREA = 1;
LiveValidation.TEXT = 2;
LiveValidation.PASSWORD = 3;
LiveValidation.CHECKBOX = 4;
LiveValidation.SELECT = 5;
LiveValidation.FILE = 6;
/****** Static methods *******/
/**
* pass an array of LiveValidation objects and it will validate all of them
*
* @var validations {Array} - an array of LiveValidation objects
* @return {Bool} - true if all passed validation, false if any fail
*/
LiveValidation.massValidate = function(validations){
var returnValue = true;
for(var i = 0, len = validations.length; i < len; ++i ){
var valid = validations[i].validate();
if(returnValue) returnValue = valid;
}
return returnValue;
}
/****** prototype ******/
LiveValidation.prototype = {
validClass: 'LV_valid',
invalidClass: 'LV_invalid',
messageClass: 'LV_validation_message',
validFieldClass: 'LV_valid_field',
invalidFieldClass: 'LV_invalid_field',
/**
* initialises all of the properties and events
*
* @var - Same as constructor above
*/
initialize: function(element, optionsObj){
var self = this;
if(!element) throw new Error("LiveValidation::initialize - No element reference or element id has been provided!");
this.element = element.nodeName ? element : document.getElementById(element);
if(!this.element) throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!");
// default properties that could not be initialised above
this.validations = [];
this.elementType = this.getElementType();
this.form = this.element.form;
// options
var options = optionsObj || {};
this.validMessage = options.validMessage || ''; // AO3
var node = options.insertAfterWhatNode || this.element;
this.insertAfterWhatNode = node.nodeType ? node : document.getElementById(node);
this.onValid = options.onValid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };
this.onInvalid = options.onInvalid || function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); };
this.onlyOnBlur = options.onlyOnBlur || false;
this.wait = options.wait || 0;
this.onlyOnSubmit = options.onlyOnSubmit || false;
// add to form if it has been provided
if(this.form){
this.formObj = LiveValidationForm.getInstance(this.form);
this.formObj.addField(this);
}
// events
// collect old events
this.oldOnFocus = this.element.onfocus || function(){};
this.oldOnBlur = this.element.onblur || function(){};
this.oldOnClick = this.element.onclick || function(){};
this.oldOnChange = this.element.onchange || function(){};
this.oldOnKeyup = this.element.onkeyup || function(){};
this.element.onfocus = function(e){ self.doOnFocus(e); return self.oldOnFocus.call(this, e); }
if(!this.onlyOnSubmit){
switch(this.elementType){
case LiveValidation.CHECKBOX:
this.element.onclick = function(e){ self.validate(); return self.oldOnClick.call(this, e); }
// let it run into the next to add a change event too
case LiveValidation.SELECT:
case LiveValidation.FILE:
this.element.onchange = function(e){ self.validate(); return self.oldOnChange.call(this, e); }
break;
default:
if(!this.onlyOnBlur) this.element.onkeyup = function(e){ self.deferValidation(); return self.oldOnKeyup.call(this, e); }
this.element.onblur = function(e){ self.doOnBlur(e); return self.oldOnBlur.call(this, e); }
}
}
this.validate();
},
/**
* destroys the instance's events (restoring previous ones) and removes it from any LiveValidationForms
*/
destroy: function(){
if(this.formObj){
// remove the field from the LiveValidationForm
this.formObj.removeField(this);
// destroy the LiveValidationForm if no LiveValidation fields left in it
this.formObj.destroy();
}
// remove events - set them back to the previous events
this.element.onfocus = this.oldOnFocus;
if(!this.onlyOnSubmit){
switch(this.elementType){
case LiveValidation.CHECKBOX:
this.element.onclick = this.oldOnClick;
// let it run into the next to add a change event too
case LiveValidation.SELECT:
case LiveValidation.FILE:
this.element.onchange = this.oldOnChange;
break;
default:
if(!this.onlyOnBlur) this.element.onkeyup = this.oldOnKeyup;
this.element.onblur = this.oldOnBlur;
}
}
this.validations = [];
this.removeMessageAndFieldClass();
},
/**
* adds a validation to perform to a LiveValidation object
*
* @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
* @return {Object} - the LiveValidation object itself so that calls can be chained
*/
add: function(validationFunction, validationParamsObj){
this.validations.push( {type: validationFunction, params: validationParamsObj || {} } );
return this;
},
/**
* removes a validation from a LiveValidation object - must have exactly the same arguments as used to add it
*
* @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
* @return {Object} - the LiveValidation object itself so that calls can be chained
*/
remove: function(validationFunction, validationParamsObj){
var found = false;
for( var i = 0, len = this.validations.length; i < len; i++ ){
if( this.validations[i].type == validationFunction ){
if (this.validations[i].params == validationParamsObj) {
found = true;
break;
}
}
}
if(found) this.validations.splice(i,1);
return this;
},
/**
* makes the validation wait the alotted time from the last keystroke
*/
deferValidation: function(e){
if(this.wait >= 300) this.removeMessageAndFieldClass();
var self = this;
if(this.timeout) clearTimeout(self.timeout);
this.timeout = setTimeout( function(){ self.validate() }, self.wait);
},
/**
* // AO3
* sets the focused flag to false when field loses focus and triggers TinyMCE to save content into field
*/
doOnBlur: function(e){
if (typeof(tinyMCE)!="undefined") tinyMCE.triggerSave(); // AO3
this.focused = false;
this.validate(e);
},
/**
* sets the focused flag to true when field gains focus
*/
doOnFocus: function(e){
this.focused = true;
},
/**
* gets the type of element, to check whether it is compatible
*
* @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
*/
getElementType: function(){
switch(true){
case (this.element.nodeName.toUpperCase() == 'TEXTAREA'):
return LiveValidation.TEXTAREA;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'TEXT'):
return LiveValidation.TEXT;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'PASSWORD'):
return LiveValidation.PASSWORD;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'CHECKBOX'):
return LiveValidation.CHECKBOX;
case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'FILE'):
return LiveValidation.FILE;
case (this.element.nodeName.toUpperCase() == 'SELECT'):
return LiveValidation.SELECT;
case (this.element.nodeName.toUpperCase() == 'INPUT'):
throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!');
default:
throw new Error('LiveValidation::getElementType - Element must be an input, select, or textarea!');
}
},
/**
* loops through all the validations added to the LiveValidation object and checks them one by one
*
* @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
* @return {Boolean} - whether the all the validations passed or if one failed
*/
doValidations: function(){
this.validationFailed = false;
for(var i = 0, len = this.validations.length; i < len; ++i){
var validation = this.validations[i];
switch(validation.type){
case Validate.Presence:
case Validate.Confirmation:
case Validate.Acceptance:
this.displayMessageWhenEmpty = true;
this.validationFailed = !this.validateElement(validation.type, validation.params);
break;
default:
this.validationFailed = !this.validateElement(validation.type, validation.params);
break;
}
if(this.validationFailed) return false;
}
this.message = this.validMessage;
return true;
},
/**
* performs validation on the element and handles any error (validation or otherwise) it throws up
*
* @var validationFunction {Function} - validation function to be used (ie Validate.Presence )
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
* @return {Boolean} - whether the validation has passed or failed
*/
validateElement: function(validationFunction, validationParamsObj){
// AO3: we want validations to ignore leading and trailing whitespace, since it will be removed
var originalValue = (this.elementType == LiveValidation.SELECT) ? this.element.options[this.element.selectedIndex].value : this.element.value;
var value = $j.trim(originalValue);
// AO3: we also want newlines to be counted as "\r\n"s, regardless of the OS and browsers' whim;
// AO3: so we count any single "\n"s and "\r"s as "\r\n", which is what they'll end up as in the db anyway
if(typeof(value)=="string"){
value = (value.replace(/\r\n/g,"\n")).replace(/\r|\n/g,"\r\n");
}
// end AO3
if(validationFunction == Validate.Acceptance){
if(this.elementType != LiveValidation.CHECKBOX) throw new Error('LiveValidation::validateElement - Element to validate acceptance must be a checkbox!');
value = this.element.checked;
}
var isValid = true;
try{
validationFunction(value, validationParamsObj);
} catch(error) {
if(error instanceof Validate.Error){
if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){
this.validationFailed = true;
this.message = error.message;
isValid = false;
}
}else{
throw error;
}
}finally{
return isValid;
}
},
/**
* makes it do the all the validations and fires off the onValid or onInvalid callbacks
*
* @return {Boolean} - whether the all the validations passed or if one failed
*/
validate: function(){
if (this.element.disabled) return true;
var isValid = this.doValidations();
if (isValid) {
this.onValid();
if (typeof jQuery != "undefined") enableSubmit();
return true;
} else {
this.onInvalid();
return false;
}
},
/**
* enables the field
*
* @return {LiveValidation} - the LiveValidation object for chaining
*/
enable: function(){
this.element.disabled = false;
return this;
},
/**
* disables the field and removes any message and styles associated with the field
*
* @return {LiveValidation} - the LiveValidation object for chaining
*/
disable: function(){
this.element.disabled = true;
this.removeMessageAndFieldClass();
return this;
},
/** Message insertion methods ****************************
*
* These are only used in the onValid and onInvalid callback functions and so if you overide the default callbacks,
* you must either impliment your own functions to do whatever you want, or call some of these from them if you
* want to keep some of the functionality
*/
/**
* makes a span containg the passed or failed message
*
* @return {HTMLSpanObject} - a span element with the message in it
*/
createMessageSpan: function(){
var span = document.createElement('span');
var textNode = document.createTextNode(this.message);
span.appendChild(textNode);
span.role = "alert";
span.id = this.element.id + "_" + this.messageClass;
return span;
},
/**
* inserts the element containing the message in place of the element that already exists (if it does)
*
* @var elementToIsert {HTMLElementObject} - an element node to insert
*/
insertMessage: function(elementToInsert){
this.removeMessage();
var className = this.validationFailed ? this.invalidClass : this.validClass;
elementToInsert.className += ' ' + this.messageClass + ' ' + className;
if(this.insertAfterWhatNode.nextSibling){
this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, this.insertAfterWhatNode.nextSibling);
} else {
this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);
}
},
/**
* changes the class of the field based on whether it is valid or not
*/
addFieldClass: function(){
this.removeFieldClass();
if(!this.validationFailed){
if(this.displayMessageWhenEmpty || this.element.value != ''){
this.element.setAttribute("aria-invalid", false);
this.element.removeAttribute("aria-describedby");
if(this.element.className.indexOf(this.validFieldClass) == -1) this.element.className += ' ' + this.validFieldClass;
}
} else {
this.element.setAttribute("aria-invalid", true);
this.element.setAttribute("aria-describedby", this.element.id + "_" + this.messageClass);
if(this.element.className.indexOf(this.invalidFieldClass) == -1) this.element.className += ' ' + this.invalidFieldClass;
}
},
/**
* removes the message element if it exists, so that the new message will replace it
*/
removeMessage: function(){
var nextEl;
var el = this.insertAfterWhatNode;
while(el.nextSibling){
if(el.nextSibling.nodeType === 1){
nextEl = el.nextSibling;
break;
}
el = el.nextSibling;
}
if(nextEl && nextEl.className.indexOf(this.messageClass) != -1) this.insertAfterWhatNode.parentNode.removeChild(nextEl);
},
/**
* removes the class that has been applied to the field to indicte if valid or not
*/
removeFieldClass: function(){
if(this.element.className.indexOf(this.invalidFieldClass) != -1) this.element.className = this.element.className.split(this.invalidFieldClass).join('');
if(this.element.className.indexOf(this.validFieldClass) != -1) this.element.className = this.element.className.split(this.validFieldClass).join(' ');
},
/**
* removes the message and the field class
*/
removeMessageAndFieldClass: function(){
this.removeMessage();
this.removeFieldClass();
}
} // end of LiveValidation class
/*************************************** LiveValidationForm class ****************************************/
/**
* This class is used internally by LiveValidation class to associate a LiveValidation field with a form it is icontained in one
*
* It will therefore not really ever be needed to be used directly by the developer, unless they want to associate a LiveValidation
* field with a form that it is not a child of
*/
/**
* handles validation of LiveValidation fields belonging to this form on its submittal
*
* @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm
*/
var LiveValidationForm = function(element){
this.initialize(element);
}
/**
* namespace to hold instances
*/
LiveValidationForm.instances = {};
/**
* gets the instance of the LiveValidationForm if it has already been made or creates it if it doesnt exist
*
* @var element {HTMLFormElement} - a dom element reference to a form
*/
LiveValidationForm.getInstance = function(element){
var rand = Math.random() * Math.random();
if(!element.id) element.id = 'formId_' + rand.toString().replace(/\./, '') + new Date().valueOf();
if(!LiveValidationForm.instances[element.id]) LiveValidationForm.instances[element.id] = new LiveValidationForm(element);
return LiveValidationForm.instances[element.id];
}
LiveValidationForm.prototype = {
/**
* constructor for LiveValidationForm - handles validation of LiveValidation fields belonging to this form on its submittal
*
* @var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm
*/
initialize: function(element){
this.name = element.id;
this.element = element;
this.fields = [];
// preserve the old onsubmit event
// AO3: tinyMCE save needs to be triggered here so live validation recognises content in the rich text editor
this.oldOnSubmit = this.element.onsubmit || function(){};
var self = this;
this.element.onsubmit = function(e){
if (typeof(tinyMCE)!="undefined") tinyMCE.triggerSave(this.fields); // AO3
var ret = (LiveValidation.massValidate(self.fields)) ? self.oldOnSubmit.call(this, e || window.event) !== false : false;
// AO3: don't freeze the form if the user has clicked on the 'cancel' button -elz, 3/2/09, Enigel 3/7/11
var buttonClicked = document.activeElement || this.explicitOriginalTarget;
if (buttonClicked.name == 'cancel_button') ret = true;
else if (!ret) {
scrollToErrorIfFound();
enableSubmit();
}
return ret;
}
},
/**
* adds a LiveValidation field to the forms fields array
*
* @var element {LiveValidation} - a LiveValidation object
*/
addField: function(newField){
this.fields.push(newField);
},
/**
* removes a LiveValidation field from the forms fields array
*
* @var victim {LiveValidation} - a LiveValidation object
*/
removeField: function(victim){
var victimless = [];
for( var i = 0, len = this.fields.length; i < len; i++){
if(this.fields[i] !== victim) victimless.push(this.fields[i]);
}
this.fields = victimless;
},
/**
* destroy this instance and its events
*
* @var force {Boolean} - whether to force the detruction even if there are fields still associated
*/
destroy: function(force){
// only destroy if has no fields and not being forced
if (this.fields.length != 0 && !force) return false;
// remove events - set back to previous events
this.element.onsubmit = this.oldOnSubmit;
// remove from the instances namespace
LiveValidationForm.instances[this.name] = null;
return true;
}
}// end of LiveValidationForm prototype
/*************************************** Validate class ****************************************/
/**
* This class contains all the methods needed for doing the actual validation itself
*
* All methods are static so that they can be used outside the context of a form field
* as they could be useful for validating stuff anywhere you want really
*
* All of them will return true if the validation is successful, but will raise a ValidationError if
* they fail, so that this can be caught and the message explaining the error can be accessed ( as just
* returning false would leave you a bit in the dark as to why it failed )
*
* Can use validation methods alone and wrap in a try..catch statement yourself if you want to access the failure
* message and handle the error, or use the Validate::now method if you just want true or false
*/
var Validate = {
/**
* validates that the field has been filled in
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Can't be empty!")
*/
Presence: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Can't be empty!";
if(value === '' || value === null || value === undefined){
Validate.fail(message);
}
return true;
},
/**
* validates that the value is numeric, does not fall within a given range of numbers
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* notANumberMessage {String} - the message to show when the validation fails when value is not a number
* (DEFAULT: "Must be a number!")
* notAnIntegerMessage {String} - the message to show when the validation fails when value is not an integer
* (DEFAULT: "Must be a number!")
* wrongNumberMessage {String} - the message to show when the validation fails when is param is used
* (DEFAULT: "Must be {is}!")
* tooLowMessage {String} - the message to show when the validation fails when minimum param is used
* (DEFAULT: "Must not be less than {minimum}!")
* tooHighMessage {String} - the message to show when the validation fails when maximum param is used
* (DEFAULT: "Must not be more than {maximum}!")
* is {Int} - the length must be this long
* minimum {Int} - the minimum length allowed
* maximum {Int} - the maximum length allowed
* onlyInteger {Boolean} - if true will only allow integers to be valid
* (DEFAULT: false)
*
* NB. can be checked if it is within a range by specifying both a minimum and a maximum
* NB. will evaluate numbers represented in scientific form (ie 2e10) correctly as numbers
*/
Numericality: function(value, paramsObj){
var suppliedValue = value;
var value = Number(value);
var paramsObj = paramsObj || {};
var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;;
var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
var notANumberMessage = paramsObj.notANumberMessage || "Must be a number!";
var notAnIntegerMessage = paramsObj.notAnIntegerMessage || "Must be an integer!";
var wrongNumberMessage = paramsObj.wrongNumberMessage || "Must be " + is + "!";
var tooLowMessage = paramsObj.tooLowMessage || "Must not be less than " + minimum + "!";
var tooHighMessage = paramsObj.tooHighMessage || "Must not be more than " + maximum + "!";
if (!isFinite(value)) Validate.fail(notANumberMessage);
if (paramsObj.onlyInteger && (/\.0+$|\.$/.test(String(suppliedValue)) || value != parseInt(value)) ) Validate.fail(notAnIntegerMessage);
switch(true){
case (is !== null):
if( value != Number(is) ) Validate.fail(wrongNumberMessage);
break;
case (minimum !== null && maximum !== null):
Validate.Numericality(value, {tooLowMessage: tooLowMessage, minimum: minimum});
Validate.Numericality(value, {tooHighMessage: tooHighMessage, maximum: maximum});
break;
case (minimum !== null):
if( value < Number(minimum) ) Validate.fail(tooLowMessage);
break;
case (maximum !== null):
if( value > Number(maximum) ) Validate.fail(tooHighMessage);
break;
}
return true;
},
/**
* validates against a RegExp pattern
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Not valid!")
* pattern {RegExp} - the regular expression pattern
* (DEFAULT: /./)
* negate {Boolean} - if set to true, will validate true if the pattern is not matched
* (DEFAULT: false)
*
* NB. will return true for an empty string, to allow for non-required, empty fields to validate.
* If you do not want this to be the case then you must either add a LiveValidation.PRESENCE validation
* or build it into the regular expression pattern
*/
Format: function(value, paramsObj){
var value = String(value);
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Not valid!";
var pattern = paramsObj.pattern || /./;
var negate = paramsObj.negate || false;
if(!negate && !pattern.test(value)) Validate.fail(message); // normal
if(negate && pattern.test(value)) Validate.fail(message); // negated
return true;
},
/**
* validates that the field contains a valid email address
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Must be a number!" or "Must be an integer!")
*/
Email: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be a valid email address!";
Validate.Format(value, { failureMessage: message, pattern: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } );
return true;
},
/**
* validates the length of the value
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* wrongLengthMessage {String} - the message to show when the fails when is param is used
* (DEFAULT: "Must be {is} characters long!")
* tooShortMessage {String} - the message to show when the fails when minimum param is used
* (DEFAULT: "Must not be less than {minimum} characters long!")
* tooLongMessage {String} - the message to show when the fails when maximum param is used
* (DEFAULT: "Must not be more than {maximum} characters long!")
* is {Int} - the length must be this long
* minimum {Int} - the minimum length allowed
* maximum {Int} - the maximum length allowed
*
* NB. can be checked if it is within a range by specifying both a minimum and a maximum
*/
Length: function(value, paramsObj){
var value = String(value);
var paramsObj = paramsObj || {};
var minimum = ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null;
var maximum = ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null;
var is = ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null;
var wrongLengthMessage = paramsObj.wrongLengthMessage || "Must be " + is + " characters long!";
var tooShortMessage = paramsObj.tooShortMessage || "Must not be less than " + minimum + " characters long!";
var tooLongMessage = paramsObj.tooLongMessage || "Must not be more than " + maximum + " characters long!";
switch(true){
case (is !== null):
if( value.length != Number(is) ) Validate.fail(wrongLengthMessage);
break;
case (minimum !== null && maximum !== null):
Validate.Length(value, {tooShortMessage: tooShortMessage, minimum: minimum});
Validate.Length(value, {tooLongMessage: tooLongMessage, maximum: maximum});
break;
case (minimum !== null):
if( value.length < Number(minimum) ) Validate.fail(tooShortMessage);
break;
case (maximum !== null):
if( value.length > Number(maximum) ) Validate.fail(tooLongMessage);
break;
default:
throw new Error("Validate::Length - Length(s) to validate against must be provided!");
}
return true;
},
/**
* validates that the value falls within a given set of values
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Must be included in the list!")
* within {Array} - an array of values that the value should fall in
* (DEFAULT: [])
* allowNull {Bool} - if true, and a null value is passed in, validates as true
* (DEFAULT: false)
* partialMatch {Bool} - if true, will not only validate against the whole value to check but also if it is a substring of the value
* (DEFAULT: false)
* caseSensitive {Bool} - if false will compare strings case insensitively
* (DEFAULT: true)
* negate {Bool} - if true, will validate that the value is not within the given set of values
* (DEFAULT: false)
*/
Inclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be included in the list!";
var caseSensitive = (paramsObj.caseSensitive === false) ? false : true;
if(paramsObj.allowNull && value == null) return true;
if(!paramsObj.allowNull && value == null) Validate.fail(message);
var within = paramsObj.within || [];
//if case insensitive, make all strings in the array lowercase, and the value too
if(!caseSensitive){
var lowerWithin = [];
for(var j = 0, length = within.length; j < length; ++j){
var item = within[j];
if(typeof item == 'string') item = item.toLowerCase();
lowerWithin.push(item);
}
within = lowerWithin;
if(typeof value == 'string') value = value.toLowerCase();
}
var found = false;
for(var i = 0, length = within.length; i < length; ++i){
if(within[i] == value) found = true;
if(paramsObj.partialMatch){
if(value.indexOf(within[i]) != -1) found = true;
}
}
if( (!paramsObj.negate && !found) || (paramsObj.negate && found) ) Validate.fail(message);
return true;
},
/**
* validates that the value does not fall within a given set of values
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Must not be included in the list!")
* within {Array} - an array of values that the value should not fall in
* (DEFAULT: [])
* allowNull {Bool} - if true, and a null value is passed in, validates as true
* (DEFAULT: false)
* partialMatch {Bool} - if true, will not only validate against the whole value to check but also if it is a substring of the value
* (DEFAULT: false)
* caseSensitive {Bool} - if false will compare strings case insensitively
* (DEFAULT: true)
*/
Exclusion: function(value, paramsObj){
var paramsObj = paramsObj || {};
paramsObj.failureMessage = paramsObj.failureMessage || "Must not be included in the list!";
paramsObj.negate = true;
Validate.Inclusion(value, paramsObj);
return true;
},
/**
* validates that the value matches that in another field
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Does not match!")
* match {String} - id of the field that this one should match
*/
Confirmation: function(value, paramsObj){
if(!paramsObj.match) throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided!");
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Does not match!";
var match = paramsObj.match.nodeName ? paramsObj.match : document.getElementById(paramsObj.match);
if(!match) throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + paramsObj.match + "'!");
if(value != match.value){
Validate.fail(message);
}
return true;
},
/**
* validates that the value is true (for use primarily in detemining if a checkbox has been checked)
*
* @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox)
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Must be accepted!")
*/
Acceptance: function(value, paramsObj){
var paramsObj = paramsObj || {};
var message = paramsObj.failureMessage || "Must be accepted!";
if(!value){
Validate.fail(message);
}
return true;
},
/**
* validates against a custom function that returns true or false (or throws a Validate.Error) when passed the value
*
* @var value {mixed} - value to be checked
* @var paramsObj {Object} - parameters for this particular validation, see below for details
*
* paramsObj properties:
* failureMessage {String} - the message to show when the field fails validation
* (DEFAULT: "Not valid!")
* against {Function} - a function that will take the value and object of arguments and return true or false
* (DEFAULT: function(){ return true; })
* args {Object} - an object of named arguments that will be passed to the custom function so are accessible through this object within it
* (DEFAULT: {})
*/
Custom: function(value, paramsObj){
var paramsObj = paramsObj || {};
var against = paramsObj.against || function(){ return true; };
var args = paramsObj.args || {};
var message = paramsObj.failureMessage || "Not valid!";
if(!against(value, args)) Validate.fail(message);
return true;
},
/**
* validates whatever it is you pass in, and handles the validation error for you so it gives a nice true or false reply
*
* @var validationFunction {Function} - validation function to be used (ie Validation.validatePresence )
* @var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox)
* @var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary
*/
now: function(validationFunction, value, validationParamsObj){
if(!validationFunction) throw new Error("Validate::now - Validation function must be provided!");
var isValid = true;
try{
validationFunction(value, validationParamsObj || {});
} catch(error) {
if(error instanceof Validate.Error){
isValid = false;
}else{
throw error;
}
}finally{
return isValid
}
},
/**
* shortcut for failing throwing a validation error
*
* @var errorMessage {String} - message to display
*/
fail: function(errorMessage){
throw new Validate.Error(errorMessage);
},
Error: function(errorMessage){
this.message = errorMessage;
this.name = 'ValidationError';
}
}
function scrollToErrorIfFound() {
var errorField = $j(".LV_invalid_field").first();
if (errorField.length !== 0) {
$j("html, body").animate({
scrollTop: errorField.offset().top
}, 1000);
errorField.focus();
}
}
// Enable submit button if there are no errors
function enableSubmit() {
if ($j(".LV_invalid_field").first().length === 0) {
$j.rails.enableFormElement($j("input[data-disable-with]"));
}
}

294
cache/d1b5fde27293b4c7d5e5b78261172c48 vendored Normal file
View File

@@ -0,0 +1,294 @@
/* MEDIA: only screen and (max-width: 42em), handheld ENDMEDIA */
#outer {
background: #fff;
font-size: 0.875em;
position: relative;
}
h1, h2, h3 {
word-break: break-all;
/* not supported in all browsers, so we need break-all as a fallback */
word-break: break-word;
}
/* non-JavaScript states
.narrow-shown: should be displayed when this stylesheet is in use
*/
body .narrow-shown {
display: block;
}
.actions li.narrow-shown {
display: inline;
}
/* JavaScript states
.javascript .narrow-hidden: should not be displayed when JS is enabled and this stylesheet is in use
*/
.javascript .narrow-hidden {
display: none;
}
/* 03 region header */
#header .logo {
height: 1.75em;
}
#header .dropdown a:focus {
outline: none;
}
#header .primary > li:first-of-type {
margin-left: 0;
}
#header .dropdown a:focus, #header .dropdown .menu a:focus {
background: transparent;
color: #111;
}
#header .open a:focus {
background: #ddd;
}
#header .primary .dropdown a:focus {
color: #fff;
}
#header .primary .open a:focus {
color: #111;
}
#header .user .open a:focus {
color: #900;
}
#header h2.collections {
padding: 1%;
margin: 0;
}
#header #small_login {
margin-left: 45px;
}
#header .dropdown, #greeting .user {
position: static;
}
#header .menu {
width: 100%;
position: absolute;
left: 0;
}
/* 04 region dashboard */
#dashboard, #dashboard.own {
border-bottom-width: 7px;
border-top-width: 7px;
padding: 0.25em 0;
}
/* 05 region main */
#main, #main.dashboard {
position: static;
}
#main.errors {
background-position: center;
}
#main.session {
background-image: none;
}
#main.errors p, #main.errors .heading {
margin-right: 0;
}
#main.errors p:last-child {
margin-bottom: 500px;
}
/* once we remove the meta class from the work form, we can remove the form .meta selectors here */
.filtered .index, form.filters, form dd, form dt, form .meta dd, form .meta dt, form.inbox {
width: 100%;
max-width: 100%;
min-width: 0;
float: none;
}
.dashboard .index {
float: none;
clear: both
}
.dashboard .landmark {
clear: both;
}
/* 10 types and groups */
.blurb dl.tags dt, .blurb dl.tags dd, dl.meta dt, dl.meta dd, .alphabet .listbox li, .media .listbox {
width: auto;
float: none;
}
.blurb dl.tags dd, dl.meta dd {
margin-left: 1em;
}
.alphabet .listbox li {
display: block;
}
/* 11 group: listbox */
.listbox .index {
width: auto;
}
/* 15 group: comments */
.thread .thread {
margin-left: 1em;
}
.comment .userstuff {
min-height: 0;
}
.comment .icon {
height: 55px;
margin-bottom: 0;
width: 55px;
}
.comment .icon .anonymous {
background: url(/images/imageset.png) no-repeat -75px -395px;
}
.comment .icon .visitor {
background: url(/images/imageset.png) no-repeat -130px -395px;
}
.comment h4.byline {
padding-left: 62px;
}
/* 16 zone: system */
.splash {
padding: 0;
}
.splash div.module, .logged-in .splash div.module {
clear: both;
margin-left: 0;
margin-right: 0;
width: 100%;
}
.splash .intro {
padding-top: 0;
}
.splash .intro h2 {
font-size: 1.5em;
word-break: normal;
}
.session #signin {
margin-left: 0;
width: 100%;
}
/* 18 zone: search browse */
form.filters dl {
width: auto;
}
/* Filters with JavaScript */
.javascript {
background: #ddd;
}
.javascript form.filters {
margin: 0;
max-width: 95%;
position: absolute;
top: 0;
right: -16em;
width: 16em; /* 14em/0.875em */
z-index: 400;
}
.javascript .filters fieldset {
border: none;
margin: 0;
position: relative;
z-index: 450;
box-shadow: none;
}
.javascript .filters p.narrow-shown {
position: relative;
}
.filtering {
right: 14em;
}
.filtering .filters #leave_filters {
background: transparent none;
border-bottom: none;
position: fixed;
top: -101em;
bottom: -101em;
left: -10em;
right: -10em;
z-index: 0;
}
.filtering #leave_filters:focus {
outline: none;
}
/* 21 userstuff */
#workskin {
margin: auto;
}
/* 22 system: messages */
.announcement .userstuff {
margin: 1%;
}
.announcement p.submit {
bottom: -0.5em;
right: 1%;
}
.announcement .thermometer-content {
width: 80%;
}
.announcement .goal .amount {
display: none;
}
.announcement .thermometer .progress .amount {
left: 0;
right: auto;
}

15
cache/e8d39ac2b8645f201df2197c4f56db7f vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* jQuery Shuffle (http://mktgdept.com/jquery-shuffle)
* A jQuery plugin for shuffling a set of elements
*
* v0.0.1 - 13 November 2009
*
* Copyright (c) 2009 Chad Smith (http://twitter.com/chadsmith)
* Dual licensed under the MIT and GPL licenses.
* http://www.opensource.org/licenses/mit-license.php
* http://www.opensource.org/licenses/gpl-license.php
*
* Shuffle elements using: $(selector).shuffle() or $.shuffle(selector)
*
**/
(function(d){d.fn.shuffle=function(c){c=[];return this.each(function(){c.push(d(this).clone(true))}).each(function(a,b){d(b).replaceWith(c[a=Math.floor(Math.random()*c.length)]);c.splice(a,1)})};d.shuffle=function(a){return d(a).shuffle()}})(jQuery);

File diff suppressed because one or more lines are too long

BIN
cache/e9d6dffb2eb319ae85deae26872daf4a vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

534
cache/f688920d715a669db37b313dd3ecffd6 vendored Normal file
View File

@@ -0,0 +1,534 @@
(function($, undefined) {
/**
* Unobtrusive scripting adapter for jQuery
* https://github.com/rails/jquery-ujs
*
* Requires jQuery 1.8.0 or later.
*
* Released under the MIT license
*
*/
// Cut down on the number of issues from people inadvertently including jquery_ujs twice
// by detecting and raising an error when it happens.
'use strict';
if ( $.rails !== undefined ) {
$.error('jquery-ujs has already been loaded!');
}
// Shorthand to make it a little easier to call public rails functions from within rails.js
var rails;
var $document = $(document);
$.rails = rails = {
// Link elements bound by jquery-ujs
linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
// Button elements bound by jquery-ujs
buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
// Select elements bound by jquery-ujs
inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
// Form elements bound by jquery-ujs
formSubmitSelector: 'form',
// Form input elements bound by jquery-ujs
formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
// Form input elements disabled during form submission
disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
// Form input elements re-enabled after form submission
enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
// Form required input elements
requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
// Form file input elements
fileInputSelector: 'input[type=file]:not([disabled])',
// Link onClick disable selector with possible reenable after remote submission
linkDisableSelector: 'a[data-disable-with], a[data-disable]',
// Button onClick disable selector with possible reenable after remote submission
buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
// Up-to-date Cross-Site Request Forgery token
csrfToken: function() {
return $('meta[name=csrf-token]').attr('content');
},
// URL param that must contain the CSRF token
csrfParam: function() {
return $('meta[name=csrf-param]').attr('content');
},
// Make sure that every Ajax request sends the CSRF token
CSRFProtection: function(xhr) {
var token = rails.csrfToken();
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
},
// Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
refreshCSRFTokens: function(){
$('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
},
// Triggers an event on an element and returns false if the event result is false
fire: function(obj, name, data) {
var event = $.Event(name);
obj.trigger(event, data);
return event.result !== false;
},
// Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
confirm: function(message) {
return confirm(message);
},
// Default ajax function, may be overridden with custom function in $.rails.ajax
ajax: function(options) {
return $.ajax(options);
},
// Default way to get an element's href. May be overridden at $.rails.href.
href: function(element) {
return element[0].href;
},
// Checks "data-remote" if true to handle the request through a XHR request.
isRemote: function(element) {
return element.data('remote') !== undefined && element.data('remote') !== false;
},
// Submits "remote" forms and links with ajax
handleRemote: function(element) {
var method, url, data, withCredentials, dataType, options;
if (rails.fire(element, 'ajax:before')) {
withCredentials = element.data('with-credentials') || null;
dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
if (element.is('form')) {
method = element.data('ujs:submit-button-formmethod') || element.attr('method');
url = element.data('ujs:submit-button-formaction') || element.attr('action');
data = $(element[0].elements).serializeArray();
// memoized value from clicked submit button
var button = element.data('ujs:submit-button');
if (button) {
data.push(button);
element.data('ujs:submit-button', null);
}
element.data('ujs:submit-button-formmethod', null);
element.data('ujs:submit-button-formaction', null);
} else if (element.is(rails.inputChangeSelector)) {
method = element.data('method');
url = element.data('url');
data = element.serialize();
if (element.data('params')) data = data + '&' + element.data('params');
} else if (element.is(rails.buttonClickSelector)) {
method = element.data('method') || 'get';
url = element.data('url');
data = element.serialize();
if (element.data('params')) data = data + '&' + element.data('params');
} else {
method = element.data('method');
url = rails.href(element);
data = element.data('params') || null;
}
options = {
type: method || 'GET', data: data, dataType: dataType,
// stopping the "ajax:beforeSend" event will cancel the ajax request
beforeSend: function(xhr, settings) {
if (settings.dataType === undefined) {
xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
}
if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
element.trigger('ajax:send', xhr);
} else {
return false;
}
},
success: function(data, status, xhr) {
element.trigger('ajax:success', [data, status, xhr]);
},
complete: function(xhr, status) {
element.trigger('ajax:complete', [xhr, status]);
},
error: function(xhr, status, error) {
element.trigger('ajax:error', [xhr, status, error]);
},
crossDomain: rails.isCrossDomain(url)
};
// There is no withCredentials for IE6-8 when
// "Enable native XMLHTTP support" is disabled
if (withCredentials) {
options.xhrFields = {
withCredentials: withCredentials
};
}
// Only pass url to `ajax` options if not blank
if (url) { options.url = url; }
return rails.ajax(options);
} else {
return false;
}
},
// Determines if the request is a cross domain request.
isCrossDomain: function(url) {
var originAnchor = document.createElement('a');
originAnchor.href = location.href;
var urlAnchor = document.createElement('a');
try {
urlAnchor.href = url;
// This is a workaround to a IE bug.
urlAnchor.href = urlAnchor.href;
// If URL protocol is false or is a string containing a single colon
// *and* host are false, assume it is not a cross-domain request
// (should only be the case for IE7 and IE compatibility mode).
// Otherwise, evaluate protocol and host of the URL against the origin
// protocol and host.
return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
(originAnchor.protocol + '//' + originAnchor.host ===
urlAnchor.protocol + '//' + urlAnchor.host));
} catch (e) {
// If there is an error parsing the URL, assume it is crossDomain.
return true;
}
},
// Handles "data-method" on links such as:
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
handleMethod: function(link) {
var href = rails.href(link),
method = link.data('method'),
target = link.attr('target'),
csrfToken = rails.csrfToken(),
csrfParam = rails.csrfParam(),
form = $('<form method="post" action="' + href + '"></form>'),
metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
}
if (target) { form.attr('target', target); }
form.hide().append(metadataInput).appendTo('body');
form.submit();
},
// Helper function that returns form elements that match the specified CSS selector
// If form is actually a "form" element this will return associated elements outside the from that have
// the html form attribute set
formElements: function(form, selector) {
return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
},
/* Disables form elements:
- Caches element value in 'ujs:enable-with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Sets disabled property to true
*/
disableFormElements: function(form) {
rails.formElements(form, rails.disableSelector).each(function() {
rails.disableFormElement($(this));
});
},
disableFormElement: function(element) {
var method, replacement;
method = element.is('button') ? 'html' : 'val';
replacement = element.data('disable-with');
if (replacement !== undefined) {
element.data('ujs:enable-with', element[method]());
element[method](replacement);
}
element.prop('disabled', true);
element.data('ujs:disabled', true);
},
/* Re-enables disabled form elements:
- Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
- Sets disabled property to false
*/
enableFormElements: function(form) {
rails.formElements(form, rails.enableSelector).each(function() {
rails.enableFormElement($(this));
});
},
enableFormElement: function(element) {
var method = element.is('button') ? 'html' : 'val';
if (element.data('ujs:enable-with') !== undefined) {
element[method](element.data('ujs:enable-with'));
element.removeData('ujs:enable-with'); // clean up cache
}
element.prop('disabled', false);
element.removeData('ujs:disabled');
},
/* For 'data-confirm' attribute:
- Fires `confirm` event
- Shows the confirmation dialog
- Fires the `confirm:complete` event
Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
*/
allowAction: function(element) {
var message = element.data('confirm'),
answer = false, callback;
if (!message) { return true; }
if (rails.fire(element, 'confirm')) {
try {
answer = rails.confirm(message);
} catch (e) {
(console.error || console.log).call(console, e.stack || e);
}
callback = rails.fire(element, 'confirm:complete', [answer]);
}
return answer && callback;
},
// Helper function which checks for blank inputs in a form that match the specified CSS selector
blankInputs: function(form, specifiedSelector, nonBlank) {
var inputs = $(), input, valueToCheck,
selector = specifiedSelector || 'input,textarea',
allInputs = form.find(selector);
allInputs.each(function() {
input = $(this);
valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
if (valueToCheck === nonBlank) {
// Don't count unchecked required radio if other radio with same name is checked
if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) {
return true; // Skip to next input
}
inputs = inputs.add(input);
}
});
return inputs.length ? inputs : false;
},
// Helper function which checks for non-blank inputs in a form that match the specified CSS selector
nonBlankInputs: function(form, specifiedSelector) {
return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
},
// Helper function, needed to provide consistent behavior in IE
stopEverything: function(e) {
$(e.target).trigger('ujs:everythingStopped');
e.stopImmediatePropagation();
return false;
},
// Replace element's html with the 'data-disable-with' after storing original html
// and prevent clicking on it
disableElement: function(element) {
var replacement = element.data('disable-with');
if (replacement !== undefined) {
element.data('ujs:enable-with', element.html()); // store enabled state
element.html(replacement);
}
element.bind('click.railsDisable', function(e) { // prevent further clicking
return rails.stopEverything(e);
});
element.data('ujs:disabled', true);
},
// Restore element to its original state which was disabled by 'disableElement' above
enableElement: function(element) {
if (element.data('ujs:enable-with') !== undefined) {
element.html(element.data('ujs:enable-with')); // set to old enabled state
element.removeData('ujs:enable-with'); // clean up cache
}
element.unbind('click.railsDisable'); // enable element
element.removeData('ujs:disabled');
}
};
if (rails.fire($document, 'rails:attachBindings')) {
$.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
// This event works the same as the load event, except that it fires every
// time the page is loaded.
//
// See https://github.com/rails/jquery-ujs/issues/357
// See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
$(window).on('pageshow.rails', function () {
$($.rails.enableSelector).each(function () {
var element = $(this);
if (element.data('ujs:disabled')) {
$.rails.enableFormElement(element);
}
});
$($.rails.linkDisableSelector).each(function () {
var element = $(this);
if (element.data('ujs:disabled')) {
$.rails.enableElement(element);
}
});
});
$document.delegate(rails.linkDisableSelector, 'ajax:complete', function() {
rails.enableElement($(this));
});
$document.delegate(rails.buttonDisableSelector, 'ajax:complete', function() {
rails.enableFormElement($(this));
});
$document.delegate(rails.linkClickSelector, 'click.rails', function(e) {
var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
if (!rails.allowAction(link)) return rails.stopEverything(e);
if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
if (rails.isRemote(link)) {
if (metaClick && (!method || method === 'GET') && !data) { return true; }
var handleRemote = rails.handleRemote(link);
// Response from rails.handleRemote() will either be false or a deferred object promise.
if (handleRemote === false) {
rails.enableElement(link);
} else {
handleRemote.fail( function() { rails.enableElement(link); } );
}
return false;
} else if (method) {
rails.handleMethod(link);
return false;
}
});
$document.delegate(rails.buttonClickSelector, 'click.rails', function(e) {
var button = $(this);
if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
var handleRemote = rails.handleRemote(button);
// Response from rails.handleRemote() will either be false or a deferred object promise.
if (handleRemote === false) {
rails.enableFormElement(button);
} else {
handleRemote.fail( function() { rails.enableFormElement(button); } );
}
return false;
});
$document.delegate(rails.inputChangeSelector, 'change.rails', function(e) {
var link = $(this);
if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
rails.handleRemote(link);
return false;
});
$document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
var form = $(this),
remote = rails.isRemote(form),
blankRequiredInputs,
nonBlankFileInputs;
if (!rails.allowAction(form)) return rails.stopEverything(e);
// Skip other logic when required values are missing or file upload is present
if (form.attr('novalidate') === undefined) {
if (form.data('ujs:formnovalidate-button') === undefined) {
blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
return rails.stopEverything(e);
}
} else {
// Clear the formnovalidate in case the next button click is not on a formnovalidate button
// Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
form.data('ujs:formnovalidate-button', undefined);
}
}
if (remote) {
nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
if (nonBlankFileInputs) {
// Slight timeout so that the submit button gets properly serialized
// (make it easy for event handler to serialize form without disabled values)
setTimeout(function(){ rails.disableFormElements(form); }, 13);
var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
// Re-enable form elements if event bindings return false (canceling normal form submission)
if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
return aborted;
}
rails.handleRemote(form);
return false;
} else {
// Slight timeout so that the submit button gets properly serialized
setTimeout(function(){ rails.disableFormElements(form); }, 13);
}
});
$document.delegate(rails.formInputClickSelector, 'click.rails', function(event) {
var button = $(this);
if (!rails.allowAction(button)) return rails.stopEverything(event);
// Register the pressed submit button
var name = button.attr('name'),
data = name ? {name:name, value:button.val()} : null;
var form = button.closest('form');
if (form.length === 0) {
form = $('#' + button.attr('form'));
}
form.data('ujs:submit-button', data);
// Save attributes from button
form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
form.data('ujs:submit-button-formaction', button.attr('formaction'));
form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
});
$document.delegate(rails.formSubmitSelector, 'ajax:send.rails', function(event) {
if (this === event.target) rails.disableFormElements($(this));
});
$document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
if (this === event.target) rails.enableFormElements($(this));
});
$(function(){
rails.refreshCSRFTokens();
});
}
})( jQuery );

186
cache/fd994d17b56e171552534f14fc2cd24b vendored Normal file
View File

@@ -0,0 +1,186 @@
/* ============================================================
* bootstrap-dropdown.js v2.3.1
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================
* OTWARCHIVE DEVS:
*
* When updating to the newest version, make sure to include the
* customizations from LINES 62-70, 103, AND 177-184 and UPDATE THIS
* MESSAGE with the new line numbers. These lines ensure the code works
* without the ARIA menu role and ensure proper behavior when both JS and
* CSS hover are used for menus.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* DROPDOWN CLASS DEFINITION
* ========================= */
var toggle = '[data-toggle=dropdown]'
, Dropdown = function (element) {
var $el = $(element).on('click.dropdown.data-api', this.toggle)
$('html').on('click.dropdown.data-api', function () {
$el.parent().removeClass('open')
})
}
Dropdown.prototype = {
constructor: Dropdown
, toggle: function (e) {
var $this = $(this)
, $parent
, isActive
if ($this.is('.disabled, :disabled')) return
$parent = getParent($this)
isActive = $parent.hasClass('open')
clearMenus()
if (isActive) {
$parent.children('ul').hide()
$this.blur()
} else {
$parent
.toggleClass('open')
.children('ul').removeAttr('style')
$this.focus()
}
$this.focus()
return false
}
, keydown: function (e) {
var $this
, $items
, $active
, $parent
, isActive
, index
if (!/(38|40|27)/.test(e.keyCode)) return
$this = $(this)
e.preventDefault()
e.stopPropagation()
if ($this.is('.disabled, :disabled')) return
$parent = getParent($this)
isActive = $parent.hasClass('open')
if (!isActive || (isActive && e.keyCode == 27)) {
if (e.which == 27) $parent.find(toggle).focus()
return $this.click()
}
$items = $('ul.menu li:not(.divider):visible a', $parent)
if (!$items.length) return
index = $items.index($items.filter(':focus'))
if (e.keyCode == 38 && index > 0) index-- // up
if (e.keyCode == 40 && index < $items.length - 1) index++ // down
if (!~index) index = 0
$items
.eq(index)
.focus()
}
}
function clearMenus() {
$(toggle).each(function () {
getParent($(this)).removeClass('open')
})
}
function getParent($this) {
var selector = $this.attr('data-target')
, $parent
if (!selector) {
selector = $this.attr('href')
selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
$parent = selector && $(selector)
if (!$parent || !$parent.length) $parent = $this.parent()
return $parent
}
/* DROPDOWN PLUGIN DEFINITION
* ========================== */
var old = $.fn.dropdown
$.fn.dropdown = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('dropdown')
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.dropdown.Constructor = Dropdown
/* DROPDOWN NO CONFLICT
* ==================== */
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old
return this
}
/* APPLY TO STANDARD DROPDOWN ELEMENTS
* =================================== */
$(document)
.on('click.dropdown.data-api', clearMenus)
.on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
.on('click.dropdown-menu', function (e) { e.stopPropagation() })
.on('click.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
.on('keydown.dropdown.data-api', toggle + ', ul.menu' , Dropdown.prototype.keydown)
.on('mouseenter', '.dropdown', function (e) {
var $parent = $(this)
if ($parent.siblings('.open').length) {
$parent.children('ul').hide()
}
})
.on('mouseleave', '.dropdown', function (e) { $(this).children('ul').removeAttr('') })
}(window.jQuery);

BIN
cache/fe5c4c7ec67e5277582208af3c5303b6 vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB