Post by Chris on Oct 10, 2006 21:28:00 GMT -8
Overview: This code adds an undo button and a dropdown list to the rows of UBBC tag buttons then keeps track of each time a button is used by recording what was changed and what it was changed to. You may then undo the operations in reverse order of when they were made or selectively undo one from the history list. Manually typing or pasting UBBC tags won't be recorded, only button initiated UBBC tags are recorded. Color tags are supported as well.
Installation: Place in Global Footer
Tested Browsers:
Preview: click me (this is a preview for another code but can be used to test functionality of this one)
ToDo:
Known Bugs:
Notes:
Edit:
Installation: Place in Global Footer
Tested Browsers:
- Internet Explorer 6
- Firefox 1.5/2.0 (RC2) beta
Preview: click me (this is a preview for another code but can be used to test functionality of this one)
<script type="text/javascript">
<!--
/////////////////////////////////////////////////////////////////////////////////////////
// Created by EtonBones at yahoo d o t com
// Do not redistribute without permission
// Feel free to edit provided a notation is
// added to this header stating that fact.
// THIS HEADER MUST REMAIN INTACT.
/////////////////////////////////////////////////////////////////////////////////////////
//Location: Global Footer
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// UBBC UNDO
/////////////////////////////////////////////////////////////////////////////////////////
/* ============ubbc buttons redirector =========== */
function addX(open, end){
var scrollTop=document.postForm.message.scrollTop;
UBBCHistory[UBBCHistory.length]=new Array(5);
if(!open){return false;}
end=(end)?end:"";
if(typeof document.selection!="undefined"){getCursorPositionIE();}
var selectionStart=document.postForm.message.selectionStart;
var selectionEnd=document.postForm.message.selectionEnd;
UBBCHistory[UBBCHistory.length-1][3]=selectionEnd;
var selectionText= document.postForm.message.value.substr(selectionStart,selectionEnd-selectionStart);
UBBCHistory[UBBCHistory.length-1][0]=selectionText;
var selectionEnd=selectionEnd+(open.length+((arguments[2])?arguments[2].length+1:0)+end.length);
add(open, end, arguments[2]);
UBBCHistory[UBBCHistory.length-1][2]=selectionStart;
UBBCHistory[UBBCHistory.length-1][4]=scrollTop;
var newOPT=document.createElement('option');
newOPT.value=UBBCHistory.length-1;
undoSLCT.appendChild(newOPT,null);
//document.postForm.message.scrollTop=scrollTop;
if(typeof document.selection=="undefined"){
document.postForm.message.selectionStart=selectionStart;
document.postForm.message.selectionEnd=selectionEnd;
}else{
getCursorPositionIE();
}
UBBCHistory[UBBCHistory.length-1][1]= document.postForm.message.value.substr( document.postForm.message.selectionStart, document.postForm.message.selectionEnd-document.postForm.message.selectionStart );
newOPT.appendChild( document.createTextNode(UBBCTrunc(UBBCHistory[UBBCHistory.length-1][1])))
newOPT.title=UBBCHistory[UBBCHistory.length-1][1];
undoBTN.disabled=false;
undoBTN.title= "Undo: "+ UBBCHistory[UBBCHistory.length-1][1];
undoSLCT.options[0].selected=true;
}
/* =========== color tag redirector=========== */
function aCX(color){ var c = (color)? color : ""; addX("[color=", "[/color]", c);}
/* ===========Main code: hook ubbc insertion events============= */
if( document.postForm ){
document.postForm.color.onchange= function(){aCX(this.options[this.selectedIndex].value);this.selectedIndex=0;}
var buttons=document.postForm.color.parentNode.childNodes;
for(i=0;i<buttons.length;i++){
if(buttons[i].nodeType==1 && buttons[i].tagName.toUpperCase()=="A"){
buttons[i].href=buttons[i].href.replace(/:add\(/i , ":addX(");
}
}
UBBCHistory=new Array();
UBBCHistory[0]=new Array(5);
var undoSLCT=document.createElement('select');
undoSLCT.id="UBBCHist";
var newOPT=document.createElement('option');
newOPT.appendChild(document.createTextNode('Undo Last UBBC'));
newOPT.selected=true;
newOPT.value=-1;
undoSLCT.appendChild(newOPT);
var newBTN=document.createElement('button');
newBTN.Type="button";
newBTN.appendChild(document.createTextNode('Undo'));
newBTN.onclick=function() {UBBCUndo((undoSLCT.options[undoSLCT.selectedIndex].value>-1)?undoSLCT.selectedIndex:null);}
newBTN.disabled=true;
newBTN.id="UBBCUndo";
//newBTN.style.backgroundColor="silver";
var oCell=document.postForm.color.parentNode;
if ( !oCell.outerHTML){
oCell.insertBefore(document.createTextNode(' '),null);
oCell.insertBefore(undoSLCT,null);
oCell.insertBefore(document.createTextNode(' '),null);
var undoBTN=oCell.insertBefore(newBTN,null);
}else{
/* IE's DOM insertion is flawed */
oCell=oCell.parentNode;
oCell.insertBefore(document.createTextNode(' '),null);
oCell.insertBefore(undoSLCT,null);
oCell.insertBefore(document.createTextNode(' '),null);
var undoBTN=oCell.insertBefore(newBTN,null);
}
undoSLCT.onchange=function(){clickSLCT();}
}
/* =======undo list event handler=========== */
function clickSLCT(){
undoBTN.disabled?document.postForm.message.focus():undoBTN.focus();
undoBTN.title=UBBCHistory[(undoSLCT.options[undoSLCT.selectedIndex].value>-1 )?undoSLCT.selectedIndex:UBBCHistory.length-1][1];
}
function UBBCUndo(index){
if(index){
lastPair=UBBCHistory[index];
var ubbc=UBBCHistory[index][1].replace(/^\s*|\s*$/g,"");
ubbc.replace(/\n/g,"").match( /(\[(.+)?(?:=.+?)?\])(.*)(\[\/\2\])/i);
var r1=RegExp.$1;
var r2=RegExp.$3;
var r3=RegExp.$4;
if(!r1){ubbc.match(/\[.+?\]/); r1=RegExp.$1;}
if(!r1){return false;}
if(r1.match(/^\[table\]/i) && r2 && r2.match(/(\s*\[tr\]\s*\[td\])/im)){
var r4=RegExp.$1;
r1+=r4;
r3=r4 + r3;
r3=r3.replace("\[tr","\[\/tr").replace("\[td","\[\/td");
r2=r2.replace(/\[\/?tr\]/gim,"").replace(/\[\/?td\]/gim,"");
}else if(r1.match(/^\[list\]/i) && r2 && r2.match(/(\[\*\])/im)){
var r4=RegExp.$1;
r1+=r4;
r2=r2.replace(/(\[\*\])/im,"");
while(r2.match(/(\[\*\])/im)){
r3=RegExp.$1+r3;
r2=r2.replace(/(\[\*\])/im,"");
}
}
for(var i=index+1;i<UBBCHistory.length;i++){
if(r2.match(/\[list\]/i) && !r2.match(/\n\[\*\]/))
{r2=r2.replace(/\[\*\]/g,"\n[/li][li]").replace(/\[\/list\]/g,"\n[\/list]");}
UBBCHistory[i][1]=UBBCHistory[i][1].replace(ubbc,r2);
UBBCHistory[i][0]=UBBCHistory[i][0].replace(ubbc,r2);
undoSLCT.options[i].title=undoSLCT.options[i].title.replace(ubbc,r2)
undoSLCT.options[i].text=UBBCTrunc(undoSLCT.options[i].title);
}
}else{
var lastPair=UBBCHistory.pop();
undoSLCT.removeChild(undoSLCT[undoSLCT.options.length-1]);
}
if( (lastPair) && UBBCHistory.length>0){
if(lastPair[1]){lastPair=lastUpdate(lastPair);}
document.postForm.message.focus();
if(lastPair[2]){
if(!document.selection){
document.postForm.message.selectionStart=lastPair[2];
document.postForm.message.selectionEnd=lastPair[3];
document.postForm.message.scrollTop=lastPair[4];
}else{
setCursorPositionIE(lastPair[2],lastPair[3]);
}
}
if(index)
{UBBCHistory.splice(index,1); undoSLCT.removeChild(undoSLCT[undoSLCT.selectedIndex]);}
undoBTN.title= "Undo: "+ UBBCHistory[UBBCHistory.length-1][1];
undoSLCT.options[0].selected=true;
}
if(UBBCHistory[UBBCHistory.length-1] == "undefined" || UBBCHistory[UBBCHistory.length-1]==",,,,"){
undoBTN.disabled=true;
undoBTN.title= "No more history";
for(var i=undoSLCT.options.length-1;i>0;i-- ) {undoSLCT.removeChild(undoSLCT[i]);}
document.postForm.message.focus();
}
}
/* ========update offsets for pre-existing history========= */
function lastUpdate(lastPair)
{
if(!lastPair){return false;}
var msg=document.postForm.message.value;
if(lastPair[2]-lastPair[1].length<1)
{var start=0}else{var start=lastPair[2]-lastPair[1].length-1;}
var msgSub=msg.substr(start);
msgFind=lastPair[1].replace(/(\[|\^|\$|\.|\||\?|\*|\+|\(|\)|\/)/g,"\\$1");
var msgFind= new RegExp(msgFind);
var growth=msgSub.search(msgFind);
if (growth==-1){return false;}else{growth=(start)?growth-lastPair[1].length-1:start;}
lastPair[2]+=growth;
lastPair[3]+=growth;
var msgSubNew=msgSub.replace(lastPair[1],lastPair[0]);
document.postForm.message.value=msg.replace(msgSub,msgSubNew);
return lastPair;
}
/* =========set caret positon and/or text selection======= */
function setCursorPositionIE(start,end) {
if(typeof document.selection!="undefined"){
var element=document.postForm.message;
end=(end)?end:start;
var range = element.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
}
/* =======get caret position and/or selection start and end===== */
function getCursorPositionIE(){
if(typeof document.selection!="undefined"){
var element = document.postForm.message;
element.focus();
var range = document.selection.createRange();
var stored_range = range.duplicate();
stored_range.moveToElementText( element );
stored_range.setEndPoint( 'EndToEnd', range );
element.selectionStart = stored_range.text.length - range.text.length;
element.selectionEnd = element.selectionStart + range.text.length;
element.selectionFaux=true;
}
}
/* ======truncate ubbc for display purpose in options list========= */
function UBBCTrunc(str){
str= str.replace(/^\s*|\s*$/g,""); // trim spaces
if(str.substr(0,1)!="\["){return "Malformed UBBC";}
var re=/(\[(.*)?(?:=.+?)?\])(.*)(\[\/\2\])/i
str=str.replace(/\n/g,"");
str=str.replace(/\[\*\]/g,"\*");
if(!str.match(/^\[tr\]/i)){str=str.replace(/\[\/*?tr\]/g,"\u00AE");}
if(!str.match(/^\[td\]/i)){str=str.replace(/\[\/*?td\]/g,"\u00A9");}
if(!str.match(re)){str.match(/(\[.*?\])/i);}
var open = RegExp.$1; open=(open)?open:"Malformed UBBC";
var end = RegExp.$4; end=(end)?end:"";
var content = RegExp.$3;content=(content)?content:"";
if(content && open.length+content.length+end.length>30)
{content=content.substr(0,30-end.length-open.length)+"\u2026";}
return open+content+end;
}//-->
</script>
ToDo:
-
Make history randomly accessible so one can skip changes they want to keep.Done
Known Bugs:
- Refreshing browser will destroy UBBC history in both firefox and IE. Firefox will however maintain the modifications to the text area while IE will discard all changes. (no fix in foreseeable future - developers welcomed to try and implement an inter-session solution)
-
If you progressively add nested UBBC tags then selectively undo one of the intermediate ones, the outer tags of the later undos will become orphaned. If you want to undo them then do them in the reverse order of when they were added or use the old fashioned way and simply edit by hand..fixed
Example:[b][b][ь][b][b]Firefox [/b][/b][/ь][/b][/b]
If you were to have all the nested bolds in the UBBC history and selectively undid the pair in red, the last nest (five nestings) becomes orphaned in history since there are no longer five nests to undo.
Notes:
- Editing a tag then deciding you no longer want it won't work. UBBC Undo only works on unedited tags (the default tags you see created when you press a button). This is not really a bug but a feature omission. The code would bloat to Microsoft or Symantec proportions if I tried to implement something like that, especially since I'm not that well versed in javascript yet.
Suggestions for improvements welcomed.
I'm new to Javascript and wrote this as a learning exercise.
I'm new to Javascript and wrote this as a learning exercise.
Edit:
- 10/14/2006 - Added randomly selectable history undo and fixed a cosmetic bug where IE sometimes selected the wrong text when replacing even though the correct text was replaced.
- 10/19/2006 -
- Fixed incorrect handling of color tags after a selective undo operation.
- Fixed incorrect handling of row and cell tags after selective undo.
- Fixed malformed UBBC UI bug for tags that contain parameters (bug was cosmetic, not a functional defect)
- Fixed incorrect handling of color tags after a selective undo operation.