Revision of Twine: Improved back and forward buttons in Sugarcane from Sat, 05/18/2013 - 23:21

This script allows Sugarcane to use HTML5 history management instead of URL hash strings to keep track of the game state. This means that various non-deterministic state changes (like random numbers, player input, state changes inside <<replace>> macros, etc.) will be properly remembered when you use the browser's Back button. This code also updates <<back>> and <<return>> and the Rewind menu.

Note: due to a conflict in the Twine engine, I've had to include Transition CSS with this script. So, you must also include one of the CSS transitions on that page.

Note 2: IE9 users will be given the old hashchange functionality anyway.

(function(){var hasPushState=(typeof window.history.pushState=="function");
History.prototype.display=function(d,b,a){var c=tale.get(d);if(a!="back"){this.history.unshift({passage:c,variables:clone(this.history[0].variables)});
this.history[0].hash=this.save();if(hasPushState&&this.history&&this.history.length>2){window.history.pushState(this.history,document.title);
}}this.history[0].hash=this.save();var e=c.render();e.style.visibility="visible";
if(a!="offscreen"){var p=$("passages");for(var j=0;j<p.childNodes.length;
j+=1){var q=p.childNodes[j];q.classList.add("transition-out");
setTimeout(function(){if(q.parentNode){q.parentNode.removeChild(q);
}},1000);}e.classList.add("transition-in");setTimeout(function(){e.classList.remove("transition-in");
},1);p.appendChild(e);}if((a=="quietly")||(a=="offscreen")){e.style.visibility="visible";
}if(a!="offscreen"){document.title=tale.title+": "+c.title;this.hash=this.save();
if(!hasPushState){this.hash=this.save();window.location.hash=this.hash;
}window.scroll(0,0);}return e;};History.prototype.restart=function(){window.location.reload();
};macros["return"]=macros.back={handler:function(a,b,e){var el,d="";
var steps=1;if(e[0]){if(e[1]=="steps"){if(isNaN(e[0])){throwError(a,"parameter before 'steps' must be a number.");
return;}else{if(e[0]<state.history.length){d=state.history[e[0]].passage.title;
steps=e[0];}}}else{if(tale.get(e[0]).id==undefined){throwError(a,"The "+e[0]+" passage does not exist");
return;}for(var c=0;c<state.history.length;c++){if(state.history[c].passage.title==e[0]){d=e[0];
steps=c;break;}}}}else{d=state.history[1].passage.title;}if(!d){return;
}else{el=document.createElement("a");el.className="return";el.onclick=function(){if(b=="back"){if(hasPushState){window.history.back();
return;}while(steps>=0){if(state.history.length>1){state.history.shift();
}steps--;}}state.display(d);};el.href="javascript:void(0)";el.innerHTML="<b>«</b> "+b[0].toUpperCase()+b.slice(1);
a.appendChild(el);}}};Interface.buildSnapback=function(){var c=false;
removeChildren(document.getElementById("snapbackMenu"));for(var a=state.history.length-1;
a>=0;a--){if(state.history[a].passage&&state.history[a].passage.tags.indexOf("bookmark")!=-1){var b=document.createElement("div");
b.pos=a;b.onclick=function(){var p=this.pos;var n=state.history[p].passage.title;
window.history.go(-(p+1));while(p>=0){if(state.history.length>1){state.history.shift();
}p--;}state.display(n);};b.innerHTML=Passage.prototype.excerpt.call(state.history[a].passage);
document.getElementById("snapbackMenu").appendChild(b);c=true;
}}if(!c){var b=document.createElement("div");b.innerHTML="<i>No passages available</i>";
document.getElementById("snapbackMenu").appendChild(b);}};History.prototype.init=function(){if(!this.restore()){this.display("Start",null);
}if(!hasPushState){this.hash=window.location.hash;this.interval=window.setInterval(function(){a.watchHash.apply(a);
},250);}};window.onpopstate=function(e){console.log("   Popped");
console.log(e.state);if(e.state===null){return;}if(e.state&&e.state.length>0){state.history=e.state;
}else{state=new History();state.init();}state.display(state.history[0].passage.title,null,"back");
};if(hasPushState){clearInterval(state.interval);}}());

Feel free to report any bugs to @webbedspace.