mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-03 18:28:25 -04:00
1361 lines
156 KiB
JavaScript
1361 lines
156 KiB
JavaScript
|
|
/*! jQuery v1.11.2 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
|
||
|
|
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.2",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each(
|
||
|
|
return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</tex
|
||
|
|
}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.exten
|
||
|
|
//# sourceMappingURL=jquery.min.map
|
||
|
|
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){!function(a,c){"undefined"!=typeof b&&b.exports?b.exports=c():"function"==typeof define&&define.amd?define(c):this[a]=c()}("bowser",function(){function a(a){function c(b){var c=a.match(b);return c&&c.length>1&&c[1]||""}function d(b){var c=a.match(b);return c&&c.length>1&&c[2]||""}var e,f=c(/(ipod|iphone|ipad)/i).toLowerCase(),g=/like android/i.test(a),h=!g&&/android/i.test(a),i=/CrOS/.test(a),j=c(/edge\/(\d+(\.\d+)?)/i),k=c(/version\/(\d+(\.\d+)?)/i),l=/tablet/i.test(a),m=!l&&/[^-]mobi/i.test(a);/opera|opr/i.test(a)?e={name:"Opera",opera:b,version:k||c(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i)}:/yabrowser/i.test(a)?e={name:"Yandex Browser",yandexbrowser:b,version:k||c(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)}:/windows phone/i.test(a)?(e={name:"Windows Phone",windowsphone:b},j?(e.msedge=b,e.version=j):(e.msie=b,e.version=c(/iemobile\/(\d+(\.\d+)?)/i))):/msie|trident/i.test(a)?e={name:"Internet Explorer",msie:b,version:c(/(?:msie |rv:)(\d+(\.\d+)?)/i)}:i?e={name:"Chrome",chromeBook:b,chrome:b,version:c(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:/chrome.+? edge/i.test(a)?e={name:"Microsoft Edge",msedge:b,version:j}:/chrome|crios|crmo/i.test(a)?e={name:"Chrome",chrome:b,version:c(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:f?(e={name:"iphone"==f?"iPhone":"ipad"==f?"iPad":"iPod"},k&&(e.version=k)):/sailfish/i.test(a)?e={name:"Sailfish",sailfish:b,version:c(/sailfish\s?browser\/(\d+(\.\d+)?)/i)}:/seamonkey\//i.test(a)?e={name:"SeaMonkey",seamonkey:b,version:c(/seamonkey\/(\d+(\.\d+)?)/i)}:/firefox|iceweasel/i.test(a)?(e={name:"Firefox",firefox:b,version:c(/(?:firefox|iceweasel)[ \/](\d+(\.\d+)?)/i)},/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(a)&&(e.firefoxos=b)):/silk/i.test(a)?e={name:"Amazon Silk",silk:b,version:c(/silk\/(\d+(\.\d+)?)/i)}:h?e={name:"Android",version:k}:/phantom/i.test(a)?e={name:"PhantomJS",phantom:b,version:c(/phantomjs\/(\d+(\.\d+)?)/i)}:/blackberry|\bbb\d+/i.test(a)||/rim\stablet/i.test(a)?e={name:"BlackBerry",blackberry:b,version:k||c(/blackberry[\d]+\/(\d+(\.\d+)?)/i)}:/(web|hpw)os/i.test(a)?(e={name:"WebOS",webos:b,version:k||c(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)},/touchpad\//i.test(a)&&(e.touchpad=b)):e=/bada/i.test(a)?{name:"Bada",bada:b,version:c(/dolfin\/(\d+(\.\d+)?)/i)}:/tizen/i.test(a)?{name:"Tizen",tizen:b,version:c(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i)||k}:/safari/i.test(a)?{name:"Safari",safari:b,version:k}:{name:c(/^(.*)\/(.*) /),version:d(/^(.*)\/(.*) /)},!e.msedge&&/(apple)?webkit/i.test(a)?(e.name=e.name||"Webkit",e.webkit=b,!e.version&&k&&(e.version=k)):!e.opera&&/gecko\//i.test(a)&&(e.name=e.name||"Gecko",e.gecko=b,e.version=e.version||c(/gecko\/(\d+(\.\d+)?)/i)),e.msedge||!h&&!e.silk?f&&(e[f]=b,e.ios=b):e.android=b;var n="";e.windowsphone?n=c(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i):f?(n=c(/os (\d+([_\s]\d+)*) like mac os x/i),n=n.replace(/[_\s]/g,".")):h?n=c(/android[ \/-](\d+(\.\d+)*)/i):e.webos?n=c(/(?:web|hpw)os\/(\d+(\.\d+)*)/i):e.blackberry?n=c(/rim\stablet\sos\s(\d+(\.\d+)*)/i):e.bada?n=c(/bada\/(\d+(\.\d+)*)/i):e.tizen&&(n=c(/tizen[\/\s](\d+(\.\d+)*)/i)),n&&(e.osversion=n);var o=n.split(".")[0];return l||"ipad"==f||h&&(3==o||4==o&&!m)||e.silk?e.tablet=b:(m||"iphone"==f||"ipod"==f||h||e.blackberry||e.webos||e.bada)&&(e.mobile=b),e.msedge||e.msie&&e.version>=10||e.yandexbrowser&&e.version>=15||e.chrome&&e.version>=20||e.firefox&&e.version>=20||e.safari&&e.version>=6||e.opera&&e.version>=10||e.ios&&e.osversion&&e.osversion.split(".")[0]>=6||e.blackberry&&e.version>=10.1?e.a=b:e.msie&&e.version<10||e.chrome&&e.version<20||e.firefox&&e.version<20||e.safari&&e.version<6||e.opera&&e.version<10||e.ios&&e.osversion&&e.osversion.split(".")[0]<
|
||
|
|
(function() {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
var diffcount;
|
||
|
|
|
||
|
|
var Diff = function (options) {
|
||
|
|
var diff = this;
|
||
|
|
Object.keys(options).forEach(function(option) {
|
||
|
|
diff[option] = options[option];
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
Diff.prototype = {
|
||
|
|
toString: function() {
|
||
|
|
return JSON.stringify(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: compress diff output by replacing these keys with numbers or alike:
|
||
|
|
/* 'addAttribute' = 0,
|
||
|
|
'modifyAttribute' = 1,
|
||
|
|
'removeAttribute' = 2,
|
||
|
|
'modifyTextElement' = 3,
|
||
|
|
'relocateGroup' = 4,
|
||
|
|
'removeElement' = 5,
|
||
|
|
'addElement' = 6,
|
||
|
|
'removeTextElement' = 7,
|
||
|
|
'addTextElement' = 8,
|
||
|
|
'replaceElement' = 9,
|
||
|
|
'modifyValue' = 10,
|
||
|
|
'modifyChecked' = 11,
|
||
|
|
'modifySelected' = 12,
|
||
|
|
'modifyComment' = 13,
|
||
|
|
'action' = 14,
|
||
|
|
'route' = 15,
|
||
|
|
'oldValue' = 16,
|
||
|
|
'newValue' = 17,
|
||
|
|
'element' = 18,
|
||
|
|
'group' = 19,
|
||
|
|
'from' = 20,
|
||
|
|
'to' = 21,
|
||
|
|
'name' = 22,
|
||
|
|
'value' = 23,
|
||
|
|
'data' = 24,
|
||
|
|
'attributes' = 25,
|
||
|
|
'nodeName' = 26,
|
||
|
|
'childNodes' = 27,
|
||
|
|
'checked' = 28,
|
||
|
|
'selected' = 29;*/
|
||
|
|
};
|
||
|
|
|
||
|
|
var SubsetMapping = function SubsetMapping(a, b) {
|
||
|
|
this.oldValue = a;
|
||
|
|
this.newValue = b;
|
||
|
|
};
|
||
|
|
|
||
|
|
SubsetMapping.prototype = {
|
||
|
|
contains: function contains(subset) {
|
||
|
|
if (subset.length < this.length) {
|
||
|
|
return subset.newValue >= this.newValue && subset.newValue < this.newValue + this.length;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
},
|
||
|
|
toString: function toString() {
|
||
|
|
return this.length + " element subset, first mapping: old " + this.oldValue + " → new " + this.newValue;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var elementDescriptors = function(el) {
|
||
|
|
var output = [];
|
||
|
|
if (el.nodeName !== '#text' && el.nodeName !== '#comment') {
|
||
|
|
output.push(el.nodeName);
|
||
|
|
if (el.attributes) {
|
||
|
|
if (el.attributes['class']) {
|
||
|
|
output.push(el.nodeName + '.' + el.attributes['class'].replace(/ /g, '.'));
|
||
|
|
}
|
||
|
|
if (el.attributes.id) {
|
||
|
|
output.push(el.nodeName + '#' + el.attributes.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
return output;
|
||
|
|
};
|
||
|
|
|
||
|
|
var findUniqueDescriptors = function(li) {
|
||
|
|
var uniqueDescriptors = {},
|
||
|
|
duplicateDescriptors = {};
|
||
|
|
|
||
|
|
li.forEach(function(node) {
|
||
|
|
elementDescriptors(node).forEach(function(descriptor) {
|
||
|
|
var inUnique = descriptor in uniqueDescriptors,
|
||
|
|
inDupes = descriptor in duplicateDescriptors;
|
||
|
|
if (!inUnique && !inDupes) {
|
||
|
|
uniqueDescriptors[descriptor] = true;
|
||
|
|
} else if (inUnique) {
|
||
|
|
delete uniqueDescriptors[descriptor];
|
||
|
|
duplicateDescriptors[descriptor] = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
return uniqueDescriptors;
|
||
|
|
};
|
||
|
|
|
||
|
|
var uniqueInBoth = function(l1, l2) {
|
||
|
|
var l1Unique = findUniqueDescriptors(l1),
|
||
|
|
l2Unique = findUniqueDescriptors(l2),
|
||
|
|
inBoth = {};
|
||
|
|
|
||
|
|
Object.keys(l1Unique).forEach(function(key) {
|
||
|
|
if (l2Unique[key]) {
|
||
|
|
inBoth[key] = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return inBoth;
|
||
|
|
};
|
||
|
|
|
||
|
|
var removeDone = function(tree) {
|
||
|
|
delete tree.outerDone;
|
||
|
|
delete tree.innerDone;
|
||
|
|
delete tree.valueDone;
|
||
|
|
if (tree.childNodes) {
|
||
|
|
return tree.childNodes.every(removeDone);
|
||
|
|
} else {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var isEqual = function(e1, e2) {
|
||
|
|
|
||
|
|
var e1Attributes, e2Attributes;
|
||
|
|
|
||
|
|
if (!['nodeName', 'value', 'checked', 'selected', 'data'].every(function(element) {
|
||
|
|
if (e1[element] !== e2[element]) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
})) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Boolean(e1.attributes) !== Boolean(e2.attributes)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Boolean(e1.childNodes) !== Boolean(e2.childNodes)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e1.attributes) {
|
||
|
|
e1Attributes = Object.keys(e1.attributes);
|
||
|
|
e2Attributes = Object.keys(e2.attributes);
|
||
|
|
|
||
|
|
if (e1Attributes.length !== e2Attributes.length) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (!e1Attributes.every(function(attribute) {
|
||
|
|
if (e1.attributes[attribute] !== e2.attributes[attribute]) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
})) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e1.childNodes) {
|
||
|
|
if (e1.childNodes.length !== e2.childNodes.length) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (!e1.childNodes.every(function(childNode, index) {
|
||
|
|
return isEqual(childNode, e2.childNodes[index]);
|
||
|
|
})) {
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
var roughlyEqual = function(e1, e2, uniqueDescriptors, sameSiblings, preventRecursion) {
|
||
|
|
var childUniqueDescriptors, nodeList1, nodeList2;
|
||
|
|
|
||
|
|
if (!e1 || !e2) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e1.nodeName !== e2.nodeName) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e1.nodeName === '#text') {
|
||
|
|
// Note that we initially don't care what the text content of a node is,
|
||
|
|
// the mere fact that it's the same tag and "has text" means it's roughly
|
||
|
|
// equal, and then we can find out the true text difference later.
|
||
|
|
return preventRecursion ? true : e1.data === e2.data;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if (e1.nodeName in uniqueDescriptors) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (e1.attributes && e2.attributes) {
|
||
|
|
|
||
|
|
if (e1.attributes.id && e1.attributes.id === e2.attributes.id) {
|
||
|
|
var idDescriptor = e1.nodeName + '#' + e1.attributes.id;
|
||
|
|
if (idDescriptor in uniqueDescriptors) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (e1.attributes['class'] && e1.attributes['class'] === e2.attributes['class']) {
|
||
|
|
var classDescriptor = e1.nodeName + '.' + e1.attributes['class'].replace(/ /g, '.');
|
||
|
|
if (classDescriptor in uniqueDescriptors) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sameSiblings) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
nodeList1 = e1.childNodes ? e1.childNodes.slice().reverse() : [];
|
||
|
|
nodeList2 = e2.childNodes ? e2.childNodes.slice().reverse() : [];
|
||
|
|
|
||
|
|
if (nodeList1.length !== nodeList2.length) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (preventRecursion) {
|
||
|
|
return nodeList1.every(function(element, index) {
|
||
|
|
return element.nodeName === nodeList2[index].nodeName;
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// note: we only allow one level of recursion at any depth. If 'preventRecursion'
|
||
|
|
// was not set, we must explicitly force it to true for child iterations.
|
||
|
|
childUniqueDescriptors = uniqueInBoth(nodeList1, nodeList2);
|
||
|
|
return nodeList1.every(function(element, index) {
|
||
|
|
return roughlyEqual(element, nodeList2[index], childUniqueDescriptors, true, true);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
var cloneObj = function(obj) {
|
||
|
|
// TODO: Do we really need to clone here? Is it not enough to just return the original object?
|
||
|
|
return JSON.parse(JSON.stringify(obj));
|
||
|
|
//return obj;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript
|
||
|
|
*/
|
||
|
|
var findCommonSubsets = function(c1, c2, marked1, marked2) {
|
||
|
|
var lcsSize = 0,
|
||
|
|
index = [],
|
||
|
|
matches = Array.apply(null, new Array(c1.length + 1)).map(function() {
|
||
|
|
return [];
|
||
|
|
}), // set up the matching table
|
||
|
|
uniqueDescriptors = uniqueInBoth(c1, c2),
|
||
|
|
// If all of the elements are the same tag, id and class, then we can
|
||
|
|
// consider them roughly the same even if they have a different number of
|
||
|
|
// children. This will reduce removing and re-adding similar elements.
|
||
|
|
subsetsSame = c1.length === c2.length,
|
||
|
|
origin, ret;
|
||
|
|
|
||
|
|
if (subsetsSame) {
|
||
|
|
|
||
|
|
c1.some(function(element, i) {
|
||
|
|
var c1Desc = elementDescriptors(element),
|
||
|
|
c2Desc = elementDescriptors(c2[i]);
|
||
|
|
if (c1Desc.length !== c2Desc.length) {
|
||
|
|
subsetsSame = false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
c1Desc.some(function(description, i) {
|
||
|
|
if (description !== c2Desc[i]) {
|
||
|
|
subsetsSame = false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (!subsetsSame) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// fill the matches with distance values
|
||
|
|
c1.forEach(function(c1Element, c1Index) {
|
||
|
|
c2.forEach(function(c2Element, c2Index) {
|
||
|
|
if (!marked1[c1Index] && !marked2[c2Index] && roughlyEqual(c1Element, c2Element, uniqueDescriptors, subsetsSame)) {
|
||
|
|
matches[c1Index + 1][c2Index + 1] = (matches[c1Index][c2Index] ? matches[c1Index][c2Index] + 1 : 1);
|
||
|
|
if (matches[c1Index + 1][c2Index + 1] >= lcsSize) {
|
||
|
|
lcsSize = matches[c1Index + 1][c2Index + 1];
|
||
|
|
index = [c1Index + 1, c2Index + 1];
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
matches[c1Index + 1][c2Index + 1] = 0;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (lcsSize === 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
origin = [index[0] - lcsSize, index[1] - lcsSize];
|
||
|
|
ret = new SubsetMapping(origin[0], origin[1]);
|
||
|
|
ret.length = lcsSize;
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* This should really be a predefined function in Array...
|
||
|
|
*/
|
||
|
|
var makeArray = function(n, v) {
|
||
|
|
return Array.apply(null, new Array(n)).map(function() {
|
||
|
|
return v;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate arrays that indicate which node belongs to which subset,
|
||
|
|
* or whether it's actually an orphan node, existing in only one
|
||
|
|
* of the two trees, rather than somewhere in both.
|
||
|
|
*
|
||
|
|
* So if t1 = <img><canvas><br>, t2 = <canvas><br><img>.
|
||
|
|
* The longest subset is "<canvas><br>" (length 2), so it will group 0.
|
||
|
|
* The second longest is "<img>" (length 1), so it will be group 1.
|
||
|
|
* gaps1 will therefore be [1,0,0] and gaps2 [0,0,1].
|
||
|
|
*
|
||
|
|
* If an element is not part of any group, it will stay being 'true', which
|
||
|
|
* is the initial value. For example:
|
||
|
|
* t1 = <img><p></p><br><canvas>, t2 = <b></b><br><canvas><img>
|
||
|
|
*
|
||
|
|
* The "<p></p>" and "<b></b>" do only show up in one of the two and will
|
||
|
|
* therefore be marked by "true". The remaining parts are parts of the
|
||
|
|
* groups 0 and 1:
|
||
|
|
* gaps1 = [1, true, 0, 0], gaps2 = [true, 0, 0, 1]
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
var getGapInformation = function(t1, t2, stable) {
|
||
|
|
|
||
|
|
var gaps1 = t1.childNodes ? makeArray(t1.childNodes.length, true) : [],
|
||
|
|
gaps2 = t2.childNodes ? makeArray(t2.childNodes.length, true) : [],
|
||
|
|
group = 0;
|
||
|
|
|
||
|
|
// give elements from the same subset the same group number
|
||
|
|
stable.forEach(function(subset) {
|
||
|
|
var i, endOld = subset.oldValue + subset.length,
|
||
|
|
endNew = subset.newValue + subset.length;
|
||
|
|
for (i = subset.oldValue; i < endOld; i += 1) {
|
||
|
|
gaps1[i] = group;
|
||
|
|
}
|
||
|
|
for (i = subset.newValue; i < endNew; i += 1) {
|
||
|
|
gaps2[i] = group;
|
||
|
|
}
|
||
|
|
group += 1;
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
gaps1: gaps1,
|
||
|
|
gaps2: gaps2
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find all matching subsets, based on immediate child differences only.
|
||
|
|
*/
|
||
|
|
var markSubTrees = function(oldTree, newTree) {
|
||
|
|
// note: the child lists are views, and so update as we update old/newTree
|
||
|
|
var oldChildren = oldTree.childNodes ? oldTree.childNodes : [],
|
||
|
|
newChildren = newTree.childNodes ? newTree.childNodes : [],
|
||
|
|
marked1 = makeArray(oldChildren.length, false),
|
||
|
|
marked2 = makeArray(newChildren.length, false),
|
||
|
|
subsets = [],
|
||
|
|
subset = true,
|
||
|
|
returnIndex = function() {
|
||
|
|
return arguments[1];
|
||
|
|
},
|
||
|
|
markBoth = function(i) {
|
||
|
|
marked1[subset.oldValue + i] = true;
|
||
|
|
marked2[subset.newValue + i] = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
while (subset) {
|
||
|
|
subset = findCommonSubsets(oldChildren, newChildren, marked1, marked2);
|
||
|
|
if (subset) {
|
||
|
|
subsets.push(subset);
|
||
|
|
|
||
|
|
Array.apply(null, new Array(subset.length)).map(returnIndex).forEach(markBoth);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return subsets;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
function swap(obj, p1, p2) {
|
||
|
|
(function(_) {
|
||
|
|
obj[p1] = obj[p2];
|
||
|
|
obj[p2] = _;
|
||
|
|
}(obj[p1]));
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
var DiffTracker = function() {
|
||
|
|
this.list = [];
|
||
|
|
};
|
||
|
|
|
||
|
|
DiffTracker.prototype = {
|
||
|
|
list: false,
|
||
|
|
add: function(diffs) {
|
||
|
|
var list = this.list;
|
||
|
|
diffs.forEach(function(diff) {
|
||
|
|
list.push(diff);
|
||
|
|
});
|
||
|
|
},
|
||
|
|
forEach: function(fn) {
|
||
|
|
this.list.forEach(fn);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var diffDOM = function(options) {
|
||
|
|
|
||
|
|
var defaults = {
|
||
|
|
debug: false,
|
||
|
|
diffcap: 10, // Limit for how many diffs are accepting when debugging. Inactive when debug is false.
|
||
|
|
maxDepth: false, // False or a numeral. If set to a numeral, limits the level of depth that the the diff mechanism looks for differences. If false, goes through the entire tree.
|
||
|
|
valueDiffing: true, // Whether to take into consideration the values of forms that differ from auto assigned values (when a user fills out a form).
|
||
|
|
// syntax: textDiff: function (node, currentValue, expectedValue, newValue)
|
||
|
|
textDiff: function() {
|
||
|
|
arguments[0].data = arguments[3];
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
// empty functions were benchmarked as running faster than both
|
||
|
|
// `f && f()` and `if (f) { f(); }`
|
||
|
|
preVirtualDiffApply: function () {},
|
||
|
|
postVirtualDiffApply: function () {},
|
||
|
|
preDiffApply: function () {},
|
||
|
|
postDiffApply: function () {}
|
||
|
|
},
|
||
|
|
i;
|
||
|
|
|
||
|
|
if (typeof options === "undefined") {
|
||
|
|
options = {};
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i in defaults) {
|
||
|
|
if (typeof options[i] === "undefined") {
|
||
|
|
this[i] = defaults[i];
|
||
|
|
} else {
|
||
|
|
this[i] = options[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
diffDOM.prototype = {
|
||
|
|
|
||
|
|
// ===== Create a diff =====
|
||
|
|
|
||
|
|
diff: function(t1Node, t2Node) {
|
||
|
|
|
||
|
|
var t1 = this.nodeToObj(t1Node),
|
||
|
|
t2 = this.nodeToObj(t2Node);
|
||
|
|
|
||
|
|
diffcount = 0;
|
||
|
|
|
||
|
|
if (this.debug) {
|
||
|
|
this.t1Orig = this.nodeToObj(t1Node);
|
||
|
|
this.t2Orig = this.nodeToObj(t2Node);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.tracker = new DiffTracker();
|
||
|
|
return this.findDiffs(t1, t2);
|
||
|
|
},
|
||
|
|
findDiffs: function(t1, t2) {
|
||
|
|
var diffs;
|
||
|
|
do {
|
||
|
|
if (this.debug) {
|
||
|
|
diffcount += 1;
|
||
|
|
if (diffcount > this.diffcap) {
|
||
|
|
window.diffError = [this.t1Orig, this.t2Orig];
|
||
|
|
throw new Error("surpassed diffcap:" + JSON.stringify(this.t1Orig) + " -> " + JSON.stringify(this.t2Orig));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
diffs = this.findNextDiff(t1, t2, []);
|
||
|
|
if (diffs.length === 0) {
|
||
|
|
// Last check if the elements really are the same now.
|
||
|
|
// If not, remove all info about being done and start over.
|
||
|
|
// Somtimes a node can be marked as done, but the creation of subsequent diffs means that it has to be changed anyway.
|
||
|
|
if (!isEqual(t1, t2)) {
|
||
|
|
removeDone(t1);
|
||
|
|
diffs = this.findNextDiff(t1, t2, []);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (diffs.length > 0) {
|
||
|
|
this.tracker.add(diffs);
|
||
|
|
this.applyVirtual(t1, diffs);
|
||
|
|
}
|
||
|
|
} while (diffs.length > 0);
|
||
|
|
return this.tracker.list;
|
||
|
|
},
|
||
|
|
findNextDiff: function(t1, t2, route) {
|
||
|
|
var diffs;
|
||
|
|
|
||
|
|
if (this.maxDepth && route.length > this.maxDepth) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
// outer differences?
|
||
|
|
if (!t1.outerDone) {
|
||
|
|
diffs = this.findOuterDiff(t1, t2, route);
|
||
|
|
if (diffs.length > 0) {
|
||
|
|
t1.outerDone = true;
|
||
|
|
return diffs;
|
||
|
|
} else {
|
||
|
|
t1.outerDone = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// inner differences?
|
||
|
|
if (!t1.innerDone) {
|
||
|
|
diffs = this.findInnerDiff(t1, t2, route);
|
||
|
|
if (diffs.length > 0) {
|
||
|
|
return diffs;
|
||
|
|
} else {
|
||
|
|
t1.innerDone = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.valueDiffing && !t1.valueDone) {
|
||
|
|
// value differences?
|
||
|
|
diffs = this.findValueDiff(t1, t2, route);
|
||
|
|
|
||
|
|
if (diffs.length > 0) {
|
||
|
|
t1.valueDone = true;
|
||
|
|
return diffs;
|
||
|
|
} else {
|
||
|
|
t1.valueDone = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// no differences
|
||
|
|
return [];
|
||
|
|
},
|
||
|
|
findOuterDiff: function(t1, t2, route) {
|
||
|
|
|
||
|
|
var diffs = [],
|
||
|
|
attr1, attr2;
|
||
|
|
|
||
|
|
if (t1.nodeName !== t2.nodeName) {
|
||
|
|
return [new Diff({
|
||
|
|
action: 'replaceElement',
|
||
|
|
oldValue: cloneObj(t1),
|
||
|
|
newValue: cloneObj(t2),
|
||
|
|
route: route
|
||
|
|
})];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (t1.data !== t2.data) {
|
||
|
|
// Comment or text node.
|
||
|
|
if (t1.nodeName === '#text') {
|
||
|
|
return [new Diff({
|
||
|
|
action: 'modifyComment',
|
||
|
|
route: route,
|
||
|
|
oldValue: t1.data,
|
||
|
|
newValue: t2.data
|
||
|
|
})];
|
||
|
|
} else {
|
||
|
|
return [new Diff({
|
||
|
|
action: 'modifyTextElement',
|
||
|
|
route: route,
|
||
|
|
oldValue: t1.data,
|
||
|
|
newValue: t2.data
|
||
|
|
})];
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
attr1 = t1.attributes ? Object.keys(t1.attributes).sort() : [];
|
||
|
|
attr2 = t2.attributes ? Object.keys(t2.attributes).sort() : [];
|
||
|
|
|
||
|
|
attr1.forEach(function(attr) {
|
||
|
|
var pos = attr2.indexOf(attr);
|
||
|
|
if (pos === -1) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'removeAttribute',
|
||
|
|
route: route,
|
||
|
|
name: attr,
|
||
|
|
value: t1.attributes[attr]
|
||
|
|
}));
|
||
|
|
} else {
|
||
|
|
attr2.splice(pos, 1);
|
||
|
|
if (t1.attributes[attr] !== t2.attributes[attr]) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'modifyAttribute',
|
||
|
|
route: route,
|
||
|
|
name: attr,
|
||
|
|
oldValue: t1.attributes[attr],
|
||
|
|
newValue: t2.attributes[attr]
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
attr2.forEach(function(attr) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'addAttribute',
|
||
|
|
route: route,
|
||
|
|
name: attr,
|
||
|
|
value: t2.attributes[attr]
|
||
|
|
}));
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
return diffs;
|
||
|
|
},
|
||
|
|
nodeToObj: function(node) {
|
||
|
|
var objNode = {}, dobj = this;
|
||
|
|
objNode.nodeName = node.nodeName;
|
||
|
|
if (objNode.nodeName === '#text' || objNode.nodeName === '#comment') {
|
||
|
|
objNode.data = node.data;
|
||
|
|
} else {
|
||
|
|
if (node.attributes && node.attributes.length > 0) {
|
||
|
|
objNode.attributes = {};
|
||
|
|
Array.prototype.slice.call(node.attributes).forEach(
|
||
|
|
function(attribute) {
|
||
|
|
objNode.attributes[attribute.name] = attribute.value;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (node.childNodes && node.childNodes.length > 0) {
|
||
|
|
objNode.childNodes = [];
|
||
|
|
Array.prototype.slice.call(node.childNodes).forEach(
|
||
|
|
function(childNode) {
|
||
|
|
objNode.childNodes.push(dobj.nodeToObj(childNode));
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (this.valueDiffing) {
|
||
|
|
if (node.value !== undefined) {
|
||
|
|
objNode.value = node.value;
|
||
|
|
}
|
||
|
|
if (node.checked !== undefined) {
|
||
|
|
objNode.checked = node.checked;
|
||
|
|
}
|
||
|
|
if (node.selected !== undefined) {
|
||
|
|
objNode.selected = node.selected;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return objNode;
|
||
|
|
},
|
||
|
|
objToNode: function(objNode, insideSvg) {
|
||
|
|
var node, dobj = this;
|
||
|
|
if (objNode.nodeName === '#text') {
|
||
|
|
node = document.createTextNode(objNode.data);
|
||
|
|
|
||
|
|
} else if (objNode.nodeName === '#comment') {
|
||
|
|
node = document.createComment(objNode.data);
|
||
|
|
} else {
|
||
|
|
if (objNode.nodeName === 'svg' || insideSvg) {
|
||
|
|
node = document.createElementNS('http://www.w3.org/2000/svg', objNode.nodeName);
|
||
|
|
insideSvg = true;
|
||
|
|
} else {
|
||
|
|
node = document.createElement(objNode.nodeName);
|
||
|
|
}
|
||
|
|
if (objNode.attributes) {
|
||
|
|
Object.keys(objNode.attributes).forEach(function(attribute) {
|
||
|
|
node.setAttribute(attribute, objNode.attributes[attribute]);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (objNode.childNodes) {
|
||
|
|
objNode.childNodes.forEach(function(childNode) {
|
||
|
|
node.appendChild(dobj.objToNode(childNode, insideSvg));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (this.valueDiffing) {
|
||
|
|
if (objNode.value) {
|
||
|
|
node.value = objNode.value;
|
||
|
|
}
|
||
|
|
if (objNode.checked) {
|
||
|
|
node.checked = objNode.checked;
|
||
|
|
}
|
||
|
|
if (objNode.selected) {
|
||
|
|
node.selected = objNode.selected;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return node;
|
||
|
|
},
|
||
|
|
findInnerDiff: function(t1, t2, route) {
|
||
|
|
|
||
|
|
var subtrees = (t1.childNodes && t2.childNodes) ? markSubTrees(t1, t2) : [],
|
||
|
|
t1ChildNodes = t1.childNodes ? t1.childNodes : [],
|
||
|
|
t2ChildNodes = t2.childNodes ? t2.childNodes : [],
|
||
|
|
childNodesLengthDifference, diffs = [],
|
||
|
|
index = 0,
|
||
|
|
last, e1, e2, i;
|
||
|
|
|
||
|
|
if (subtrees.length > 1) {
|
||
|
|
/* Two or more groups have been identified among the childnodes of t1
|
||
|
|
* and t2.
|
||
|
|
*/
|
||
|
|
return this.attemptGroupRelocation(t1, t2, subtrees, route);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 0 or 1 groups of similar child nodes have been found
|
||
|
|
* for t1 and t2. 1 If there is 1, it could be a sign that the
|
||
|
|
* contents are the same. When the number of groups is below 2,
|
||
|
|
* t1 and t2 are made to have the same length and each of the
|
||
|
|
* pairs of child nodes are diffed.
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
last = Math.max(t1ChildNodes.length, t2ChildNodes.length);
|
||
|
|
if (t1ChildNodes.length !== t2ChildNodes.length) {
|
||
|
|
childNodesLengthDifference = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < last; i += 1) {
|
||
|
|
e1 = t1ChildNodes[i];
|
||
|
|
e2 = t2ChildNodes[i];
|
||
|
|
|
||
|
|
if (childNodesLengthDifference) {
|
||
|
|
/* t1 and t2 have different amounts of childNodes. Add
|
||
|
|
* and remove as necessary to obtain the same length */
|
||
|
|
if (e1 && !e2) {
|
||
|
|
if (e1.nodeName === '#text') {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'removeTextElement',
|
||
|
|
route: route.concat(index),
|
||
|
|
value: e1.data
|
||
|
|
}));
|
||
|
|
index -= 1;
|
||
|
|
} else {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'removeElement',
|
||
|
|
route: route.concat(index),
|
||
|
|
element: cloneObj(e1)
|
||
|
|
}));
|
||
|
|
index -= 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
} else if (e2 && !e1) {
|
||
|
|
if (e2.nodeName === '#text') {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'addTextElement',
|
||
|
|
route: route.concat(index),
|
||
|
|
value: e2.data
|
||
|
|
}));
|
||
|
|
} else {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'addElement',
|
||
|
|
route: route.concat(index),
|
||
|
|
element: cloneObj(e2)
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* We are now guaranteed that childNodes e1 and e2 exist,
|
||
|
|
* and that they can be diffed.
|
||
|
|
*/
|
||
|
|
/* Diffs in child nodes should not affect the parent node,
|
||
|
|
* so we let these diffs be submitted together with other
|
||
|
|
* diffs.
|
||
|
|
*/
|
||
|
|
|
||
|
|
if (e1 && e2) {
|
||
|
|
diffs = diffs.concat(this.findNextDiff(e1, e2, route.concat(index)));
|
||
|
|
}
|
||
|
|
|
||
|
|
index += 1;
|
||
|
|
|
||
|
|
}
|
||
|
|
t1.innerDone = true;
|
||
|
|
return diffs;
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
attemptGroupRelocation: function(t1, t2, subtrees, route) {
|
||
|
|
/* Either t1.childNodes and t2.childNodes have the same length, or
|
||
|
|
* there are at least two groups of similar elements can be found.
|
||
|
|
* attempts are made at equalizing t1 with t2. First all initial
|
||
|
|
* elements with no group affiliation (gaps=true) are removed (if
|
||
|
|
* only in t1) or added (if only in t2). Then the creation of a group
|
||
|
|
* relocation diff is attempted.
|
||
|
|
*/
|
||
|
|
|
||
|
|
var gapInformation = getGapInformation(t1, t2, subtrees),
|
||
|
|
gaps1 = gapInformation.gaps1,
|
||
|
|
gaps2 = gapInformation.gaps2,
|
||
|
|
shortest = Math.min(gaps1.length, gaps2.length),
|
||
|
|
destinationDifferent, toGroup,
|
||
|
|
group, node, similarNode, testI, diffs = [],
|
||
|
|
index1, index2, j;
|
||
|
|
|
||
|
|
|
||
|
|
for (index2 = 0, index1 = 0; index2 < shortest; index1 += 1, index2 += 1) {
|
||
|
|
if (gaps1[index2] === true) {
|
||
|
|
node = t1.childNodes[index1];
|
||
|
|
if (node.nodeName === '#text') {
|
||
|
|
if (t2.childNodes[index2].nodeName === '#text' && node.data !== t2.childNodes[index2].data) {
|
||
|
|
testI = index1;
|
||
|
|
while (t1.childNodes.length > testI + 1 && t1.childNodes[testI + 1].nodeName === '#text') {
|
||
|
|
testI += 1;
|
||
|
|
if (t2.childNodes[index2].data === t1.childNodes[testI].data) {
|
||
|
|
similarNode = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!similarNode) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'modifyTextElement',
|
||
|
|
route: route.concat(index2),
|
||
|
|
oldValue: node.data,
|
||
|
|
newValue: t2.childNodes[index2].data
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'removeTextElement',
|
||
|
|
route: route.concat(index2),
|
||
|
|
value: node.data
|
||
|
|
}));
|
||
|
|
gaps1.splice(index2, 1);
|
||
|
|
shortest = Math.min(gaps1.length, gaps2.length);
|
||
|
|
index2 -= 1;
|
||
|
|
} else {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'removeElement',
|
||
|
|
route: route.concat(index2),
|
||
|
|
element: cloneObj(node)
|
||
|
|
}));
|
||
|
|
gaps1.splice(index2, 1);
|
||
|
|
shortest = Math.min(gaps1.length, gaps2.length);
|
||
|
|
index2 -= 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
} else if (gaps2[index2] === true) {
|
||
|
|
node = t2.childNodes[index2];
|
||
|
|
if (node.nodeName === '#text') {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'addTextElement',
|
||
|
|
route: route.concat(index2),
|
||
|
|
value: node.data
|
||
|
|
}));
|
||
|
|
gaps1.splice(index2, 0, true);
|
||
|
|
shortest = Math.min(gaps1.length, gaps2.length);
|
||
|
|
index1 -= 1;
|
||
|
|
} else {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'addElement',
|
||
|
|
route: route.concat(index2),
|
||
|
|
element: cloneObj(node)
|
||
|
|
}));
|
||
|
|
gaps1.splice(index2, 0, true);
|
||
|
|
shortest = Math.min(gaps1.length, gaps2.length);
|
||
|
|
index1 -= 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
} else if (gaps1[index2] !== gaps2[index2]) {
|
||
|
|
if (diffs.length > 0) {
|
||
|
|
return diffs;
|
||
|
|
}
|
||
|
|
// group relocation
|
||
|
|
group = subtrees[gaps1[index2]];
|
||
|
|
toGroup = Math.min(group.newValue, (t1.childNodes.length - group.length));
|
||
|
|
if (toGroup !== group.oldValue) {
|
||
|
|
// Check whether destination nodes are different than originating ones.
|
||
|
|
destinationDifferent = false;
|
||
|
|
for (j = 0; j < group.length; j += 1) {
|
||
|
|
if (!roughlyEqual(t1.childNodes[toGroup + j], t1.childNodes[group.oldValue + j], [], false, true)) {
|
||
|
|
destinationDifferent = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (destinationDifferent) {
|
||
|
|
return [new Diff({
|
||
|
|
action: 'relocateGroup',
|
||
|
|
groupLength: group.length,
|
||
|
|
from: group.oldValue,
|
||
|
|
to: toGroup,
|
||
|
|
route: route
|
||
|
|
})];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return diffs;
|
||
|
|
},
|
||
|
|
|
||
|
|
findValueDiff: function(t1, t2, route) {
|
||
|
|
// Differences of value. Only useful if the value/selection/checked value
|
||
|
|
// differs from what is represented in the DOM. For example in the case
|
||
|
|
// of filled out forms, etc.
|
||
|
|
var diffs = [];
|
||
|
|
|
||
|
|
if (t1.selected !== t2.selected) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'modifySelected',
|
||
|
|
oldValue: t1.selected,
|
||
|
|
newValue: t2.selected,
|
||
|
|
route: route
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((t1.value || t2.value) && t1.value !== t2.value && t1.nodeName !== 'OPTION') {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'modifyValue',
|
||
|
|
oldValue: t1.value,
|
||
|
|
newValue: t2.value,
|
||
|
|
route: route
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
if (t1.checked !== t2.checked) {
|
||
|
|
diffs.push(new Diff({
|
||
|
|
action: 'modifyChecked',
|
||
|
|
oldValue: t1.checked,
|
||
|
|
newValue: t2.checked,
|
||
|
|
route: route
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
return diffs;
|
||
|
|
},
|
||
|
|
|
||
|
|
// ===== Apply a virtual diff =====
|
||
|
|
|
||
|
|
applyVirtual: function(tree, diffs) {
|
||
|
|
var dobj = this;
|
||
|
|
if (diffs.length === 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
diffs.forEach(function(diff) {
|
||
|
|
dobj.applyVirtualDiff(tree, diff);
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
getFromVirtualRoute: function(tree, route) {
|
||
|
|
var node = tree,
|
||
|
|
parentNode, nodeIndex;
|
||
|
|
|
||
|
|
route = route.slice();
|
||
|
|
while (route.length > 0) {
|
||
|
|
if (!node.childNodes) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
nodeIndex = route.splice(0, 1)[0];
|
||
|
|
parentNode = node;
|
||
|
|
node = node.childNodes[nodeIndex];
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
node: node,
|
||
|
|
parentNode: parentNode,
|
||
|
|
nodeIndex: nodeIndex
|
||
|
|
};
|
||
|
|
},
|
||
|
|
applyVirtualDiff: function(tree, diff) {
|
||
|
|
var routeInfo = this.getFromVirtualRoute(tree, diff.route),
|
||
|
|
node = routeInfo.node,
|
||
|
|
parentNode = routeInfo.parentNode,
|
||
|
|
nodeIndex = routeInfo.nodeIndex,
|
||
|
|
newNode, route, c;
|
||
|
|
|
||
|
|
// pre-diff hook
|
||
|
|
var info = {
|
||
|
|
diff: diff,
|
||
|
|
node: node
|
||
|
|
};
|
||
|
|
|
||
|
|
if (this.preVirtualDiffApply(info)) { return true; }
|
||
|
|
|
||
|
|
switch (diff.action) {
|
||
|
|
case 'addAttribute':
|
||
|
|
if (!node.attributes) {
|
||
|
|
node.attributes = {};
|
||
|
|
}
|
||
|
|
|
||
|
|
node.attributes[diff.name] = diff.value;
|
||
|
|
|
||
|
|
if (diff.name === 'checked') {
|
||
|
|
node.checked = true;
|
||
|
|
} else if (diff.name === 'selected') {
|
||
|
|
node.selected = true;
|
||
|
|
} else if (node.nodeName === 'INPUT' && diff.name === 'value') {
|
||
|
|
node.value = diff.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
case 'modifyAttribute':
|
||
|
|
node.attributes[diff.name] = diff.newValue;
|
||
|
|
if (node.nodeName === 'INPUT' && diff.name === 'value') {
|
||
|
|
node.value = diff.value;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'removeAttribute':
|
||
|
|
|
||
|
|
delete node.attributes[diff.name];
|
||
|
|
|
||
|
|
if (Object.keys(node.attributes).length === 0) {
|
||
|
|
delete node.attributes;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (diff.name === 'checked') {
|
||
|
|
delete node.checked;
|
||
|
|
} else if (diff.name === 'selected') {
|
||
|
|
delete node.selected;
|
||
|
|
} else if (node.nodeName === 'INPUT' && diff.name === 'value') {
|
||
|
|
delete node.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
case 'modifyTextElement':
|
||
|
|
node.data = diff.newValue;
|
||
|
|
|
||
|
|
if (parentNode.nodeName === 'TEXTAREA') {
|
||
|
|
parentNode.value = diff.newValue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'modifyValue':
|
||
|
|
node.value = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'modifyComment':
|
||
|
|
node.data = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'modifyChecked':
|
||
|
|
node.checked = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'modifySelected':
|
||
|
|
node.selected = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'replaceElement':
|
||
|
|
newNode = cloneObj(diff.newValue);
|
||
|
|
newNode.outerDone = true;
|
||
|
|
newNode.innerDone = true;
|
||
|
|
newNode.valueDone = true;
|
||
|
|
parentNode.childNodes[nodeIndex] = newNode;
|
||
|
|
break;
|
||
|
|
case 'relocateGroup':
|
||
|
|
node.childNodes.splice(diff.from, diff.groupLength).reverse()
|
||
|
|
.forEach(function(movedNode) {
|
||
|
|
node.childNodes.splice(diff.to, 0, movedNode);
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 'removeElement':
|
||
|
|
parentNode.childNodes.splice(nodeIndex, 1);
|
||
|
|
break;
|
||
|
|
case 'addElement':
|
||
|
|
route = diff.route.slice();
|
||
|
|
c = route.splice(route.length - 1, 1)[0];
|
||
|
|
node = this.getFromVirtualRoute(tree, route).node;
|
||
|
|
newNode = cloneObj(diff.element);
|
||
|
|
newNode.outerDone = true;
|
||
|
|
newNode.innerDone = true;
|
||
|
|
newNode.valueDone = true;
|
||
|
|
|
||
|
|
if (!node.childNodes) {
|
||
|
|
node.childNodes = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (c >= node.childNodes.length) {
|
||
|
|
node.childNodes.push(newNode);
|
||
|
|
} else {
|
||
|
|
node.childNodes.splice(c, 0, newNode);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'removeTextElement':
|
||
|
|
parentNode.childNodes.splice(nodeIndex, 1);
|
||
|
|
if (parentNode.nodeName === 'TEXTAREA') {
|
||
|
|
delete parentNode.value;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'addTextElement':
|
||
|
|
route = diff.route.slice();
|
||
|
|
c = route.splice(route.length - 1, 1)[0];
|
||
|
|
newNode = {};
|
||
|
|
newNode.nodeName = '#text';
|
||
|
|
newNode.data = diff.value;
|
||
|
|
node = this.getFromVirtualRoute(tree, route).node;
|
||
|
|
if (!node.childNodes) {
|
||
|
|
node.childNodes = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (c >= node.childNodes.length) {
|
||
|
|
node.childNodes.push(newNode);
|
||
|
|
} else {
|
||
|
|
node.childNodes.splice(c, 0, newNode);
|
||
|
|
}
|
||
|
|
if (node.nodeName === 'TEXTAREA') {
|
||
|
|
node.value = diff.newValue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
console.log('unknown action');
|
||
|
|
}
|
||
|
|
|
||
|
|
// capture newNode for the callback
|
||
|
|
info.newNode = newNode;
|
||
|
|
this.postVirtualDiffApply(info);
|
||
|
|
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// ===== Apply a diff =====
|
||
|
|
|
||
|
|
apply: function(tree, diffs) {
|
||
|
|
var dobj = this;
|
||
|
|
|
||
|
|
if (diffs.length === 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
diffs.forEach(function(diff) {
|
||
|
|
if (!dobj.applyDiff(tree, diff)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
getFromRoute: function(tree, route) {
|
||
|
|
route = route.slice();
|
||
|
|
var c, node = tree;
|
||
|
|
while (route.length > 0) {
|
||
|
|
if (!node.childNodes) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
c = route.splice(0, 1)[0];
|
||
|
|
node = node.childNodes[c];
|
||
|
|
}
|
||
|
|
return node;
|
||
|
|
},
|
||
|
|
applyDiff: function(tree, diff) {
|
||
|
|
var node = this.getFromRoute(tree, diff.route),
|
||
|
|
newNode, reference, route, c;
|
||
|
|
|
||
|
|
// pre-diff hook
|
||
|
|
var info = {
|
||
|
|
diff: diff,
|
||
|
|
node: node
|
||
|
|
};
|
||
|
|
|
||
|
|
if (this.preDiffApply(info)) { return true; }
|
||
|
|
|
||
|
|
switch (diff.action) {
|
||
|
|
case 'addAttribute':
|
||
|
|
if (!node || !node.setAttribute) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.setAttribute(diff.name, diff.value);
|
||
|
|
break;
|
||
|
|
case 'modifyAttribute':
|
||
|
|
if (!node || !node.setAttribute) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.setAttribute(diff.name, diff.newValue);
|
||
|
|
break;
|
||
|
|
case 'removeAttribute':
|
||
|
|
if (!node || !node.removeAttribute) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.removeAttribute(diff.name);
|
||
|
|
break;
|
||
|
|
case 'modifyTextElement':
|
||
|
|
if (!node || node.nodeType !== 3) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
this.textDiff(node, node.data, diff.oldValue, diff.newValue);
|
||
|
|
break;
|
||
|
|
case 'modifyValue':
|
||
|
|
if (!node || typeof node.value === 'undefined') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.value = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'modifyComment':
|
||
|
|
if (!node || typeof node.data === 'undefined') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
this.textDiff(node, node.data, diff.oldValue, diff.newValue);
|
||
|
|
break;
|
||
|
|
case 'modifyChecked':
|
||
|
|
if (!node || typeof node.checked === 'undefined') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.checked = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'modifySelected':
|
||
|
|
if (!node || typeof node.selected === 'undefined') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.selected = diff.newValue;
|
||
|
|
break;
|
||
|
|
case 'replaceElement':
|
||
|
|
node.parentNode.replaceChild(this.objToNode(diff.newValue, node.namespaceURI === 'http://www.w3.org/2000/svg'), node);
|
||
|
|
break;
|
||
|
|
case 'relocateGroup':
|
||
|
|
Array.apply(null, new Array(diff.groupLength)).map(function() {
|
||
|
|
return node.removeChild(node.childNodes[diff.from]);
|
||
|
|
}).forEach(function(childNode, index) {
|
||
|
|
if (index === 0) {
|
||
|
|
reference = node.childNodes[diff.to];
|
||
|
|
}
|
||
|
|
node.insertBefore(childNode, reference);
|
||
|
|
});
|
||
|
|
break;
|
||
|
|
case 'removeElement':
|
||
|
|
node.parentNode.removeChild(node);
|
||
|
|
break;
|
||
|
|
case 'addElement':
|
||
|
|
route = diff.route.slice();
|
||
|
|
c = route.splice(route.length - 1, 1)[0];
|
||
|
|
node = this.getFromRoute(tree, route);
|
||
|
|
node.insertBefore(this.objToNode(diff.element, node.namespaceURI === 'http://www.w3.org/2000/svg'), node.childNodes[c]);
|
||
|
|
break;
|
||
|
|
case 'removeTextElement':
|
||
|
|
if (!node || node.nodeType !== 3) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.parentNode.removeChild(node);
|
||
|
|
break;
|
||
|
|
case 'addTextElement':
|
||
|
|
route = diff.route.slice();
|
||
|
|
c = route.splice(route.length - 1, 1)[0];
|
||
|
|
newNode = document.createTextNode(diff.value);
|
||
|
|
node = this.getFromRoute(tree, route);
|
||
|
|
if (!node || !node.childNodes) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
node.insertBefore(newNode, node.childNodes[c]);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
console.log('unknown action');
|
||
|
|
}
|
||
|
|
|
||
|
|
// if a new node was created, we might be interested in it
|
||
|
|
// post diff hook
|
||
|
|
info.newNode = newNode;
|
||
|
|
this.postDiffApply(info);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
},
|
||
|
|
|
||
|
|
// ===== Undo a diff =====
|
||
|
|
|
||
|
|
undo: function(tree, diffs) {
|
||
|
|
diffs = diffs.slice();
|
||
|
|
var dobj = this;
|
||
|
|
if (!diffs.length) {
|
||
|
|
diffs = [diffs];
|
||
|
|
}
|
||
|
|
diffs.reverse();
|
||
|
|
diffs.forEach(function(diff) {
|
||
|
|
dobj.undoDiff(tree, diff);
|
||
|
|
});
|
||
|
|
},
|
||
|
|
undoDiff: function(tree, diff) {
|
||
|
|
|
||
|
|
switch (diff.action) {
|
||
|
|
case 'addAttribute':
|
||
|
|
diff.action = 'removeAttribute';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifyAttribute':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'removeAttribute':
|
||
|
|
diff.action = 'addAttribute';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifyTextElement':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifyValue':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifyComment':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifyChecked':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'modifySelected':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'replaceElement':
|
||
|
|
swap(diff, 'oldValue', 'newValue');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'relocateGroup':
|
||
|
|
swap(diff, 'from', 'to');
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'removeElement':
|
||
|
|
diff.action = 'addElement';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'addElement':
|
||
|
|
diff.action = 'removeElement';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'removeTextElement':
|
||
|
|
diff.action = 'addTextElement';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
case 'addTextElement':
|
||
|
|
diff.action = 'removeTextElement';
|
||
|
|
this.applyDiff(tree, diff);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
console.log('unknown action');
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (typeof exports !== 'undefined') {
|
||
|
|
if (typeof module !== 'undefined' && module.exports) {
|
||
|
|
exports = module.exports = diffDOM;
|
||
|
|
}
|
||
|
|
exports.diffDOM = diffDOM;
|
||
|
|
} else {
|
||
|
|
// `window` in the browser, or `exports` on the server
|
||
|
|
this.diffDOM = diffDOM;
|
||
|
|
}
|
||
|
|
|
||
|
|
}.call(this));
|
||
|
|
|
||
|
|
"use strict";!function(t,e){e.GOVUK=e.GOVUK||{},GOVUK.Modules=GOVUK.Modules||{},GOVUK.modules={find:function(e){var n,r="[data-module]",e=e||t("body");return n=e.find(r),e.is(r)&&(n=n.add(e)),n},start:function(e){function n(t){return u(r(t))}function r(t){return t.replace(/-([a-z])/g,function(t){return t[1].toUpperCase()})}function u(t){return t.charAt(0).toUpperCase()+t.slice(1)}for(var o=this.find(e),d=0,a=o.length;a>d;d++){var i,s=t(o[d]),f=n(s.data("module")),c=s.data("module-started");"function"!=typeof GOVUK.Modules[f]||c||(i=new GOVUK.Modules[f],i.start(s),s.data("module-started",!0))}}}}(jQuery,window);
|
||
|
|
"use strict";(function(){var e=this,t=e.jQuery;"undefined"==typeof GOVUK&&(e.GOVUK={});var s=function(e,s){var i;this.selectedClass="selected",this.focusedClass="focused",void 0!==s&&t.each(s,function(e,t){this[e]=t}.bind(this)),"string"==typeof e?(i=t(e),this.selector=e,this.setInitialState(t(this.selector))):void 0!==e&&(this.$elms=e,this.setInitialState(this.$elms)),this.addEvents()};s.prototype.addEvents=function(){"undefined"!=typeof this.$elms?this.addElementLevelEvents():this.addDocumentLevelEvents()},s.prototype.setInitialState=function(e){e.each(function(e,s){var i=t(s);i.is(":checked")&&this.markSelected(i)}.bind(this))},s.prototype.markFocused=function(e,t){"focused"===t?e.parent("label").addClass(this.focusedClass):e.parent("label").removeClass(this.focusedClass)},s.prototype.markSelected=function(e){var s;"radio"===e.attr("type")?(s=e.attr("name"),t(e[0].form).find('input[name="'+s+'"]').parent("label").removeClass(this.selectedClass),e.parent("label").addClass(this.selectedClass)):e.is(":checked")?e.parent("label").addClass(this.selectedClass):e.parent("label").removeClass(this.selectedClass)},s.prototype.addElementLevelEvents=function(){this.clickHandler=this.getClickHandler(),this.focusHandler=this.getFocusHandler({level:"element"}),this.$elms.on("click",this.clickHandler).on("focus blur",this.focusHandler)},s.prototype.addDocumentLevelEvents=function(){this.clickHandler=this.getClickHandler(),this.focusHandler=this.getFocusHandler({level:"document"}),t(document).on("click",this.selector,this.clickHandler).on("focus blur",this.selector,this.focusHandler)},s.prototype.getClickHandler=function(){return function(e){this.markSelected(t(e.target))}.bind(this)},s.prototype.getFocusHandler=function(e){var s="document"===e.level?"focusin":"focus";return function(e){var i=e.type===s?"focused":"blurred";this.markFocused(t(e.target),i)}.bind(this)},s.prototype.destroy=function(){"undefined"!=typeof this.selector?t(document).off("click",this.selector,this.clickHandler).off("focus blur",this.selector,this.focusHandler):this.$elms.off("click",this.clickHandler).off("focus blur",this.focusHandler)},e.GOVUK.SelectionButtons=s}).call(void 0);
|
||
|
|
"use strict";!function(){function e(e,t,n){e.addEventListener?e.addEventListener(t,function(e){n(e,e.target)},!1):e.attachEvent&&e.attachEvent("on"+t,function(e){n(e,e.srcElement)})}function t(t,n){e(t,"keypress",function(e,t){"SUMMARY"===t.nodeName&&32===e.keyCode&&(e.preventDefault?e.preventDefault():e.returnValue=!1)}),e(t,"keyup",function(e,t){(13===e.keyCode||32===e.keyCode)&&n(e,t)}),e(t,"mouseup",function(e,t){n(e,t)})}function n(e,t){do if(!e||e.nodeName.toLowerCase()===t)break;while(e=e.parentNode);return e}function a(e){function a(e){var t="true"===e.__details.__summary.getAttribute("aria-expanded"),n="true"===e.__details.__content.getAttribute("aria-hidden");if(e.__details.__summary.setAttribute("aria-expanded",t?"false":"true"),e.__details.__content.setAttribute("aria-hidden",n?"false":"true"),!r){e.__details.__content.style.display=t?"none":"";var a=null!==e.__details.getAttribute("open");a?e.__details.removeAttribute("open"):e.__details.setAttribute("open","open")}return e.__twisty&&(e.__twisty.firstChild.nodeValue=t?"►":"▼",e.__twisty.setAttribute("class",t?"arrow arrow-closed":"arrow arrow-open")),!0}if(!i&&(i=!0,0!==(e=document.getElementsByTagName("details")).length)){var o=e.length,s=0;for(s;o>s;s++){var u=e[s];u.__summary=u.getElementsByTagName("summary").item(0),u.__content=u.getElementsByTagName("div").item(0),u.__content.id||(u.__content.id="details-content-"+s),u.setAttribute("role","group"),u.__summary.setAttribute("role","button"),u.__summary.setAttribute("aria-controls",u.__content.id),r||(u.__summary.tabIndex=0);var _=null!==u.getAttribute("open");if(_===!0?(u.__summary.setAttribute("aria-expanded","true"),u.__content.setAttribute("aria-hidden","false")):(u.__summary.setAttribute("aria-expanded","false"),u.__content.setAttribute("aria-hidden","true"),r||(u.__content.style.display="none")),u.__summary.__details=u,!r){var d=document.createElement("i");_===!0?(d.className="arrow arrow-open",d.appendChild(document.createTextNode("▼"))):(d.className="arrow arrow-closed",d.appendChild(document.createTextNode("►"))),u.__summary.__twisty=u.__summary.insertBefore(d,u.__summary.firstChild),u.__summary.__twisty.setAttribute("aria-hidden","true")}}t(document,function(e,t){return(t=n(t,"summary"))?a(t):!0})}}var r="boolean"==typeof document.createElement("details").open,i=!1;e(document,"DOMContentLoaded",a),e(window,"load",a)}();
|
||
|
|
"use strict";!function(e){document.queryCommandSupported("copy")&&(e.ApiKey=function(){var e={keyVisible:function(e,n){return'\n <span class="api-key-key">'+e+"</span>\n <input type='button' class='api-key-button-copy' value='Copy "+n+" to clipboard' />\n "},keyCopied:function(e){return"\n <span class=\"api-key-key\">Copied to clipboard</span>\n <input type='button' class='api-key-button-show' value='Show "+e+"' />\n "}};this.copyKey=function(e,n){var t=window.getSelection?window.getSelection():document.selection,o=document.createRange();t.removeAllRanges(),o.selectNodeContents(e),t.addRange(o),document.execCommand("copy"),t.removeAllRanges(),n()},this.start=function(n){var t=this,o=$(n),i=o.data("key"),a=o.data("thing");o.html(e.keyVisible(i,a)).attr("aria-live","polite").on("click",".api-key-button-copy",function(){return t.copyKey($(".api-key-key",n)[0],function(){return o.html(e.keyCopied(a))})}).on("click",".api-key-button-show",function(){return o.html(e.keyVisible(i,a))})}})}(window.GOVUK.Modules);
|
||
|
|
"use strict";!function(t){t.Autofocus=function(){this.start=function(t){$("input, textarea, select",t).eq(0).trigger("focus")}}}(window.GOVUK.Modules);
|
||
|
|
"use strict";!function(t){if("oninput"in document.createElement("input")){var e=/\(\(([^\)\((\?)]+)(\?\?)?([^\)\(]*)\)\)/g;t.HighlightTags=function(){var t=this;this.start=function(t){this.$textbox=$(t).wrap("\n <div class='textbox-highlight-wrapper' />\n ").after(this.$background=$('\n <div class="textbox-highlight-background" aria-hidden="true" />\n ')).on("input",this.update),this.initialHeight=this.$textbox.height(),this.$background.css({width:this.$textbox.outerWidth(),"border-width":this.$textbox.css("border-width")}),this.$textbox.trigger("input")},this.resize=function(){return t.$textbox.height(Math.max(t.initialHeight,t.$background.outerHeight()))},this.escapedMessage=function(){return $("<div/>").text(t.$textbox.val()).html()},this.replacePlaceholders=function(){return t.$background.html(t.escapedMessage().replace(e,function(t,e,i,n){return n&&i?"<span class='placeholder-conditional'>(("+e+"??</span>"+n+"))":"<span class='placeholder'>(("+e+n+"))</span>"}))},this.update=function(){return t.replacePlaceholders()&&t.resize()}}}}(window.GOVUK.Modules);
|
||
|
|
"use strict";!function(t){t.FileUpload=function(){var t=this;this.submit=function(){return t.$form.trigger("submit")},this.showCancelButton=function(){return $(".file-upload-button",t.$form).replaceWith("\n <a href=\"\" class='file-upload-button-cancel'>Cancel upload</a>\n ")},this.start=function(t){var n=this;this.$form=$(t),$(window).on("pageshow",function(){return n.$form[0].reset()}),this.$form.on("change",".file-upload-field",function(){return n.submit()&&n.showCancelButton()})}}}(window.GOVUK.Modules);
|
||
|
|
"use strict";!function(n){var t,e={},r=new diffDOM,u=function(n){return function(t){return function(){var e=n.get(0),u=$(t[n.data("key")]).get(0),i=r.diff(e,u);r.apply(e,i)}}},i=function(n){return e[n]=e[n]||[]},o=function(n,t){for(;n.length;)n.shift()(t)},a=function(n){return n.length=0},f=function c(n,e,r,u){var i=arguments;1===r.push(n)&&$.ajax(e).done(function(n){return o(r,n)}).fail(function(){return a(r)}),t=setTimeout(function(){return c.apply(void 0,i)},u)};n.UpdateContent=function(){this.start=function(n){return f(u($(n)),$(n).data("resource"),i($(n).data("resource")),1e3*($(n).data("interval-seconds")||1.5))}}}(window.GOVUK.Modules);
|
||
|
|
"use strict";!function(t){t.ExpandCollapse=function(){var t=this;this.start=function(t){this.$component=$(t),this.$toggle=this.$component.find(".toggle").on("click",this.change).on("keydown",this.filterKeyPresses([32,13],this.change)),this.getNativeHeight()<this.$component.data("max-height")&&this.change()},this.filterKeyPresses=function(t,e){return function(n){t.indexOf(n.keyCode)||(n.preventDefault(),e())}},this.getNativeHeight=function(){var t=this.$component.clone().css({position:"absolute",left:"9999px",width:this.$component.width(),"font-size":this.$component.css("font-size"),"line-height":this.$component.css("line-height")}).addClass("expanded");$("body").append(t);var e=t.height();return t.remove(),e},this.change=function(){return t.toggleCollapsed()&&t.$toggle.remove()},this.toggleCollapsed=function(){return t.$component.addClass("expanded")}}}(window.GOVUK.Modules);
|
||
|
|
"use strict";$(function(){return GOVUK.modules.start()}),$(function(){return new GOVUK.SelectionButtons(".block-label input, .sms-message-option input")}),$(function(){return $(".error-message").eq(0).parent("label").next("input").trigger("focus")});
|