From 00e2249f0b355046ad2413a382295f09f487971e Mon Sep 17 00:00:00 2001 From: carson Date: Wed, 13 May 2020 06:45:43 -0700 Subject: [PATCH] history testing and rework --- TODO.md | 6 +-- dist/kutty.js | 69 ++++++++++++++++++-------- dist/kutty.min.js | 2 +- dist/kutty.min.js.gz | Bin 5271 -> 5342 bytes src/kutty.js | 107 +++++++++++++++++++++++----------------- test/browser-only.html | 81 ++++++++++++++++++++++++++++++- test/index.html | 2 + test/kt-push-url.js | 108 ++++++++++++++++++++--------------------- www/events.md | 2 +- www/reference.md | 2 +- 10 files changed, 252 insertions(+), 127 deletions(-) diff --git a/TODO.md b/TODO.md index 6fb8fbad..23fa16f5 100644 --- a/TODO.md +++ b/TODO.md @@ -7,19 +7,19 @@ ## TODOS * Testing + * history + * polling + * kt-boost * interval parsing * table elements in responses * scrolling/'revealed' event * checkbox inputs - * history - * kt-boost * kt-swap-oob (verify, chrome coverage tool bad?) * X-KT-Trigger response header * SSE stuff * kt-trigger delay * class operation parsing * class toggling - * polling * transition model for content swaps diff --git a/dist/kutty.js b/dist/kutty.js index 1db52df1..0cfa821c 100644 --- a/dist/kutty.js +++ b/dist/kutty.js @@ -8,6 +8,21 @@ var kutty = kutty || (function () { // Utilities //==================================================================== + function makeBrowserHistorySupport() { + return { + getFullPath: function () { + return location.pathname + location.search; + }, + getLocalStorage: function () { + return localStorage; + }, + getHistory: function () { + return history; + } + }; + } + var browserHistorySupport = makeBrowserHistorySupport(); + function parseInterval(str) { if (str === "null" || str === "false" || str === "") { return null; @@ -36,6 +51,10 @@ var kutty = kutty || (function () { function getDocument() { return document; } + + function getBody() { + return getDocument().body; + } function getClosestMatch(elt, condition) { if (condition(elt)) { @@ -147,7 +166,7 @@ var kutty = kutty || (function () { } function bodyContains(elt) { - return getDocument().body.contains(elt); + return getBody().contains(elt); } function concat(arr1, arr2) { @@ -179,7 +198,7 @@ var kutty = kutty || (function () { } else { var data = getInternalData(elt); if (data.boosted) { - return getDocument().body; + return getBody(); } else { return elt; } @@ -208,7 +227,7 @@ var kutty = kutty || (function () { settleTasks = settleTasks.concat(swapOuterHTML(target, fragment)); } else { child.parentNode.removeChild(child); - triggerEvent(getDocument().body, "oobErrorNoTarget.kutty", {content: child}) + triggerEvent(getBody(), "oobErrorNoTarget.kutty", {content: child}) } } }); @@ -665,14 +684,14 @@ var kutty = kutty || (function () { var target, event, listener; if (isFunction(arg1)) { ready(function(){ - target = getDocument().body; + target = getBody(); event = "all.kutty"; listener = arg1; target.addEventListener(event, listener); }) } else if (isFunction(arg2)) { ready(function () { - target = getDocument().body; + target = getBody(); event = arg1; listener = arg2; target.addEventListener(event, listener); @@ -690,7 +709,7 @@ var kutty = kutty || (function () { //==================================================================== function getHistoryElement() { var historyElt = getDocument().querySelector('[kt-history-elt]'); - return historyElt || getDocument().body; + return historyElt || getBody(); } function purgeOldestPaths(paths, historyTimestamps) { @@ -702,32 +721,32 @@ var kutty = kutty || (function () { slot++; if (slot > 20) { delete historyTimestamps[path]; - localStorage.removeItem(path); + browserHistorySupport.getLocalStorage().removeItem(path); } }); } function bumpHistoryAccessDate(pathAndSearch) { - var historyTimestamps = JSON.parse(localStorage.getItem("kt-history-timestamps")) || {}; + var historyTimestamps = JSON.parse(browserHistorySupport.getLocalStorage().getItem("kt-history-timestamps")) || {}; historyTimestamps[pathAndSearch] = Date.now(); var paths = Object.keys(historyTimestamps); if (paths.length > 20) { purgeOldestPaths(paths, historyTimestamps); } - localStorage.setItem("kt-history-timestamps", JSON.stringify(historyTimestamps)); + browserHistorySupport.getLocalStorage().setItem("kt-history-timestamps", JSON.stringify(historyTimestamps)); } function saveHistory() { var elt = getHistoryElement(); - var pathAndSearch = location.pathname+location.search; - triggerEvent(getDocument().body, "historyUpdate.kutty", {path:pathAndSearch, historyElt:elt}); - history.replaceState({}, getDocument().title, window.location.href); - localStorage.setItem('kt-history:' + pathAndSearch, elt.innerHTML); + var pathAndSearch = browserHistorySupport.getFullPath(); + triggerEvent(getBody(), "historyUpdate.kutty", {path:pathAndSearch, historyElt:elt}); + browserHistorySupport.getHistory().replaceState({}, getDocument().title); + browserHistorySupport.getLocalStorage().setItem('kt-history:' + pathAndSearch, elt.innerHTML); bumpHistoryAccessDate(pathAndSearch); } function pushUrlIntoHistory(url) { - history.pushState({}, "", url ); + browserHistorySupport.getHistory().pushState({}, "", url ); } function settleImmediately(settleTasks) { @@ -739,25 +758,25 @@ var kutty = kutty || (function () { function loadHistoryFromServer(pathAndSearch) { var request = new XMLHttpRequest(); var details = {path: pathAndSearch, xhr:request}; - triggerEvent(getDocument().body, "historyCacheMiss.kutty", details); + triggerEvent(getBody(), "historyCacheMiss.kutty", details); request.open('GET', pathAndSearch, true); request.onload = function () { if (this.status >= 200 && this.status < 400) { - triggerEvent(getDocument().body, "historyCacheMissLoad.kutty", details); + triggerEvent(getBody(), "historyCacheMissLoad.kutty", details); var fragment = makeFragment(this.response); fragment = fragment.querySelector('[kt-history-elt]') || fragment; settleImmediately(swapInnerHTML(getHistoryElement(), fragment)); } else { - triggerEvent(getDocument().body, "historyCacheMissLoadError.kutty", details); + triggerEvent(getBody(), "historyCacheMissLoadError.kutty", details); } }; request.send(); } function restoreHistory() { - var pathAndSearch = location.pathname+location.search; - triggerEvent(getDocument().body, "historyRestore.kutty", {path:pathAndSearch}); - var content = localStorage.getItem('kt-history:' + pathAndSearch); + var pathAndSearch = browserHistorySupport.getFullPath(); + triggerEvent(getBody(), "historyRestore.kutty", {path:pathAndSearch}); + var content = browserHistorySupport.getLocalStorage().getItem('kt-history:' + pathAndSearch); if (content) { bumpHistoryAccessDate(pathAndSearch); settleImmediately(swapInnerHTML(getHistoryElement(), makeFragment(content))); @@ -1138,7 +1157,7 @@ var kutty = kutty || (function () { // initialize the document ready(function () { - var body = getDocument().body; + var body = getBody(); processNode(body); triggerEvent(body, 'load.kutty', {}); window.onpopstate = function () { @@ -1164,6 +1183,14 @@ var kutty = kutty || (function () { } } + function setBrowserHistorySupport(mock) { + browserHistorySupport = mock; + } + + function restoreBrowserHistorySupport() { + browserHistorySupport = browserHistorySupport(); + } + // Public API return { processElement: processNode, diff --git a/dist/kutty.min.js b/dist/kutty.min.js index 93a5a15a..2ac4fa22 100644 --- a/dist/kutty.min.js +++ b/dist/kutty.min.js @@ -1 +1 @@ -var kutty=kutty||function(){"use strict";var e=["get","post","put","delete","patch"];function g(e){if(e==="null"||e==="false"||e===""){return null}else if(e.lastIndexOf("ms")===e.length-2){return parseFloat(e.substr(0,e.length-2))}else if(e.lastIndexOf("s")===e.length-1){return parseFloat(e.substr(0,e.length-1))*1e3}else{return parseFloat(e)}}function h(e,t){return e.getAttribute&&e.getAttribute(t)}function u(e,t){return h(e,t)||h(e,"data-"+t)}function n(e){return e.parentElement}function o(){return document}function s(e,t){if(t(e)){return e}else if(n(e)){return s(n(e),t)}else{return null}}function p(e,t){var r=null;s(e,function(e){return r=h(e,t)});return r}function l(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return e!=null&&r!=null&&r.call(e,t)}function a(e,t){do{if(e==null||l(e,t))return e}while(e=e&&n(e))}function r(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}return i}function f(e){var t=r(e);switch(t){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return i(""+e+"
",1);case"col":return i(""+e+"
",2);case"tr":return i(""+e+"
",2);case"td":case"th":return i(""+e+"
",3);default:return i(e,0)}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function c(e){return t(e,"Function")}function v(e){return t(e,"Object")}function m(e){var t="kutty-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function k(e,t){if(e){for(var r=0;r=0}function y(e){return o().body.contains(e)}function b(e,t){return e.concat(t)}function w(e){return e.split(/\s+/)}function S(e){var t=o().styleSheets[0];t.insertRule(e,t.cssRules.length)}function E(e){var t=s(e,function(e){return h(e,"kt-target")!==null});if(t){var r=h(t,"kt-target");if(r==="this"){return t}else{return o().querySelector(r)}}else{var n=m(e);if(n.boosted){return o().body}else{return e}}}function L(t,r){k(t.attributes,function(e){if(!r.hasAttribute(e.name)){t.removeAttribute(e.name)}});k(r.attributes,function(e){t.setAttribute(e.name,e.value)})}function O(e){var n=[];k(e.children,function(e){if(u(e,"kt-swap-oob")==="true"){var t=o().getElementById(e.id);if(t){var r=o().createDocumentFragment();r.appendChild(e);n=n.concat(x(t,r))}else{e.parentNode.removeChild(e);te(o().body,"oobErrorNoTarget.kutty",{content:e})}}});return n}function T(n,e){var i=[];k(e.querySelectorAll("[id]"),function(e){var t=n.querySelector(e.tagName+"[id="+e.id+"]");if(t){var r=e.cloneNode();L(e,t);i.push(function(){L(e,r)})}});return i}function C(e,t,r){var n=T(e,r);while(r.childNodes.length>0){var i=r.firstChild;e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE){te(i,"load.kutty",{});Z(i)}}return n}function x(e,t){if(e.tagName==="BODY"){return R(e,t)}else{var r=C(n(e),e,t);n(e).removeChild(e);return r}}function N(e,t){return C(e,e.firstChild,t)}function q(e,t){return C(n(e),e,t)}function A(e,t){return C(e,null,t)}function M(e,t){return C(n(e),e.nextSibling,t)}function R(e,t){var r=e.firstChild;var n=C(e,r,t);if(r){while(r.nextSibling){e.removeChild(r.nextSibling)}e.removeChild(r)}return n}function D(e,t){var r=p(e,"kt-select");if(r){var n=o().createDocumentFragment();k(t.querySelectorAll(r),function(e){n.appendChild(e)});t=n}return t}function I(e,t,r,n){var i=f(n);if(i){var a=O(i);i=D(r,i);switch(e){case"outerHTML":return b(a,x(t,i));case"afterbegin":return b(a,N(t,i));case"beforebegin":return b(a,q(t,i));case"beforeend":return b(a,A(t,i));case"afterend":return b(a,M(t,i));default:return b(a,R(t,i))}}}function F(e,t){if(t){if(t.indexOf("{")===0){var r=JSON.parse(t);for(var n in r){if(r.hasOwnProperty(n)){var i=r[n];if(!v(i)){i={value:i}}te(e,n,i)}}}else{te(e,t,[])}}}function H(e){var t={trigger:"click"};var r=u(e,"kt-trigger");if(r){var n=w(r);if(n.length>0){var i=n[0];if(i==="every"){t.pollInterval=g(n[1])}else if(i.indexOf("sse:")===0){t.sseEvent=i.substr(4)}else{t["trigger"]=i;for(var a=1;a1){var r=t[0];var n=t[1].trim();var i;var a;if(n.indexOf(":")>0){var o=n.split(":");i=o[0];a=g(o[1])}else{i=n;a=100}return{operation:r,cssClass:i,delay:a}}else{return null}}function X(i,e,t){k(e.split("&"),function(e){var n=0;k(e.split(","),function(e){var t=e.trim();var r=P(t);if(r){if(r.operation==="toggle"){setTimeout(function(){setInterval(function(){i.classList[r.operation].call(i.classList,r.cssClass)},r.delay)},n);n=n+r.delay}else{n=n+r.delay;setTimeout(function(){i.classList[r.operation].call(i.classList,r.cssClass)},n)}}})})}function U(e,t,r,n){var i=m(e);i.timeout=setTimeout(function(){if(y(e)){Oe(e,t,r);U(e,t,u(e,"kt-"+t),n)}},n)}function j(e){return location.hostname===e.hostname&&h(e,"href")&&!h(e,"href").startsWith("#")}function B(e,t,r){if(e.tagName==="A"&&j(e)||e.tagName==="FORM"){t.boosted=true;var n,i;if(e.tagName==="A"){n="get";i=h(e,"href")}else{var a=h(e,"method");n=a?a.toLowerCase():"get";i=h(e,"action")}K(e,n,i,t,r,true)}}function J(e){return e.tagName==="FORM"||l(e,'input[type="submit"], button')&&a(e,"form")!==null||e.tagName==="A"&&e.href&&e.href.indexOf("#")!==0}function K(i,a,o,e,u,s){var t=function(e){if(s||J(i))e.preventDefault();var t=m(e);var r=m(i);if(!t.handled){t.handled=true;if(u.once){if(r.triggeredOnce){return}else{r.triggeredOnce=true}}if(u.changed){if(r.lastValue===i.value){return}else{r.lastValue=i.value}}if(r.delayed){clearTimeout(r.delayed)}var n=function(){Oe(i,a,o,e.target)};if(u.delay){r.delayed=setTimeout(n,u.delay)}else{n()}}};e.trigger=u.trigger;e.eventListener=t;i.addEventListener(u.trigger,t)}function V(){if(!window["kuttyScrollHandler"]){var e=function(){k(o().querySelectorAll("[kt-trigger='revealed']"),function(e){z(e)})};window["kuttyScrollHandler"]=e;window.addEventListener("scroll",e)}}function z(e){var t=m(e);if(!t.revealed&&d(e)){t.revealed=true;Oe(e,t.verb,t.path)}}function G(e){if(!y(e)){e.sseSource.close();return true}}function _(t,e){var r={config:{withCredentials:true}};te(t,"initSSE.kutty",r);var n=new EventSource(e,r.config);n.onerror=function(e){te(t,"sseError.kutty",{error:e,source:n});G(t)};m(t).sseSource=n}function W(e,t,r,n){var i=s(e,function(e){return e.sseSource});if(i){var a=function(){if(!G(i)){if(y(e)){Oe(e,t,r)}else{i.sseSource.removeEventListener(n,a)}}};i.sseSource.addEventListener(n,a)}else{te(e,"noSSESourceError.kutty")}}function Y(e,t,r,n,i){var a=function(){if(!n.loaded){n.loaded=true;Oe(e,t,r)}};if(i){setTimeout(a,i)}else{a()}}function Q(r,n,i){var a=false;k(e,function(e){var t=u(r,"kt-"+e);if(t){a=true;n.path=t;n.verb=e;if(i.sseEvent){W(r,e,t,i.sseEvent)}else if(i.trigger==="revealed"){V();z(r)}else if(i.trigger==="load"){Y(r,e,t,n,i.delay)}else if(i.pollInterval){n.polling=true;U(r,e,t,i.pollInterval)}else{K(r,e,t,n,i)}}});return a}function Z(e){var t=m(e);if(!t.processed){t.processed=true;var r=H(e);var n=Q(e,t,r);if(!n&&p(e,"kt-boost")==="true"){B(e,t,r)}var i=u(e,"kt-sse-source");if(i){_(e,i)}var a=u(e,"kt-classes");if(a){X(e,a)}}if(e.children){k(e.children,function(e){Z(e)})}}function $(e,t,r){var n=p(e,"kt-error-url");if(n){var i=new XMLHttpRequest;i.open("POST",n);i.setRequestHeader("Content-Type","application/json;charset=UTF-8");i.send(JSON.stringify({elt:e.id,event:t,detail:r}))}}function ee(e,t){var r;if(window.CustomEvent&&typeof window.CustomEvent==="function"){r=new CustomEvent(e,{bubbles:true,cancelable:true,detail:t})}else{r=o().createEvent("CustomEvent");r.initCustomEvent(e,true,true,t)}return r}function te(e,t,r){r["elt"]=e;var n=ee(t,r);if(kutty.logger){kutty.logger(e,t,r);if(t.indexOf("Error")>0){$(e,t,r)}}var i=e.dispatchEvent(n);var a=e.dispatchEvent(ee("all.kutty",{originalDetail:r,originalEvent:n}));return i&&a}function re(e,t,r){var n,i,a;if(c(e)){Te(function(){n=o().body;i="all.kutty";a=e;n.addEventListener(i,a)})}else if(c(t)){Te(function(){n=o().body;i=e;a=t;n.addEventListener(i,a)})}else{n=e;i=t;a=r;n.addEventListener(i,a)}}function ne(){var e=o().querySelector("[kt-history-elt]");return e||o().body}function ie(e,r){e=e.sort(function(e,t){return r[t]-r[e]});var t=0;k(e,function(e){t++;if(t>20){delete r[e];localStorage.removeItem(e)}})}function ae(e){var t=JSON.parse(localStorage.getItem("kt-history-timestamps"))||{};t[e]=Date.now();var r=Object.keys(t);if(r.length>20){ie(r,t)}localStorage.setItem("kt-history-timestamps",JSON.stringify(t))}function oe(){var e=ne();var t=location.pathname+location.search;te(o().body,"historyUpdate.kutty",{path:t,historyElt:e});history.replaceState({},o().title,window.location.href);localStorage.setItem("kt-history:"+t,e.innerHTML);ae(t)}function ue(e){history.pushState({},"",e)}function se(e){k(e,function(e){e.call()})}function le(e){var t=new XMLHttpRequest;var r={path:e,xhr:t};te(o().body,"historyCacheMiss.kutty",r);t.open("GET",e,true);t.onload=function(){if(this.status>=200&&this.status<400){te(o().body,"historyCacheMissLoad.kutty",r);var e=f(this.response);e=e.querySelector("[kt-history-elt]")||e;se(R(ne(),e))}else{te(o().body,"historyCacheMissLoadError.kutty",r)}};t.send()}function fe(){var e=location.pathname+location.search;te(o().body,"historyRestore.kutty",{path:e});var t=localStorage.getItem("kt-history:"+e);if(t){ae(e);se(R(ne(),f(t)))}else{le(e)}}function ce(e){return p(e,"kt-push-url")==="true"||e.tagName==="A"&&m(e).boosted}function ve(e){ye(e,"add")}function de(e){ye(e,"remove")}function ye(e,t){var r=p(e,"kt-indicator");if(r){var n=o().querySelectorAll(r)}else{n=[e]}k(n,function(e){e.classList[t].call(e.classList,"kutty-request")})}function ge(e,t){for(var r=0;r0){r["swapStyle"]=n[0];for(var i=1;i=200&&this.status<400){if(this.status!==204){if(!te(o,"beforeSwap.kutty",y))return;var i=this.response;if(n){oe()}var a=Le(o);u.classList.add("kutty-swapping");var e=function(){try{var e=I(a.swapStyle,u,o,i);u.classList.remove("kutty-swapping");u.classList.add("kutty-settling");te(o,"afterSwap.kutty",y);var t=function(){k(e,function(e){e.call()});u.classList.remove("kutty-settling");if(n){ue(r||v);oe()}te(o,"afterSettle.kutty",y)};if(a.settleDelay>0){setTimeout(t,a.settleDelay)}else{t()}}catch(e){te(o,"swapError.kutty",y);throw e}};if(a.swapDelay>0){setTimeout(e,g(a.swapDelay))}else{e()}}}else{te(o,"responseError.kutty",y)}}catch(e){y["exception"]=e;te(o,"onLoadError.kutty",y);throw e}finally{de(o);s();te(o,"afterOnLoad.kutty",y)}};f.onerror=function(){de(o);te(o,"sendError.kutty",y);s()};if(!te(o,"beforeRequest.kutty",y))return s();ve(o);f.send(e==="get"?null:be(c))}function Te(e){if(o().readyState!=="loading"){e()}else{o().addEventListener("DOMContentLoaded",e)}}S(".kutty-indicator{opacity:0;transition: opacity 200ms ease-in;}");S(".kutty-request .kutty-indicator{opacity:1}");S(".kutty-request.kutty-indicator{opacity:1}");Te(function(){var e=o().body;Z(e);te(e,"load.kutty",{});window.onpopstate=function(){fe()}});function Ce(e){return eval(e)}function xe(t){kutty.on("load.kutty",function(e){t(e.detail.elt)})}function Ne(){kutty.logger=function(e,t,r){if(console){console.log(t,e,r)}}}return{processElement:Z,on:re,onLoad:xe,logAll:Ne,version:"0.0.1",_:Ce}}(); \ No newline at end of file +var kutty=kutty||function(){"use strict";var t=["get","post","put","delete","patch"];function e(){return{getFullPath:function(){return location.pathname+location.search},getLocalStorage:function(){return localStorage},getHistory:function(){return history}}}var n=e();function y(t){if(t==="null"||t==="false"||t===""){return null}else if(t.lastIndexOf("ms")===t.length-2){return parseFloat(t.substr(0,t.length-2))}else if(t.lastIndexOf("s")===t.length-1){return parseFloat(t.substr(0,t.length-1))*1e3}else{return parseFloat(t)}}function h(t,e){return t.getAttribute&&t.getAttribute(e)}function u(t,e){return h(t,e)||h(t,"data-"+e)}function i(t){return t.parentElement}function a(){return document}function o(){return a().body}function s(t,e){if(e(t)){return t}else if(i(t)){return s(i(t),e)}else{return null}}function p(t,e){var r=null;s(t,function(t){return r=h(t,e)});return r}function l(t,e){var r=t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector;return t!=null&&r!=null&&r.call(t,e)}function f(t,e){do{if(t==null||l(t,e))return t}while(t=t&&i(t))}function r(t){var e=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=e.exec(t);if(r){return r[1].toLowerCase()}else{return""}}function c(t,e){var r=new DOMParser;var n=r.parseFromString(t,"text/html");var i=n.body;while(e>0){e--;i=i.firstChild}return i}function v(t){var e=r(t);switch(e){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return c(""+t+"
",1);case"col":return c(""+t+"
",2);case"tr":return c(""+t+"
",2);case"td":case"th":return c(""+t+"
",3);default:return c(t,0)}}function d(t,e){return Object.prototype.toString.call(t)==="[object "+e+"]"}function g(t){return d(t,"Function")}function m(t){return d(t,"Object")}function k(t){var e="kutty-internal-data";var r=t[e];if(!r){r=t[e]={}}return r}function b(t,e){if(t){for(var r=0;r=0}function w(t){return o().contains(t)}function E(t,e){return t.concat(e)}function L(t){return t.split(/\s+/)}function O(t){var e=a().styleSheets[0];e.insertRule(t,e.cssRules.length)}function T(t){var e=s(t,function(t){return h(t,"kt-target")!==null});if(e){var r=h(e,"kt-target");if(r==="this"){return e}else{return a().querySelector(r)}}else{var n=k(t);if(n.boosted){return o()}else{return t}}}function C(e,r){b(e.attributes,function(t){if(!r.hasAttribute(t.name)){e.removeAttribute(t.name)}});b(r.attributes,function(t){e.setAttribute(t.name,t.value)})}function x(t){var n=[];b(t.children,function(t){if(u(t,"kt-swap-oob")==="true"){var e=a().getElementById(t.id);if(e){var r=a().createDocumentFragment();r.appendChild(t);n=n.concat(A(e,r))}else{t.parentNode.removeChild(t);it(o(),"oobErrorNoTarget.kutty",{content:t})}}});return n}function N(n,t){var i=[];b(t.querySelectorAll("[id]"),function(t){var e=n.querySelector(t.tagName+"[id="+t.id+"]");if(e){var r=t.cloneNode();C(t,e);i.push(function(){C(t,r)})}});return i}function q(t,e,r){var n=N(t,r);while(r.childNodes.length>0){var i=r.firstChild;t.insertBefore(i,e);if(i.nodeType!==Node.TEXT_NODE){it(i,"load.kutty",{});et(i)}}return n}function A(t,e){if(t.tagName==="BODY"){return H(t,e)}else{var r=q(i(t),t,e);i(t).removeChild(t);return r}}function M(t,e){return q(t,t.firstChild,e)}function R(t,e){return q(i(t),t,e)}function D(t,e){return q(t,null,e)}function F(t,e){return q(i(t),t.nextSibling,e)}function H(t,e){var r=t.firstChild;var n=q(t,r,e);if(r){while(r.nextSibling){t.removeChild(r.nextSibling)}t.removeChild(r)}return n}function I(t,e){var r=p(t,"kt-select");if(r){var n=a().createDocumentFragment();b(e.querySelectorAll(r),function(t){n.appendChild(t)});e=n}return e}function P(t,e,r,n){var i=v(n);if(i){var a=x(i);i=I(r,i);switch(t){case"outerHTML":return E(a,A(e,i));case"afterbegin":return E(a,M(e,i));case"beforebegin":return E(a,R(e,i));case"beforeend":return E(a,D(e,i));case"afterend":return E(a,F(e,i));default:return E(a,H(e,i))}}}function X(t,e){if(e){if(e.indexOf("{")===0){var r=JSON.parse(e);for(var n in r){if(r.hasOwnProperty(n)){var i=r[n];if(!m(i)){i={value:i}}it(t,n,i)}}}else{it(t,e,[])}}}function U(t){var e={trigger:"click"};var r=u(t,"kt-trigger");if(r){var n=L(r);if(n.length>0){var i=n[0];if(i==="every"){e.pollInterval=y(n[1])}else if(i.indexOf("sse:")===0){e.sseEvent=i.substr(4)}else{e["trigger"]=i;for(var a=1;a1){var r=e[0];var n=e[1].trim();var i;var a;if(n.indexOf(":")>0){var o=n.split(":");i=o[0];a=y(o[1])}else{i=n;a=100}return{operation:r,cssClass:i,delay:a}}else{return null}}function B(i,t,e){b(t.split("&"),function(t){var n=0;b(t.split(","),function(t){var e=t.trim();var r=j(e);if(r){if(r.operation==="toggle"){setTimeout(function(){setInterval(function(){i.classList[r.operation].call(i.classList,r.cssClass)},r.delay)},n);n=n+r.delay}else{n=n+r.delay;setTimeout(function(){i.classList[r.operation].call(i.classList,r.cssClass)},n)}}})})}function J(t,e,r,n){var i=k(t);i.timeout=setTimeout(function(){if(w(t)){xt(t,e,r);J(t,e,u(t,"kt-"+e),n)}},n)}function K(t){return location.hostname===t.hostname&&h(t,"href")&&!h(t,"href").startsWith("#")}function V(t,e,r){if(t.tagName==="A"&&K(t)||t.tagName==="FORM"){e.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=h(t,"href")}else{var a=h(t,"method");n=a?a.toLowerCase():"get";i=h(t,"action")}G(t,n,i,e,r,true)}}function z(t){return t.tagName==="FORM"||l(t,'input[type="submit"], button')&&f(t,"form")!==null||t.tagName==="A"&&t.href&&t.href.indexOf("#")!==0}function G(i,a,o,t,u,s){var e=function(t){if(s||z(i))t.preventDefault();var e=k(t);var r=k(i);if(!e.handled){e.handled=true;if(u.once){if(r.triggeredOnce){return}else{r.triggeredOnce=true}}if(u.changed){if(r.lastValue===i.value){return}else{r.lastValue=i.value}}if(r.delayed){clearTimeout(r.delayed)}var n=function(){xt(i,a,o,t.target)};if(u.delay){r.delayed=setTimeout(n,u.delay)}else{n()}}};t.trigger=u.trigger;t.eventListener=e;i.addEventListener(u.trigger,e)}function _(){if(!window["kuttyScrollHandler"]){var t=function(){b(a().querySelectorAll("[kt-trigger='revealed']"),function(t){W(t)})};window["kuttyScrollHandler"]=t;window.addEventListener("scroll",t)}}function W(t){var e=k(t);if(!e.revealed&&S(t)){e.revealed=true;xt(t,e.verb,e.path)}}function Y(t){if(!w(t)){t.sseSource.close();return true}}function Q(e,t){var r={config:{withCredentials:true}};it(e,"initSSE.kutty",r);var n=new EventSource(t,r.config);n.onerror=function(t){it(e,"sseError.kutty",{error:t,source:n});Y(e)};k(e).sseSource=n}function Z(t,e,r,n){var i=s(t,function(t){return t.sseSource});if(i){var a=function(){if(!Y(i)){if(w(t)){xt(t,e,r)}else{i.sseSource.removeEventListener(n,a)}}};i.sseSource.addEventListener(n,a)}else{it(t,"noSSESourceError.kutty")}}function $(t,e,r,n,i){var a=function(){if(!n.loaded){n.loaded=true;xt(t,e,r)}};if(i){setTimeout(a,i)}else{a()}}function tt(r,n,i){var a=false;b(t,function(t){var e=u(r,"kt-"+t);if(e){a=true;n.path=e;n.verb=t;if(i.sseEvent){Z(r,t,e,i.sseEvent)}else if(i.trigger==="revealed"){_();W(r)}else if(i.trigger==="load"){$(r,t,e,n,i.delay)}else if(i.pollInterval){n.polling=true;J(r,t,e,i.pollInterval)}else{G(r,t,e,n,i)}}});return a}function et(t){var e=k(t);if(!e.processed){e.processed=true;var r=U(t);var n=tt(t,e,r);if(!n&&p(t,"kt-boost")==="true"){V(t,e,r)}var i=u(t,"kt-sse-source");if(i){Q(t,i)}var a=u(t,"kt-classes");if(a){B(t,a)}}if(t.children){b(t.children,function(t){et(t)})}}function rt(t,e,r){var n=p(t,"kt-error-url");if(n){var i=new XMLHttpRequest;i.open("POST",n);i.setRequestHeader("Content-Type","application/json;charset=UTF-8");i.send(JSON.stringify({elt:t.id,event:e,detail:r}))}}function nt(t,e){var r;if(window.CustomEvent&&typeof window.CustomEvent==="function"){r=new CustomEvent(t,{bubbles:true,cancelable:true,detail:e})}else{r=a().createEvent("CustomEvent");r.initCustomEvent(t,true,true,e)}return r}function it(t,e,r){r["elt"]=t;var n=nt(e,r);if(kutty.logger){kutty.logger(t,e,r);if(e.indexOf("Error")>0){rt(t,e,r)}}var i=t.dispatchEvent(n);var a=t.dispatchEvent(nt("all.kutty",{originalDetail:r,originalEvent:n}));return i&&a}function at(t,e,r){var n,i,a;if(g(t)){Nt(function(){n=o();i="all.kutty";a=t;n.addEventListener(i,a)})}else if(g(e)){Nt(function(){n=o();i=t;a=e;n.addEventListener(i,a)})}else{n=t;i=e;a=r;n.addEventListener(i,a)}}function ot(){var t=a().querySelector("[kt-history-elt]");return t||o()}function ut(t,r){t=t.sort(function(t,e){return r[e]-r[t]});var e=0;b(t,function(t){e++;if(e>20){delete r[t];n.getLocalStorage().removeItem(t)}})}function st(t){var e=JSON.parse(n.getLocalStorage().getItem("kt-history-timestamps"))||{};e[t]=Date.now();var r=Object.keys(e);if(r.length>20){ut(r,e)}n.getLocalStorage().setItem("kt-history-timestamps",JSON.stringify(e))}function lt(){var t=ot();var e=n.getFullPath();it(o(),"historyUpdate.kutty",{path:e,historyElt:t});n.getHistory().replaceState({},a().title);n.getLocalStorage().setItem("kt-history:"+e,t.innerHTML);st(e)}function ft(t){n.getHistory().pushState({},"",t)}function ct(t){b(t,function(t){t.call()})}function vt(t){var e=new XMLHttpRequest;var r={path:t,xhr:e};it(o(),"historyCacheMiss.kutty",r);e.open("GET",t,true);e.onload=function(){if(this.status>=200&&this.status<400){it(o(),"historyCacheMissLoad.kutty",r);var t=v(this.response);t=t.querySelector("[kt-history-elt]")||t;ct(H(ot(),t))}else{it(o(),"historyCacheMissLoadError.kutty",r)}};e.send()}function dt(){var t=n.getFullPath();it(o(),"historyRestore.kutty",{path:t});var e=n.getLocalStorage().getItem("kt-history:"+t);if(e){st(t);ct(H(ot(),v(e)))}else{vt(t)}}function gt(t){return p(t,"kt-push-url")==="true"||t.tagName==="A"&&k(t).boosted}function yt(t){pt(t,"add")}function ht(t){pt(t,"remove")}function pt(t,e){var r=p(t,"kt-indicator");if(r){var n=a().querySelectorAll(r)}else{n=[t]}b(n,function(t){t.classList[e].call(t.classList,"kutty-request")})}function mt(t,e){for(var r=0;r0){r["swapStyle"]=n[0];for(var i=1;i=200&&this.status<400){if(this.status!==204){if(!it(o,"beforeSwap.kutty",g))return;var i=this.response;if(n){lt()}var a=Ct(o);u.classList.add("kutty-swapping");var t=function(){try{var t=P(a.swapStyle,u,o,i);u.classList.remove("kutty-swapping");u.classList.add("kutty-settling");it(o,"afterSwap.kutty",g);var e=function(){b(t,function(t){t.call()});u.classList.remove("kutty-settling");if(n){ft(r||v);lt()}it(o,"afterSettle.kutty",g)};if(a.settleDelay>0){setTimeout(e,a.settleDelay)}else{e()}}catch(t){it(o,"swapError.kutty",g);throw t}};if(a.swapDelay>0){setTimeout(t,y(a.swapDelay))}else{t()}}}else{it(o,"responseError.kutty",g)}}catch(t){g["exception"]=t;it(o,"onLoadError.kutty",g);throw t}finally{ht(o);s();it(o,"afterOnLoad.kutty",g)}};f.onerror=function(){ht(o);it(o,"sendError.kutty",g);s()};if(!it(o,"beforeRequest.kutty",g))return s();yt(o);f.send(t==="get"?null:Et(c))}function Nt(t){if(a().readyState!=="loading"){t()}else{a().addEventListener("DOMContentLoaded",t)}}O(".kutty-indicator{opacity:0;transition: opacity 200ms ease-in;}");O(".kutty-request .kutty-indicator{opacity:1}");O(".kutty-request.kutty-indicator{opacity:1}");Nt(function(){var t=o();et(t);it(t,"load.kutty",{});window.onpopstate=function(){dt()}});function qt(t){return eval(t)}function At(e){kutty.on("load.kutty",function(t){e(t.detail.elt)})}function Mt(){kutty.logger=function(t,e,r){if(console){console.log(e,t,r)}}}function Rt(t){n=t}function Dt(){n=n()}return{processElement:et,on:at,onLoad:At,logAll:Mt,version:"0.0.1",_:qt}}(); \ No newline at end of file diff --git a/dist/kutty.min.js.gz b/dist/kutty.min.js.gz index 275dda9563de5af6e1c8bce531ca0a3e528ec683..47c55f0c031acef61fb0d3c2e070fba404342f77 100644 GIT binary patch literal 5342 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 literal 5271 zcmV;I6lm)oiwFqxLAzc618a43ba^gqX>KlRa{!%Ndw1J5vi~X$c8^QelwxOlA4d$+ zlh{s^){e8bvrVJ;_Rta}v8G5BglsD^zx$mB0D_e4?%97V5*!SG!OU+SfNxW^bF8vb*a4nXV+^Mw6Mq0$TKq!-;m`W`ou_!D0S>oTZ%%zt2n(EO^T*a2h z&NPtWI-3MCNfJ>k^ISYU(DNkCD`{UuxK>gxRk4F4wS-SQ*r}hVm4080<^9h)5~-QD(kpu?u)vel8++<$lj<<7WYF3yA-tW+ zT0150c8<19h-x4PNmJYH($_^4N#%>EI^V>wBexo%jpwuh`wLa-Qm+;gbcOfU zXf=YP7?wn}18}n^u0+FYIs6>rTK;py^imx#yoN#@Po3vxoetk6;wY2HJe6Gq47 zP+y^XKSTYdw`5({n`m+4U<}w!N)>ST{aC%U_#LagJy6U*4%HP%r|COxrb58{rd$?d zkjG)30aTvD#Dmb8oGur}HZ&-`*5%w`*UP)C7?*ebtSF>onS1@*czC=)pbbnM;_*@y0d5n%5MvtI5`KTM+)2YI6A$-OdSQ9)slmYW@Q4w~b z#_3sBH8Fv1IT)P!pG&D$7MlU?YNp`5m>U#CAc7!8jyI&-;wSy>KYwI(w2ovyv~8 z;T5n1T|hhqSKpoF((FKWmo9o`c};N+0Mt?nUkO0&CPsL(dJoFq&&KUNLZXq9sg_43 zUU;k0DgJ^Xz={?NS&RwPQS1f4ztPTn5~cAsHXivjNvc8xskb>9JC!`cPhy zQuGNAM6^c300skDqrJKyp=e}t85EI`PG+UkQk2(#XkwU+uSD3A7E7b(Xcb^#I{g3} zv4?#UfL)Lc;$K@numgEnNL&~Q~-u{Q}~fi|jfF7tkU_oLqr05tr=m=O$?Eu=%={;+5 zR9FoK9A+)#hBP^YM+gUx0u^O0%#$X}D*^x1yUWuP7Xw`fX@pRcg(gr+CqQy7r&-aW z`QX!BlLc%N{kcg5i)xX)-WaP(acU?!krsYB=TE*zdE4Yv{1=SWsPJm@lS8_XNcg06lF2KxJAX$e4!-7MjTaYdgCu{N@gRHL6p+rT52F*4} z4LTaO1D|&d60X4!O{Y=~#3;{3H=;HHk98^Lb4SPS;E~;K7o-XdFHi*#E95O`nn0ho zDD(V1#(6N+WEvEZsx)bO)`+R95XP-F_mMQpm|QvY1WnioSUSt_|SZ6raP8-y7DGW z@36ko&iB7bLCj`AUrY-H%yup?gzy(3pvOGZ!byclvtqH-(GF%_;8W9R$C`Y%H_u!hv)gwjT+(*m4y;0I+&2^7QXa{oqD`g0Det#iI;`66I!M<2-i z)U<%6gZ+Io!!_!4iW3~D2*S_9Jgurh7O_av`l&+0XYgZWY3TON$h%t{eF1rZM-pv~ z1*E@Lm3#~wkdqm@X;Mg4PNzAl1jKNc*<1pI`Hml2t&;oA8H5e6xRb2XLyzGV=Uv{{ zNcF8G!Wv#k7VuP1u(f9zSTbHiynQzR;jjh8Tz(+>sf+U*b@w%onLIN5Bv?@i=}el4 zCuT+#TCk!;qv0Pn*dJbIk(VQya(@P~eZc{UbT7NRlzGgQoQQCD_nG$w`A({I^=qbQ zf%u!Be!j8MS2wPCEp~TtY^(@*&2P`nPYD7{enBX}h7x7*CKGU<2~|zO47>?75mL&{ zbE#(~Ko4we`eWKI+YDL^Qk!M}!q`q*fwS;4{hwPYVR!yqj{1(kWQeK1Lz}B;2htE- z7Trtg&?xm+Il+XLMqaGAM~i zf5d*)Sfgf5W5b+NECFs$gKI3tIp%uqk+%g?A;M~7y9OWS_>9_EJ_cCY9~s z;ei$8f1!T`0c0ji>u_pF3;{7R#)*tZxlENck*2+7jPh*eOj@D!IZqC2UKAt2+V%Bq zu88b+gA4&$a4hW#lcjxvHj*+Ly)5911}jd-<72-uaOABZ_+M<&&$!kx=1N3Gq#|nB%C`pl zZTa9CX7fCU-Q96u^LuV#-C`8$Lqu~8KZ_KLBn{7>Y`K6TPh#kDQ7+YpQbsgtoA|Jz zxsJa>6e+C}#>97$O$TcTlxK&any{EG&8q=-!t7aNOpxh|i({L^snGastmD%9d0=@r6<33UBn}`B2O@N zJ5^|Vj%Pn{Y`ft@Ofccems6+q#urgaHsI53Xa^BBQL-pXkSY`V^6P# zjpR7oOXJa1^3J-OBKfBcT4+~ycWus1 zaX_oUWP>J8Cb*7PjKYq3+iD-xtQn zX7PV-7ntnC$*OxxmGel>QV}*jpPszadT}lRs4H{|5G@vg_;_}4DKPrXuxM$%zLTKU zfUSpIr0Zd^2}0|10YNs0YtR3v$|43g3hAv*K3%@;{jXpi#Ww1tSZ8VV2# zxi;QIDFdsE1WEucXltMq-kYbeUg3eDjB~ZWI|$;Pm;G^8(J4O9uwdO!H$K4d0&)iD zAWAUxtVr`CvsIC8AaZmVP2m&5O7E;wwsaGMSxPHMgz=ZsPan9DMaPR-;*AbTBx2eI znrCP<4Zw^50iR?kfsy_TM$iM$63Eg-Jxb~w%7dFX5zywr1w}kF=vk^&4|W%8%hoAA zJh;<}=7^RYID`_g8&#>i)XJ}2s-eE>siC~8ZN$94;UM(h9_hrZ7oY=tdt(Q?#F$>? z7cil8YTf92E$4Kh!9U=Z4Vrp&t5$CaRjI4+7K!O0l@WYlvT9)Z_?EreBnI zF5@=mk~eZy+1%TfCve6f2#Q2o<5f?O7j;lZ`v>l2vrV{TMs6>WvqnSL-s>+LFwedSY9fR9psHDaM1ByH* zpTgV6hduF*K}-k@i3_t;vRafy1%spY{iPj4mK=is&jVDaNQO;R`SkFusGA}~%|6l- z_oPwQ|4c*ACH{0&RMx)QKV`)OKcJL!S;MUq8|`9AEj%n7NiXWRAqZ+9M<6bI+EQUa z0J7y4?`i=tTv4PBKIeu~`nL|*b#Qu5s{=V(681!V?O5}e%us>Y$n z-1R4OYERp;yX*2adq9)YaIck0SFmaH3vFOWJcP~pkUSYe4tQ024OvYoKCjcSbX_3O? zHftE2<2Ds`4X?eAcpTRS0-fA=YiDh;O;lCiJe#?bP2ogtCRsu8qYwk}x%bOuPlVv@ z)x(4E!R^BJ$1(wN6NUxkWSpSJ+o-k>)e2xg+2^JMc-hdmb(o z-t_2wIzJH<&U7Le)*q$?(askd)5)5%VQ!{~y4Q@5nGxuu4LD-?`&OEJE4I^XYV4U- z?6yJLo@l#+Tj`cwcIDfKx8>ihJgEZAQrOnXKYu+Y;Mq*kA+S#8m9sniOIN8uwH?&0 z>*j_oe*C_BcWS47PFIwA#~x6E!Zrt~kDZFi=0waS3!3wb_6;ot1wavE`j@OS4E@`T zvgTNnsz!7zti8%+W&XV_&zjv$h`V$lY(lZdw+p=SHV`g277?}{(dpuT1eOMIH^1ST zq<75+Pq+2P={BU~jw>YQbj)pWBuftBtT}bgtkX_`EMrgLop1vecVcX~(ma6c6bQ}! z{1;n*-;t)=>e?)#-T8m`)OizW&mcgIybOGq90w(>b;(YVgJwVuvd4B=nA-+3{O(=k za~oRQD|w}no{4zg7jJXC6WR8&8=v_I26Xq3!p3&MDm01AvEdiJJ> z2mm&xDlcHSO@q<)sS@Yg*#74Q+Yz>nTeVA3AM zw?$S=h0C$q0_Dc{2woOnVQWgfFXnUPylxBA$A(30WC~s-UGHiyGVYv7KUDyEYBJ4G zESjYZAWpjtW0H|SJOOqyftr1ThvVpKyn`VfwZ7V*5)X++skuwX{81t;xQ6BFnJVw_ z&a)ZDdYaB4GMf5d-8J68zeQGJYRwAJ8OEE+YAEFWNbT3!<*d%3&;6t zJti$eIP)adHLsd2vvZUgn$AWPve+05fjRD~yaDD7t!GRnjuS4(I9&Y^o#cSg(z}gp z^J@(L6xi*G>TJ)3m$*i}b3BYE-FcMd!B+aG$=fydruhZjn&G~AkpKa9LS+{s}l18 z3vK>!ig!}(l2h>bRhi=jR(?QI6Uw`IYi&*zP53|f65$m$8L<@%?qvj>Aa@>okP-N) d3U^QJ_xJk;BKm!B2<8gf_y1JNY 20) { - delete historyTimestamps[path]; - localStorage.removeItem(path); + function saveToHistoryCache(url, content, title, scroll) { + var historyCache = JSON.parse(localStorage.getItem("kutty-history-cache")) || []; + for (var i = 0; i < historyCache.length; i++) { + if (historyCache[i].url === url) { + historyCache = historyCache.slice(i, 1); + break; } - }); + } + historyCache.push({url:url, content: content, title:title, scroll:scroll}) + while (historyCache.length > kutty.config.historyCacheSize) { + historyCache.shift(); + } + localStorage.setItem("kutty-history-cache", JSON.stringify(historyCache)); } - function bumpHistoryAccessDate(pathAndSearch) { - var historyTimestamps = JSON.parse(localStorage.getItem("kt-history-timestamps")) || {}; - historyTimestamps[pathAndSearch] = Date.now(); - var paths = Object.keys(historyTimestamps); - if (paths.length > 20) { - purgeOldestPaths(paths, historyTimestamps); + function getCachedHistory(url) { + var historyCache = JSON.parse(localStorage.getItem("kutty-history-cache")) || []; + for (var i = 0; i < historyCache.length; i++) { + if (historyCache[i].url === url) { + return historyCache[i]; + } } - localStorage.setItem("kt-history-timestamps", JSON.stringify(historyTimestamps)); + return null; } function saveHistory() { var elt = getHistoryElement(); - var pathAndSearch = location.pathname+location.search; - triggerEvent(getDocument().body, "historyUpdate.kutty", {path:pathAndSearch, historyElt:elt}); - history.replaceState({}, getDocument().title, window.location.href); - localStorage.setItem('kt-history:' + pathAndSearch, elt.innerHTML); - bumpHistoryAccessDate(pathAndSearch); + var path = currentPathForHistory || location.pathname+location.search; + triggerEvent(getDocument().body, "beforeHistorySave.kutty", {path:path, historyElt:elt}); + if(kutty.config.historyEnabled) history.replaceState({}, getDocument().title, window.location.href); + saveToHistoryCache(path, elt.innerHTML, getDocument().title, window.scrollY); } - function pushUrlIntoHistory(url) { - history.pushState({}, "", url ); + function pushUrlIntoHistory(path) { + if(kutty.config.historyEnabled) history.pushState({}, "", path); + currentPathForHistory = path; } function settleImmediately(settleTasks) { @@ -736,17 +739,18 @@ var kutty = kutty || (function () { }); } - function loadHistoryFromServer(pathAndSearch) { + function loadHistoryFromServer(path) { var request = new XMLHttpRequest(); - var details = {path: pathAndSearch, xhr:request}; + var details = {path: path, xhr:request}; triggerEvent(getDocument().body, "historyCacheMiss.kutty", details); - request.open('GET', pathAndSearch, true); + request.open('GET', path, true); request.onload = function () { if (this.status >= 200 && this.status < 400) { triggerEvent(getDocument().body, "historyCacheMissLoad.kutty", details); var fragment = makeFragment(this.response); fragment = fragment.querySelector('[kt-history-elt]') || fragment; settleImmediately(swapInnerHTML(getHistoryElement(), fragment)); + currentPathForHistory = path; } else { triggerEvent(getDocument().body, "historyCacheMissLoadError.kutty", details); } @@ -754,15 +758,18 @@ var kutty = kutty || (function () { request.send(); } - function restoreHistory() { - var pathAndSearch = location.pathname+location.search; - triggerEvent(getDocument().body, "historyRestore.kutty", {path:pathAndSearch}); - var content = localStorage.getItem('kt-history:' + pathAndSearch); - if (content) { - bumpHistoryAccessDate(pathAndSearch); - settleImmediately(swapInnerHTML(getHistoryElement(), makeFragment(content))); + function restoreHistory(path) { + saveHistory(currentPathForHistory); + path = path || location.pathname+location.search; + triggerEvent(getDocument().body, "historyRestore.kutty", {path:path}); + var cached = getCachedHistory(path); + if (cached) { + settleImmediately(swapInnerHTML(getHistoryElement(), makeFragment(cached.content))); + document.title = cached.title; + window.scrollTo(0, cached.scroll); + currentPathForHistory = path; } else { - loadHistoryFromServer(pathAndSearch); + loadHistoryFromServer(path); } } @@ -958,9 +965,9 @@ var kutty = kutty || (function () { function getSwapSpecification(elt) { var swapInfo = getClosestAttributeValue(elt, "kt-swap"); var swapSpec = { - "swapStyle" : "innerHTML", - "swapDelay" : 0, - "settleDelay" : 100 + "swapStyle" : kutty.config.defaultSwapStyle, + "swapDelay" : kutty.config.defaultSwapDelay, + "settleDelay" : kutty.config.defaultSettleDelay } if (swapInfo) { var split = splitOnWhitespace(swapInfo); @@ -1075,7 +1082,6 @@ var kutty = kutty || (function () { // push URL and save new page if (shouldSaveHistory) { pushUrlIntoHistory(pushedUrl || requestURL ); - saveHistory(); } triggerEvent(elt, 'afterSettle.kutty', eventDetail); } @@ -1136,8 +1142,19 @@ var kutty = kutty || (function () { addRule(".kutty-request .kutty-indicator{opacity:1}"); addRule(".kutty-request.kutty-indicator{opacity:1}"); + function getConfig() { + return Object.assign({ + historyEnabled:true, + historyCacheSize:10, + defaultSwapStyle:'innerHTML', + defaultSwapDelay:0, + defaultSettleDelay:100 + }, JSON.parse(getDocument().querySelector('meta[name="kutty-config"]').content)) + } + // initialize the document ready(function () { + kutty.config = getConfig(); var body = getDocument().body; processNode(body); triggerEvent(body, 'load.kutty', {}); @@ -1170,8 +1187,10 @@ var kutty = kutty || (function () { on: addKuttyEventListener, onLoad: onLoadHelper, logAll : logAll, + logger : null, + config : null, version: "0.0.1", _:internalEval } } -)(); \ No newline at end of file +)() || kutty; \ No newline at end of file diff --git a/test/browser-only.html b/test/browser-only.html index adf19f95..71525c1e 100644 --- a/test/browser-only.html +++ b/test/browser-only.html @@ -18,8 +18,85 @@ - - + + diff --git a/test/kt-push-url.js b/test/kt-push-url.js index 3806acfe..e60ff618 100644 --- a/test/kt-push-url.js +++ b/test/kt-push-url.js @@ -1,77 +1,77 @@ describe("kt-push-url attribute", function() { + var KUTTY_HISTORY_CACHE = "kutty-history-cache"; beforeEach(function () { this.server = makeServer(); clearWorkArea(); + localStorage.removeItem(KUTTY_HISTORY_CACHE); }); afterEach(function () { this.server.restore(); clearWorkArea(); + localStorage.removeItem(KUTTY_HISTORY_CACHE); }); - it("should handle a basic back button click", function (done) { + it("navigation should push an element into the cache ", function () { this.server.respondWith("GET", "/test", "second"); - getWorkArea().innerHTML.should.be.equal(""); var div = make('
first
'); div.click(); this.server.respond(); getWorkArea().textContent.should.equal("second") - history.back(); - setTimeout(function(){ - getWorkArea().textContent.should.equal("first"); - done(); - }, 20); + var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE)); + cache.length.should.equal(1); }); - it("should handle two forward clicks then back twice", function (done) { - var i = 0; - this.server.respondWith("GET", "/test", function(xhr){ - i++; - xhr.respond(200, {}, "" + i); - }); - - getWorkArea().innerHTML.should.equal(""); - var div = make('
0
'); - div.click(); - this.server.respond(); - getWorkArea().textContent.should.equal("1") - - div.click(); - this.server.respond(); - getWorkArea().textContent.should.equal("2") - - history.back(); - setTimeout(function(){ - getWorkArea().textContent.should.equal("1"); - history.back(); - setTimeout(function(){ - getWorkArea().textContent.should.equal("0"); - done(); - }, 20); - }, 20); - }) - - it("should handle a back, forward, back button click", function (done) { + it("restore should return old value", function () { this.server.respondWith("GET", "/test", "second"); - - getWorkArea().innerHTML.should.equal(""); - var div = make('
first
'); + getWorkArea().innerHTML.should.be.equal(""); + var div = make('
first
'); div.click(); this.server.respond(); getWorkArea().textContent.should.equal("second") - history.back(); - setTimeout(function(){ - getWorkArea().textContent.should.equal("first"); - history.forward(); - setTimeout(function() { - getWorkArea().textContent.should.equal("second"); - history.back(); - setTimeout(function() { - getWorkArea().textContent.should.equal("first"); - done(); - }, 20); - }, 20); - }, 20); - }) -}); + kutty._('restoreHistory')(location.pathname+location.search) + getWorkArea().textContent.should.equal("first") + }); + + it("cache should only store 10 entries", function () { + var x = 0; + this.server.respondWith("GET", /test.*/, function(xhr){ + x++; + xhr.respond(200, {}, '
') + }); + getWorkArea().innerHTML.should.be.equal(""); + make('
'); + for (var i = 0; i < 20; i++) { // issue 20 requests + byId("d1").click(); + this.server.respond(); + } + var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE)); + cache.length.should.equal(10); // should only be 10 elements + }); + + it("cache miss should issue another GET", function () { + this.server.respondWith("GET", "/test1", '
test1
'); + this.server.respondWith("GET", "/test2", '
test2
'); + + make('
init
'); + + byId("d1").click(); + this.server.respond(); + var workArea = getWorkArea(); + workArea.textContent.should.equal("test1") + + byId("d2").click(); + this.server.respond(); + workArea.textContent.should.equal("test2") + + var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE)); + + cache.length.should.equal(2); + localStorage.removeItem(KUTTY_HISTORY_CACHE); // clear cache + kutty._('restoreHistory')("/test1") + this.server.respond(); + getWorkArea().textContent.should.equal("test1") + }); + +}); \ No newline at end of file diff --git a/www/events.md b/www/events.md index 9e3c964f..24b038ff 100644 --- a/www/events.md +++ b/www/events.md @@ -97,7 +97,7 @@ This event is triggered when kutty handles a history restoration action * `detail.path` - the path and query of the page being restored -### Event - [`historyUpdate.kutty`](#historyUpdate.kutty) +### Event - [`beforeHistorySave.kutty`](#beforeHistorySave.kutty) This event is triggered when kutty handles a history restoration action diff --git a/www/reference.md b/www/reference.md index c65fbc20..07ee601e 100644 --- a/www/reference.md +++ b/www/reference.md @@ -76,7 +76,7 @@ title: kutty - Attributes | [`historyCacheMissError.kutty`](/events#historyCacheMissError.kutty) | triggered on a unsuccessful remote retrieval | [`historyCacheMissLoad.kutty`](/events#historyCacheMissLoad.kutty) | triggered on a succesful remote retrieval | [`historyRestore.kutty`](/events#historyRestore.kutty) | triggered when kutty handles a history restoration action -| [`historyUpdate.kutty`](/events#historyUpdate.kutty) | triggered when a new history element is added to the local cache +| [`beforeHistorySave.kutty`](/events#beforeHistorySave.kutty) | triggered before content is saved to the history cache | [`initSSE.kutty`](/events#initSSE.kutty) | triggered when a new Server Sent Event source is created | [`load.kutty`](/events#load.kutty) | triggered when new content is added to the DOM | [`noSSESourceError.kutty`](/events#noSSESourceError.kutty) | triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined