Added option to restore revisions #1791
parent
e1fe1f0f9c
commit
d246a191c6
|
@ -576,6 +576,104 @@ exports.deletePad = function(padID, callback)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.restoreRevision = function(padID, rev, callback)
|
||||||
|
{
|
||||||
|
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||||
|
|
||||||
|
//check if rev is a number
|
||||||
|
if(rev !== undefined && typeof rev != "number")
|
||||||
|
{
|
||||||
|
//try to parse the number
|
||||||
|
if(!isNaN(parseInt(rev)))
|
||||||
|
{
|
||||||
|
rev = parseInt(rev);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback(new customError("rev is not a number", "apierror"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//ensure this is not a negativ number
|
||||||
|
if(rev !== undefined && rev < 0)
|
||||||
|
{
|
||||||
|
callback(new customError("rev is a negativ number","apierror"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ensure this is not a float value
|
||||||
|
if(rev !== undefined && !is_int(rev))
|
||||||
|
{
|
||||||
|
callback(new customError("rev is a float value","apierror"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the pad
|
||||||
|
getPadSafe(padID, true, function(err, pad)
|
||||||
|
{
|
||||||
|
if(ERR(err, callback)) return;
|
||||||
|
|
||||||
|
|
||||||
|
//check if this is a valid revision
|
||||||
|
if(rev > pad.getHeadRevisionNumber())
|
||||||
|
{
|
||||||
|
callback(new customError("rev is higher than the head revision of the pad","apierror"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pad.getInternalRevisionAText(rev, function(err, atext)
|
||||||
|
{
|
||||||
|
if(ERR(err, callback)) return;
|
||||||
|
|
||||||
|
var oldText = pad.text();
|
||||||
|
atext.text += "\n";
|
||||||
|
function eachAttribRun(attribs, func)
|
||||||
|
{
|
||||||
|
var attribsIter = Changeset.opIterator(attribs);
|
||||||
|
var textIndex = 0;
|
||||||
|
var newTextStart = 0;
|
||||||
|
var newTextEnd = atext.text.length;
|
||||||
|
while (attribsIter.hasNext())
|
||||||
|
{
|
||||||
|
var op = attribsIter.next();
|
||||||
|
var nextIndex = textIndex + op.chars;
|
||||||
|
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd))
|
||||||
|
{
|
||||||
|
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||||
|
}
|
||||||
|
textIndex = nextIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new changeset with a helper builder object
|
||||||
|
var builder = Changeset.builder(oldText.length);
|
||||||
|
|
||||||
|
// assemble each line into the builder
|
||||||
|
eachAttribRun(atext.attribs, function(start, end, attribs)
|
||||||
|
{
|
||||||
|
builder.insert(atext.text.substring(start, end), attribs);
|
||||||
|
});
|
||||||
|
|
||||||
|
var lastNewlinePos = oldText.lastIndexOf('\n');
|
||||||
|
if (lastNewlinePos < 0) {
|
||||||
|
builder.remove(oldText.length-1,0);
|
||||||
|
} else {
|
||||||
|
builder.remove(lastNewlinePos, oldText.match(/\n/g).length-1);
|
||||||
|
builder.remove(oldText.length - lastNewlinePos-1,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var changeset = builder.toString();
|
||||||
|
|
||||||
|
//append the changeset
|
||||||
|
pad.appendRevision(changeset);
|
||||||
|
//
|
||||||
|
callback(null, changeset);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||||
the destination will be overwritten if it exists.
|
the destination will be overwritten if it exists.
|
||||||
|
|
Loading…
Reference in New Issue