You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
17675 lines
722 KiB
JavaScript
17675 lines
722 KiB
JavaScript
var webodf_version = "0.4.2-2010-ge06842f";
|
|
function Runtime() {
|
|
}
|
|
Runtime.prototype.getVariable = function(name) {
|
|
};
|
|
Runtime.prototype.toJson = function(anything) {
|
|
};
|
|
Runtime.prototype.fromJson = function(jsonstr) {
|
|
};
|
|
Runtime.prototype.byteArrayFromString = function(string, encoding) {
|
|
};
|
|
Runtime.prototype.byteArrayToString = function(bytearray, encoding) {
|
|
};
|
|
Runtime.prototype.read = function(path, offset, length, callback) {
|
|
};
|
|
Runtime.prototype.readFile = function(path, encoding, callback) {
|
|
};
|
|
Runtime.prototype.readFileSync = function(path, encoding) {
|
|
};
|
|
Runtime.prototype.loadXML = function(path, callback) {
|
|
};
|
|
Runtime.prototype.writeFile = function(path, data, callback) {
|
|
};
|
|
Runtime.prototype.isFile = function(path, callback) {
|
|
};
|
|
Runtime.prototype.getFileSize = function(path, callback) {
|
|
};
|
|
Runtime.prototype.deleteFile = function(path, callback) {
|
|
};
|
|
Runtime.prototype.log = function(msgOrCategory, msg) {
|
|
};
|
|
Runtime.prototype.setTimeout = function(callback, milliseconds) {
|
|
};
|
|
Runtime.prototype.clearTimeout = function(timeoutID) {
|
|
};
|
|
Runtime.prototype.libraryPaths = function() {
|
|
};
|
|
Runtime.prototype.currentDirectory = function() {
|
|
};
|
|
Runtime.prototype.setCurrentDirectory = function(dir) {
|
|
};
|
|
Runtime.prototype.type = function() {
|
|
};
|
|
Runtime.prototype.getDOMImplementation = function() {
|
|
};
|
|
Runtime.prototype.parseXML = function(xml) {
|
|
};
|
|
Runtime.prototype.exit = function(exitCode) {
|
|
};
|
|
Runtime.prototype.getWindow = function() {
|
|
};
|
|
Runtime.prototype.requestAnimationFrame = function(callback) {
|
|
};
|
|
Runtime.prototype.cancelAnimationFrame = function(requestId) {
|
|
};
|
|
Runtime.prototype.assert = function(condition, message, callback) {
|
|
};
|
|
var IS_COMPILED_CODE = true;
|
|
Runtime.byteArrayToString = function(bytearray, encoding) {
|
|
function byteArrayToString(bytearray) {
|
|
var s = "", i, l = bytearray.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
s += String.fromCharCode(bytearray[i] & 255)
|
|
}
|
|
return s
|
|
}
|
|
function utf8ByteArrayToString(bytearray) {
|
|
var s = "", i, l = bytearray.length, chars = [], c0, c1, c2, c3, codepoint;
|
|
for(i = 0;i < l;i += 1) {
|
|
c0 = (bytearray[i]);
|
|
if(c0 < 128) {
|
|
chars.push(c0)
|
|
}else {
|
|
i += 1;
|
|
c1 = (bytearray[i]);
|
|
if(c0 >= 194 && c0 < 224) {
|
|
chars.push((c0 & 31) << 6 | c1 & 63)
|
|
}else {
|
|
i += 1;
|
|
c2 = (bytearray[i]);
|
|
if(c0 >= 224 && c0 < 240) {
|
|
chars.push((c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63)
|
|
}else {
|
|
i += 1;
|
|
c3 = (bytearray[i]);
|
|
if(c0 >= 240 && c0 < 245) {
|
|
codepoint = (c0 & 7) << 18 | (c1 & 63) << 12 | (c2 & 63) << 6 | c3 & 63;
|
|
codepoint -= 65536;
|
|
chars.push((codepoint >> 10) + 55296, (codepoint & 1023) + 56320)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(chars.length === 1E3) {
|
|
s += String.fromCharCode.apply(null, chars);
|
|
chars.length = 0
|
|
}
|
|
}
|
|
return s + String.fromCharCode.apply(null, chars)
|
|
}
|
|
var result;
|
|
if(encoding === "utf8") {
|
|
result = utf8ByteArrayToString(bytearray)
|
|
}else {
|
|
if(encoding !== "binary") {
|
|
this.log("Unsupported encoding: " + encoding)
|
|
}
|
|
result = byteArrayToString(bytearray)
|
|
}
|
|
return result
|
|
};
|
|
Runtime.getVariable = function(name) {
|
|
try {
|
|
return eval(name)
|
|
}catch(e) {
|
|
return undefined
|
|
}
|
|
};
|
|
Runtime.toJson = function(anything) {
|
|
return JSON.stringify(anything)
|
|
};
|
|
Runtime.fromJson = function(jsonstr) {
|
|
return JSON.parse(jsonstr)
|
|
};
|
|
Runtime.getFunctionName = function getFunctionName(f) {
|
|
var m;
|
|
if(f.name === undefined) {
|
|
m = (new RegExp("function\\s+(\\w+)")).exec(f);
|
|
return m && m[1]
|
|
}
|
|
return f.name
|
|
};
|
|
function BrowserRuntime(logoutput) {
|
|
var self = this, cache = {};
|
|
function getUtf8LengthForString(string) {
|
|
var l = string.length, i, n, j = 0;
|
|
for(i = 0;i < l;i += 1) {
|
|
n = string.charCodeAt(i);
|
|
j += 1 + (n > 128) + (n > 2048);
|
|
if(n > 55040 && n < 57344) {
|
|
j += 1;
|
|
i += 1
|
|
}
|
|
}
|
|
return j
|
|
}
|
|
function utf8ByteArrayFromString(string, length, addBOM) {
|
|
var l = string.length, bytearray, i, n, j;
|
|
bytearray = new Uint8Array(new ArrayBuffer(length));
|
|
if(addBOM) {
|
|
bytearray[0] = 239;
|
|
bytearray[1] = 187;
|
|
bytearray[2] = 191;
|
|
j = 3
|
|
}else {
|
|
j = 0
|
|
}
|
|
for(i = 0;i < l;i += 1) {
|
|
n = string.charCodeAt(i);
|
|
if(n < 128) {
|
|
bytearray[j] = n;
|
|
j += 1
|
|
}else {
|
|
if(n < 2048) {
|
|
bytearray[j] = 192 | n >>> 6;
|
|
bytearray[j + 1] = 128 | n & 63;
|
|
j += 2
|
|
}else {
|
|
if(n <= 55040 || n >= 57344) {
|
|
bytearray[j] = 224 | n >>> 12 & 15;
|
|
bytearray[j + 1] = 128 | n >>> 6 & 63;
|
|
bytearray[j + 2] = 128 | n & 63;
|
|
j += 3
|
|
}else {
|
|
i += 1;
|
|
n = (n - 55296 << 10 | string.charCodeAt(i) - 56320) + 65536;
|
|
bytearray[j] = 240 | n >>> 18 & 7;
|
|
bytearray[j + 1] = 128 | n >>> 12 & 63;
|
|
bytearray[j + 2] = 128 | n >>> 6 & 63;
|
|
bytearray[j + 3] = 128 | n & 63;
|
|
j += 4
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bytearray
|
|
}
|
|
function utf8ByteArrayFromXHRString(string, wishLength) {
|
|
var addBOM = false, length = getUtf8LengthForString(string);
|
|
if(typeof wishLength === "number") {
|
|
if(wishLength !== length && wishLength !== length + 3) {
|
|
return undefined
|
|
}
|
|
addBOM = length + 3 === wishLength;
|
|
length = wishLength
|
|
}
|
|
return utf8ByteArrayFromString(string, length, addBOM)
|
|
}
|
|
function byteArrayFromString(string) {
|
|
var l = string.length, a = new Uint8Array(new ArrayBuffer(l)), i;
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = string.charCodeAt(i) & 255
|
|
}
|
|
return a
|
|
}
|
|
this.byteArrayFromString = function(string, encoding) {
|
|
var result;
|
|
if(encoding === "utf8") {
|
|
result = utf8ByteArrayFromString(string, getUtf8LengthForString(string), false)
|
|
}else {
|
|
if(encoding !== "binary") {
|
|
self.log("unknown encoding: " + encoding)
|
|
}
|
|
result = byteArrayFromString(string)
|
|
}
|
|
return result
|
|
};
|
|
this.byteArrayToString = Runtime.byteArrayToString;
|
|
this.getVariable = Runtime.getVariable;
|
|
this.fromJson = Runtime.fromJson;
|
|
this.toJson = Runtime.toJson;
|
|
function log(msgOrCategory, msg) {
|
|
var node, doc, category;
|
|
if(msg !== undefined) {
|
|
category = msgOrCategory
|
|
}else {
|
|
msg = msgOrCategory
|
|
}
|
|
if(logoutput) {
|
|
doc = logoutput.ownerDocument;
|
|
if(category) {
|
|
node = doc.createElement("span");
|
|
node.className = category;
|
|
node.appendChild(doc.createTextNode(category));
|
|
logoutput.appendChild(node);
|
|
logoutput.appendChild(doc.createTextNode(" "))
|
|
}
|
|
node = doc.createElement("span");
|
|
if(msg.length > 0 && msg[0] === "<") {
|
|
node.innerHTML = msg
|
|
}else {
|
|
node.appendChild(doc.createTextNode(msg))
|
|
}
|
|
logoutput.appendChild(node);
|
|
logoutput.appendChild(doc.createElement("br"))
|
|
}else {
|
|
if(console) {
|
|
console.log(msg)
|
|
}
|
|
}
|
|
if(category === "alert") {
|
|
alert(msg)
|
|
}
|
|
}
|
|
function assert(condition, message, callback) {
|
|
if(!condition) {
|
|
log("alert", "ASSERTION FAILED:\n" + message);
|
|
if(callback) {
|
|
callback()
|
|
}
|
|
throw message;
|
|
}
|
|
}
|
|
function arrayToUint8Array(buffer) {
|
|
var l = buffer.length, i, a = new Uint8Array(new ArrayBuffer(l));
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = buffer[i]
|
|
}
|
|
return a
|
|
}
|
|
function stringToBinaryWorkaround(xhr) {
|
|
var cl, data;
|
|
cl = xhr.getResponseHeader("Content-Length");
|
|
if(cl) {
|
|
cl = parseInt(cl, 10)
|
|
}
|
|
if(cl && cl !== xhr.responseText.length) {
|
|
data = utf8ByteArrayFromXHRString(xhr.responseText, cl)
|
|
}
|
|
if(data === undefined) {
|
|
data = byteArrayFromString(xhr.responseText)
|
|
}
|
|
return data
|
|
}
|
|
function handleXHRResult(path, encoding, xhr) {
|
|
var r, d, a, data;
|
|
if(xhr.status === 0 && !xhr.responseText) {
|
|
r = {err:"File " + path + " is empty.", data:null}
|
|
}else {
|
|
if(xhr.status === 200 || xhr.status === 0) {
|
|
if(xhr.response && typeof xhr.response !== "string") {
|
|
if(encoding === "binary") {
|
|
d = (xhr.response);
|
|
data = new Uint8Array(d)
|
|
}else {
|
|
data = String(xhr.response)
|
|
}
|
|
}else {
|
|
if(encoding === "binary") {
|
|
if(xhr.responseBody !== null && String(typeof VBArray) !== "undefined") {
|
|
a = (new VBArray(xhr.responseBody)).toArray();
|
|
data = arrayToUint8Array(a)
|
|
}else {
|
|
data = stringToBinaryWorkaround(xhr)
|
|
}
|
|
}else {
|
|
data = xhr.responseText
|
|
}
|
|
}
|
|
cache[path] = data;
|
|
r = {err:null, data:data}
|
|
}else {
|
|
r = {err:xhr.responseText || xhr.statusText, data:null}
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
function createXHR(path, encoding, async) {
|
|
var xhr = new XMLHttpRequest;
|
|
xhr.open("GET", path, async);
|
|
if(xhr.overrideMimeType) {
|
|
if(encoding !== "binary") {
|
|
xhr.overrideMimeType("text/plain; charset=" + encoding)
|
|
}else {
|
|
xhr.overrideMimeType("text/plain; charset=x-user-defined")
|
|
}
|
|
}
|
|
return xhr
|
|
}
|
|
function readFile(path, encoding, callback) {
|
|
if(cache.hasOwnProperty(path)) {
|
|
callback(null, cache[path]);
|
|
return
|
|
}
|
|
var xhr = createXHR(path, encoding, true);
|
|
function handleResult() {
|
|
var r;
|
|
if(xhr.readyState === 4) {
|
|
r = handleXHRResult(path, encoding, xhr);
|
|
callback(r.err, r.data)
|
|
}
|
|
}
|
|
xhr.onreadystatechange = handleResult;
|
|
try {
|
|
xhr.send(null)
|
|
}catch(e) {
|
|
callback(e.message, null)
|
|
}
|
|
}
|
|
function read(path, offset, length, callback) {
|
|
readFile(path, "binary", function(err, result) {
|
|
var r = null;
|
|
if(result) {
|
|
if(typeof result === "string") {
|
|
throw"This should not happen.";
|
|
}
|
|
r = (result.subarray(offset, offset + length))
|
|
}
|
|
callback(err, r)
|
|
})
|
|
}
|
|
function readFileSync(path, encoding) {
|
|
var xhr = createXHR(path, encoding, false), r;
|
|
try {
|
|
xhr.send(null);
|
|
r = handleXHRResult(path, encoding, xhr);
|
|
if(r.err) {
|
|
throw r.err;
|
|
}
|
|
if(r.data === null) {
|
|
throw"No data read from " + path + ".";
|
|
}
|
|
}catch(e) {
|
|
throw e;
|
|
}
|
|
return r.data
|
|
}
|
|
function writeFile(path, data, callback) {
|
|
cache[path] = data;
|
|
var xhr = new XMLHttpRequest, d;
|
|
function handleResult() {
|
|
if(xhr.readyState === 4) {
|
|
if(xhr.status === 0 && !xhr.responseText) {
|
|
callback("File " + path + " is empty.")
|
|
}else {
|
|
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 0) {
|
|
callback(null)
|
|
}else {
|
|
callback("Status " + String(xhr.status) + ": " + xhr.responseText || xhr.statusText)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
xhr.open("PUT", path, true);
|
|
xhr.onreadystatechange = handleResult;
|
|
if(data.buffer && !xhr.sendAsBinary) {
|
|
d = data.buffer
|
|
}else {
|
|
d = self.byteArrayToString(data, "binary")
|
|
}
|
|
try {
|
|
if(xhr.sendAsBinary) {
|
|
xhr.sendAsBinary(d)
|
|
}else {
|
|
xhr.send(d)
|
|
}
|
|
}catch(e) {
|
|
self.log("HUH? " + e + " " + data);
|
|
callback(e.message)
|
|
}
|
|
}
|
|
function deleteFile(path, callback) {
|
|
delete cache[path];
|
|
var xhr = new XMLHttpRequest;
|
|
xhr.open("DELETE", path, true);
|
|
xhr.onreadystatechange = function() {
|
|
if(xhr.readyState === 4) {
|
|
if(xhr.status < 200 && xhr.status >= 300) {
|
|
callback(xhr.responseText)
|
|
}else {
|
|
callback(null)
|
|
}
|
|
}
|
|
};
|
|
xhr.send(null)
|
|
}
|
|
function loadXML(path, callback) {
|
|
var xhr = new XMLHttpRequest;
|
|
function handleResult() {
|
|
if(xhr.readyState === 4) {
|
|
if(xhr.status === 0 && !xhr.responseText) {
|
|
callback("File " + path + " is empty.", null)
|
|
}else {
|
|
if(xhr.status === 200 || xhr.status === 0) {
|
|
callback(null, xhr.responseXML)
|
|
}else {
|
|
callback(xhr.responseText, null)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
xhr.open("GET", path, true);
|
|
if(xhr.overrideMimeType) {
|
|
xhr.overrideMimeType("text/xml")
|
|
}
|
|
xhr.onreadystatechange = handleResult;
|
|
try {
|
|
xhr.send(null)
|
|
}catch(e) {
|
|
callback(e.message, null)
|
|
}
|
|
}
|
|
function isFile(path, callback) {
|
|
self.getFileSize(path, function(size) {
|
|
callback(size !== -1)
|
|
})
|
|
}
|
|
function getFileSize(path, callback) {
|
|
if(cache.hasOwnProperty(path) && typeof cache[path] !== "string") {
|
|
callback(cache[path].length);
|
|
return
|
|
}
|
|
var xhr = new XMLHttpRequest;
|
|
xhr.open("HEAD", path, true);
|
|
xhr.onreadystatechange = function() {
|
|
if(xhr.readyState !== 4) {
|
|
return
|
|
}
|
|
var cl = xhr.getResponseHeader("Content-Length");
|
|
if(cl) {
|
|
callback(parseInt(cl, 10))
|
|
}else {
|
|
readFile(path, "binary", function(err, data) {
|
|
if(!err) {
|
|
callback(data.length)
|
|
}else {
|
|
callback(-1)
|
|
}
|
|
})
|
|
}
|
|
};
|
|
xhr.send(null)
|
|
}
|
|
this.readFile = readFile;
|
|
this.read = read;
|
|
this.readFileSync = readFileSync;
|
|
this.writeFile = writeFile;
|
|
this.deleteFile = deleteFile;
|
|
this.loadXML = loadXML;
|
|
this.isFile = isFile;
|
|
this.getFileSize = getFileSize;
|
|
this.log = log;
|
|
this.assert = assert;
|
|
this.setTimeout = function(f, msec) {
|
|
return setTimeout(function() {
|
|
f()
|
|
}, msec)
|
|
};
|
|
this.clearTimeout = function(timeoutID) {
|
|
clearTimeout(timeoutID)
|
|
};
|
|
this.libraryPaths = function() {
|
|
return["lib"]
|
|
};
|
|
this.setCurrentDirectory = function() {
|
|
};
|
|
this.currentDirectory = function() {
|
|
return""
|
|
};
|
|
this.type = function() {
|
|
return"BrowserRuntime"
|
|
};
|
|
this.getDOMImplementation = function() {
|
|
return window.document.implementation
|
|
};
|
|
this.parseXML = function(xml) {
|
|
var parser = new DOMParser;
|
|
return parser.parseFromString(xml, "text/xml")
|
|
};
|
|
this.exit = function(exitCode) {
|
|
log("Calling exit with code " + String(exitCode) + ", but exit() is not implemented.")
|
|
};
|
|
this.getWindow = function() {
|
|
return window
|
|
};
|
|
this.requestAnimationFrame = function(callback) {
|
|
var rAF = window.requestAnimationFrame || (window.webkitRequestAnimationFrame || (window.mozRequestAnimationFrame || window.msRequestAnimationFrame)), requestId = 0;
|
|
if(rAF) {
|
|
rAF.bind(window);
|
|
requestId = (rAF)(callback)
|
|
}else {
|
|
return setTimeout(callback, 15)
|
|
}
|
|
return requestId
|
|
};
|
|
this.cancelAnimationFrame = function(requestId) {
|
|
var cAF = window.cancelAnimationFrame || (window.webkitCancelAnimationFrame || (window.mozCancelAnimationFrame || window.msCancelAnimationFrame));
|
|
if(cAF) {
|
|
cAF.bind(window);
|
|
(cAF)(requestId)
|
|
}else {
|
|
clearTimeout(requestId)
|
|
}
|
|
}
|
|
}
|
|
function NodeJSRuntime() {
|
|
var self = this, fs = require("fs"), pathmod = require("path"), currentDirectory = "", parser, domImplementation;
|
|
function bufferToUint8Array(buffer) {
|
|
var l = buffer.length, i, a = new Uint8Array(new ArrayBuffer(l));
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = buffer[i]
|
|
}
|
|
return a
|
|
}
|
|
this.byteArrayFromString = function(string, encoding) {
|
|
var buf = new Buffer(string, encoding), i, l = buf.length, a = new Uint8Array(new ArrayBuffer(l));
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = buf[i]
|
|
}
|
|
return a
|
|
};
|
|
this.byteArrayToString = Runtime.byteArrayToString;
|
|
this.getVariable = Runtime.getVariable;
|
|
this.fromJson = Runtime.fromJson;
|
|
this.toJson = Runtime.toJson;
|
|
function isFile(path, callback) {
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
fs.stat(path, function(err, stats) {
|
|
callback(!err && stats.isFile())
|
|
})
|
|
}
|
|
function readFile(path, encoding, callback) {
|
|
function convert(err, data) {
|
|
if(err) {
|
|
return callback(err, null)
|
|
}
|
|
if(!data) {
|
|
return callback("No data for " + path + ".", null)
|
|
}
|
|
var d;
|
|
if(typeof data === "string") {
|
|
d = (data);
|
|
return callback(err, d)
|
|
}
|
|
d = (data);
|
|
callback(err, bufferToUint8Array(d))
|
|
}
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
if(encoding !== "binary") {
|
|
fs.readFile(path, encoding, convert)
|
|
}else {
|
|
fs.readFile(path, null, convert)
|
|
}
|
|
}
|
|
this.readFile = readFile;
|
|
function loadXML(path, callback) {
|
|
readFile(path, "utf-8", function(err, data) {
|
|
if(err) {
|
|
return callback(err, null)
|
|
}
|
|
if(!data) {
|
|
return callback("No data for " + path + ".", null)
|
|
}
|
|
var d = (data);
|
|
callback(null, self.parseXML(d))
|
|
})
|
|
}
|
|
this.loadXML = loadXML;
|
|
this.writeFile = function(path, data, callback) {
|
|
var buf = new Buffer(data);
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
fs.writeFile(path, buf, "binary", function(err) {
|
|
callback(err || null)
|
|
})
|
|
};
|
|
this.deleteFile = function(path, callback) {
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
fs.unlink(path, callback)
|
|
};
|
|
this.read = function(path, offset, length, callback) {
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
fs.open(path, "r+", 666, function(err, fd) {
|
|
if(err) {
|
|
callback(err, null);
|
|
return
|
|
}
|
|
var buffer = new Buffer(length);
|
|
fs.read(fd, buffer, 0, length, offset, function(err) {
|
|
fs.close(fd);
|
|
callback(err, bufferToUint8Array(buffer))
|
|
})
|
|
})
|
|
};
|
|
this.readFileSync = function(path, encoding) {
|
|
var s, enc = encoding === "binary" ? null : encoding, r = fs.readFileSync(path, enc);
|
|
if(r === null) {
|
|
throw"File " + path + " could not be read.";
|
|
}
|
|
if(encoding === "binary") {
|
|
s = (r);
|
|
s = bufferToUint8Array(s)
|
|
}else {
|
|
s = (r)
|
|
}
|
|
return s
|
|
};
|
|
this.isFile = isFile;
|
|
this.getFileSize = function(path, callback) {
|
|
path = pathmod.resolve(currentDirectory, path);
|
|
fs.stat(path, function(err, stats) {
|
|
if(err) {
|
|
callback(-1)
|
|
}else {
|
|
callback(stats.size)
|
|
}
|
|
})
|
|
};
|
|
function log(msgOrCategory, msg) {
|
|
var category;
|
|
if(msg !== undefined) {
|
|
category = msgOrCategory
|
|
}else {
|
|
msg = msgOrCategory
|
|
}
|
|
if(category === "alert") {
|
|
process.stderr.write("\n!!!!! ALERT !!!!!" + "\n")
|
|
}
|
|
process.stderr.write(msg + "\n");
|
|
if(category === "alert") {
|
|
process.stderr.write("!!!!! ALERT !!!!!" + "\n")
|
|
}
|
|
}
|
|
this.log = log;
|
|
function assert(condition, message, callback) {
|
|
if(!condition) {
|
|
process.stderr.write("ASSERTION FAILED: " + message);
|
|
if(callback) {
|
|
callback()
|
|
}
|
|
}
|
|
}
|
|
this.assert = assert;
|
|
this.setTimeout = function(f, msec) {
|
|
return setTimeout(function() {
|
|
f()
|
|
}, msec)
|
|
};
|
|
this.clearTimeout = function(timeoutID) {
|
|
clearTimeout(timeoutID)
|
|
};
|
|
this.libraryPaths = function() {
|
|
return[__dirname]
|
|
};
|
|
this.setCurrentDirectory = function(dir) {
|
|
currentDirectory = dir
|
|
};
|
|
this.currentDirectory = function() {
|
|
return currentDirectory
|
|
};
|
|
this.type = function() {
|
|
return"NodeJSRuntime"
|
|
};
|
|
this.getDOMImplementation = function() {
|
|
return domImplementation
|
|
};
|
|
this.parseXML = function(xml) {
|
|
return parser.parseFromString(xml, "text/xml")
|
|
};
|
|
this.exit = process.exit;
|
|
this.getWindow = function() {
|
|
return null
|
|
};
|
|
this.requestAnimationFrame = function(callback) {
|
|
return setTimeout(callback, 15)
|
|
};
|
|
this.cancelAnimationFrame = function(requestId) {
|
|
clearTimeout(requestId)
|
|
};
|
|
function init() {
|
|
var DOMParser = require("xmldom").DOMParser;
|
|
parser = new DOMParser;
|
|
domImplementation = self.parseXML("<a/>").implementation
|
|
}
|
|
init()
|
|
}
|
|
function RhinoRuntime() {
|
|
var self = this, Packages = {}, dom = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(), builder, entityresolver, currentDirectory = "";
|
|
dom.setValidating(false);
|
|
dom.setNamespaceAware(true);
|
|
dom.setExpandEntityReferences(false);
|
|
dom.setSchema(null);
|
|
entityresolver = Packages.org.xml.sax.EntityResolver({resolveEntity:function(publicId, systemId) {
|
|
var file;
|
|
function open(path) {
|
|
var reader = new Packages.java.io.FileReader(path), source = new Packages.org.xml.sax.InputSource(reader);
|
|
return source
|
|
}
|
|
file = systemId;
|
|
return open(file)
|
|
}});
|
|
builder = dom.newDocumentBuilder();
|
|
builder.setEntityResolver(entityresolver);
|
|
this.byteArrayFromString = function(string, encoding) {
|
|
var i, l = string.length, a = new Uint8Array(new ArrayBuffer(l));
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = string.charCodeAt(i) & 255
|
|
}
|
|
return a
|
|
};
|
|
this.byteArrayToString = Runtime.byteArrayToString;
|
|
this.getVariable = Runtime.getVariable;
|
|
this.fromJson = Runtime.fromJson;
|
|
this.toJson = Runtime.toJson;
|
|
function loadXML(path, callback) {
|
|
var file = new Packages.java.io.File(path), xmlDocument = null;
|
|
try {
|
|
xmlDocument = builder.parse(file)
|
|
}catch(err) {
|
|
print(err);
|
|
return callback(err, null)
|
|
}
|
|
callback(null, xmlDocument)
|
|
}
|
|
function runtimeReadFile(path, encoding, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var file = new Packages.java.io.File(path), data, rhinoencoding = encoding === "binary" ? "latin1" : encoding;
|
|
if(!file.isFile()) {
|
|
callback(path + " is not a file.", null)
|
|
}else {
|
|
data = readFile(path, rhinoencoding);
|
|
if(data && encoding === "binary") {
|
|
data = self.byteArrayFromString(data, "binary")
|
|
}
|
|
callback(null, data)
|
|
}
|
|
}
|
|
function runtimeReadFileSync(path, encoding) {
|
|
var file = new Packages.java.io.File(path);
|
|
if(!file.isFile()) {
|
|
return null
|
|
}
|
|
if(encoding === "binary") {
|
|
encoding = "latin1"
|
|
}
|
|
return readFile(path, encoding)
|
|
}
|
|
function isFile(path, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var file = new Packages.java.io.File(path);
|
|
callback(file.isFile())
|
|
}
|
|
this.loadXML = loadXML;
|
|
this.readFile = runtimeReadFile;
|
|
this.writeFile = function(path, data, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var out = new Packages.java.io.FileOutputStream(path), i, l = data.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
out.write(data[i])
|
|
}
|
|
out.close();
|
|
callback(null)
|
|
};
|
|
this.deleteFile = function(path, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var file = new Packages.java.io.File(path), otherPath = path + Math.random(), other = new Packages.java.io.File(otherPath);
|
|
if(file.rename(other)) {
|
|
other.deleteOnExit();
|
|
callback(null)
|
|
}else {
|
|
callback("Could not delete " + path)
|
|
}
|
|
};
|
|
this.read = function(path, offset, length, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var data = runtimeReadFileSync(path, "binary");
|
|
if(data) {
|
|
callback(null, this.byteArrayFromString(data.substring(offset, offset + length), "binary"))
|
|
}else {
|
|
callback("Cannot read " + path, null)
|
|
}
|
|
};
|
|
this.readFileSync = function(path, encoding) {
|
|
if(!encoding) {
|
|
return""
|
|
}
|
|
var s = readFile(path, encoding);
|
|
if(s === null) {
|
|
throw"File could not be read.";
|
|
}
|
|
return s
|
|
};
|
|
this.isFile = isFile;
|
|
this.getFileSize = function(path, callback) {
|
|
if(currentDirectory) {
|
|
path = currentDirectory + "/" + path
|
|
}
|
|
var file = new Packages.java.io.File(path);
|
|
callback(file.length())
|
|
};
|
|
function log(msgOrCategory, msg) {
|
|
var category;
|
|
if(msg !== undefined) {
|
|
category = msgOrCategory
|
|
}else {
|
|
msg = msgOrCategory
|
|
}
|
|
if(category === "alert") {
|
|
print("\n!!!!! ALERT !!!!!")
|
|
}
|
|
print(msg);
|
|
if(category === "alert") {
|
|
print("!!!!! ALERT !!!!!")
|
|
}
|
|
}
|
|
this.log = log;
|
|
function assert(condition, message, callback) {
|
|
if(!condition) {
|
|
log("alert", "ASSERTION FAILED: " + message);
|
|
if(callback) {
|
|
callback()
|
|
}
|
|
}
|
|
}
|
|
this.assert = assert;
|
|
this.setTimeout = function(f) {
|
|
f();
|
|
return 0
|
|
};
|
|
this.clearTimeout = function() {
|
|
};
|
|
this.libraryPaths = function() {
|
|
return["lib"]
|
|
};
|
|
this.setCurrentDirectory = function(dir) {
|
|
currentDirectory = dir
|
|
};
|
|
this.currentDirectory = function() {
|
|
return currentDirectory
|
|
};
|
|
this.type = function() {
|
|
return"RhinoRuntime"
|
|
};
|
|
this.getDOMImplementation = function() {
|
|
return builder.getDOMImplementation()
|
|
};
|
|
this.parseXML = function(xml) {
|
|
var reader = new Packages.java.io.StringReader(xml), source = new Packages.org.xml.sax.InputSource(reader);
|
|
return builder.parse(source)
|
|
};
|
|
this.exit = quit;
|
|
this.getWindow = function() {
|
|
return null
|
|
};
|
|
this.requestAnimationFrame = function(callback) {
|
|
callback();
|
|
return 0
|
|
};
|
|
this.cancelAnimationFrame = function() {
|
|
}
|
|
}
|
|
Runtime.create = function create() {
|
|
var result;
|
|
if(String(typeof window) !== "undefined") {
|
|
result = new BrowserRuntime(window.document.getElementById("logoutput"))
|
|
}else {
|
|
if(String(typeof require) !== "undefined") {
|
|
result = new NodeJSRuntime
|
|
}else {
|
|
result = new RhinoRuntime
|
|
}
|
|
}
|
|
return result
|
|
};
|
|
var runtime = Runtime.create();
|
|
var core = {};
|
|
var gui = {};
|
|
var xmldom = {};
|
|
var odf = {};
|
|
var ops = {};
|
|
(function() {
|
|
function loadDependenciesFromManifest(dir, dependencies, expectFail) {
|
|
var path = dir + "/manifest.json", content, list, manifest, m;
|
|
runtime.log("Loading manifest: " + path);
|
|
try {
|
|
content = runtime.readFileSync(path, "utf-8")
|
|
}catch(e) {
|
|
if(expectFail) {
|
|
runtime.log("No loadable manifest found.")
|
|
}else {
|
|
console.log(String(e));
|
|
throw e;
|
|
}
|
|
return
|
|
}
|
|
list = JSON.parse((content));
|
|
manifest = (list);
|
|
for(m in manifest) {
|
|
if(manifest.hasOwnProperty(m)) {
|
|
dependencies[m] = {dir:dir, deps:manifest[m]}
|
|
}
|
|
}
|
|
}
|
|
function loadDependenciesFromManifests() {
|
|
var dependencies = [], paths = runtime.libraryPaths(), i;
|
|
if(runtime.currentDirectory() && paths.indexOf(runtime.currentDirectory()) === -1) {
|
|
loadDependenciesFromManifest(runtime.currentDirectory(), dependencies, true)
|
|
}
|
|
for(i = 0;i < paths.length;i += 1) {
|
|
loadDependenciesFromManifest(paths[i], dependencies)
|
|
}
|
|
return dependencies
|
|
}
|
|
function getPath(dir, className) {
|
|
return dir + "/" + className.replace(".", "/") + ".js"
|
|
}
|
|
function getLoadList(classNames, dependencies, isDefined) {
|
|
var loadList = [], stack = {}, visited = {};
|
|
function visit(n) {
|
|
if(visited[n] || isDefined(n)) {
|
|
return
|
|
}
|
|
if(stack[n]) {
|
|
throw"Circular dependency detected for " + n + ".";
|
|
}
|
|
stack[n] = true;
|
|
if(!dependencies[n]) {
|
|
throw"Missing dependency information for class " + n + ".";
|
|
}
|
|
var d = dependencies[n], deps = d.deps, i, l = deps.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
visit(deps[i])
|
|
}
|
|
stack[n] = false;
|
|
visited[n] = true;
|
|
loadList.push(getPath(d.dir, n))
|
|
}
|
|
classNames.forEach(visit);
|
|
return loadList
|
|
}
|
|
function addContent(path, content) {
|
|
content += "\n//# sourceURL=" + path;
|
|
content += "\n//@ sourceURL=" + path;
|
|
return content
|
|
}
|
|
function loadFiles(paths) {
|
|
var i, content;
|
|
for(i = 0;i < paths.length;i += 1) {
|
|
content = runtime.readFileSync(paths[i], "utf-8");
|
|
content = addContent(paths[i], (content));
|
|
eval(content)
|
|
}
|
|
}
|
|
function loadFilesInBrowser(paths, callback) {
|
|
var e = document.currentScript || document.documentElement.lastChild, df = document.createDocumentFragment(), script, i;
|
|
for(i = 0;i < paths.length;i += 1) {
|
|
script = document.createElement("script");
|
|
script.type = "text/javascript";
|
|
script.charset = "utf-8";
|
|
script.async = false;
|
|
script.setAttribute("src", paths[i]);
|
|
df.appendChild(script)
|
|
}
|
|
if(callback) {
|
|
script.onload = callback
|
|
}
|
|
e.parentNode.insertBefore(df, e)
|
|
}
|
|
var dependencies, packages = {core:core, gui:gui, xmldom:xmldom, odf:odf, ops:ops};
|
|
function isDefined(classname) {
|
|
var parts = classname.split("."), i, p = packages, l = parts.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
if(!p.hasOwnProperty(parts[i])) {
|
|
return false
|
|
}
|
|
p = (p[parts[i]])
|
|
}
|
|
return true
|
|
}
|
|
runtime.loadClasses = function(classnames, callback) {
|
|
if(IS_COMPILED_CODE || classnames.length === 0) {
|
|
return callback && callback()
|
|
}
|
|
dependencies = dependencies || loadDependenciesFromManifests();
|
|
classnames = getLoadList(classnames, dependencies, isDefined);
|
|
if(classnames.length === 0) {
|
|
return callback && callback()
|
|
}
|
|
if(runtime.type() === "BrowserRuntime" && callback) {
|
|
loadFilesInBrowser(classnames, callback)
|
|
}else {
|
|
loadFiles(classnames);
|
|
if(callback) {
|
|
callback()
|
|
}
|
|
}
|
|
};
|
|
runtime.loadClass = function(classname, callback) {
|
|
runtime.loadClasses([classname], callback)
|
|
}
|
|
})();
|
|
(function() {
|
|
var translator = function(string) {
|
|
return string
|
|
};
|
|
function tr(original) {
|
|
var result = translator(original);
|
|
if(!result || String(typeof result) !== "string") {
|
|
return original
|
|
}
|
|
return result
|
|
}
|
|
runtime.getTranslator = function() {
|
|
return translator
|
|
};
|
|
runtime.setTranslator = function(translatorFunction) {
|
|
translator = translatorFunction
|
|
};
|
|
runtime.tr = tr
|
|
})();
|
|
(function(args) {
|
|
if(args) {
|
|
args = Array.prototype.slice.call((args))
|
|
}else {
|
|
args = []
|
|
}
|
|
function run(argv) {
|
|
if(!argv.length) {
|
|
return
|
|
}
|
|
var script = argv[0];
|
|
runtime.readFile(script, "utf8", function(err, code) {
|
|
var path = "", pathEndIndex = script.lastIndexOf("/"), codestring = (code);
|
|
if(pathEndIndex !== -1) {
|
|
path = script.substring(0, pathEndIndex)
|
|
}else {
|
|
path = "."
|
|
}
|
|
runtime.setCurrentDirectory(path);
|
|
function inner_run() {
|
|
var script, path, args, argv, result;
|
|
result = (eval(codestring));
|
|
if(result) {
|
|
runtime.exit(result)
|
|
}
|
|
return
|
|
}
|
|
if(err) {
|
|
runtime.log(err);
|
|
runtime.exit(1)
|
|
}else {
|
|
if(codestring === null) {
|
|
runtime.log("No code found for " + script);
|
|
runtime.exit(1)
|
|
}else {
|
|
inner_run.apply(null, argv)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if(runtime.type() === "NodeJSRuntime") {
|
|
run(process.argv.slice(2))
|
|
}else {
|
|
if(runtime.type() === "RhinoRuntime") {
|
|
run(args)
|
|
}else {
|
|
run(args.slice(1))
|
|
}
|
|
}
|
|
})(String(typeof arguments) !== "undefined" && arguments);
|
|
core.Async = function Async() {
|
|
this.forEach = function(items, f, callback) {
|
|
var i, l = items.length, itemsDone = 0;
|
|
function end(err) {
|
|
if(itemsDone !== l) {
|
|
if(err) {
|
|
itemsDone = l;
|
|
callback(err)
|
|
}else {
|
|
itemsDone += 1;
|
|
if(itemsDone === l) {
|
|
callback(null)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(i = 0;i < l;i += 1) {
|
|
f(items[i], end)
|
|
}
|
|
};
|
|
this.destroyAll = function(items, callback) {
|
|
function destroy(itemIndex, err) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
if(itemIndex < items.length) {
|
|
items[itemIndex](function(err) {
|
|
destroy(itemIndex + 1, err)
|
|
})
|
|
}else {
|
|
callback()
|
|
}
|
|
}
|
|
}
|
|
destroy(0, undefined)
|
|
}
|
|
};
|
|
function makeBase64() {
|
|
function makeB64tab(bin) {
|
|
var t = {}, i, l;
|
|
for(i = 0, l = bin.length;i < l;i += 1) {
|
|
t[bin.charAt(i)] = i
|
|
}
|
|
return t
|
|
}
|
|
var b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", b64tab = makeB64tab(b64chars), convertUTF16StringToBase64, convertBase64ToUTF16String, window = runtime.getWindow(), btoa, atob;
|
|
function stringToArray(s) {
|
|
var i, l = s.length, a = new Uint8Array(new ArrayBuffer(l));
|
|
for(i = 0;i < l;i += 1) {
|
|
a[i] = s.charCodeAt(i) & 255
|
|
}
|
|
return a
|
|
}
|
|
function convertUTF8ArrayToBase64(bin) {
|
|
var n, b64 = "", i, l = bin.length - 2;
|
|
for(i = 0;i < l;i += 3) {
|
|
n = bin[i] << 16 | bin[i + 1] << 8 | bin[i + 2];
|
|
b64 += (b64chars[n >>> 18]);
|
|
b64 += (b64chars[n >>> 12 & 63]);
|
|
b64 += (b64chars[n >>> 6 & 63]);
|
|
b64 += (b64chars[n & 63])
|
|
}
|
|
if(i === l + 1) {
|
|
n = bin[i] << 4;
|
|
b64 += (b64chars[n >>> 6]);
|
|
b64 += (b64chars[n & 63]);
|
|
b64 += "=="
|
|
}else {
|
|
if(i === l) {
|
|
n = bin[i] << 10 | bin[i + 1] << 2;
|
|
b64 += (b64chars[n >>> 12]);
|
|
b64 += (b64chars[n >>> 6 & 63]);
|
|
b64 += (b64chars[n & 63]);
|
|
b64 += "="
|
|
}
|
|
}
|
|
return b64
|
|
}
|
|
function convertBase64ToUTF8Array(b64) {
|
|
b64 = b64.replace(/[^A-Za-z0-9+\/]+/g, "");
|
|
var l = b64.length, bin = new Uint8Array(new ArrayBuffer(3 * l)), padlen = b64.length % 4, o = 0, i, n;
|
|
for(i = 0;i < l;i += 4) {
|
|
n = (b64tab[b64.charAt(i)] || 0) << 18 | (b64tab[b64.charAt(i + 1)] || 0) << 12 | (b64tab[b64.charAt(i + 2)] || 0) << 6 | (b64tab[b64.charAt(i + 3)] || 0);
|
|
bin[o] = n >> 16;
|
|
bin[o + 1] = n >> 8 & 255;
|
|
bin[o + 2] = n & 255;
|
|
o += 3
|
|
}
|
|
l = 3 * l - [0, 0, 2, 1][padlen];
|
|
return bin.subarray(0, l)
|
|
}
|
|
function convertUTF16ArrayToUTF8Array(uni) {
|
|
var i, n, l = uni.length, o = 0, bin = new Uint8Array(new ArrayBuffer(3 * l));
|
|
for(i = 0;i < l;i += 1) {
|
|
n = (uni[i]);
|
|
if(n < 128) {
|
|
bin[o++] = n
|
|
}else {
|
|
if(n < 2048) {
|
|
bin[o++] = 192 | n >>> 6;
|
|
bin[o++] = 128 | n & 63
|
|
}else {
|
|
bin[o++] = 224 | n >>> 12 & 15;
|
|
bin[o++] = 128 | n >>> 6 & 63;
|
|
bin[o++] = 128 | n & 63
|
|
}
|
|
}
|
|
}
|
|
return bin.subarray(0, o)
|
|
}
|
|
function convertUTF8ArrayToUTF16Array(bin) {
|
|
var i, c0, c1, c2, l = bin.length, uni = new Uint8Array(new ArrayBuffer(l)), o = 0;
|
|
for(i = 0;i < l;i += 1) {
|
|
c0 = (bin[i]);
|
|
if(c0 < 128) {
|
|
uni[o++] = c0
|
|
}else {
|
|
i += 1;
|
|
c1 = (bin[i]);
|
|
if(c0 < 224) {
|
|
uni[o++] = (c0 & 31) << 6 | c1 & 63
|
|
}else {
|
|
i += 1;
|
|
c2 = (bin[i]);
|
|
uni[o++] = (c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63
|
|
}
|
|
}
|
|
}
|
|
return uni.subarray(0, o)
|
|
}
|
|
function convertUTF8StringToBase64(bin) {
|
|
return convertUTF8ArrayToBase64(stringToArray(bin))
|
|
}
|
|
function convertBase64ToUTF8String(b64) {
|
|
return String.fromCharCode.apply(String, convertBase64ToUTF8Array(b64))
|
|
}
|
|
function convertUTF8StringToUTF16Array(bin) {
|
|
return convertUTF8ArrayToUTF16Array(stringToArray(bin))
|
|
}
|
|
function convertUTF8ArrayToUTF16String(bin) {
|
|
var b = convertUTF8ArrayToUTF16Array(bin), r = "", i = 0, chunksize = 45E3;
|
|
while(i < b.length) {
|
|
r += String.fromCharCode.apply(String, b.subarray(i, i + chunksize));
|
|
i += chunksize
|
|
}
|
|
return r
|
|
}
|
|
function convertUTF8StringToUTF16String_internal(bin, i, end) {
|
|
var c0, c1, c2, j, str = "";
|
|
for(j = i;j < end;j += 1) {
|
|
c0 = bin.charCodeAt(j) & 255;
|
|
if(c0 < 128) {
|
|
str += String.fromCharCode(c0)
|
|
}else {
|
|
j += 1;
|
|
c1 = bin.charCodeAt(j) & 255;
|
|
if(c0 < 224) {
|
|
str += String.fromCharCode((c0 & 31) << 6 | c1 & 63)
|
|
}else {
|
|
j += 1;
|
|
c2 = bin.charCodeAt(j) & 255;
|
|
str += String.fromCharCode((c0 & 15) << 12 | (c1 & 63) << 6 | c2 & 63)
|
|
}
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
function convertUTF8StringToUTF16String(bin, callback) {
|
|
var partsize = 1E5, str = "", pos = 0;
|
|
if(bin.length < partsize) {
|
|
callback(convertUTF8StringToUTF16String_internal(bin, 0, bin.length), true);
|
|
return
|
|
}
|
|
if(typeof bin !== "string") {
|
|
bin = bin.slice()
|
|
}
|
|
function f() {
|
|
var end = pos + partsize;
|
|
if(end > bin.length) {
|
|
end = bin.length
|
|
}
|
|
str += convertUTF8StringToUTF16String_internal(bin, pos, end);
|
|
pos = end;
|
|
end = pos === bin.length;
|
|
if(callback(str, end) && !end) {
|
|
runtime.setTimeout(f, 0)
|
|
}
|
|
}
|
|
f()
|
|
}
|
|
function convertUTF16StringToUTF8Array(uni) {
|
|
return convertUTF16ArrayToUTF8Array(stringToArray(uni))
|
|
}
|
|
function convertUTF16ArrayToUTF8String(uni) {
|
|
return String.fromCharCode.apply(String, convertUTF16ArrayToUTF8Array(uni))
|
|
}
|
|
function convertUTF16StringToUTF8String(uni) {
|
|
return String.fromCharCode.apply(String, convertUTF16ArrayToUTF8Array(stringToArray(uni)))
|
|
}
|
|
if(window && window.btoa) {
|
|
btoa = window.btoa;
|
|
convertUTF16StringToBase64 = function(uni) {
|
|
return btoa(convertUTF16StringToUTF8String(uni))
|
|
}
|
|
}else {
|
|
btoa = convertUTF8StringToBase64;
|
|
convertUTF16StringToBase64 = function(uni) {
|
|
return convertUTF8ArrayToBase64(convertUTF16StringToUTF8Array(uni))
|
|
}
|
|
}
|
|
if(window && window.atob) {
|
|
atob = window.atob;
|
|
convertBase64ToUTF16String = function(b64) {
|
|
var b = atob(b64);
|
|
return convertUTF8StringToUTF16String_internal(b, 0, b.length)
|
|
}
|
|
}else {
|
|
atob = convertBase64ToUTF8String;
|
|
convertBase64ToUTF16String = function(b64) {
|
|
return convertUTF8ArrayToUTF16String(convertBase64ToUTF8Array(b64))
|
|
}
|
|
}
|
|
core.Base64 = function Base64() {
|
|
this.convertUTF8ArrayToBase64 = convertUTF8ArrayToBase64;
|
|
this.convertByteArrayToBase64 = convertUTF8ArrayToBase64;
|
|
this.convertBase64ToUTF8Array = convertBase64ToUTF8Array;
|
|
this.convertBase64ToByteArray = convertBase64ToUTF8Array;
|
|
this.convertUTF16ArrayToUTF8Array = convertUTF16ArrayToUTF8Array;
|
|
this.convertUTF16ArrayToByteArray = convertUTF16ArrayToUTF8Array;
|
|
this.convertUTF8ArrayToUTF16Array = convertUTF8ArrayToUTF16Array;
|
|
this.convertByteArrayToUTF16Array = convertUTF8ArrayToUTF16Array;
|
|
this.convertUTF8StringToBase64 = convertUTF8StringToBase64;
|
|
this.convertBase64ToUTF8String = convertBase64ToUTF8String;
|
|
this.convertUTF8StringToUTF16Array = convertUTF8StringToUTF16Array;
|
|
this.convertUTF8ArrayToUTF16String = convertUTF8ArrayToUTF16String;
|
|
this.convertByteArrayToUTF16String = convertUTF8ArrayToUTF16String;
|
|
this.convertUTF8StringToUTF16String = convertUTF8StringToUTF16String;
|
|
this.convertUTF16StringToUTF8Array = convertUTF16StringToUTF8Array;
|
|
this.convertUTF16StringToByteArray = convertUTF16StringToUTF8Array;
|
|
this.convertUTF16ArrayToUTF8String = convertUTF16ArrayToUTF8String;
|
|
this.convertUTF16StringToUTF8String = convertUTF16StringToUTF8String;
|
|
this.convertUTF16StringToBase64 = convertUTF16StringToBase64;
|
|
this.convertBase64ToUTF16String = convertBase64ToUTF16String;
|
|
this.fromBase64 = convertBase64ToUTF8String;
|
|
this.toBase64 = convertUTF8StringToBase64;
|
|
this.atob = atob;
|
|
this.btoa = btoa;
|
|
this.utob = convertUTF16StringToUTF8String;
|
|
this.btou = convertUTF8StringToUTF16String;
|
|
this.encode = convertUTF16StringToBase64;
|
|
this.encodeURI = function(u) {
|
|
return convertUTF16StringToBase64(u).replace(/[+\/]/g, function(m0) {
|
|
return m0 === "+" ? "-" : "_"
|
|
}).replace(/\\=+$/, "")
|
|
};
|
|
this.decode = function(a) {
|
|
return convertBase64ToUTF16String(a.replace(/[\-_]/g, function(m0) {
|
|
return m0 === "-" ? "+" : "/"
|
|
}))
|
|
};
|
|
return this
|
|
};
|
|
return core.Base64
|
|
}
|
|
core.Base64 = makeBase64();
|
|
core.ByteArray = function ByteArray(data) {
|
|
this.pos = 0;
|
|
this.data = data;
|
|
this.readUInt32LE = function() {
|
|
this.pos += 4;
|
|
var d = this.data, pos = this.pos;
|
|
return d[--pos] << 24 | d[--pos] << 16 | d[--pos] << 8 | d[--pos]
|
|
};
|
|
this.readUInt16LE = function() {
|
|
this.pos += 2;
|
|
var d = this.data, pos = this.pos;
|
|
return d[--pos] << 8 | d[--pos]
|
|
}
|
|
};
|
|
core.ByteArrayWriter = function ByteArrayWriter(encoding) {
|
|
var self = this, length = 0, bufferSize = 1024, data = new Uint8Array(new ArrayBuffer(bufferSize));
|
|
function expand(extraLength) {
|
|
var newData;
|
|
if(extraLength > bufferSize - length) {
|
|
bufferSize = Math.max(2 * bufferSize, length + extraLength);
|
|
newData = new Uint8Array(new ArrayBuffer(bufferSize));
|
|
newData.set(data);
|
|
data = newData
|
|
}
|
|
}
|
|
this.appendByteArrayWriter = function(writer) {
|
|
self.appendByteArray(writer.getByteArray())
|
|
};
|
|
this.appendByteArray = function(array) {
|
|
var l = array.length;
|
|
expand(l);
|
|
data.set(array, length);
|
|
length += l
|
|
};
|
|
this.appendArray = function(array) {
|
|
var l = array.length;
|
|
expand(l);
|
|
data.set(array, length);
|
|
length += l
|
|
};
|
|
this.appendUInt16LE = function(value) {
|
|
self.appendArray([value & 255, value >> 8 & 255])
|
|
};
|
|
this.appendUInt32LE = function(value) {
|
|
self.appendArray([value & 255, value >> 8 & 255, value >> 16 & 255, value >> 24 & 255])
|
|
};
|
|
this.appendString = function(string) {
|
|
self.appendByteArray(runtime.byteArrayFromString(string, encoding))
|
|
};
|
|
this.getLength = function() {
|
|
return length
|
|
};
|
|
this.getByteArray = function() {
|
|
var a = new Uint8Array(new ArrayBuffer(length));
|
|
a.set(data.subarray(0, length));
|
|
return a
|
|
}
|
|
};
|
|
core.CSSUnits = function CSSUnits() {
|
|
var self = this, sizemap = {"in":1, "cm":2.54, "mm":25.4, "pt":72, "pc":12};
|
|
this.convert = function(value, oldUnit, newUnit) {
|
|
return value * sizemap[newUnit] / sizemap[oldUnit]
|
|
};
|
|
this.convertMeasure = function(measure, newUnit) {
|
|
var value, oldUnit, newMeasure;
|
|
if(measure && newUnit) {
|
|
value = parseFloat(measure);
|
|
oldUnit = measure.replace(value.toString(), "");
|
|
newMeasure = self.convert(value, oldUnit, newUnit).toString()
|
|
}else {
|
|
newMeasure = ""
|
|
}
|
|
return newMeasure
|
|
};
|
|
this.getUnits = function(measure) {
|
|
return measure.substr(measure.length - 2, measure.length)
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
var browserQuirks;
|
|
function getBrowserQuirks() {
|
|
var range, directBoundingRect, rangeBoundingRect, testContainer, testElement, detectedQuirks, window, document, docElement, body, docOverflow, bodyOverflow, bodyHeight, bodyScroll;
|
|
if(browserQuirks === undefined) {
|
|
window = runtime.getWindow();
|
|
document = window && window.document;
|
|
docElement = document.documentElement;
|
|
body = document.body;
|
|
browserQuirks = {rangeBCRIgnoresElementBCR:false, unscaledRangeClientRects:false, elementBCRIgnoresBodyScroll:false};
|
|
if(document) {
|
|
testContainer = document.createElement("div");
|
|
testContainer.style.position = "absolute";
|
|
testContainer.style.left = "-99999px";
|
|
testContainer.style.transform = "scale(2)";
|
|
testContainer.style["-webkit-transform"] = "scale(2)";
|
|
testElement = document.createElement("div");
|
|
testContainer.appendChild(testElement);
|
|
body.appendChild(testContainer);
|
|
range = document.createRange();
|
|
range.selectNode(testElement);
|
|
browserQuirks.rangeBCRIgnoresElementBCR = range.getClientRects().length === 0;
|
|
testElement.appendChild(document.createTextNode("Rect transform test"));
|
|
directBoundingRect = testElement.getBoundingClientRect();
|
|
rangeBoundingRect = range.getBoundingClientRect();
|
|
browserQuirks.unscaledRangeClientRects = Math.abs(directBoundingRect.height - rangeBoundingRect.height) > 2;
|
|
testContainer.style.transform = "";
|
|
testContainer.style["-webkit-transform"] = "";
|
|
docOverflow = docElement.style.overflow;
|
|
bodyOverflow = body.style.overflow;
|
|
bodyHeight = body.style.height;
|
|
bodyScroll = body.scrollTop;
|
|
docElement.style.overflow = "visible";
|
|
body.style.overflow = "visible";
|
|
body.style.height = "200%";
|
|
body.scrollTop = body.scrollHeight;
|
|
browserQuirks.elementBCRIgnoresBodyScroll = range.getBoundingClientRect().top !== testElement.getBoundingClientRect().top;
|
|
body.scrollTop = bodyScroll;
|
|
body.style.height = bodyHeight;
|
|
body.style.overflow = bodyOverflow;
|
|
docElement.style.overflow = docOverflow;
|
|
range.detach();
|
|
body.removeChild(testContainer);
|
|
detectedQuirks = Object.keys(browserQuirks).map(function(quirk) {
|
|
return quirk + ":" + String(browserQuirks[quirk])
|
|
}).join(", ");
|
|
runtime.log("Detected browser quirks - " + detectedQuirks)
|
|
}
|
|
}
|
|
return browserQuirks
|
|
}
|
|
core.DomUtils = function DomUtils() {
|
|
var sharedRange = null;
|
|
function getSharedRange(doc) {
|
|
var range;
|
|
if(sharedRange) {
|
|
range = sharedRange
|
|
}else {
|
|
sharedRange = range = (doc.createRange())
|
|
}
|
|
return range
|
|
}
|
|
function findStablePoint(container, offset) {
|
|
var c = container;
|
|
if(offset < c.childNodes.length) {
|
|
c = c.childNodes.item(offset);
|
|
offset = 0;
|
|
while(c.firstChild) {
|
|
c = c.firstChild
|
|
}
|
|
}else {
|
|
while(c.lastChild) {
|
|
c = c.lastChild;
|
|
offset = c.nodeType === Node.TEXT_NODE ? c.textContent.length : c.childNodes.length
|
|
}
|
|
}
|
|
return{container:c, offset:offset}
|
|
}
|
|
function getPositionInContainingNode(node, container) {
|
|
var offset = 0, n;
|
|
while(node.parentNode !== container) {
|
|
runtime.assert(node.parentNode !== null, "parent is null");
|
|
node = (node.parentNode)
|
|
}
|
|
n = container.firstChild;
|
|
while(n !== node) {
|
|
offset += 1;
|
|
n = n.nextSibling
|
|
}
|
|
return offset
|
|
}
|
|
function splitBoundaries(range) {
|
|
var modifiedNodes = [], originalEndContainer, resetToContainerLength, end, splitStart, node, text, offset;
|
|
if(range.startContainer.nodeType === Node.TEXT_NODE || range.endContainer.nodeType === Node.TEXT_NODE) {
|
|
originalEndContainer = range.endContainer;
|
|
resetToContainerLength = range.endContainer.nodeType !== Node.TEXT_NODE ? range.endOffset === range.endContainer.childNodes.length : false;
|
|
end = findStablePoint(range.endContainer, range.endOffset);
|
|
if(end.container === originalEndContainer) {
|
|
originalEndContainer = null
|
|
}
|
|
range.setEnd(end.container, end.offset);
|
|
node = range.endContainer;
|
|
if(range.endOffset !== 0 && node.nodeType === Node.TEXT_NODE) {
|
|
text = (node);
|
|
if(range.endOffset !== text.length) {
|
|
modifiedNodes.push(text.splitText(range.endOffset));
|
|
modifiedNodes.push(text)
|
|
}
|
|
}
|
|
node = range.startContainer;
|
|
if(range.startOffset !== 0 && node.nodeType === Node.TEXT_NODE) {
|
|
text = (node);
|
|
if(range.startOffset !== text.length) {
|
|
splitStart = text.splitText(range.startOffset);
|
|
modifiedNodes.push(text);
|
|
modifiedNodes.push(splitStart);
|
|
range.setStart(splitStart, 0)
|
|
}
|
|
}
|
|
if(originalEndContainer !== null) {
|
|
node = range.endContainer;
|
|
while(node.parentNode && node.parentNode !== originalEndContainer) {
|
|
node = node.parentNode
|
|
}
|
|
if(resetToContainerLength) {
|
|
offset = originalEndContainer.childNodes.length
|
|
}else {
|
|
offset = getPositionInContainingNode(node, originalEndContainer)
|
|
}
|
|
range.setEnd(originalEndContainer, offset)
|
|
}
|
|
}
|
|
return modifiedNodes
|
|
}
|
|
this.splitBoundaries = splitBoundaries;
|
|
function containsRange(container, insideRange) {
|
|
return container.compareBoundaryPoints(Range.START_TO_START, insideRange) <= 0 && container.compareBoundaryPoints(Range.END_TO_END, insideRange) >= 0
|
|
}
|
|
this.containsRange = containsRange;
|
|
function rangesIntersect(range1, range2) {
|
|
return range1.compareBoundaryPoints(Range.END_TO_START, range2) <= 0 && range1.compareBoundaryPoints(Range.START_TO_END, range2) >= 0
|
|
}
|
|
this.rangesIntersect = rangesIntersect;
|
|
function maximumOffset(node) {
|
|
return node.nodeType === Node.TEXT_NODE ? (node).length : node.childNodes.length
|
|
}
|
|
function getNodesInRange(range, nodeFilter, whatToShow) {
|
|
var document = range.startContainer.ownerDocument, elements = [], rangeRoot = range.commonAncestorContainer, root = (rangeRoot.nodeType === Node.TEXT_NODE ? rangeRoot.parentNode : rangeRoot), treeWalker = document.createTreeWalker(root, whatToShow, nodeFilter, false), currentNode, lastNodeInRange, endNodeCompareFlags, comparePositionResult;
|
|
if(range.endContainer.childNodes[range.endOffset - 1]) {
|
|
lastNodeInRange = (range.endContainer.childNodes[range.endOffset - 1]);
|
|
endNodeCompareFlags = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINED_BY
|
|
}else {
|
|
lastNodeInRange = (range.endContainer);
|
|
endNodeCompareFlags = Node.DOCUMENT_POSITION_PRECEDING
|
|
}
|
|
if(range.startContainer.childNodes[range.startOffset]) {
|
|
currentNode = (range.startContainer.childNodes[range.startOffset]);
|
|
treeWalker.currentNode = currentNode
|
|
}else {
|
|
if(range.startOffset === maximumOffset(range.startContainer)) {
|
|
currentNode = (range.startContainer);
|
|
treeWalker.currentNode = currentNode;
|
|
treeWalker.lastChild();
|
|
currentNode = treeWalker.nextNode()
|
|
}else {
|
|
currentNode = (range.startContainer);
|
|
treeWalker.currentNode = currentNode
|
|
}
|
|
}
|
|
if(currentNode && nodeFilter(currentNode) === NodeFilter.FILTER_ACCEPT) {
|
|
elements.push(currentNode)
|
|
}
|
|
currentNode = treeWalker.nextNode();
|
|
while(currentNode) {
|
|
comparePositionResult = lastNodeInRange.compareDocumentPosition(currentNode);
|
|
if(comparePositionResult !== 0 && (comparePositionResult & endNodeCompareFlags) === 0) {
|
|
break
|
|
}
|
|
elements.push(currentNode);
|
|
currentNode = treeWalker.nextNode()
|
|
}
|
|
return elements
|
|
}
|
|
this.getNodesInRange = getNodesInRange;
|
|
function mergeTextNodes(node, nextNode) {
|
|
var mergedNode = null, text, nextText;
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
text = (node);
|
|
if(text.length === 0) {
|
|
text.parentNode.removeChild(text);
|
|
if(nextNode.nodeType === Node.TEXT_NODE) {
|
|
mergedNode = nextNode
|
|
}
|
|
}else {
|
|
if(nextNode.nodeType === Node.TEXT_NODE) {
|
|
nextText = (nextNode);
|
|
text.appendData(nextText.data);
|
|
nextNode.parentNode.removeChild(nextNode)
|
|
}
|
|
mergedNode = node
|
|
}
|
|
}
|
|
return mergedNode
|
|
}
|
|
function normalizeTextNodes(node) {
|
|
if(node && node.nextSibling) {
|
|
node = mergeTextNodes(node, node.nextSibling)
|
|
}
|
|
if(node && node.previousSibling) {
|
|
mergeTextNodes(node.previousSibling, node)
|
|
}
|
|
}
|
|
this.normalizeTextNodes = normalizeTextNodes;
|
|
function rangeContainsNode(limits, node) {
|
|
var range = node.ownerDocument.createRange(), nodeRange = node.ownerDocument.createRange(), result;
|
|
range.setStart(limits.startContainer, limits.startOffset);
|
|
range.setEnd(limits.endContainer, limits.endOffset);
|
|
nodeRange.selectNodeContents(node);
|
|
result = containsRange(range, nodeRange);
|
|
range.detach();
|
|
nodeRange.detach();
|
|
return result
|
|
}
|
|
this.rangeContainsNode = rangeContainsNode;
|
|
function mergeIntoParent(targetNode) {
|
|
var parent = targetNode.parentNode;
|
|
while(targetNode.firstChild) {
|
|
parent.insertBefore(targetNode.firstChild, targetNode)
|
|
}
|
|
parent.removeChild(targetNode);
|
|
return parent
|
|
}
|
|
this.mergeIntoParent = mergeIntoParent;
|
|
function removeUnwantedNodes(targetNode, shouldRemove) {
|
|
var parent = targetNode.parentNode, node = targetNode.firstChild, next;
|
|
while(node) {
|
|
next = node.nextSibling;
|
|
removeUnwantedNodes(node, shouldRemove);
|
|
node = next
|
|
}
|
|
if(parent && shouldRemove(targetNode)) {
|
|
mergeIntoParent(targetNode)
|
|
}
|
|
return parent
|
|
}
|
|
this.removeUnwantedNodes = removeUnwantedNodes;
|
|
function getElementsByTagNameNS(node, namespace, tagName) {
|
|
var e = [], list, i, l;
|
|
list = node.getElementsByTagNameNS(namespace, tagName);
|
|
e.length = l = list.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
e[i] = (list.item(i))
|
|
}
|
|
return e
|
|
}
|
|
this.getElementsByTagNameNS = getElementsByTagNameNS;
|
|
function containsNode(parent, descendant) {
|
|
return parent === descendant || (parent).contains((descendant))
|
|
}
|
|
this.containsNode = containsNode;
|
|
function containsNodeForBrokenWebKit(parent, descendant) {
|
|
return parent === descendant || Boolean(parent.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY)
|
|
}
|
|
function comparePoints(c1, o1, c2, o2) {
|
|
if(c1 === c2) {
|
|
return o2 - o1
|
|
}
|
|
var comparison = c1.compareDocumentPosition(c2);
|
|
if(comparison === 2) {
|
|
comparison = -1
|
|
}else {
|
|
if(comparison === 4) {
|
|
comparison = 1
|
|
}else {
|
|
if(comparison === 10) {
|
|
o1 = getPositionInContainingNode(c1, c2);
|
|
comparison = o1 < o2 ? 1 : -1
|
|
}else {
|
|
o2 = getPositionInContainingNode(c2, c1);
|
|
comparison = o2 < o1 ? -1 : 1
|
|
}
|
|
}
|
|
}
|
|
return comparison
|
|
}
|
|
this.comparePoints = comparePoints;
|
|
function adaptRangeDifferenceToZoomLevel(inputNumber, zoomLevel) {
|
|
if(getBrowserQuirks().unscaledRangeClientRects) {
|
|
return inputNumber
|
|
}
|
|
return inputNumber / zoomLevel
|
|
}
|
|
this.adaptRangeDifferenceToZoomLevel = adaptRangeDifferenceToZoomLevel;
|
|
function getBoundingClientRect(node) {
|
|
var doc = (node.ownerDocument), quirks = getBrowserQuirks(), range, element, rect, body = doc.body;
|
|
if(quirks.unscaledRangeClientRects === false || quirks.rangeBCRIgnoresElementBCR) {
|
|
if(node.nodeType === Node.ELEMENT_NODE) {
|
|
element = (node);
|
|
rect = element.getBoundingClientRect();
|
|
if(quirks.elementBCRIgnoresBodyScroll) {
|
|
return({left:rect.left + body.scrollLeft, right:rect.right + body.scrollLeft, top:rect.top + body.scrollTop, bottom:rect.bottom + body.scrollTop})
|
|
}
|
|
return rect
|
|
}
|
|
}
|
|
range = getSharedRange(doc);
|
|
range.selectNode(node);
|
|
return range.getBoundingClientRect()
|
|
}
|
|
this.getBoundingClientRect = getBoundingClientRect;
|
|
function mapKeyValObjOntoNode(node, properties, nsResolver) {
|
|
Object.keys(properties).forEach(function(key) {
|
|
var parts = key.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), value = properties[key], element;
|
|
if(ns) {
|
|
element = (node.getElementsByTagNameNS(ns, localName)[0]);
|
|
if(!element) {
|
|
element = node.ownerDocument.createElementNS(ns, key);
|
|
node.appendChild(element)
|
|
}
|
|
element.textContent = value
|
|
}else {
|
|
runtime.log("Key ignored: " + key)
|
|
}
|
|
})
|
|
}
|
|
this.mapKeyValObjOntoNode = mapKeyValObjOntoNode;
|
|
function removeKeyElementsFromNode(node, propertyNames, nsResolver) {
|
|
propertyNames.forEach(function(propertyName) {
|
|
var parts = propertyName.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), element;
|
|
if(ns) {
|
|
element = (node.getElementsByTagNameNS(ns, localName)[0]);
|
|
if(element) {
|
|
element.parentNode.removeChild(element)
|
|
}else {
|
|
runtime.log("Element for " + propertyName + " not found.")
|
|
}
|
|
}else {
|
|
runtime.log("Property Name ignored: " + propertyName)
|
|
}
|
|
})
|
|
}
|
|
this.removeKeyElementsFromNode = removeKeyElementsFromNode;
|
|
function getKeyValRepresentationOfNode(node, prefixResolver) {
|
|
var properties = {}, currentSibling = node.firstElementChild, prefix;
|
|
while(currentSibling) {
|
|
prefix = prefixResolver(currentSibling.namespaceURI);
|
|
if(prefix) {
|
|
properties[prefix + ":" + currentSibling.localName] = currentSibling.textContent
|
|
}
|
|
currentSibling = currentSibling.nextElementSibling
|
|
}
|
|
return properties
|
|
}
|
|
this.getKeyValRepresentationOfNode = getKeyValRepresentationOfNode;
|
|
function mapObjOntoNode(node, properties, nsResolver) {
|
|
Object.keys(properties).forEach(function(key) {
|
|
var parts = key.split(":"), prefix = parts[0], localName = parts[1], ns = nsResolver(prefix), value = properties[key], valueType = typeof value, element;
|
|
if(valueType === "object") {
|
|
if(Object.keys((value)).length) {
|
|
if(ns) {
|
|
element = (node.getElementsByTagNameNS(ns, localName)[0]) || node.ownerDocument.createElementNS(ns, key)
|
|
}else {
|
|
element = (node.getElementsByTagName(localName)[0]) || node.ownerDocument.createElement(key)
|
|
}
|
|
node.appendChild(element);
|
|
mapObjOntoNode(element, (value), nsResolver)
|
|
}
|
|
}else {
|
|
if(ns) {
|
|
runtime.assert(valueType === "number" || valueType === "string", "attempting to map unsupported type '" + valueType + "' (key: " + key + ")");
|
|
node.setAttributeNS(ns, key, String(value))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
this.mapObjOntoNode = mapObjOntoNode;
|
|
function init(self) {
|
|
var appVersion, webKitOrSafari, ie, window = runtime.getWindow();
|
|
if(window === null) {
|
|
return
|
|
}
|
|
appVersion = window.navigator.appVersion.toLowerCase();
|
|
webKitOrSafari = appVersion.indexOf("chrome") === -1 && (appVersion.indexOf("applewebkit") !== -1 || appVersion.indexOf("safari") !== -1);
|
|
ie = appVersion.indexOf("msie");
|
|
if(webKitOrSafari || ie) {
|
|
self.containsNode = containsNodeForBrokenWebKit
|
|
}
|
|
}
|
|
init(this)
|
|
};
|
|
return core.DomUtils
|
|
})();
|
|
core.Cursor = function Cursor(document, memberId) {
|
|
var cursorns = "urn:webodf:names:cursor", cursorNode = document.createElementNS(cursorns, "cursor"), anchorNode = document.createElementNS(cursorns, "anchor"), forwardSelection, recentlyModifiedNodes = [], selectedRange = (document.createRange()), isCollapsed, domUtils = new core.DomUtils;
|
|
function putIntoTextNode(node, container, offset) {
|
|
runtime.assert(Boolean(container), "putCursorIntoTextNode: invalid container");
|
|
var parent = container.parentNode;
|
|
runtime.assert(Boolean(parent), "putCursorIntoTextNode: container without parent");
|
|
runtime.assert(offset >= 0 && offset <= container.length, "putCursorIntoTextNode: offset is out of bounds");
|
|
if(offset === 0) {
|
|
parent.insertBefore(node, container)
|
|
}else {
|
|
if(offset === container.length) {
|
|
parent.insertBefore(node, container.nextSibling)
|
|
}else {
|
|
container.splitText(offset);
|
|
parent.insertBefore(node, container.nextSibling)
|
|
}
|
|
}
|
|
}
|
|
function removeNode(node) {
|
|
if(node.parentNode) {
|
|
recentlyModifiedNodes.push(node.previousSibling);
|
|
recentlyModifiedNodes.push(node.nextSibling);
|
|
node.parentNode.removeChild(node)
|
|
}
|
|
}
|
|
function putNode(node, container, offset) {
|
|
if(container.nodeType === Node.TEXT_NODE) {
|
|
putIntoTextNode(node, (container), offset)
|
|
}else {
|
|
if(container.nodeType === Node.ELEMENT_NODE) {
|
|
container.insertBefore(node, container.childNodes.item(offset))
|
|
}
|
|
}
|
|
recentlyModifiedNodes.push(node.previousSibling);
|
|
recentlyModifiedNodes.push(node.nextSibling)
|
|
}
|
|
function getStartNode() {
|
|
return forwardSelection ? anchorNode : cursorNode
|
|
}
|
|
function getEndNode() {
|
|
return forwardSelection ? cursorNode : anchorNode
|
|
}
|
|
this.getNode = function() {
|
|
return cursorNode
|
|
};
|
|
this.getAnchorNode = function() {
|
|
return anchorNode.parentNode ? anchorNode : cursorNode
|
|
};
|
|
this.getSelectedRange = function() {
|
|
if(isCollapsed) {
|
|
selectedRange.setStartBefore(cursorNode);
|
|
selectedRange.collapse(true)
|
|
}else {
|
|
selectedRange.setStartAfter(getStartNode());
|
|
selectedRange.setEndBefore(getEndNode())
|
|
}
|
|
return selectedRange
|
|
};
|
|
this.setSelectedRange = function(range, isForwardSelection) {
|
|
if(selectedRange && selectedRange !== range) {
|
|
selectedRange.detach()
|
|
}
|
|
selectedRange = range;
|
|
forwardSelection = isForwardSelection !== false;
|
|
isCollapsed = range.collapsed;
|
|
if(range.collapsed) {
|
|
removeNode(anchorNode);
|
|
removeNode(cursorNode);
|
|
putNode(cursorNode, (range.startContainer), range.startOffset)
|
|
}else {
|
|
removeNode(anchorNode);
|
|
removeNode(cursorNode);
|
|
putNode(getEndNode(), (range.endContainer), range.endOffset);
|
|
putNode(getStartNode(), (range.startContainer), range.startOffset)
|
|
}
|
|
recentlyModifiedNodes.forEach(domUtils.normalizeTextNodes);
|
|
recentlyModifiedNodes.length = 0
|
|
};
|
|
this.hasForwardSelection = function() {
|
|
return forwardSelection
|
|
};
|
|
this.remove = function() {
|
|
removeNode(cursorNode);
|
|
recentlyModifiedNodes.forEach(domUtils.normalizeTextNodes);
|
|
recentlyModifiedNodes.length = 0
|
|
};
|
|
function init() {
|
|
cursorNode.setAttributeNS(cursorns, "memberId", memberId);
|
|
anchorNode.setAttributeNS(cursorns, "memberId", memberId)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
core.Destroyable = function Destroyable() {
|
|
};
|
|
core.Destroyable.prototype.destroy = function(callback) {
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
core.EventNotifier = function EventNotifier(eventIds) {
|
|
var eventListener = {};
|
|
this.emit = function(eventId, args) {
|
|
var i, subscribers;
|
|
runtime.assert(eventListener.hasOwnProperty(eventId), 'unknown event fired "' + eventId + '"');
|
|
subscribers = eventListener[eventId];
|
|
for(i = 0;i < subscribers.length;i += 1) {
|
|
subscribers[i](args)
|
|
}
|
|
};
|
|
this.subscribe = function(eventId, cb) {
|
|
runtime.assert(eventListener.hasOwnProperty(eventId), 'tried to subscribe to unknown event "' + eventId + '"');
|
|
eventListener[eventId].push(cb)
|
|
};
|
|
this.unsubscribe = function(eventId, cb) {
|
|
var cbIndex;
|
|
runtime.assert(eventListener.hasOwnProperty(eventId), 'tried to unsubscribe from unknown event "' + eventId + '"');
|
|
cbIndex = eventListener[eventId].indexOf(cb);
|
|
runtime.assert(cbIndex !== -1, 'tried to unsubscribe unknown callback from event "' + eventId + '"');
|
|
if(cbIndex !== -1) {
|
|
eventListener[eventId].splice(cbIndex, 1)
|
|
}
|
|
};
|
|
function init() {
|
|
var i, eventId;
|
|
for(i = 0;i < eventIds.length;i += 1) {
|
|
eventId = eventIds[i];
|
|
runtime.assert(!eventListener.hasOwnProperty(eventId), 'Duplicated event ids: "' + eventId + '" registered more than once.');
|
|
eventListener[eventId] = []
|
|
}
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
core.LoopWatchDog = function LoopWatchDog(timeout, maxChecks) {
|
|
var startTime = Date.now(), checks = 0;
|
|
function check() {
|
|
var t;
|
|
if(timeout) {
|
|
t = Date.now();
|
|
if(t - startTime > timeout) {
|
|
runtime.log("alert", "watchdog timeout");
|
|
throw"timeout!";
|
|
}
|
|
}
|
|
if(maxChecks > 0) {
|
|
checks += 1;
|
|
if(checks > maxChecks) {
|
|
runtime.log("alert", "watchdog loop overflow");
|
|
throw"loop overflow";
|
|
}
|
|
}
|
|
}
|
|
this.check = check
|
|
};
|
|
core.PositionIterator = function PositionIterator(root, whatToShow, filter, expandEntityReferences) {
|
|
var self = this, walker, currentPos, nodeFilter, TEXT_NODE = Node.TEXT_NODE, ELEMENT_NODE = Node.ELEMENT_NODE, FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, FILTER_REJECT = NodeFilter.FILTER_REJECT;
|
|
function EmptyTextNodeFilter() {
|
|
this.acceptNode = function(node) {
|
|
var text = (node);
|
|
if(!node || node.nodeType === TEXT_NODE && text.length === 0) {
|
|
return FILTER_REJECT
|
|
}
|
|
return FILTER_ACCEPT
|
|
}
|
|
}
|
|
function FilteredEmptyTextNodeFilter(filter) {
|
|
this.acceptNode = function(node) {
|
|
var text = (node);
|
|
if(!node || node.nodeType === TEXT_NODE && text.length === 0) {
|
|
return FILTER_REJECT
|
|
}
|
|
return filter.acceptNode(node)
|
|
}
|
|
}
|
|
this.nextPosition = function() {
|
|
var currentNode = walker.currentNode, nodeType = currentNode.nodeType, text = (currentNode);
|
|
if(currentNode === root) {
|
|
return false
|
|
}
|
|
if(currentPos === 0 && nodeType === ELEMENT_NODE) {
|
|
if(walker.firstChild() === null) {
|
|
currentPos = 1
|
|
}
|
|
}else {
|
|
if(nodeType === TEXT_NODE && currentPos + 1 < text.length) {
|
|
currentPos += 1
|
|
}else {
|
|
if(walker.nextSibling() !== null) {
|
|
currentPos = 0
|
|
}else {
|
|
if(walker.parentNode()) {
|
|
currentPos = 1
|
|
}else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
};
|
|
function setAtEnd() {
|
|
var text = (walker.currentNode), type = text.nodeType;
|
|
if(type === TEXT_NODE) {
|
|
currentPos = text.length - 1
|
|
}else {
|
|
currentPos = type === ELEMENT_NODE ? 1 : 0
|
|
}
|
|
}
|
|
function previousNode() {
|
|
if(walker.previousSibling() === null) {
|
|
if(!walker.parentNode() || walker.currentNode === root) {
|
|
walker.firstChild();
|
|
return false
|
|
}
|
|
currentPos = 0
|
|
}else {
|
|
setAtEnd()
|
|
}
|
|
return true
|
|
}
|
|
this.previousPosition = function() {
|
|
var moved = true, currentNode = walker.currentNode;
|
|
if(currentPos === 0) {
|
|
moved = previousNode()
|
|
}else {
|
|
if(currentNode.nodeType === TEXT_NODE) {
|
|
currentPos -= 1
|
|
}else {
|
|
if(walker.lastChild() !== null) {
|
|
setAtEnd()
|
|
}else {
|
|
if(currentNode === root) {
|
|
moved = false
|
|
}else {
|
|
currentPos = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return moved
|
|
};
|
|
this.previousNode = previousNode;
|
|
this.container = function() {
|
|
var n = (walker.currentNode), t = n.nodeType;
|
|
if(currentPos === 0 && t !== TEXT_NODE) {
|
|
n = (n.parentNode)
|
|
}
|
|
return n
|
|
};
|
|
this.rightNode = function() {
|
|
var n = walker.currentNode, text = (n), nodeType = n.nodeType;
|
|
if(nodeType === TEXT_NODE && currentPos === text.length) {
|
|
n = n.nextSibling;
|
|
while(n && nodeFilter(n) !== FILTER_ACCEPT) {
|
|
n = n.nextSibling
|
|
}
|
|
}else {
|
|
if(nodeType === ELEMENT_NODE && currentPos === 1) {
|
|
n = null
|
|
}
|
|
}
|
|
return n
|
|
};
|
|
this.leftNode = function() {
|
|
var n = walker.currentNode;
|
|
if(currentPos === 0) {
|
|
n = n.previousSibling;
|
|
while(n && nodeFilter(n) !== FILTER_ACCEPT) {
|
|
n = n.previousSibling
|
|
}
|
|
}else {
|
|
if(n.nodeType === ELEMENT_NODE) {
|
|
n = n.lastChild;
|
|
while(n && nodeFilter(n) !== FILTER_ACCEPT) {
|
|
n = n.previousSibling
|
|
}
|
|
}
|
|
}
|
|
return n
|
|
};
|
|
this.getCurrentNode = function() {
|
|
var n = (walker.currentNode);
|
|
return n
|
|
};
|
|
this.unfilteredDomOffset = function() {
|
|
if(walker.currentNode.nodeType === TEXT_NODE) {
|
|
return currentPos
|
|
}
|
|
var c = 0, n = walker.currentNode;
|
|
if(currentPos === 1) {
|
|
n = n.lastChild
|
|
}else {
|
|
n = n.previousSibling
|
|
}
|
|
while(n) {
|
|
c += 1;
|
|
n = n.previousSibling
|
|
}
|
|
return c
|
|
};
|
|
this.getPreviousSibling = function() {
|
|
var currentNode = walker.currentNode, sibling = walker.previousSibling();
|
|
walker.currentNode = currentNode;
|
|
return sibling
|
|
};
|
|
this.getNextSibling = function() {
|
|
var currentNode = walker.currentNode, sibling = walker.nextSibling();
|
|
walker.currentNode = currentNode;
|
|
return sibling
|
|
};
|
|
function moveToAcceptedNode() {
|
|
var node = walker.currentNode, filterResult, moveResult;
|
|
filterResult = nodeFilter(node);
|
|
if(node !== root) {
|
|
node = node.parentNode;
|
|
while(node && node !== root) {
|
|
if(nodeFilter(node) === FILTER_REJECT) {
|
|
walker.currentNode = node;
|
|
filterResult = FILTER_REJECT
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
}
|
|
if(filterResult === FILTER_REJECT) {
|
|
currentPos = 1;
|
|
moveResult = self.nextPosition()
|
|
}else {
|
|
if(filterResult === FILTER_ACCEPT) {
|
|
moveResult = true
|
|
}else {
|
|
moveResult = self.nextPosition()
|
|
}
|
|
}
|
|
if(moveResult) {
|
|
runtime.assert(nodeFilter(walker.currentNode) === FILTER_ACCEPT, "moveToAcceptedNode did not result in walker being on an accepted node")
|
|
}
|
|
return moveResult
|
|
}
|
|
this.setPositionBeforeElement = function(element) {
|
|
runtime.assert(Boolean(element), "setPositionBeforeElement called without element");
|
|
walker.currentNode = element;
|
|
currentPos = 0;
|
|
return moveToAcceptedNode()
|
|
};
|
|
this.setUnfilteredPosition = function(container, offset) {
|
|
var text;
|
|
runtime.assert(Boolean(container), "PositionIterator.setUnfilteredPosition called without container");
|
|
walker.currentNode = container;
|
|
if(container.nodeType === TEXT_NODE) {
|
|
currentPos = offset;
|
|
text = (container);
|
|
runtime.assert(offset <= text.length, "Error in setPosition: " + offset + " > " + text.length);
|
|
runtime.assert(offset >= 0, "Error in setPosition: " + offset + " < 0");
|
|
if(offset === text.length) {
|
|
if(walker.nextSibling()) {
|
|
currentPos = 0
|
|
}else {
|
|
if(walker.parentNode()) {
|
|
currentPos = 1
|
|
}else {
|
|
runtime.assert(false, "Error in setUnfilteredPosition: position not valid.")
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
if(offset < container.childNodes.length) {
|
|
walker.currentNode = (container.childNodes.item(offset));
|
|
currentPos = 0
|
|
}else {
|
|
currentPos = 1
|
|
}
|
|
return moveToAcceptedNode()
|
|
};
|
|
this.moveToEnd = function() {
|
|
walker.currentNode = root;
|
|
currentPos = 1
|
|
};
|
|
this.moveToEndOfNode = function(node) {
|
|
var text;
|
|
if(node.nodeType === TEXT_NODE) {
|
|
text = (node);
|
|
self.setUnfilteredPosition(text, text.length)
|
|
}else {
|
|
walker.currentNode = node;
|
|
currentPos = 1
|
|
}
|
|
};
|
|
this.isBeforeNode = function() {
|
|
return currentPos === 0
|
|
};
|
|
this.getNodeFilter = function() {
|
|
return nodeFilter
|
|
};
|
|
function init() {
|
|
var f;
|
|
if(filter) {
|
|
f = new FilteredEmptyTextNodeFilter(filter)
|
|
}else {
|
|
f = new EmptyTextNodeFilter
|
|
}
|
|
nodeFilter = (f.acceptNode);
|
|
nodeFilter.acceptNode = nodeFilter;
|
|
whatToShow = whatToShow || NodeFilter.SHOW_ALL;
|
|
runtime.assert(root.nodeType !== Node.TEXT_NODE, "Internet Explorer doesn't allow tree walker roots to be text nodes");
|
|
walker = root.ownerDocument.createTreeWalker(root, whatToShow, nodeFilter, expandEntityReferences);
|
|
currentPos = 0;
|
|
if(walker.firstChild() === null) {
|
|
currentPos = 1
|
|
}
|
|
}
|
|
init()
|
|
};
|
|
core.PositionFilter = function PositionFilter() {
|
|
};
|
|
core.PositionFilter.FilterResult = {FILTER_ACCEPT:1, FILTER_REJECT:2, FILTER_SKIP:3};
|
|
core.PositionFilter.prototype.acceptPosition = function(point) {
|
|
};
|
|
(function() {
|
|
return core.PositionFilter
|
|
})();
|
|
core.PositionFilterChain = function PositionFilterChain() {
|
|
var filterChain = [], FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
|
|
this.acceptPosition = function(iterator) {
|
|
var i;
|
|
for(i = 0;i < filterChain.length;i += 1) {
|
|
if(filterChain[i].acceptPosition(iterator) === FILTER_REJECT) {
|
|
return FILTER_REJECT
|
|
}
|
|
}
|
|
return FILTER_ACCEPT
|
|
};
|
|
this.addFilter = function(filterInstance) {
|
|
filterChain.push(filterInstance)
|
|
}
|
|
};
|
|
core.zip_HuftNode = function() {
|
|
this.e = 0;
|
|
this.b = 0;
|
|
this.n = 0;
|
|
this.t = null
|
|
};
|
|
core.zip_HuftList = function() {
|
|
this.next = null;
|
|
this.list = null
|
|
};
|
|
core.RawInflate = function RawInflate() {
|
|
var zip_WSIZE = 32768;
|
|
var zip_STORED_BLOCK = 0;
|
|
var zip_lbits = 9;
|
|
var zip_dbits = 6;
|
|
var zip_slide = [];
|
|
var zip_wp;
|
|
var zip_fixed_tl = null;
|
|
var zip_fixed_td;
|
|
var zip_fixed_bl;
|
|
var zip_bit_buf;
|
|
var zip_bit_len;
|
|
var zip_method;
|
|
var zip_eof;
|
|
var zip_copy_leng;
|
|
var zip_copy_dist;
|
|
var zip_tl, zip_td;
|
|
var zip_bl, zip_bd;
|
|
var zip_inflate_data;
|
|
var zip_inflate_pos;
|
|
var zip_MASK_BITS = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535];
|
|
var zip_cplens = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0];
|
|
var zip_cplext = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99];
|
|
var zip_cpdist = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
|
|
var zip_cpdext = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
|
|
var zip_border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
function Zip_HuftBuild(b, n, s, d, e, mm) {
|
|
this.BMAX = 16;
|
|
this.N_MAX = 288;
|
|
this.status = 0;
|
|
this.root = null;
|
|
this.m = 0;
|
|
var a, c = new Array(this.BMAX + 1), el, f, g, h, i, j, k, lx = new Array(this.BMAX + 1), p, pidx, q, r = new core.zip_HuftNode, u = new Array(this.BMAX), v = new Array(this.N_MAX), w, x = new Array(this.BMAX + 1), xp, y, z, o, tail;
|
|
tail = this.root = null;
|
|
for(i = 0;i < c.length;i++) {
|
|
c[i] = 0
|
|
}
|
|
for(i = 0;i < lx.length;i++) {
|
|
lx[i] = 0
|
|
}
|
|
for(i = 0;i < u.length;i++) {
|
|
u[i] = null
|
|
}
|
|
for(i = 0;i < v.length;i++) {
|
|
v[i] = 0
|
|
}
|
|
for(i = 0;i < x.length;i++) {
|
|
x[i] = 0
|
|
}
|
|
el = n > 256 ? b[256] : this.BMAX;
|
|
p = b;
|
|
pidx = 0;
|
|
i = n;
|
|
do {
|
|
c[p[pidx]]++;
|
|
pidx++
|
|
}while(--i > 0);
|
|
if(c[0] === n) {
|
|
this.root = null;
|
|
this.m = 0;
|
|
this.status = 0;
|
|
return
|
|
}
|
|
for(j = 1;j <= this.BMAX;j++) {
|
|
if(c[j] !== 0) {
|
|
break
|
|
}
|
|
}
|
|
k = j;
|
|
if(mm < j) {
|
|
mm = j
|
|
}
|
|
for(i = this.BMAX;i !== 0;i--) {
|
|
if(c[i] !== 0) {
|
|
break
|
|
}
|
|
}
|
|
g = i;
|
|
if(mm > i) {
|
|
mm = i
|
|
}
|
|
for(y = 1 << j;j < i;j++, y <<= 1) {
|
|
y -= c[j];
|
|
if(y < 0) {
|
|
this.status = 2;
|
|
this.m = mm;
|
|
return
|
|
}
|
|
}
|
|
y -= c[i];
|
|
if(y < 0) {
|
|
this.status = 2;
|
|
this.m = mm;
|
|
return
|
|
}
|
|
c[i] += y;
|
|
x[1] = j = 0;
|
|
p = c;
|
|
pidx = 1;
|
|
xp = 2;
|
|
while(--i > 0) {
|
|
j += p[pidx++];
|
|
x[xp++] = j
|
|
}
|
|
p = b;
|
|
pidx = 0;
|
|
i = 0;
|
|
do {
|
|
j = p[pidx++];
|
|
if(j !== 0) {
|
|
v[x[j]++] = i
|
|
}
|
|
}while(++i < n);
|
|
n = x[g];
|
|
x[0] = i = 0;
|
|
p = v;
|
|
pidx = 0;
|
|
h = -1;
|
|
w = lx[0] = 0;
|
|
q = null;
|
|
z = 0;
|
|
k -= 1;
|
|
for(k += 1;k <= g;k++) {
|
|
a = c[k];
|
|
while(a-- > 0) {
|
|
while(k > w + lx[1 + h]) {
|
|
w += lx[1 + h];
|
|
h++;
|
|
z = g - w;
|
|
z = z > mm ? mm : z;
|
|
j = k - w;
|
|
f = 1 << j;
|
|
if(f > a + 1) {
|
|
f -= a + 1;
|
|
xp = k;
|
|
while(++j < z) {
|
|
f <<= 1;
|
|
if(f <= c[++xp]) {
|
|
break
|
|
}
|
|
f -= c[xp]
|
|
}
|
|
}
|
|
if(w + j > el && w < el) {
|
|
j = el - w
|
|
}
|
|
z = 1 << j;
|
|
lx[1 + h] = j;
|
|
q = new Array(z);
|
|
for(o = 0;o < z;o++) {
|
|
q[o] = new core.zip_HuftNode
|
|
}
|
|
if(tail === null) {
|
|
tail = this.root = new core.zip_HuftList
|
|
}else {
|
|
tail = tail.next = new core.zip_HuftList
|
|
}
|
|
tail.next = null;
|
|
tail.list = q;
|
|
u[h] = q;
|
|
if(h > 0) {
|
|
x[h] = i;
|
|
r.b = lx[h];
|
|
r.e = 16 + j;
|
|
r.t = q;
|
|
j = (i & (1 << w) - 1) >> w - lx[h];
|
|
u[h - 1][j].e = r.e;
|
|
u[h - 1][j].b = r.b;
|
|
u[h - 1][j].n = (r).n;
|
|
u[h - 1][j].t = r.t
|
|
}
|
|
}
|
|
r.b = k - w;
|
|
if(pidx >= n) {
|
|
r.e = 99
|
|
}else {
|
|
if(p[pidx] < s) {
|
|
r.e = p[pidx] < 256 ? 16 : 15;
|
|
r.n = p[pidx++]
|
|
}else {
|
|
r.e = e[p[pidx] - s];
|
|
r.n = d[p[pidx++] - s]
|
|
}
|
|
}
|
|
f = 1 << k - w;
|
|
for(j = i >> w;j < z;j += f) {
|
|
q[j].e = r.e;
|
|
q[j].b = r.b;
|
|
q[j].n = (r).n;
|
|
q[j].t = r.t
|
|
}
|
|
for(j = 1 << k - 1;(i & j) !== 0;j >>= 1) {
|
|
i ^= j
|
|
}
|
|
i ^= j;
|
|
while((i & (1 << w) - 1) !== x[h]) {
|
|
w -= lx[h];
|
|
h--
|
|
}
|
|
}
|
|
}
|
|
this.m = lx[1];
|
|
this.status = y !== 0 && g !== 1 ? 1 : 0
|
|
}
|
|
function zip_GET_BYTE() {
|
|
if(zip_inflate_data.length === zip_inflate_pos) {
|
|
return-1
|
|
}
|
|
return zip_inflate_data[zip_inflate_pos++]
|
|
}
|
|
function zip_NEEDBITS(n) {
|
|
while(zip_bit_len < n) {
|
|
zip_bit_buf |= zip_GET_BYTE() << zip_bit_len;
|
|
zip_bit_len += 8
|
|
}
|
|
}
|
|
function zip_GETBITS(n) {
|
|
return zip_bit_buf & zip_MASK_BITS[n]
|
|
}
|
|
function zip_DUMPBITS(n) {
|
|
zip_bit_buf >>= n;
|
|
zip_bit_len -= n
|
|
}
|
|
function zip_inflate_codes(buff, off, size) {
|
|
var e, t, n;
|
|
if(size === 0) {
|
|
return 0
|
|
}
|
|
n = 0;
|
|
for(;;) {
|
|
zip_NEEDBITS(zip_bl);
|
|
t = zip_tl.list[zip_GETBITS(zip_bl)];
|
|
e = t.e;
|
|
while(e > 16) {
|
|
if(e === 99) {
|
|
return-1
|
|
}
|
|
zip_DUMPBITS(t.b);
|
|
e -= 16;
|
|
zip_NEEDBITS(e);
|
|
t = t.t[zip_GETBITS(e)];
|
|
e = t.e
|
|
}
|
|
zip_DUMPBITS(t.b);
|
|
if(e === 16) {
|
|
zip_wp &= zip_WSIZE - 1;
|
|
buff[off + n++] = zip_slide[zip_wp++] = t.n;
|
|
if(n === size) {
|
|
return size
|
|
}
|
|
}else {
|
|
if(e === 15) {
|
|
break
|
|
}
|
|
zip_NEEDBITS(e);
|
|
zip_copy_leng = t.n + zip_GETBITS(e);
|
|
zip_DUMPBITS(e);
|
|
zip_NEEDBITS(zip_bd);
|
|
t = zip_td.list[zip_GETBITS(zip_bd)];
|
|
e = t.e;
|
|
while(e > 16) {
|
|
if(e === 99) {
|
|
return-1
|
|
}
|
|
zip_DUMPBITS(t.b);
|
|
e -= 16;
|
|
zip_NEEDBITS(e);
|
|
t = t.t[zip_GETBITS(e)];
|
|
e = t.e
|
|
}
|
|
zip_DUMPBITS(t.b);
|
|
zip_NEEDBITS(e);
|
|
zip_copy_dist = zip_wp - t.n - zip_GETBITS(e);
|
|
zip_DUMPBITS(e);
|
|
while(zip_copy_leng > 0 && n < size) {
|
|
zip_copy_leng--;
|
|
zip_copy_dist &= zip_WSIZE - 1;
|
|
zip_wp &= zip_WSIZE - 1;
|
|
buff[off + n++] = zip_slide[zip_wp++] = zip_slide[zip_copy_dist++]
|
|
}
|
|
if(n === size) {
|
|
return size
|
|
}
|
|
}
|
|
}
|
|
zip_method = -1;
|
|
return n
|
|
}
|
|
function zip_inflate_stored(buff, off, size) {
|
|
var n;
|
|
n = zip_bit_len & 7;
|
|
zip_DUMPBITS(n);
|
|
zip_NEEDBITS(16);
|
|
n = zip_GETBITS(16);
|
|
zip_DUMPBITS(16);
|
|
zip_NEEDBITS(16);
|
|
if(n !== (~zip_bit_buf & 65535)) {
|
|
return-1
|
|
}
|
|
zip_DUMPBITS(16);
|
|
zip_copy_leng = n;
|
|
n = 0;
|
|
while(zip_copy_leng > 0 && n < size) {
|
|
zip_copy_leng--;
|
|
zip_wp &= zip_WSIZE - 1;
|
|
zip_NEEDBITS(8);
|
|
buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8);
|
|
zip_DUMPBITS(8)
|
|
}
|
|
if(zip_copy_leng === 0) {
|
|
zip_method = -1
|
|
}
|
|
return n
|
|
}
|
|
var zip_fixed_bd;
|
|
function zip_inflate_fixed(buff, off, size) {
|
|
if(zip_fixed_tl === null) {
|
|
var i;
|
|
var l = new Array(288);
|
|
var h;
|
|
for(i = 0;i < 144;i++) {
|
|
l[i] = 8
|
|
}
|
|
for(i = 144;i < 256;i++) {
|
|
l[i] = 9
|
|
}
|
|
for(i = 256;i < 280;i++) {
|
|
l[i] = 7
|
|
}
|
|
for(i = 280;i < 288;i++) {
|
|
l[i] = 8
|
|
}
|
|
zip_fixed_bl = 7;
|
|
h = new Zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext, zip_fixed_bl);
|
|
if(h.status !== 0) {
|
|
alert("HufBuild error: " + h.status);
|
|
return-1
|
|
}
|
|
zip_fixed_tl = h.root;
|
|
zip_fixed_bl = h.m;
|
|
for(i = 0;i < 30;i++) {
|
|
l[i] = 5
|
|
}
|
|
zip_fixed_bd = 5;
|
|
h = new Zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd);
|
|
if(h.status > 1) {
|
|
zip_fixed_tl = null;
|
|
alert("HufBuild error: " + h.status);
|
|
return-1
|
|
}
|
|
zip_fixed_td = h.root;
|
|
zip_fixed_bd = h.m
|
|
}
|
|
zip_tl = zip_fixed_tl;
|
|
zip_td = zip_fixed_td;
|
|
zip_bl = zip_fixed_bl;
|
|
zip_bd = zip_fixed_bd;
|
|
return zip_inflate_codes(buff, off, size)
|
|
}
|
|
function zip_inflate_dynamic(buff, off, size) {
|
|
var i;
|
|
var j;
|
|
var l;
|
|
var n;
|
|
var t;
|
|
var nb;
|
|
var nl;
|
|
var nd;
|
|
var ll = new Array(286 + 30);
|
|
var h;
|
|
for(i = 0;i < ll.length;i++) {
|
|
ll[i] = 0
|
|
}
|
|
zip_NEEDBITS(5);
|
|
nl = 257 + zip_GETBITS(5);
|
|
zip_DUMPBITS(5);
|
|
zip_NEEDBITS(5);
|
|
nd = 1 + zip_GETBITS(5);
|
|
zip_DUMPBITS(5);
|
|
zip_NEEDBITS(4);
|
|
nb = 4 + zip_GETBITS(4);
|
|
zip_DUMPBITS(4);
|
|
if(nl > 286 || nd > 30) {
|
|
return-1
|
|
}
|
|
for(j = 0;j < nb;j++) {
|
|
zip_NEEDBITS(3);
|
|
ll[zip_border[j]] = zip_GETBITS(3);
|
|
zip_DUMPBITS(3)
|
|
}
|
|
for(j = nb;j < 19;j++) {
|
|
ll[zip_border[j]] = 0
|
|
}
|
|
zip_bl = 7;
|
|
h = new Zip_HuftBuild(ll, 19, 19, null, null, zip_bl);
|
|
if(h.status !== 0) {
|
|
return-1
|
|
}
|
|
zip_tl = h.root;
|
|
zip_bl = h.m;
|
|
n = nl + nd;
|
|
i = l = 0;
|
|
while(i < n) {
|
|
zip_NEEDBITS(zip_bl);
|
|
t = zip_tl.list[zip_GETBITS(zip_bl)];
|
|
j = t.b;
|
|
zip_DUMPBITS(j);
|
|
j = t.n;
|
|
if(j < 16) {
|
|
ll[i++] = l = j
|
|
}else {
|
|
if(j === 16) {
|
|
zip_NEEDBITS(2);
|
|
j = 3 + zip_GETBITS(2);
|
|
zip_DUMPBITS(2);
|
|
if(i + j > n) {
|
|
return-1
|
|
}
|
|
while(j-- > 0) {
|
|
ll[i++] = l
|
|
}
|
|
}else {
|
|
if(j === 17) {
|
|
zip_NEEDBITS(3);
|
|
j = 3 + zip_GETBITS(3);
|
|
zip_DUMPBITS(3);
|
|
if(i + j > n) {
|
|
return-1
|
|
}
|
|
while(j-- > 0) {
|
|
ll[i++] = 0
|
|
}
|
|
l = 0
|
|
}else {
|
|
zip_NEEDBITS(7);
|
|
j = 11 + zip_GETBITS(7);
|
|
zip_DUMPBITS(7);
|
|
if(i + j > n) {
|
|
return-1
|
|
}
|
|
while(j-- > 0) {
|
|
ll[i++] = 0
|
|
}
|
|
l = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
zip_bl = zip_lbits;
|
|
h = new Zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl);
|
|
if(zip_bl === 0) {
|
|
h.status = 1
|
|
}
|
|
if(h.status !== 0) {
|
|
return-1
|
|
}
|
|
zip_tl = h.root;
|
|
zip_bl = h.m;
|
|
for(i = 0;i < nd;i++) {
|
|
ll[i] = ll[i + nl]
|
|
}
|
|
zip_bd = zip_dbits;
|
|
h = new Zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd);
|
|
zip_td = h.root;
|
|
zip_bd = h.m;
|
|
if(zip_bd === 0 && nl > 257) {
|
|
return-1
|
|
}
|
|
if(h.status !== 0) {
|
|
return-1
|
|
}
|
|
return zip_inflate_codes(buff, off, size)
|
|
}
|
|
function zip_inflate_start() {
|
|
zip_slide.length = 2 * zip_WSIZE;
|
|
zip_wp = 0;
|
|
zip_bit_buf = 0;
|
|
zip_bit_len = 0;
|
|
zip_method = -1;
|
|
zip_eof = false;
|
|
zip_copy_leng = zip_copy_dist = 0;
|
|
zip_tl = null
|
|
}
|
|
function zip_inflate_internal(buff, off, size) {
|
|
var n = 0, i;
|
|
while(n < size) {
|
|
if(zip_eof && zip_method === -1) {
|
|
return n
|
|
}
|
|
if(zip_copy_leng > 0) {
|
|
if(zip_method !== zip_STORED_BLOCK) {
|
|
while(zip_copy_leng > 0 && n < size) {
|
|
zip_copy_leng--;
|
|
zip_copy_dist &= zip_WSIZE - 1;
|
|
zip_wp &= zip_WSIZE - 1;
|
|
buff[off + n] = zip_slide[zip_wp] = zip_slide[zip_copy_dist];
|
|
n += 1;
|
|
zip_wp += 1;
|
|
zip_copy_dist += 1
|
|
}
|
|
}else {
|
|
while(zip_copy_leng > 0 && n < size) {
|
|
zip_copy_leng -= 1;
|
|
zip_wp &= zip_WSIZE - 1;
|
|
zip_NEEDBITS(8);
|
|
buff[off + n] = zip_slide[zip_wp] = zip_GETBITS(8);
|
|
n += 1;
|
|
zip_wp += 1;
|
|
zip_DUMPBITS(8)
|
|
}
|
|
if(zip_copy_leng === 0) {
|
|
zip_method = -1
|
|
}
|
|
}
|
|
if(n === size) {
|
|
return n
|
|
}
|
|
}
|
|
if(zip_method === -1) {
|
|
if(zip_eof) {
|
|
break
|
|
}
|
|
zip_NEEDBITS(1);
|
|
if(zip_GETBITS(1) !== 0) {
|
|
zip_eof = true
|
|
}
|
|
zip_DUMPBITS(1);
|
|
zip_NEEDBITS(2);
|
|
zip_method = zip_GETBITS(2);
|
|
zip_DUMPBITS(2);
|
|
zip_tl = null;
|
|
zip_copy_leng = 0
|
|
}
|
|
switch(zip_method) {
|
|
case 0:
|
|
i = zip_inflate_stored(buff, off + n, size - n);
|
|
break;
|
|
case 1:
|
|
if(zip_tl !== null) {
|
|
i = zip_inflate_codes(buff, off + n, size - n)
|
|
}else {
|
|
i = zip_inflate_fixed(buff, off + n, size - n)
|
|
}
|
|
break;
|
|
case 2:
|
|
if(zip_tl !== null) {
|
|
i = zip_inflate_codes(buff, off + n, size - n)
|
|
}else {
|
|
i = zip_inflate_dynamic(buff, off + n, size - n)
|
|
}
|
|
break;
|
|
default:
|
|
i = -1;
|
|
break
|
|
}
|
|
if(i === -1) {
|
|
if(zip_eof) {
|
|
return 0
|
|
}
|
|
return-1
|
|
}
|
|
n += i
|
|
}
|
|
return n
|
|
}
|
|
function zip_inflate(data, size) {
|
|
zip_inflate_start();
|
|
zip_inflate_data = data;
|
|
zip_inflate_pos = 0;
|
|
var buff = new Uint8Array(new ArrayBuffer(size));
|
|
zip_inflate_internal(buff, 0, size);
|
|
zip_inflate_data = new Uint8Array(new ArrayBuffer(0));
|
|
return buff
|
|
}
|
|
this.inflate = zip_inflate
|
|
};
|
|
core.ScheduledTask = function ScheduledTask(fn, delay) {
|
|
var timeoutId, scheduled = false, args = [];
|
|
function cancel() {
|
|
if(scheduled) {
|
|
runtime.clearTimeout(timeoutId);
|
|
scheduled = false
|
|
}
|
|
}
|
|
function execute() {
|
|
cancel();
|
|
fn.apply(undefined, args);
|
|
args = null
|
|
}
|
|
this.trigger = function() {
|
|
args = Array.prototype.slice.call(arguments);
|
|
if(!scheduled) {
|
|
scheduled = true;
|
|
timeoutId = runtime.setTimeout(execute, delay)
|
|
}
|
|
};
|
|
this.triggerImmediate = function() {
|
|
args = Array.prototype.slice.call(arguments);
|
|
execute()
|
|
};
|
|
this.processRequests = function() {
|
|
if(scheduled) {
|
|
execute()
|
|
}
|
|
};
|
|
this.cancel = cancel;
|
|
this.destroy = function(callback) {
|
|
cancel();
|
|
callback()
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
core.StepIterator = function StepIterator(filter, iterator) {
|
|
var FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, cachedContainer, cachedOffset, cachedFilterResult;
|
|
function resetCache() {
|
|
cachedContainer = null;
|
|
cachedOffset = undefined;
|
|
cachedFilterResult = undefined
|
|
}
|
|
function isStep() {
|
|
if(cachedFilterResult === undefined) {
|
|
cachedFilterResult = filter.acceptPosition(iterator) === FILTER_ACCEPT
|
|
}
|
|
return(cachedFilterResult)
|
|
}
|
|
this.isStep = isStep;
|
|
function setPosition(newContainer, newOffset) {
|
|
resetCache();
|
|
return iterator.setUnfilteredPosition(newContainer, newOffset)
|
|
}
|
|
this.setPosition = setPosition;
|
|
function container() {
|
|
if(!cachedContainer) {
|
|
cachedContainer = iterator.container()
|
|
}
|
|
return cachedContainer
|
|
}
|
|
this.container = container;
|
|
function offset() {
|
|
if(cachedOffset === undefined) {
|
|
cachedOffset = iterator.unfilteredDomOffset()
|
|
}
|
|
return(cachedOffset)
|
|
}
|
|
this.offset = offset;
|
|
function nextStep() {
|
|
resetCache();
|
|
while(iterator.nextPosition()) {
|
|
resetCache();
|
|
if(isStep()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
this.nextStep = nextStep;
|
|
function previousStep() {
|
|
resetCache();
|
|
while(iterator.previousPosition()) {
|
|
resetCache();
|
|
if(isStep()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
this.previousStep = previousStep;
|
|
this.roundToClosestStep = function() {
|
|
var currentContainer = container(), currentOffset = offset(), isAtStep = isStep();
|
|
if(!isAtStep) {
|
|
isAtStep = previousStep();
|
|
if(!isAtStep) {
|
|
setPosition(currentContainer, currentOffset);
|
|
isAtStep = nextStep()
|
|
}
|
|
}
|
|
return isAtStep
|
|
};
|
|
this.roundToPreviousStep = function() {
|
|
var isAtStep = isStep();
|
|
if(!isAtStep) {
|
|
isAtStep = previousStep()
|
|
}
|
|
return isAtStep
|
|
};
|
|
this.roundToNextStep = function() {
|
|
var isAtStep = isStep();
|
|
if(!isAtStep) {
|
|
isAtStep = nextStep()
|
|
}
|
|
return isAtStep
|
|
}
|
|
};
|
|
core.TestData;
|
|
core.AsyncTestData;
|
|
core.UnitTest = function UnitTest() {
|
|
};
|
|
core.UnitTest.prototype.setUp = function() {
|
|
};
|
|
core.UnitTest.prototype.tearDown = function() {
|
|
};
|
|
core.UnitTest.prototype.description = function() {
|
|
};
|
|
core.UnitTest.prototype.tests = function() {
|
|
};
|
|
core.UnitTest.prototype.asyncTests = function() {
|
|
};
|
|
core.UnitTest.provideTestAreaDiv = function() {
|
|
var maindoc = runtime.getWindow().document, testarea = maindoc.getElementById("testarea");
|
|
runtime.assert(!testarea, 'Unclean test environment, found a div with id "testarea".');
|
|
testarea = maindoc.createElement("div");
|
|
testarea.setAttribute("id", "testarea");
|
|
maindoc.body.appendChild(testarea);
|
|
return(testarea)
|
|
};
|
|
core.UnitTest.cleanupTestAreaDiv = function() {
|
|
var maindoc = runtime.getWindow().document, testarea = maindoc.getElementById("testarea");
|
|
runtime.assert(!!testarea && testarea.parentNode === maindoc.body, 'Test environment broken, found no div with id "testarea" below body.');
|
|
maindoc.body.removeChild(testarea)
|
|
};
|
|
core.UnitTest.createOdtDocument = function(xml, namespaceMap) {
|
|
var xmlDoc = "<?xml version='1.0' encoding='UTF-8'?>";
|
|
xmlDoc += "<office:document";
|
|
Object.keys(namespaceMap).forEach(function(key) {
|
|
xmlDoc += " xmlns:" + key + '="' + namespaceMap[key] + '"'
|
|
});
|
|
xmlDoc += ">";
|
|
xmlDoc += xml;
|
|
xmlDoc += "</office:document>";
|
|
return runtime.parseXML(xmlDoc)
|
|
};
|
|
core.UnitTestLogger = function UnitTestLogger() {
|
|
var messages = [], errors = 0, start = 0, suite = "", test = "";
|
|
this.startTest = function(suiteName, testName) {
|
|
messages = [];
|
|
errors = 0;
|
|
suite = suiteName;
|
|
test = testName;
|
|
start = (new Date).getTime()
|
|
};
|
|
this.endTest = function() {
|
|
var end = (new Date).getTime();
|
|
return{description:test, suite:[suite, test], success:errors === 0, log:messages, time:end - start}
|
|
};
|
|
this.debug = function(msg) {
|
|
messages.push({category:"debug", message:msg})
|
|
};
|
|
this.fail = function(msg) {
|
|
errors += 1;
|
|
messages.push({category:"fail", message:msg})
|
|
};
|
|
this.pass = function(msg) {
|
|
messages.push({category:"pass", message:msg})
|
|
}
|
|
};
|
|
core.UnitTestRunner = function UnitTestRunner(resourcePrefix, logger) {
|
|
var failedTests = 0, failedTestsOnBeginExpectFail, areObjectsEqual, expectFail = false;
|
|
this.resourcePrefix = function() {
|
|
return resourcePrefix
|
|
};
|
|
this.beginExpectFail = function() {
|
|
failedTestsOnBeginExpectFail = failedTests;
|
|
expectFail = true
|
|
};
|
|
this.endExpectFail = function() {
|
|
var hasNoFailedTests = failedTestsOnBeginExpectFail === failedTests;
|
|
expectFail = false;
|
|
failedTests = failedTestsOnBeginExpectFail;
|
|
if(hasNoFailedTests) {
|
|
failedTests += 1;
|
|
logger.fail("Expected at least one failed test, but none registered.")
|
|
}
|
|
};
|
|
function debug(msg) {
|
|
logger.debug(msg)
|
|
}
|
|
function testFailed(msg) {
|
|
failedTests += 1;
|
|
if(!expectFail) {
|
|
logger.fail(msg)
|
|
}else {
|
|
logger.debug(msg)
|
|
}
|
|
}
|
|
function testPassed(msg) {
|
|
logger.pass(msg)
|
|
}
|
|
function areArraysEqual(a, b) {
|
|
var i;
|
|
try {
|
|
if(a.length !== b.length) {
|
|
testFailed("array of length " + a.length + " should be " + b.length + " long");
|
|
return false
|
|
}
|
|
for(i = 0;i < a.length;i += 1) {
|
|
if(a[i] !== b[i]) {
|
|
testFailed(a[i] + " should be " + b[i] + " at array index " + i);
|
|
return false
|
|
}
|
|
}
|
|
}catch(ex) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
function areAttributesEqual(a, b, skipReverseCheck) {
|
|
var aatts = a.attributes, n = aatts.length, i, att, v;
|
|
for(i = 0;i < n;i += 1) {
|
|
att = (aatts.item(i));
|
|
if(att.prefix !== "xmlns" && att.namespaceURI !== "urn:webodf:names:steps") {
|
|
v = b.getAttributeNS(att.namespaceURI, att.localName);
|
|
if(!b.hasAttributeNS(att.namespaceURI, att.localName)) {
|
|
testFailed("Attribute " + att.localName + " with value " + att.value + " was not present");
|
|
return false
|
|
}
|
|
if(v !== att.value) {
|
|
testFailed("Attribute " + att.localName + " was " + v + " should be " + att.value);
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return skipReverseCheck ? true : areAttributesEqual(b, a, true)
|
|
}
|
|
function areNodesEqual(a, b) {
|
|
var an, bn, atype = a.nodeType, btype = b.nodeType;
|
|
if(atype !== btype) {
|
|
testFailed("Nodetype '" + atype + "' should be '" + btype + "'");
|
|
return false
|
|
}
|
|
if(atype === Node.TEXT_NODE) {
|
|
if((a).data === (b).data) {
|
|
return true
|
|
}
|
|
testFailed("Textnode data '" + (a).data + "' should be '" + (b).data + "'");
|
|
return false
|
|
}
|
|
runtime.assert(atype === Node.ELEMENT_NODE, "Only textnodes and elements supported.");
|
|
if(a.namespaceURI !== b.namespaceURI) {
|
|
testFailed("namespace '" + a.namespaceURI + "' should be '" + b.namespaceURI + "'");
|
|
return false
|
|
}
|
|
if(a.localName !== b.localName) {
|
|
testFailed("localName '" + a.localName + "' should be '" + b.localName + "'");
|
|
return false
|
|
}
|
|
if(!areAttributesEqual((a), (b), false)) {
|
|
return false
|
|
}
|
|
an = a.firstChild;
|
|
bn = b.firstChild;
|
|
while(an) {
|
|
if(!bn) {
|
|
testFailed("Nodetype '" + an.nodeType + "' is unexpected here.");
|
|
return false
|
|
}
|
|
if(!areNodesEqual(an, bn)) {
|
|
return false
|
|
}
|
|
an = an.nextSibling;
|
|
bn = bn.nextSibling
|
|
}
|
|
if(bn) {
|
|
testFailed("Nodetype '" + bn.nodeType + "' is missing here.");
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
function isResultCorrect(actual, expected) {
|
|
if(expected === 0) {
|
|
return actual === expected && 1 / actual === 1 / expected
|
|
}
|
|
if(actual === expected) {
|
|
return true
|
|
}
|
|
if(actual === null || expected === null) {
|
|
return false
|
|
}
|
|
if(typeof expected === "number" && isNaN(expected)) {
|
|
return typeof actual === "number" && isNaN(actual)
|
|
}
|
|
if(Object.prototype.toString.call(expected) === Object.prototype.toString.call([])) {
|
|
return areArraysEqual((actual), (expected))
|
|
}
|
|
if(typeof expected === "object" && typeof actual === "object") {
|
|
if((expected).constructor === Element || (expected).constructor === Node) {
|
|
return areNodesEqual((actual), (expected))
|
|
}
|
|
return areObjectsEqual((actual), (expected))
|
|
}
|
|
return false
|
|
}
|
|
function stringify(v) {
|
|
if(v === 0 && 1 / v < 0) {
|
|
return"-0"
|
|
}
|
|
return String(v)
|
|
}
|
|
function shouldBe(t, a, b) {
|
|
if(typeof a !== "string" || typeof b !== "string") {
|
|
debug("WARN: shouldBe() expects string arguments")
|
|
}
|
|
var exception, av, bv;
|
|
try {
|
|
av = eval(a)
|
|
}catch(e) {
|
|
exception = e
|
|
}
|
|
bv = eval(b);
|
|
if(exception) {
|
|
testFailed(a + " should be " + bv + ". Threw exception " + exception)
|
|
}else {
|
|
if(isResultCorrect(av, bv)) {
|
|
testPassed(a + " is " + b)
|
|
}else {
|
|
if(String(typeof av) === String(typeof bv)) {
|
|
testFailed(a + " should be " + bv + ". Was " + stringify(av) + ".")
|
|
}else {
|
|
testFailed(a + " should be " + bv + " (of type " + typeof bv + "). Was " + av + " (of type " + typeof av + ").")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function shouldBeNonNull(t, a) {
|
|
var exception, av;
|
|
try {
|
|
av = eval(a)
|
|
}catch(e) {
|
|
exception = e
|
|
}
|
|
if(exception) {
|
|
testFailed(a + " should be non-null. Threw exception " + exception)
|
|
}else {
|
|
if(av !== null) {
|
|
testPassed(a + " is non-null.")
|
|
}else {
|
|
testFailed(a + " should be non-null. Was " + av)
|
|
}
|
|
}
|
|
}
|
|
function shouldBeNull(t, a) {
|
|
shouldBe(t, a, "null")
|
|
}
|
|
areObjectsEqual = function(a, b) {
|
|
var akeys = Object.keys(a), bkeys = Object.keys(b);
|
|
akeys.sort();
|
|
bkeys.sort();
|
|
return areArraysEqual(akeys, bkeys) && Object.keys(a).every(function(key) {
|
|
var aval = a[key], bval = b[key];
|
|
if(!isResultCorrect(aval, bval)) {
|
|
testFailed(aval + " should be " + bval + " for key " + key);
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
};
|
|
this.areNodesEqual = areNodesEqual;
|
|
this.shouldBeNull = shouldBeNull;
|
|
this.shouldBeNonNull = shouldBeNonNull;
|
|
this.shouldBe = shouldBe;
|
|
this.testFailed = testFailed;
|
|
this.countFailedTests = function() {
|
|
return failedTests
|
|
};
|
|
this.name = function(functions) {
|
|
var i, fname, nf = [], l = functions.length;
|
|
nf.length = l;
|
|
for(i = 0;i < l;i += 1) {
|
|
fname = Runtime.getFunctionName(functions[i]) || "";
|
|
if(fname === "") {
|
|
throw"Found a function without a name.";
|
|
}
|
|
nf[i] = {f:functions[i], name:fname}
|
|
}
|
|
return nf
|
|
}
|
|
};
|
|
core.UnitTester = function UnitTester() {
|
|
var self = this, failedTests = 0, logger = new core.UnitTestLogger, results = {}, inBrowser = runtime.type() === "BrowserRuntime";
|
|
this.resourcePrefix = "";
|
|
function link(text, code) {
|
|
return"<span style='color:blue;cursor:pointer' onclick='" + code + "'>" + text + "</span>"
|
|
}
|
|
this.reporter = function(r) {
|
|
var i, m;
|
|
if(inBrowser) {
|
|
runtime.log("<span>Running " + link(r.description, 'runTest("' + r.suite[0] + '","' + r.description + '")') + "</span>")
|
|
}else {
|
|
runtime.log("Running " + r.description)
|
|
}
|
|
if(!r.success) {
|
|
for(i = 0;i < r.log.length;i += 1) {
|
|
m = r.log[i];
|
|
runtime.log(m.category, m.message)
|
|
}
|
|
}
|
|
};
|
|
function report(r) {
|
|
if(self.reporter) {
|
|
self.reporter(r)
|
|
}
|
|
}
|
|
this.runTests = function(TestClass, callback, testNames) {
|
|
var testName = Runtime.getFunctionName(TestClass) || "", tname, runner = new core.UnitTestRunner(self.resourcePrefix, logger), test = new TestClass(runner), testResults = {}, i, t, tests, texpectFail, lastFailCount;
|
|
if(results.hasOwnProperty(testName)) {
|
|
runtime.log("Test " + testName + " has already run.");
|
|
return
|
|
}
|
|
if(inBrowser) {
|
|
runtime.log("<span>Running " + link(testName, 'runSuite("' + testName + '");') + ": " + test.description() + "</span>")
|
|
}else {
|
|
runtime.log("Running " + testName + ": " + test.description)
|
|
}
|
|
tests = test.tests();
|
|
for(i = 0;i < tests.length;i += 1) {
|
|
t = tests[i].f;
|
|
tname = tests[i].name;
|
|
texpectFail = tests[i].expectFail === true;
|
|
if(testNames.length && testNames.indexOf(tname) === -1) {
|
|
continue
|
|
}
|
|
lastFailCount = runner.countFailedTests();
|
|
test.setUp();
|
|
logger.startTest(testName, tname);
|
|
if(texpectFail) {
|
|
runner.beginExpectFail()
|
|
}
|
|
try {
|
|
t()
|
|
}catch(e) {
|
|
runner.testFailed("Unexpected exception encountered: " + e.toString() + "\n" + e.stack)
|
|
}
|
|
if(texpectFail) {
|
|
runner.endExpectFail()
|
|
}
|
|
report(logger.endTest());
|
|
test.tearDown();
|
|
testResults[tname] = lastFailCount === runner.countFailedTests()
|
|
}
|
|
function runAsyncTests(todo) {
|
|
if(todo.length === 0) {
|
|
results[testName] = testResults;
|
|
failedTests += runner.countFailedTests();
|
|
callback();
|
|
return
|
|
}
|
|
t = todo[0].f;
|
|
var fname = todo[0].name, expectFail = todo[0].expectFail === true;
|
|
lastFailCount = runner.countFailedTests();
|
|
if(testNames.length && testNames.indexOf(fname) === -1) {
|
|
runAsyncTests(todo.slice(1))
|
|
}else {
|
|
test.setUp();
|
|
logger.startTest(testName, fname);
|
|
if(expectFail) {
|
|
runner.beginExpectFail()
|
|
}
|
|
t(function() {
|
|
if(expectFail) {
|
|
runner.endExpectFail()
|
|
}
|
|
report(logger.endTest());
|
|
test.tearDown();
|
|
testResults[fname] = lastFailCount === runner.countFailedTests();
|
|
runAsyncTests(todo.slice(1))
|
|
})
|
|
}
|
|
}
|
|
runAsyncTests(test.asyncTests())
|
|
};
|
|
this.countFailedTests = function() {
|
|
return failedTests
|
|
};
|
|
this.results = function() {
|
|
return results
|
|
}
|
|
};
|
|
core.Utils = function Utils() {
|
|
function hashString(value) {
|
|
var hash = 0, i, l;
|
|
for(i = 0, l = value.length;i < l;i += 1) {
|
|
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
hash |= 0
|
|
}
|
|
return hash
|
|
}
|
|
this.hashString = hashString;
|
|
var mergeObjects;
|
|
function mergeItems(destination, source) {
|
|
if(source && Array.isArray(source)) {
|
|
destination = destination || [];
|
|
if(!Array.isArray(destination)) {
|
|
throw"Destination is not an array.";
|
|
}
|
|
destination = (destination).concat((source).map(function(obj) {
|
|
return mergeItems(null, obj)
|
|
}))
|
|
}else {
|
|
if(source && typeof source === "object") {
|
|
destination = destination || {};
|
|
if(typeof destination !== "object") {
|
|
throw"Destination is not an object.";
|
|
}
|
|
Object.keys((source)).forEach(function(p) {
|
|
destination[p] = mergeItems(destination[p], source[p])
|
|
})
|
|
}else {
|
|
destination = source
|
|
}
|
|
}
|
|
return destination
|
|
}
|
|
mergeObjects = function(destination, source) {
|
|
Object.keys(source).forEach(function(p) {
|
|
destination[p] = mergeItems(destination[p], source[p])
|
|
});
|
|
return destination
|
|
};
|
|
this.mergeObjects = mergeObjects
|
|
};
|
|
/*
|
|
|
|
WebODF
|
|
Copyright (c) 2010 Jos van den Oever
|
|
Licensed under the ... License:
|
|
|
|
Project home: http://www.webodf.org/
|
|
*/
|
|
core.Zip = function Zip(url, entriesReadCallback) {
|
|
var entries, filesize, nEntries, inflate = (new core.RawInflate).inflate, zip = this, base64 = new core.Base64;
|
|
function crc32(data) {
|
|
var table = [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684,
|
|
3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925,
|
|
453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989,
|
|
3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462,
|
|
1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115,
|
|
1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918E3, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117], crc =
|
|
0, i, iTop = data.length, x = 0, y = 0;
|
|
crc = crc ^ -1;
|
|
for(i = 0;i < iTop;i += 1) {
|
|
y = (crc ^ data[i]) & 255;
|
|
x = table[y];
|
|
crc = crc >>> 8 ^ x
|
|
}
|
|
return crc ^ -1
|
|
}
|
|
function dosTime2Date(dostime) {
|
|
var year = (dostime >> 25 & 127) + 1980, month = (dostime >> 21 & 15) - 1, mday = dostime >> 16 & 31, hour = dostime >> 11 & 15, min = dostime >> 5 & 63, sec = (dostime & 31) << 1, d = new Date(year, month, mday, hour, min, sec);
|
|
return d
|
|
}
|
|
function date2DosTime(date) {
|
|
var y = date.getFullYear();
|
|
return y < 1980 ? 0 : y - 1980 << 25 | date.getMonth() + 1 << 21 | date.getDate() << 16 | date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1
|
|
}
|
|
function ZipEntry(url, stream) {
|
|
var sig, namelen, extralen, commentlen, compressionMethod, compressedSize, uncompressedSize, offset, entry = this;
|
|
function handleEntryData(data, callback) {
|
|
var estream = new core.ByteArray(data), esig = estream.readUInt32LE(), filenamelen, eextralen;
|
|
if(esig !== 67324752) {
|
|
callback("File entry signature is wrong." + esig.toString() + " " + data.length.toString(), null);
|
|
return
|
|
}
|
|
estream.pos += 22;
|
|
filenamelen = estream.readUInt16LE();
|
|
eextralen = estream.readUInt16LE();
|
|
estream.pos += filenamelen + eextralen;
|
|
if(compressionMethod) {
|
|
data = data.subarray(estream.pos, estream.pos + compressedSize);
|
|
if(compressedSize !== data.length) {
|
|
callback("The amount of compressed bytes read was " + data.length.toString() + " instead of " + compressedSize.toString() + " for " + entry.filename + " in " + url + ".", null);
|
|
return
|
|
}
|
|
data = inflate(data, uncompressedSize)
|
|
}else {
|
|
data = data.subarray(estream.pos, estream.pos + uncompressedSize)
|
|
}
|
|
if(uncompressedSize !== data.length) {
|
|
callback("The amount of bytes read was " + data.length.toString() + " instead of " + uncompressedSize.toString() + " for " + entry.filename + " in " + url + ".", null);
|
|
return
|
|
}
|
|
entry.data = data;
|
|
callback(null, data)
|
|
}
|
|
function load(callback) {
|
|
if(entry.data !== null) {
|
|
callback(null, entry.data);
|
|
return
|
|
}
|
|
var size = compressedSize + 34 + namelen + extralen + 256;
|
|
if(size + offset > filesize) {
|
|
size = filesize - offset
|
|
}
|
|
runtime.read(url, offset, size, function(err, data) {
|
|
if(err || data === null) {
|
|
callback(err, data)
|
|
}else {
|
|
handleEntryData(data, callback)
|
|
}
|
|
})
|
|
}
|
|
this.load = load;
|
|
function set(filename, data, compressed, date) {
|
|
entry.filename = filename;
|
|
entry.data = data;
|
|
entry.compressed = compressed;
|
|
entry.date = date
|
|
}
|
|
this.set = set;
|
|
this.error = null;
|
|
if(!stream) {
|
|
return
|
|
}
|
|
sig = stream.readUInt32LE();
|
|
if(sig !== 33639248) {
|
|
this.error = "Central directory entry has wrong signature at position " + (stream.pos - 4).toString() + ' for file "' + url + '": ' + stream.data.length.toString();
|
|
return
|
|
}
|
|
stream.pos += 6;
|
|
compressionMethod = stream.readUInt16LE();
|
|
this.date = dosTime2Date(stream.readUInt32LE());
|
|
stream.readUInt32LE();
|
|
compressedSize = stream.readUInt32LE();
|
|
uncompressedSize = stream.readUInt32LE();
|
|
namelen = stream.readUInt16LE();
|
|
extralen = stream.readUInt16LE();
|
|
commentlen = stream.readUInt16LE();
|
|
stream.pos += 8;
|
|
offset = stream.readUInt32LE();
|
|
this.filename = runtime.byteArrayToString(stream.data.subarray(stream.pos, stream.pos + namelen), "utf8");
|
|
this.data = null;
|
|
stream.pos += namelen + extralen + commentlen
|
|
}
|
|
function handleCentralDirectory(data, callback) {
|
|
var stream = new core.ByteArray(data), i, e;
|
|
entries = [];
|
|
for(i = 0;i < nEntries;i += 1) {
|
|
e = new ZipEntry(url, stream);
|
|
if(e.error) {
|
|
callback(e.error, zip);
|
|
return
|
|
}
|
|
entries[entries.length] = e
|
|
}
|
|
callback(null, zip)
|
|
}
|
|
function handleCentralDirectoryEnd(data, callback) {
|
|
if(data.length !== 22) {
|
|
callback("Central directory length should be 22.", zip);
|
|
return
|
|
}
|
|
var stream = new core.ByteArray(data), sig, disk, cddisk, diskNEntries, cdsSize, cdsOffset;
|
|
sig = stream.readUInt32LE();
|
|
if(sig !== 101010256) {
|
|
callback("Central directory signature is wrong: " + sig.toString(), zip);
|
|
return
|
|
}
|
|
disk = stream.readUInt16LE();
|
|
if(disk !== 0) {
|
|
callback("Zip files with non-zero disk numbers are not supported.", zip);
|
|
return
|
|
}
|
|
cddisk = stream.readUInt16LE();
|
|
if(cddisk !== 0) {
|
|
callback("Zip files with non-zero disk numbers are not supported.", zip);
|
|
return
|
|
}
|
|
diskNEntries = stream.readUInt16LE();
|
|
nEntries = stream.readUInt16LE();
|
|
if(diskNEntries !== nEntries) {
|
|
callback("Number of entries is inconsistent.", zip);
|
|
return
|
|
}
|
|
cdsSize = stream.readUInt32LE();
|
|
cdsOffset = stream.readUInt16LE();
|
|
cdsOffset = filesize - 22 - cdsSize;
|
|
runtime.read(url, cdsOffset, filesize - cdsOffset, function(err, data) {
|
|
if(err || data === null) {
|
|
callback(err, zip)
|
|
}else {
|
|
handleCentralDirectory(data, callback)
|
|
}
|
|
})
|
|
}
|
|
function load(filename, callback) {
|
|
var entry = null, e, i;
|
|
for(i = 0;i < entries.length;i += 1) {
|
|
e = entries[i];
|
|
if(e.filename === filename) {
|
|
entry = e;
|
|
break
|
|
}
|
|
}
|
|
if(entry) {
|
|
if(entry.data) {
|
|
callback(null, entry.data)
|
|
}else {
|
|
entry.load(callback)
|
|
}
|
|
}else {
|
|
callback(filename + " not found.", null)
|
|
}
|
|
}
|
|
function loadAsString(filename, callback) {
|
|
load(filename, function(err, data) {
|
|
if(err || data === null) {
|
|
return callback(err, null)
|
|
}
|
|
var d = runtime.byteArrayToString(data, "utf8");
|
|
callback(null, d)
|
|
})
|
|
}
|
|
function loadContentXmlAsFragments(filename, handler) {
|
|
zip.loadAsString(filename, function(err, data) {
|
|
if(err) {
|
|
return handler.rootElementReady(err)
|
|
}
|
|
handler.rootElementReady(null, data, true)
|
|
})
|
|
}
|
|
function loadAsDataURL(filename, mimetype, callback) {
|
|
load(filename, function(err, data) {
|
|
if(err || !data) {
|
|
return callback(err, null)
|
|
}
|
|
var p = data, chunksize = 45E3, i = 0, dataurl;
|
|
if(!mimetype) {
|
|
if(p[1] === 80 && (p[2] === 78 && p[3] === 71)) {
|
|
mimetype = "image/png"
|
|
}else {
|
|
if(p[0] === 255 && (p[1] === 216 && p[2] === 255)) {
|
|
mimetype = "image/jpeg"
|
|
}else {
|
|
if(p[0] === 71 && (p[1] === 73 && p[2] === 70)) {
|
|
mimetype = "image/gif"
|
|
}else {
|
|
mimetype = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dataurl = "data:" + mimetype + ";base64,";
|
|
while(i < data.length) {
|
|
dataurl += base64.convertUTF8ArrayToBase64(p.subarray(i, Math.min(i + chunksize, p.length)));
|
|
i += chunksize
|
|
}
|
|
callback(null, dataurl)
|
|
})
|
|
}
|
|
function loadAsDOM(filename, callback) {
|
|
zip.loadAsString(filename, function(err, xmldata) {
|
|
if(err || xmldata === null) {
|
|
callback(err, null);
|
|
return
|
|
}
|
|
var parser = new DOMParser, dom = parser.parseFromString(xmldata, "text/xml");
|
|
callback(null, dom)
|
|
})
|
|
}
|
|
function save(filename, data, compressed, date) {
|
|
var i, entry;
|
|
for(i = 0;i < entries.length;i += 1) {
|
|
entry = entries[i];
|
|
if(entry.filename === filename) {
|
|
entry.set(filename, data, compressed, date);
|
|
return
|
|
}
|
|
}
|
|
entry = new ZipEntry(url);
|
|
entry.set(filename, data, compressed, date);
|
|
entries.push(entry)
|
|
}
|
|
function remove(filename) {
|
|
var i, entry;
|
|
for(i = 0;i < entries.length;i += 1) {
|
|
entry = entries[i];
|
|
if(entry.filename === filename) {
|
|
entries.splice(i, 1);
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
function writeEntry(entry) {
|
|
var data = new core.ByteArrayWriter("utf8"), length = 0;
|
|
data.appendArray([80, 75, 3, 4, 20, 0, 0, 0, 0, 0]);
|
|
if(entry.data) {
|
|
length = entry.data.length
|
|
}
|
|
data.appendUInt32LE(date2DosTime(entry.date));
|
|
data.appendUInt32LE(entry.data ? crc32(entry.data) : 0);
|
|
data.appendUInt32LE(length);
|
|
data.appendUInt32LE(length);
|
|
data.appendUInt16LE(entry.filename.length);
|
|
data.appendUInt16LE(0);
|
|
data.appendString(entry.filename);
|
|
if(entry.data) {
|
|
data.appendByteArray(entry.data)
|
|
}
|
|
return data
|
|
}
|
|
function writeCODEntry(entry, offset) {
|
|
var data = new core.ByteArrayWriter("utf8"), length = 0;
|
|
data.appendArray([80, 75, 1, 2, 20, 0, 20, 0, 0, 0, 0, 0]);
|
|
if(entry.data) {
|
|
length = entry.data.length
|
|
}
|
|
data.appendUInt32LE(date2DosTime(entry.date));
|
|
data.appendUInt32LE(entry.data ? crc32(entry.data) : 0);
|
|
data.appendUInt32LE(length);
|
|
data.appendUInt32LE(length);
|
|
data.appendUInt16LE(entry.filename.length);
|
|
data.appendArray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
data.appendUInt32LE(offset);
|
|
data.appendString(entry.filename);
|
|
return data
|
|
}
|
|
function loadAllEntries(position, callback) {
|
|
if(position === entries.length) {
|
|
callback(null);
|
|
return
|
|
}
|
|
var entry = entries[position];
|
|
if(entry.data !== null) {
|
|
loadAllEntries(position + 1, callback);
|
|
return
|
|
}
|
|
entry.load(function(err) {
|
|
if(err) {
|
|
callback(err);
|
|
return
|
|
}
|
|
loadAllEntries(position + 1, callback)
|
|
})
|
|
}
|
|
function createByteArray(successCallback, errorCallback) {
|
|
loadAllEntries(0, function(err) {
|
|
if(err) {
|
|
errorCallback(err);
|
|
return
|
|
}
|
|
var i, e, codoffset, codsize, data = new core.ByteArrayWriter("utf8"), offsets = [0];
|
|
for(i = 0;i < entries.length;i += 1) {
|
|
data.appendByteArrayWriter(writeEntry(entries[i]));
|
|
offsets.push(data.getLength())
|
|
}
|
|
codoffset = data.getLength();
|
|
for(i = 0;i < entries.length;i += 1) {
|
|
e = entries[i];
|
|
data.appendByteArrayWriter(writeCODEntry(e, offsets[i]))
|
|
}
|
|
codsize = data.getLength() - codoffset;
|
|
data.appendArray([80, 75, 5, 6, 0, 0, 0, 0]);
|
|
data.appendUInt16LE(entries.length);
|
|
data.appendUInt16LE(entries.length);
|
|
data.appendUInt32LE(codsize);
|
|
data.appendUInt32LE(codoffset);
|
|
data.appendArray([0, 0]);
|
|
successCallback(data.getByteArray())
|
|
})
|
|
}
|
|
function writeAs(newurl, callback) {
|
|
createByteArray(function(data) {
|
|
runtime.writeFile(newurl, data, callback)
|
|
}, callback)
|
|
}
|
|
function write(callback) {
|
|
writeAs(url, callback)
|
|
}
|
|
this.load = load;
|
|
this.save = save;
|
|
this.remove = remove;
|
|
this.write = write;
|
|
this.writeAs = writeAs;
|
|
this.createByteArray = createByteArray;
|
|
this.loadContentXmlAsFragments = loadContentXmlAsFragments;
|
|
this.loadAsString = loadAsString;
|
|
this.loadAsDOM = loadAsDOM;
|
|
this.loadAsDataURL = loadAsDataURL;
|
|
this.getEntries = function() {
|
|
return entries.slice()
|
|
};
|
|
filesize = -1;
|
|
if(entriesReadCallback === null) {
|
|
entries = [];
|
|
return
|
|
}
|
|
runtime.getFileSize(url, function(size) {
|
|
filesize = size;
|
|
if(filesize < 0) {
|
|
entriesReadCallback("File '" + url + "' cannot be read.", zip)
|
|
}else {
|
|
runtime.read(url, filesize - 22, 22, function(err, data) {
|
|
if(err || (entriesReadCallback === null || data === null)) {
|
|
entriesReadCallback(err, zip)
|
|
}else {
|
|
handleCentralDirectoryEnd(data, entriesReadCallback)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
};
|
|
xmldom.LSSerializerFilter = function LSSerializerFilter() {
|
|
};
|
|
xmldom.LSSerializerFilter.prototype.acceptNode = function(node) {
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.OdfNodeFilter = function OdfNodeFilter() {
|
|
this.acceptNode = function(node) {
|
|
var result;
|
|
if(node.namespaceURI === "http://www.w3.org/1999/xhtml") {
|
|
result = NodeFilter.FILTER_SKIP
|
|
}else {
|
|
if(node.namespaceURI && node.namespaceURI.match(/^urn:webodf:/)) {
|
|
result = NodeFilter.FILTER_REJECT
|
|
}else {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.Namespaces = {namespaceMap:{db:"urn:oasis:names:tc:opendocument:xmlns:database:1.0", dc:"http://purl.org/dc/elements/1.1/", dr3d:"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0", draw:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", chart:"urn:oasis:names:tc:opendocument:xmlns:chart:1.0", fo:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", form:"urn:oasis:names:tc:opendocument:xmlns:form:1.0", meta:"urn:oasis:names:tc:opendocument:xmlns:meta:1.0", number:"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
|
|
office:"urn:oasis:names:tc:opendocument:xmlns:office:1.0", presentation:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", style:"urn:oasis:names:tc:opendocument:xmlns:style:1.0", svg:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0", table:"urn:oasis:names:tc:opendocument:xmlns:table:1.0", text:"urn:oasis:names:tc:opendocument:xmlns:text:1.0", xlink:"http://www.w3.org/1999/xlink", xml:"http://www.w3.org/XML/1998/namespace"}, prefixMap:{}, dbns:"urn:oasis:names:tc:opendocument:xmlns:database:1.0",
|
|
dcns:"http://purl.org/dc/elements/1.1/", dr3dns:"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0", drawns:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", chartns:"urn:oasis:names:tc:opendocument:xmlns:chart:1.0", fons:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", formns:"urn:oasis:names:tc:opendocument:xmlns:form:1.0", metans:"urn:oasis:names:tc:opendocument:xmlns:meta:1.0", numberns:"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0", officens:"urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
|
presentationns:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", stylens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0", svgns:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0", tablens:"urn:oasis:names:tc:opendocument:xmlns:table:1.0", textns:"urn:oasis:names:tc:opendocument:xmlns:text:1.0", xlinkns:"http://www.w3.org/1999/xlink", xmlns:"http://www.w3.org/XML/1998/namespace"};
|
|
(function() {
|
|
var map = odf.Namespaces.namespaceMap, pmap = odf.Namespaces.prefixMap, prefix;
|
|
for(prefix in map) {
|
|
if(map.hasOwnProperty(prefix)) {
|
|
pmap[map[prefix]] = prefix
|
|
}
|
|
}
|
|
})();
|
|
odf.Namespaces.forEachPrefix = function forEachPrefix(cb) {
|
|
var ns = odf.Namespaces.namespaceMap, prefix;
|
|
for(prefix in ns) {
|
|
if(ns.hasOwnProperty(prefix)) {
|
|
cb(prefix, ns[prefix])
|
|
}
|
|
}
|
|
};
|
|
odf.Namespaces.lookupNamespaceURI = function lookupNamespaceURI(prefix) {
|
|
var r = null;
|
|
if(odf.Namespaces.namespaceMap.hasOwnProperty(prefix)) {
|
|
r = (odf.Namespaces.namespaceMap[prefix])
|
|
}
|
|
return r
|
|
};
|
|
odf.Namespaces.lookupPrefix = function lookupPrefix(namespaceURI) {
|
|
var map = odf.Namespaces.prefixMap;
|
|
return map.hasOwnProperty(namespaceURI) ? map[namespaceURI] : null
|
|
};
|
|
odf.Namespaces.lookupNamespaceURI.lookupNamespaceURI = odf.Namespaces.lookupNamespaceURI;
|
|
xmldom.XPathIterator = function XPathIterator() {
|
|
};
|
|
xmldom.XPathIterator.prototype.next = function() {
|
|
};
|
|
xmldom.XPathIterator.prototype.reset = function() {
|
|
};
|
|
xmldom.XPathAtom;
|
|
function createXPathSingleton() {
|
|
var createXPathPathIterator, parsePredicates;
|
|
function isSmallestPositive(a, b, c) {
|
|
return a !== -1 && ((a < b || b === -1) && (a < c || c === -1))
|
|
}
|
|
function parseXPathStep(xpath, pos, end, steps) {
|
|
var location = "", predicates = [], brapos = xpath.indexOf("[", pos), slapos = xpath.indexOf("/", pos), eqpos = xpath.indexOf("=", pos);
|
|
if(isSmallestPositive(slapos, brapos, eqpos)) {
|
|
location = xpath.substring(pos, slapos);
|
|
pos = slapos + 1
|
|
}else {
|
|
if(isSmallestPositive(brapos, slapos, eqpos)) {
|
|
location = xpath.substring(pos, brapos);
|
|
pos = parsePredicates(xpath, brapos, predicates)
|
|
}else {
|
|
if(isSmallestPositive(eqpos, slapos, brapos)) {
|
|
location = xpath.substring(pos, eqpos);
|
|
pos = eqpos
|
|
}else {
|
|
location = xpath.substring(pos, end);
|
|
pos = end
|
|
}
|
|
}
|
|
}
|
|
steps.push({location:location, predicates:predicates});
|
|
return pos
|
|
}
|
|
function parseXPath(xpath) {
|
|
var steps = [], p = 0, end = xpath.length, value;
|
|
while(p < end) {
|
|
p = parseXPathStep(xpath, p, end, steps);
|
|
if(p < end && xpath[p] === "=") {
|
|
value = xpath.substring(p + 1, end);
|
|
if(value.length > 2 && (value[0] === "'" || value[0] === '"')) {
|
|
value = value.slice(1, value.length - 1)
|
|
}else {
|
|
try {
|
|
value = parseInt(value, 10)
|
|
}catch(ignore) {
|
|
}
|
|
}
|
|
p = end
|
|
}
|
|
}
|
|
return{steps:steps, value:value}
|
|
}
|
|
parsePredicates = function parsePredicates(xpath, start, predicates) {
|
|
var pos = start, l = xpath.length, depth = 0;
|
|
while(pos < l) {
|
|
if(xpath[pos] === "]") {
|
|
depth -= 1;
|
|
if(depth <= 0) {
|
|
predicates.push(parseXPath(xpath.substring(start, pos)))
|
|
}
|
|
}else {
|
|
if(xpath[pos] === "[") {
|
|
if(depth <= 0) {
|
|
start = pos + 1
|
|
}
|
|
depth += 1
|
|
}
|
|
}
|
|
pos += 1
|
|
}
|
|
return pos
|
|
};
|
|
function XPathNodeIterator() {
|
|
var node = null, done = false;
|
|
this.setNode = function setNode(n) {
|
|
node = n
|
|
};
|
|
this.reset = function() {
|
|
done = false
|
|
};
|
|
this.next = function next() {
|
|
var val = done ? null : node;
|
|
done = true;
|
|
return val
|
|
}
|
|
}
|
|
function AttributeIterator(it, namespace, localName) {
|
|
this.reset = function reset() {
|
|
it.reset()
|
|
};
|
|
this.next = function next() {
|
|
var node = it.next();
|
|
while(node) {
|
|
if(node.nodeType === Node.ELEMENT_NODE) {
|
|
node = (node).getAttributeNodeNS(namespace, localName)
|
|
}
|
|
if(node) {
|
|
return node
|
|
}
|
|
node = it.next()
|
|
}
|
|
return node
|
|
}
|
|
}
|
|
function AllChildElementIterator(it, recurse) {
|
|
var root = it.next(), node = null;
|
|
this.reset = function reset() {
|
|
it.reset();
|
|
root = it.next();
|
|
node = null
|
|
};
|
|
this.next = function next() {
|
|
while(root) {
|
|
if(node) {
|
|
if(recurse && node.firstChild) {
|
|
node = node.firstChild
|
|
}else {
|
|
while(!node.nextSibling && node !== root) {
|
|
node = node.parentNode
|
|
}
|
|
if(node === root) {
|
|
root = it.next()
|
|
}else {
|
|
node = node.nextSibling
|
|
}
|
|
}
|
|
}else {
|
|
do {
|
|
node = root.firstChild;
|
|
if(!node) {
|
|
root = it.next()
|
|
}
|
|
}while(root && !node)
|
|
}
|
|
if(node && node.nodeType === Node.ELEMENT_NODE) {
|
|
return node
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
function ConditionIterator(it, condition) {
|
|
this.reset = function reset() {
|
|
it.reset()
|
|
};
|
|
this.next = function next() {
|
|
var n = it.next();
|
|
while(n && !condition(n)) {
|
|
n = it.next()
|
|
}
|
|
return n
|
|
}
|
|
}
|
|
function createNodenameFilter(it, name, namespaceResolver) {
|
|
var s = name.split(":", 2), namespace = namespaceResolver(s[0]), localName = s[1];
|
|
return new ConditionIterator(it, function(node) {
|
|
return node.localName === localName && node.namespaceURI === namespace
|
|
})
|
|
}
|
|
function createPredicateFilteredIterator(it, p, namespaceResolver) {
|
|
var nit = new XPathNodeIterator, pit = createXPathPathIterator(nit, p, namespaceResolver), value = p.value;
|
|
if(value === undefined) {
|
|
return new ConditionIterator(it, function(node) {
|
|
nit.setNode(node);
|
|
pit.reset();
|
|
return pit.next() !== null
|
|
})
|
|
}
|
|
return new ConditionIterator(it, function(node) {
|
|
nit.setNode(node);
|
|
pit.reset();
|
|
var n = pit.next();
|
|
return n ? n.nodeValue === value : false
|
|
})
|
|
}
|
|
function item(p, i) {
|
|
return p[i]
|
|
}
|
|
createXPathPathIterator = function createXPathPathIterator(it, xpath, namespaceResolver) {
|
|
var i, j, step, location, s, p, ns;
|
|
for(i = 0;i < xpath.steps.length;i += 1) {
|
|
step = xpath.steps[i];
|
|
location = step.location;
|
|
if(location === "") {
|
|
it = new AllChildElementIterator(it, false)
|
|
}else {
|
|
if(location[0] === "@") {
|
|
s = location.substr(1).split(":", 2);
|
|
ns = namespaceResolver(s[0]);
|
|
if(!ns) {
|
|
throw"No namespace associated with the prefix " + s[0];
|
|
}
|
|
it = new AttributeIterator(it, ns, s[1])
|
|
}else {
|
|
if(location !== ".") {
|
|
it = new AllChildElementIterator(it, false);
|
|
if(location.indexOf(":") !== -1) {
|
|
it = createNodenameFilter(it, location, namespaceResolver)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(j = 0;j < step.predicates.length;j += 1) {
|
|
p = item(step.predicates, j);
|
|
it = createPredicateFilteredIterator(it, p, namespaceResolver)
|
|
}
|
|
}
|
|
return it
|
|
};
|
|
function fallback(node, xpath, namespaceResolver) {
|
|
var it = new XPathNodeIterator, i, nodelist, parsedXPath;
|
|
it.setNode(node);
|
|
parsedXPath = parseXPath(xpath);
|
|
it = createXPathPathIterator(it, parsedXPath, namespaceResolver);
|
|
nodelist = [];
|
|
i = it.next();
|
|
while(i) {
|
|
nodelist.push(i);
|
|
i = it.next()
|
|
}
|
|
return nodelist
|
|
}
|
|
function getODFElementsWithXPath(node, xpath, namespaceResolver) {
|
|
var doc = node.ownerDocument, nodes, elements = [], n = null;
|
|
if(!doc || typeof doc.evaluate !== "function") {
|
|
elements = fallback(node, xpath, namespaceResolver)
|
|
}else {
|
|
nodes = doc.evaluate(xpath, node, namespaceResolver, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
|
|
n = nodes.iterateNext();
|
|
while(n !== null) {
|
|
if(n.nodeType === Node.ELEMENT_NODE) {
|
|
elements.push(n)
|
|
}
|
|
n = nodes.iterateNext()
|
|
}
|
|
}
|
|
return elements
|
|
}
|
|
return{getODFElementsWithXPath:getODFElementsWithXPath}
|
|
}
|
|
xmldom.XPath = createXPathSingleton();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.StyleInfo = function StyleInfo() {
|
|
var chartns = odf.Namespaces.chartns, dbns = odf.Namespaces.dbns, dr3dns = odf.Namespaces.dr3dns, drawns = odf.Namespaces.drawns, formns = odf.Namespaces.formns, numberns = odf.Namespaces.numberns, officens = odf.Namespaces.officens, presentationns = odf.Namespaces.presentationns, stylens = odf.Namespaces.stylens, tablens = odf.Namespaces.tablens, textns = odf.Namespaces.textns, nsprefixes = {"urn:oasis:names:tc:opendocument:xmlns:chart:1.0":"chart:", "urn:oasis:names:tc:opendocument:xmlns:database:1.0":"db:",
|
|
"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0":"dr3d:", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0":"draw:", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0":"fo:", "urn:oasis:names:tc:opendocument:xmlns:form:1.0":"form:", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0":"number:", "urn:oasis:names:tc:opendocument:xmlns:office:1.0":"office:", "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0":"presentation:", "urn:oasis:names:tc:opendocument:xmlns:style:1.0":"style:",
|
|
"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0":"svg:", "urn:oasis:names:tc:opendocument:xmlns:table:1.0":"table:", "urn:oasis:names:tc:opendocument:xmlns:text:1.0":"chart:", "http://www.w3.org/XML/1998/namespace":"xml:"}, elementstyles = {"text":[{ens:stylens, en:"tab-stop", ans:stylens, a:"leader-text-style"}, {ens:stylens, en:"drop-cap", ans:stylens, a:"style-name"}, {ens:textns, en:"notes-configuration", ans:textns, a:"citation-body-style-name"}, {ens:textns, en:"notes-configuration",
|
|
ans:textns, a:"citation-style-name"}, {ens:textns, en:"a", ans:textns, a:"style-name"}, {ens:textns, en:"alphabetical-index", ans:textns, a:"style-name"}, {ens:textns, en:"linenumbering-configuration", ans:textns, a:"style-name"}, {ens:textns, en:"list-level-style-number", ans:textns, a:"style-name"}, {ens:textns, en:"ruby-text", ans:textns, a:"style-name"}, {ens:textns, en:"span", ans:textns, a:"style-name"}, {ens:textns, en:"a", ans:textns, a:"visited-style-name"}, {ens:stylens, en:"text-properties",
|
|
ans:stylens, a:"text-line-through-text-style"}, {ens:textns, en:"alphabetical-index-source", ans:textns, a:"main-entry-style-name"}, {ens:textns, en:"index-entry-bibliography", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-chapter", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-link-end", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-link-start", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-page-number", ans:textns, a:"style-name"}, {ens:textns,
|
|
en:"index-entry-span", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-tab-stop", ans:textns, a:"style-name"}, {ens:textns, en:"index-entry-text", ans:textns, a:"style-name"}, {ens:textns, en:"index-title-template", ans:textns, a:"style-name"}, {ens:textns, en:"list-level-style-bullet", ans:textns, a:"style-name"}, {ens:textns, en:"outline-level-style", ans:textns, a:"style-name"}], "paragraph":[{ens:drawns, en:"caption", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"circle", ans:drawns,
|
|
a:"text-style-name"}, {ens:drawns, en:"connector", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"control", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"custom-shape", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"ellipse", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"frame", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"line", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"measure", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"path", ans:drawns, a:"text-style-name"},
|
|
{ens:drawns, en:"polygon", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"polyline", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"rect", ans:drawns, a:"text-style-name"}, {ens:drawns, en:"regular-polygon", ans:drawns, a:"text-style-name"}, {ens:officens, en:"annotation", ans:drawns, a:"text-style-name"}, {ens:formns, en:"column", ans:formns, a:"text-style-name"}, {ens:stylens, en:"style", ans:stylens, a:"next-style-name"}, {ens:tablens, en:"body", ans:tablens, a:"paragraph-style-name"},
|
|
{ens:tablens, en:"even-columns", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"even-rows", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"first-column", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"first-row", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"last-column", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"last-row", ans:tablens, a:"paragraph-style-name"}, {ens:tablens, en:"odd-columns", ans:tablens, a:"paragraph-style-name"},
|
|
{ens:tablens, en:"odd-rows", ans:tablens, a:"paragraph-style-name"}, {ens:textns, en:"notes-configuration", ans:textns, a:"default-style-name"}, {ens:textns, en:"alphabetical-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"bibliography-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"h", ans:textns, a:"style-name"}, {ens:textns, en:"illustration-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"index-source-style", ans:textns, a:"style-name"},
|
|
{ens:textns, en:"object-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"p", ans:textns, a:"style-name"}, {ens:textns, en:"table-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"table-of-content-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"table-index-entry-template", ans:textns, a:"style-name"}, {ens:textns, en:"user-index-entry-template", ans:textns, a:"style-name"}, {ens:stylens, en:"page-layout-properties", ans:stylens, a:"register-truth-ref-style-name"}],
|
|
"chart":[{ens:chartns, en:"axis", ans:chartns, a:"style-name"}, {ens:chartns, en:"chart", ans:chartns, a:"style-name"}, {ens:chartns, en:"data-label", ans:chartns, a:"style-name"}, {ens:chartns, en:"data-point", ans:chartns, a:"style-name"}, {ens:chartns, en:"equation", ans:chartns, a:"style-name"}, {ens:chartns, en:"error-indicator", ans:chartns, a:"style-name"}, {ens:chartns, en:"floor", ans:chartns, a:"style-name"}, {ens:chartns, en:"footer", ans:chartns, a:"style-name"}, {ens:chartns, en:"grid",
|
|
ans:chartns, a:"style-name"}, {ens:chartns, en:"legend", ans:chartns, a:"style-name"}, {ens:chartns, en:"mean-value", ans:chartns, a:"style-name"}, {ens:chartns, en:"plot-area", ans:chartns, a:"style-name"}, {ens:chartns, en:"regression-curve", ans:chartns, a:"style-name"}, {ens:chartns, en:"series", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-gain-marker", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-loss-marker", ans:chartns, a:"style-name"}, {ens:chartns, en:"stock-range-line",
|
|
ans:chartns, a:"style-name"}, {ens:chartns, en:"subtitle", ans:chartns, a:"style-name"}, {ens:chartns, en:"title", ans:chartns, a:"style-name"}, {ens:chartns, en:"wall", ans:chartns, a:"style-name"}], "section":[{ens:textns, en:"alphabetical-index", ans:textns, a:"style-name"}, {ens:textns, en:"bibliography", ans:textns, a:"style-name"}, {ens:textns, en:"illustration-index", ans:textns, a:"style-name"}, {ens:textns, en:"index-title", ans:textns, a:"style-name"}, {ens:textns, en:"object-index",
|
|
ans:textns, a:"style-name"}, {ens:textns, en:"section", ans:textns, a:"style-name"}, {ens:textns, en:"table-of-content", ans:textns, a:"style-name"}, {ens:textns, en:"table-index", ans:textns, a:"style-name"}, {ens:textns, en:"user-index", ans:textns, a:"style-name"}], "ruby":[{ens:textns, en:"ruby", ans:textns, a:"style-name"}], "table":[{ens:dbns, en:"query", ans:dbns, a:"style-name"}, {ens:dbns, en:"table-representation", ans:dbns, a:"style-name"}, {ens:tablens, en:"background", ans:tablens,
|
|
a:"style-name"}, {ens:tablens, en:"table", ans:tablens, a:"style-name"}], "table-column":[{ens:dbns, en:"column", ans:dbns, a:"style-name"}, {ens:tablens, en:"table-column", ans:tablens, a:"style-name"}], "table-row":[{ens:dbns, en:"query", ans:dbns, a:"default-row-style-name"}, {ens:dbns, en:"table-representation", ans:dbns, a:"default-row-style-name"}, {ens:tablens, en:"table-row", ans:tablens, a:"style-name"}], "table-cell":[{ens:dbns, en:"column", ans:dbns, a:"default-cell-style-name"}, {ens:tablens,
|
|
en:"table-column", ans:tablens, a:"default-cell-style-name"}, {ens:tablens, en:"table-row", ans:tablens, a:"default-cell-style-name"}, {ens:tablens, en:"body", ans:tablens, a:"style-name"}, {ens:tablens, en:"covered-table-cell", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"covered-table-cell", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"even-rows", ans:tablens, a:"style-name"},
|
|
{ens:tablens, en:"first-column", ans:tablens, a:"style-name"}, {ens:tablens, en:"first-row", ans:tablens, a:"style-name"}, {ens:tablens, en:"last-column", ans:tablens, a:"style-name"}, {ens:tablens, en:"last-row", ans:tablens, a:"style-name"}, {ens:tablens, en:"odd-columns", ans:tablens, a:"style-name"}, {ens:tablens, en:"odd-rows", ans:tablens, a:"style-name"}, {ens:tablens, en:"table-cell", ans:tablens, a:"style-name"}], "graphic":[{ens:dr3dns, en:"cube", ans:drawns, a:"style-name"}, {ens:dr3dns,
|
|
en:"extrude", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"rotate", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"scene", ans:drawns, a:"style-name"}, {ens:dr3dns, en:"sphere", ans:drawns, a:"style-name"}, {ens:drawns, en:"caption", ans:drawns, a:"style-name"}, {ens:drawns, en:"circle", ans:drawns, a:"style-name"}, {ens:drawns, en:"connector", ans:drawns, a:"style-name"}, {ens:drawns, en:"control", ans:drawns, a:"style-name"}, {ens:drawns, en:"custom-shape", ans:drawns, a:"style-name"}, {ens:drawns,
|
|
en:"ellipse", ans:drawns, a:"style-name"}, {ens:drawns, en:"frame", ans:drawns, a:"style-name"}, {ens:drawns, en:"g", ans:drawns, a:"style-name"}, {ens:drawns, en:"line", ans:drawns, a:"style-name"}, {ens:drawns, en:"measure", ans:drawns, a:"style-name"}, {ens:drawns, en:"page-thumbnail", ans:drawns, a:"style-name"}, {ens:drawns, en:"path", ans:drawns, a:"style-name"}, {ens:drawns, en:"polygon", ans:drawns, a:"style-name"}, {ens:drawns, en:"polyline", ans:drawns, a:"style-name"}, {ens:drawns, en:"rect",
|
|
ans:drawns, a:"style-name"}, {ens:drawns, en:"regular-polygon", ans:drawns, a:"style-name"}, {ens:officens, en:"annotation", ans:drawns, a:"style-name"}], "presentation":[{ens:dr3dns, en:"cube", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"extrude", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"rotate", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"scene", ans:presentationns, a:"style-name"}, {ens:dr3dns, en:"sphere", ans:presentationns, a:"style-name"}, {ens:drawns, en:"caption",
|
|
ans:presentationns, a:"style-name"}, {ens:drawns, en:"circle", ans:presentationns, a:"style-name"}, {ens:drawns, en:"connector", ans:presentationns, a:"style-name"}, {ens:drawns, en:"control", ans:presentationns, a:"style-name"}, {ens:drawns, en:"custom-shape", ans:presentationns, a:"style-name"}, {ens:drawns, en:"ellipse", ans:presentationns, a:"style-name"}, {ens:drawns, en:"frame", ans:presentationns, a:"style-name"}, {ens:drawns, en:"g", ans:presentationns, a:"style-name"}, {ens:drawns, en:"line",
|
|
ans:presentationns, a:"style-name"}, {ens:drawns, en:"measure", ans:presentationns, a:"style-name"}, {ens:drawns, en:"page-thumbnail", ans:presentationns, a:"style-name"}, {ens:drawns, en:"path", ans:presentationns, a:"style-name"}, {ens:drawns, en:"polygon", ans:presentationns, a:"style-name"}, {ens:drawns, en:"polyline", ans:presentationns, a:"style-name"}, {ens:drawns, en:"rect", ans:presentationns, a:"style-name"}, {ens:drawns, en:"regular-polygon", ans:presentationns, a:"style-name"}, {ens:officens,
|
|
en:"annotation", ans:presentationns, a:"style-name"}], "drawing-page":[{ens:drawns, en:"page", ans:drawns, a:"style-name"}, {ens:presentationns, en:"notes", ans:drawns, a:"style-name"}, {ens:stylens, en:"handout-master", ans:drawns, a:"style-name"}, {ens:stylens, en:"master-page", ans:drawns, a:"style-name"}], "list-style":[{ens:textns, en:"list", ans:textns, a:"style-name"}, {ens:textns, en:"numbered-paragraph", ans:textns, a:"style-name"}, {ens:textns, en:"list-item", ans:textns, a:"style-override"},
|
|
{ens:stylens, en:"style", ans:stylens, a:"list-style-name"}], "data":[{ens:stylens, en:"style", ans:stylens, a:"data-style-name"}, {ens:stylens, en:"style", ans:stylens, a:"percentage-data-style-name"}, {ens:presentationns, en:"date-time-decl", ans:stylens, a:"data-style-name"}, {ens:textns, en:"creation-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"creation-time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"database-display", ans:stylens, a:"data-style-name"}, {ens:textns,
|
|
en:"date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"editing-duration", ans:stylens, a:"data-style-name"}, {ens:textns, en:"expression", ans:stylens, a:"data-style-name"}, {ens:textns, en:"meta-field", ans:stylens, a:"data-style-name"}, {ens:textns, en:"modification-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"modification-time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"print-date", ans:stylens, a:"data-style-name"}, {ens:textns, en:"print-time", ans:stylens,
|
|
a:"data-style-name"}, {ens:textns, en:"table-formula", ans:stylens, a:"data-style-name"}, {ens:textns, en:"time", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-defined", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-field-get", ans:stylens, a:"data-style-name"}, {ens:textns, en:"user-field-input", ans:stylens, a:"data-style-name"}, {ens:textns, en:"variable-get", ans:stylens, a:"data-style-name"}, {ens:textns, en:"variable-input", ans:stylens, a:"data-style-name"}, {ens:textns,
|
|
en:"variable-set", ans:stylens, a:"data-style-name"}], "page-layout":[{ens:presentationns, en:"notes", ans:stylens, a:"page-layout-name"}, {ens:stylens, en:"handout-master", ans:stylens, a:"page-layout-name"}, {ens:stylens, en:"master-page", ans:stylens, a:"page-layout-name"}]}, elements, xpath = xmldom.XPath;
|
|
function hasDerivedStyles(odfbody, nsResolver, styleElement) {
|
|
var nodes, xp, styleName = styleElement.getAttributeNS(stylens, "name"), styleFamily = styleElement.getAttributeNS(stylens, "family");
|
|
xp = "//style:*[@style:parent-style-name='" + styleName + "'][@style:family='" + styleFamily + "']";
|
|
nodes = xpath.getODFElementsWithXPath(odfbody, xp, nsResolver);
|
|
if(nodes.length) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
function prefixUsedStyleNames(element, prefix) {
|
|
var i, stylename, a, e, ns, elname, elns, localName, length = 0;
|
|
elname = elements[element.localName];
|
|
if(elname) {
|
|
elns = elname[element.namespaceURI];
|
|
if(elns) {
|
|
length = elns.length
|
|
}
|
|
}
|
|
for(i = 0;i < length;i += 1) {
|
|
a = (elns[i]);
|
|
ns = a.ns;
|
|
localName = a.localname;
|
|
stylename = element.getAttributeNS(ns, localName);
|
|
if(stylename) {
|
|
element.setAttributeNS(ns, nsprefixes[ns] + localName, prefix + stylename)
|
|
}
|
|
}
|
|
e = element.firstElementChild;
|
|
while(e) {
|
|
prefixUsedStyleNames(e, prefix);
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
function prefixStyleName(styleElement, prefix) {
|
|
var stylename = styleElement.getAttributeNS(drawns, "name"), ns;
|
|
if(stylename) {
|
|
ns = drawns
|
|
}else {
|
|
stylename = styleElement.getAttributeNS(stylens, "name");
|
|
if(stylename) {
|
|
ns = stylens
|
|
}
|
|
}
|
|
if(ns) {
|
|
styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", prefix + stylename)
|
|
}
|
|
}
|
|
function prefixStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) {
|
|
var s;
|
|
if(styleElementsRoot) {
|
|
s = styleElementsRoot.firstChild;
|
|
while(s) {
|
|
if(s.nodeType === Node.ELEMENT_NODE) {
|
|
prefixStyleName((s), prefix)
|
|
}
|
|
s = s.nextSibling
|
|
}
|
|
prefixUsedStyleNames(styleElementsRoot, prefix);
|
|
if(styleUsingElementsRoot) {
|
|
prefixUsedStyleNames(styleUsingElementsRoot, prefix)
|
|
}
|
|
}
|
|
}
|
|
function removeRegExpFromUsedStyleNames(element, regExp) {
|
|
var i, stylename, e, elname, elns, a, ns, localName, length = 0;
|
|
elname = elements[element.localName];
|
|
if(elname) {
|
|
elns = elname[element.namespaceURI];
|
|
if(elns) {
|
|
length = elns.length
|
|
}
|
|
}
|
|
for(i = 0;i < length;i += 1) {
|
|
a = (elns[i]);
|
|
ns = a.ns;
|
|
localName = a.localname;
|
|
stylename = element.getAttributeNS(ns, localName);
|
|
if(stylename) {
|
|
stylename = stylename.replace(regExp, "");
|
|
element.setAttributeNS(ns, nsprefixes[ns] + localName, stylename)
|
|
}
|
|
}
|
|
e = element.firstElementChild;
|
|
while(e) {
|
|
removeRegExpFromUsedStyleNames(e, regExp);
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
function removeRegExpFromStyleName(styleElement, regExp) {
|
|
var stylename = styleElement.getAttributeNS(drawns, "name"), ns;
|
|
if(stylename) {
|
|
ns = drawns
|
|
}else {
|
|
stylename = styleElement.getAttributeNS(stylens, "name");
|
|
if(stylename) {
|
|
ns = stylens
|
|
}
|
|
}
|
|
if(ns) {
|
|
stylename = stylename.replace(regExp, "");
|
|
styleElement.setAttributeNS(ns, nsprefixes[ns] + "name", stylename)
|
|
}
|
|
}
|
|
function removePrefixFromStyleNames(styleElementsRoot, prefix, styleUsingElementsRoot) {
|
|
var s, regExp = new RegExp("^" + prefix);
|
|
if(styleElementsRoot) {
|
|
s = styleElementsRoot.firstChild;
|
|
while(s) {
|
|
if(s.nodeType === Node.ELEMENT_NODE) {
|
|
removeRegExpFromStyleName((s), regExp)
|
|
}
|
|
s = s.nextSibling
|
|
}
|
|
removeRegExpFromUsedStyleNames(styleElementsRoot, regExp);
|
|
if(styleUsingElementsRoot) {
|
|
removeRegExpFromUsedStyleNames(styleUsingElementsRoot, regExp)
|
|
}
|
|
}
|
|
}
|
|
function determineStylesForNode(element, usedStyles) {
|
|
var i, stylename, elname, elns, a, ns, localName, keyname, length = 0, map;
|
|
elname = elements[element.localName];
|
|
if(elname) {
|
|
elns = elname[element.namespaceURI];
|
|
if(elns) {
|
|
length = elns.length
|
|
}
|
|
}
|
|
for(i = 0;i < length;i += 1) {
|
|
a = (elns[i]);
|
|
ns = a.ns;
|
|
localName = a.localname;
|
|
stylename = element.getAttributeNS(ns, localName);
|
|
if(stylename) {
|
|
usedStyles = usedStyles || {};
|
|
keyname = a.keyname;
|
|
if(usedStyles.hasOwnProperty(keyname)) {
|
|
usedStyles[keyname][stylename] = 1
|
|
}else {
|
|
map = {};
|
|
map[stylename] = 1;
|
|
usedStyles[keyname] = map
|
|
}
|
|
}
|
|
}
|
|
return usedStyles
|
|
}
|
|
function determineUsedStyles(styleUsingElementsRoot, usedStyles) {
|
|
var i, e;
|
|
determineStylesForNode(styleUsingElementsRoot, usedStyles);
|
|
i = styleUsingElementsRoot.firstChild;
|
|
while(i) {
|
|
if(i.nodeType === Node.ELEMENT_NODE) {
|
|
e = (i);
|
|
determineUsedStyles(e, usedStyles)
|
|
}
|
|
i = i.nextSibling
|
|
}
|
|
}
|
|
function StyleDefinition(key, name, family) {
|
|
this.key = key;
|
|
this.name = name;
|
|
this.family = family;
|
|
this.requires = {}
|
|
}
|
|
function getStyleDefinition(stylename, stylefamily, knownStyles) {
|
|
var styleKey = stylename + '"' + stylefamily, styleDefinition = knownStyles[styleKey];
|
|
if(!styleDefinition) {
|
|
styleDefinition = knownStyles[styleKey] = new StyleDefinition(styleKey, stylename, stylefamily)
|
|
}
|
|
return styleDefinition
|
|
}
|
|
function determineDependentStyles(element, styleScope, knownStyles) {
|
|
var i, stylename, elname, elns, a, ns, localName, e, referencedStyleFamily, referencedStyleDef, length = 0, newScopeName = element.getAttributeNS(stylens, "name"), newScopeFamily = element.getAttributeNS(stylens, "family");
|
|
if(newScopeName && newScopeFamily) {
|
|
styleScope = getStyleDefinition(newScopeName, newScopeFamily, knownStyles)
|
|
}
|
|
if(styleScope) {
|
|
elname = elements[element.localName];
|
|
if(elname) {
|
|
elns = elname[element.namespaceURI];
|
|
if(elns) {
|
|
length = elns.length
|
|
}
|
|
}
|
|
for(i = 0;i < length;i += 1) {
|
|
a = (elns[i]);
|
|
ns = a.ns;
|
|
localName = a.localname;
|
|
stylename = element.getAttributeNS(ns, localName);
|
|
if(stylename) {
|
|
referencedStyleFamily = a.keyname;
|
|
referencedStyleDef = getStyleDefinition(stylename, referencedStyleFamily, knownStyles);
|
|
styleScope.requires[referencedStyleDef.key] = referencedStyleDef
|
|
}
|
|
}
|
|
}
|
|
e = element.firstElementChild;
|
|
while(e) {
|
|
determineDependentStyles(e, styleScope, knownStyles);
|
|
e = e.nextElementSibling
|
|
}
|
|
return knownStyles
|
|
}
|
|
function inverse() {
|
|
var i, l, keyname, list, item, e = {}, map, array, en, ens;
|
|
for(keyname in elementstyles) {
|
|
if(elementstyles.hasOwnProperty(keyname)) {
|
|
list = elementstyles[keyname];
|
|
l = list.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
item = list[i];
|
|
en = item.en;
|
|
ens = item.ens;
|
|
if(e.hasOwnProperty(en)) {
|
|
map = e[en]
|
|
}else {
|
|
e[en] = map = {}
|
|
}
|
|
if(map.hasOwnProperty(ens)) {
|
|
array = map[ens]
|
|
}else {
|
|
map[ens] = array = []
|
|
}
|
|
array.push({ns:item.ans, localname:item.a, keyname:keyname})
|
|
}
|
|
}
|
|
}
|
|
return e
|
|
}
|
|
function mergeRequiredStyles(styleDependency, usedStyles) {
|
|
var family = usedStyles[styleDependency.family];
|
|
if(!family) {
|
|
family = usedStyles[styleDependency.family] = {}
|
|
}
|
|
family[styleDependency.name] = 1;
|
|
Object.keys((styleDependency.requires)).forEach(function(requiredStyleKey) {
|
|
mergeRequiredStyles((styleDependency.requires[requiredStyleKey]), usedStyles)
|
|
})
|
|
}
|
|
function mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles) {
|
|
var automaticStyles = determineDependentStyles(automaticStylesRoot, null, {});
|
|
Object.keys(automaticStyles).forEach(function(styleKey) {
|
|
var automaticStyleDefinition = automaticStyles[styleKey], usedFamily = usedStyles[automaticStyleDefinition.family];
|
|
if(usedFamily && usedFamily.hasOwnProperty(automaticStyleDefinition.name)) {
|
|
mergeRequiredStyles(automaticStyleDefinition, usedStyles)
|
|
}
|
|
})
|
|
}
|
|
function collectUsedFontFaces(usedFontFaceDeclMap, styleElement) {
|
|
var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, currentElement;
|
|
function collectByAttribute(localName) {
|
|
var fontFaceName = currentElement.getAttributeNS(stylens, localName);
|
|
if(fontFaceName) {
|
|
usedFontFaceDeclMap[fontFaceName] = true
|
|
}
|
|
}
|
|
e = styleElement && styleElement.firstElementChild;
|
|
while(e) {
|
|
currentElement = e;
|
|
localNames.forEach(collectByAttribute);
|
|
collectUsedFontFaces(usedFontFaceDeclMap, currentElement);
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
this.collectUsedFontFaces = collectUsedFontFaces;
|
|
function changeFontFaceNames(styleElement, fontFaceNameChangeMap) {
|
|
var localNames = ["font-name", "font-name-asian", "font-name-complex"], e, currentElement;
|
|
function changeFontFaceNameByAttribute(localName) {
|
|
var fontFaceName = currentElement.getAttributeNS(stylens, localName);
|
|
if(fontFaceName && fontFaceNameChangeMap.hasOwnProperty(fontFaceName)) {
|
|
currentElement.setAttributeNS(stylens, "style:" + localName, fontFaceNameChangeMap[fontFaceName])
|
|
}
|
|
}
|
|
e = styleElement && styleElement.firstElementChild;
|
|
while(e) {
|
|
currentElement = e;
|
|
localNames.forEach(changeFontFaceNameByAttribute);
|
|
changeFontFaceNames(currentElement, fontFaceNameChangeMap);
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
this.changeFontFaceNames = changeFontFaceNames;
|
|
this.UsedStyleList = function(styleUsingElementsRoot, automaticStylesRoot) {
|
|
var usedStyles = {};
|
|
this.uses = function(element) {
|
|
var localName = element.localName, name = element.getAttributeNS(drawns, "name") || element.getAttributeNS(stylens, "name"), keyName, map;
|
|
if(localName === "style") {
|
|
keyName = element.getAttributeNS(stylens, "family")
|
|
}else {
|
|
if(element.namespaceURI === numberns) {
|
|
keyName = "data"
|
|
}else {
|
|
keyName = localName
|
|
}
|
|
}
|
|
map = usedStyles[keyName];
|
|
return map ? map[name] > 0 : false
|
|
};
|
|
determineUsedStyles(styleUsingElementsRoot, usedStyles);
|
|
if(automaticStylesRoot) {
|
|
mergeUsedAutomaticStyles(automaticStylesRoot, usedStyles)
|
|
}
|
|
};
|
|
this.hasDerivedStyles = hasDerivedStyles;
|
|
this.prefixStyleNames = prefixStyleNames;
|
|
this.removePrefixFromStyleNames = removePrefixFromStyleNames;
|
|
this.determineStylesForNode = determineStylesForNode;
|
|
elements = inverse()
|
|
};
|
|
if(typeof Object.create !== "function") {
|
|
Object["create"] = function(o) {
|
|
var F = function() {
|
|
};
|
|
F.prototype = o;
|
|
return new F
|
|
}
|
|
}
|
|
xmldom.LSSerializer = function LSSerializer() {
|
|
var self = this;
|
|
function Namespaces(nsmap) {
|
|
function invertMap(map) {
|
|
var m = {}, i;
|
|
for(i in map) {
|
|
if(map.hasOwnProperty(i)) {
|
|
m[map[i]] = i
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
var current = nsmap || {}, currentrev = invertMap(nsmap), levels = [current], levelsrev = [currentrev], level = 0;
|
|
this.push = function() {
|
|
level += 1;
|
|
current = levels[level] = Object.create(current);
|
|
currentrev = levelsrev[level] = Object.create(currentrev)
|
|
};
|
|
this.pop = function() {
|
|
levels.pop();
|
|
levelsrev.pop();
|
|
level -= 1;
|
|
current = levels[level];
|
|
currentrev = levelsrev[level]
|
|
};
|
|
this.getLocalNamespaceDefinitions = function() {
|
|
return currentrev
|
|
};
|
|
this.getQName = function(node) {
|
|
var ns = node.namespaceURI, i = 0, p;
|
|
if(!ns) {
|
|
return node.localName
|
|
}
|
|
p = currentrev[ns];
|
|
if(p) {
|
|
return p + ":" + node.localName
|
|
}
|
|
do {
|
|
if(p || !node.prefix) {
|
|
p = "ns" + i;
|
|
i += 1
|
|
}else {
|
|
p = node.prefix
|
|
}
|
|
if(current[p] === ns) {
|
|
break
|
|
}
|
|
if(!current[p]) {
|
|
current[p] = ns;
|
|
currentrev[ns] = p;
|
|
break
|
|
}
|
|
p = null
|
|
}while(p === null);
|
|
return p + ":" + node.localName
|
|
}
|
|
}
|
|
function escapeContent(value) {
|
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, """)
|
|
}
|
|
function serializeAttribute(qname, attr) {
|
|
var escapedValue = typeof attr.value === "string" ? escapeContent(attr.value) : attr.value, s = qname + '="' + escapedValue + '"';
|
|
return s
|
|
}
|
|
function startElement(ns, qname, element) {
|
|
var s = "", atts = (element.attributes), length, i, attr, attstr = "", accept, prefix, nsmap;
|
|
s += "<" + qname;
|
|
length = atts.length;
|
|
for(i = 0;i < length;i += 1) {
|
|
attr = (atts.item(i));
|
|
if(attr.namespaceURI !== "http://www.w3.org/2000/xmlns/") {
|
|
accept = self.filter ? self.filter.acceptNode(attr) : NodeFilter.FILTER_ACCEPT;
|
|
if(accept === NodeFilter.FILTER_ACCEPT) {
|
|
attstr += " " + serializeAttribute(ns.getQName(attr), attr)
|
|
}
|
|
}
|
|
}
|
|
nsmap = ns.getLocalNamespaceDefinitions();
|
|
for(i in nsmap) {
|
|
if(nsmap.hasOwnProperty(i)) {
|
|
prefix = nsmap[i];
|
|
if(!prefix) {
|
|
s += ' xmlns="' + i + '"'
|
|
}else {
|
|
if(prefix !== "xmlns") {
|
|
s += " xmlns:" + nsmap[i] + '="' + i + '"'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s += attstr + ">";
|
|
return s
|
|
}
|
|
function serializeNode(ns, node) {
|
|
var s = "", accept = self.filter ? self.filter.acceptNode(node) : NodeFilter.FILTER_ACCEPT, child, qname;
|
|
if(accept === NodeFilter.FILTER_ACCEPT && node.nodeType === Node.ELEMENT_NODE) {
|
|
ns.push();
|
|
qname = ns.getQName(node);
|
|
s += startElement(ns, qname, node)
|
|
}
|
|
if(accept === NodeFilter.FILTER_ACCEPT || accept === NodeFilter.FILTER_SKIP) {
|
|
child = node.firstChild;
|
|
while(child) {
|
|
s += serializeNode(ns, child);
|
|
child = child.nextSibling
|
|
}
|
|
if(node.nodeValue) {
|
|
s += escapeContent(node.nodeValue)
|
|
}
|
|
}
|
|
if(qname) {
|
|
s += "</" + qname + ">";
|
|
ns.pop()
|
|
}
|
|
return s
|
|
}
|
|
this.filter = null;
|
|
this.writeToString = function(node, nsmap) {
|
|
if(!node) {
|
|
return""
|
|
}
|
|
var ns = new Namespaces(nsmap);
|
|
return serializeNode(ns, node)
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
var styleInfo = new odf.StyleInfo, domUtils = new core.DomUtils, officens = "urn:oasis:names:tc:opendocument:xmlns:office:1.0", manifestns = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0", webodfns = "urn:webodf:names:scope", stylens = odf.Namespaces.stylens, nodeorder = ["meta", "settings", "scripts", "font-face-decls", "styles", "automatic-styles", "master-styles", "body"], automaticStylePrefix = (new Date).getTime() + "_webodf_", base64 = new core.Base64, documentStylesScope = "document-styles",
|
|
documentContentScope = "document-content";
|
|
function getDirectChild(node, ns, name) {
|
|
node = node ? node.firstChild : null;
|
|
while(node) {
|
|
if(node.localName === name && node.namespaceURI === ns) {
|
|
return(node)
|
|
}
|
|
node = node.nextSibling
|
|
}
|
|
return null
|
|
}
|
|
function getNodePosition(child) {
|
|
var i, l = nodeorder.length;
|
|
for(i = 0;i < l;i += 1) {
|
|
if(child.namespaceURI === officens && child.localName === nodeorder[i]) {
|
|
return i
|
|
}
|
|
}
|
|
return-1
|
|
}
|
|
function OdfStylesFilter(styleUsingElementsRoot, automaticStyles) {
|
|
var usedStyleList = new styleInfo.UsedStyleList(styleUsingElementsRoot, automaticStyles), odfNodeFilter = new odf.OdfNodeFilter;
|
|
this.acceptNode = function(node) {
|
|
var result = odfNodeFilter.acceptNode(node);
|
|
if(result === NodeFilter.FILTER_ACCEPT && (node.parentNode === automaticStyles && node.nodeType === Node.ELEMENT_NODE)) {
|
|
if(usedStyleList.uses((node))) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}else {
|
|
result = NodeFilter.FILTER_REJECT
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
function OdfContentFilter(styleUsingElementsRoot, automaticStyles) {
|
|
var odfStylesFilter = new OdfStylesFilter(styleUsingElementsRoot, automaticStyles);
|
|
this.acceptNode = function(node) {
|
|
var result = odfStylesFilter.acceptNode(node);
|
|
if(result === NodeFilter.FILTER_ACCEPT && (node.parentNode && (node.parentNode.namespaceURI === odf.Namespaces.textns && (node.parentNode.localName === "s" || node.parentNode.localName === "tab")))) {
|
|
result = NodeFilter.FILTER_REJECT
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
function setChild(node, child) {
|
|
if(!child) {
|
|
return
|
|
}
|
|
var childpos = getNodePosition(child), pos, c = node.firstChild;
|
|
if(childpos === -1) {
|
|
return
|
|
}
|
|
while(c) {
|
|
pos = getNodePosition(c);
|
|
if(pos !== -1 && pos > childpos) {
|
|
break
|
|
}
|
|
c = c.nextSibling
|
|
}
|
|
node.insertBefore(child, c)
|
|
}
|
|
odf.ODFElement = function ODFElement() {
|
|
};
|
|
odf.ODFDocumentElement = function ODFDocumentElement() {
|
|
};
|
|
odf.ODFDocumentElement.prototype = new odf.ODFElement;
|
|
odf.ODFDocumentElement.prototype.constructor = odf.ODFDocumentElement;
|
|
odf.ODFDocumentElement.prototype.automaticStyles;
|
|
odf.ODFDocumentElement.prototype.body;
|
|
odf.ODFDocumentElement.prototype.fontFaceDecls = null;
|
|
odf.ODFDocumentElement.prototype.manifest = null;
|
|
odf.ODFDocumentElement.prototype.masterStyles;
|
|
odf.ODFDocumentElement.prototype.meta;
|
|
odf.ODFDocumentElement.prototype.settings = null;
|
|
odf.ODFDocumentElement.prototype.styles;
|
|
odf.ODFDocumentElement.namespaceURI = officens;
|
|
odf.ODFDocumentElement.localName = "document";
|
|
odf.AnnotationElement = function AnnotationElement() {
|
|
};
|
|
odf.AnnotationElement.prototype.annotationEndElement;
|
|
odf.OdfPart = function OdfPart(name, mimetype, container, zip) {
|
|
var self = this;
|
|
this.size = 0;
|
|
this.type = null;
|
|
this.name = name;
|
|
this.container = container;
|
|
this.url = null;
|
|
this.mimetype = mimetype;
|
|
this.document = null;
|
|
this.onstatereadychange = null;
|
|
this.onchange;
|
|
this.EMPTY = 0;
|
|
this.LOADING = 1;
|
|
this.DONE = 2;
|
|
this.state = this.EMPTY;
|
|
this.data = "";
|
|
this.load = function() {
|
|
if(zip === null) {
|
|
return
|
|
}
|
|
this.mimetype = mimetype;
|
|
zip.loadAsDataURL(name, mimetype, function(err, url) {
|
|
if(err) {
|
|
runtime.log(err)
|
|
}
|
|
self.url = url;
|
|
if(self.onchange) {
|
|
self.onchange(self)
|
|
}
|
|
if(self.onstatereadychange) {
|
|
self.onstatereadychange(self)
|
|
}
|
|
})
|
|
}
|
|
};
|
|
odf.OdfPart.prototype.load = function() {
|
|
};
|
|
odf.OdfPart.prototype.getUrl = function() {
|
|
if(this.data) {
|
|
return"data:;base64," + base64.toBase64(this.data)
|
|
}
|
|
return null
|
|
};
|
|
odf.OdfContainer = function OdfContainer(url, onstatereadychange) {
|
|
var self = this, zip, partMimetypes = {}, contentElement;
|
|
this.onstatereadychange = onstatereadychange;
|
|
this.onchange = null;
|
|
this.state = null;
|
|
this.rootElement;
|
|
function removeProcessingInstructions(element) {
|
|
var n = element.firstChild, next, e;
|
|
while(n) {
|
|
next = n.nextSibling;
|
|
if(n.nodeType === Node.ELEMENT_NODE) {
|
|
e = (n);
|
|
removeProcessingInstructions(e)
|
|
}else {
|
|
if(n.nodeType === Node.PROCESSING_INSTRUCTION_NODE) {
|
|
element.removeChild(n)
|
|
}
|
|
}
|
|
n = next
|
|
}
|
|
}
|
|
function linkAnnotationStartAndEndElements(rootElement) {
|
|
var document = rootElement.ownerDocument, annotationStarts = {}, n, name, annotationStart, nodeIterator = document.createNodeIterator(rootElement, NodeFilter.SHOW_ELEMENT, null, false);
|
|
n = (nodeIterator.nextNode());
|
|
while(n) {
|
|
if(n.namespaceURI === officens) {
|
|
if(n.localName === "annotation") {
|
|
name = n.getAttributeNS(officens, "name");
|
|
if(name) {
|
|
if(annotationStarts.hasOwnProperty(name)) {
|
|
runtime.log("Warning: annotation name used more than once with <office:annotation/>: '" + name + "'")
|
|
}else {
|
|
annotationStarts[name] = n
|
|
}
|
|
}
|
|
}else {
|
|
if(n.localName === "annotation-end") {
|
|
name = n.getAttributeNS(officens, "name");
|
|
if(name) {
|
|
if(annotationStarts.hasOwnProperty(name)) {
|
|
annotationStart = (annotationStarts[name]);
|
|
if(!annotationStart.annotationEndElement) {
|
|
annotationStart.annotationEndElement = n
|
|
}else {
|
|
runtime.log("Warning: annotation name used more than once with <office:annotation-end/>: '" + name + "'")
|
|
}
|
|
}else {
|
|
runtime.log("Warning: annotation end without an annotation start, name: '" + name + "'")
|
|
}
|
|
}else {
|
|
runtime.log("Warning: annotation end without a name found")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
n = (nodeIterator.nextNode())
|
|
}
|
|
}
|
|
function setAutomaticStylesScope(stylesRootElement, scope) {
|
|
var n = stylesRootElement && stylesRootElement.firstChild;
|
|
while(n) {
|
|
if(n.nodeType === Node.ELEMENT_NODE) {
|
|
(n).setAttributeNS(webodfns, "scope", scope)
|
|
}
|
|
n = n.nextSibling
|
|
}
|
|
}
|
|
function getEnsuredMetaElement() {
|
|
var root = self.rootElement, meta = root.meta;
|
|
if(!meta) {
|
|
root.meta = meta = document.createElementNS(officens, "meta");
|
|
setChild(root, meta)
|
|
}
|
|
return meta
|
|
}
|
|
function getMetaData(metadataNs, metadataLocalName) {
|
|
var node = self.rootElement.meta, textNode;
|
|
node = node && node.firstChild;
|
|
while(node && (node.namespaceURI !== metadataNs || node.localName !== metadataLocalName)) {
|
|
node = node.nextSibling
|
|
}
|
|
node = node && node.firstChild;
|
|
while(node && node.nodeType !== Node.TEXT_NODE) {
|
|
node = node.nextSibling
|
|
}
|
|
if(node) {
|
|
textNode = (node);
|
|
return textNode.data
|
|
}
|
|
return null
|
|
}
|
|
function unusedKey(key, map1, map2) {
|
|
var i = 0, postFixedKey;
|
|
key = key.replace(/\d+$/, "");
|
|
postFixedKey = key;
|
|
while(map1.hasOwnProperty(postFixedKey) || map2.hasOwnProperty(postFixedKey)) {
|
|
i += 1;
|
|
postFixedKey = key + i
|
|
}
|
|
return postFixedKey
|
|
}
|
|
function mapByFontFaceName(fontFaceDecls) {
|
|
var fn, result = {}, fontname;
|
|
fn = fontFaceDecls.firstChild;
|
|
while(fn) {
|
|
if(fn.nodeType === Node.ELEMENT_NODE && (fn.namespaceURI === stylens && fn.localName === "font-face")) {
|
|
fontname = (fn).getAttributeNS(stylens, "name");
|
|
result[fontname] = fn
|
|
}
|
|
fn = fn.nextSibling
|
|
}
|
|
return result
|
|
}
|
|
function mergeFontFaceDecls(targetFontFaceDeclsRootElement, sourceFontFaceDeclsRootElement) {
|
|
var e, s, fontFaceName, newFontFaceName, targetFontFaceDeclsMap, sourceFontFaceDeclsMap, fontFaceNameChangeMap = {};
|
|
targetFontFaceDeclsMap = mapByFontFaceName(targetFontFaceDeclsRootElement);
|
|
sourceFontFaceDeclsMap = mapByFontFaceName(sourceFontFaceDeclsRootElement);
|
|
e = sourceFontFaceDeclsRootElement.firstElementChild;
|
|
while(e) {
|
|
s = e.nextElementSibling;
|
|
if(e.namespaceURI === stylens && e.localName === "font-face") {
|
|
fontFaceName = e.getAttributeNS(stylens, "name");
|
|
if(targetFontFaceDeclsMap.hasOwnProperty(fontFaceName)) {
|
|
if(!e.isEqualNode(targetFontFaceDeclsMap[fontFaceName])) {
|
|
newFontFaceName = unusedKey(fontFaceName, targetFontFaceDeclsMap, sourceFontFaceDeclsMap);
|
|
e.setAttributeNS(stylens, "style:name", newFontFaceName);
|
|
targetFontFaceDeclsRootElement.appendChild(e);
|
|
targetFontFaceDeclsMap[newFontFaceName] = e;
|
|
delete sourceFontFaceDeclsMap[fontFaceName];
|
|
fontFaceNameChangeMap[fontFaceName] = newFontFaceName
|
|
}
|
|
}else {
|
|
targetFontFaceDeclsRootElement.appendChild(e);
|
|
targetFontFaceDeclsMap[fontFaceName] = e;
|
|
delete sourceFontFaceDeclsMap[fontFaceName]
|
|
}
|
|
}
|
|
e = s
|
|
}
|
|
return fontFaceNameChangeMap
|
|
}
|
|
function cloneStylesInScope(stylesRootElement, scope) {
|
|
var copy = null, e, s, scopeAttrValue;
|
|
if(stylesRootElement) {
|
|
copy = stylesRootElement.cloneNode(true);
|
|
e = copy.firstElementChild;
|
|
while(e) {
|
|
s = e.nextElementSibling;
|
|
scopeAttrValue = e.getAttributeNS(webodfns, "scope");
|
|
if(scopeAttrValue && scopeAttrValue !== scope) {
|
|
copy.removeChild(e)
|
|
}
|
|
e = s
|
|
}
|
|
}
|
|
return copy
|
|
}
|
|
function cloneFontFaceDeclsUsedInStyles(fontFaceDeclsRootElement, stylesRootElementList) {
|
|
var e, nextSibling, fontFaceName, copy = null, usedFontFaceDeclMap = {};
|
|
if(fontFaceDeclsRootElement) {
|
|
stylesRootElementList.forEach(function(stylesRootElement) {
|
|
styleInfo.collectUsedFontFaces(usedFontFaceDeclMap, stylesRootElement)
|
|
});
|
|
copy = fontFaceDeclsRootElement.cloneNode(true);
|
|
e = copy.firstElementChild;
|
|
while(e) {
|
|
nextSibling = e.nextElementSibling;
|
|
fontFaceName = e.getAttributeNS(stylens, "name");
|
|
if(!usedFontFaceDeclMap[fontFaceName]) {
|
|
copy.removeChild(e)
|
|
}
|
|
e = nextSibling
|
|
}
|
|
}
|
|
return copy
|
|
}
|
|
function importRootNode(xmldoc) {
|
|
var doc = self.rootElement.ownerDocument, node;
|
|
if(xmldoc) {
|
|
removeProcessingInstructions(xmldoc.documentElement);
|
|
try {
|
|
node = doc.importNode(xmldoc.documentElement, true)
|
|
}catch(ignore) {
|
|
}
|
|
}
|
|
return node
|
|
}
|
|
function setState(state) {
|
|
self.state = state;
|
|
if(self.onchange) {
|
|
self.onchange(self)
|
|
}
|
|
if(self.onstatereadychange) {
|
|
self.onstatereadychange(self)
|
|
}
|
|
}
|
|
function setRootElement(root) {
|
|
contentElement = null;
|
|
self.rootElement = (root);
|
|
root.fontFaceDecls = getDirectChild(root, officens, "font-face-decls");
|
|
root.styles = getDirectChild(root, officens, "styles");
|
|
root.automaticStyles = getDirectChild(root, officens, "automatic-styles");
|
|
root.masterStyles = getDirectChild(root, officens, "master-styles");
|
|
root.body = getDirectChild(root, officens, "body");
|
|
root.meta = getDirectChild(root, officens, "meta");
|
|
linkAnnotationStartAndEndElements(root)
|
|
}
|
|
function handleFlatXml(xmldoc) {
|
|
var root = importRootNode(xmldoc);
|
|
if(!root || (root.localName !== "document" || root.namespaceURI !== officens)) {
|
|
setState(OdfContainer.INVALID);
|
|
return
|
|
}
|
|
setRootElement((root));
|
|
setState(OdfContainer.DONE)
|
|
}
|
|
function handleStylesXml(xmldoc) {
|
|
var node = importRootNode(xmldoc), root = self.rootElement, n;
|
|
if(!node || (node.localName !== "document-styles" || node.namespaceURI !== officens)) {
|
|
setState(OdfContainer.INVALID);
|
|
return
|
|
}
|
|
root.fontFaceDecls = getDirectChild(node, officens, "font-face-decls");
|
|
setChild(root, root.fontFaceDecls);
|
|
n = getDirectChild(node, officens, "styles");
|
|
root.styles = n || xmldoc.createElementNS(officens, "styles");
|
|
setChild(root, root.styles);
|
|
n = getDirectChild(node, officens, "automatic-styles");
|
|
root.automaticStyles = n || xmldoc.createElementNS(officens, "automatic-styles");
|
|
setAutomaticStylesScope(root.automaticStyles, documentStylesScope);
|
|
setChild(root, root.automaticStyles);
|
|
node = getDirectChild(node, officens, "master-styles");
|
|
root.masterStyles = node || xmldoc.createElementNS(officens, "master-styles");
|
|
setChild(root, root.masterStyles);
|
|
styleInfo.prefixStyleNames(root.automaticStyles, automaticStylePrefix, root.masterStyles)
|
|
}
|
|
function handleContentXml(xmldoc) {
|
|
var node = importRootNode(xmldoc), root, automaticStyles, fontFaceDecls, fontFaceNameChangeMap, c;
|
|
if(!node || (node.localName !== "document-content" || node.namespaceURI !== officens)) {
|
|
setState(OdfContainer.INVALID);
|
|
return
|
|
}
|
|
root = self.rootElement;
|
|
fontFaceDecls = getDirectChild(node, officens, "font-face-decls");
|
|
if(root.fontFaceDecls && fontFaceDecls) {
|
|
fontFaceNameChangeMap = mergeFontFaceDecls(root.fontFaceDecls, fontFaceDecls)
|
|
}else {
|
|
if(fontFaceDecls) {
|
|
root.fontFaceDecls = fontFaceDecls;
|
|
setChild(root, fontFaceDecls)
|
|
}
|
|
}
|
|
automaticStyles = getDirectChild(node, officens, "automatic-styles");
|
|
setAutomaticStylesScope(automaticStyles, documentContentScope);
|
|
if(fontFaceNameChangeMap) {
|
|
styleInfo.changeFontFaceNames(automaticStyles, fontFaceNameChangeMap)
|
|
}
|
|
if(root.automaticStyles && automaticStyles) {
|
|
c = automaticStyles.firstChild;
|
|
while(c) {
|
|
root.automaticStyles.appendChild(c);
|
|
c = automaticStyles.firstChild
|
|
}
|
|
}else {
|
|
if(automaticStyles) {
|
|
root.automaticStyles = automaticStyles;
|
|
setChild(root, automaticStyles)
|
|
}
|
|
}
|
|
node = getDirectChild(node, officens, "body");
|
|
if(node === null) {
|
|
throw"<office:body/> tag is mising.";
|
|
}
|
|
root.body = node;
|
|
setChild(root, root.body)
|
|
}
|
|
function handleMetaXml(xmldoc) {
|
|
var node = importRootNode(xmldoc), root;
|
|
if(!node || (node.localName !== "document-meta" || node.namespaceURI !== officens)) {
|
|
return
|
|
}
|
|
root = self.rootElement;
|
|
root.meta = getDirectChild(node, officens, "meta");
|
|
setChild(root, root.meta)
|
|
}
|
|
function handleSettingsXml(xmldoc) {
|
|
var node = importRootNode(xmldoc), root;
|
|
if(!node || (node.localName !== "document-settings" || node.namespaceURI !== officens)) {
|
|
return
|
|
}
|
|
root = self.rootElement;
|
|
root.settings = getDirectChild(node, officens, "settings");
|
|
setChild(root, root.settings)
|
|
}
|
|
function handleManifestXml(xmldoc) {
|
|
var node = importRootNode(xmldoc), root, e;
|
|
if(!node || (node.localName !== "manifest" || node.namespaceURI !== manifestns)) {
|
|
return
|
|
}
|
|
root = self.rootElement;
|
|
root.manifest = (node);
|
|
e = root.manifest.firstElementChild;
|
|
while(e) {
|
|
if(e.localName === "file-entry" && e.namespaceURI === manifestns) {
|
|
partMimetypes[e.getAttributeNS(manifestns, "full-path")] = e.getAttributeNS(manifestns, "media-type")
|
|
}
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
function loadNextComponent(remainingComponents) {
|
|
var component = remainingComponents.shift();
|
|
if(component) {
|
|
zip.loadAsDOM(component.path, function(err, xmldoc) {
|
|
component.handler(xmldoc);
|
|
if(err || self.state === OdfContainer.INVALID) {
|
|
return
|
|
}
|
|
loadNextComponent(remainingComponents)
|
|
})
|
|
}else {
|
|
linkAnnotationStartAndEndElements(self.rootElement);
|
|
setState(OdfContainer.DONE)
|
|
}
|
|
}
|
|
function loadComponents() {
|
|
var componentOrder = [{path:"styles.xml", handler:handleStylesXml}, {path:"content.xml", handler:handleContentXml}, {path:"meta.xml", handler:handleMetaXml}, {path:"settings.xml", handler:handleSettingsXml}, {path:"META-INF/manifest.xml", handler:handleManifestXml}];
|
|
loadNextComponent(componentOrder)
|
|
}
|
|
function createDocumentElement(name) {
|
|
var s = "";
|
|
function defineNamespace(prefix, ns) {
|
|
s += " xmlns:" + prefix + '="' + ns + '"'
|
|
}
|
|
odf.Namespaces.forEachPrefix(defineNamespace);
|
|
return'<?xml version="1.0" encoding="UTF-8"?><office:' + name + " " + s + ' office:version="1.2">'
|
|
}
|
|
function serializeMetaXml() {
|
|
var serializer = new xmldom.LSSerializer, s = createDocumentElement("document-meta");
|
|
serializer.filter = new odf.OdfNodeFilter;
|
|
s += serializer.writeToString(self.rootElement.meta, odf.Namespaces.namespaceMap);
|
|
s += "</office:document-meta>";
|
|
return s
|
|
}
|
|
function createManifestEntry(fullPath, mediaType) {
|
|
var element = document.createElementNS(manifestns, "manifest:file-entry");
|
|
element.setAttributeNS(manifestns, "manifest:full-path", fullPath);
|
|
element.setAttributeNS(manifestns, "manifest:media-type", mediaType);
|
|
return element
|
|
}
|
|
function serializeManifestXml() {
|
|
var header = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n', xml = '<manifest:manifest xmlns:manifest="' + manifestns + '" manifest:version="1.2"></manifest:manifest>', manifest = (runtime.parseXML(xml)), manifestRoot = getDirectChild(manifest, manifestns, "manifest"), serializer = new xmldom.LSSerializer, fullPath;
|
|
for(fullPath in partMimetypes) {
|
|
if(partMimetypes.hasOwnProperty(fullPath)) {
|
|
manifestRoot.appendChild(createManifestEntry(fullPath, partMimetypes[fullPath]))
|
|
}
|
|
}
|
|
serializer.filter = new odf.OdfNodeFilter;
|
|
return header + serializer.writeToString(manifest, odf.Namespaces.namespaceMap)
|
|
}
|
|
function serializeSettingsXml() {
|
|
var serializer = new xmldom.LSSerializer, s = createDocumentElement("document-settings");
|
|
serializer.filter = new odf.OdfNodeFilter;
|
|
if(self.rootElement.settings.firstElementChild) {
|
|
s += serializer.writeToString(self.rootElement.settings, odf.Namespaces.namespaceMap)
|
|
}
|
|
s += "</office:document-settings>";
|
|
return s
|
|
}
|
|
function serializeStylesXml() {
|
|
var fontFaceDecls, automaticStyles, masterStyles, nsmap = odf.Namespaces.namespaceMap, serializer = new xmldom.LSSerializer, s = createDocumentElement("document-styles");
|
|
automaticStyles = cloneStylesInScope(self.rootElement.automaticStyles, documentStylesScope);
|
|
masterStyles = (self.rootElement.masterStyles.cloneNode(true));
|
|
fontFaceDecls = cloneFontFaceDeclsUsedInStyles(self.rootElement.fontFaceDecls, [masterStyles, self.rootElement.styles, automaticStyles]);
|
|
styleInfo.removePrefixFromStyleNames(automaticStyles, automaticStylePrefix, masterStyles);
|
|
serializer.filter = new OdfStylesFilter(masterStyles, automaticStyles);
|
|
s += serializer.writeToString(fontFaceDecls, nsmap);
|
|
s += serializer.writeToString(self.rootElement.styles, nsmap);
|
|
s += serializer.writeToString(automaticStyles, nsmap);
|
|
s += serializer.writeToString(masterStyles, nsmap);
|
|
s += "</office:document-styles>";
|
|
return s
|
|
}
|
|
function serializeContentXml() {
|
|
var fontFaceDecls, automaticStyles, nsmap = odf.Namespaces.namespaceMap, serializer = new xmldom.LSSerializer, s = createDocumentElement("document-content");
|
|
automaticStyles = cloneStylesInScope(self.rootElement.automaticStyles, documentContentScope);
|
|
fontFaceDecls = cloneFontFaceDeclsUsedInStyles(self.rootElement.fontFaceDecls, [automaticStyles]);
|
|
serializer.filter = new OdfContentFilter(self.rootElement.body, automaticStyles);
|
|
s += serializer.writeToString(fontFaceDecls, nsmap);
|
|
s += serializer.writeToString(automaticStyles, nsmap);
|
|
s += serializer.writeToString(self.rootElement.body, nsmap);
|
|
s += "</office:document-content>";
|
|
return s
|
|
}
|
|
function createElement(type) {
|
|
var original = document.createElementNS(type.namespaceURI, type.localName), method, iface = new type.Type;
|
|
for(method in iface) {
|
|
if(iface.hasOwnProperty(method)) {
|
|
original[method] = iface[method]
|
|
}
|
|
}
|
|
return original
|
|
}
|
|
function loadFromXML(url, callback) {
|
|
runtime.loadXML(url, function(err, dom) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
handleFlatXml(dom)
|
|
}
|
|
})
|
|
}
|
|
this.setRootElement = setRootElement;
|
|
this.getContentElement = function() {
|
|
var body;
|
|
if(!contentElement) {
|
|
body = self.rootElement.body;
|
|
contentElement = getDirectChild(body, officens, "text") || (getDirectChild(body, officens, "presentation") || getDirectChild(body, officens, "spreadsheet"))
|
|
}
|
|
if(!contentElement) {
|
|
throw"Could not find content element in <office:body/>.";
|
|
}
|
|
return contentElement
|
|
};
|
|
this.getDocumentType = function() {
|
|
var content = self.getContentElement();
|
|
return content && content.localName
|
|
};
|
|
this.getPart = function(partname) {
|
|
return new odf.OdfPart(partname, partMimetypes[partname], self, zip)
|
|
};
|
|
this.getPartData = function(url, callback) {
|
|
zip.load(url, callback)
|
|
};
|
|
function setMetadata(setProperties, removedPropertyNames) {
|
|
var metaElement = getEnsuredMetaElement();
|
|
if(setProperties) {
|
|
domUtils.mapKeyValObjOntoNode(metaElement, setProperties, odf.Namespaces.lookupNamespaceURI)
|
|
}
|
|
if(removedPropertyNames) {
|
|
domUtils.removeKeyElementsFromNode(metaElement, removedPropertyNames, odf.Namespaces.lookupNamespaceURI)
|
|
}
|
|
}
|
|
this.setMetadata = setMetadata;
|
|
this.incrementEditingCycles = function() {
|
|
var currentValueString = getMetaData(odf.Namespaces.metans, "editing-cycles"), currentCycles = currentValueString ? parseInt(currentValueString, 10) : 0;
|
|
if(isNaN(currentCycles)) {
|
|
currentCycles = 0
|
|
}
|
|
setMetadata({"meta:editing-cycles":currentCycles + 1}, null)
|
|
};
|
|
function updateMetadataForSaving() {
|
|
var generatorString, window = runtime.getWindow();
|
|
generatorString = "WebODF/" + (String(typeof webodf_version) !== "undefined" ? webodf_version : "FromSource");
|
|
if(window) {
|
|
generatorString = generatorString + " " + window.navigator.userAgent
|
|
}
|
|
setMetadata({"meta:generator":generatorString}, null)
|
|
}
|
|
function createEmptyTextDocument() {
|
|
var emptyzip = new core.Zip("", null), data = runtime.byteArrayFromString("application/vnd.oasis.opendocument.text", "utf8"), root = self.rootElement, text = document.createElementNS(officens, "text");
|
|
emptyzip.save("mimetype", data, false, new Date);
|
|
function addToplevelElement(memberName, realLocalName) {
|
|
var element;
|
|
if(!realLocalName) {
|
|
realLocalName = memberName
|
|
}
|
|
element = document.createElementNS(officens, realLocalName);
|
|
root[memberName] = element;
|
|
root.appendChild(element)
|
|
}
|
|
addToplevelElement("meta");
|
|
addToplevelElement("settings");
|
|
addToplevelElement("scripts");
|
|
addToplevelElement("fontFaceDecls", "font-face-decls");
|
|
addToplevelElement("styles");
|
|
addToplevelElement("automaticStyles", "automatic-styles");
|
|
addToplevelElement("masterStyles", "master-styles");
|
|
addToplevelElement("body");
|
|
root.body.appendChild(text);
|
|
partMimetypes["/"] = "application/vnd.oasis.opendocument.text";
|
|
partMimetypes["settings.xml"] = "text/xml";
|
|
partMimetypes["meta.xml"] = "text/xml";
|
|
partMimetypes["styles.xml"] = "text/xml";
|
|
partMimetypes["content.xml"] = "text/xml";
|
|
setState(OdfContainer.DONE);
|
|
return emptyzip
|
|
}
|
|
function fillZip() {
|
|
var data, date = new Date;
|
|
updateMetadataForSaving();
|
|
data = runtime.byteArrayFromString(serializeSettingsXml(), "utf8");
|
|
zip.save("settings.xml", data, true, date);
|
|
data = runtime.byteArrayFromString(serializeMetaXml(), "utf8");
|
|
zip.save("meta.xml", data, true, date);
|
|
data = runtime.byteArrayFromString(serializeStylesXml(), "utf8");
|
|
zip.save("styles.xml", data, true, date);
|
|
data = runtime.byteArrayFromString(serializeContentXml(), "utf8");
|
|
zip.save("content.xml", data, true, date);
|
|
data = runtime.byteArrayFromString(serializeManifestXml(), "utf8");
|
|
zip.save("META-INF/manifest.xml", data, true, date)
|
|
}
|
|
function createByteArray(successCallback, errorCallback) {
|
|
fillZip();
|
|
zip.createByteArray(successCallback, errorCallback)
|
|
}
|
|
this.createByteArray = createByteArray;
|
|
function saveAs(newurl, callback) {
|
|
fillZip();
|
|
zip.writeAs(newurl, function(err) {
|
|
callback(err)
|
|
})
|
|
}
|
|
this.saveAs = saveAs;
|
|
this.save = function(callback) {
|
|
saveAs(url, callback)
|
|
};
|
|
this.getUrl = function() {
|
|
return url
|
|
};
|
|
this.setBlob = function(filename, mimetype, content) {
|
|
var data = base64.convertBase64ToByteArray(content), date = new Date;
|
|
zip.save(filename, data, false, date);
|
|
if(partMimetypes.hasOwnProperty(filename)) {
|
|
runtime.log(filename + " has been overwritten.")
|
|
}
|
|
partMimetypes[filename] = mimetype
|
|
};
|
|
this.removeBlob = function(filename) {
|
|
var foundAndRemoved = zip.remove(filename);
|
|
runtime.assert(foundAndRemoved, "file is not found: " + filename);
|
|
delete partMimetypes[filename]
|
|
};
|
|
this.state = OdfContainer.LOADING;
|
|
this.rootElement = (createElement({Type:odf.ODFDocumentElement, namespaceURI:odf.ODFDocumentElement.namespaceURI, localName:odf.ODFDocumentElement.localName}));
|
|
if(url) {
|
|
zip = new core.Zip(url, function(err, zipobject) {
|
|
zip = zipobject;
|
|
if(err) {
|
|
loadFromXML(url, function(xmlerr) {
|
|
if(err) {
|
|
zip.error = err + "\n" + xmlerr;
|
|
setState(OdfContainer.INVALID)
|
|
}
|
|
})
|
|
}else {
|
|
loadComponents()
|
|
}
|
|
})
|
|
}else {
|
|
zip = createEmptyTextDocument()
|
|
}
|
|
};
|
|
odf.OdfContainer.EMPTY = 0;
|
|
odf.OdfContainer.LOADING = 1;
|
|
odf.OdfContainer.DONE = 2;
|
|
odf.OdfContainer.INVALID = 3;
|
|
odf.OdfContainer.SAVING = 4;
|
|
odf.OdfContainer.MODIFIED = 5;
|
|
odf.OdfContainer.getContainer = function(url) {
|
|
return new odf.OdfContainer(url, null)
|
|
};
|
|
return odf.OdfContainer
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.OdfUtils = function OdfUtils() {
|
|
var textns = odf.Namespaces.textns, drawns = odf.Namespaces.drawns, xlinkns = odf.Namespaces.xlinkns, whitespaceOnly = /^\s*$/, domUtils = new core.DomUtils;
|
|
function isImage(e) {
|
|
var name = e && e.localName;
|
|
return name === "image" && e.namespaceURI === drawns
|
|
}
|
|
this.isImage = isImage;
|
|
function isCharacterFrame(e) {
|
|
return e !== null && (e.nodeType === Node.ELEMENT_NODE && (e.localName === "frame" && (e.namespaceURI === drawns && (e).getAttributeNS(textns, "anchor-type") === "as-char")))
|
|
}
|
|
this.isCharacterFrame = isCharacterFrame;
|
|
function isAnnotation(e) {
|
|
var name = e && e.localName;
|
|
return name === "annotation" && e.namespaceURI === odf.Namespaces.officens
|
|
}
|
|
function isAnnotationWrapper(e) {
|
|
var name = e && e.localName;
|
|
return name === "div" && (e).className === "annotationWrapper"
|
|
}
|
|
function isInlineRoot(e) {
|
|
return isAnnotation(e) || isAnnotationWrapper(e)
|
|
}
|
|
this.isInlineRoot = isInlineRoot;
|
|
this.isTextSpan = function(e) {
|
|
var name = e && e.localName;
|
|
return name === "span" && e.namespaceURI === textns
|
|
};
|
|
function isHyperlink(node) {
|
|
var name = node && node.localName;
|
|
return name === "a" && node.namespaceURI === textns
|
|
}
|
|
this.isHyperlink = isHyperlink;
|
|
this.getHyperlinkTarget = function(element) {
|
|
return element.getAttributeNS(xlinkns, "href")
|
|
};
|
|
function isParagraph(e) {
|
|
var name = e && e.localName;
|
|
return(name === "p" || name === "h") && e.namespaceURI === textns
|
|
}
|
|
this.isParagraph = isParagraph;
|
|
function getParagraphElement(node) {
|
|
while(node && !isParagraph(node)) {
|
|
node = node.parentNode
|
|
}
|
|
return(node)
|
|
}
|
|
this.getParagraphElement = getParagraphElement;
|
|
this.isWithinTrackedChanges = function(node, container) {
|
|
while(node && node !== container) {
|
|
if(node.namespaceURI === textns && node.localName === "tracked-changes") {
|
|
return true
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
return false
|
|
};
|
|
this.isListItem = function(e) {
|
|
var name = e && e.localName;
|
|
return name === "list-item" && e.namespaceURI === textns
|
|
};
|
|
this.isLineBreak = function(e) {
|
|
var name = e && e.localName;
|
|
return name === "line-break" && e.namespaceURI === textns
|
|
};
|
|
function isODFWhitespace(text) {
|
|
return/^[ \t\r\n]+$/.test(text)
|
|
}
|
|
this.isODFWhitespace = isODFWhitespace;
|
|
function isGroupingElement(n) {
|
|
if(n === null || n.nodeType !== Node.ELEMENT_NODE) {
|
|
return false
|
|
}
|
|
var e = (n), localName = e.localName;
|
|
return/^(span|p|h|a|meta)$/.test(localName) && e.namespaceURI === textns || localName === "span" && e.className === "annotationHighlight"
|
|
}
|
|
this.isGroupingElement = isGroupingElement;
|
|
function isCharacterElement(e) {
|
|
var n = e && e.localName, ns, r = false;
|
|
if(n) {
|
|
ns = e.namespaceURI;
|
|
if(ns === textns) {
|
|
r = n === "s" || (n === "tab" || n === "line-break")
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
this.isCharacterElement = isCharacterElement;
|
|
function isAnchoredAsCharacterElement(e) {
|
|
return isCharacterElement(e) || (isCharacterFrame(e) || isInlineRoot(e))
|
|
}
|
|
this.isAnchoredAsCharacterElement = isAnchoredAsCharacterElement;
|
|
function isSpaceElement(e) {
|
|
var n = e && e.localName, ns, r = false;
|
|
if(n) {
|
|
ns = e.namespaceURI;
|
|
if(ns === textns) {
|
|
r = n === "s"
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
this.isSpaceElement = isSpaceElement;
|
|
function firstChild(node) {
|
|
while(node.firstChild !== null && isGroupingElement(node)) {
|
|
node = node.firstChild
|
|
}
|
|
return node
|
|
}
|
|
this.firstChild = firstChild;
|
|
function lastChild(node) {
|
|
while(node.lastChild !== null && isGroupingElement(node)) {
|
|
node = node.lastChild
|
|
}
|
|
return node
|
|
}
|
|
this.lastChild = lastChild;
|
|
function previousNode(node) {
|
|
while(!isParagraph(node) && node.previousSibling === null) {
|
|
node = (node.parentNode)
|
|
}
|
|
return isParagraph(node) ? null : lastChild((node.previousSibling))
|
|
}
|
|
this.previousNode = previousNode;
|
|
function nextNode(node) {
|
|
while(!isParagraph(node) && node.nextSibling === null) {
|
|
node = (node.parentNode)
|
|
}
|
|
return isParagraph(node) ? null : firstChild((node.nextSibling))
|
|
}
|
|
this.nextNode = nextNode;
|
|
function scanLeftForNonSpace(node) {
|
|
var r = false, text;
|
|
while(node) {
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
text = (node);
|
|
if(text.length === 0) {
|
|
node = previousNode(text)
|
|
}else {
|
|
return!isODFWhitespace(text.data.substr(text.length - 1, 1))
|
|
}
|
|
}else {
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
r = isSpaceElement(node) === false;
|
|
node = null
|
|
}else {
|
|
node = previousNode(node)
|
|
}
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
this.scanLeftForNonSpace = scanLeftForNonSpace;
|
|
function lookLeftForCharacter(node) {
|
|
var text, r = 0, tl = 0;
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
tl = (node).length
|
|
}
|
|
if(tl > 0) {
|
|
text = (node).data;
|
|
if(!isODFWhitespace(text.substr(tl - 1, 1))) {
|
|
r = 1
|
|
}else {
|
|
if(tl === 1) {
|
|
r = scanLeftForNonSpace(previousNode(node)) ? 2 : 0
|
|
}else {
|
|
r = isODFWhitespace(text.substr(tl - 2, 1)) ? 0 : 2
|
|
}
|
|
}
|
|
}else {
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
r = 1
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
this.lookLeftForCharacter = lookLeftForCharacter;
|
|
function lookRightForCharacter(node) {
|
|
var r = false, l = 0;
|
|
if(node && node.nodeType === Node.TEXT_NODE) {
|
|
l = (node).length
|
|
}
|
|
if(l > 0) {
|
|
r = !isODFWhitespace((node).data.substr(0, 1))
|
|
}else {
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
r = true
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
this.lookRightForCharacter = lookRightForCharacter;
|
|
function scanLeftForAnyCharacter(node) {
|
|
var r = false, l;
|
|
node = node && lastChild(node);
|
|
while(node) {
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
l = (node).length
|
|
}else {
|
|
l = 0
|
|
}
|
|
if(l > 0 && !isODFWhitespace((node).data)) {
|
|
r = true;
|
|
break
|
|
}
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
r = true;
|
|
break
|
|
}
|
|
node = previousNode(node)
|
|
}
|
|
return r
|
|
}
|
|
this.scanLeftForAnyCharacter = scanLeftForAnyCharacter;
|
|
function scanRightForAnyCharacter(node) {
|
|
var r = false, l;
|
|
node = node && firstChild(node);
|
|
while(node) {
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
l = (node).length
|
|
}else {
|
|
l = 0
|
|
}
|
|
if(l > 0 && !isODFWhitespace((node).data)) {
|
|
r = true;
|
|
break
|
|
}
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
r = true;
|
|
break
|
|
}
|
|
node = nextNode(node)
|
|
}
|
|
return r
|
|
}
|
|
this.scanRightForAnyCharacter = scanRightForAnyCharacter;
|
|
function isTrailingWhitespace(textnode, offset) {
|
|
if(!isODFWhitespace(textnode.data.substr(offset))) {
|
|
return false
|
|
}
|
|
return!scanRightForAnyCharacter(nextNode(textnode))
|
|
}
|
|
this.isTrailingWhitespace = isTrailingWhitespace;
|
|
function isSignificantWhitespace(textNode, offset) {
|
|
var text = textNode.data, result;
|
|
if(!isODFWhitespace(text[offset])) {
|
|
return false
|
|
}
|
|
if(isAnchoredAsCharacterElement(textNode.parentNode)) {
|
|
return false
|
|
}
|
|
if(offset > 0) {
|
|
if(!isODFWhitespace(text[offset - 1])) {
|
|
result = true
|
|
}
|
|
}else {
|
|
if(scanLeftForNonSpace(previousNode(textNode))) {
|
|
result = true
|
|
}
|
|
}
|
|
if(result === true) {
|
|
return isTrailingWhitespace(textNode, offset) ? false : true
|
|
}
|
|
return false
|
|
}
|
|
this.isSignificantWhitespace = isSignificantWhitespace;
|
|
this.isDowngradableSpaceElement = function(node) {
|
|
if(node.namespaceURI === textns && node.localName === "s") {
|
|
return scanLeftForNonSpace(previousNode(node)) && scanRightForAnyCharacter(nextNode(node))
|
|
}
|
|
return false
|
|
};
|
|
function getFirstNonWhitespaceChild(node) {
|
|
var child = node && node.firstChild;
|
|
while(child && (child.nodeType === Node.TEXT_NODE && whitespaceOnly.test(child.nodeValue))) {
|
|
child = child.nextSibling
|
|
}
|
|
return child
|
|
}
|
|
this.getFirstNonWhitespaceChild = getFirstNonWhitespaceChild;
|
|
function parseLength(length) {
|
|
var re = /(-?[0-9]*[0-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px)|(%))/, m = re.exec(length);
|
|
if(!m) {
|
|
return null
|
|
}
|
|
return{value:parseFloat(m[1]), unit:m[3]}
|
|
}
|
|
this.parseLength = parseLength;
|
|
function parsePositiveLength(length) {
|
|
var result = parseLength(length);
|
|
if(result && (result.value <= 0 || result.unit === "%")) {
|
|
return null
|
|
}
|
|
return result
|
|
}
|
|
function parseNonNegativeLength(length) {
|
|
var result = parseLength(length);
|
|
if(result && (result.value < 0 || result.unit === "%")) {
|
|
return null
|
|
}
|
|
return result
|
|
}
|
|
this.parseNonNegativeLength = parseNonNegativeLength;
|
|
function parsePercentage(length) {
|
|
var result = parseLength(length);
|
|
if(result && result.unit !== "%") {
|
|
return null
|
|
}
|
|
return result
|
|
}
|
|
function parseFoFontSize(fontSize) {
|
|
return parsePositiveLength(fontSize) || parsePercentage(fontSize)
|
|
}
|
|
this.parseFoFontSize = parseFoFontSize;
|
|
function parseFoLineHeight(lineHeight) {
|
|
return parseNonNegativeLength(lineHeight) || parsePercentage(lineHeight)
|
|
}
|
|
this.parseFoLineHeight = parseFoLineHeight;
|
|
function isTextContentContainingNode(node) {
|
|
switch(node.namespaceURI) {
|
|
case odf.Namespaces.drawns:
|
|
;
|
|
case odf.Namespaces.svgns:
|
|
;
|
|
case odf.Namespaces.dr3dns:
|
|
return false;
|
|
case odf.Namespaces.textns:
|
|
switch(node.localName) {
|
|
case "note-body":
|
|
;
|
|
case "ruby-text":
|
|
return false
|
|
}
|
|
break;
|
|
case odf.Namespaces.officens:
|
|
switch(node.localName) {
|
|
case "annotation":
|
|
;
|
|
case "binary-data":
|
|
;
|
|
case "event-listeners":
|
|
return false
|
|
}
|
|
break;
|
|
default:
|
|
switch(node.localName) {
|
|
case "cursor":
|
|
;
|
|
case "editinfo":
|
|
return false
|
|
}
|
|
break
|
|
}
|
|
return true
|
|
}
|
|
this.isTextContentContainingNode = isTextContentContainingNode;
|
|
function isSignificantTextContent(textNode) {
|
|
return Boolean(getParagraphElement(textNode) && (!isODFWhitespace(textNode.textContent) || isSignificantWhitespace(textNode, 0)))
|
|
}
|
|
function removePartiallyContainedNodes(range, nodes) {
|
|
while(nodes.length > 0 && !domUtils.rangeContainsNode(range, (nodes[0]))) {
|
|
nodes.shift()
|
|
}
|
|
while(nodes.length > 0 && !domUtils.rangeContainsNode(range, (nodes[nodes.length - 1]))) {
|
|
nodes.pop()
|
|
}
|
|
}
|
|
function getTextNodes(range, includePartial) {
|
|
var textNodes;
|
|
function nodeFilter(node) {
|
|
var result = NodeFilter.FILTER_REJECT;
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
if(isSignificantTextContent((node))) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}
|
|
}else {
|
|
if(isTextContentContainingNode(node)) {
|
|
result = NodeFilter.FILTER_SKIP
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
textNodes = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
if(!includePartial) {
|
|
removePartiallyContainedNodes(range, textNodes)
|
|
}
|
|
return textNodes
|
|
}
|
|
this.getTextNodes = getTextNodes;
|
|
function getTextElements(range, includePartial, includeInsignificantWhitespace) {
|
|
var elements;
|
|
function nodeFilter(node) {
|
|
var result = NodeFilter.FILTER_REJECT;
|
|
if(isCharacterElement(node.parentNode) || isInlineRoot(node)) {
|
|
result = NodeFilter.FILTER_REJECT
|
|
}else {
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
if(includeInsignificantWhitespace || isSignificantTextContent((node))) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}
|
|
}else {
|
|
if(isAnchoredAsCharacterElement(node)) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}else {
|
|
if(isTextContentContainingNode(node) || isGroupingElement(node)) {
|
|
result = NodeFilter.FILTER_SKIP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
if(!includePartial) {
|
|
removePartiallyContainedNodes(range, elements)
|
|
}
|
|
return elements
|
|
}
|
|
this.getTextElements = getTextElements;
|
|
function prependParentContainers(startContainer, elements, filter) {
|
|
var container = startContainer;
|
|
while(container) {
|
|
if(filter(container)) {
|
|
if(elements[0] !== container) {
|
|
elements.unshift(container)
|
|
}
|
|
break
|
|
}
|
|
if(isInlineRoot(container)) {
|
|
break
|
|
}
|
|
container = container.parentNode
|
|
}
|
|
}
|
|
this.getParagraphElements = function(range) {
|
|
var elements;
|
|
function nodeFilter(node) {
|
|
var result = NodeFilter.FILTER_REJECT;
|
|
if(isParagraph(node)) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}else {
|
|
if(isTextContentContainingNode(node) || isGroupingElement(node)) {
|
|
result = NodeFilter.FILTER_SKIP
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT);
|
|
prependParentContainers((range.startContainer), elements, isParagraph);
|
|
return elements
|
|
};
|
|
this.getImageElements = function(range) {
|
|
var elements;
|
|
function nodeFilter(node) {
|
|
var result = NodeFilter.FILTER_SKIP;
|
|
if(isImage(node)) {
|
|
result = NodeFilter.FILTER_ACCEPT
|
|
}
|
|
return result
|
|
}
|
|
elements = domUtils.getNodesInRange(range, nodeFilter, NodeFilter.SHOW_ELEMENT);
|
|
prependParentContainers((range.startContainer), elements, isImage);
|
|
return elements
|
|
};
|
|
function getRightNode(container, offset) {
|
|
var node = container;
|
|
if(offset < node.childNodes.length - 1) {
|
|
node = (node.childNodes[offset + 1])
|
|
}else {
|
|
while(!node.nextSibling) {
|
|
node = node.parentNode
|
|
}
|
|
node = node.nextSibling
|
|
}
|
|
while(node.firstChild) {
|
|
node = node.firstChild
|
|
}
|
|
return node
|
|
}
|
|
this.getHyperlinkElements = function(range) {
|
|
var links = [], newRange = (range.cloneRange()), node, textNodes;
|
|
if(range.collapsed && range.endContainer.nodeType === Node.ELEMENT_NODE) {
|
|
node = getRightNode(range.endContainer, range.endOffset);
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
newRange.setEnd(node, 1)
|
|
}
|
|
}
|
|
textNodes = getTextElements(newRange, true, false);
|
|
textNodes.forEach(function(node) {
|
|
var parent = node.parentNode;
|
|
while(!isParagraph(parent)) {
|
|
if(isHyperlink(parent) && links.indexOf(parent) === -1) {
|
|
links.push(parent);
|
|
break
|
|
}
|
|
parent = parent.parentNode
|
|
}
|
|
});
|
|
newRange.detach();
|
|
return links
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.AnnotatableCanvas = function AnnotatableCanvas() {
|
|
};
|
|
gui.AnnotatableCanvas.prototype.refreshSize = function() {
|
|
};
|
|
gui.AnnotatableCanvas.prototype.getZoomLevel = function() {
|
|
};
|
|
gui.AnnotatableCanvas.prototype.getSizer = function() {
|
|
};
|
|
gui.AnnotationViewManager = function AnnotationViewManager(canvas, odfFragment, annotationsPane, showAnnotationRemoveButton) {
|
|
var annotations = [], doc = odfFragment.ownerDocument, odfUtils = new odf.OdfUtils, CONNECTOR_MARGIN = 30, NOTE_MARGIN = 20, window = runtime.getWindow();
|
|
runtime.assert(Boolean(window), "Expected to be run in an environment which has a global window, like a browser.");
|
|
function wrapAnnotation(annotation) {
|
|
var annotationWrapper = doc.createElement("div"), annotationNote = doc.createElement("div"), connectorHorizontal = doc.createElement("div"), connectorAngular = doc.createElement("div"), removeButton;
|
|
annotationWrapper.className = "annotationWrapper";
|
|
annotation.parentNode.insertBefore(annotationWrapper, annotation);
|
|
annotationNote.className = "annotationNote";
|
|
annotationNote.appendChild(annotation);
|
|
if(showAnnotationRemoveButton) {
|
|
removeButton = doc.createElement("div");
|
|
removeButton.className = "annotationRemoveButton";
|
|
annotationNote.appendChild(removeButton)
|
|
}
|
|
connectorHorizontal.className = "annotationConnector horizontal";
|
|
connectorAngular.className = "annotationConnector angular";
|
|
annotationWrapper.appendChild(annotationNote);
|
|
annotationWrapper.appendChild(connectorHorizontal);
|
|
annotationWrapper.appendChild(connectorAngular)
|
|
}
|
|
function unwrapAnnotation(annotation) {
|
|
var annotationWrapper = annotation.parentNode.parentNode;
|
|
if(annotationWrapper.localName === "div") {
|
|
annotationWrapper.parentNode.insertBefore(annotation, annotationWrapper);
|
|
annotationWrapper.parentNode.removeChild(annotationWrapper)
|
|
}
|
|
}
|
|
function highlightAnnotation(annotation) {
|
|
var annotationEnd = annotation.annotationEndElement, range = doc.createRange(), annotationName = annotation.getAttributeNS(odf.Namespaces.officens, "name"), textNodes;
|
|
if(annotationEnd) {
|
|
range.setStart(annotation, annotation.childNodes.length);
|
|
range.setEnd(annotationEnd, 0);
|
|
textNodes = odfUtils.getTextNodes(range, false);
|
|
textNodes.forEach(function(n) {
|
|
var container = doc.createElement("span");
|
|
container.className = "annotationHighlight";
|
|
container.setAttribute("annotation", annotationName);
|
|
n.parentNode.insertBefore(container, n);
|
|
container.appendChild(n)
|
|
})
|
|
}
|
|
range.detach()
|
|
}
|
|
function unhighlightAnnotation(annotation) {
|
|
var annotationName = annotation.getAttributeNS(odf.Namespaces.officens, "name"), highlightSpans = doc.querySelectorAll('span.annotationHighlight[annotation="' + annotationName + '"]'), i, container;
|
|
for(i = 0;i < highlightSpans.length;i += 1) {
|
|
container = highlightSpans.item(i);
|
|
while(container.firstChild) {
|
|
container.parentNode.insertBefore(container.firstChild, container)
|
|
}
|
|
container.parentNode.removeChild(container)
|
|
}
|
|
}
|
|
function lineDistance(point1, point2) {
|
|
var xs = 0, ys = 0;
|
|
xs = point2.x - point1.x;
|
|
xs = xs * xs;
|
|
ys = point2.y - point1.y;
|
|
ys = ys * ys;
|
|
return Math.sqrt(xs + ys)
|
|
}
|
|
function renderAnnotation(annotation) {
|
|
var annotationNote = (annotation.parentNode), connectorHorizontal = annotationNote.nextElementSibling, connectorAngular = connectorHorizontal.nextElementSibling, annotationWrapper = (annotationNote.parentNode), connectorAngle = 0, previousAnnotation = annotations[annotations.indexOf(annotation) - 1], previousRect, zoomLevel = canvas.getZoomLevel();
|
|
annotationNote.style.left = (annotationsPane.getBoundingClientRect().left - annotationWrapper.getBoundingClientRect().left) / zoomLevel + "px";
|
|
annotationNote.style.width = annotationsPane.getBoundingClientRect().width / zoomLevel + "px";
|
|
connectorHorizontal.style.width = parseFloat(annotationNote.style.left) - CONNECTOR_MARGIN + "px";
|
|
if(previousAnnotation) {
|
|
previousRect = (previousAnnotation.parentNode).getBoundingClientRect();
|
|
if((annotationWrapper.getBoundingClientRect().top - previousRect.bottom) / zoomLevel <= NOTE_MARGIN) {
|
|
annotationNote.style.top = Math.abs(annotationWrapper.getBoundingClientRect().top - previousRect.bottom) / zoomLevel + NOTE_MARGIN + "px"
|
|
}else {
|
|
annotationNote.style.top = "0px"
|
|
}
|
|
}
|
|
connectorAngular.style.left = connectorHorizontal.getBoundingClientRect().width / zoomLevel + "px";
|
|
connectorAngular.style.width = lineDistance({x:connectorAngular.getBoundingClientRect().left / zoomLevel, y:connectorAngular.getBoundingClientRect().top / zoomLevel}, {x:annotationNote.getBoundingClientRect().left / zoomLevel, y:annotationNote.getBoundingClientRect().top / zoomLevel}) + "px";
|
|
connectorAngle = Math.asin((annotationNote.getBoundingClientRect().top - connectorAngular.getBoundingClientRect().top) / (zoomLevel * parseFloat(connectorAngular.style.width)));
|
|
connectorAngular.style.transform = "rotate(" + connectorAngle + "rad)";
|
|
connectorAngular.style.MozTransform = "rotate(" + connectorAngle + "rad)";
|
|
connectorAngular.style.WebkitTransform = "rotate(" + connectorAngle + "rad)";
|
|
connectorAngular.style.msTransform = "rotate(" + connectorAngle + "rad)"
|
|
}
|
|
function showAnnotationsPane(show) {
|
|
var sizer = canvas.getSizer();
|
|
if(show) {
|
|
annotationsPane.style.display = "inline-block";
|
|
sizer.style.paddingRight = window.getComputedStyle(annotationsPane).width
|
|
}else {
|
|
annotationsPane.style.display = "none";
|
|
sizer.style.paddingRight = 0
|
|
}
|
|
canvas.refreshSize()
|
|
}
|
|
function sortAnnotations() {
|
|
annotations.sort(function(a, b) {
|
|
if((a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) {
|
|
return-1
|
|
}
|
|
return 1
|
|
})
|
|
}
|
|
function rerenderAnnotations() {
|
|
var i;
|
|
for(i = 0;i < annotations.length;i += 1) {
|
|
renderAnnotation(annotations[i])
|
|
}
|
|
}
|
|
this.rerenderAnnotations = rerenderAnnotations;
|
|
function getMinimumHeightForAnnotationPane() {
|
|
if(annotationsPane.style.display !== "none" && annotations.length > 0) {
|
|
return((annotations[annotations.length - 1].parentNode).getBoundingClientRect().bottom - annotationsPane.getBoundingClientRect().top) / canvas.getZoomLevel() + "px"
|
|
}
|
|
return null
|
|
}
|
|
this.getMinimumHeightForAnnotationPane = getMinimumHeightForAnnotationPane;
|
|
function addAnnotation(annotation) {
|
|
showAnnotationsPane(true);
|
|
annotations.push(annotation);
|
|
sortAnnotations();
|
|
wrapAnnotation(annotation);
|
|
if(annotation.annotationEndElement) {
|
|
highlightAnnotation(annotation)
|
|
}
|
|
rerenderAnnotations()
|
|
}
|
|
this.addAnnotation = addAnnotation;
|
|
function forgetAnnotation(annotation) {
|
|
var index = annotations.indexOf(annotation);
|
|
unwrapAnnotation(annotation);
|
|
unhighlightAnnotation(annotation);
|
|
if(index !== -1) {
|
|
annotations.splice(index, 1)
|
|
}
|
|
if(annotations.length === 0) {
|
|
showAnnotationsPane(false)
|
|
}
|
|
}
|
|
function forgetAnnotations() {
|
|
while(annotations.length) {
|
|
forgetAnnotation(annotations[0])
|
|
}
|
|
}
|
|
this.forgetAnnotations = forgetAnnotations
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
function Point(x, y) {
|
|
var self = this;
|
|
this.getDistance = function(point) {
|
|
var xOffset = self.x - point.x, yOffset = self.y - point.y;
|
|
return Math.sqrt(xOffset * xOffset + yOffset * yOffset)
|
|
};
|
|
this.getCenter = function(point) {
|
|
return new Point((self.x + point.x) / 2, (self.y + point.y) / 2)
|
|
};
|
|
this.x;
|
|
this.y;
|
|
function init() {
|
|
self.x = x;
|
|
self.y = y
|
|
}
|
|
init()
|
|
}
|
|
gui.ZoomHelper = function() {
|
|
var zoomableElement, panPoint, previousPanPoint, firstPinchDistance, zoom, previousZoom, maxZoom = 4, offsetParent, events = new core.EventNotifier([gui.ZoomHelper.signalZoomChanged]), gestures = {NONE:0, SCROLL:1, PINCH:2}, currentGesture = gestures.NONE, requiresCustomScrollBars = runtime.getWindow().hasOwnProperty("ontouchstart");
|
|
function applyCSSTransform(x, y, scale, is3D) {
|
|
var transformCommand;
|
|
if(is3D) {
|
|
transformCommand = "translate3d(" + x + "px, " + y + "px, 0) scale3d(" + scale + ", " + scale + ", 1)"
|
|
}else {
|
|
transformCommand = "translate(" + x + "px, " + y + "px) scale(" + scale + ")"
|
|
}
|
|
zoomableElement.style.WebkitTransform = transformCommand;
|
|
zoomableElement.style.MozTransform = transformCommand;
|
|
zoomableElement.style.msTransform = transformCommand;
|
|
zoomableElement.style.OTransform = transformCommand;
|
|
zoomableElement.style.transform = transformCommand
|
|
}
|
|
function applyTransform(is3D) {
|
|
if(is3D) {
|
|
applyCSSTransform(-panPoint.x, -panPoint.y, zoom, true)
|
|
}else {
|
|
applyCSSTransform(0, 0, zoom, true);
|
|
applyCSSTransform(0, 0, zoom, false)
|
|
}
|
|
}
|
|
function applyFastTransform() {
|
|
applyTransform(true)
|
|
}
|
|
function applyDetailedTransform() {
|
|
applyTransform(false)
|
|
}
|
|
function enableScrollBars(enable) {
|
|
if(!offsetParent || !requiresCustomScrollBars) {
|
|
return
|
|
}
|
|
var initialOverflow = offsetParent.style.overflow, enabled = offsetParent.classList.contains("customScrollbars");
|
|
if(enable && enabled || !enable && !enabled) {
|
|
return
|
|
}
|
|
if(enable) {
|
|
offsetParent.classList.add("customScrollbars");
|
|
offsetParent.style.overflow = "hidden";
|
|
runtime.requestAnimationFrame(function() {
|
|
offsetParent.style.overflow = initialOverflow
|
|
})
|
|
}else {
|
|
offsetParent.classList.remove("customScrollbars")
|
|
}
|
|
}
|
|
function removeScroll() {
|
|
applyCSSTransform(-panPoint.x, -panPoint.y, zoom, true);
|
|
offsetParent.scrollLeft = 0;
|
|
offsetParent.scrollTop = 0;
|
|
enableScrollBars(false)
|
|
}
|
|
function restoreScroll() {
|
|
applyCSSTransform(0, 0, zoom, true);
|
|
offsetParent.scrollLeft = panPoint.x;
|
|
offsetParent.scrollTop = panPoint.y;
|
|
enableScrollBars(true)
|
|
}
|
|
function getPoint(touch) {
|
|
return new Point(touch.pageX - zoomableElement.offsetLeft, touch.pageY - zoomableElement.offsetTop)
|
|
}
|
|
function sanitizePointForPan(point) {
|
|
return new Point(Math.min(Math.max(point.x, zoomableElement.offsetLeft), (zoomableElement.offsetLeft + zoomableElement.offsetWidth) * zoom - offsetParent.clientWidth), Math.min(Math.max(point.y, zoomableElement.offsetTop), (zoomableElement.offsetTop + zoomableElement.offsetHeight) * zoom - offsetParent.clientHeight))
|
|
}
|
|
function processPan(point) {
|
|
if(previousPanPoint) {
|
|
panPoint.x -= point.x - previousPanPoint.x;
|
|
panPoint.y -= point.y - previousPanPoint.y;
|
|
panPoint = sanitizePointForPan(panPoint)
|
|
}
|
|
previousPanPoint = point
|
|
}
|
|
function processZoom(zoomPoint, incrementalZoom) {
|
|
var originalZoom = zoom, actuallyIncrementedZoom, minZoom = Math.min(maxZoom, zoomableElement.offsetParent.clientWidth / zoomableElement.offsetWidth);
|
|
zoom = previousZoom * incrementalZoom;
|
|
zoom = Math.min(Math.max(zoom, minZoom), maxZoom);
|
|
actuallyIncrementedZoom = zoom / originalZoom;
|
|
panPoint.x += (actuallyIncrementedZoom - 1) * (zoomPoint.x + panPoint.x);
|
|
panPoint.y += (actuallyIncrementedZoom - 1) * (zoomPoint.y + panPoint.y)
|
|
}
|
|
function processPinch(point1, point2) {
|
|
var zoomPoint = point1.getCenter(point2), pinchDistance = point1.getDistance(point2), incrementalZoom = pinchDistance / firstPinchDistance;
|
|
processPan(zoomPoint);
|
|
processZoom(zoomPoint, incrementalZoom)
|
|
}
|
|
function prepareGesture(event) {
|
|
var fingers = event.touches.length, point1 = fingers > 0 ? getPoint(event.touches[0]) : null, point2 = fingers > 1 ? getPoint(event.touches[1]) : null;
|
|
if(point1 && point2) {
|
|
firstPinchDistance = point1.getDistance(point2);
|
|
previousZoom = zoom;
|
|
previousPanPoint = point1.getCenter(point2);
|
|
removeScroll();
|
|
currentGesture = gestures.PINCH
|
|
}else {
|
|
if(point1) {
|
|
previousPanPoint = point1;
|
|
currentGesture = gestures.SCROLL
|
|
}
|
|
}
|
|
}
|
|
function processGesture(event) {
|
|
var fingers = event.touches.length, point1 = fingers > 0 ? getPoint(event.touches[0]) : null, point2 = fingers > 1 ? getPoint(event.touches[1]) : null;
|
|
if(point1 && point2) {
|
|
event.preventDefault();
|
|
if(currentGesture === gestures.SCROLL) {
|
|
currentGesture = gestures.PINCH;
|
|
removeScroll();
|
|
firstPinchDistance = point1.getDistance(point2);
|
|
return
|
|
}
|
|
processPinch(point1, point2);
|
|
applyFastTransform()
|
|
}else {
|
|
if(point1) {
|
|
if(currentGesture === gestures.PINCH) {
|
|
currentGesture = gestures.SCROLL;
|
|
restoreScroll();
|
|
return
|
|
}
|
|
processPan(point1)
|
|
}
|
|
}
|
|
}
|
|
function sanitizeGesture() {
|
|
if(currentGesture === gestures.PINCH) {
|
|
events.emit(gui.ZoomHelper.signalZoomChanged, zoom);
|
|
restoreScroll();
|
|
applyDetailedTransform()
|
|
}
|
|
currentGesture = gestures.NONE
|
|
}
|
|
this.subscribe = function(eventid, cb) {
|
|
events.subscribe(eventid, cb)
|
|
};
|
|
this.unsubscribe = function(eventid, cb) {
|
|
events.unsubscribe(eventid, cb)
|
|
};
|
|
this.getZoomLevel = function() {
|
|
return zoom
|
|
};
|
|
this.setZoomLevel = function(zoomLevel) {
|
|
if(zoomableElement) {
|
|
zoom = zoomLevel;
|
|
applyDetailedTransform();
|
|
events.emit(gui.ZoomHelper.signalZoomChanged, zoom)
|
|
}
|
|
};
|
|
function registerGestureListeners() {
|
|
if(offsetParent) {
|
|
offsetParent.addEventListener("touchstart", (prepareGesture), false);
|
|
offsetParent.addEventListener("touchmove", (processGesture), false);
|
|
offsetParent.addEventListener("touchend", (sanitizeGesture), false)
|
|
}
|
|
}
|
|
function unregisterGestureListeners() {
|
|
if(offsetParent) {
|
|
offsetParent.removeEventListener("touchstart", (prepareGesture), false);
|
|
offsetParent.removeEventListener("touchmove", (processGesture), false);
|
|
offsetParent.removeEventListener("touchend", (sanitizeGesture), false)
|
|
}
|
|
}
|
|
this.destroy = function(callback) {
|
|
unregisterGestureListeners();
|
|
enableScrollBars(false);
|
|
callback()
|
|
};
|
|
this.setZoomableElement = function(element) {
|
|
unregisterGestureListeners();
|
|
zoomableElement = element;
|
|
offsetParent = (zoomableElement.offsetParent);
|
|
applyDetailedTransform();
|
|
registerGestureListeners();
|
|
enableScrollBars(true)
|
|
};
|
|
function init() {
|
|
zoom = 1;
|
|
previousZoom = 1;
|
|
panPoint = new Point(0, 0)
|
|
}
|
|
init()
|
|
};
|
|
gui.ZoomHelper.signalZoomChanged = "zoomChanged"
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
var xpath = xmldom.XPath, base64 = new core.Base64;
|
|
function getEmbeddedFontDeclarations(fontFaceDecls) {
|
|
var decls = {}, fonts, i, font, name, uris, href, family;
|
|
if(!fontFaceDecls) {
|
|
return decls
|
|
}
|
|
fonts = xpath.getODFElementsWithXPath(fontFaceDecls, "style:font-face[svg:font-face-src]", odf.Namespaces.lookupNamespaceURI);
|
|
for(i = 0;i < fonts.length;i += 1) {
|
|
font = fonts[i];
|
|
name = font.getAttributeNS(odf.Namespaces.stylens, "name");
|
|
family = font.getAttributeNS(odf.Namespaces.svgns, "font-family");
|
|
uris = xpath.getODFElementsWithXPath(font, "svg:font-face-src/svg:font-face-uri", odf.Namespaces.lookupNamespaceURI);
|
|
if(uris.length > 0) {
|
|
href = uris[0].getAttributeNS(odf.Namespaces.xlinkns, "href");
|
|
decls[name] = {href:href, family:family}
|
|
}
|
|
}
|
|
return decls
|
|
}
|
|
function addFontToCSS(name, font, fontdata, stylesheet) {
|
|
var cssFamily = font.family || name, rule = "@font-face { font-family: '" + cssFamily + "'; src: " + "url(data:application/x-font-ttf;charset=binary;base64," + base64.convertUTF8ArrayToBase64(fontdata) + ') format("truetype"); }';
|
|
try {
|
|
stylesheet.insertRule(rule, stylesheet.cssRules.length)
|
|
}catch(e) {
|
|
runtime.log("Problem inserting rule in CSS: " + runtime.toJson(e) + "\nRule: " + rule)
|
|
}
|
|
}
|
|
function loadFontIntoCSS(embeddedFontDeclarations, odfContainer, pos, stylesheet, callback) {
|
|
var name, i = 0, n;
|
|
for(n in embeddedFontDeclarations) {
|
|
if(embeddedFontDeclarations.hasOwnProperty(n)) {
|
|
if(i === pos) {
|
|
name = n;
|
|
break
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
if(!name) {
|
|
if(callback) {
|
|
callback()
|
|
}
|
|
return
|
|
}
|
|
odfContainer.getPartData(embeddedFontDeclarations[name].href, function(err, fontdata) {
|
|
if(err) {
|
|
runtime.log(err)
|
|
}else {
|
|
if(!fontdata) {
|
|
runtime.log("missing font data for " + embeddedFontDeclarations[name].href)
|
|
}else {
|
|
addFontToCSS(name, embeddedFontDeclarations[name], fontdata, stylesheet)
|
|
}
|
|
}
|
|
loadFontIntoCSS(embeddedFontDeclarations, odfContainer, pos + 1, stylesheet, callback)
|
|
})
|
|
}
|
|
function loadFontsIntoCSS(embeddedFontDeclarations, odfContainer, stylesheet) {
|
|
loadFontIntoCSS(embeddedFontDeclarations, odfContainer, 0, stylesheet)
|
|
}
|
|
odf.FontLoader = function FontLoader() {
|
|
this.loadFonts = function(odfContainer, stylesheet) {
|
|
var embeddedFontDeclarations, fontFaceDecls = odfContainer.rootElement.fontFaceDecls;
|
|
while(stylesheet.cssRules.length) {
|
|
stylesheet.deleteRule(stylesheet.cssRules.length - 1)
|
|
}
|
|
if(fontFaceDecls) {
|
|
embeddedFontDeclarations = getEmbeddedFontDeclarations(fontFaceDecls);
|
|
loadFontsIntoCSS(embeddedFontDeclarations, odfContainer, stylesheet)
|
|
}
|
|
}
|
|
};
|
|
return odf.FontLoader
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.Formatting = function Formatting() {
|
|
var odfContainer, styleInfo = new odf.StyleInfo, svgns = odf.Namespaces.svgns, stylens = odf.Namespaces.stylens, textns = odf.Namespaces.textns, numberns = odf.Namespaces.numberns, fons = odf.Namespaces.fons, odfUtils = new odf.OdfUtils, domUtils = new core.DomUtils, utils = new core.Utils, builtInDefaultStyleAttributesByFamily = {"paragraph":{"style:paragraph-properties":{"fo:text-align":"left"}}}, defaultPageFormatSettings = {width:21.001, height:29.7, margin:2, padding:0};
|
|
function getSystemDefaultStyleAttributes(styleFamily) {
|
|
var result, builtInDefaultStyleAttributes = builtInDefaultStyleAttributesByFamily[styleFamily];
|
|
if(builtInDefaultStyleAttributes) {
|
|
result = utils.mergeObjects({}, builtInDefaultStyleAttributes)
|
|
}else {
|
|
result = {}
|
|
}
|
|
return result
|
|
}
|
|
this.getSystemDefaultStyleAttributes = getSystemDefaultStyleAttributes;
|
|
this.setOdfContainer = function(odfcontainer) {
|
|
odfContainer = odfcontainer
|
|
};
|
|
function getDirectChild(node, ns, name) {
|
|
var e = node && node.firstElementChild;
|
|
while(e) {
|
|
if(e.namespaceURI === ns && e.localName === name) {
|
|
break
|
|
}
|
|
e = e.nextElementSibling
|
|
}
|
|
return e
|
|
}
|
|
function getFontMap() {
|
|
var fontFaceDecls = odfContainer.rootElement.fontFaceDecls, fontFaceDeclsMap = {}, node, name, family;
|
|
node = fontFaceDecls && fontFaceDecls.firstElementChild;
|
|
while(node) {
|
|
name = node.getAttributeNS(stylens, "name");
|
|
if(name) {
|
|
family = node.getAttributeNS(svgns, "font-family");
|
|
if(family || node.getElementsByTagNameNS(svgns, "font-face-uri").length > 0) {
|
|
fontFaceDeclsMap[name] = family
|
|
}
|
|
}
|
|
node = node.nextElementSibling
|
|
}
|
|
return fontFaceDeclsMap
|
|
}
|
|
this.getFontMap = getFontMap;
|
|
this.getAvailableParagraphStyles = function() {
|
|
var node = odfContainer.rootElement.styles, p_family, p_name, p_displayName, paragraphStyles = [];
|
|
node = node && node.firstElementChild;
|
|
while(node) {
|
|
if(node.localName === "style" && node.namespaceURI === stylens) {
|
|
p_family = node.getAttributeNS(stylens, "family");
|
|
if(p_family === "paragraph") {
|
|
p_name = node.getAttributeNS(stylens, "name");
|
|
p_displayName = node.getAttributeNS(stylens, "display-name") || p_name;
|
|
if(p_name && p_displayName) {
|
|
paragraphStyles.push({name:p_name, displayName:p_displayName})
|
|
}
|
|
}
|
|
}
|
|
node = node.nextElementSibling
|
|
}
|
|
return paragraphStyles
|
|
};
|
|
this.isStyleUsed = function(styleElement) {
|
|
var hasDerivedStyles, isUsed, root = odfContainer.rootElement;
|
|
hasDerivedStyles = styleInfo.hasDerivedStyles(root, odf.Namespaces.lookupNamespaceURI, styleElement);
|
|
isUsed = (new styleInfo.UsedStyleList(root.styles)).uses(styleElement) || ((new styleInfo.UsedStyleList(root.automaticStyles)).uses(styleElement) || (new styleInfo.UsedStyleList(root.body)).uses(styleElement));
|
|
return hasDerivedStyles || isUsed
|
|
};
|
|
function getDefaultStyleElement(family) {
|
|
var node = odfContainer.rootElement.styles.firstElementChild;
|
|
while(node) {
|
|
if(node.namespaceURI === stylens && (node.localName === "default-style" && node.getAttributeNS(stylens, "family") === family)) {
|
|
return node
|
|
}
|
|
node = node.nextElementSibling
|
|
}
|
|
return null
|
|
}
|
|
this.getDefaultStyleElement = getDefaultStyleElement;
|
|
function getStyleElement(styleName, family, styleElements) {
|
|
var node, nodeStyleName, styleListElement, i;
|
|
styleElements = styleElements || [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles];
|
|
for(i = 0;i < styleElements.length;i += 1) {
|
|
styleListElement = (styleElements[i]);
|
|
node = styleListElement.firstElementChild;
|
|
while(node) {
|
|
nodeStyleName = node.getAttributeNS(stylens, "name");
|
|
if(node.namespaceURI === stylens && (node.localName === "style" && (node.getAttributeNS(stylens, "family") === family && nodeStyleName === styleName))) {
|
|
return node
|
|
}
|
|
if(family === "list-style" && (node.namespaceURI === textns && (node.localName === "list-style" && nodeStyleName === styleName))) {
|
|
return node
|
|
}
|
|
if(family === "data" && (node.namespaceURI === numberns && nodeStyleName === styleName)) {
|
|
return node
|
|
}
|
|
node = node.nextElementSibling
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
this.getStyleElement = getStyleElement;
|
|
function getStyleAttributes(styleNode) {
|
|
var i, a, map, ai, propertiesMap = {}, propertiesNode = styleNode.firstElementChild;
|
|
while(propertiesNode) {
|
|
if(propertiesNode.namespaceURI === stylens) {
|
|
map = propertiesMap[propertiesNode.nodeName] = {};
|
|
a = propertiesNode.attributes;
|
|
for(i = 0;i < a.length;i += 1) {
|
|
ai = (a.item(i));
|
|
map[ai.name] = ai.value
|
|
}
|
|
}
|
|
propertiesNode = propertiesNode.nextElementSibling
|
|
}
|
|
a = styleNode.attributes;
|
|
for(i = 0;i < a.length;i += 1) {
|
|
ai = (a.item(i));
|
|
propertiesMap[ai.name] = ai.value
|
|
}
|
|
return propertiesMap
|
|
}
|
|
this.getStyleAttributes = getStyleAttributes;
|
|
function getInheritedStyleAttributes(styleNode, includeSystemDefault) {
|
|
var styleListElement = odfContainer.rootElement.styles, parentStyleName, propertiesMap, inheritedPropertiesMap = {}, styleFamily = styleNode.getAttributeNS(stylens, "family"), node = styleNode;
|
|
while(node) {
|
|
propertiesMap = getStyleAttributes(node);
|
|
inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap);
|
|
parentStyleName = node.getAttributeNS(stylens, "parent-style-name");
|
|
if(parentStyleName) {
|
|
node = getStyleElement(parentStyleName, styleFamily, [styleListElement])
|
|
}else {
|
|
node = null
|
|
}
|
|
}
|
|
node = getDefaultStyleElement(styleFamily);
|
|
if(node) {
|
|
propertiesMap = getStyleAttributes(node);
|
|
inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap)
|
|
}
|
|
if(includeSystemDefault !== false) {
|
|
propertiesMap = getSystemDefaultStyleAttributes(styleFamily);
|
|
inheritedPropertiesMap = utils.mergeObjects(propertiesMap, inheritedPropertiesMap)
|
|
}
|
|
return inheritedPropertiesMap
|
|
}
|
|
this.getInheritedStyleAttributes = getInheritedStyleAttributes;
|
|
this.getFirstCommonParentStyleNameOrSelf = function(styleName) {
|
|
var automaticStyleElementList = odfContainer.rootElement.automaticStyles, styleElementList = odfContainer.rootElement.styles, styleElement;
|
|
styleElement = getStyleElement(styleName, "paragraph", [automaticStyleElementList]);
|
|
while(styleElement) {
|
|
styleName = styleElement.getAttributeNS(stylens, "parent-style-name");
|
|
styleElement = getStyleElement(styleName, "paragraph", [automaticStyleElementList])
|
|
}
|
|
styleElement = getStyleElement(styleName, "paragraph", [styleElementList]);
|
|
if(!styleElement) {
|
|
return null
|
|
}
|
|
return styleName
|
|
};
|
|
this.hasParagraphStyle = function(styleName) {
|
|
return Boolean(getStyleElement(styleName, "paragraph"))
|
|
};
|
|
function buildStyleChain(node, collectedChains) {
|
|
var parent = (node.nodeType === Node.TEXT_NODE ? node.parentNode : node), nodeStyles, appliedStyles = [], chainKey = "", foundContainer = false;
|
|
while(parent) {
|
|
if(!foundContainer && odfUtils.isGroupingElement(parent)) {
|
|
foundContainer = true
|
|
}
|
|
nodeStyles = styleInfo.determineStylesForNode(parent);
|
|
if(nodeStyles) {
|
|
appliedStyles.push(nodeStyles)
|
|
}
|
|
parent = (parent.parentNode)
|
|
}
|
|
function chainStyles(usedStyleMap) {
|
|
Object.keys(usedStyleMap).forEach(function(styleFamily) {
|
|
Object.keys(usedStyleMap[styleFamily]).forEach(function(styleName) {
|
|
chainKey += "|" + styleFamily + ":" + styleName + "|"
|
|
})
|
|
})
|
|
}
|
|
if(foundContainer) {
|
|
appliedStyles.forEach(chainStyles);
|
|
if(collectedChains) {
|
|
collectedChains[chainKey] = appliedStyles
|
|
}
|
|
}
|
|
return foundContainer ? appliedStyles : undefined
|
|
}
|
|
function isCommonStyleElement(styleNode) {
|
|
return styleNode.parentNode === odfContainer.rootElement.styles
|
|
}
|
|
function calculateAppliedStyle(styleChain) {
|
|
var mergedChildStyle = {orderedStyles:[]};
|
|
styleChain.forEach(function(elementStyleSet) {
|
|
Object.keys((elementStyleSet)).forEach(function(styleFamily) {
|
|
var styleName = Object.keys(elementStyleSet[styleFamily])[0], styleSummary = {name:styleName, family:styleFamily, displayName:undefined, isCommonStyle:false}, styleElement, parentStyle;
|
|
styleElement = getStyleElement(styleName, styleFamily);
|
|
if(styleElement) {
|
|
parentStyle = getInheritedStyleAttributes((styleElement));
|
|
mergedChildStyle = utils.mergeObjects(parentStyle, mergedChildStyle);
|
|
styleSummary.displayName = styleElement.getAttributeNS(stylens, "display-name");
|
|
styleSummary.isCommonStyle = isCommonStyleElement(styleElement)
|
|
}else {
|
|
runtime.log("No style element found for '" + styleName + "' of family '" + styleFamily + "'")
|
|
}
|
|
mergedChildStyle.orderedStyles.push(styleSummary)
|
|
})
|
|
});
|
|
return mergedChildStyle
|
|
}
|
|
function getAppliedStyles(nodes, calculatedStylesCache) {
|
|
var styleChains = {}, styles = [];
|
|
if(!calculatedStylesCache) {
|
|
calculatedStylesCache = {}
|
|
}
|
|
nodes.forEach(function(n) {
|
|
buildStyleChain(n, styleChains)
|
|
});
|
|
Object.keys(styleChains).forEach(function(key) {
|
|
if(!calculatedStylesCache[key]) {
|
|
calculatedStylesCache[key] = calculateAppliedStyle(styleChains[key])
|
|
}
|
|
styles.push(calculatedStylesCache[key])
|
|
});
|
|
return styles
|
|
}
|
|
this.getAppliedStyles = getAppliedStyles;
|
|
this.getAppliedStylesForElement = function(node, calculatedStylesCache) {
|
|
return getAppliedStyles([node], calculatedStylesCache)[0]
|
|
};
|
|
this.updateStyle = function(styleNode, properties) {
|
|
var fontName, fontFaceNode;
|
|
domUtils.mapObjOntoNode(styleNode, properties, odf.Namespaces.lookupNamespaceURI);
|
|
fontName = properties["style:text-properties"] && properties["style:text-properties"]["style:font-name"];
|
|
if(fontName && !getFontMap().hasOwnProperty(fontName)) {
|
|
fontFaceNode = styleNode.ownerDocument.createElementNS(stylens, "style:font-face");
|
|
fontFaceNode.setAttributeNS(stylens, "style:name", fontName);
|
|
fontFaceNode.setAttributeNS(svgns, "svg:font-family", fontName);
|
|
odfContainer.rootElement.fontFaceDecls.appendChild(fontFaceNode)
|
|
}
|
|
};
|
|
this.createDerivedStyleObject = function(parentStyleName, family, overrides) {
|
|
var originalStyleElement = (getStyleElement(parentStyleName, family)), newStyleObject;
|
|
runtime.assert(Boolean(originalStyleElement), "No style element found for '" + parentStyleName + "' of family '" + family + "'");
|
|
if(isCommonStyleElement(originalStyleElement)) {
|
|
newStyleObject = {"style:parent-style-name":parentStyleName}
|
|
}else {
|
|
newStyleObject = getStyleAttributes(originalStyleElement)
|
|
}
|
|
newStyleObject["style:family"] = family;
|
|
utils.mergeObjects(newStyleObject, overrides);
|
|
return newStyleObject
|
|
};
|
|
this.getDefaultTabStopDistance = function() {
|
|
var defaultParagraph = getDefaultStyleElement("paragraph"), paragraphProperties = defaultParagraph && defaultParagraph.firstElementChild, tabStopDistance;
|
|
while(paragraphProperties) {
|
|
if(paragraphProperties.namespaceURI === stylens && paragraphProperties.localName === "paragraph-properties") {
|
|
tabStopDistance = paragraphProperties.getAttributeNS(stylens, "tab-stop-distance")
|
|
}
|
|
paragraphProperties = paragraphProperties.nextElementSibling
|
|
}
|
|
if(!tabStopDistance) {
|
|
tabStopDistance = "1.25cm"
|
|
}
|
|
return odfUtils.parseNonNegativeLength(tabStopDistance)
|
|
};
|
|
function getPageLayoutStyleElement(styleName, styleFamily) {
|
|
var masterPageName, layoutName, pageLayoutElements, node, i, styleElement = getStyleElement(styleName, styleFamily);
|
|
runtime.assert(styleFamily === "paragraph" || styleFamily === "table", "styleFamily has to be either paragraph or table");
|
|
if(styleElement) {
|
|
masterPageName = styleElement.getAttributeNS(stylens, "master-page-name") || "Standard";
|
|
node = odfContainer.rootElement.masterStyles.lastElementChild;
|
|
while(node) {
|
|
if(node.getAttributeNS(stylens, "name") === masterPageName) {
|
|
break
|
|
}
|
|
node = node.previousElementSibling
|
|
}
|
|
layoutName = node.getAttributeNS(stylens, "page-layout-name");
|
|
pageLayoutElements = domUtils.getElementsByTagNameNS(odfContainer.rootElement.automaticStyles, stylens, "page-layout");
|
|
for(i = 0;i < pageLayoutElements.length;i += 1) {
|
|
node = pageLayoutElements[i];
|
|
if(node.getAttributeNS(stylens, "name") === layoutName) {
|
|
return(node)
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
function lengthInCm(length, defaultValue) {
|
|
var result = odfUtils.parseLength(length), value = defaultValue;
|
|
if(result) {
|
|
switch(result.unit) {
|
|
case "cm":
|
|
value = result.value;
|
|
break;
|
|
case "mm":
|
|
value = result.value * 0.1;
|
|
break;
|
|
case "in":
|
|
value = result.value * 2.54;
|
|
break;
|
|
case "pt":
|
|
value = result.value * 0.035277778;
|
|
break;
|
|
case "pc":
|
|
;
|
|
case "px":
|
|
;
|
|
case "em":
|
|
break;
|
|
default:
|
|
runtime.log("Unit identifier: " + result.unit + " is not supported.");
|
|
break
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
this.getContentSize = function(styleName, styleFamily) {
|
|
var pageLayoutElement, props, printOrientation, defaultOrientedPageWidth, defaultOrientedPageHeight, pageWidth, pageHeight, margin, marginLeft, marginRight, marginTop, marginBottom, padding, paddingLeft, paddingRight, paddingTop, paddingBottom;
|
|
pageLayoutElement = getPageLayoutStyleElement(styleName, styleFamily);
|
|
if(!pageLayoutElement) {
|
|
pageLayoutElement = getDirectChild(odfContainer.rootElement.styles, stylens, "default-page-layout")
|
|
}
|
|
props = getDirectChild(pageLayoutElement, stylens, "page-layout-properties");
|
|
if(props) {
|
|
printOrientation = props.getAttributeNS(stylens, "print-orientation") || "portrait";
|
|
if(printOrientation === "portrait") {
|
|
defaultOrientedPageWidth = defaultPageFormatSettings.width;
|
|
defaultOrientedPageHeight = defaultPageFormatSettings.height
|
|
}else {
|
|
defaultOrientedPageWidth = defaultPageFormatSettings.height;
|
|
defaultOrientedPageHeight = defaultPageFormatSettings.width
|
|
}
|
|
pageWidth = lengthInCm(props.getAttributeNS(fons, "page-width"), defaultOrientedPageWidth);
|
|
pageHeight = lengthInCm(props.getAttributeNS(fons, "page-height"), defaultOrientedPageHeight);
|
|
margin = lengthInCm(props.getAttributeNS(fons, "margin"), null);
|
|
if(margin === null) {
|
|
marginLeft = lengthInCm(props.getAttributeNS(fons, "margin-left"), defaultPageFormatSettings.margin);
|
|
marginRight = lengthInCm(props.getAttributeNS(fons, "margin-right"), defaultPageFormatSettings.margin);
|
|
marginTop = lengthInCm(props.getAttributeNS(fons, "margin-top"), defaultPageFormatSettings.margin);
|
|
marginBottom = lengthInCm(props.getAttributeNS(fons, "margin-bottom"), defaultPageFormatSettings.margin)
|
|
}else {
|
|
marginLeft = marginRight = marginTop = marginBottom = margin
|
|
}
|
|
padding = lengthInCm(props.getAttributeNS(fons, "padding"), null);
|
|
if(padding === null) {
|
|
paddingLeft = lengthInCm(props.getAttributeNS(fons, "padding-left"), defaultPageFormatSettings.padding);
|
|
paddingRight = lengthInCm(props.getAttributeNS(fons, "padding-right"), defaultPageFormatSettings.padding);
|
|
paddingTop = lengthInCm(props.getAttributeNS(fons, "padding-top"), defaultPageFormatSettings.padding);
|
|
paddingBottom = lengthInCm(props.getAttributeNS(fons, "padding-bottom"), defaultPageFormatSettings.padding)
|
|
}else {
|
|
paddingLeft = paddingRight = paddingTop = paddingBottom = padding
|
|
}
|
|
}
|
|
return{width:pageWidth - marginLeft - marginRight - paddingLeft - paddingRight, height:pageHeight - marginTop - marginBottom - paddingTop - paddingBottom}
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.StyleTreeNode = function StyleTreeNode(element) {
|
|
this.derivedStyles = {};
|
|
this.element = element
|
|
};
|
|
odf.Style2CSS = function Style2CSS() {
|
|
var drawns = odf.Namespaces.drawns, fons = odf.Namespaces.fons, officens = odf.Namespaces.officens, stylens = odf.Namespaces.stylens, svgns = odf.Namespaces.svgns, tablens = odf.Namespaces.tablens, textns = odf.Namespaces.textns, xlinkns = odf.Namespaces.xlinkns, presentationns = odf.Namespaces.presentationns, familynamespaceprefixes = {"graphic":"draw", "drawing-page":"draw", "paragraph":"text", "presentation":"presentation", "ruby":"text", "section":"text", "table":"table", "table-cell":"table",
|
|
"table-column":"table", "table-row":"table", "text":"text", "list":"text", "page":"office"}, familytagnames = {"graphic":["circle", "connected", "control", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"], "paragraph":["alphabetical-index-entry-template", "h", "illustration-index-entry-template", "index-source-style", "object-index-entry-template", "p", "table-index-entry-template", "table-of-content-entry-template",
|
|
"user-index-entry-template"], "presentation":["caption", "circle", "connector", "control", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"], "drawing-page":["caption", "circle", "connector", "control", "page", "custom-shape", "ellipse", "frame", "g", "line", "measure", "page-thumbnail", "path", "polygon", "polyline", "rect", "regular-polygon"], "ruby":["ruby", "ruby-text"], "section":["alphabetical-index", "bibliography",
|
|
"illustration-index", "index-title", "object-index", "section", "table-of-content", "table-index", "user-index"], "table":["background", "table"], "table-cell":["body", "covered-table-cell", "even-columns", "even-rows", "first-column", "first-row", "last-column", "last-row", "odd-columns", "odd-rows", "table-cell"], "table-column":["table-column"], "table-row":["table-row"], "text":["a", "index-entry-chapter", "index-entry-link-end", "index-entry-link-start", "index-entry-page-number", "index-entry-span",
|
|
"index-entry-tab-stop", "index-entry-text", "index-title-template", "linenumbering-configuration", "list-level-style-number", "list-level-style-bullet", "outline-level-style", "span"], "list":["list-item"]}, textPropertySimpleMapping = [[fons, "color", "color"], [fons, "background-color", "background-color"], [fons, "font-weight", "font-weight"], [fons, "font-style", "font-style"]], bgImageSimpleMapping = [[stylens, "repeat", "background-repeat"]], paragraphPropertySimpleMapping = [[fons, "background-color",
|
|
"background-color"], [fons, "text-align", "text-align"], [fons, "text-indent", "text-indent"], [fons, "padding", "padding"], [fons, "padding-left", "padding-left"], [fons, "padding-right", "padding-right"], [fons, "padding-top", "padding-top"], [fons, "padding-bottom", "padding-bottom"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"], [fons, "margin", "margin"], [fons, "margin-left", "margin-left"],
|
|
[fons, "margin-right", "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"], [fons, "border", "border"]], graphicPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "min-height", "min-height"], [drawns, "stroke", "border"], [svgns, "stroke-color", "border-color"], [svgns, "stroke-width", "border-width"], [fons, "border", "border"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top",
|
|
"border-top"], [fons, "border-bottom", "border-bottom"]], tablecellPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"], [fons, "border", "border"]], tablecolumnPropertySimpleMapping = [[stylens, "column-width", "width"]], tablerowPropertySimpleMapping = [[stylens, "row-height", "height"], [fons, "keep-together", null]], tablePropertySimpleMapping =
|
|
[[stylens, "width", "width"], [fons, "margin-left", "margin-left"], [fons, "margin-right", "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"]], pageContentPropertySimpleMapping = [[fons, "background-color", "background-color"], [fons, "padding", "padding"], [fons, "padding-left", "padding-left"], [fons, "padding-right", "padding-right"], [fons, "padding-top", "padding-top"], [fons, "padding-bottom", "padding-bottom"], [fons, "border", "border"], [fons,
|
|
"border-left", "border-left"], [fons, "border-right", "border-right"], [fons, "border-top", "border-top"], [fons, "border-bottom", "border-bottom"], [fons, "margin", "margin"], [fons, "margin-left", "margin-left"], [fons, "margin-right", "margin-right"], [fons, "margin-top", "margin-top"], [fons, "margin-bottom", "margin-bottom"]], pageSizePropertySimpleMapping = [[fons, "page-width", "width"], [fons, "page-height", "height"]], borderPropertyMap = {"border":true, "border-left":true, "border-right":true,
|
|
"border-top":true, "border-bottom":true, "stroke-width":true}, fontFaceDeclsMap = {}, utils = new odf.OdfUtils, documentType, odfRoot, defaultFontSize, xpath = xmldom.XPath, cssUnits = new core.CSSUnits;
|
|
function getStyleMap(stylesnode) {
|
|
var node, name, family, style, stylemap = {};
|
|
if(!stylesnode) {
|
|
return stylemap
|
|
}
|
|
node = stylesnode.firstElementChild;
|
|
while(node) {
|
|
if(node.namespaceURI === stylens && (node.localName === "style" || node.localName === "default-style")) {
|
|
family = node.getAttributeNS(stylens, "family")
|
|
}else {
|
|
if(node.namespaceURI === textns && node.localName === "list-style") {
|
|
family = "list"
|
|
}else {
|
|
if(node.namespaceURI === stylens && (node.localName === "page-layout" || node.localName === "default-page-layout")) {
|
|
family = "page"
|
|
}else {
|
|
family = undefined
|
|
}
|
|
}
|
|
}
|
|
if(family) {
|
|
name = node.getAttributeNS(stylens, "name");
|
|
if(!name) {
|
|
name = ""
|
|
}
|
|
if(stylemap.hasOwnProperty(family)) {
|
|
style = stylemap[family]
|
|
}else {
|
|
stylemap[family] = style = {}
|
|
}
|
|
style[name] = node
|
|
}
|
|
node = node.nextElementSibling
|
|
}
|
|
return stylemap
|
|
}
|
|
function findStyle(stylestree, name) {
|
|
if(stylestree.hasOwnProperty(name)) {
|
|
return stylestree[name]
|
|
}
|
|
var n, style = null;
|
|
for(n in stylestree) {
|
|
if(stylestree.hasOwnProperty(n)) {
|
|
style = findStyle(stylestree[n].derivedStyles, name);
|
|
if(style) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return style
|
|
}
|
|
function addStyleToStyleTree(stylename, stylesmap, stylestree) {
|
|
var style, parentname, parentstyle;
|
|
if(!stylesmap.hasOwnProperty(stylename)) {
|
|
return null
|
|
}
|
|
style = new odf.StyleTreeNode(stylesmap[stylename]);
|
|
parentname = style.element.getAttributeNS(stylens, "parent-style-name");
|
|
parentstyle = null;
|
|
if(parentname) {
|
|
parentstyle = findStyle(stylestree, parentname) || addStyleToStyleTree(parentname, stylesmap, stylestree)
|
|
}
|
|
if(parentstyle) {
|
|
parentstyle.derivedStyles[stylename] = style
|
|
}else {
|
|
stylestree[stylename] = style
|
|
}
|
|
delete stylesmap[stylename];
|
|
return style
|
|
}
|
|
function addStyleMapToStyleTree(stylesmap, stylestree) {
|
|
var name;
|
|
for(name in stylesmap) {
|
|
if(stylesmap.hasOwnProperty(name)) {
|
|
addStyleToStyleTree(name, stylesmap, stylestree)
|
|
}
|
|
}
|
|
}
|
|
function createSelector(family, name) {
|
|
var prefix = familynamespaceprefixes[family], namepart, selector;
|
|
if(prefix === undefined) {
|
|
return null
|
|
}
|
|
if(name) {
|
|
namepart = "[" + prefix + '|style-name="' + name + '"]'
|
|
}else {
|
|
namepart = ""
|
|
}
|
|
if(prefix === "presentation") {
|
|
prefix = "draw";
|
|
if(name) {
|
|
namepart = '[presentation|style-name="' + name + '"]'
|
|
}else {
|
|
namepart = ""
|
|
}
|
|
}
|
|
selector = prefix + "|" + familytagnames[family].join(namepart + "," + prefix + "|") + namepart;
|
|
return selector
|
|
}
|
|
function getSelectors(family, name, node) {
|
|
var selectors = [], ss, derivedStyles = node.derivedStyles, n;
|
|
ss = createSelector(family, name);
|
|
if(ss !== null) {
|
|
selectors.push(ss)
|
|
}
|
|
for(n in derivedStyles) {
|
|
if(derivedStyles.hasOwnProperty(n)) {
|
|
ss = getSelectors(family, n, derivedStyles[n]);
|
|
selectors = selectors.concat(ss)
|
|
}
|
|
}
|
|
return selectors
|
|
}
|
|
function getDirectChild(node, ns, name) {
|
|
var e = node && node.firstElementChild;
|
|
while(e) {
|
|
if(e.namespaceURI === ns && e.localName === name) {
|
|
break
|
|
}
|
|
e = e.nextElementSibling
|
|
}
|
|
return e
|
|
}
|
|
function fixBorderWidth(value) {
|
|
var index = value.indexOf(" "), width, theRestOfBorderAttributes;
|
|
if(index !== -1) {
|
|
width = value.substring(0, index);
|
|
theRestOfBorderAttributes = value.substring(index)
|
|
}else {
|
|
width = value;
|
|
theRestOfBorderAttributes = ""
|
|
}
|
|
width = utils.parseLength(width);
|
|
if(width && (width.unit === "pt" && width.value < 0.75)) {
|
|
value = "0.75pt" + theRestOfBorderAttributes
|
|
}
|
|
return value
|
|
}
|
|
function applySimpleMapping(props, mapping) {
|
|
var rule = "", i, r, value;
|
|
for(i = 0;i < mapping.length;i += 1) {
|
|
r = mapping[i];
|
|
value = props.getAttributeNS(r[0], r[1]);
|
|
if(value) {
|
|
value = value.trim();
|
|
if(borderPropertyMap.hasOwnProperty(r[1])) {
|
|
value = fixBorderWidth(value)
|
|
}
|
|
if(r[2]) {
|
|
rule += r[2] + ":" + value + ";"
|
|
}
|
|
}
|
|
}
|
|
return rule
|
|
}
|
|
function getFontSize(styleNode) {
|
|
var props = getDirectChild(styleNode, stylens, "text-properties");
|
|
if(props) {
|
|
return utils.parseFoFontSize(props.getAttributeNS(fons, "font-size"))
|
|
}
|
|
return null
|
|
}
|
|
function getParentStyleNode(styleNode) {
|
|
var parentStyleName = "", parentStyleFamily = "", parentStyleNode = null, xp;
|
|
if(styleNode.localName === "default-style") {
|
|
return null
|
|
}
|
|
parentStyleName = styleNode.getAttributeNS(stylens, "parent-style-name");
|
|
parentStyleFamily = styleNode.getAttributeNS(stylens, "family");
|
|
if(parentStyleName) {
|
|
xp = "//style:*[@style:name='" + parentStyleName + "'][@style:family='" + parentStyleFamily + "']"
|
|
}else {
|
|
xp = "//style:default-style[@style:family='" + parentStyleFamily + "']"
|
|
}
|
|
parentStyleNode = xpath.getODFElementsWithXPath((odfRoot), xp, odf.Namespaces.lookupNamespaceURI)[0];
|
|
return parentStyleNode
|
|
}
|
|
function getTextProperties(props) {
|
|
var rule = "", fontName, fontSize, value, textDecoration = "", fontSizeRule = "", sizeMultiplier = 1, parentStyle;
|
|
rule += applySimpleMapping(props, textPropertySimpleMapping);
|
|
value = props.getAttributeNS(stylens, "text-underline-style");
|
|
if(value === "solid") {
|
|
textDecoration += " underline"
|
|
}
|
|
value = props.getAttributeNS(stylens, "text-line-through-style");
|
|
if(value === "solid") {
|
|
textDecoration += " line-through"
|
|
}
|
|
if(textDecoration.length) {
|
|
textDecoration = "text-decoration:" + textDecoration + ";";
|
|
rule += textDecoration
|
|
}
|
|
fontName = props.getAttributeNS(stylens, "font-name") || props.getAttributeNS(fons, "font-family");
|
|
if(fontName) {
|
|
value = fontFaceDeclsMap[fontName];
|
|
rule += "font-family: " + (value || fontName) + ";"
|
|
}
|
|
parentStyle = (props.parentNode);
|
|
fontSize = getFontSize(parentStyle);
|
|
if(!fontSize) {
|
|
return rule
|
|
}
|
|
while(parentStyle) {
|
|
fontSize = getFontSize(parentStyle);
|
|
if(fontSize) {
|
|
if(fontSize.unit !== "%") {
|
|
fontSizeRule = "font-size: " + fontSize.value * sizeMultiplier + fontSize.unit + ";";
|
|
break
|
|
}
|
|
sizeMultiplier *= fontSize.value / 100
|
|
}
|
|
parentStyle = getParentStyleNode(parentStyle)
|
|
}
|
|
if(!fontSizeRule) {
|
|
fontSizeRule = "font-size: " + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ";"
|
|
}
|
|
rule += fontSizeRule;
|
|
return rule
|
|
}
|
|
function getParagraphProperties(props) {
|
|
var rule = "", bgimage, url, lineHeight;
|
|
rule += applySimpleMapping(props, paragraphPropertySimpleMapping);
|
|
bgimage = getDirectChild(props, stylens, "background-image");
|
|
if(bgimage) {
|
|
url = bgimage.getAttributeNS(xlinkns, "href");
|
|
if(url) {
|
|
rule += "background-image: url('odfkit:" + url + "');";
|
|
rule += applySimpleMapping(bgimage, bgImageSimpleMapping)
|
|
}
|
|
}
|
|
lineHeight = props.getAttributeNS(fons, "line-height");
|
|
if(lineHeight && lineHeight !== "normal") {
|
|
lineHeight = utils.parseFoLineHeight(lineHeight);
|
|
if(lineHeight.unit !== "%") {
|
|
rule += "line-height: " + lineHeight.value + lineHeight.unit + ";"
|
|
}else {
|
|
rule += "line-height: " + lineHeight.value / 100 + ";"
|
|
}
|
|
}
|
|
return rule
|
|
}
|
|
function matchToRgb(m, r, g, b) {
|
|
return r + r + g + g + b + b
|
|
}
|
|
function hexToRgb(hex) {
|
|
var result, shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
hex = hex.replace(shorthandRegex, matchToRgb);
|
|
result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? {r:parseInt(result[1], 16), g:parseInt(result[2], 16), b:parseInt(result[3], 16)} : null
|
|
}
|
|
function isNumber(n) {
|
|
return!isNaN(parseFloat(n))
|
|
}
|
|
function getGraphicProperties(props) {
|
|
var rule = "", alpha, bgcolor, fill;
|
|
rule += applySimpleMapping(props, graphicPropertySimpleMapping);
|
|
alpha = props.getAttributeNS(drawns, "opacity");
|
|
fill = props.getAttributeNS(drawns, "fill");
|
|
bgcolor = props.getAttributeNS(drawns, "fill-color");
|
|
if(fill === "solid" || fill === "hatch") {
|
|
if(bgcolor && bgcolor !== "none") {
|
|
alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1;
|
|
bgcolor = hexToRgb(bgcolor);
|
|
if(bgcolor) {
|
|
rule += "background-color: rgba(" + bgcolor.r + "," + bgcolor.g + "," + bgcolor.b + "," + alpha + ");"
|
|
}
|
|
}else {
|
|
rule += "background: none;"
|
|
}
|
|
}else {
|
|
if(fill === "none") {
|
|
rule += "background: none;"
|
|
}
|
|
}
|
|
return rule
|
|
}
|
|
function getDrawingPageProperties(props) {
|
|
var rule = "";
|
|
rule += applySimpleMapping(props, graphicPropertySimpleMapping);
|
|
if(props.getAttributeNS(presentationns, "background-visible") === "true") {
|
|
rule += "background: none;"
|
|
}
|
|
return rule
|
|
}
|
|
function getTableCellProperties(props) {
|
|
var rule = "";
|
|
rule += applySimpleMapping(props, tablecellPropertySimpleMapping);
|
|
return rule
|
|
}
|
|
function getTableRowProperties(props) {
|
|
var rule = "";
|
|
rule += applySimpleMapping(props, tablerowPropertySimpleMapping);
|
|
return rule
|
|
}
|
|
function getTableColumnProperties(props) {
|
|
var rule = "";
|
|
rule += applySimpleMapping(props, tablecolumnPropertySimpleMapping);
|
|
return rule
|
|
}
|
|
function getTableProperties(props) {
|
|
var rule = "", borderModel;
|
|
rule += applySimpleMapping(props, tablePropertySimpleMapping);
|
|
borderModel = props.getAttributeNS(tablens, "border-model");
|
|
if(borderModel === "collapsing") {
|
|
rule += "border-collapse:collapse;"
|
|
}else {
|
|
if(borderModel === "separating") {
|
|
rule += "border-collapse:separate;"
|
|
}
|
|
}
|
|
return rule
|
|
}
|
|
function addStyleRule(sheet, family, name, node) {
|
|
var selectors = getSelectors(family, name, node), selector = selectors.join(","), rule = "", properties;
|
|
properties = getDirectChild(node.element, stylens, "text-properties");
|
|
if(properties) {
|
|
rule += getTextProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "paragraph-properties");
|
|
if(properties) {
|
|
rule += getParagraphProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "graphic-properties");
|
|
if(properties) {
|
|
rule += getGraphicProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "drawing-page-properties");
|
|
if(properties) {
|
|
rule += getDrawingPageProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "table-cell-properties");
|
|
if(properties) {
|
|
rule += getTableCellProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "table-row-properties");
|
|
if(properties) {
|
|
rule += getTableRowProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "table-column-properties");
|
|
if(properties) {
|
|
rule += getTableColumnProperties(properties)
|
|
}
|
|
properties = getDirectChild(node.element, stylens, "table-properties");
|
|
if(properties) {
|
|
rule += getTableProperties(properties)
|
|
}
|
|
if(rule.length === 0) {
|
|
return
|
|
}
|
|
rule = selector + "{" + rule + "}";
|
|
try {
|
|
sheet.insertRule(rule, sheet.cssRules.length)
|
|
}catch(e) {
|
|
throw e;
|
|
}
|
|
}
|
|
function getNumberRule(node) {
|
|
var style = node.getAttributeNS(stylens, "num-format"), suffix = node.getAttributeNS(stylens, "num-suffix") || "", prefix = node.getAttributeNS(stylens, "num-prefix") || "", stylemap = {1:"decimal", "a":"lower-latin", "A":"upper-latin", "i":"lower-roman", "I":"upper-roman"}, content = "";
|
|
if(prefix) {
|
|
content += ' "' + prefix + '"'
|
|
}
|
|
if(stylemap.hasOwnProperty(style)) {
|
|
content += " counter(list, " + stylemap[style] + ")"
|
|
}else {
|
|
if(style) {
|
|
content += ' "' + style + '"'
|
|
}else {
|
|
content += " ''"
|
|
}
|
|
}
|
|
return"content:" + content + ' "' + suffix + '"'
|
|
}
|
|
function getImageRule() {
|
|
return"content: none;"
|
|
}
|
|
function getBulletRule(node) {
|
|
var bulletChar = node.getAttributeNS(textns, "bullet-char");
|
|
return"content: '" + bulletChar + "';"
|
|
}
|
|
function addListStyleRule(sheet, name, node, itemrule) {
|
|
var selector = 'text|list[text|style-name="' + name + '"]', level = node.getAttributeNS(textns, "level"), itemSelector, listItemRule, listLevelProps = getDirectChild(node, stylens, "list-level-properties"), listLevelLabelAlign = getDirectChild(listLevelProps, stylens, "list-level-label-alignment"), bulletIndent, listIndent, bulletWidth, rule;
|
|
if(listLevelLabelAlign) {
|
|
bulletIndent = listLevelLabelAlign.getAttributeNS(fons, "text-indent");
|
|
listIndent = listLevelLabelAlign.getAttributeNS(fons, "margin-left")
|
|
}
|
|
if(!bulletIndent) {
|
|
bulletIndent = "-0.6cm"
|
|
}
|
|
if(bulletIndent.charAt(0) === "-") {
|
|
bulletWidth = bulletIndent.substring(1)
|
|
}else {
|
|
bulletWidth = "-" + bulletIndent
|
|
}
|
|
level = level && parseInt(level, 10);
|
|
while(level > 1) {
|
|
selector += " > text|list-item > text|list";
|
|
level -= 1
|
|
}
|
|
if(listIndent) {
|
|
itemSelector = selector;
|
|
itemSelector += " > text|list-item > *:not(text|list):first-child";
|
|
listItemRule = itemSelector + "{";
|
|
listItemRule += "margin-left:" + listIndent + ";";
|
|
listItemRule += "}";
|
|
try {
|
|
sheet.insertRule(listItemRule, sheet.cssRules.length)
|
|
}catch(e1) {
|
|
runtime.log("cannot load rule: " + listItemRule)
|
|
}
|
|
}
|
|
selector += " > text|list-item > *:not(text|list):first-child:before";
|
|
rule = selector + "{" + itemrule + ";";
|
|
rule += "counter-increment:list;";
|
|
rule += "margin-left:" + bulletIndent + ";";
|
|
rule += "width:" + bulletWidth + ";";
|
|
rule += "display:inline-block}";
|
|
try {
|
|
sheet.insertRule(rule, sheet.cssRules.length)
|
|
}catch(e2) {
|
|
runtime.log("cannot load rule: " + rule)
|
|
}
|
|
}
|
|
function addPageStyleRules(sheet, node) {
|
|
var rule = "", imageProps, url, contentLayoutRule = "", pageSizeRule = "", props = getDirectChild(node, stylens, "page-layout-properties"), stylename, masterStyles, e, masterStyleName;
|
|
if(!props) {
|
|
return
|
|
}
|
|
stylename = node.getAttributeNS(stylens, "name");
|
|
rule += applySimpleMapping(props, pageContentPropertySimpleMapping);
|
|
imageProps = getDirectChild(props, stylens, "background-image");
|
|
if(imageProps) {
|
|
url = imageProps.getAttributeNS(xlinkns, "href");
|
|
if(url) {
|
|
rule += "background-image: url('odfkit:" + url + "');";
|
|
rule += applySimpleMapping(imageProps, bgImageSimpleMapping)
|
|
}
|
|
}
|
|
if(documentType === "presentation") {
|
|
masterStyles = getDirectChild((node.parentNode.parentNode), officens, "master-styles");
|
|
e = masterStyles && masterStyles.firstElementChild;
|
|
while(e) {
|
|
if(e.namespaceURI === stylens && (e.localName === "master-page" && e.getAttributeNS(stylens, "page-layout-name") === stylename)) {
|
|
masterStyleName = e.getAttributeNS(stylens, "name");
|
|
contentLayoutRule = "draw|page[draw|master-page-name=" + masterStyleName + "] {" + rule + "}";
|
|
pageSizeRule = "office|body, draw|page[draw|master-page-name=" + masterStyleName + "] {" + applySimpleMapping(props, pageSizePropertySimpleMapping) + " }";
|
|
try {
|
|
sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
|
|
sheet.insertRule(pageSizeRule, sheet.cssRules.length)
|
|
}catch(e1) {
|
|
throw e1;
|
|
}
|
|
}
|
|
e = e.nextElementSibling
|
|
}
|
|
}else {
|
|
if(documentType === "text") {
|
|
contentLayoutRule = "office|text {" + rule + "}";
|
|
rule = "";
|
|
pageSizeRule = "office|body {" + "width: " + props.getAttributeNS(fons, "page-width") + ";" + "}";
|
|
try {
|
|
sheet.insertRule(contentLayoutRule, sheet.cssRules.length);
|
|
sheet.insertRule(pageSizeRule, sheet.cssRules.length)
|
|
}catch(e2) {
|
|
throw e2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function addListStyleRules(sheet, name, node) {
|
|
var n = node.firstChild, e, itemrule;
|
|
while(n) {
|
|
if(n.namespaceURI === textns) {
|
|
e = (n);
|
|
if(n.localName === "list-level-style-number") {
|
|
itemrule = getNumberRule(e);
|
|
addListStyleRule(sheet, name, e, itemrule)
|
|
}else {
|
|
if(n.localName === "list-level-style-image") {
|
|
itemrule = getImageRule();
|
|
addListStyleRule(sheet, name, e, itemrule)
|
|
}else {
|
|
if(n.localName === "list-level-style-bullet") {
|
|
itemrule = getBulletRule(e);
|
|
addListStyleRule(sheet, name, e, itemrule)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
n = n.nextSibling
|
|
}
|
|
}
|
|
function addRule(sheet, family, name, node) {
|
|
if(family === "list") {
|
|
addListStyleRules(sheet, name, node.element)
|
|
}else {
|
|
if(family === "page") {
|
|
addPageStyleRules(sheet, node.element)
|
|
}else {
|
|
addStyleRule(sheet, family, name, node)
|
|
}
|
|
}
|
|
}
|
|
function addRules(sheet, family, name, node) {
|
|
addRule(sheet, family, name, node);
|
|
var n;
|
|
for(n in node.derivedStyles) {
|
|
if(node.derivedStyles.hasOwnProperty(n)) {
|
|
addRules(sheet, family, n, node.derivedStyles[n])
|
|
}
|
|
}
|
|
}
|
|
this.style2css = function(doctype, stylesheet, fontFaceMap, styles, autostyles) {
|
|
var doc, styletree, tree, rule, name, family, stylenodes, styleautonodes;
|
|
while(stylesheet.cssRules.length) {
|
|
stylesheet.deleteRule(stylesheet.cssRules.length - 1)
|
|
}
|
|
doc = null;
|
|
if(styles) {
|
|
doc = styles.ownerDocument;
|
|
odfRoot = styles.parentNode
|
|
}
|
|
if(autostyles) {
|
|
doc = autostyles.ownerDocument;
|
|
odfRoot = autostyles.parentNode
|
|
}
|
|
if(!doc) {
|
|
return
|
|
}
|
|
odf.Namespaces.forEachPrefix(function(prefix, ns) {
|
|
rule = "@namespace " + prefix + " url(" + ns + ");";
|
|
try {
|
|
stylesheet.insertRule(rule, stylesheet.cssRules.length)
|
|
}catch(ignore) {
|
|
}
|
|
});
|
|
fontFaceDeclsMap = fontFaceMap;
|
|
documentType = doctype;
|
|
defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue("font-size") || "12pt";
|
|
stylenodes = getStyleMap(styles);
|
|
styleautonodes = getStyleMap(autostyles);
|
|
styletree = {};
|
|
for(family in familynamespaceprefixes) {
|
|
if(familynamespaceprefixes.hasOwnProperty(family)) {
|
|
tree = styletree[family] = {};
|
|
addStyleMapToStyleTree(stylenodes[family], tree);
|
|
addStyleMapToStyleTree(styleautonodes[family], tree);
|
|
for(name in tree) {
|
|
if(tree.hasOwnProperty(name)) {
|
|
addRules(stylesheet, family, name, tree[name])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.Canvas = function Canvas() {
|
|
};
|
|
ops.Canvas.prototype.getZoomLevel = function() {
|
|
};
|
|
ops.Canvas.prototype.getElement = function() {
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
function LoadingQueue() {
|
|
var queue = [], taskRunning = false;
|
|
function run(task) {
|
|
taskRunning = true;
|
|
runtime.setTimeout(function() {
|
|
try {
|
|
task()
|
|
}catch(e) {
|
|
runtime.log(String(e))
|
|
}
|
|
taskRunning = false;
|
|
if(queue.length > 0) {
|
|
run(queue.pop())
|
|
}
|
|
}, 10)
|
|
}
|
|
this.clearQueue = function() {
|
|
queue.length = 0
|
|
};
|
|
this.addToQueue = function(loadingTask) {
|
|
if(queue.length === 0 && !taskRunning) {
|
|
return run(loadingTask)
|
|
}
|
|
queue.push(loadingTask)
|
|
}
|
|
}
|
|
function PageSwitcher(css) {
|
|
var sheet = (css.sheet), position = 1;
|
|
function updateCSS() {
|
|
while(sheet.cssRules.length > 0) {
|
|
sheet.deleteRule(0)
|
|
}
|
|
sheet.insertRule("#shadowContent draw|page {display:none;}", 0);
|
|
sheet.insertRule("office|presentation draw|page {display:none;}", 1);
|
|
sheet.insertRule("#shadowContent draw|page:nth-of-type(" + position + ") {display:block;}", 2);
|
|
sheet.insertRule("office|presentation draw|page:nth-of-type(" + position + ") {display:block;}", 3)
|
|
}
|
|
this.showFirstPage = function() {
|
|
position = 1;
|
|
updateCSS()
|
|
};
|
|
this.showNextPage = function() {
|
|
position += 1;
|
|
updateCSS()
|
|
};
|
|
this.showPreviousPage = function() {
|
|
if(position > 1) {
|
|
position -= 1;
|
|
updateCSS()
|
|
}
|
|
};
|
|
this.showPage = function(n) {
|
|
if(n > 0) {
|
|
position = n;
|
|
updateCSS()
|
|
}
|
|
};
|
|
this.css = css;
|
|
this.destroy = function(callback) {
|
|
css.parentNode.removeChild(css);
|
|
callback()
|
|
}
|
|
}
|
|
function listenEvent(eventTarget, eventType, eventHandler) {
|
|
if(eventTarget.addEventListener) {
|
|
eventTarget.addEventListener(eventType, eventHandler, false)
|
|
}else {
|
|
if(eventTarget.attachEvent) {
|
|
eventType = "on" + eventType;
|
|
eventTarget.attachEvent(eventType, eventHandler)
|
|
}else {
|
|
eventTarget["on" + eventType] = eventHandler
|
|
}
|
|
}
|
|
}
|
|
var drawns = odf.Namespaces.drawns, fons = odf.Namespaces.fons, officens = odf.Namespaces.officens, stylens = odf.Namespaces.stylens, svgns = odf.Namespaces.svgns, tablens = odf.Namespaces.tablens, textns = odf.Namespaces.textns, xlinkns = odf.Namespaces.xlinkns, xmlns = odf.Namespaces.xmlns, presentationns = odf.Namespaces.presentationns, webodfhelperns = "urn:webodf:names:helper", window = runtime.getWindow(), xpath = xmldom.XPath, odfUtils = new odf.OdfUtils, domUtils = new core.DomUtils;
|
|
function clear(element) {
|
|
while(element.firstChild) {
|
|
element.removeChild(element.firstChild)
|
|
}
|
|
}
|
|
function clearCSSStyleSheet(style) {
|
|
var stylesheet = (style.sheet), cssRules = stylesheet.cssRules;
|
|
while(cssRules.length) {
|
|
stylesheet.deleteRule(cssRules.length - 1)
|
|
}
|
|
}
|
|
function handleStyles(odfcontainer, formatting, stylesxmlcss) {
|
|
var style2css = new odf.Style2CSS;
|
|
style2css.style2css(odfcontainer.getDocumentType(), (stylesxmlcss.sheet), formatting.getFontMap(), odfcontainer.rootElement.styles, odfcontainer.rootElement.automaticStyles)
|
|
}
|
|
function handleFonts(odfContainer, fontcss) {
|
|
var fontLoader = new odf.FontLoader;
|
|
fontLoader.loadFonts(odfContainer, (fontcss.sheet))
|
|
}
|
|
function getMasterPageElement(odfContainer, masterPageName) {
|
|
if(!masterPageName) {
|
|
return null
|
|
}
|
|
var masterStyles = odfContainer.rootElement.masterStyles, masterStylesChild = masterStyles.firstElementChild;
|
|
while(masterStylesChild) {
|
|
if(masterStylesChild.getAttributeNS(stylens, "name") === masterPageName && (masterStylesChild.localName === "master-page" && masterStylesChild.namespaceURI === stylens)) {
|
|
break
|
|
}
|
|
masterStylesChild = masterStylesChild.nextElementSibling
|
|
}
|
|
return masterStylesChild
|
|
}
|
|
function dropTemplateDrawFrames(clonedNode) {
|
|
var i, element, presentationClass, clonedDrawFrameElements = clonedNode.getElementsByTagNameNS(drawns, "frame");
|
|
for(i = 0;i < clonedDrawFrameElements.length;i += 1) {
|
|
element = (clonedDrawFrameElements[i]);
|
|
presentationClass = element.getAttributeNS(presentationns, "class");
|
|
if(presentationClass && !/^(date-time|footer|header|page-number)$/.test(presentationClass)) {
|
|
element.parentNode.removeChild(element)
|
|
}
|
|
}
|
|
}
|
|
function getHeaderFooter(odfContainer, frame, headerFooterId) {
|
|
var headerFooter = null, i, declElements = odfContainer.rootElement.body.getElementsByTagNameNS(presentationns, headerFooterId + "-decl"), headerFooterName = frame.getAttributeNS(presentationns, "use-" + headerFooterId + "-name"), element;
|
|
if(headerFooterName && declElements.length > 0) {
|
|
for(i = 0;i < declElements.length;i += 1) {
|
|
element = (declElements[i]);
|
|
if(element.getAttributeNS(presentationns, "name") === headerFooterName) {
|
|
headerFooter = element.textContent;
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return headerFooter
|
|
}
|
|
function setContainerValue(rootElement, ns, localName, value) {
|
|
var i, containerList, document = rootElement.ownerDocument, e;
|
|
containerList = rootElement.getElementsByTagNameNS(ns, localName);
|
|
for(i = 0;i < containerList.length;i += 1) {
|
|
clear(containerList[i]);
|
|
if(value) {
|
|
e = (containerList[i]);
|
|
e.appendChild(document.createTextNode(value))
|
|
}
|
|
}
|
|
}
|
|
function setDrawElementPosition(styleid, frame, stylesheet) {
|
|
frame.setAttributeNS(webodfhelperns, "styleid", styleid);
|
|
var rule, anchor = frame.getAttributeNS(textns, "anchor-type"), x = frame.getAttributeNS(svgns, "x"), y = frame.getAttributeNS(svgns, "y"), width = frame.getAttributeNS(svgns, "width"), height = frame.getAttributeNS(svgns, "height"), minheight = frame.getAttributeNS(fons, "min-height"), minwidth = frame.getAttributeNS(fons, "min-width");
|
|
if(anchor === "as-char") {
|
|
rule = "display: inline-block;"
|
|
}else {
|
|
if(anchor || (x || y)) {
|
|
rule = "position: absolute;"
|
|
}else {
|
|
if(width || (height || (minheight || minwidth))) {
|
|
rule = "display: block;"
|
|
}
|
|
}
|
|
}
|
|
if(x) {
|
|
rule += "left: " + x + ";"
|
|
}
|
|
if(y) {
|
|
rule += "top: " + y + ";"
|
|
}
|
|
if(width) {
|
|
rule += "width: " + width + ";"
|
|
}
|
|
if(height) {
|
|
rule += "height: " + height + ";"
|
|
}
|
|
if(minheight) {
|
|
rule += "min-height: " + minheight + ";"
|
|
}
|
|
if(minwidth) {
|
|
rule += "min-width: " + minwidth + ";"
|
|
}
|
|
if(rule) {
|
|
rule = "draw|" + frame.localName + '[webodfhelper|styleid="' + styleid + '"] {' + rule + "}";
|
|
stylesheet.insertRule(rule, stylesheet.cssRules.length)
|
|
}
|
|
}
|
|
function getUrlFromBinaryDataElement(image) {
|
|
var node = image.firstChild;
|
|
while(node) {
|
|
if(node.namespaceURI === officens && node.localName === "binary-data") {
|
|
return"data:image/png;base64," + node.textContent.replace(/[\r\n\s]/g, "")
|
|
}
|
|
node = node.nextSibling
|
|
}
|
|
return""
|
|
}
|
|
function setImage(id, container, image, stylesheet) {
|
|
image.setAttributeNS(webodfhelperns, "styleid", id);
|
|
var url = image.getAttributeNS(xlinkns, "href"), part;
|
|
function callback(url) {
|
|
var rule;
|
|
if(url) {
|
|
rule = "background-image: url(" + url + ");";
|
|
rule = 'draw|image[webodfhelper|styleid="' + id + '"] {' + rule + "}";
|
|
stylesheet.insertRule(rule, stylesheet.cssRules.length)
|
|
}
|
|
}
|
|
function onchange(p) {
|
|
callback(p.url)
|
|
}
|
|
if(url) {
|
|
try {
|
|
part = container.getPart(url);
|
|
part.onchange = onchange;
|
|
part.load()
|
|
}catch(e) {
|
|
runtime.log("slight problem: " + String(e))
|
|
}
|
|
}else {
|
|
url = getUrlFromBinaryDataElement(image);
|
|
callback(url)
|
|
}
|
|
}
|
|
function formatParagraphAnchors(odfbody) {
|
|
var n, i, nodes = xpath.getODFElementsWithXPath(odfbody, ".//*[*[@text:anchor-type='paragraph']]", odf.Namespaces.lookupNamespaceURI);
|
|
for(i = 0;i < nodes.length;i += 1) {
|
|
n = nodes[i];
|
|
if(n.setAttributeNS) {
|
|
n.setAttributeNS(webodfhelperns, "containsparagraphanchor", true)
|
|
}
|
|
}
|
|
}
|
|
function modifyTables(odffragment, documentns) {
|
|
var i, tableCells, node;
|
|
function modifyTableCell(node) {
|
|
if(node.hasAttributeNS(tablens, "number-columns-spanned")) {
|
|
node.setAttributeNS(documentns, "colspan", node.getAttributeNS(tablens, "number-columns-spanned"))
|
|
}
|
|
if(node.hasAttributeNS(tablens, "number-rows-spanned")) {
|
|
node.setAttributeNS(documentns, "rowspan", node.getAttributeNS(tablens, "number-rows-spanned"))
|
|
}
|
|
}
|
|
tableCells = odffragment.getElementsByTagNameNS(tablens, "table-cell");
|
|
for(i = 0;i < tableCells.length;i += 1) {
|
|
node = (tableCells.item(i));
|
|
modifyTableCell(node)
|
|
}
|
|
}
|
|
function modifyLineBreakElements(odffragment) {
|
|
var document = odffragment.ownerDocument, lineBreakElements = domUtils.getElementsByTagNameNS(odffragment, textns, "line-break");
|
|
lineBreakElements.forEach(function(lineBreak) {
|
|
if(!lineBreak.hasChildNodes()) {
|
|
lineBreak.appendChild(document.createElement("br"))
|
|
}
|
|
})
|
|
}
|
|
function expandSpaceElements(odffragment) {
|
|
var spaces, doc = odffragment.ownerDocument;
|
|
function expandSpaceElement(space) {
|
|
var j, count;
|
|
while(space.firstChild) {
|
|
space.removeChild(space.firstChild)
|
|
}
|
|
space.appendChild(doc.createTextNode(" "));
|
|
count = parseInt(space.getAttributeNS(textns, "c"), 10);
|
|
if(count > 1) {
|
|
space.removeAttributeNS(textns, "c");
|
|
for(j = 1;j < count;j += 1) {
|
|
space.parentNode.insertBefore(space.cloneNode(true), space)
|
|
}
|
|
}
|
|
}
|
|
spaces = domUtils.getElementsByTagNameNS(odffragment, textns, "s");
|
|
spaces.forEach(expandSpaceElement)
|
|
}
|
|
function expandTabElements(odffragment) {
|
|
var tabs;
|
|
tabs = domUtils.getElementsByTagNameNS(odffragment, textns, "tab");
|
|
tabs.forEach(function(tab) {
|
|
tab.textContent = "\t"
|
|
})
|
|
}
|
|
function modifyDrawElements(odfbody, stylesheet) {
|
|
var node, drawElements = [], i;
|
|
node = odfbody.firstElementChild;
|
|
while(node && node !== odfbody) {
|
|
if(node.namespaceURI === drawns) {
|
|
drawElements[drawElements.length] = node
|
|
}
|
|
if(node.firstElementChild) {
|
|
node = node.firstElementChild
|
|
}else {
|
|
while(node && (node !== odfbody && !node.nextElementSibling)) {
|
|
node = (node.parentNode)
|
|
}
|
|
if(node && node.nextElementSibling) {
|
|
node = node.nextElementSibling
|
|
}
|
|
}
|
|
}
|
|
for(i = 0;i < drawElements.length;i += 1) {
|
|
node = drawElements[i];
|
|
setDrawElementPosition("frame" + String(i), node, stylesheet)
|
|
}
|
|
formatParagraphAnchors(odfbody)
|
|
}
|
|
function cloneMasterPages(odfContainer, shadowContent, odfbody, stylesheet) {
|
|
var masterPageName, masterPageElement, styleId, clonedPageElement, clonedElement, pageNumber = 0, i, element, elementToClone, document = odfContainer.rootElement.ownerDocument;
|
|
element = odfbody.firstElementChild;
|
|
if(!(element && (element.namespaceURI === officens && (element.localName === "presentation" || element.localName === "drawing")))) {
|
|
return
|
|
}
|
|
element = element.firstElementChild;
|
|
while(element) {
|
|
masterPageName = element.getAttributeNS(drawns, "master-page-name");
|
|
masterPageElement = getMasterPageElement(odfContainer, masterPageName);
|
|
if(masterPageElement) {
|
|
styleId = element.getAttributeNS(webodfhelperns, "styleid");
|
|
clonedPageElement = document.createElementNS(drawns, "draw:page");
|
|
elementToClone = masterPageElement.firstElementChild;
|
|
i = 0;
|
|
while(elementToClone) {
|
|
if(elementToClone.getAttributeNS(presentationns, "placeholder") !== "true") {
|
|
clonedElement = (elementToClone.cloneNode(true));
|
|
clonedPageElement.appendChild(clonedElement);
|
|
setDrawElementPosition(styleId + "_" + i, clonedElement, stylesheet)
|
|
}
|
|
elementToClone = elementToClone.nextElementSibling;
|
|
i += 1
|
|
}
|
|
dropTemplateDrawFrames(clonedPageElement);
|
|
shadowContent.appendChild(clonedPageElement);
|
|
pageNumber = String(shadowContent.getElementsByTagNameNS(drawns, "page").length);
|
|
setContainerValue(clonedPageElement, textns, "page-number", pageNumber);
|
|
setContainerValue(clonedPageElement, presentationns, "header", getHeaderFooter(odfContainer, (element), "header"));
|
|
setContainerValue(clonedPageElement, presentationns, "footer", getHeaderFooter(odfContainer, (element), "footer"));
|
|
setDrawElementPosition(styleId, clonedPageElement, stylesheet);
|
|
clonedPageElement.setAttributeNS(drawns, "draw:master-page-name", masterPageElement.getAttributeNS(stylens, "name"))
|
|
}
|
|
element = element.nextElementSibling
|
|
}
|
|
}
|
|
function setVideo(container, plugin) {
|
|
var video, source, url, doc = plugin.ownerDocument, part;
|
|
url = plugin.getAttributeNS(xlinkns, "href");
|
|
function callback(url, mimetype) {
|
|
var ns = doc.documentElement.namespaceURI;
|
|
if(mimetype.substr(0, 6) === "video/") {
|
|
video = doc.createElementNS(ns, "video");
|
|
video.setAttribute("controls", "controls");
|
|
source = doc.createElementNS(ns, "source");
|
|
if(url) {
|
|
source.setAttribute("src", url)
|
|
}
|
|
source.setAttribute("type", mimetype);
|
|
video.appendChild(source);
|
|
plugin.parentNode.appendChild(video)
|
|
}else {
|
|
plugin.innerHtml = "Unrecognised Plugin"
|
|
}
|
|
}
|
|
function onchange(p) {
|
|
callback(p.url, p.mimetype)
|
|
}
|
|
if(url) {
|
|
try {
|
|
part = container.getPart(url);
|
|
part.onchange = onchange;
|
|
part.load()
|
|
}catch(e) {
|
|
runtime.log("slight problem: " + String(e))
|
|
}
|
|
}else {
|
|
runtime.log("using MP4 data fallback");
|
|
url = getUrlFromBinaryDataElement(plugin);
|
|
callback(url, "video/mp4")
|
|
}
|
|
}
|
|
function getNumberRule(node) {
|
|
var style = node.getAttributeNS(stylens, "num-format"), suffix = node.getAttributeNS(stylens, "num-suffix") || "", prefix = node.getAttributeNS(stylens, "num-prefix") || "", rule = "", stylemap = {1:"decimal", "a":"lower-latin", "A":"upper-latin", "i":"lower-roman", "I":"upper-roman"}, content;
|
|
content = prefix;
|
|
if(stylemap.hasOwnProperty(style)) {
|
|
content += " counter(list, " + stylemap[style] + ")"
|
|
}else {
|
|
if(style) {
|
|
content += "'" + style + "';"
|
|
}else {
|
|
content += " ''"
|
|
}
|
|
}
|
|
if(suffix) {
|
|
content += " '" + suffix + "'"
|
|
}
|
|
rule = "content: " + content + ";";
|
|
return rule
|
|
}
|
|
function getImageRule() {
|
|
var rule = "content: none;";
|
|
return rule
|
|
}
|
|
function getBulletRule(node) {
|
|
var bulletChar = node.getAttributeNS(textns, "bullet-char");
|
|
return"content: '" + bulletChar + "';"
|
|
}
|
|
function getBulletsRule(node) {
|
|
var itemrule;
|
|
if(node) {
|
|
if(node.localName === "list-level-style-number") {
|
|
itemrule = getNumberRule(node)
|
|
}else {
|
|
if(node.localName === "list-level-style-image") {
|
|
itemrule = getImageRule()
|
|
}else {
|
|
if(node.localName === "list-level-style-bullet") {
|
|
itemrule = getBulletRule(node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return itemrule
|
|
}
|
|
function loadLists(odffragment, stylesheet, documentns) {
|
|
var i, lists, node, id, continueList, styleName, rule, listMap = {}, parentList, listStyles, listStyleMap = {}, bulletRule;
|
|
listStyles = window.document.getElementsByTagNameNS(textns, "list-style");
|
|
for(i = 0;i < listStyles.length;i += 1) {
|
|
node = (listStyles.item(i));
|
|
styleName = node.getAttributeNS(stylens, "name");
|
|
if(styleName) {
|
|
listStyleMap[styleName] = node
|
|
}
|
|
}
|
|
lists = odffragment.getElementsByTagNameNS(textns, "list");
|
|
for(i = 0;i < lists.length;i += 1) {
|
|
node = (lists.item(i));
|
|
id = node.getAttributeNS(xmlns, "id");
|
|
if(id) {
|
|
continueList = node.getAttributeNS(textns, "continue-list");
|
|
node.setAttributeNS(documentns, "id", id);
|
|
rule = "text|list#" + id + " > text|list-item > *:first-child:before {";
|
|
styleName = node.getAttributeNS(textns, "style-name");
|
|
if(styleName) {
|
|
node = listStyleMap[styleName];
|
|
bulletRule = getBulletsRule((odfUtils.getFirstNonWhitespaceChild(node)))
|
|
}
|
|
if(continueList) {
|
|
parentList = listMap[continueList];
|
|
while(parentList) {
|
|
parentList = listMap[parentList]
|
|
}
|
|
rule += "counter-increment:" + continueList + ";";
|
|
if(bulletRule) {
|
|
bulletRule = bulletRule.replace("list", continueList);
|
|
rule += bulletRule
|
|
}else {
|
|
rule += "content:counter(" + continueList + ");"
|
|
}
|
|
}else {
|
|
continueList = "";
|
|
if(bulletRule) {
|
|
bulletRule = bulletRule.replace("list", id);
|
|
rule += bulletRule
|
|
}else {
|
|
rule += "content: counter(" + id + ");"
|
|
}
|
|
rule += "counter-increment:" + id + ";";
|
|
stylesheet.insertRule("text|list#" + id + " {counter-reset:" + id + "}", stylesheet.cssRules.length)
|
|
}
|
|
rule += "}";
|
|
listMap[id] = continueList;
|
|
if(rule) {
|
|
stylesheet.insertRule(rule, stylesheet.cssRules.length)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function findWebODFStyleSheet(head) {
|
|
var style = head.firstElementChild;
|
|
while(style && !(style.localName === "style" && style.hasAttribute("webodfcss"))) {
|
|
style = style.nextElementSibling
|
|
}
|
|
return(style)
|
|
}
|
|
function addWebODFStyleSheet(document) {
|
|
var head = (document.getElementsByTagName("head")[0]), css, style, href, count = document.styleSheets.length;
|
|
style = findWebODFStyleSheet(head);
|
|
if(style) {
|
|
count = parseInt(style.getAttribute("webodfcss"), 10);
|
|
style.setAttribute("webodfcss", count + 1);
|
|
return style
|
|
}
|
|
if(String(typeof webodf_css) === "string") {
|
|
css = (webodf_css)
|
|
}else {
|
|
href = "webodf.css";
|
|
if(runtime.currentDirectory) {
|
|
href = runtime.currentDirectory();
|
|
if(href.length > 0 && href.substr(-1) !== "/") {
|
|
href += "/"
|
|
}
|
|
href += "../webodf.css"
|
|
}
|
|
css = (runtime.readFileSync(href, "utf-8"))
|
|
}
|
|
style = (document.createElementNS(head.namespaceURI, "style"));
|
|
style.setAttribute("media", "screen, print, handheld, projection");
|
|
style.setAttribute("type", "text/css");
|
|
style.setAttribute("webodfcss", "1");
|
|
style.appendChild(document.createTextNode(css));
|
|
head.appendChild(style);
|
|
return style
|
|
}
|
|
function removeWebODFStyleSheet(webodfcss) {
|
|
var count = parseInt(webodfcss.getAttribute("webodfcss"), 10);
|
|
if(count === 1) {
|
|
webodfcss.parentNode.removeChild(webodfcss)
|
|
}else {
|
|
webodfcss.setAttribute("count", count - 1)
|
|
}
|
|
}
|
|
function addStyleSheet(document) {
|
|
var head = (document.getElementsByTagName("head")[0]), style = document.createElementNS(head.namespaceURI, "style"), text = "";
|
|
style.setAttribute("type", "text/css");
|
|
style.setAttribute("media", "screen, print, handheld, projection");
|
|
odf.Namespaces.forEachPrefix(function(prefix, ns) {
|
|
text += "@namespace " + prefix + " url(" + ns + ");\n"
|
|
});
|
|
text += "@namespace webodfhelper url(" + webodfhelperns + ");\n";
|
|
style.appendChild(document.createTextNode(text));
|
|
head.appendChild(style);
|
|
return(style)
|
|
}
|
|
odf.OdfCanvas = function OdfCanvas(element) {
|
|
runtime.assert(element !== null && element !== undefined, "odf.OdfCanvas constructor needs DOM element");
|
|
runtime.assert(element.ownerDocument !== null && element.ownerDocument !== undefined, "odf.OdfCanvas constructor needs DOM");
|
|
var self = this, doc = (element.ownerDocument), async = new core.Async, odfcontainer, formatting = new odf.Formatting, pageSwitcher, sizer = null, annotationsPane = null, allowAnnotations = false, showAnnotationRemoveButton = false, annotationViewManager = null, webodfcss, fontcss, stylesxmlcss, positioncss, shadowContent, eventHandlers = {}, waitingForDoneTimeoutId, redrawContainerTask, shouldRefreshCss = false, shouldRerenderAnnotations = false, loadingQueue = new LoadingQueue, zoomHelper =
|
|
new gui.ZoomHelper;
|
|
function loadImages(container, odffragment, stylesheet) {
|
|
var i, images, node;
|
|
function loadImage(name, container, node, stylesheet) {
|
|
loadingQueue.addToQueue(function() {
|
|
setImage(name, container, node, stylesheet)
|
|
})
|
|
}
|
|
images = odffragment.getElementsByTagNameNS(drawns, "image");
|
|
for(i = 0;i < images.length;i += 1) {
|
|
node = (images.item(i));
|
|
loadImage("image" + String(i), container, node, stylesheet)
|
|
}
|
|
}
|
|
function loadVideos(container, odffragment) {
|
|
var i, plugins, node;
|
|
function loadVideo(container, node) {
|
|
loadingQueue.addToQueue(function() {
|
|
setVideo(container, node)
|
|
})
|
|
}
|
|
plugins = odffragment.getElementsByTagNameNS(drawns, "plugin");
|
|
for(i = 0;i < plugins.length;i += 1) {
|
|
node = (plugins.item(i));
|
|
loadVideo(container, node)
|
|
}
|
|
}
|
|
function addEventListener(eventType, eventHandler) {
|
|
var handlers;
|
|
if(eventHandlers.hasOwnProperty(eventType)) {
|
|
handlers = eventHandlers[eventType]
|
|
}else {
|
|
handlers = eventHandlers[eventType] = []
|
|
}
|
|
if(eventHandler && handlers.indexOf(eventHandler) === -1) {
|
|
handlers.push(eventHandler)
|
|
}
|
|
}
|
|
function fireEvent(eventType, args) {
|
|
if(!eventHandlers.hasOwnProperty(eventType)) {
|
|
return
|
|
}
|
|
var handlers = eventHandlers[eventType], i;
|
|
for(i = 0;i < handlers.length;i += 1) {
|
|
handlers[i].apply(null, args)
|
|
}
|
|
}
|
|
function fixContainerSize() {
|
|
var minHeight, odfdoc = sizer.firstChild, zoomLevel = zoomHelper.getZoomLevel();
|
|
if(!odfdoc) {
|
|
return
|
|
}
|
|
sizer.style.WebkitTransformOrigin = "0% 0%";
|
|
sizer.style.MozTransformOrigin = "0% 0%";
|
|
sizer.style.msTransformOrigin = "0% 0%";
|
|
sizer.style.OTransformOrigin = "0% 0%";
|
|
sizer.style.transformOrigin = "0% 0%";
|
|
if(annotationViewManager) {
|
|
minHeight = annotationViewManager.getMinimumHeightForAnnotationPane();
|
|
if(minHeight) {
|
|
sizer.style.minHeight = minHeight
|
|
}else {
|
|
sizer.style.removeProperty("min-height")
|
|
}
|
|
}
|
|
element.style.width = Math.round(zoomLevel * sizer.offsetWidth) + "px";
|
|
element.style.height = Math.round(zoomLevel * sizer.offsetHeight) + "px"
|
|
}
|
|
function redrawContainer() {
|
|
if(shouldRefreshCss) {
|
|
handleStyles(odfcontainer, formatting, stylesxmlcss);
|
|
shouldRefreshCss = false
|
|
}
|
|
if(shouldRerenderAnnotations) {
|
|
if(annotationViewManager) {
|
|
annotationViewManager.rerenderAnnotations()
|
|
}
|
|
shouldRerenderAnnotations = false
|
|
}
|
|
fixContainerSize()
|
|
}
|
|
function handleContent(container, odfnode) {
|
|
var css = (positioncss.sheet);
|
|
clear(element);
|
|
sizer = (doc.createElementNS(element.namespaceURI, "div"));
|
|
sizer.style.display = "inline-block";
|
|
sizer.style.background = "white";
|
|
sizer.style.setProperty("float", "left", "important");
|
|
sizer.appendChild(odfnode);
|
|
element.appendChild(sizer);
|
|
annotationsPane = (doc.createElementNS(element.namespaceURI, "div"));
|
|
annotationsPane.id = "annotationsPane";
|
|
shadowContent = doc.createElementNS(element.namespaceURI, "div");
|
|
shadowContent.id = "shadowContent";
|
|
shadowContent.style.position = "absolute";
|
|
shadowContent.style.top = 0;
|
|
shadowContent.style.left = 0;
|
|
container.getContentElement().appendChild(shadowContent);
|
|
modifyDrawElements(odfnode.body, css);
|
|
cloneMasterPages(container, shadowContent, odfnode.body, css);
|
|
modifyTables(odfnode.body, element.namespaceURI);
|
|
modifyLineBreakElements(odfnode.body);
|
|
expandSpaceElements(odfnode.body);
|
|
expandTabElements(odfnode.body);
|
|
loadImages(container, odfnode.body, css);
|
|
loadVideos(container, odfnode.body);
|
|
loadLists(odfnode.body, css, element.namespaceURI);
|
|
sizer.insertBefore(shadowContent, sizer.firstChild);
|
|
zoomHelper.setZoomableElement(sizer)
|
|
}
|
|
function modifyAnnotations(odffragment) {
|
|
var annotationNodes = (domUtils.getElementsByTagNameNS(odffragment, officens, "annotation"));
|
|
annotationNodes.forEach(annotationViewManager.addAnnotation);
|
|
annotationViewManager.rerenderAnnotations()
|
|
}
|
|
function handleAnnotations(odfnode) {
|
|
if(allowAnnotations) {
|
|
if(!annotationsPane.parentNode) {
|
|
sizer.appendChild(annotationsPane)
|
|
}
|
|
if(annotationViewManager) {
|
|
annotationViewManager.forgetAnnotations()
|
|
}
|
|
annotationViewManager = new gui.AnnotationViewManager(self, odfnode.body, annotationsPane, showAnnotationRemoveButton);
|
|
modifyAnnotations(odfnode.body);
|
|
fixContainerSize()
|
|
}else {
|
|
if(annotationsPane.parentNode) {
|
|
sizer.removeChild(annotationsPane);
|
|
annotationViewManager.forgetAnnotations();
|
|
fixContainerSize()
|
|
}
|
|
}
|
|
}
|
|
function refreshOdf(suppressEvent) {
|
|
function callback() {
|
|
clearCSSStyleSheet(fontcss);
|
|
clearCSSStyleSheet(stylesxmlcss);
|
|
clearCSSStyleSheet(positioncss);
|
|
clear(element);
|
|
element.style.display = "inline-block";
|
|
var odfnode = odfcontainer.rootElement;
|
|
element.ownerDocument.importNode(odfnode, true);
|
|
formatting.setOdfContainer(odfcontainer);
|
|
handleFonts(odfcontainer, fontcss);
|
|
handleStyles(odfcontainer, formatting, stylesxmlcss);
|
|
handleContent(odfcontainer, odfnode);
|
|
handleAnnotations(odfnode);
|
|
if(!suppressEvent) {
|
|
fireEvent("statereadychange", [odfcontainer])
|
|
}
|
|
}
|
|
if(odfcontainer.state === odf.OdfContainer.DONE) {
|
|
callback()
|
|
}else {
|
|
runtime.log("WARNING: refreshOdf called but ODF was not DONE.");
|
|
waitingForDoneTimeoutId = runtime.setTimeout(function later_cb() {
|
|
if(odfcontainer.state === odf.OdfContainer.DONE) {
|
|
callback()
|
|
}else {
|
|
runtime.log("will be back later...");
|
|
waitingForDoneTimeoutId = runtime.setTimeout(later_cb, 500)
|
|
}
|
|
}, 100)
|
|
}
|
|
}
|
|
this.refreshCSS = function() {
|
|
shouldRefreshCss = true;
|
|
redrawContainerTask.trigger()
|
|
};
|
|
this.refreshSize = function() {
|
|
redrawContainerTask.trigger()
|
|
};
|
|
this.odfContainer = function() {
|
|
return odfcontainer
|
|
};
|
|
this.setOdfContainer = function(container, suppressEvent) {
|
|
odfcontainer = container;
|
|
refreshOdf(suppressEvent === true)
|
|
};
|
|
function load(url) {
|
|
loadingQueue.clearQueue();
|
|
element.innerHTML = runtime.tr("Loading") + " " + url + "...";
|
|
element.removeAttribute("style");
|
|
odfcontainer = new odf.OdfContainer(url, function(container) {
|
|
odfcontainer = container;
|
|
refreshOdf(false)
|
|
})
|
|
}
|
|
this["load"] = load;
|
|
this.load = load;
|
|
this.save = function(callback) {
|
|
odfcontainer.save(callback)
|
|
};
|
|
this.addListener = function(eventName, handler) {
|
|
switch(eventName) {
|
|
case "click":
|
|
listenEvent(element, eventName, handler);
|
|
break;
|
|
default:
|
|
addEventListener(eventName, handler);
|
|
break
|
|
}
|
|
};
|
|
this.getFormatting = function() {
|
|
return formatting
|
|
};
|
|
this.getAnnotationViewManager = function() {
|
|
return annotationViewManager
|
|
};
|
|
this.refreshAnnotations = function() {
|
|
handleAnnotations(odfcontainer.rootElement)
|
|
};
|
|
this.rerenderAnnotations = function() {
|
|
if(annotationViewManager) {
|
|
shouldRerenderAnnotations = true;
|
|
redrawContainerTask.trigger()
|
|
}
|
|
};
|
|
this.getSizer = function() {
|
|
return(sizer)
|
|
};
|
|
this.enableAnnotations = function(allow, showRemoveButton) {
|
|
if(allow !== allowAnnotations) {
|
|
allowAnnotations = allow;
|
|
showAnnotationRemoveButton = showRemoveButton;
|
|
if(odfcontainer) {
|
|
handleAnnotations(odfcontainer.rootElement)
|
|
}
|
|
}
|
|
};
|
|
this.addAnnotation = function(annotation) {
|
|
if(annotationViewManager) {
|
|
annotationViewManager.addAnnotation(annotation);
|
|
fixContainerSize()
|
|
}
|
|
};
|
|
this.forgetAnnotations = function() {
|
|
if(annotationViewManager) {
|
|
annotationViewManager.forgetAnnotations();
|
|
fixContainerSize()
|
|
}
|
|
};
|
|
this.getZoomHelper = function() {
|
|
return zoomHelper
|
|
};
|
|
this.setZoomLevel = function(zoom) {
|
|
zoomHelper.setZoomLevel(zoom)
|
|
};
|
|
this.getZoomLevel = function() {
|
|
return zoomHelper.getZoomLevel()
|
|
};
|
|
this.fitToContainingElement = function(width, height) {
|
|
var zoomLevel = zoomHelper.getZoomLevel(), realWidth = element.offsetWidth / zoomLevel, realHeight = element.offsetHeight / zoomLevel, zoom;
|
|
zoom = width / realWidth;
|
|
if(height / realHeight < zoom) {
|
|
zoom = height / realHeight
|
|
}
|
|
zoomHelper.setZoomLevel(zoom)
|
|
};
|
|
this.fitToWidth = function(width) {
|
|
var realWidth = element.offsetWidth / zoomHelper.getZoomLevel();
|
|
zoomHelper.setZoomLevel(width / realWidth)
|
|
};
|
|
this.fitSmart = function(width, height) {
|
|
var realWidth, realHeight, newScale, zoomLevel = zoomHelper.getZoomLevel();
|
|
realWidth = element.offsetWidth / zoomLevel;
|
|
realHeight = element.offsetHeight / zoomLevel;
|
|
newScale = width / realWidth;
|
|
if(height !== undefined) {
|
|
if(height / realHeight < newScale) {
|
|
newScale = height / realHeight
|
|
}
|
|
}
|
|
zoomHelper.setZoomLevel(Math.min(1, newScale))
|
|
};
|
|
this.fitToHeight = function(height) {
|
|
var realHeight = element.offsetHeight / zoomHelper.getZoomLevel();
|
|
zoomHelper.setZoomLevel(height / realHeight)
|
|
};
|
|
this.showFirstPage = function() {
|
|
pageSwitcher.showFirstPage()
|
|
};
|
|
this.showNextPage = function() {
|
|
pageSwitcher.showNextPage()
|
|
};
|
|
this.showPreviousPage = function() {
|
|
pageSwitcher.showPreviousPage()
|
|
};
|
|
this.showPage = function(n) {
|
|
pageSwitcher.showPage(n);
|
|
fixContainerSize()
|
|
};
|
|
this.getElement = function() {
|
|
return element
|
|
};
|
|
this.addCssForFrameWithImage = function(frame) {
|
|
var frameName = frame.getAttributeNS(drawns, "name"), fc = frame.firstElementChild;
|
|
setDrawElementPosition(frameName, frame, (positioncss.sheet));
|
|
if(fc) {
|
|
setImage(frameName + "img", odfcontainer, fc, (positioncss.sheet))
|
|
}
|
|
};
|
|
this.destroy = function(callback) {
|
|
var head = (doc.getElementsByTagName("head")[0]), cleanup = [pageSwitcher.destroy, redrawContainerTask.destroy];
|
|
runtime.clearTimeout(waitingForDoneTimeoutId);
|
|
if(annotationsPane && annotationsPane.parentNode) {
|
|
annotationsPane.parentNode.removeChild(annotationsPane)
|
|
}
|
|
zoomHelper.destroy(function() {
|
|
if(sizer) {
|
|
element.removeChild(sizer);
|
|
sizer = null
|
|
}
|
|
});
|
|
removeWebODFStyleSheet(webodfcss);
|
|
head.removeChild(fontcss);
|
|
head.removeChild(stylesxmlcss);
|
|
head.removeChild(positioncss);
|
|
async.destroyAll(cleanup, callback)
|
|
};
|
|
function init() {
|
|
webodfcss = addWebODFStyleSheet(doc);
|
|
pageSwitcher = new PageSwitcher(addStyleSheet(doc));
|
|
fontcss = addStyleSheet(doc);
|
|
stylesxmlcss = addStyleSheet(doc);
|
|
positioncss = addStyleSheet(doc);
|
|
redrawContainerTask = new core.ScheduledTask(redrawContainer, 0);
|
|
zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, fixContainerSize)
|
|
}
|
|
init()
|
|
}
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.MemberProperties = function() {
|
|
this.fullName;
|
|
this.color;
|
|
this.imageUrl
|
|
};
|
|
ops.Member = function Member(memberId, properties) {
|
|
var props = new ops.MemberProperties;
|
|
function getMemberId() {
|
|
return memberId
|
|
}
|
|
function getProperties() {
|
|
return props
|
|
}
|
|
function setProperties(newProperties) {
|
|
Object.keys(newProperties).forEach(function(key) {
|
|
props[key] = newProperties[key]
|
|
})
|
|
}
|
|
function removeProperties(removedProperties) {
|
|
Object.keys(removedProperties).forEach(function(key) {
|
|
if(key !== "fullName" && (key !== "color" && (key !== "imageUrl" && props.hasOwnProperty(key)))) {
|
|
delete props[key]
|
|
}
|
|
})
|
|
}
|
|
this.getMemberId = getMemberId;
|
|
this.getProperties = getProperties;
|
|
this.setProperties = setProperties;
|
|
this.removeProperties = removeProperties;
|
|
function init() {
|
|
runtime.assert(Boolean(memberId), "No memberId was supplied!");
|
|
if(!properties.fullName) {
|
|
properties.fullName = runtime.tr("Unknown Author")
|
|
}
|
|
if(!properties.color) {
|
|
properties.color = "black"
|
|
}
|
|
if(!properties.imageUrl) {
|
|
properties.imageUrl = "avatar-joe.png"
|
|
}
|
|
props = properties
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.StepCounter;
|
|
gui.SelectionMover = function SelectionMover(cursor, rootNode) {
|
|
var odfUtils = new odf.OdfUtils, positionIterator, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT;
|
|
function getIteratorAtCursor() {
|
|
positionIterator.setUnfilteredPosition(cursor.getNode(), 0);
|
|
return positionIterator
|
|
}
|
|
function getMaximumNodePosition(node) {
|
|
return node.nodeType === Node.TEXT_NODE ? node.textContent.length : node.childNodes.length
|
|
}
|
|
function getClientRect(clientRectangles, useRightEdge) {
|
|
var rectangle, simplifiedRectangle = null;
|
|
if(clientRectangles && clientRectangles.length > 0) {
|
|
rectangle = useRightEdge ? clientRectangles.item(clientRectangles.length - 1) : clientRectangles.item(0)
|
|
}
|
|
if(rectangle) {
|
|
simplifiedRectangle = {top:rectangle.top, left:useRightEdge ? rectangle.right : rectangle.left, bottom:rectangle.bottom}
|
|
}
|
|
return simplifiedRectangle
|
|
}
|
|
function getVisibleRect(container, offset, range, useRightEdge) {
|
|
var rectangle, nodeType = container.nodeType;
|
|
range.setStart(container, offset);
|
|
range.collapse(!useRightEdge);
|
|
rectangle = getClientRect(range.getClientRects(), useRightEdge === true);
|
|
if(!rectangle && offset > 0) {
|
|
range.setStart(container, offset - 1);
|
|
range.setEnd(container, offset);
|
|
rectangle = getClientRect(range.getClientRects(), true)
|
|
}
|
|
if(!rectangle) {
|
|
if(nodeType === Node.ELEMENT_NODE && (offset > 0 && (container).childNodes.length >= offset)) {
|
|
rectangle = getVisibleRect(container, offset - 1, range, true)
|
|
}else {
|
|
if(container.nodeType === Node.TEXT_NODE && offset > 0) {
|
|
rectangle = getVisibleRect(container, offset - 1, range, true)
|
|
}else {
|
|
if(container.previousSibling) {
|
|
rectangle = getVisibleRect(container.previousSibling, getMaximumNodePosition(container.previousSibling), range, true)
|
|
}else {
|
|
if(container.parentNode && container.parentNode !== rootNode) {
|
|
rectangle = getVisibleRect(container.parentNode, 0, range, false)
|
|
}else {
|
|
range.selectNode(rootNode);
|
|
rectangle = getClientRect(range.getClientRects(), false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
runtime.assert(Boolean(rectangle), "No visible rectangle found");
|
|
return(rectangle)
|
|
}
|
|
function convertForwardStepsBetweenFilters(stepsFilter1, filter1, filter2) {
|
|
var iterator = getIteratorAtCursor(), watch = new core.LoopWatchDog(1E4), pendingStepsFilter2 = 0, stepsFilter2 = 0;
|
|
while(stepsFilter1 > 0 && iterator.nextPosition()) {
|
|
watch.check();
|
|
if(filter2.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
pendingStepsFilter2 += 1;
|
|
if(filter1.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
stepsFilter2 += pendingStepsFilter2;
|
|
pendingStepsFilter2 = 0;
|
|
stepsFilter1 -= 1
|
|
}
|
|
}
|
|
}
|
|
return stepsFilter2
|
|
}
|
|
function convertBackwardStepsBetweenFilters(stepsFilter1, filter1, filter2) {
|
|
var iterator = getIteratorAtCursor(), watch = new core.LoopWatchDog(1E4), pendingStepsFilter2 = 0, stepsFilter2 = 0;
|
|
while(stepsFilter1 > 0 && iterator.previousPosition()) {
|
|
watch.check();
|
|
if(filter2.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
pendingStepsFilter2 += 1;
|
|
if(filter1.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
stepsFilter2 += pendingStepsFilter2;
|
|
pendingStepsFilter2 = 0;
|
|
stepsFilter1 -= 1
|
|
}
|
|
}
|
|
}
|
|
return stepsFilter2
|
|
}
|
|
function countLineSteps(filter, direction, iterator) {
|
|
var c = iterator.container(), steps = 0, bestContainer = null, bestOffset, bestXDiff = 10, xDiff, bestCount = 0, top, left, lastTop, rect, range = (rootNode.ownerDocument.createRange()), watch = new core.LoopWatchDog(1E4);
|
|
rect = getVisibleRect(c, iterator.unfilteredDomOffset(), range);
|
|
top = rect.top;
|
|
left = rect.left;
|
|
lastTop = top;
|
|
while((direction < 0 ? iterator.previousPosition() : iterator.nextPosition()) === true) {
|
|
watch.check();
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
steps += 1;
|
|
c = iterator.container();
|
|
rect = getVisibleRect(c, iterator.unfilteredDomOffset(), range);
|
|
if(rect.top !== top) {
|
|
if(rect.top !== lastTop && lastTop !== top) {
|
|
break
|
|
}
|
|
lastTop = rect.top;
|
|
xDiff = Math.abs(left - rect.left);
|
|
if(bestContainer === null || xDiff < bestXDiff) {
|
|
bestContainer = c;
|
|
bestOffset = iterator.unfilteredDomOffset();
|
|
bestXDiff = xDiff;
|
|
bestCount = steps
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(bestContainer !== null) {
|
|
iterator.setUnfilteredPosition(bestContainer, (bestOffset));
|
|
steps = bestCount
|
|
}else {
|
|
steps = 0
|
|
}
|
|
range.detach();
|
|
return steps
|
|
}
|
|
function countLinesSteps(lines, filter) {
|
|
var iterator = getIteratorAtCursor(), stepCount = 0, steps = 0, direction = lines < 0 ? -1 : 1;
|
|
lines = Math.abs(lines);
|
|
while(lines > 0) {
|
|
stepCount += countLineSteps(filter, direction, iterator);
|
|
if(stepCount === 0) {
|
|
break
|
|
}
|
|
steps += stepCount;
|
|
lines -= 1
|
|
}
|
|
return steps * direction
|
|
}
|
|
function countStepsToLineBoundary(direction, filter) {
|
|
var fnNextPos, increment, lastRect, rect, onSameLine, iterator = getIteratorAtCursor(), paragraphNode = odfUtils.getParagraphElement(iterator.getCurrentNode()), steps = 0, range = (rootNode.ownerDocument.createRange());
|
|
if(direction < 0) {
|
|
fnNextPos = iterator.previousPosition;
|
|
increment = -1
|
|
}else {
|
|
fnNextPos = iterator.nextPosition;
|
|
increment = 1
|
|
}
|
|
lastRect = getVisibleRect(iterator.container(), iterator.unfilteredDomOffset(), range);
|
|
while(fnNextPos.call(iterator)) {
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
if(odfUtils.getParagraphElement(iterator.getCurrentNode()) !== paragraphNode) {
|
|
break
|
|
}
|
|
rect = getVisibleRect(iterator.container(), iterator.unfilteredDomOffset(), range);
|
|
if(rect.bottom !== lastRect.bottom) {
|
|
onSameLine = rect.top >= lastRect.top && rect.bottom < lastRect.bottom || rect.top <= lastRect.top && rect.bottom > lastRect.bottom;
|
|
if(!onSameLine) {
|
|
break
|
|
}
|
|
}
|
|
steps += increment;
|
|
lastRect = rect
|
|
}
|
|
}
|
|
range.detach();
|
|
return steps
|
|
}
|
|
this.getStepCounter = function() {
|
|
return{convertForwardStepsBetweenFilters:convertForwardStepsBetweenFilters, convertBackwardStepsBetweenFilters:convertBackwardStepsBetweenFilters, countLinesSteps:countLinesSteps, countStepsToLineBoundary:countStepsToLineBoundary}
|
|
};
|
|
function init() {
|
|
positionIterator = gui.SelectionMover.createPositionIterator(rootNode);
|
|
var range = rootNode.ownerDocument.createRange();
|
|
range.setStart(positionIterator.container(), positionIterator.unfilteredDomOffset());
|
|
range.collapse(true);
|
|
cursor.setSelectedRange(range)
|
|
}
|
|
init()
|
|
};
|
|
gui.SelectionMover.createPositionIterator = function(rootNode) {
|
|
function CursorFilter() {
|
|
this.acceptNode = function(node) {
|
|
if(!node || (node.namespaceURI === "urn:webodf:names:cursor" || node.namespaceURI === "urn:webodf:names:editinfo")) {
|
|
return NodeFilter.FILTER_REJECT
|
|
}
|
|
return NodeFilter.FILTER_ACCEPT
|
|
}
|
|
}
|
|
var filter = new CursorFilter;
|
|
return new core.PositionIterator(rootNode, 5, filter, false)
|
|
};
|
|
(function() {
|
|
return gui.SelectionMover
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.Document = function Document() {
|
|
};
|
|
ops.Document.prototype.getMemberIds = function() {
|
|
};
|
|
ops.Document.prototype.removeCursor = function(memberid) {
|
|
};
|
|
ops.Document.prototype.getDocumentElement = function() {
|
|
};
|
|
ops.Document.prototype.getRootNode = function() {
|
|
};
|
|
ops.Document.prototype.getDOMDocument = function() {
|
|
};
|
|
ops.Document.prototype.cloneDocumentElement = function() {
|
|
};
|
|
ops.Document.prototype.setDocumentElement = function(element) {
|
|
};
|
|
ops.Document.prototype.subscribe = function(eventid, cb) {
|
|
};
|
|
ops.Document.prototype.unsubscribe = function(eventid, cb) {
|
|
};
|
|
ops.Document.prototype.getCanvas = function() {
|
|
};
|
|
ops.Document.prototype.createRootFilter = function(inputMemberId) {
|
|
};
|
|
ops.Document.signalCursorAdded = "cursor/added";
|
|
ops.Document.signalCursorRemoved = "cursor/removed";
|
|
ops.Document.signalCursorMoved = "cursor/moved";
|
|
ops.Document.signalMemberAdded = "member/added";
|
|
ops.Document.signalMemberUpdated = "member/updated";
|
|
ops.Document.signalMemberRemoved = "member/removed";
|
|
ops.OdtCursor = function OdtCursor(memberId, document) {
|
|
var self = this, validSelectionTypes = {}, selectionType, selectionMover, cursor, events = new core.EventNotifier([ops.OdtCursor.signalCursorUpdated]);
|
|
this.removeFromDocument = function() {
|
|
cursor.remove()
|
|
};
|
|
this.subscribe = function(eventid, cb) {
|
|
events.subscribe(eventid, cb)
|
|
};
|
|
this.unsubscribe = function(eventid, cb) {
|
|
events.unsubscribe(eventid, cb)
|
|
};
|
|
this.getStepCounter = function() {
|
|
return selectionMover.getStepCounter()
|
|
};
|
|
this.getMemberId = function() {
|
|
return memberId
|
|
};
|
|
this.getNode = function() {
|
|
return cursor.getNode()
|
|
};
|
|
this.getAnchorNode = function() {
|
|
return cursor.getAnchorNode()
|
|
};
|
|
this.getSelectedRange = function() {
|
|
return cursor.getSelectedRange()
|
|
};
|
|
this.setSelectedRange = function(range, isForwardSelection) {
|
|
cursor.setSelectedRange(range, isForwardSelection);
|
|
events.emit(ops.OdtCursor.signalCursorUpdated, self)
|
|
};
|
|
this.hasForwardSelection = function() {
|
|
return cursor.hasForwardSelection()
|
|
};
|
|
this.getDocument = function() {
|
|
return document
|
|
};
|
|
this.getSelectionType = function() {
|
|
return selectionType
|
|
};
|
|
this.setSelectionType = function(value) {
|
|
if(validSelectionTypes.hasOwnProperty(value)) {
|
|
selectionType = value
|
|
}else {
|
|
runtime.log("Invalid selection type: " + value)
|
|
}
|
|
};
|
|
this.resetSelectionType = function() {
|
|
self.setSelectionType(ops.OdtCursor.RangeSelection)
|
|
};
|
|
function init() {
|
|
cursor = new core.Cursor(document.getDOMDocument(), memberId);
|
|
selectionMover = new gui.SelectionMover(cursor, document.getRootNode());
|
|
validSelectionTypes[ops.OdtCursor.RangeSelection] = true;
|
|
validSelectionTypes[ops.OdtCursor.RegionSelection] = true;
|
|
self.resetSelectionType()
|
|
}
|
|
init()
|
|
};
|
|
ops.OdtCursor.RangeSelection = "Range";
|
|
ops.OdtCursor.RegionSelection = "Region";
|
|
ops.OdtCursor.signalCursorUpdated = "cursorUpdated";
|
|
(function() {
|
|
return ops.OdtCursor
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.Operation = function Operation() {
|
|
};
|
|
ops.Operation.prototype.init = function(data) {
|
|
};
|
|
ops.Operation.prototype.isEdit;
|
|
ops.Operation.prototype.group;
|
|
ops.Operation.prototype.execute = function(document) {
|
|
};
|
|
ops.Operation.prototype.spec = function() {
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2010-2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
var nextNodeId = 0;
|
|
ops.StepsCache = function StepsCache(rootElement, filter, bucketSize) {
|
|
var coordinatens = "urn:webodf:names:steps", stepToDomPoint = {}, nodeToBookmark = {}, odfUtils = new odf.OdfUtils, domUtils = new core.DomUtils, basePoint, lastUndamagedCacheStep, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, verifyCache;
|
|
function ParagraphBookmark(nodeId, steps, paragraphNode) {
|
|
this.nodeId = nodeId;
|
|
this.steps = steps;
|
|
this.node = paragraphNode;
|
|
this.nextBookmark = null;
|
|
this.previousBookmark = null;
|
|
this.setIteratorPosition = function(iterator) {
|
|
iterator.setPositionBeforeElement(paragraphNode);
|
|
do {
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
break
|
|
}
|
|
}while(iterator.nextPosition())
|
|
}
|
|
}
|
|
function RootBookmark(nodeId, steps, rootNode) {
|
|
this.nodeId = nodeId;
|
|
this.steps = steps;
|
|
this.node = rootNode;
|
|
this.nextBookmark = null;
|
|
this.previousBookmark = null;
|
|
this.setIteratorPosition = function(iterator) {
|
|
iterator.setUnfilteredPosition(rootNode, 0);
|
|
do {
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
break
|
|
}
|
|
}while(iterator.nextPosition())
|
|
}
|
|
}
|
|
function inspectBookmarks(bookmark1, bookmark2) {
|
|
var parts = "[" + bookmark1.nodeId;
|
|
if(bookmark2) {
|
|
parts += " => " + bookmark2.nodeId
|
|
}
|
|
return parts + "]"
|
|
}
|
|
function isUndamagedBookmark(bookmark) {
|
|
return lastUndamagedCacheStep === undefined || bookmark.steps <= lastUndamagedCacheStep
|
|
}
|
|
function verifyCacheImpl() {
|
|
var bookmark = basePoint, previousBookmark, nextBookmark, documentPosition, loopCheck = new core.LoopWatchDog(0, 1E5);
|
|
while(bookmark) {
|
|
loopCheck.check();
|
|
previousBookmark = bookmark.previousBookmark;
|
|
if(previousBookmark) {
|
|
runtime.assert(previousBookmark.nextBookmark === bookmark, "Broken bookmark link to previous @" + inspectBookmarks(previousBookmark, bookmark))
|
|
}else {
|
|
runtime.assert(bookmark === basePoint, "Broken bookmark link @" + inspectBookmarks(bookmark));
|
|
runtime.assert(isUndamagedBookmark(basePoint), "Base point is damaged @" + inspectBookmarks(bookmark))
|
|
}
|
|
nextBookmark = bookmark.nextBookmark;
|
|
if(nextBookmark) {
|
|
runtime.assert(nextBookmark.previousBookmark === bookmark, "Broken bookmark link to next @" + inspectBookmarks(bookmark, nextBookmark))
|
|
}
|
|
if(isUndamagedBookmark(bookmark)) {
|
|
runtime.assert(domUtils.containsNode(rootElement, bookmark.node), "Disconnected node is being reported as undamaged @" + inspectBookmarks(bookmark));
|
|
if(previousBookmark) {
|
|
documentPosition = bookmark.node.compareDocumentPosition(previousBookmark.node);
|
|
runtime.assert(documentPosition === 0 || (documentPosition & Node.DOCUMENT_POSITION_PRECEDING) !== 0, "Bookmark order with previous does not reflect DOM order @" + inspectBookmarks(previousBookmark, bookmark))
|
|
}
|
|
if(nextBookmark) {
|
|
if(domUtils.containsNode(rootElement, nextBookmark.node)) {
|
|
documentPosition = bookmark.node.compareDocumentPosition(nextBookmark.node);
|
|
runtime.assert(documentPosition === 0 || (documentPosition & Node.DOCUMENT_POSITION_FOLLOWING) !== 0, "Bookmark order with next does not reflect DOM order @" + inspectBookmarks(bookmark, nextBookmark))
|
|
}
|
|
}
|
|
}
|
|
bookmark = bookmark.nextBookmark
|
|
}
|
|
}
|
|
function getBucket(steps) {
|
|
return Math.floor(steps / bucketSize) * bucketSize
|
|
}
|
|
function getDestinationBucket(steps) {
|
|
return Math.ceil(steps / bucketSize) * bucketSize
|
|
}
|
|
function clearNodeId(node) {
|
|
node.removeAttributeNS(coordinatens, "nodeId")
|
|
}
|
|
function getNodeId(node) {
|
|
var id = "";
|
|
if(node.nodeType === Node.ELEMENT_NODE) {
|
|
id = (node).getAttributeNS(coordinatens, "nodeId")
|
|
}
|
|
return id
|
|
}
|
|
function setNodeId(node) {
|
|
var nodeId = nextNodeId.toString();
|
|
node.setAttributeNS(coordinatens, "nodeId", nodeId);
|
|
nextNodeId += 1;
|
|
return nodeId
|
|
}
|
|
function isValidBookmarkForNode(node, bookmark) {
|
|
return bookmark.node === node
|
|
}
|
|
function getNodeBookmark(node, steps) {
|
|
var nodeId = getNodeId(node) || setNodeId(node), existingBookmark;
|
|
existingBookmark = nodeToBookmark[nodeId];
|
|
if(!existingBookmark) {
|
|
existingBookmark = nodeToBookmark[nodeId] = new ParagraphBookmark(nodeId, steps, node)
|
|
}else {
|
|
if(!isValidBookmarkForNode(node, existingBookmark)) {
|
|
runtime.log("Cloned node detected. Creating new bookmark");
|
|
nodeId = setNodeId(node);
|
|
existingBookmark = nodeToBookmark[nodeId] = new ParagraphBookmark(nodeId, steps, node)
|
|
}else {
|
|
existingBookmark.steps = steps
|
|
}
|
|
}
|
|
return existingBookmark
|
|
}
|
|
function getClosestBookmark(steps) {
|
|
var cacheBucket, cachePoint, loopGuard = new core.LoopWatchDog(0, 1E4);
|
|
if(lastUndamagedCacheStep !== undefined && steps > lastUndamagedCacheStep) {
|
|
steps = lastUndamagedCacheStep
|
|
}
|
|
cacheBucket = getBucket(steps);
|
|
while(!cachePoint && cacheBucket !== 0) {
|
|
cachePoint = stepToDomPoint[cacheBucket];
|
|
cacheBucket -= bucketSize
|
|
}
|
|
cachePoint = cachePoint || basePoint;
|
|
while(cachePoint.nextBookmark && cachePoint.nextBookmark.steps <= steps) {
|
|
loopGuard.check();
|
|
cachePoint = cachePoint.nextBookmark
|
|
}
|
|
return cachePoint
|
|
}
|
|
function getUndamagedBookmark(bookmark) {
|
|
if(lastUndamagedCacheStep !== undefined && bookmark.steps > lastUndamagedCacheStep) {
|
|
bookmark = getClosestBookmark(lastUndamagedCacheStep)
|
|
}
|
|
return bookmark
|
|
}
|
|
function removeBookmark(currentBookmark) {
|
|
if(currentBookmark.previousBookmark) {
|
|
currentBookmark.previousBookmark.nextBookmark = currentBookmark.nextBookmark
|
|
}
|
|
if(currentBookmark.nextBookmark) {
|
|
currentBookmark.nextBookmark.previousBookmark = currentBookmark.previousBookmark
|
|
}
|
|
}
|
|
function insertBookmark(previousBookmark, newBookmark) {
|
|
var nextBookmark;
|
|
if(previousBookmark !== newBookmark && previousBookmark.nextBookmark !== newBookmark) {
|
|
removeBookmark(newBookmark);
|
|
nextBookmark = previousBookmark.nextBookmark;
|
|
newBookmark.nextBookmark = previousBookmark.nextBookmark;
|
|
newBookmark.previousBookmark = previousBookmark;
|
|
previousBookmark.nextBookmark = newBookmark;
|
|
if(nextBookmark) {
|
|
nextBookmark.previousBookmark = newBookmark
|
|
}
|
|
}
|
|
}
|
|
function repairCacheUpToStep(currentIteratorStep) {
|
|
var damagedBookmark, undamagedBookmark, nextBookmark, stepsBucket;
|
|
if(lastUndamagedCacheStep !== undefined && lastUndamagedCacheStep < currentIteratorStep) {
|
|
undamagedBookmark = getClosestBookmark(lastUndamagedCacheStep);
|
|
damagedBookmark = undamagedBookmark.nextBookmark;
|
|
while(damagedBookmark && damagedBookmark.steps <= currentIteratorStep) {
|
|
nextBookmark = damagedBookmark.nextBookmark;
|
|
stepsBucket = getDestinationBucket(damagedBookmark.steps);
|
|
if(stepToDomPoint[stepsBucket] === damagedBookmark) {
|
|
delete stepToDomPoint[stepsBucket]
|
|
}
|
|
if(!domUtils.containsNode(rootElement, damagedBookmark.node)) {
|
|
removeBookmark(damagedBookmark);
|
|
delete nodeToBookmark[damagedBookmark.nodeId]
|
|
}else {
|
|
damagedBookmark.steps = currentIteratorStep + 1
|
|
}
|
|
damagedBookmark = nextBookmark
|
|
}
|
|
lastUndamagedCacheStep = currentIteratorStep
|
|
}else {
|
|
undamagedBookmark = getClosestBookmark(currentIteratorStep)
|
|
}
|
|
return undamagedBookmark
|
|
}
|
|
this.updateCache = function(steps, iterator, isStep) {
|
|
var cacheBucket, existingCachePoint, bookmark, closestPriorBookmark, node = iterator.getCurrentNode();
|
|
if(iterator.isBeforeNode() && odfUtils.isParagraph(node)) {
|
|
if(!isStep) {
|
|
steps += 1
|
|
}
|
|
closestPriorBookmark = repairCacheUpToStep(steps);
|
|
bookmark = getNodeBookmark((node), steps);
|
|
insertBookmark(closestPriorBookmark, bookmark);
|
|
cacheBucket = getDestinationBucket(bookmark.steps);
|
|
existingCachePoint = stepToDomPoint[cacheBucket];
|
|
if(!existingCachePoint || bookmark.steps > existingCachePoint.steps) {
|
|
stepToDomPoint[cacheBucket] = bookmark
|
|
}
|
|
verifyCache()
|
|
}
|
|
};
|
|
this.setToClosestStep = function(steps, iterator) {
|
|
var cachePoint;
|
|
verifyCache();
|
|
cachePoint = getClosestBookmark(steps);
|
|
cachePoint.setIteratorPosition(iterator);
|
|
return cachePoint.steps
|
|
};
|
|
function findBookmarkedAncestor(node) {
|
|
var currentNode = node, nodeId, bookmark = null;
|
|
while(!bookmark && (currentNode && currentNode !== rootElement)) {
|
|
nodeId = getNodeId(currentNode);
|
|
if(nodeId) {
|
|
bookmark = nodeToBookmark[nodeId];
|
|
if(bookmark && !isValidBookmarkForNode(currentNode, bookmark)) {
|
|
runtime.log("Cloned node detected. Creating new bookmark");
|
|
bookmark = null;
|
|
clearNodeId((currentNode))
|
|
}
|
|
}
|
|
currentNode = currentNode.parentNode
|
|
}
|
|
return bookmark
|
|
}
|
|
this.setToClosestDomPoint = function(node, offset, iterator) {
|
|
var bookmark, b, key;
|
|
verifyCache();
|
|
if(node === rootElement && offset === 0) {
|
|
bookmark = basePoint
|
|
}else {
|
|
if(node === rootElement && offset === rootElement.childNodes.length) {
|
|
bookmark = basePoint;
|
|
for(key in stepToDomPoint) {
|
|
if(stepToDomPoint.hasOwnProperty(key)) {
|
|
b = stepToDomPoint[key];
|
|
if(b.steps > bookmark.steps) {
|
|
bookmark = b
|
|
}
|
|
}
|
|
}
|
|
}else {
|
|
bookmark = findBookmarkedAncestor(node.childNodes.item(offset) || node);
|
|
if(!bookmark) {
|
|
iterator.setUnfilteredPosition(node, offset);
|
|
while(!bookmark && iterator.previousNode()) {
|
|
bookmark = findBookmarkedAncestor(iterator.getCurrentNode())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bookmark = getUndamagedBookmark(bookmark || basePoint);
|
|
bookmark.setIteratorPosition(iterator);
|
|
return bookmark.steps
|
|
};
|
|
this.damageCacheAfterStep = function(inflectionStep) {
|
|
if(inflectionStep < 0) {
|
|
inflectionStep = 0
|
|
}
|
|
if(lastUndamagedCacheStep === undefined) {
|
|
lastUndamagedCacheStep = inflectionStep
|
|
}else {
|
|
if(inflectionStep < lastUndamagedCacheStep) {
|
|
lastUndamagedCacheStep = inflectionStep
|
|
}
|
|
}
|
|
verifyCache()
|
|
};
|
|
function init() {
|
|
var rootElementId = getNodeId(rootElement) || setNodeId(rootElement);
|
|
basePoint = new RootBookmark(rootElementId, 0, rootElement);
|
|
verifyCache = ops.StepsCache.ENABLE_CACHE_VERIFICATION ? verifyCacheImpl : function() {
|
|
}
|
|
}
|
|
init()
|
|
};
|
|
ops.StepsCache.ENABLE_CACHE_VERIFICATION = false;
|
|
ops.StepsCache.Bookmark = function Bookmark() {
|
|
};
|
|
ops.StepsCache.Bookmark.prototype.nodeId;
|
|
ops.StepsCache.Bookmark.prototype.node;
|
|
ops.StepsCache.Bookmark.prototype.steps;
|
|
ops.StepsCache.Bookmark.prototype.previousBookmark;
|
|
ops.StepsCache.Bookmark.prototype.nextBookmark;
|
|
ops.StepsCache.Bookmark.prototype.setIteratorPosition = function(iterator) {
|
|
}
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
(function() {
|
|
var PREVIOUS_STEP = 0, NEXT_STEP = 1;
|
|
ops.StepsTranslator = function StepsTranslator(getRootNode, newIterator, filter, bucketSize) {
|
|
var rootNode = getRootNode(), stepsCache = new ops.StepsCache(rootNode, filter, bucketSize), domUtils = new core.DomUtils, iterator = newIterator(getRootNode()), FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT;
|
|
function verifyRootNode() {
|
|
var currentRootNode = getRootNode();
|
|
if(currentRootNode !== rootNode) {
|
|
runtime.log("Undo detected. Resetting steps cache");
|
|
rootNode = currentRootNode;
|
|
stepsCache = new ops.StepsCache(rootNode, filter, bucketSize);
|
|
iterator = newIterator(rootNode)
|
|
}
|
|
}
|
|
this.convertStepsToDomPoint = function(steps) {
|
|
var stepsFromRoot, isStep;
|
|
if(isNaN(steps)) {
|
|
throw new TypeError("Requested steps is not numeric (" + steps + ")");
|
|
}
|
|
if(steps < 0) {
|
|
throw new RangeError("Requested steps is negative (" + steps + ")");
|
|
}
|
|
verifyRootNode();
|
|
stepsFromRoot = stepsCache.setToClosestStep(steps, iterator);
|
|
while(stepsFromRoot < steps && iterator.nextPosition()) {
|
|
isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
|
|
if(isStep) {
|
|
stepsFromRoot += 1
|
|
}
|
|
stepsCache.updateCache(stepsFromRoot, iterator, isStep)
|
|
}
|
|
if(stepsFromRoot !== steps) {
|
|
throw new RangeError("Requested steps (" + steps + ") exceeds available steps (" + stepsFromRoot + ")");
|
|
}
|
|
return{node:iterator.container(), offset:iterator.unfilteredDomOffset()}
|
|
};
|
|
function roundToPreferredStep(iterator, roundDirection) {
|
|
if(!roundDirection || filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
return true
|
|
}
|
|
while(iterator.previousPosition()) {
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
if(roundDirection(PREVIOUS_STEP, iterator.container(), iterator.unfilteredDomOffset())) {
|
|
return true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
while(iterator.nextPosition()) {
|
|
if(filter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
if(roundDirection(NEXT_STEP, iterator.container(), iterator.unfilteredDomOffset())) {
|
|
return true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
this.convertDomPointToSteps = function(node, offset, roundDirection) {
|
|
var stepsFromRoot, beforeRoot, destinationNode, destinationOffset, rounding = 0, isStep;
|
|
verifyRootNode();
|
|
if(!domUtils.containsNode(rootNode, node)) {
|
|
beforeRoot = domUtils.comparePoints(rootNode, 0, node, offset) < 0;
|
|
node = (rootNode);
|
|
offset = beforeRoot ? 0 : (rootNode).childNodes.length
|
|
}
|
|
iterator.setUnfilteredPosition(node, offset);
|
|
if(!roundToPreferredStep(iterator, roundDirection)) {
|
|
iterator.setUnfilteredPosition(node, offset)
|
|
}
|
|
destinationNode = iterator.container();
|
|
destinationOffset = iterator.unfilteredDomOffset();
|
|
stepsFromRoot = stepsCache.setToClosestDomPoint(destinationNode, destinationOffset, iterator);
|
|
if(domUtils.comparePoints(iterator.container(), iterator.unfilteredDomOffset(), destinationNode, destinationOffset) < 0) {
|
|
return stepsFromRoot > 0 ? stepsFromRoot - 1 : stepsFromRoot
|
|
}
|
|
while(!(iterator.container() === destinationNode && iterator.unfilteredDomOffset() === destinationOffset) && iterator.nextPosition()) {
|
|
isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
|
|
if(isStep) {
|
|
stepsFromRoot += 1
|
|
}
|
|
stepsCache.updateCache(stepsFromRoot, iterator, isStep)
|
|
}
|
|
return stepsFromRoot + rounding
|
|
};
|
|
this.prime = function() {
|
|
var stepsFromRoot, isStep;
|
|
verifyRootNode();
|
|
stepsFromRoot = stepsCache.setToClosestStep(0, iterator);
|
|
while(iterator.nextPosition()) {
|
|
isStep = filter.acceptPosition(iterator) === FILTER_ACCEPT;
|
|
if(isStep) {
|
|
stepsFromRoot += 1
|
|
}
|
|
stepsCache.updateCache(stepsFromRoot, iterator, isStep)
|
|
}
|
|
};
|
|
this.handleStepsInserted = function(eventArgs) {
|
|
verifyRootNode();
|
|
stepsCache.damageCacheAfterStep(eventArgs.position)
|
|
};
|
|
this.handleStepsRemoved = function(eventArgs) {
|
|
verifyRootNode();
|
|
stepsCache.damageCacheAfterStep(eventArgs.position - 1)
|
|
}
|
|
};
|
|
ops.StepsTranslator.PREVIOUS_STEP = PREVIOUS_STEP;
|
|
ops.StepsTranslator.NEXT_STEP = NEXT_STEP;
|
|
return ops.StepsTranslator
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.TextPositionFilter = function TextPositionFilter(getRootNode) {
|
|
var odfUtils = new odf.OdfUtils, ELEMENT_NODE = Node.ELEMENT_NODE, TEXT_NODE = Node.TEXT_NODE, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT;
|
|
function checkLeftRight(container, leftNode, rightNode) {
|
|
var r, firstPos, rightOfChar;
|
|
if(leftNode) {
|
|
if(odfUtils.isInlineRoot(leftNode) && odfUtils.isGroupingElement(rightNode)) {
|
|
return FILTER_REJECT
|
|
}
|
|
r = odfUtils.lookLeftForCharacter(leftNode);
|
|
if(r === 1) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
if(r === 2 && (odfUtils.scanRightForAnyCharacter(rightNode) || odfUtils.scanRightForAnyCharacter(odfUtils.nextNode(container)))) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
}
|
|
firstPos = leftNode === null && odfUtils.isParagraph(container);
|
|
rightOfChar = odfUtils.lookRightForCharacter(rightNode);
|
|
if(firstPos) {
|
|
if(rightOfChar) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
return odfUtils.scanRightForAnyCharacter(rightNode) ? FILTER_REJECT : FILTER_ACCEPT
|
|
}
|
|
if(!rightOfChar) {
|
|
return FILTER_REJECT
|
|
}
|
|
leftNode = leftNode || odfUtils.previousNode(container);
|
|
return odfUtils.scanLeftForAnyCharacter(leftNode) ? FILTER_REJECT : FILTER_ACCEPT
|
|
}
|
|
this.acceptPosition = function(iterator) {
|
|
var container = iterator.container(), nodeType = container.nodeType, offset, text, leftChar, rightChar, leftNode, rightNode, r;
|
|
if(nodeType !== ELEMENT_NODE && nodeType !== TEXT_NODE) {
|
|
return FILTER_REJECT
|
|
}
|
|
if(nodeType === TEXT_NODE) {
|
|
if(!odfUtils.isGroupingElement(container.parentNode) || odfUtils.isWithinTrackedChanges(container.parentNode, getRootNode())) {
|
|
return FILTER_REJECT
|
|
}
|
|
offset = iterator.unfilteredDomOffset();
|
|
text = container.data;
|
|
runtime.assert(offset !== text.length, "Unexpected offset.");
|
|
if(offset > 0) {
|
|
leftChar = (text[offset - 1]);
|
|
if(!odfUtils.isODFWhitespace(leftChar)) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
if(offset > 1) {
|
|
leftChar = (text[offset - 2]);
|
|
if(!odfUtils.isODFWhitespace(leftChar)) {
|
|
r = FILTER_ACCEPT
|
|
}else {
|
|
if(!odfUtils.isODFWhitespace(text.substr(0, offset))) {
|
|
return FILTER_REJECT
|
|
}
|
|
}
|
|
}else {
|
|
leftNode = odfUtils.previousNode(container);
|
|
if(odfUtils.scanLeftForNonSpace(leftNode)) {
|
|
r = FILTER_ACCEPT
|
|
}
|
|
}
|
|
if(r === FILTER_ACCEPT) {
|
|
return odfUtils.isTrailingWhitespace((container), offset) ? FILTER_REJECT : FILTER_ACCEPT
|
|
}
|
|
rightChar = (text[offset]);
|
|
if(odfUtils.isODFWhitespace(rightChar)) {
|
|
return FILTER_REJECT
|
|
}
|
|
return odfUtils.scanLeftForAnyCharacter(odfUtils.previousNode(container)) ? FILTER_REJECT : FILTER_ACCEPT
|
|
}
|
|
leftNode = iterator.leftNode();
|
|
rightNode = container;
|
|
container = (container.parentNode);
|
|
r = checkLeftRight(container, leftNode, rightNode)
|
|
}else {
|
|
if(!odfUtils.isGroupingElement(container) || odfUtils.isWithinTrackedChanges(container, getRootNode())) {
|
|
r = FILTER_REJECT
|
|
}else {
|
|
leftNode = iterator.leftNode();
|
|
rightNode = iterator.rightNode();
|
|
r = checkLeftRight(container, leftNode, rightNode)
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OdtDocument = function OdtDocument(odfCanvas) {
|
|
var self = this, odfUtils, domUtils, cursors = {}, members = {}, eventNotifier = new core.EventNotifier([ops.Document.signalMemberAdded, ops.Document.signalMemberUpdated, ops.Document.signalMemberRemoved, ops.Document.signalCursorAdded, ops.Document.signalCursorRemoved, ops.Document.signalCursorMoved, ops.OdtDocument.signalParagraphChanged, ops.OdtDocument.signalParagraphStyleModified, ops.OdtDocument.signalCommonStyleCreated, ops.OdtDocument.signalCommonStyleDeleted, ops.OdtDocument.signalTableAdded,
|
|
ops.OdtDocument.signalOperationStart, ops.OdtDocument.signalOperationEnd, ops.OdtDocument.signalProcessingBatchStart, ops.OdtDocument.signalProcessingBatchEnd, ops.OdtDocument.signalUndoStackChanged, ops.OdtDocument.signalStepsInserted, ops.OdtDocument.signalStepsRemoved]), FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT, filter, stepsTranslator, lastEditingOp, unsupportedMetadataRemoved = false;
|
|
function getRootNode() {
|
|
var element = odfCanvas.odfContainer().getContentElement(), localName = element && element.localName;
|
|
runtime.assert(localName === "text", "Unsupported content element type '" + localName + "' for OdtDocument");
|
|
return element
|
|
}
|
|
this.getDocumentElement = function() {
|
|
return odfCanvas.odfContainer().rootElement
|
|
};
|
|
this.getDOMDocument = function() {
|
|
return(this.getDocumentElement().ownerDocument)
|
|
};
|
|
this.cloneDocumentElement = function() {
|
|
var rootElement = self.getDocumentElement(), annotationViewManager = odfCanvas.getAnnotationViewManager(), initialDoc;
|
|
if(annotationViewManager) {
|
|
annotationViewManager.forgetAnnotations()
|
|
}
|
|
initialDoc = rootElement.cloneNode(true);
|
|
odfCanvas.refreshAnnotations();
|
|
return initialDoc
|
|
};
|
|
this.setDocumentElement = function(documentElement) {
|
|
var odfContainer = odfCanvas.odfContainer();
|
|
odfContainer.setRootElement(documentElement);
|
|
odfCanvas.setOdfContainer(odfContainer, true);
|
|
odfCanvas.refreshCSS()
|
|
};
|
|
function getDOMDocument() {
|
|
return(self.getDocumentElement().ownerDocument)
|
|
}
|
|
this.getDOMDocument = getDOMDocument;
|
|
function isRoot(node) {
|
|
if(node.namespaceURI === odf.Namespaces.officens && node.localName === "text" || node.namespaceURI === odf.Namespaces.officens && node.localName === "annotation") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
function getRoot(node) {
|
|
while(node && !isRoot(node)) {
|
|
node = (node.parentNode)
|
|
}
|
|
return node
|
|
}
|
|
this.getRootElement = getRoot;
|
|
function RootFilter(anchor) {
|
|
this.acceptPosition = function(iterator) {
|
|
var node = iterator.container(), anchorNode;
|
|
if(typeof anchor === "string") {
|
|
anchorNode = cursors[anchor].getNode()
|
|
}else {
|
|
anchorNode = anchor
|
|
}
|
|
if(getRoot(node) === getRoot(anchorNode)) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
return FILTER_REJECT
|
|
}
|
|
}
|
|
function createStepIterator(container, offset, filters, subTree) {
|
|
var positionIterator = gui.SelectionMover.createPositionIterator(subTree), filterOrChain, stepIterator;
|
|
if(filters.length === 1) {
|
|
filterOrChain = filters[0]
|
|
}else {
|
|
filterOrChain = new core.PositionFilterChain;
|
|
filters.forEach(filterOrChain.addFilter)
|
|
}
|
|
stepIterator = new core.StepIterator(filterOrChain, positionIterator);
|
|
stepIterator.setPosition(container, offset);
|
|
return stepIterator
|
|
}
|
|
this.createStepIterator = createStepIterator;
|
|
function getIteratorAtPosition(position) {
|
|
var iterator = gui.SelectionMover.createPositionIterator(getRootNode()), point = stepsTranslator.convertStepsToDomPoint(position);
|
|
iterator.setUnfilteredPosition(point.node, point.offset);
|
|
return iterator
|
|
}
|
|
this.getIteratorAtPosition = getIteratorAtPosition;
|
|
this.convertDomPointToCursorStep = function(node, offset, roundDirection) {
|
|
return stepsTranslator.convertDomPointToSteps(node, offset, roundDirection)
|
|
};
|
|
this.convertDomToCursorRange = function(selection, constraint) {
|
|
var point1, point2, anchorConstraint = constraint && constraint(selection.anchorNode, selection.anchorOffset), focusConstraint;
|
|
point1 = stepsTranslator.convertDomPointToSteps(selection.anchorNode, selection.anchorOffset, anchorConstraint);
|
|
if(!constraint && (selection.anchorNode === selection.focusNode && selection.anchorOffset === selection.focusOffset)) {
|
|
point2 = point1
|
|
}else {
|
|
focusConstraint = constraint && constraint(selection.focusNode, selection.focusOffset);
|
|
point2 = stepsTranslator.convertDomPointToSteps(selection.focusNode, selection.focusOffset, focusConstraint)
|
|
}
|
|
return{position:point1, length:point2 - point1}
|
|
};
|
|
this.convertCursorToDomRange = function(position, length) {
|
|
var range = getDOMDocument().createRange(), point1, point2;
|
|
point1 = stepsTranslator.convertStepsToDomPoint(position);
|
|
if(length) {
|
|
point2 = stepsTranslator.convertStepsToDomPoint(position + length);
|
|
if(length > 0) {
|
|
range.setStart(point1.node, point1.offset);
|
|
range.setEnd(point2.node, point2.offset)
|
|
}else {
|
|
range.setStart(point2.node, point2.offset);
|
|
range.setEnd(point1.node, point1.offset)
|
|
}
|
|
}else {
|
|
range.setStart(point1.node, point1.offset)
|
|
}
|
|
return range
|
|
};
|
|
function getTextNodeAtStep(steps, memberid) {
|
|
var iterator = getIteratorAtPosition(steps), node = iterator.container(), lastTextNode, nodeOffset = 0, cursorNode = null, text;
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
lastTextNode = (node);
|
|
nodeOffset = (iterator.unfilteredDomOffset());
|
|
if(lastTextNode.length > 0) {
|
|
if(nodeOffset > 0) {
|
|
lastTextNode = lastTextNode.splitText(nodeOffset)
|
|
}
|
|
lastTextNode.parentNode.insertBefore(getDOMDocument().createTextNode(""), lastTextNode);
|
|
lastTextNode = (lastTextNode.previousSibling);
|
|
nodeOffset = 0
|
|
}
|
|
}else {
|
|
lastTextNode = getDOMDocument().createTextNode("");
|
|
nodeOffset = 0;
|
|
node.insertBefore(lastTextNode, iterator.rightNode())
|
|
}
|
|
if(memberid) {
|
|
if(cursors[memberid] && self.getCursorPosition(memberid) === steps) {
|
|
cursorNode = cursors[memberid].getNode();
|
|
while(cursorNode.nextSibling && cursorNode.nextSibling.localName === "cursor") {
|
|
cursorNode.parentNode.insertBefore(cursorNode.nextSibling, cursorNode)
|
|
}
|
|
if(lastTextNode.length > 0 && lastTextNode.nextSibling !== cursorNode) {
|
|
lastTextNode = getDOMDocument().createTextNode("");
|
|
nodeOffset = 0
|
|
}
|
|
cursorNode.parentNode.insertBefore(lastTextNode, cursorNode)
|
|
}
|
|
}else {
|
|
while(lastTextNode.nextSibling && lastTextNode.nextSibling.localName === "cursor") {
|
|
lastTextNode.parentNode.insertBefore(lastTextNode.nextSibling, lastTextNode)
|
|
}
|
|
}
|
|
while(lastTextNode.previousSibling && lastTextNode.previousSibling.nodeType === Node.TEXT_NODE) {
|
|
text = (lastTextNode.previousSibling);
|
|
text.appendData(lastTextNode.data);
|
|
nodeOffset = text.length;
|
|
lastTextNode = text;
|
|
lastTextNode.parentNode.removeChild(lastTextNode.nextSibling)
|
|
}
|
|
while(lastTextNode.nextSibling && lastTextNode.nextSibling.nodeType === Node.TEXT_NODE) {
|
|
text = (lastTextNode.nextSibling);
|
|
lastTextNode.appendData(text.data);
|
|
lastTextNode.parentNode.removeChild(text)
|
|
}
|
|
return{textNode:lastTextNode, offset:nodeOffset}
|
|
}
|
|
function getParagraphElement(node) {
|
|
return odfUtils.getParagraphElement(node)
|
|
}
|
|
function getStyleElement(styleName, styleFamily) {
|
|
return odfCanvas.getFormatting().getStyleElement(styleName, styleFamily)
|
|
}
|
|
this.getStyleElement = getStyleElement;
|
|
function getParagraphStyleElement(styleName) {
|
|
return getStyleElement(styleName, "paragraph")
|
|
}
|
|
function getParagraphStyleAttributes(styleName) {
|
|
var node = getParagraphStyleElement(styleName);
|
|
if(node) {
|
|
return odfCanvas.getFormatting().getInheritedStyleAttributes(node, false)
|
|
}
|
|
return null
|
|
}
|
|
function handleOperationExecuted(op) {
|
|
var spec = op.spec(), memberId = spec.memberid, date = (new Date(spec.timestamp)).toISOString(), odfContainer = odfCanvas.odfContainer(), fullName;
|
|
if(op.isEdit) {
|
|
fullName = self.getMember(memberId).getProperties().fullName;
|
|
odfContainer.setMetadata({"dc:creator":fullName, "dc:date":date}, null);
|
|
if(!lastEditingOp) {
|
|
odfContainer.incrementEditingCycles();
|
|
if(!unsupportedMetadataRemoved) {
|
|
odfContainer.setMetadata(null, ["meta:editing-duration", "meta:document-statistic"])
|
|
}
|
|
}
|
|
lastEditingOp = op
|
|
}
|
|
}
|
|
function upgradeWhitespaceToElement(textNode, offset) {
|
|
runtime.assert(textNode.data[offset] === " ", "upgradeWhitespaceToElement: textNode.data[offset] should be a literal space");
|
|
var space = textNode.ownerDocument.createElementNS(odf.Namespaces.textns, "text:s"), container = textNode.parentNode, adjacentNode = textNode;
|
|
space.appendChild(textNode.ownerDocument.createTextNode(" "));
|
|
if(textNode.length === 1) {
|
|
container.replaceChild(space, textNode)
|
|
}else {
|
|
textNode.deleteData(offset, 1);
|
|
if(offset > 0) {
|
|
if(offset < textNode.length) {
|
|
textNode.splitText(offset)
|
|
}
|
|
adjacentNode = textNode.nextSibling
|
|
}
|
|
container.insertBefore(space, adjacentNode)
|
|
}
|
|
return space
|
|
}
|
|
function upgradeWhitespacesAtPosition(position) {
|
|
var iterator = getIteratorAtPosition(position), container, offset, i;
|
|
iterator.previousPosition();
|
|
iterator.previousPosition();
|
|
for(i = -1;i <= 1;i += 1) {
|
|
container = iterator.container();
|
|
offset = iterator.unfilteredDomOffset();
|
|
if(container.nodeType === Node.TEXT_NODE && (container.data[offset] === " " && odfUtils.isSignificantWhitespace((container), offset))) {
|
|
container = upgradeWhitespaceToElement((container), offset);
|
|
iterator.moveToEndOfNode(container)
|
|
}
|
|
iterator.nextPosition()
|
|
}
|
|
}
|
|
this.upgradeWhitespacesAtPosition = upgradeWhitespacesAtPosition;
|
|
this.downgradeWhitespacesAtPosition = function(position) {
|
|
var iterator = getIteratorAtPosition(position), container, offset, firstSpaceElementChild, lastSpaceElementChild;
|
|
container = iterator.container();
|
|
offset = iterator.unfilteredDomOffset();
|
|
while(!odfUtils.isSpaceElement(container) && container.childNodes.item(offset)) {
|
|
container = (container.childNodes.item(offset));
|
|
offset = 0
|
|
}
|
|
if(container.nodeType === Node.TEXT_NODE) {
|
|
container = (container.parentNode)
|
|
}
|
|
if(odfUtils.isDowngradableSpaceElement(container)) {
|
|
firstSpaceElementChild = container.firstChild;
|
|
lastSpaceElementChild = container.lastChild;
|
|
domUtils.mergeIntoParent(container);
|
|
if(lastSpaceElementChild !== firstSpaceElementChild) {
|
|
domUtils.normalizeTextNodes(lastSpaceElementChild)
|
|
}
|
|
domUtils.normalizeTextNodes(firstSpaceElementChild)
|
|
}
|
|
};
|
|
this.getParagraphStyleElement = getParagraphStyleElement;
|
|
this.getParagraphElement = getParagraphElement;
|
|
this.getParagraphStyleAttributes = getParagraphStyleAttributes;
|
|
this.getTextNodeAtStep = getTextNodeAtStep;
|
|
function paragraphOrRoot(container, offset, root) {
|
|
var node = container.childNodes.item(offset) || container, paragraph = getParagraphElement(node);
|
|
if(paragraph && domUtils.containsNode(root, paragraph)) {
|
|
return(paragraph)
|
|
}
|
|
return root
|
|
}
|
|
this.fixCursorPositions = function() {
|
|
Object.keys(cursors).forEach(function(memberId) {
|
|
var cursor = cursors[memberId], root = getRoot(cursor.getNode()), rootFilter = self.createRootFilter(root), subTree, startPoint, endPoint, selectedRange, cursorMoved = false;
|
|
selectedRange = cursor.getSelectedRange();
|
|
subTree = paragraphOrRoot((selectedRange.startContainer), selectedRange.startOffset, root);
|
|
startPoint = createStepIterator((selectedRange.startContainer), selectedRange.startOffset, [filter, rootFilter], subTree);
|
|
if(!selectedRange.collapsed) {
|
|
subTree = paragraphOrRoot((selectedRange.endContainer), selectedRange.endOffset, root);
|
|
endPoint = createStepIterator((selectedRange.endContainer), selectedRange.endOffset, [filter, rootFilter], subTree)
|
|
}else {
|
|
endPoint = startPoint
|
|
}
|
|
if(!startPoint.isStep() || !endPoint.isStep()) {
|
|
cursorMoved = true;
|
|
runtime.assert(startPoint.roundToClosestStep(), "No walkable step found for cursor owned by " + memberId);
|
|
selectedRange.setStart(startPoint.container(), startPoint.offset());
|
|
runtime.assert(endPoint.roundToClosestStep(), "No walkable step found for cursor owned by " + memberId);
|
|
selectedRange.setEnd(endPoint.container(), endPoint.offset())
|
|
}else {
|
|
if(startPoint.container() === endPoint.container() && startPoint.offset() === endPoint.offset()) {
|
|
if(!selectedRange.collapsed || cursor.getAnchorNode() !== cursor.getNode()) {
|
|
cursorMoved = true;
|
|
selectedRange.setStart(startPoint.container(), startPoint.offset());
|
|
selectedRange.collapse(true)
|
|
}
|
|
}
|
|
}
|
|
if(cursorMoved) {
|
|
cursor.setSelectedRange(selectedRange, cursor.hasForwardSelection());
|
|
self.emit(ops.Document.signalCursorMoved, cursor)
|
|
}
|
|
})
|
|
};
|
|
this.getCursorPosition = function(memberid) {
|
|
var cursor = cursors[memberid];
|
|
return cursor ? stepsTranslator.convertDomPointToSteps(cursor.getNode(), 0) : 0
|
|
};
|
|
this.getCursorSelection = function(memberid) {
|
|
var cursor = cursors[memberid], focusPosition = 0, anchorPosition = 0;
|
|
if(cursor) {
|
|
focusPosition = stepsTranslator.convertDomPointToSteps(cursor.getNode(), 0);
|
|
anchorPosition = stepsTranslator.convertDomPointToSteps(cursor.getAnchorNode(), 0)
|
|
}
|
|
return{position:anchorPosition, length:focusPosition - anchorPosition}
|
|
};
|
|
this.getPositionFilter = function() {
|
|
return filter
|
|
};
|
|
this.getOdfCanvas = function() {
|
|
return odfCanvas
|
|
};
|
|
this.getCanvas = function() {
|
|
return odfCanvas
|
|
};
|
|
this.getRootNode = getRootNode;
|
|
this.addMember = function(member) {
|
|
runtime.assert(members[member.getMemberId()] === undefined, "This member already exists");
|
|
members[member.getMemberId()] = member
|
|
};
|
|
this.getMember = function(memberId) {
|
|
return members.hasOwnProperty(memberId) ? members[memberId] : null
|
|
};
|
|
this.removeMember = function(memberId) {
|
|
delete members[memberId]
|
|
};
|
|
this.getCursor = function(memberid) {
|
|
return cursors[memberid]
|
|
};
|
|
this.getMemberIds = function() {
|
|
var list = [], i;
|
|
for(i in cursors) {
|
|
if(cursors.hasOwnProperty(i)) {
|
|
list.push(cursors[i].getMemberId())
|
|
}
|
|
}
|
|
return list
|
|
};
|
|
this.addCursor = function(cursor) {
|
|
runtime.assert(Boolean(cursor), "OdtDocument::addCursor without cursor");
|
|
var memberid = cursor.getMemberId(), initialSelection = self.convertCursorToDomRange(0, 0);
|
|
runtime.assert(typeof memberid === "string", "OdtDocument::addCursor has cursor without memberid");
|
|
runtime.assert(!cursors[memberid], "OdtDocument::addCursor is adding a duplicate cursor with memberid " + memberid);
|
|
cursor.setSelectedRange(initialSelection, true);
|
|
cursors[memberid] = cursor
|
|
};
|
|
this.removeCursor = function(memberid) {
|
|
var cursor = cursors[memberid];
|
|
if(cursor) {
|
|
cursor.removeFromDocument();
|
|
delete cursors[memberid];
|
|
self.emit(ops.Document.signalCursorRemoved, memberid);
|
|
return true
|
|
}
|
|
return false
|
|
};
|
|
this.moveCursor = function(memberid, position, length, selectionType) {
|
|
var cursor = cursors[memberid], selectionRange = self.convertCursorToDomRange(position, length);
|
|
if(cursor) {
|
|
cursor.setSelectedRange(selectionRange, length >= 0);
|
|
cursor.setSelectionType(selectionType || ops.OdtCursor.RangeSelection)
|
|
}
|
|
};
|
|
this.getFormatting = function() {
|
|
return odfCanvas.getFormatting()
|
|
};
|
|
this.emit = function(eventid, args) {
|
|
eventNotifier.emit(eventid, args)
|
|
};
|
|
this.subscribe = function(eventid, cb) {
|
|
eventNotifier.subscribe(eventid, cb)
|
|
};
|
|
this.unsubscribe = function(eventid, cb) {
|
|
eventNotifier.unsubscribe(eventid, cb)
|
|
};
|
|
this.createRootFilter = function(inputMemberId) {
|
|
return new RootFilter(inputMemberId)
|
|
};
|
|
this.close = function(callback) {
|
|
callback()
|
|
};
|
|
this.destroy = function(callback) {
|
|
callback()
|
|
};
|
|
function init() {
|
|
filter = new ops.TextPositionFilter(getRootNode);
|
|
odfUtils = new odf.OdfUtils;
|
|
domUtils = new core.DomUtils;
|
|
stepsTranslator = new ops.StepsTranslator(getRootNode, gui.SelectionMover.createPositionIterator, filter, 500);
|
|
eventNotifier.subscribe(ops.OdtDocument.signalStepsInserted, stepsTranslator.handleStepsInserted);
|
|
eventNotifier.subscribe(ops.OdtDocument.signalStepsRemoved, stepsTranslator.handleStepsRemoved);
|
|
eventNotifier.subscribe(ops.OdtDocument.signalOperationEnd, handleOperationExecuted)
|
|
}
|
|
init()
|
|
};
|
|
ops.OdtDocument.signalParagraphChanged = "paragraph/changed";
|
|
ops.OdtDocument.signalTableAdded = "table/added";
|
|
ops.OdtDocument.signalCommonStyleCreated = "style/created";
|
|
ops.OdtDocument.signalCommonStyleDeleted = "style/deleted";
|
|
ops.OdtDocument.signalParagraphStyleModified = "paragraphstyle/modified";
|
|
ops.OdtDocument.signalOperationStart = "operation/start";
|
|
ops.OdtDocument.signalOperationEnd = "operation/end";
|
|
ops.OdtDocument.signalProcessingBatchStart = "router/batchstart";
|
|
ops.OdtDocument.signalProcessingBatchEnd = "router/batchend";
|
|
ops.OdtDocument.signalUndoStackChanged = "undo/changed";
|
|
ops.OdtDocument.signalStepsInserted = "steps/inserted";
|
|
ops.OdtDocument.signalStepsRemoved = "steps/removed";
|
|
(function() {
|
|
return ops.OdtDocument
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpAddAnnotation = function OpAddAnnotation() {
|
|
var memberid, timestamp, position, length, name, doc;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = parseInt(data.timestamp, 10);
|
|
position = parseInt(data.position, 10);
|
|
length = parseInt(data.length, 10) || 0;
|
|
name = data.name
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function createAnnotationNode(odtDocument, date) {
|
|
var annotationNode, creatorNode, dateNode, listNode, listItemNode, paragraphNode;
|
|
annotationNode = (doc.createElementNS(odf.Namespaces.officens, "office:annotation"));
|
|
annotationNode.setAttributeNS(odf.Namespaces.officens, "office:name", name);
|
|
creatorNode = doc.createElementNS(odf.Namespaces.dcns, "dc:creator");
|
|
creatorNode.setAttributeNS("urn:webodf:names:editinfo", "editinfo:memberid", memberid);
|
|
creatorNode.textContent = odtDocument.getMember(memberid).getProperties().fullName;
|
|
dateNode = doc.createElementNS(odf.Namespaces.dcns, "dc:date");
|
|
dateNode.appendChild(doc.createTextNode(date.toISOString()));
|
|
listNode = doc.createElementNS(odf.Namespaces.textns, "text:list");
|
|
listItemNode = doc.createElementNS(odf.Namespaces.textns, "text:list-item");
|
|
paragraphNode = doc.createElementNS(odf.Namespaces.textns, "text:p");
|
|
listItemNode.appendChild(paragraphNode);
|
|
listNode.appendChild(listItemNode);
|
|
annotationNode.appendChild(creatorNode);
|
|
annotationNode.appendChild(dateNode);
|
|
annotationNode.appendChild(listNode);
|
|
return annotationNode
|
|
}
|
|
function createAnnotationEnd() {
|
|
var annotationEnd;
|
|
annotationEnd = doc.createElementNS(odf.Namespaces.officens, "office:annotation-end");
|
|
annotationEnd.setAttributeNS(odf.Namespaces.officens, "office:name", name);
|
|
return annotationEnd
|
|
}
|
|
function insertNodeAtPosition(odtDocument, node, insertPosition) {
|
|
var previousNode, parentNode, domPosition = odtDocument.getTextNodeAtStep(insertPosition, memberid);
|
|
if(domPosition) {
|
|
previousNode = domPosition.textNode;
|
|
parentNode = previousNode.parentNode;
|
|
if(domPosition.offset !== previousNode.length) {
|
|
previousNode.splitText(domPosition.offset)
|
|
}
|
|
parentNode.insertBefore(node, previousNode.nextSibling);
|
|
if(previousNode.length === 0) {
|
|
parentNode.removeChild(previousNode)
|
|
}
|
|
}
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), annotation, annotationEnd, cursor = odtDocument.getCursor(memberid), selectedRange, paragraphElement, domUtils = new core.DomUtils;
|
|
doc = odtDocument.getDOMDocument();
|
|
annotation = createAnnotationNode(odtDocument, new Date(timestamp));
|
|
if(length) {
|
|
annotationEnd = createAnnotationEnd();
|
|
annotation.annotationEndElement = annotationEnd;
|
|
insertNodeAtPosition(odtDocument, annotationEnd, position + length)
|
|
}
|
|
insertNodeAtPosition(odtDocument, annotation, position);
|
|
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position, length:length});
|
|
if(cursor) {
|
|
selectedRange = doc.createRange();
|
|
paragraphElement = domUtils.getElementsByTagNameNS(annotation, odf.Namespaces.textns, "p")[0];
|
|
selectedRange.selectNodeContents(paragraphElement);
|
|
cursor.setSelectedRange(selectedRange, false);
|
|
odtDocument.emit(ops.Document.signalCursorMoved, cursor)
|
|
}
|
|
odtDocument.getOdfCanvas().addAnnotation(annotation);
|
|
odtDocument.fixCursorPositions();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"AddAnnotation", memberid:memberid, timestamp:timestamp, position:position, length:length, name:name}
|
|
}
|
|
};
|
|
ops.OpAddAnnotation.Spec;
|
|
ops.OpAddAnnotation.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpAddCursor = function OpAddCursor() {
|
|
var memberid, timestamp;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), cursor = odtDocument.getCursor(memberid);
|
|
if(cursor) {
|
|
return false
|
|
}
|
|
cursor = new ops.OdtCursor(memberid, odtDocument);
|
|
odtDocument.addCursor(cursor);
|
|
odtDocument.emit(ops.Document.signalCursorAdded, cursor);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"AddCursor", memberid:memberid, timestamp:timestamp}
|
|
}
|
|
};
|
|
ops.OpAddCursor.Spec;
|
|
ops.OpAddCursor.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpAddMember = function OpAddMember() {
|
|
var memberid, timestamp, setProperties;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = parseInt(data.timestamp, 10);
|
|
setProperties = data.setProperties
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), member;
|
|
if(odtDocument.getMember(memberid)) {
|
|
return false
|
|
}
|
|
member = new ops.Member(memberid, setProperties);
|
|
odtDocument.addMember(member);
|
|
odtDocument.emit(ops.Document.signalMemberAdded, member);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"AddMember", memberid:memberid, timestamp:timestamp, setProperties:setProperties}
|
|
}
|
|
};
|
|
ops.OpAddMember.Spec;
|
|
ops.OpAddMember.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpAddStyle = function OpAddStyle() {
|
|
var memberid, timestamp, styleName, styleFamily, isAutomaticStyle, setProperties, stylens = odf.Namespaces.stylens;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
styleName = data.styleName;
|
|
styleFamily = data.styleFamily;
|
|
isAutomaticStyle = data.isAutomaticStyle === "true" || data.isAutomaticStyle === true;
|
|
setProperties = data.setProperties
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), odfContainer = odtDocument.getOdfCanvas().odfContainer(), formatting = odtDocument.getFormatting(), dom = odtDocument.getDOMDocument(), styleNode = dom.createElementNS(stylens, "style:style");
|
|
if(!styleNode) {
|
|
return false
|
|
}
|
|
if(setProperties) {
|
|
formatting.updateStyle(styleNode, setProperties)
|
|
}
|
|
styleNode.setAttributeNS(stylens, "style:family", styleFamily);
|
|
styleNode.setAttributeNS(stylens, "style:name", styleName);
|
|
if(isAutomaticStyle) {
|
|
odfContainer.rootElement.automaticStyles.appendChild(styleNode)
|
|
}else {
|
|
odfContainer.rootElement.styles.appendChild(styleNode)
|
|
}
|
|
odtDocument.getOdfCanvas().refreshCSS();
|
|
if(!isAutomaticStyle) {
|
|
odtDocument.emit(ops.OdtDocument.signalCommonStyleCreated, {name:styleName, family:styleFamily})
|
|
}
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"AddStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, styleFamily:styleFamily, isAutomaticStyle:isAutomaticStyle, setProperties:setProperties}
|
|
}
|
|
};
|
|
ops.OpAddStyle.Spec;
|
|
ops.OpAddStyle.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.ObjectNameGenerator = function ObjectNameGenerator(odfContainer, memberId) {
|
|
var stylens = odf.Namespaces.stylens, drawns = odf.Namespaces.drawns, xlinkns = odf.Namespaces.xlinkns, domUtils = new core.DomUtils, utils = new core.Utils, memberIdHash = utils.hashString(memberId), styleNameGenerator = null, frameNameGenerator = null, imageNameGenerator = null, existingFrameNames = {}, existingImageNames = {};
|
|
function NameGenerator(prefix, findExistingNames) {
|
|
var reportedNames = {};
|
|
this.generateName = function() {
|
|
var existingNames = findExistingNames(), startIndex = 0, name;
|
|
do {
|
|
name = prefix + startIndex;
|
|
startIndex += 1
|
|
}while(reportedNames[name] || existingNames[name]);
|
|
reportedNames[name] = true;
|
|
return name
|
|
}
|
|
}
|
|
function getAllStyleNames() {
|
|
var styleElements = [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles], styleNames = {};
|
|
function getStyleNames(styleListElement) {
|
|
var e = styleListElement.firstElementChild;
|
|
while(e) {
|
|
if(e.namespaceURI === stylens && e.localName === "style") {
|
|
styleNames[e.getAttributeNS(stylens, "name")] = true
|
|
}
|
|
e = e.nextElementSibling
|
|
}
|
|
}
|
|
styleElements.forEach(getStyleNames);
|
|
return styleNames
|
|
}
|
|
this.generateStyleName = function() {
|
|
if(styleNameGenerator === null) {
|
|
styleNameGenerator = new NameGenerator("auto" + memberIdHash + "_", function() {
|
|
return getAllStyleNames()
|
|
})
|
|
}
|
|
return styleNameGenerator.generateName()
|
|
};
|
|
this.generateFrameName = function() {
|
|
if(frameNameGenerator === null) {
|
|
var nodes = domUtils.getElementsByTagNameNS(odfContainer.rootElement.body, drawns, "frame");
|
|
nodes.forEach(function(frame) {
|
|
existingFrameNames[frame.getAttributeNS(drawns, "name")] = true
|
|
});
|
|
frameNameGenerator = new NameGenerator("fr" + memberIdHash + "_", function() {
|
|
return existingFrameNames
|
|
})
|
|
}
|
|
return frameNameGenerator.generateName()
|
|
};
|
|
this.generateImageName = function() {
|
|
if(imageNameGenerator === null) {
|
|
var nodes = domUtils.getElementsByTagNameNS(odfContainer.rootElement.body, drawns, "image");
|
|
nodes.forEach(function(image) {
|
|
var path = image.getAttributeNS(xlinkns, "href");
|
|
path = path.substring("Pictures/".length, path.lastIndexOf("."));
|
|
existingImageNames[path] = true
|
|
});
|
|
imageNameGenerator = new NameGenerator("img" + memberIdHash + "_", function() {
|
|
return existingImageNames
|
|
})
|
|
}
|
|
return imageNameGenerator.generateName()
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.TextStyleApplicator = function TextStyleApplicator(objectNameGenerator, formatting, automaticStyles) {
|
|
var domUtils = new core.DomUtils, textns = odf.Namespaces.textns, stylens = odf.Namespaces.stylens, textProperties = "style:text-properties", webodfns = "urn:webodf:names:scope";
|
|
function StyleLookup(info) {
|
|
var cachedAppliedStyles = {};
|
|
function compare(expected, actual) {
|
|
if(typeof expected === "object" && typeof actual === "object") {
|
|
return Object.keys(expected).every(function(key) {
|
|
return compare(expected[key], actual[key])
|
|
})
|
|
}
|
|
return expected === actual
|
|
}
|
|
this.isStyleApplied = function(textNode) {
|
|
var appliedStyle = formatting.getAppliedStylesForElement(textNode, cachedAppliedStyles);
|
|
return compare(info, appliedStyle)
|
|
}
|
|
}
|
|
function StyleManager(info) {
|
|
var createdStyles = {};
|
|
function createDirectFormat(existingStyleName, document) {
|
|
var derivedStyleInfo, derivedStyleNode;
|
|
derivedStyleInfo = existingStyleName ? formatting.createDerivedStyleObject(existingStyleName, "text", info) : info;
|
|
derivedStyleNode = document.createElementNS(stylens, "style:style");
|
|
formatting.updateStyle(derivedStyleNode, derivedStyleInfo);
|
|
derivedStyleNode.setAttributeNS(stylens, "style:name", objectNameGenerator.generateStyleName());
|
|
derivedStyleNode.setAttributeNS(stylens, "style:family", "text");
|
|
derivedStyleNode.setAttributeNS(webodfns, "scope", "document-content");
|
|
automaticStyles.appendChild(derivedStyleNode);
|
|
return derivedStyleNode
|
|
}
|
|
function getDirectStyle(existingStyleName, document) {
|
|
existingStyleName = existingStyleName || "";
|
|
if(!createdStyles.hasOwnProperty(existingStyleName)) {
|
|
createdStyles[existingStyleName] = createDirectFormat(existingStyleName, document)
|
|
}
|
|
return createdStyles[existingStyleName].getAttributeNS(stylens, "name")
|
|
}
|
|
this.applyStyleToContainer = function(container) {
|
|
var name = getDirectStyle(container.getAttributeNS(textns, "style-name"), container.ownerDocument);
|
|
container.setAttributeNS(textns, "text:style-name", name)
|
|
}
|
|
}
|
|
function isTextSpan(node) {
|
|
return node.localName === "span" && node.namespaceURI === textns
|
|
}
|
|
function moveToNewSpan(startNode, limits) {
|
|
var document = startNode.ownerDocument, originalContainer = (startNode.parentNode), styledContainer, trailingContainer, moveTrailing, node, nextNode, loopGuard = new core.LoopWatchDog(1E4), styledNodes = [];
|
|
if(!isTextSpan(originalContainer)) {
|
|
styledContainer = document.createElementNS(textns, "text:span");
|
|
originalContainer.insertBefore(styledContainer, startNode);
|
|
moveTrailing = false
|
|
}else {
|
|
if(startNode.previousSibling && !domUtils.rangeContainsNode(limits, (originalContainer.firstChild))) {
|
|
styledContainer = originalContainer.cloneNode(false);
|
|
originalContainer.parentNode.insertBefore(styledContainer, originalContainer.nextSibling);
|
|
moveTrailing = true
|
|
}else {
|
|
styledContainer = originalContainer;
|
|
moveTrailing = true
|
|
}
|
|
}
|
|
styledNodes.push(startNode);
|
|
node = startNode.nextSibling;
|
|
while(node && domUtils.rangeContainsNode(limits, node)) {
|
|
loopGuard.check();
|
|
styledNodes.push(node);
|
|
node = node.nextSibling
|
|
}
|
|
styledNodes.forEach(function(n) {
|
|
if(n.parentNode !== styledContainer) {
|
|
styledContainer.appendChild(n)
|
|
}
|
|
});
|
|
if(node && moveTrailing) {
|
|
trailingContainer = styledContainer.cloneNode(false);
|
|
styledContainer.parentNode.insertBefore(trailingContainer, styledContainer.nextSibling);
|
|
while(node) {
|
|
loopGuard.check();
|
|
nextNode = node.nextSibling;
|
|
trailingContainer.appendChild(node);
|
|
node = nextNode
|
|
}
|
|
}
|
|
return(styledContainer)
|
|
}
|
|
this.applyStyle = function(textNodes, limits, info) {
|
|
var textPropsOnly = {}, isStyled, container, styleCache, styleLookup;
|
|
runtime.assert(info && info.hasOwnProperty(textProperties), "applyStyle without any text properties");
|
|
textPropsOnly[textProperties] = info[textProperties];
|
|
styleCache = new StyleManager(textPropsOnly);
|
|
styleLookup = new StyleLookup(textPropsOnly);
|
|
function apply(n) {
|
|
isStyled = styleLookup.isStyleApplied(n);
|
|
if(isStyled === false) {
|
|
container = moveToNewSpan(n, limits);
|
|
styleCache.applyStyleToContainer(container)
|
|
}
|
|
}
|
|
textNodes.forEach(apply)
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpApplyDirectStyling = function OpApplyDirectStyling() {
|
|
var memberid, timestamp, position, length, setProperties, odfUtils = new odf.OdfUtils, domUtils = new core.DomUtils;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = parseInt(data.position, 10);
|
|
length = parseInt(data.length, 10);
|
|
setProperties = data.setProperties
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function applyStyle(odtDocument, range, info) {
|
|
var odfCanvas = odtDocument.getOdfCanvas(), odfContainer = odfCanvas.odfContainer(), nextTextNodes = domUtils.splitBoundaries(range), textNodes = odfUtils.getTextNodes(range, false), limits, textStyles;
|
|
limits = {startContainer:range.startContainer, startOffset:range.startOffset, endContainer:range.endContainer, endOffset:range.endOffset};
|
|
textStyles = new odf.TextStyleApplicator(new odf.ObjectNameGenerator((odfContainer), memberid), odtDocument.getFormatting(), odfContainer.rootElement.automaticStyles);
|
|
textStyles.applyStyle(textNodes, limits, info);
|
|
nextTextNodes.forEach(domUtils.normalizeTextNodes)
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), range = odtDocument.convertCursorToDomRange(position, length), impactedParagraphs = odfUtils.getParagraphElements(range);
|
|
applyStyle(odtDocument, range, setProperties);
|
|
range.detach();
|
|
odtDocument.getOdfCanvas().refreshCSS();
|
|
odtDocument.fixCursorPositions();
|
|
impactedParagraphs.forEach(function(n) {
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:n, memberId:memberid, timeStamp:timestamp})
|
|
});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"ApplyDirectStyling", memberid:memberid, timestamp:timestamp, position:position, length:length, setProperties:setProperties}
|
|
}
|
|
};
|
|
ops.OpApplyDirectStyling.Spec;
|
|
ops.OpApplyDirectStyling.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpApplyHyperlink = function OpApplyHyperlink() {
|
|
var memberid, timestamp, position, length, hyperlink, domUtils = new core.DomUtils, odfUtils = new odf.OdfUtils;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
length = data.length;
|
|
hyperlink = data.hyperlink
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function createHyperlink(document, hyperlink) {
|
|
var node = document.createElementNS(odf.Namespaces.textns, "text:a");
|
|
node.setAttributeNS(odf.Namespaces.xlinkns, "xlink:type", "simple");
|
|
node.setAttributeNS(odf.Namespaces.xlinkns, "xlink:href", hyperlink);
|
|
return node
|
|
}
|
|
function isPartOfLink(node) {
|
|
while(node) {
|
|
if(odfUtils.isHyperlink(node)) {
|
|
return true
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
return false
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), ownerDocument = odtDocument.getDOMDocument(), range = odtDocument.convertCursorToDomRange(position, length), boundaryNodes = domUtils.splitBoundaries(range), modifiedParagraphs = [], textNodes = odfUtils.getTextNodes(range, false);
|
|
if(textNodes.length === 0) {
|
|
return false
|
|
}
|
|
textNodes.forEach(function(node) {
|
|
var linkNode, paragraph = odfUtils.getParagraphElement(node);
|
|
runtime.assert(isPartOfLink(node) === false, "The given range should not contain any link.");
|
|
linkNode = createHyperlink(ownerDocument, hyperlink);
|
|
node.parentNode.insertBefore(linkNode, node);
|
|
linkNode.appendChild(node);
|
|
if(modifiedParagraphs.indexOf(paragraph) === -1) {
|
|
modifiedParagraphs.push(paragraph)
|
|
}
|
|
});
|
|
boundaryNodes.forEach(domUtils.normalizeTextNodes);
|
|
range.detach();
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
modifiedParagraphs.forEach(function(paragraph) {
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraph, memberId:memberid, timeStamp:timestamp})
|
|
});
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"ApplyHyperlink", memberid:memberid, timestamp:timestamp, position:position, length:length, hyperlink:hyperlink}
|
|
}
|
|
};
|
|
ops.OpApplyHyperlink.Spec;
|
|
ops.OpApplyHyperlink.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpInsertImage = function OpInsertImage() {
|
|
var memberid, timestamp, position, filename, frameWidth, frameHeight, frameStyleName, frameName, drawns = odf.Namespaces.drawns, svgns = odf.Namespaces.svgns, textns = odf.Namespaces.textns, xlinkns = odf.Namespaces.xlinkns;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
filename = data.filename;
|
|
frameWidth = data.frameWidth;
|
|
frameHeight = data.frameHeight;
|
|
frameStyleName = data.frameStyleName;
|
|
frameName = data.frameName
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function createFrameElement(document) {
|
|
var imageNode = document.createElementNS(drawns, "draw:image"), frameNode = document.createElementNS(drawns, "draw:frame");
|
|
imageNode.setAttributeNS(xlinkns, "xlink:href", filename);
|
|
imageNode.setAttributeNS(xlinkns, "xlink:type", "simple");
|
|
imageNode.setAttributeNS(xlinkns, "xlink:show", "embed");
|
|
imageNode.setAttributeNS(xlinkns, "xlink:actuate", "onLoad");
|
|
frameNode.setAttributeNS(drawns, "draw:style-name", frameStyleName);
|
|
frameNode.setAttributeNS(drawns, "draw:name", frameName);
|
|
frameNode.setAttributeNS(textns, "text:anchor-type", "as-char");
|
|
frameNode.setAttributeNS(svgns, "svg:width", frameWidth);
|
|
frameNode.setAttributeNS(svgns, "svg:height", frameHeight);
|
|
frameNode.appendChild(imageNode);
|
|
return frameNode
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), odfCanvas = odtDocument.getOdfCanvas(), domPosition = odtDocument.getTextNodeAtStep(position, memberid), textNode, refNode, paragraphElement, frameElement;
|
|
if(!domPosition) {
|
|
return false
|
|
}
|
|
textNode = domPosition.textNode;
|
|
paragraphElement = odtDocument.getParagraphElement(textNode);
|
|
refNode = domPosition.offset !== textNode.length ? textNode.splitText(domPosition.offset) : textNode.nextSibling;
|
|
frameElement = createFrameElement(odtDocument.getDOMDocument());
|
|
textNode.parentNode.insertBefore(frameElement, refNode);
|
|
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position, length:1});
|
|
if(textNode.length === 0) {
|
|
textNode.parentNode.removeChild(textNode)
|
|
}
|
|
odfCanvas.addCssForFrameWithImage(frameElement);
|
|
odfCanvas.refreshCSS();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphElement, memberId:memberid, timeStamp:timestamp});
|
|
odfCanvas.rerenderAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"InsertImage", memberid:memberid, timestamp:timestamp, filename:filename, position:position, frameWidth:frameWidth, frameHeight:frameHeight, frameStyleName:frameStyleName, frameName:frameName}
|
|
}
|
|
};
|
|
ops.OpInsertImage.Spec;
|
|
ops.OpInsertImage.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpInsertTable = function OpInsertTable() {
|
|
var memberid, timestamp, initialRows, initialColumns, position, tableName, tableStyleName, tableColumnStyleName, tableCellStyleMatrix, tablens = "urn:oasis:names:tc:opendocument:xmlns:table:1.0", textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
initialRows = data.initialRows;
|
|
initialColumns = data.initialColumns;
|
|
tableName = data.tableName;
|
|
tableStyleName = data.tableStyleName;
|
|
tableColumnStyleName = data.tableColumnStyleName;
|
|
tableCellStyleMatrix = data.tableCellStyleMatrix
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function getCellStyleName(row, column) {
|
|
var rowStyles;
|
|
if(tableCellStyleMatrix.length === 1) {
|
|
rowStyles = tableCellStyleMatrix[0]
|
|
}else {
|
|
if(tableCellStyleMatrix.length === 3) {
|
|
switch(row) {
|
|
case 0:
|
|
rowStyles = tableCellStyleMatrix[0];
|
|
break;
|
|
case initialRows - 1:
|
|
rowStyles = tableCellStyleMatrix[2];
|
|
break;
|
|
default:
|
|
rowStyles = tableCellStyleMatrix[1];
|
|
break
|
|
}
|
|
}else {
|
|
rowStyles = tableCellStyleMatrix[row]
|
|
}
|
|
}
|
|
if(rowStyles.length === 1) {
|
|
return rowStyles[0]
|
|
}
|
|
if(rowStyles.length === 3) {
|
|
switch(column) {
|
|
case 0:
|
|
return rowStyles[0];
|
|
case initialColumns - 1:
|
|
return rowStyles[2];
|
|
default:
|
|
return rowStyles[1]
|
|
}
|
|
}
|
|
return rowStyles[column]
|
|
}
|
|
function createTableNode(document) {
|
|
var tableNode = document.createElementNS(tablens, "table:table"), columns = document.createElementNS(tablens, "table:table-column"), row, cell, paragraph, rowCounter, columnCounter, cellStyleName;
|
|
if(tableStyleName) {
|
|
tableNode.setAttributeNS(tablens, "table:style-name", tableStyleName)
|
|
}
|
|
if(tableName) {
|
|
tableNode.setAttributeNS(tablens, "table:name", tableName)
|
|
}
|
|
columns.setAttributeNS(tablens, "table:number-columns-repeated", initialColumns);
|
|
if(tableColumnStyleName) {
|
|
columns.setAttributeNS(tablens, "table:style-name", tableColumnStyleName)
|
|
}
|
|
tableNode.appendChild(columns);
|
|
for(rowCounter = 0;rowCounter < initialRows;rowCounter += 1) {
|
|
row = document.createElementNS(tablens, "table:table-row");
|
|
for(columnCounter = 0;columnCounter < initialColumns;columnCounter += 1) {
|
|
cell = document.createElementNS(tablens, "table:table-cell");
|
|
cellStyleName = getCellStyleName(rowCounter, columnCounter);
|
|
if(cellStyleName) {
|
|
cell.setAttributeNS(tablens, "table:style-name", cellStyleName)
|
|
}
|
|
paragraph = document.createElementNS(textns, "text:p");
|
|
cell.appendChild(paragraph);
|
|
row.appendChild(cell)
|
|
}
|
|
tableNode.appendChild(row)
|
|
}
|
|
return tableNode
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), domPosition = odtDocument.getTextNodeAtStep(position), rootNode = odtDocument.getRootNode(), previousSibling, tableNode;
|
|
if(domPosition) {
|
|
tableNode = createTableNode(odtDocument.getDOMDocument());
|
|
previousSibling = odtDocument.getParagraphElement(domPosition.textNode);
|
|
rootNode.insertBefore(tableNode, previousSibling.nextSibling);
|
|
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position, length:initialColumns * initialRows + 1});
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalTableAdded, {tableElement:tableNode, memberId:memberid, timeStamp:timestamp});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
}
|
|
return false
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"InsertTable", memberid:memberid, timestamp:timestamp, position:position, initialRows:initialRows, initialColumns:initialColumns, tableName:tableName, tableStyleName:tableStyleName, tableColumnStyleName:tableColumnStyleName, tableCellStyleMatrix:tableCellStyleMatrix}
|
|
}
|
|
};
|
|
ops.OpInsertTable.Spec;
|
|
ops.OpInsertTable.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpInsertText = function OpInsertText() {
|
|
var space = " ", tab = "\t", memberid, timestamp, position, moveCursor, text;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
text = data.text;
|
|
moveCursor = data.moveCursor === "true" || data.moveCursor === true
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function triggerLayoutInWebkit(textNode) {
|
|
var parent = textNode.parentNode, next = textNode.nextSibling;
|
|
parent.removeChild(textNode);
|
|
parent.insertBefore(textNode, next)
|
|
}
|
|
function requiresSpaceElement(text, index) {
|
|
return text[index] === space && (index === 0 || (index === text.length - 1 || text[index - 1] === space))
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), domPosition, previousNode, parentElement, nextNode = null, ownerDocument = odtDocument.getDOMDocument(), paragraphElement, textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0", toInsertIndex = 0, spaceTag, spaceElement, cursor = odtDocument.getCursor(memberid), i;
|
|
function insertTextNode(toInsertText) {
|
|
parentElement.insertBefore(ownerDocument.createTextNode(toInsertText), nextNode)
|
|
}
|
|
odtDocument.upgradeWhitespacesAtPosition(position);
|
|
domPosition = odtDocument.getTextNodeAtStep(position);
|
|
if(domPosition) {
|
|
previousNode = domPosition.textNode;
|
|
nextNode = previousNode.nextSibling;
|
|
parentElement = (previousNode.parentNode);
|
|
paragraphElement = odtDocument.getParagraphElement(previousNode);
|
|
for(i = 0;i < text.length;i += 1) {
|
|
if(requiresSpaceElement(text, i) || text[i] === tab) {
|
|
if(toInsertIndex === 0) {
|
|
if(domPosition.offset !== previousNode.length) {
|
|
nextNode = previousNode.splitText(domPosition.offset)
|
|
}
|
|
if(0 < i) {
|
|
previousNode.appendData(text.substring(0, i))
|
|
}
|
|
}else {
|
|
if(toInsertIndex < i) {
|
|
insertTextNode(text.substring(toInsertIndex, i))
|
|
}
|
|
}
|
|
toInsertIndex = i + 1;
|
|
spaceTag = text[i] === space ? "text:s" : "text:tab";
|
|
spaceElement = ownerDocument.createElementNS(textns, spaceTag);
|
|
spaceElement.appendChild(ownerDocument.createTextNode(text[i]));
|
|
parentElement.insertBefore(spaceElement, nextNode)
|
|
}
|
|
}
|
|
if(toInsertIndex === 0) {
|
|
previousNode.insertData(domPosition.offset, text)
|
|
}else {
|
|
if(toInsertIndex < text.length) {
|
|
insertTextNode(text.substring(toInsertIndex))
|
|
}
|
|
}
|
|
triggerLayoutInWebkit(previousNode);
|
|
if(previousNode.length === 0) {
|
|
previousNode.parentNode.removeChild(previousNode)
|
|
}
|
|
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position, length:text.length});
|
|
if(cursor && moveCursor) {
|
|
odtDocument.moveCursor(memberid, position + text.length, 0);
|
|
odtDocument.emit(ops.Document.signalCursorMoved, cursor)
|
|
}
|
|
if(position > 0) {
|
|
if(position > 1) {
|
|
odtDocument.downgradeWhitespacesAtPosition(position - 2)
|
|
}
|
|
odtDocument.downgradeWhitespacesAtPosition(position - 1)
|
|
}
|
|
odtDocument.downgradeWhitespacesAtPosition(position);
|
|
odtDocument.downgradeWhitespacesAtPosition(position + text.length - 1);
|
|
odtDocument.downgradeWhitespacesAtPosition(position + text.length);
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphElement, memberId:memberid, timeStamp:timestamp});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
}
|
|
return false
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"InsertText", memberid:memberid, timestamp:timestamp, position:position, text:text, moveCursor:moveCursor}
|
|
}
|
|
};
|
|
ops.OpInsertText.Spec;
|
|
ops.OpInsertText.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpMoveCursor = function OpMoveCursor() {
|
|
var memberid, timestamp, position, length, selectionType;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
length = data.length || 0;
|
|
selectionType = data.selectionType || ops.OdtCursor.RangeSelection
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), cursor = odtDocument.getCursor(memberid), selectedRange;
|
|
if(!cursor) {
|
|
return false
|
|
}
|
|
selectedRange = odtDocument.convertCursorToDomRange(position, length);
|
|
cursor.setSelectedRange(selectedRange, length >= 0);
|
|
cursor.setSelectionType(selectionType);
|
|
odtDocument.emit(ops.Document.signalCursorMoved, cursor);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"MoveCursor", memberid:memberid, timestamp:timestamp, position:position, length:length, selectionType:selectionType}
|
|
}
|
|
};
|
|
ops.OpMoveCursor.Spec;
|
|
ops.OpMoveCursor.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveAnnotation = function OpRemoveAnnotation() {
|
|
var memberid, timestamp, position, length, domUtils;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = parseInt(data.position, 10);
|
|
length = parseInt(data.length, 10);
|
|
domUtils = new core.DomUtils
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), iterator = odtDocument.getIteratorAtPosition(position), container = iterator.container(), annotationNode, annotationEnd;
|
|
while(!(container.namespaceURI === odf.Namespaces.officens && container.localName === "annotation")) {
|
|
container = container.parentNode
|
|
}
|
|
if(container === null) {
|
|
return false
|
|
}
|
|
annotationNode = (container);
|
|
annotationEnd = annotationNode.annotationEndElement;
|
|
odtDocument.getOdfCanvas().forgetAnnotations();
|
|
function insert(node) {
|
|
(annotationNode).parentNode.insertBefore(node, annotationNode)
|
|
}
|
|
domUtils.getElementsByTagNameNS(annotationNode, "urn:webodf:names:cursor", "cursor").forEach(insert);
|
|
domUtils.getElementsByTagNameNS(annotationNode, "urn:webodf:names:cursor", "anchor").forEach(insert);
|
|
annotationNode.parentNode.removeChild(annotationNode);
|
|
if(annotationEnd) {
|
|
annotationEnd.parentNode.removeChild(annotationEnd)
|
|
}
|
|
odtDocument.emit(ops.OdtDocument.signalStepsRemoved, {position:position > 0 ? position - 1 : position, length:length});
|
|
odtDocument.fixCursorPositions();
|
|
odtDocument.getOdfCanvas().refreshAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveAnnotation", memberid:memberid, timestamp:timestamp, position:position, length:length}
|
|
}
|
|
};
|
|
ops.OpRemoveAnnotation.Spec;
|
|
ops.OpRemoveAnnotation.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveBlob = function OpRemoveBlob() {
|
|
var memberid, timestamp, filename;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
filename = data.filename
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document);
|
|
odtDocument.getOdfCanvas().odfContainer().removeBlob(filename);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveBlob", memberid:memberid, timestamp:timestamp, filename:filename}
|
|
}
|
|
};
|
|
ops.OpRemoveBlob.Spec;
|
|
ops.OpRemoveBlob.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveCursor = function OpRemoveCursor() {
|
|
var memberid, timestamp;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document);
|
|
if(!odtDocument.removeCursor(memberid)) {
|
|
return false
|
|
}
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveCursor", memberid:memberid, timestamp:timestamp}
|
|
}
|
|
};
|
|
ops.OpRemoveCursor.Spec;
|
|
ops.OpRemoveCursor.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveHyperlink = function OpRemoveHyperlink() {
|
|
var memberid, timestamp, position, length, domUtils = new core.DomUtils, odfUtils = new odf.OdfUtils;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
length = data.length
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), range = odtDocument.convertCursorToDomRange(position, length), links = odfUtils.getHyperlinkElements(range), node;
|
|
runtime.assert(links.length === 1, "The given range should only contain a single link.");
|
|
node = domUtils.mergeIntoParent((links[0]));
|
|
range.detach();
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:odfUtils.getParagraphElement(node), memberId:memberid, timeStamp:timestamp});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveHyperlink", memberid:memberid, timestamp:timestamp, position:position, length:length}
|
|
}
|
|
};
|
|
ops.OpRemoveHyperlink.Spec;
|
|
ops.OpRemoveHyperlink.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveMember = function OpRemoveMember() {
|
|
var memberid, timestamp;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = parseInt(data.timestamp, 10)
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document);
|
|
if(!odtDocument.getMember(memberid)) {
|
|
return false
|
|
}
|
|
odtDocument.removeMember(memberid);
|
|
odtDocument.emit(ops.Document.signalMemberRemoved, memberid);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveMember", memberid:memberid, timestamp:timestamp}
|
|
}
|
|
};
|
|
ops.OpRemoveMember.Spec;
|
|
ops.OpRemoveMember.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveStyle = function OpRemoveStyle() {
|
|
var memberid, timestamp, styleName, styleFamily;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
styleName = data.styleName;
|
|
styleFamily = data.styleFamily
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), styleNode = odtDocument.getStyleElement(styleName, styleFamily);
|
|
if(!styleNode) {
|
|
return false
|
|
}
|
|
styleNode.parentNode.removeChild(styleNode);
|
|
odtDocument.getOdfCanvas().refreshCSS();
|
|
odtDocument.emit(ops.OdtDocument.signalCommonStyleDeleted, {name:styleName, family:styleFamily});
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, styleFamily:styleFamily}
|
|
}
|
|
};
|
|
ops.OpRemoveStyle.Spec;
|
|
ops.OpRemoveStyle.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpRemoveText = function OpRemoveText() {
|
|
var memberid, timestamp, position, length, odfUtils, domUtils, editinfons = "urn:webodf:names:editinfo", odfNodeNamespaceMap = {};
|
|
this.init = function(data) {
|
|
runtime.assert(data.length >= 0, "OpRemoveText only supports positive lengths");
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = parseInt(data.position, 10);
|
|
length = parseInt(data.length, 10);
|
|
odfUtils = new odf.OdfUtils;
|
|
domUtils = new core.DomUtils;
|
|
odfNodeNamespaceMap[odf.Namespaces.dbns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.dcns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.dr3dns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.drawns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.chartns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.formns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.numberns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.officens] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.presentationns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.stylens] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.svgns] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.tablens] = true;
|
|
odfNodeNamespaceMap[odf.Namespaces.textns] = true
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
function CollapsingRules(rootNode) {
|
|
function isOdfNode(node) {
|
|
return odfNodeNamespaceMap.hasOwnProperty(node.namespaceURI)
|
|
}
|
|
function shouldRemove(node) {
|
|
return isOdfNode(node) || (node.localName === "br" && odfUtils.isLineBreak(node.parentNode) || node.nodeType === Node.TEXT_NODE && isOdfNode((node.parentNode)))
|
|
}
|
|
function isEmpty(node) {
|
|
var childNode;
|
|
if(odfUtils.isCharacterElement(node)) {
|
|
return false
|
|
}
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
return node.textContent.length === 0
|
|
}
|
|
childNode = node.firstChild;
|
|
while(childNode) {
|
|
if(isOdfNode(childNode) || !isEmpty(childNode)) {
|
|
return false
|
|
}
|
|
childNode = childNode.nextSibling
|
|
}
|
|
return true
|
|
}
|
|
this.isEmpty = isEmpty;
|
|
function isCollapsibleContainer(node) {
|
|
return!odfUtils.isParagraph(node) && (node !== rootNode && isEmpty(node))
|
|
}
|
|
function mergeChildrenIntoParent(targetNode) {
|
|
var parent;
|
|
if(targetNode.nodeType === Node.TEXT_NODE) {
|
|
parent = targetNode.parentNode;
|
|
parent.removeChild(targetNode)
|
|
}else {
|
|
parent = domUtils.removeUnwantedNodes(targetNode, shouldRemove)
|
|
}
|
|
if(parent && isCollapsibleContainer(parent)) {
|
|
return mergeChildrenIntoParent(parent)
|
|
}
|
|
return parent
|
|
}
|
|
this.mergeChildrenIntoParent = mergeChildrenIntoParent
|
|
}
|
|
function mergeParagraphs(first, second, collapseRules) {
|
|
var child, destination = first, source = second, secondParent, insertionPoint = null;
|
|
if(collapseRules.isEmpty(first)) {
|
|
if(second.parentNode !== first.parentNode) {
|
|
secondParent = second.parentNode;
|
|
first.parentNode.insertBefore(second, first.nextSibling)
|
|
}
|
|
source = first;
|
|
destination = second;
|
|
insertionPoint = destination.getElementsByTagNameNS(editinfons, "editinfo").item(0) || destination.firstChild
|
|
}
|
|
while(source.firstChild) {
|
|
child = source.firstChild;
|
|
source.removeChild(child);
|
|
if(child.localName !== "editinfo") {
|
|
destination.insertBefore(child, insertionPoint)
|
|
}
|
|
}
|
|
if(secondParent && collapseRules.isEmpty(secondParent)) {
|
|
collapseRules.mergeChildrenIntoParent(secondParent)
|
|
}
|
|
collapseRules.mergeChildrenIntoParent(source);
|
|
return destination
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), paragraphElement, destinationParagraph, range, textNodes, paragraphs, cursor = odtDocument.getCursor(memberid), collapseRules = new CollapsingRules(odtDocument.getRootNode());
|
|
odtDocument.upgradeWhitespacesAtPosition(position);
|
|
odtDocument.upgradeWhitespacesAtPosition(position + length);
|
|
range = odtDocument.convertCursorToDomRange(position, length);
|
|
domUtils.splitBoundaries(range);
|
|
paragraphElement = odtDocument.getParagraphElement(range.startContainer);
|
|
textNodes = odfUtils.getTextElements(range, false, true);
|
|
paragraphs = odfUtils.getParagraphElements(range);
|
|
range.detach();
|
|
textNodes.forEach(function(element) {
|
|
collapseRules.mergeChildrenIntoParent(element)
|
|
});
|
|
function merge(destination, paragraph) {
|
|
return mergeParagraphs(destination, paragraph, collapseRules)
|
|
}
|
|
destinationParagraph = paragraphs.reduce(merge);
|
|
odtDocument.emit(ops.OdtDocument.signalStepsRemoved, {position:position, length:length});
|
|
odtDocument.downgradeWhitespacesAtPosition(position);
|
|
odtDocument.fixCursorPositions();
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:destinationParagraph || paragraphElement, memberId:memberid, timeStamp:timestamp});
|
|
if(cursor) {
|
|
cursor.resetSelectionType();
|
|
odtDocument.emit(ops.Document.signalCursorMoved, cursor)
|
|
}
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"RemoveText", memberid:memberid, timestamp:timestamp, position:position, length:length}
|
|
}
|
|
};
|
|
ops.OpRemoveText.Spec;
|
|
ops.OpRemoveText.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpSetBlob = function OpSetBlob() {
|
|
var memberid, timestamp, filename, mimetype, content;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
filename = data.filename;
|
|
mimetype = data.mimetype;
|
|
content = data.content
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document);
|
|
odtDocument.getOdfCanvas().odfContainer().setBlob(filename, mimetype, content);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"SetBlob", memberid:memberid, timestamp:timestamp, filename:filename, mimetype:mimetype, content:content}
|
|
}
|
|
};
|
|
ops.OpSetBlob.Spec;
|
|
ops.OpSetBlob.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpSetParagraphStyle = function OpSetParagraphStyle() {
|
|
var memberid, timestamp, position, styleName, textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0";
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
styleName = data.styleName
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), iterator, paragraphNode;
|
|
iterator = odtDocument.getIteratorAtPosition(position);
|
|
paragraphNode = odtDocument.getParagraphElement(iterator.container());
|
|
if(paragraphNode) {
|
|
if(styleName !== "") {
|
|
paragraphNode.setAttributeNS(textns, "text:style-name", styleName)
|
|
}else {
|
|
paragraphNode.removeAttributeNS(textns, "style-name")
|
|
}
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphNode, timeStamp:timestamp, memberId:memberid});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
}
|
|
return false
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"SetParagraphStyle", memberid:memberid, timestamp:timestamp, position:position, styleName:styleName}
|
|
}
|
|
};
|
|
ops.OpSetParagraphStyle.Spec;
|
|
ops.OpSetParagraphStyle.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpSplitParagraph = function OpSplitParagraph() {
|
|
var memberid, timestamp, position, moveCursor, odfUtils;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
position = data.position;
|
|
moveCursor = data.moveCursor === "true" || data.moveCursor === true;
|
|
odfUtils = new odf.OdfUtils
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), domPosition, paragraphNode, targetNode, node, splitNode, splitChildNode, keptChildNode, cursor = odtDocument.getCursor(memberid);
|
|
odtDocument.upgradeWhitespacesAtPosition(position);
|
|
domPosition = odtDocument.getTextNodeAtStep(position);
|
|
if(!domPosition) {
|
|
return false
|
|
}
|
|
paragraphNode = odtDocument.getParagraphElement(domPosition.textNode);
|
|
if(!paragraphNode) {
|
|
return false
|
|
}
|
|
if(odfUtils.isListItem(paragraphNode.parentNode)) {
|
|
targetNode = paragraphNode.parentNode
|
|
}else {
|
|
targetNode = paragraphNode
|
|
}
|
|
if(domPosition.offset === 0) {
|
|
keptChildNode = domPosition.textNode.previousSibling;
|
|
splitChildNode = null
|
|
}else {
|
|
keptChildNode = domPosition.textNode;
|
|
if(domPosition.offset >= domPosition.textNode.length) {
|
|
splitChildNode = null
|
|
}else {
|
|
splitChildNode = (domPosition.textNode.splitText(domPosition.offset))
|
|
}
|
|
}
|
|
node = domPosition.textNode;
|
|
while(node !== targetNode) {
|
|
node = node.parentNode;
|
|
splitNode = node.cloneNode(false);
|
|
if(splitChildNode) {
|
|
splitNode.appendChild(splitChildNode)
|
|
}
|
|
if(keptChildNode) {
|
|
while(keptChildNode && keptChildNode.nextSibling) {
|
|
splitNode.appendChild(keptChildNode.nextSibling)
|
|
}
|
|
}else {
|
|
while(node.firstChild) {
|
|
splitNode.appendChild(node.firstChild)
|
|
}
|
|
}
|
|
node.parentNode.insertBefore(splitNode, node.nextSibling);
|
|
keptChildNode = node;
|
|
splitChildNode = splitNode
|
|
}
|
|
if(odfUtils.isListItem(splitChildNode)) {
|
|
splitChildNode = splitChildNode.childNodes.item(0)
|
|
}
|
|
if(domPosition.textNode.length === 0) {
|
|
domPosition.textNode.parentNode.removeChild(domPosition.textNode)
|
|
}
|
|
odtDocument.emit(ops.OdtDocument.signalStepsInserted, {position:position, length:1});
|
|
if(cursor && moveCursor) {
|
|
odtDocument.moveCursor(memberid, position + 1, 0);
|
|
odtDocument.emit(ops.Document.signalCursorMoved, cursor)
|
|
}
|
|
odtDocument.fixCursorPositions();
|
|
odtDocument.getOdfCanvas().refreshSize();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:paragraphNode, memberId:memberid, timeStamp:timestamp});
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphChanged, {paragraphElement:splitChildNode, memberId:memberid, timeStamp:timestamp});
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"SplitParagraph", memberid:memberid, timestamp:timestamp, position:position, moveCursor:moveCursor}
|
|
}
|
|
};
|
|
ops.OpSplitParagraph.Spec;
|
|
ops.OpSplitParagraph.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpUpdateMember = function OpUpdateMember() {
|
|
var memberid, timestamp, setProperties, removedProperties;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = parseInt(data.timestamp, 10);
|
|
setProperties = data.setProperties;
|
|
removedProperties = data.removedProperties
|
|
};
|
|
this.isEdit = false;
|
|
this.group = undefined;
|
|
function updateCreators(doc) {
|
|
var xpath = xmldom.XPath, xp = "//dc:creator[@editinfo:memberid='" + memberid + "']", creators = xpath.getODFElementsWithXPath(doc.getRootNode(), xp, function(prefix) {
|
|
if(prefix === "editinfo") {
|
|
return"urn:webodf:names:editinfo"
|
|
}
|
|
return odf.Namespaces.lookupNamespaceURI(prefix)
|
|
}), i;
|
|
for(i = 0;i < creators.length;i += 1) {
|
|
creators[i].textContent = setProperties.fullName
|
|
}
|
|
}
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), member = odtDocument.getMember(memberid);
|
|
if(!member) {
|
|
return false
|
|
}
|
|
if(removedProperties) {
|
|
member.removeProperties(removedProperties)
|
|
}
|
|
if(setProperties) {
|
|
member.setProperties(setProperties);
|
|
if(setProperties.fullName) {
|
|
updateCreators(odtDocument)
|
|
}
|
|
}
|
|
odtDocument.emit(ops.Document.signalMemberUpdated, member);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"UpdateMember", memberid:memberid, timestamp:timestamp, setProperties:setProperties, removedProperties:removedProperties}
|
|
}
|
|
};
|
|
ops.OpUpdateMember.Spec;
|
|
ops.OpUpdateMember.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpUpdateMetadata = function OpUpdateMetadata() {
|
|
var memberid, timestamp, setProperties, removedProperties;
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = parseInt(data.timestamp, 10);
|
|
setProperties = data.setProperties;
|
|
removedProperties = data.removedProperties
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), odfContainer = odtDocument.getOdfCanvas().odfContainer(), removedPropertiesArray = [];
|
|
if(removedProperties) {
|
|
removedPropertiesArray = removedProperties.attributes.split(",")
|
|
}
|
|
odfContainer.setMetadata(setProperties, removedPropertiesArray);
|
|
return true
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"UpdateMetadata", memberid:memberid, timestamp:timestamp, setProperties:setProperties, removedProperties:removedProperties}
|
|
}
|
|
};
|
|
ops.OpUpdateMetadata.Spec;
|
|
ops.OpUpdateMetadata.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OpUpdateParagraphStyle = function OpUpdateParagraphStyle() {
|
|
var memberid, timestamp, styleName, setProperties, removedProperties, paragraphPropertiesName = "style:paragraph-properties", textPropertiesName = "style:text-properties", stylens = odf.Namespaces.stylens;
|
|
function removedAttributesFromStyleNode(node, removedAttributeNames) {
|
|
var i, attributeNameParts, attributeNameList = removedAttributeNames ? removedAttributeNames.split(",") : [];
|
|
for(i = 0;i < attributeNameList.length;i += 1) {
|
|
attributeNameParts = attributeNameList[i].split(":");
|
|
node.removeAttributeNS((odf.Namespaces.lookupNamespaceURI(attributeNameParts[0])), attributeNameParts[1])
|
|
}
|
|
}
|
|
this.init = function(data) {
|
|
memberid = data.memberid;
|
|
timestamp = data.timestamp;
|
|
styleName = data.styleName;
|
|
setProperties = data.setProperties;
|
|
removedProperties = data.removedProperties
|
|
};
|
|
this.isEdit = true;
|
|
this.group = undefined;
|
|
this.execute = function(document) {
|
|
var odtDocument = (document), formatting = odtDocument.getFormatting(), styleNode, object, paragraphPropertiesNode, textPropertiesNode;
|
|
if(styleName !== "") {
|
|
styleNode = odtDocument.getParagraphStyleElement(styleName)
|
|
}else {
|
|
styleNode = formatting.getDefaultStyleElement("paragraph")
|
|
}
|
|
if(styleNode) {
|
|
paragraphPropertiesNode = (styleNode.getElementsByTagNameNS(stylens, "paragraph-properties").item(0));
|
|
textPropertiesNode = (styleNode.getElementsByTagNameNS(stylens, "text-properties").item(0));
|
|
if(setProperties) {
|
|
formatting.updateStyle(styleNode, setProperties)
|
|
}
|
|
if(removedProperties) {
|
|
object = (removedProperties[paragraphPropertiesName]);
|
|
if(paragraphPropertiesNode && object) {
|
|
removedAttributesFromStyleNode(paragraphPropertiesNode, object.attributes);
|
|
if(paragraphPropertiesNode.attributes.length === 0) {
|
|
styleNode.removeChild(paragraphPropertiesNode)
|
|
}
|
|
}
|
|
object = (removedProperties[textPropertiesName]);
|
|
if(textPropertiesNode && object) {
|
|
removedAttributesFromStyleNode(textPropertiesNode, object.attributes);
|
|
if(textPropertiesNode.attributes.length === 0) {
|
|
styleNode.removeChild(textPropertiesNode)
|
|
}
|
|
}
|
|
removedAttributesFromStyleNode(styleNode, removedProperties.attributes)
|
|
}
|
|
odtDocument.getOdfCanvas().refreshCSS();
|
|
odtDocument.emit(ops.OdtDocument.signalParagraphStyleModified, styleName);
|
|
odtDocument.getOdfCanvas().rerenderAnnotations();
|
|
return true
|
|
}
|
|
return false
|
|
};
|
|
this.spec = function() {
|
|
return{optype:"UpdateParagraphStyle", memberid:memberid, timestamp:timestamp, styleName:styleName, setProperties:setProperties, removedProperties:removedProperties}
|
|
}
|
|
};
|
|
ops.OpUpdateParagraphStyle.Spec;
|
|
ops.OpUpdateParagraphStyle.InitSpec;
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OperationFactory = function OperationFactory() {
|
|
var specs;
|
|
this.register = function(specName, specConstructor) {
|
|
specs[specName] = specConstructor
|
|
};
|
|
this.create = function(spec) {
|
|
var op = null, Constructor = specs[spec.optype];
|
|
if(Constructor) {
|
|
op = new Constructor;
|
|
op.init(spec)
|
|
}
|
|
return op
|
|
};
|
|
function init() {
|
|
specs = {AddMember:ops.OpAddMember, UpdateMember:ops.OpUpdateMember, RemoveMember:ops.OpRemoveMember, AddCursor:ops.OpAddCursor, ApplyDirectStyling:ops.OpApplyDirectStyling, SetBlob:ops.OpSetBlob, RemoveBlob:ops.OpRemoveBlob, InsertImage:ops.OpInsertImage, InsertTable:ops.OpInsertTable, InsertText:ops.OpInsertText, RemoveText:ops.OpRemoveText, SplitParagraph:ops.OpSplitParagraph, SetParagraphStyle:ops.OpSetParagraphStyle, UpdateParagraphStyle:ops.OpUpdateParagraphStyle, AddStyle:ops.OpAddStyle,
|
|
RemoveStyle:ops.OpRemoveStyle, MoveCursor:ops.OpMoveCursor, RemoveCursor:ops.OpRemoveCursor, AddAnnotation:ops.OpAddAnnotation, RemoveAnnotation:ops.OpRemoveAnnotation, UpdateMetadata:ops.OpUpdateMetadata, ApplyHyperlink:ops.OpApplyHyperlink, RemoveHyperlink:ops.OpRemoveHyperlink}
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OperationRouter = function OperationRouter() {
|
|
};
|
|
ops.OperationRouter.prototype.setOperationFactory = function(f) {
|
|
};
|
|
ops.OperationRouter.prototype.setPlaybackFunction = function(playback_func) {
|
|
};
|
|
ops.OperationRouter.prototype.push = function(operations) {
|
|
};
|
|
ops.OperationRouter.prototype.close = function(callback) {
|
|
};
|
|
ops.OperationRouter.prototype.subscribe = function(eventId, cb) {
|
|
};
|
|
ops.OperationRouter.prototype.unsubscribe = function(eventId, cb) {
|
|
};
|
|
ops.OperationRouter.prototype.hasLocalUnsyncedOps = function() {
|
|
};
|
|
ops.OperationRouter.prototype.hasSessionHostConnection = function() {
|
|
};
|
|
ops.OperationRouter.signalProcessingBatchStart = "router/batchstart";
|
|
ops.OperationRouter.signalProcessingBatchEnd = "router/batchend";
|
|
/*
|
|
|
|
Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.TrivialOperationRouter = function TrivialOperationRouter() {
|
|
var events = new core.EventNotifier([ops.OperationRouter.signalProcessingBatchStart, ops.OperationRouter.signalProcessingBatchEnd]), operationFactory, playbackFunction, groupIdentifier = 0;
|
|
this.setOperationFactory = function(f) {
|
|
operationFactory = f
|
|
};
|
|
this.setPlaybackFunction = function(playback_func) {
|
|
playbackFunction = playback_func
|
|
};
|
|
this.push = function(operations) {
|
|
groupIdentifier += 1;
|
|
events.emit(ops.OperationRouter.signalProcessingBatchStart, {});
|
|
operations.forEach(function(op) {
|
|
var timedOp, opspec = op.spec();
|
|
opspec.timestamp = (new Date).getTime();
|
|
timedOp = operationFactory.create(opspec);
|
|
timedOp.group = "g" + groupIdentifier;
|
|
playbackFunction(timedOp)
|
|
});
|
|
events.emit(ops.OperationRouter.signalProcessingBatchEnd, {})
|
|
};
|
|
this.close = function(cb) {
|
|
cb()
|
|
};
|
|
this.subscribe = function(eventId, cb) {
|
|
events.subscribe(eventId, cb)
|
|
};
|
|
this.unsubscribe = function(eventId, cb) {
|
|
events.unsubscribe(eventId, cb)
|
|
};
|
|
this.hasLocalUnsyncedOps = function() {
|
|
return false
|
|
};
|
|
this.hasSessionHostConnection = function() {
|
|
return true
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.Session = function Session(odfCanvas) {
|
|
var self = this, operationFactory = new ops.OperationFactory, odtDocument = new ops.OdtDocument(odfCanvas), operationRouter = null;
|
|
function forwardBatchStart(args) {
|
|
odtDocument.emit(ops.OdtDocument.signalProcessingBatchStart, args)
|
|
}
|
|
function forwardBatchEnd(args) {
|
|
odtDocument.emit(ops.OdtDocument.signalProcessingBatchEnd, args)
|
|
}
|
|
this.setOperationFactory = function(opFactory) {
|
|
operationFactory = opFactory;
|
|
if(operationRouter) {
|
|
operationRouter.setOperationFactory(operationFactory)
|
|
}
|
|
};
|
|
this.setOperationRouter = function(opRouter) {
|
|
if(operationRouter) {
|
|
operationRouter.unsubscribe(ops.OperationRouter.signalProcessingBatchStart, forwardBatchStart);
|
|
operationRouter.unsubscribe(ops.OperationRouter.signalProcessingBatchEnd, forwardBatchEnd)
|
|
}
|
|
operationRouter = opRouter;
|
|
operationRouter.subscribe(ops.OperationRouter.signalProcessingBatchStart, forwardBatchStart);
|
|
operationRouter.subscribe(ops.OperationRouter.signalProcessingBatchEnd, forwardBatchEnd);
|
|
opRouter.setPlaybackFunction(function(op) {
|
|
odtDocument.emit(ops.OdtDocument.signalOperationStart, op);
|
|
if(op.execute(odtDocument)) {
|
|
odtDocument.emit(ops.OdtDocument.signalOperationEnd, op);
|
|
return true
|
|
}
|
|
return false
|
|
});
|
|
opRouter.setOperationFactory(operationFactory)
|
|
};
|
|
this.getOperationFactory = function() {
|
|
return operationFactory
|
|
};
|
|
this.getOdtDocument = function() {
|
|
return odtDocument
|
|
};
|
|
this.enqueue = function(ops) {
|
|
operationRouter.push(ops)
|
|
};
|
|
this.close = function(callback) {
|
|
operationRouter.close(function(err) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
odtDocument.close(callback)
|
|
}
|
|
})
|
|
};
|
|
this.destroy = function(callback) {
|
|
odtDocument.destroy(callback)
|
|
};
|
|
function init() {
|
|
self.setOperationRouter(new ops.TrivialOperationRouter)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.AnnotationController = function AnnotationController(session, inputMemberId) {
|
|
var odtDocument = session.getOdtDocument(), isAnnotatable = false, eventNotifier = new core.EventNotifier([gui.AnnotationController.annotatableChanged]), officens = odf.Namespaces.officens;
|
|
function isWithinAnnotation(node, container) {
|
|
while(node && node !== container) {
|
|
if(node.namespaceURI === officens && node.localName === "annotation") {
|
|
return true
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
return false
|
|
}
|
|
function updatedCachedValues() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), cursorNode = cursor && cursor.getNode(), newIsAnnotatable = false;
|
|
if(cursorNode) {
|
|
newIsAnnotatable = !isWithinAnnotation(cursorNode, odtDocument.getRootNode())
|
|
}
|
|
if(newIsAnnotatable !== isAnnotatable) {
|
|
isAnnotatable = newIsAnnotatable;
|
|
eventNotifier.emit(gui.AnnotationController.annotatableChanged, isAnnotatable)
|
|
}
|
|
}
|
|
function onCursorAdded(cursor) {
|
|
if(cursor.getMemberId() === inputMemberId) {
|
|
updatedCachedValues()
|
|
}
|
|
}
|
|
function onCursorRemoved(memberId) {
|
|
if(memberId === inputMemberId) {
|
|
updatedCachedValues()
|
|
}
|
|
}
|
|
function onCursorMoved(cursor) {
|
|
if(cursor.getMemberId() === inputMemberId) {
|
|
updatedCachedValues()
|
|
}
|
|
}
|
|
this.isAnnotatable = function() {
|
|
return isAnnotatable
|
|
};
|
|
this.addAnnotation = function() {
|
|
var op = new ops.OpAddAnnotation, selection = odtDocument.getCursorSelection(inputMemberId), length = selection.length, position = selection.position;
|
|
if(!isAnnotatable) {
|
|
return
|
|
}
|
|
position = length >= 0 ? position : position + length;
|
|
length = Math.abs(length);
|
|
op.init({memberid:inputMemberId, position:position, length:length, name:inputMemberId + Date.now()});
|
|
session.enqueue([op])
|
|
};
|
|
this.removeAnnotation = function(annotationNode) {
|
|
var startStep, endStep, op, moveCursor;
|
|
startStep = odtDocument.convertDomPointToCursorStep(annotationNode, 0) + 1;
|
|
endStep = odtDocument.convertDomPointToCursorStep(annotationNode, annotationNode.childNodes.length);
|
|
op = new ops.OpRemoveAnnotation;
|
|
op.init({memberid:inputMemberId, position:startStep, length:endStep - startStep});
|
|
moveCursor = new ops.OpMoveCursor;
|
|
moveCursor.init({memberid:inputMemberId, position:startStep > 0 ? startStep - 1 : startStep, length:0});
|
|
session.enqueue([op, moveCursor])
|
|
};
|
|
this.subscribe = function(eventid, cb) {
|
|
eventNotifier.subscribe(eventid, cb)
|
|
};
|
|
this.unsubscribe = function(eventid, cb) {
|
|
eventNotifier.unsubscribe(eventid, cb)
|
|
};
|
|
this.destroy = function(callback) {
|
|
odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved);
|
|
callback()
|
|
};
|
|
function init() {
|
|
odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded);
|
|
odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
|
|
odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved);
|
|
updatedCachedValues()
|
|
}
|
|
init()
|
|
};
|
|
gui.AnnotationController.annotatableChanged = "annotatable/changed";
|
|
(function() {
|
|
return gui.AnnotationController
|
|
})();
|
|
gui.Avatar = function Avatar(parentElement, avatarInitiallyVisible) {
|
|
var self = this, handle, image, pendingImageUrl, displayShown = "block", displayHidden = "none";
|
|
this.setColor = function(color) {
|
|
image.style.borderColor = color
|
|
};
|
|
this.setImageUrl = function(url) {
|
|
if(self.isVisible()) {
|
|
image.src = url
|
|
}else {
|
|
pendingImageUrl = url
|
|
}
|
|
};
|
|
this.isVisible = function() {
|
|
return handle.style.display === displayShown
|
|
};
|
|
this.show = function() {
|
|
if(pendingImageUrl) {
|
|
image.src = pendingImageUrl;
|
|
pendingImageUrl = undefined
|
|
}
|
|
handle.style.display = displayShown
|
|
};
|
|
this.hide = function() {
|
|
handle.style.display = displayHidden
|
|
};
|
|
this.markAsFocussed = function(isFocussed) {
|
|
if(isFocussed) {
|
|
handle.classList.add("active")
|
|
}else {
|
|
handle.classList.remove("active")
|
|
}
|
|
};
|
|
this.destroy = function(callback) {
|
|
parentElement.removeChild(handle);
|
|
callback()
|
|
};
|
|
function init() {
|
|
var document = (parentElement.ownerDocument), htmlns = document.documentElement.namespaceURI;
|
|
handle = (document.createElementNS(htmlns, "div"));
|
|
image = (document.createElementNS(htmlns, "img"));
|
|
image.width = 64;
|
|
image.height = 64;
|
|
handle.appendChild(image);
|
|
handle.style.width = "64px";
|
|
handle.style.height = "70px";
|
|
handle.style.position = "absolute";
|
|
handle.style.top = "-80px";
|
|
handle.style.left = "-34px";
|
|
handle.style.display = avatarInitiallyVisible ? displayShown : displayHidden;
|
|
handle.className = "handle";
|
|
parentElement.appendChild(handle)
|
|
}
|
|
init()
|
|
};
|
|
gui.Caret = function Caret(cursor, avatarInitiallyVisible, blinkOnRangeSelect) {
|
|
var MIN_CARET_HEIGHT_PX = 8, DEFAULT_CARET_TOP = "5%", DEFAULT_CARET_HEIGHT = "1em", BLINK_PERIOD_MS = 500, span, avatar, cursorNode, overlayElement, domUtils = new core.DomUtils, async = new core.Async, redrawTask, blinkTask, shouldResetBlink = false, shouldCheckCaretVisibility = false, shouldUpdateCaretSize = false, state = {isFocused:false, isShown:true, visibility:"hidden"}, lastState = {isFocused:!state.isFocused, isShown:!state.isShown, visibility:"hidden"};
|
|
function blinkCaret() {
|
|
span.style.opacity = span.style.opacity === "0" ? "1" : "0";
|
|
blinkTask.trigger()
|
|
}
|
|
function getCaretClientRectWithMargin(caretElement, margin) {
|
|
var caretRect = caretElement.getBoundingClientRect();
|
|
return{left:caretRect.left - margin.left, top:caretRect.top - margin.top, right:caretRect.right + margin.right, bottom:caretRect.bottom + margin.bottom}
|
|
}
|
|
function length(node) {
|
|
return node.nodeType === Node.TEXT_NODE ? node.textContent.length : node.childNodes.length
|
|
}
|
|
function verticalOverlap(cursorNode, rangeRect) {
|
|
var cursorRect = cursorNode.getBoundingClientRect(), intersectTop = 0, intersectBottom = 0;
|
|
if(cursorRect && rangeRect) {
|
|
intersectTop = Math.max(cursorRect.top, rangeRect.top);
|
|
intersectBottom = Math.min(cursorRect.bottom, rangeRect.bottom)
|
|
}
|
|
return intersectBottom - intersectTop
|
|
}
|
|
function getSelectionRect() {
|
|
var range = cursor.getSelectedRange().cloneRange(), node = cursor.getNode(), nextRectangle, selectionRectangle = null, nodeLength;
|
|
if(node.previousSibling) {
|
|
nodeLength = length(node.previousSibling);
|
|
range.setStart(node.previousSibling, nodeLength > 0 ? nodeLength - 1 : 0);
|
|
range.setEnd(node.previousSibling, nodeLength);
|
|
nextRectangle = range.getBoundingClientRect();
|
|
if(nextRectangle && nextRectangle.height) {
|
|
selectionRectangle = nextRectangle
|
|
}
|
|
}
|
|
if(node.nextSibling) {
|
|
range.setStart(node.nextSibling, 0);
|
|
range.setEnd(node.nextSibling, length(node.nextSibling) > 0 ? 1 : 0);
|
|
nextRectangle = range.getBoundingClientRect();
|
|
if(nextRectangle && nextRectangle.height) {
|
|
if(!selectionRectangle || verticalOverlap(node, nextRectangle) > verticalOverlap(node, selectionRectangle)) {
|
|
selectionRectangle = nextRectangle
|
|
}
|
|
}
|
|
}
|
|
return selectionRectangle
|
|
}
|
|
function updateCaretHeightAndPosition() {
|
|
var selectionRect = getSelectionRect(), canvas = (cursor.getDocument().getCanvas()), zoomLevel = canvas.getZoomLevel(), rootRect = domUtils.getBoundingClientRect(canvas.getSizer()), caretRect, caretStyle;
|
|
if(selectionRect) {
|
|
span.style.top = "0";
|
|
caretRect = domUtils.getBoundingClientRect(span);
|
|
if(selectionRect.height < MIN_CARET_HEIGHT_PX) {
|
|
selectionRect = {top:selectionRect.top - (MIN_CARET_HEIGHT_PX - selectionRect.height) / 2, height:MIN_CARET_HEIGHT_PX}
|
|
}
|
|
span.style.height = domUtils.adaptRangeDifferenceToZoomLevel(selectionRect.height, zoomLevel) + "px";
|
|
span.style.top = domUtils.adaptRangeDifferenceToZoomLevel(selectionRect.top - caretRect.top, zoomLevel) + "px"
|
|
}else {
|
|
span.style.height = DEFAULT_CARET_HEIGHT;
|
|
span.style.top = DEFAULT_CARET_TOP
|
|
}
|
|
if(overlayElement) {
|
|
caretStyle = runtime.getWindow().getComputedStyle(span, null);
|
|
caretRect = domUtils.getBoundingClientRect(span);
|
|
overlayElement.style.bottom = domUtils.adaptRangeDifferenceToZoomLevel(rootRect.bottom - caretRect.bottom, zoomLevel) + "px";
|
|
overlayElement.style.left = domUtils.adaptRangeDifferenceToZoomLevel(caretRect.right - rootRect.left, zoomLevel) + "px";
|
|
if(caretStyle.font) {
|
|
overlayElement.style.font = caretStyle.font
|
|
}else {
|
|
overlayElement.style.fontStyle = caretStyle.fontStyle;
|
|
overlayElement.style.fontVariant = caretStyle.fontVariant;
|
|
overlayElement.style.fontWeight = caretStyle.fontWeight;
|
|
overlayElement.style.fontSize = caretStyle.fontSize;
|
|
overlayElement.style.lineHeight = caretStyle.lineHeight;
|
|
overlayElement.style.fontFamily = caretStyle.fontFamily
|
|
}
|
|
}
|
|
}
|
|
function ensureVisible() {
|
|
var canvasElement = cursor.getDocument().getCanvas().getElement(), canvasContainerElement = (canvasElement.parentNode), caretRect, canvasContainerRect, horizontalMargin = canvasContainerElement.offsetWidth - canvasContainerElement.clientWidth + 5, verticalMargin = canvasContainerElement.offsetHeight - canvasContainerElement.clientHeight + 5;
|
|
caretRect = getCaretClientRectWithMargin(span, {top:verticalMargin, left:horizontalMargin, bottom:verticalMargin, right:horizontalMargin});
|
|
canvasContainerRect = canvasContainerElement.getBoundingClientRect();
|
|
if(caretRect.top < canvasContainerRect.top) {
|
|
canvasContainerElement.scrollTop -= canvasContainerRect.top - caretRect.top
|
|
}else {
|
|
if(caretRect.bottom > canvasContainerRect.bottom) {
|
|
canvasContainerElement.scrollTop += caretRect.bottom - canvasContainerRect.bottom
|
|
}
|
|
}
|
|
if(caretRect.left < canvasContainerRect.left) {
|
|
canvasContainerElement.scrollLeft -= canvasContainerRect.left - caretRect.left
|
|
}else {
|
|
if(caretRect.right > canvasContainerRect.right) {
|
|
canvasContainerElement.scrollLeft += caretRect.right - canvasContainerRect.right
|
|
}
|
|
}
|
|
}
|
|
function hasStateChanged(property) {
|
|
return lastState[property] !== state[property]
|
|
}
|
|
function saveState() {
|
|
Object.keys(state).forEach(function(key) {
|
|
lastState[key] = state[key]
|
|
})
|
|
}
|
|
function updateCaret() {
|
|
if(state.isShown === false || (cursor.getSelectionType() !== ops.OdtCursor.RangeSelection || !blinkOnRangeSelect && !cursor.getSelectedRange().collapsed)) {
|
|
state.visibility = "hidden";
|
|
span.style.visibility = "hidden";
|
|
blinkTask.cancel()
|
|
}else {
|
|
state.visibility = "visible";
|
|
span.style.visibility = "visible";
|
|
if(state.isFocused === false) {
|
|
span.style.opacity = "1";
|
|
blinkTask.cancel()
|
|
}else {
|
|
if(shouldResetBlink || hasStateChanged("visibility")) {
|
|
span.style.opacity = "1";
|
|
blinkTask.cancel()
|
|
}
|
|
blinkTask.trigger()
|
|
}
|
|
if(shouldUpdateCaretSize || (shouldCheckCaretVisibility || hasStateChanged("visibility"))) {
|
|
updateCaretHeightAndPosition()
|
|
}
|
|
if(shouldCheckCaretVisibility) {
|
|
ensureVisible()
|
|
}
|
|
}
|
|
if(hasStateChanged("isFocused")) {
|
|
avatar.markAsFocussed(state.isFocused)
|
|
}
|
|
saveState();
|
|
shouldResetBlink = false;
|
|
shouldCheckCaretVisibility = false;
|
|
shouldUpdateCaretSize = false
|
|
}
|
|
this.handleUpdate = function() {
|
|
shouldUpdateCaretSize = true;
|
|
if(state.visibility !== "hidden") {
|
|
state.visibility = "hidden";
|
|
span.style.visibility = "hidden"
|
|
}
|
|
redrawTask.trigger()
|
|
};
|
|
this.refreshCursorBlinking = function() {
|
|
shouldResetBlink = true;
|
|
redrawTask.trigger()
|
|
};
|
|
this.setFocus = function() {
|
|
state.isFocused = true;
|
|
redrawTask.trigger()
|
|
};
|
|
this.removeFocus = function() {
|
|
state.isFocused = false;
|
|
redrawTask.trigger()
|
|
};
|
|
this.show = function() {
|
|
state.isShown = true;
|
|
redrawTask.trigger()
|
|
};
|
|
this.hide = function() {
|
|
state.isShown = false;
|
|
redrawTask.trigger()
|
|
};
|
|
this.setAvatarImageUrl = function(url) {
|
|
avatar.setImageUrl(url)
|
|
};
|
|
this.setColor = function(newColor) {
|
|
span.style.borderColor = newColor;
|
|
avatar.setColor(newColor)
|
|
};
|
|
this.getCursor = function() {
|
|
return cursor
|
|
};
|
|
this.getFocusElement = function() {
|
|
return span
|
|
};
|
|
this.toggleHandleVisibility = function() {
|
|
if(avatar.isVisible()) {
|
|
avatar.hide()
|
|
}else {
|
|
avatar.show()
|
|
}
|
|
};
|
|
this.showHandle = function() {
|
|
avatar.show()
|
|
};
|
|
this.hideHandle = function() {
|
|
avatar.hide()
|
|
};
|
|
this.setOverlayElement = function(element) {
|
|
overlayElement = element;
|
|
shouldUpdateCaretSize = true;
|
|
redrawTask.trigger()
|
|
};
|
|
this.ensureVisible = function() {
|
|
shouldCheckCaretVisibility = true;
|
|
redrawTask.trigger()
|
|
};
|
|
function destroy(callback) {
|
|
cursorNode.removeChild(span);
|
|
callback()
|
|
}
|
|
this.destroy = function(callback) {
|
|
var cleanup = [redrawTask.destroy, blinkTask.destroy, avatar.destroy, destroy];
|
|
async.destroyAll(cleanup, callback)
|
|
};
|
|
function init() {
|
|
var dom = cursor.getDocument().getDOMDocument(), htmlns = dom.documentElement.namespaceURI;
|
|
span = (dom.createElementNS(htmlns, "span"));
|
|
span.className = "caret";
|
|
span.style.top = DEFAULT_CARET_TOP;
|
|
cursorNode = cursor.getNode();
|
|
cursorNode.appendChild(span);
|
|
avatar = new gui.Avatar(cursorNode, avatarInitiallyVisible);
|
|
redrawTask = new core.ScheduledTask(updateCaret, 0);
|
|
blinkTask = new core.ScheduledTask(blinkCaret, BLINK_PERIOD_MS);
|
|
redrawTask.triggerImmediate()
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.TextSerializer = function TextSerializer() {
|
|
var self = this, odfUtils = new odf.OdfUtils;
|
|
function serializeNode(node) {
|
|
var s = "", accept = self.filter ? self.filter.acceptNode(node) : NodeFilter.FILTER_ACCEPT, nodeType = node.nodeType, child;
|
|
if((accept === NodeFilter.FILTER_ACCEPT || accept === NodeFilter.FILTER_SKIP) && odfUtils.isTextContentContainingNode(node)) {
|
|
child = node.firstChild;
|
|
while(child) {
|
|
s += serializeNode(child);
|
|
child = child.nextSibling
|
|
}
|
|
}
|
|
if(accept === NodeFilter.FILTER_ACCEPT) {
|
|
if(nodeType === Node.ELEMENT_NODE && odfUtils.isParagraph(node)) {
|
|
s += "\n"
|
|
}else {
|
|
if(nodeType === Node.TEXT_NODE && node.textContent) {
|
|
s += node.textContent
|
|
}
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
this.filter = null;
|
|
this.writeToString = function(node) {
|
|
var plainText;
|
|
if(!node) {
|
|
return""
|
|
}
|
|
plainText = serializeNode(node);
|
|
if(plainText[plainText.length - 1] === "\n") {
|
|
plainText = plainText.substr(0, plainText.length - 1)
|
|
}
|
|
return plainText
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.MimeDataExporter = function MimeDataExporter() {
|
|
var textSerializer, filter;
|
|
this.exportRangeToDataTransfer = function(dataTransfer, range) {
|
|
var document = range.startContainer.ownerDocument, serializedFragment, fragmentContainer;
|
|
fragmentContainer = document.createElement("span");
|
|
fragmentContainer.appendChild(range.cloneContents());
|
|
serializedFragment = textSerializer.writeToString(fragmentContainer);
|
|
try {
|
|
dataTransfer.setData("text/plain", serializedFragment)
|
|
}catch(e) {
|
|
dataTransfer.setData("Text", serializedFragment)
|
|
}
|
|
};
|
|
function init() {
|
|
textSerializer = new odf.TextSerializer;
|
|
filter = new odf.OdfNodeFilter;
|
|
textSerializer.filter = filter
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.Clipboard = function Clipboard(mimeDataExporter) {
|
|
this.setDataFromRange = function(e, range) {
|
|
var result, clipboard = e.clipboardData, window = runtime.getWindow();
|
|
if(!clipboard && window) {
|
|
clipboard = window.clipboardData
|
|
}
|
|
if(clipboard) {
|
|
result = true;
|
|
mimeDataExporter.exportRangeToDataTransfer((clipboard), range);
|
|
e.preventDefault()
|
|
}else {
|
|
result = false
|
|
}
|
|
return result
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.StyleSummary = function StyleSummary(styles) {
|
|
var propertyValues = {};
|
|
function getPropertyValues(section, propertyName) {
|
|
var cacheKey = section + "|" + propertyName, values;
|
|
if(!propertyValues.hasOwnProperty(cacheKey)) {
|
|
values = [];
|
|
styles.forEach(function(style) {
|
|
var styleSection = style[section], value = styleSection && styleSection[propertyName];
|
|
if(values.indexOf(value) === -1) {
|
|
values.push(value)
|
|
}
|
|
});
|
|
propertyValues[cacheKey] = values
|
|
}
|
|
return propertyValues[cacheKey]
|
|
}
|
|
this.getPropertyValues = getPropertyValues;
|
|
function lazilyLoaded(section, propertyName, acceptedPropertyValues) {
|
|
return function() {
|
|
var existingPropertyValues = getPropertyValues(section, propertyName);
|
|
return acceptedPropertyValues.length >= existingPropertyValues.length && existingPropertyValues.every(function(v) {
|
|
return acceptedPropertyValues.indexOf(v) !== -1
|
|
})
|
|
}
|
|
}
|
|
function getCommonValue(section, propertyName) {
|
|
var values = getPropertyValues(section, propertyName);
|
|
return values.length === 1 ? values[0] : undefined
|
|
}
|
|
this.getCommonValue = getCommonValue;
|
|
this.isBold = lazilyLoaded("style:text-properties", "fo:font-weight", ["bold"]);
|
|
this.isItalic = lazilyLoaded("style:text-properties", "fo:font-style", ["italic"]);
|
|
this.hasUnderline = lazilyLoaded("style:text-properties", "style:text-underline-style", ["solid"]);
|
|
this.hasStrikeThrough = lazilyLoaded("style:text-properties", "style:text-line-through-style", ["solid"]);
|
|
this.fontSize = function() {
|
|
var stringFontSize = getCommonValue("style:text-properties", "fo:font-size");
|
|
return(stringFontSize && parseFloat(stringFontSize))
|
|
};
|
|
this.fontName = function() {
|
|
return getCommonValue("style:text-properties", "style:font-name")
|
|
};
|
|
this.isAlignedLeft = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["left", "start"]);
|
|
this.isAlignedCenter = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["center"]);
|
|
this.isAlignedRight = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["right", "end"]);
|
|
this.isAlignedJustified = lazilyLoaded("style:paragraph-properties", "fo:text-align", ["justify"]);
|
|
this.text = {isBold:this.isBold, isItalic:this.isItalic, hasUnderline:this.hasUnderline, hasStrikeThrough:this.hasStrikeThrough, fontSize:this.fontSize, fontName:this.fontName};
|
|
this.paragraph = {isAlignedLeft:this.isAlignedLeft, isAlignedCenter:this.isAlignedCenter, isAlignedRight:this.isAlignedRight, isAlignedJustified:this.isAlignedJustified}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.DirectFormattingController = function DirectFormattingController(session, inputMemberId, objectNameGenerator, directParagraphStylingEnabled) {
|
|
var self = this, odtDocument = session.getOdtDocument(), utils = new core.Utils, odfUtils = new odf.OdfUtils, eventNotifier = new core.EventNotifier([gui.DirectFormattingController.textStylingChanged, gui.DirectFormattingController.paragraphStylingChanged]), textns = odf.Namespaces.textns, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, directCursorStyleProperties, selectionAppliedStyles = [], selectionStylesSummary = new gui.StyleSummary(selectionAppliedStyles);
|
|
function getNodes(range) {
|
|
var container, nodes;
|
|
if(range.collapsed) {
|
|
container = range.startContainer;
|
|
if(container.hasChildNodes() && range.startOffset < container.childNodes.length) {
|
|
container = container.childNodes.item(range.startOffset)
|
|
}
|
|
nodes = [container]
|
|
}else {
|
|
nodes = odfUtils.getTextNodes(range, true)
|
|
}
|
|
return nodes
|
|
}
|
|
function getSelectionAppliedStyles() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), range = cursor && cursor.getSelectedRange(), nodes = range ? getNodes(range) : [], selectionStyles = odtDocument.getFormatting().getAppliedStyles(nodes);
|
|
if(selectionStyles[0] && directCursorStyleProperties) {
|
|
selectionStyles[0] = utils.mergeObjects(selectionStyles[0], (directCursorStyleProperties))
|
|
}
|
|
return selectionStyles
|
|
}
|
|
function createDiff(oldSummary, newSummary) {
|
|
var diffMap = {};
|
|
Object.keys(oldSummary).forEach(function(funcName) {
|
|
var oldValue = oldSummary[funcName](), newValue = newSummary[funcName]();
|
|
if(oldValue !== newValue) {
|
|
diffMap[funcName] = newValue
|
|
}
|
|
});
|
|
return diffMap
|
|
}
|
|
function updateSelectionStylesInfo() {
|
|
var textStyleDiff, paragraphStyleDiff, newSelectionStylesSummary;
|
|
selectionAppliedStyles = getSelectionAppliedStyles();
|
|
newSelectionStylesSummary = new gui.StyleSummary(selectionAppliedStyles);
|
|
textStyleDiff = createDiff(selectionStylesSummary.text, newSelectionStylesSummary.text);
|
|
paragraphStyleDiff = createDiff(selectionStylesSummary.paragraph, newSelectionStylesSummary.paragraph);
|
|
selectionStylesSummary = newSelectionStylesSummary;
|
|
if(Object.keys(textStyleDiff).length > 0) {
|
|
eventNotifier.emit(gui.DirectFormattingController.textStylingChanged, textStyleDiff)
|
|
}
|
|
if(Object.keys(paragraphStyleDiff).length > 0) {
|
|
eventNotifier.emit(gui.DirectFormattingController.paragraphStylingChanged, paragraphStyleDiff)
|
|
}
|
|
}
|
|
function onCursorEvent(cursorOrId) {
|
|
var cursorMemberId = typeof cursorOrId === "string" ? cursorOrId : cursorOrId.getMemberId();
|
|
if(cursorMemberId === inputMemberId) {
|
|
updateSelectionStylesInfo()
|
|
}
|
|
}
|
|
function onParagraphStyleModified() {
|
|
updateSelectionStylesInfo()
|
|
}
|
|
function onParagraphChanged(args) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), p = args.paragraphElement;
|
|
if(cursor && odtDocument.getParagraphElement(cursor.getNode()) === p) {
|
|
updateSelectionStylesInfo()
|
|
}
|
|
}
|
|
function toggle(predicate, toggleMethod) {
|
|
toggleMethod(!predicate());
|
|
return true
|
|
}
|
|
function formatTextSelection(textProperties) {
|
|
var selection = odtDocument.getCursorSelection(inputMemberId), op, properties = {"style:text-properties":textProperties};
|
|
if(selection.length !== 0) {
|
|
op = new ops.OpApplyDirectStyling;
|
|
op.init({memberid:inputMemberId, position:selection.position, length:selection.length, setProperties:properties});
|
|
session.enqueue([op])
|
|
}else {
|
|
directCursorStyleProperties = utils.mergeObjects(directCursorStyleProperties || {}, properties);
|
|
updateSelectionStylesInfo()
|
|
}
|
|
}
|
|
this.formatTextSelection = formatTextSelection;
|
|
function applyTextPropertyToSelection(propertyName, propertyValue) {
|
|
var textProperties = {};
|
|
textProperties[propertyName] = propertyValue;
|
|
formatTextSelection(textProperties)
|
|
}
|
|
this.createCursorStyleOp = function(position, length, useCachedStyle) {
|
|
var styleOp = null, properties = useCachedStyle ? selectionAppliedStyles[0] : directCursorStyleProperties;
|
|
if(properties && properties["style:text-properties"]) {
|
|
styleOp = new ops.OpApplyDirectStyling;
|
|
styleOp.init({memberid:inputMemberId, position:position, length:length, setProperties:{"style:text-properties":properties["style:text-properties"]}});
|
|
directCursorStyleProperties = null;
|
|
updateSelectionStylesInfo()
|
|
}
|
|
return styleOp
|
|
};
|
|
function clearCursorStyle(op) {
|
|
var spec = op.spec();
|
|
if(directCursorStyleProperties && spec.memberid === inputMemberId) {
|
|
if(spec.optype !== "SplitParagraph") {
|
|
directCursorStyleProperties = null;
|
|
updateSelectionStylesInfo()
|
|
}
|
|
}
|
|
}
|
|
function setBold(checked) {
|
|
var value = checked ? "bold" : "normal";
|
|
applyTextPropertyToSelection("fo:font-weight", value)
|
|
}
|
|
this.setBold = setBold;
|
|
function setItalic(checked) {
|
|
var value = checked ? "italic" : "normal";
|
|
applyTextPropertyToSelection("fo:font-style", value)
|
|
}
|
|
this.setItalic = setItalic;
|
|
function setHasUnderline(checked) {
|
|
var value = checked ? "solid" : "none";
|
|
applyTextPropertyToSelection("style:text-underline-style", value)
|
|
}
|
|
this.setHasUnderline = setHasUnderline;
|
|
function setHasStrikethrough(checked) {
|
|
var value = checked ? "solid" : "none";
|
|
applyTextPropertyToSelection("style:text-line-through-style", value)
|
|
}
|
|
this.setHasStrikethrough = setHasStrikethrough;
|
|
function setFontSize(value) {
|
|
applyTextPropertyToSelection("fo:font-size", value + "pt")
|
|
}
|
|
this.setFontSize = setFontSize;
|
|
function setFontName(value) {
|
|
applyTextPropertyToSelection("style:font-name", value)
|
|
}
|
|
this.setFontName = setFontName;
|
|
this.getAppliedStyles = function() {
|
|
return selectionAppliedStyles
|
|
};
|
|
this.toggleBold = toggle.bind(self, function() {
|
|
return selectionStylesSummary.isBold()
|
|
}, setBold);
|
|
this.toggleItalic = toggle.bind(self, function() {
|
|
return selectionStylesSummary.isItalic()
|
|
}, setItalic);
|
|
this.toggleUnderline = toggle.bind(self, function() {
|
|
return selectionStylesSummary.hasUnderline()
|
|
}, setHasUnderline);
|
|
this.toggleStrikethrough = toggle.bind(self, function() {
|
|
return selectionStylesSummary.hasStrikeThrough()
|
|
}, setHasStrikethrough);
|
|
this.isBold = function() {
|
|
return selectionStylesSummary.isBold()
|
|
};
|
|
this.isItalic = function() {
|
|
return selectionStylesSummary.isItalic()
|
|
};
|
|
this.hasUnderline = function() {
|
|
return selectionStylesSummary.hasUnderline()
|
|
};
|
|
this.hasStrikeThrough = function() {
|
|
return selectionStylesSummary.hasStrikeThrough()
|
|
};
|
|
this.fontSize = function() {
|
|
return selectionStylesSummary.fontSize()
|
|
};
|
|
this.fontName = function() {
|
|
return selectionStylesSummary.fontName()
|
|
};
|
|
this.isAlignedLeft = function() {
|
|
return selectionStylesSummary.isAlignedLeft()
|
|
};
|
|
this.isAlignedCenter = function() {
|
|
return selectionStylesSummary.isAlignedCenter()
|
|
};
|
|
this.isAlignedRight = function() {
|
|
return selectionStylesSummary.isAlignedRight()
|
|
};
|
|
this.isAlignedJustified = function() {
|
|
return selectionStylesSummary.isAlignedJustified()
|
|
};
|
|
function roundUp(step) {
|
|
return step === ops.StepsTranslator.NEXT_STEP
|
|
}
|
|
function getOwnProperty(obj, key) {
|
|
return obj.hasOwnProperty(key) ? obj[key] : undefined
|
|
}
|
|
function applyParagraphDirectStyling(applyDirectStyling) {
|
|
var range = odtDocument.getCursor(inputMemberId).getSelectedRange(), paragraphs = odfUtils.getParagraphElements(range), formatting = odtDocument.getFormatting(), operations = [], derivedStyleNames = {}, defaultStyleName;
|
|
paragraphs.forEach(function(paragraph) {
|
|
var paragraphStartPoint = odtDocument.convertDomPointToCursorStep(paragraph, 0, roundUp), paragraphStyleName = paragraph.getAttributeNS(odf.Namespaces.textns, "style-name"), newParagraphStyleName, opAddStyle, opSetParagraphStyle, paragraphProperties;
|
|
if(paragraphStyleName) {
|
|
newParagraphStyleName = getOwnProperty(derivedStyleNames, paragraphStyleName)
|
|
}else {
|
|
newParagraphStyleName = defaultStyleName
|
|
}
|
|
if(!newParagraphStyleName) {
|
|
newParagraphStyleName = objectNameGenerator.generateStyleName();
|
|
if(paragraphStyleName) {
|
|
derivedStyleNames[paragraphStyleName] = newParagraphStyleName;
|
|
paragraphProperties = formatting.createDerivedStyleObject(paragraphStyleName, "paragraph", {})
|
|
}else {
|
|
defaultStyleName = newParagraphStyleName;
|
|
paragraphProperties = {}
|
|
}
|
|
paragraphProperties = applyDirectStyling(paragraphProperties);
|
|
opAddStyle = new ops.OpAddStyle;
|
|
opAddStyle.init({memberid:inputMemberId, styleName:newParagraphStyleName.toString(), styleFamily:"paragraph", isAutomaticStyle:true, setProperties:paragraphProperties});
|
|
operations.push(opAddStyle)
|
|
}
|
|
opSetParagraphStyle = new ops.OpSetParagraphStyle;
|
|
opSetParagraphStyle.init({memberid:inputMemberId, styleName:newParagraphStyleName.toString(), position:paragraphStartPoint});
|
|
operations.push(opSetParagraphStyle)
|
|
});
|
|
session.enqueue(operations)
|
|
}
|
|
function applySimpleParagraphDirectStyling(styleOverrides) {
|
|
applyParagraphDirectStyling(function(paragraphStyle) {
|
|
return utils.mergeObjects(paragraphStyle, styleOverrides)
|
|
})
|
|
}
|
|
function alignParagraph(alignment) {
|
|
applySimpleParagraphDirectStyling({"style:paragraph-properties":{"fo:text-align":alignment}})
|
|
}
|
|
this.alignParagraphLeft = function() {
|
|
alignParagraph("left");
|
|
return true
|
|
};
|
|
this.alignParagraphCenter = function() {
|
|
alignParagraph("center");
|
|
return true
|
|
};
|
|
this.alignParagraphRight = function() {
|
|
alignParagraph("right");
|
|
return true
|
|
};
|
|
this.alignParagraphJustified = function() {
|
|
alignParagraph("justify");
|
|
return true
|
|
};
|
|
function modifyParagraphIndent(direction, paragraphStyle) {
|
|
var tabStopDistance = odtDocument.getFormatting().getDefaultTabStopDistance(), paragraphProperties = paragraphStyle["style:paragraph-properties"], indentValue, indent, newIndent;
|
|
if(paragraphProperties) {
|
|
indentValue = paragraphProperties["fo:margin-left"];
|
|
if(indentValue) {
|
|
indent = odfUtils.parseLength(indentValue)
|
|
}
|
|
}
|
|
if(indent && indent.unit === tabStopDistance.unit) {
|
|
newIndent = indent.value + direction * tabStopDistance.value + indent.unit
|
|
}else {
|
|
newIndent = direction * tabStopDistance.value + tabStopDistance.unit
|
|
}
|
|
return utils.mergeObjects(paragraphStyle, {"style:paragraph-properties":{"fo:margin-left":newIndent}})
|
|
}
|
|
this.indent = function() {
|
|
applyParagraphDirectStyling(modifyParagraphIndent.bind(null, 1));
|
|
return true
|
|
};
|
|
this.outdent = function() {
|
|
applyParagraphDirectStyling(modifyParagraphIndent.bind(null, -1));
|
|
return true
|
|
};
|
|
function isSelectionAtTheEndOfLastParagraph(range, paragraphNode) {
|
|
var iterator = gui.SelectionMover.createPositionIterator(paragraphNode), rootConstrainedFilter = new core.PositionFilterChain;
|
|
rootConstrainedFilter.addFilter(odtDocument.getPositionFilter());
|
|
rootConstrainedFilter.addFilter(odtDocument.createRootFilter(inputMemberId));
|
|
iterator.setUnfilteredPosition((range.endContainer), range.endOffset);
|
|
while(iterator.nextPosition()) {
|
|
if(rootConstrainedFilter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
return odtDocument.getParagraphElement(iterator.getCurrentNode()) !== paragraphNode
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
function isTextStyleDifferentFromFirstParagraph(range, paragraphNode) {
|
|
var textNodes = getNodes(range), textStyle = odtDocument.getFormatting().getAppliedStyles(textNodes)[0], paragraphStyle = odtDocument.getFormatting().getAppliedStylesForElement(paragraphNode);
|
|
if(!textStyle || (textStyle["style:family"] !== "text" || !textStyle["style:text-properties"])) {
|
|
return false
|
|
}
|
|
if(!paragraphStyle || !paragraphStyle["style:text-properties"]) {
|
|
return true
|
|
}
|
|
textStyle = (textStyle["style:text-properties"]);
|
|
paragraphStyle = (paragraphStyle["style:text-properties"]);
|
|
return!Object.keys(textStyle).every(function(key) {
|
|
return textStyle[key] === paragraphStyle[key]
|
|
})
|
|
}
|
|
this.createParagraphStyleOps = function(position) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), range = cursor.getSelectedRange(), operations = [], op, startNode, endNode, paragraphNode, properties, parentStyleName, styleName;
|
|
if(cursor.hasForwardSelection()) {
|
|
startNode = cursor.getAnchorNode();
|
|
endNode = cursor.getNode()
|
|
}else {
|
|
startNode = cursor.getNode();
|
|
endNode = cursor.getAnchorNode()
|
|
}
|
|
paragraphNode = (odtDocument.getParagraphElement(endNode));
|
|
runtime.assert(Boolean(paragraphNode), "DirectFormattingController: Cursor outside paragraph");
|
|
if(!isSelectionAtTheEndOfLastParagraph(range, paragraphNode)) {
|
|
return operations
|
|
}
|
|
if(endNode !== startNode) {
|
|
paragraphNode = (odtDocument.getParagraphElement(startNode))
|
|
}
|
|
if(!directCursorStyleProperties && !isTextStyleDifferentFromFirstParagraph(range, paragraphNode)) {
|
|
return operations
|
|
}
|
|
properties = selectionAppliedStyles[0];
|
|
if(!properties) {
|
|
return operations
|
|
}
|
|
parentStyleName = paragraphNode.getAttributeNS(textns, "style-name");
|
|
if(parentStyleName) {
|
|
properties = {"style:text-properties":properties["style:text-properties"]};
|
|
properties = odtDocument.getFormatting().createDerivedStyleObject(parentStyleName, "paragraph", properties)
|
|
}
|
|
styleName = objectNameGenerator.generateStyleName();
|
|
op = new ops.OpAddStyle;
|
|
op.init({memberid:inputMemberId, styleName:styleName, styleFamily:"paragraph", isAutomaticStyle:true, setProperties:properties});
|
|
operations.push(op);
|
|
op = new ops.OpSetParagraphStyle;
|
|
op.init({memberid:inputMemberId, styleName:styleName, position:position});
|
|
operations.push(op);
|
|
return operations
|
|
};
|
|
this.subscribe = function(eventid, cb) {
|
|
eventNotifier.subscribe(eventid, cb)
|
|
};
|
|
this.unsubscribe = function(eventid, cb) {
|
|
eventNotifier.unsubscribe(eventid, cb)
|
|
};
|
|
this.destroy = function(callback) {
|
|
odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorEvent);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorEvent);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphStyleModified, onParagraphStyleModified);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, clearCursorStyle);
|
|
callback()
|
|
};
|
|
function emptyFunction() {
|
|
}
|
|
function init() {
|
|
odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorEvent);
|
|
odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorEvent);
|
|
odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent);
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, onParagraphStyleModified);
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
|
|
odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, clearCursorStyle);
|
|
updateSelectionStylesInfo();
|
|
if(!directParagraphStylingEnabled) {
|
|
self.alignParagraphCenter = emptyFunction;
|
|
self.alignParagraphJustified = emptyFunction;
|
|
self.alignParagraphLeft = emptyFunction;
|
|
self.alignParagraphRight = emptyFunction;
|
|
self.createParagraphStyleOps = function() {
|
|
return[]
|
|
};
|
|
self.indent = emptyFunction;
|
|
self.outdent = emptyFunction
|
|
}
|
|
}
|
|
init()
|
|
};
|
|
gui.DirectFormattingController.textStylingChanged = "textStyling/changed";
|
|
gui.DirectFormattingController.paragraphStylingChanged = "paragraphStyling/changed";
|
|
(function() {
|
|
return gui.DirectFormattingController
|
|
})();
|
|
gui.HyperlinkClickHandler = function HyperlinkClickHandler(getRootNode) {
|
|
var webodfns = "urn:webodf:names:helper", links = "links", inactive = "inactive", None = gui.HyperlinkClickHandler.Modifier.None, Ctrl = gui.HyperlinkClickHandler.Modifier.Ctrl, Meta = gui.HyperlinkClickHandler.Modifier.Meta, odfUtils = new odf.OdfUtils, xpath = xmldom.XPath, modifier = None;
|
|
function getHyperlinkElement(node) {
|
|
while(node !== null) {
|
|
if(odfUtils.isHyperlink(node)) {
|
|
return(node)
|
|
}
|
|
if(odfUtils.isParagraph(node)) {
|
|
break
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
return null
|
|
}
|
|
this.handleClick = function(e) {
|
|
var target = e.target || e.srcElement, modifierPressed, linkElement, url, rootNode, bookmarks;
|
|
if(e.ctrlKey) {
|
|
modifierPressed = Ctrl
|
|
}else {
|
|
if(e.metaKey) {
|
|
modifierPressed = Meta
|
|
}
|
|
}
|
|
if(modifier !== None && modifier !== modifierPressed) {
|
|
return
|
|
}
|
|
linkElement = getHyperlinkElement((target));
|
|
if(!linkElement) {
|
|
return
|
|
}
|
|
url = odfUtils.getHyperlinkTarget(linkElement);
|
|
if(url === "") {
|
|
return
|
|
}
|
|
if(url[0] === "#") {
|
|
url = url.substring(1);
|
|
rootNode = (getRootNode());
|
|
bookmarks = xpath.getODFElementsWithXPath(rootNode, "//text:bookmark-start[@text:name='" + url + "']", odf.Namespaces.lookupNamespaceURI);
|
|
if(bookmarks.length === 0) {
|
|
bookmarks = xpath.getODFElementsWithXPath(rootNode, "//text:bookmark[@text:name='" + url + "']", odf.Namespaces.lookupNamespaceURI)
|
|
}
|
|
if(bookmarks.length > 0) {
|
|
bookmarks[0].scrollIntoView(true)
|
|
}
|
|
}else {
|
|
runtime.getWindow().open(url)
|
|
}
|
|
if(e.preventDefault) {
|
|
e.preventDefault()
|
|
}else {
|
|
e.returnValue = false
|
|
}
|
|
};
|
|
function showPointerCursor() {
|
|
getRootNode().removeAttributeNS(webodfns, links)
|
|
}
|
|
this.showPointerCursor = showPointerCursor;
|
|
function showTextCursor() {
|
|
getRootNode().setAttributeNS(webodfns, links, inactive)
|
|
}
|
|
this.showTextCursor = showTextCursor;
|
|
this.setModifier = function(value) {
|
|
modifier = value;
|
|
if(modifier !== None) {
|
|
showTextCursor()
|
|
}else {
|
|
showPointerCursor()
|
|
}
|
|
}
|
|
};
|
|
gui.HyperlinkClickHandler.Modifier = {None:0, Ctrl:1, Meta:2};
|
|
gui.HyperlinkController = function HyperlinkController(session, inputMemberId) {
|
|
var odfUtils = new odf.OdfUtils, odtDocument = session.getOdtDocument();
|
|
function addHyperlink(hyperlink, insertionText) {
|
|
var selection = odtDocument.getCursorSelection(inputMemberId), op = new ops.OpApplyHyperlink, operations = [];
|
|
if(selection.length === 0 || insertionText) {
|
|
insertionText = insertionText || hyperlink;
|
|
op = new ops.OpInsertText;
|
|
op.init({memberid:inputMemberId, position:selection.position, text:insertionText});
|
|
selection.length = insertionText.length;
|
|
operations.push(op)
|
|
}
|
|
op = new ops.OpApplyHyperlink;
|
|
op.init({memberid:inputMemberId, position:selection.position, length:selection.length, hyperlink:hyperlink});
|
|
operations.push(op);
|
|
session.enqueue(operations)
|
|
}
|
|
this.addHyperlink = addHyperlink;
|
|
function removeHyperlinks() {
|
|
var iterator = gui.SelectionMover.createPositionIterator(odtDocument.getRootNode()), selectedRange = odtDocument.getCursor(inputMemberId).getSelectedRange(), links = odfUtils.getHyperlinkElements(selectedRange), removeEntireLink = selectedRange.collapsed && links.length === 1, domRange = odtDocument.getDOMDocument().createRange(), operations = [], cursorRange, firstLink, lastLink, offset, op;
|
|
if(links.length === 0) {
|
|
return
|
|
}
|
|
links.forEach(function(link) {
|
|
domRange.selectNodeContents(link);
|
|
cursorRange = odtDocument.convertDomToCursorRange({anchorNode:(domRange.startContainer), anchorOffset:domRange.startOffset, focusNode:(domRange.endContainer), focusOffset:domRange.endOffset});
|
|
op = new ops.OpRemoveHyperlink;
|
|
op.init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length});
|
|
operations.push(op)
|
|
});
|
|
if(!removeEntireLink) {
|
|
firstLink = (links[0]);
|
|
if(selectedRange.comparePoint(firstLink, 0) === -1) {
|
|
domRange.setStart(firstLink, 0);
|
|
domRange.setEnd(selectedRange.startContainer, selectedRange.startOffset);
|
|
cursorRange = odtDocument.convertDomToCursorRange({anchorNode:(domRange.startContainer), anchorOffset:domRange.startOffset, focusNode:(domRange.endContainer), focusOffset:domRange.endOffset});
|
|
if(cursorRange.length > 0) {
|
|
op = new ops.OpApplyHyperlink;
|
|
(op).init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length, hyperlink:odfUtils.getHyperlinkTarget(firstLink)});
|
|
operations.push(op)
|
|
}
|
|
}
|
|
lastLink = (links[links.length - 1]);
|
|
iterator.moveToEndOfNode(lastLink);
|
|
offset = iterator.unfilteredDomOffset();
|
|
if(selectedRange.comparePoint(lastLink, offset) === 1) {
|
|
domRange.setStart(selectedRange.endContainer, selectedRange.endOffset);
|
|
domRange.setEnd(lastLink, offset);
|
|
cursorRange = odtDocument.convertDomToCursorRange({anchorNode:(domRange.startContainer), anchorOffset:domRange.startOffset, focusNode:(domRange.endContainer), focusOffset:domRange.endOffset});
|
|
if(cursorRange.length > 0) {
|
|
op = new ops.OpApplyHyperlink;
|
|
(op).init({memberid:inputMemberId, position:cursorRange.position, length:cursorRange.length, hyperlink:odfUtils.getHyperlinkTarget(lastLink)});
|
|
operations.push(op)
|
|
}
|
|
}
|
|
}
|
|
session.enqueue(operations);
|
|
domRange.detach()
|
|
}
|
|
this.removeHyperlinks = removeHyperlinks
|
|
};
|
|
gui.EventManager = function EventManager(odtDocument) {
|
|
var window = (runtime.getWindow()), bindToDirectHandler = {"beforecut":true, "beforepaste":true}, bindToWindow = {"mousedown":true, "mouseup":true, "focus":true}, eventDelegates = {}, eventTrap, canvasElement = odtDocument.getCanvas().getElement();
|
|
function EventDelegate() {
|
|
var self = this, recentEvents = [];
|
|
this.filters = [];
|
|
this.handlers = [];
|
|
this.handleEvent = function(e) {
|
|
if(recentEvents.indexOf(e) === -1) {
|
|
recentEvents.push(e);
|
|
if(self.filters.every(function(filter) {
|
|
return filter(e)
|
|
})) {
|
|
self.handlers.forEach(function(handler) {
|
|
handler(e)
|
|
})
|
|
}
|
|
runtime.setTimeout(function() {
|
|
recentEvents.splice(recentEvents.indexOf(e), 1)
|
|
}, 0)
|
|
}
|
|
}
|
|
}
|
|
function WindowScrollState(window) {
|
|
var x = window.scrollX, y = window.scrollY;
|
|
this.restore = function() {
|
|
if(window.scrollX !== x || window.scrollY !== y) {
|
|
window.scrollTo(x, y)
|
|
}
|
|
}
|
|
}
|
|
function ElementScrollState(element) {
|
|
var top = element.scrollTop, left = element.scrollLeft;
|
|
this.restore = function() {
|
|
if(element.scrollTop !== top || element.scrollLeft !== left) {
|
|
element.scrollTop = top;
|
|
element.scrollLeft = left
|
|
}
|
|
}
|
|
}
|
|
function listenEvent(eventTarget, eventType, eventHandler) {
|
|
var onVariant = "on" + eventType, bound = false;
|
|
if(eventTarget.attachEvent) {
|
|
eventTarget.attachEvent(onVariant, eventHandler);
|
|
bound = true
|
|
}
|
|
if(!bound && eventTarget.addEventListener) {
|
|
eventTarget.addEventListener(eventType, eventHandler, false);
|
|
bound = true
|
|
}
|
|
if((!bound || bindToDirectHandler[eventType]) && eventTarget.hasOwnProperty(onVariant)) {
|
|
eventTarget[onVariant] = eventHandler
|
|
}
|
|
}
|
|
function getDelegateForEvent(eventName, shouldCreate) {
|
|
var delegate = eventDelegates[eventName] || null;
|
|
if(!delegate && shouldCreate) {
|
|
delegate = eventDelegates[eventName] = new EventDelegate;
|
|
if(bindToWindow[eventName]) {
|
|
listenEvent(window, eventName, delegate.handleEvent)
|
|
}
|
|
listenEvent(eventTrap, eventName, delegate.handleEvent);
|
|
listenEvent(canvasElement, eventName, delegate.handleEvent)
|
|
}
|
|
return delegate
|
|
}
|
|
this.addFilter = function(eventName, filter) {
|
|
var delegate = getDelegateForEvent(eventName, true);
|
|
delegate.filters.push(filter)
|
|
};
|
|
this.removeFilter = function(eventName, filter) {
|
|
var delegate = getDelegateForEvent(eventName, true), index = delegate.filters.indexOf(filter);
|
|
if(index !== -1) {
|
|
delegate.filters.splice(index, 1)
|
|
}
|
|
};
|
|
this.subscribe = function(eventName, handler) {
|
|
var delegate = getDelegateForEvent(eventName, true);
|
|
delegate.handlers.push(handler)
|
|
};
|
|
this.unsubscribe = function(eventName, handler) {
|
|
var delegate = getDelegateForEvent(eventName, false), handlerIndex = delegate && delegate.handlers.indexOf(handler);
|
|
if(delegate && handlerIndex !== -1) {
|
|
delegate.handlers.splice(handlerIndex, 1)
|
|
}
|
|
};
|
|
function hasFocus() {
|
|
return odtDocument.getDOMDocument().activeElement === eventTrap
|
|
}
|
|
this.hasFocus = hasFocus;
|
|
function findScrollableParents(element) {
|
|
var scrollParents = [];
|
|
while(element) {
|
|
if(element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight) {
|
|
scrollParents.push(new ElementScrollState(element))
|
|
}
|
|
element = (element.parentNode)
|
|
}
|
|
scrollParents.push(new WindowScrollState(window));
|
|
return scrollParents
|
|
}
|
|
this.focus = function() {
|
|
var scrollParents;
|
|
if(!hasFocus()) {
|
|
scrollParents = findScrollableParents(eventTrap);
|
|
eventTrap.focus();
|
|
scrollParents.forEach(function(scrollParent) {
|
|
scrollParent.restore()
|
|
})
|
|
}
|
|
};
|
|
this.getEventTrap = function() {
|
|
return eventTrap
|
|
};
|
|
this.blur = function() {
|
|
if(hasFocus()) {
|
|
eventTrap.blur()
|
|
}
|
|
};
|
|
this.destroy = function(callback) {
|
|
eventTrap.parentNode.removeChild(eventTrap);
|
|
callback()
|
|
};
|
|
function init() {
|
|
var sizerElement = odtDocument.getOdfCanvas().getSizer(), doc = sizerElement.ownerDocument;
|
|
runtime.assert(Boolean(window), "EventManager requires a window object to operate correctly");
|
|
eventTrap = (doc.createElement("input"));
|
|
eventTrap.id = "eventTrap";
|
|
eventTrap.setAttribute("tabindex", -1);
|
|
sizerElement.appendChild(eventTrap)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.IOSSafariSupport = function(eventManager) {
|
|
var window = runtime.getWindow(), eventTrap = eventManager.getEventTrap();
|
|
function suppressFocusScrollIfKeyboardOpen() {
|
|
if(window.innerHeight !== window.outerHeight) {
|
|
eventTrap.style.display = "none";
|
|
runtime.requestAnimationFrame(function() {
|
|
eventTrap.style.display = "block"
|
|
})
|
|
}
|
|
}
|
|
this.destroy = function(callback) {
|
|
eventManager.unsubscribe("focus", suppressFocusScrollIfKeyboardOpen);
|
|
eventTrap.removeAttribute("autocapitalize");
|
|
callback()
|
|
};
|
|
function init() {
|
|
eventManager.subscribe("focus", suppressFocusScrollIfKeyboardOpen);
|
|
eventTrap.setAttribute("autocapitalize", "off")
|
|
}
|
|
init()
|
|
};
|
|
gui.ImageController = function ImageController(session, inputMemberId, objectNameGenerator) {
|
|
var cmPerPixel = 0.0264583333333334, fileExtensionByMimetype = {"image/gif":".gif", "image/jpeg":".jpg", "image/png":".png"}, textns = odf.Namespaces.textns, odtDocument = session.getOdtDocument(), formatting = odtDocument.getFormatting(), paragraphStyleToPageContentSizeMap = {};
|
|
function createAddGraphicsStyleOp(name) {
|
|
var op = new ops.OpAddStyle;
|
|
op.init({memberid:inputMemberId, styleName:name, styleFamily:"graphic", isAutomaticStyle:false, setProperties:{"style:graphic-properties":{"text:anchor-type":"paragraph", "svg:x":"0cm", "svg:y":"0cm", "style:wrap":"dynamic", "style:number-wrapped-paragraphs":"no-limit", "style:wrap-contour":"false", "style:vertical-pos":"top", "style:vertical-rel":"paragraph", "style:horizontal-pos":"center", "style:horizontal-rel":"paragraph"}}});
|
|
return op
|
|
}
|
|
function createAddFrameStyleOp(styleName, parentStyleName) {
|
|
var op = new ops.OpAddStyle;
|
|
op.init({memberid:inputMemberId, styleName:styleName, styleFamily:"graphic", isAutomaticStyle:true, setProperties:{"style:parent-style-name":parentStyleName, "style:graphic-properties":{"style:vertical-pos":"top", "style:vertical-rel":"baseline", "style:horizontal-pos":"center", "style:horizontal-rel":"paragraph", "fo:background-color":"transparent", "style:background-transparency":"100%", "style:shadow":"none", "style:mirror":"none", "fo:clip":"rect(0cm, 0cm, 0cm, 0cm)", "draw:luminance":"0%",
|
|
"draw:contrast":"0%", "draw:red":"0%", "draw:green":"0%", "draw:blue":"0%", "draw:gamma":"100%", "draw:color-inversion":"false", "draw:image-opacity":"100%", "draw:color-mode":"standard"}}});
|
|
return op
|
|
}
|
|
function getFileExtension(mimetype) {
|
|
mimetype = mimetype.toLowerCase();
|
|
return fileExtensionByMimetype.hasOwnProperty(mimetype) ? fileExtensionByMimetype[mimetype] : null
|
|
}
|
|
function insertImageInternal(mimetype, content, widthInCm, heightInCm) {
|
|
var graphicsStyleName = "Graphics", stylesElement = odtDocument.getOdfCanvas().odfContainer().rootElement.styles, fileExtension = getFileExtension(mimetype), fileName, graphicsStyleElement, frameStyleName, op, operations = [];
|
|
runtime.assert(fileExtension !== null, "Image type is not supported: " + mimetype);
|
|
fileName = "Pictures/" + objectNameGenerator.generateImageName() + fileExtension;
|
|
op = new ops.OpSetBlob;
|
|
op.init({memberid:inputMemberId, filename:fileName, mimetype:mimetype, content:content});
|
|
operations.push(op);
|
|
graphicsStyleElement = formatting.getStyleElement(graphicsStyleName, "graphic", [stylesElement]);
|
|
if(!graphicsStyleElement) {
|
|
op = createAddGraphicsStyleOp(graphicsStyleName);
|
|
operations.push(op)
|
|
}
|
|
frameStyleName = objectNameGenerator.generateStyleName();
|
|
op = createAddFrameStyleOp(frameStyleName, graphicsStyleName);
|
|
operations.push(op);
|
|
op = new ops.OpInsertImage;
|
|
op.init({memberid:inputMemberId, position:odtDocument.getCursorPosition(inputMemberId), filename:fileName, frameWidth:widthInCm + "cm", frameHeight:heightInCm + "cm", frameStyleName:frameStyleName, frameName:objectNameGenerator.generateFrameName()});
|
|
operations.push(op);
|
|
session.enqueue(operations)
|
|
}
|
|
function trimmedSize(originalSize, pageContentSize) {
|
|
var widthRatio = 1, heightRatio = 1, ratio;
|
|
if(originalSize.width > pageContentSize.width) {
|
|
widthRatio = pageContentSize.width / originalSize.width
|
|
}
|
|
if(originalSize.height > pageContentSize.height) {
|
|
heightRatio = pageContentSize.height / originalSize.height
|
|
}
|
|
ratio = Math.min(widthRatio, heightRatio);
|
|
return{width:originalSize.width * ratio, height:originalSize.height * ratio}
|
|
}
|
|
this.insertImage = function(mimetype, content, widthInPx, heightInPx) {
|
|
var paragraphElement, styleName, pageContentSize, originalSize, newSize;
|
|
runtime.assert(widthInPx > 0 && heightInPx > 0, "Both width and height of the image should be greater than 0px.");
|
|
paragraphElement = odtDocument.getParagraphElement(odtDocument.getCursor(inputMemberId).getNode());
|
|
styleName = paragraphElement.getAttributeNS(textns, "style-name");
|
|
if(!paragraphStyleToPageContentSizeMap.hasOwnProperty(styleName)) {
|
|
paragraphStyleToPageContentSizeMap[styleName] = formatting.getContentSize(styleName, "paragraph")
|
|
}
|
|
pageContentSize = paragraphStyleToPageContentSizeMap[styleName];
|
|
originalSize = {width:widthInPx * cmPerPixel, height:heightInPx * cmPerPixel};
|
|
newSize = trimmedSize(originalSize, pageContentSize);
|
|
insertImageInternal(mimetype, content, newSize.width, newSize.height)
|
|
}
|
|
};
|
|
gui.ImageSelector = function ImageSelector(odfCanvas) {
|
|
var svgns = odf.Namespaces.svgns, imageSelectorId = "imageSelector", selectorBorderWidth = 1, squareClassNames = ["topLeft", "topRight", "bottomRight", "bottomLeft", "topMiddle", "rightMiddle", "bottomMiddle", "leftMiddle"], document = odfCanvas.getElement().ownerDocument, hasSelection = false;
|
|
function createSelectorElement() {
|
|
var sizerElement = odfCanvas.getSizer(), selectorElement = (document.createElement("div"));
|
|
selectorElement.id = "imageSelector";
|
|
selectorElement.style.borderWidth = selectorBorderWidth + "px";
|
|
sizerElement.appendChild(selectorElement);
|
|
function createDiv(className) {
|
|
var squareElement = document.createElement("div");
|
|
squareElement.className = className;
|
|
selectorElement.appendChild(squareElement)
|
|
}
|
|
squareClassNames.forEach(createDiv);
|
|
return selectorElement
|
|
}
|
|
function getPosition(element, referenceElement) {
|
|
var rect = element.getBoundingClientRect(), refRect = referenceElement.getBoundingClientRect(), zoomLevel = odfCanvas.getZoomLevel();
|
|
return{left:(rect.left - refRect.left) / zoomLevel - selectorBorderWidth, top:(rect.top - refRect.top) / zoomLevel - selectorBorderWidth}
|
|
}
|
|
this.select = function(frameElement) {
|
|
var selectorElement = document.getElementById(imageSelectorId), position;
|
|
if(!selectorElement) {
|
|
selectorElement = createSelectorElement()
|
|
}
|
|
hasSelection = true;
|
|
position = getPosition(frameElement, (selectorElement.parentNode));
|
|
selectorElement.style.display = "block";
|
|
selectorElement.style.left = position.left + "px";
|
|
selectorElement.style.top = position.top + "px";
|
|
selectorElement.style.width = frameElement.getAttributeNS(svgns, "width");
|
|
selectorElement.style.height = frameElement.getAttributeNS(svgns, "height")
|
|
};
|
|
this.clearSelection = function() {
|
|
var selectorElement;
|
|
if(hasSelection) {
|
|
selectorElement = document.getElementById(imageSelectorId);
|
|
if(selectorElement) {
|
|
selectorElement.style.display = "none"
|
|
}
|
|
}
|
|
hasSelection = false
|
|
};
|
|
this.isSelectorElement = function(node) {
|
|
var selectorElement = document.getElementById(imageSelectorId);
|
|
if(!selectorElement) {
|
|
return false
|
|
}
|
|
return node === selectorElement || node.parentNode === selectorElement
|
|
}
|
|
};
|
|
(function() {
|
|
function DetectSafariCompositionError(eventManager) {
|
|
var lastCompositionValue, suppressedKeyPress = false;
|
|
function suppressIncorrectKeyPress(e) {
|
|
suppressedKeyPress = e.which && String.fromCharCode(e.which) === lastCompositionValue;
|
|
lastCompositionValue = undefined;
|
|
return suppressedKeyPress === false
|
|
}
|
|
function clearSuppression() {
|
|
suppressedKeyPress = false
|
|
}
|
|
function trapComposedValue(e) {
|
|
lastCompositionValue = e.data;
|
|
suppressedKeyPress = false
|
|
}
|
|
function init() {
|
|
eventManager.subscribe("textInput", clearSuppression);
|
|
eventManager.subscribe("compositionend", trapComposedValue);
|
|
eventManager.addFilter("keypress", suppressIncorrectKeyPress)
|
|
}
|
|
this.destroy = function(callback) {
|
|
eventManager.unsubscribe("textInput", clearSuppression);
|
|
eventManager.unsubscribe("compositionend", trapComposedValue);
|
|
eventManager.removeFilter("keypress", suppressIncorrectKeyPress);
|
|
callback()
|
|
};
|
|
init()
|
|
}
|
|
gui.InputMethodEditor = function InputMethodEditor(inputMemberId, eventManager) {
|
|
var cursorns = "urn:webodf:names:cursor", localCursor = null, eventTrap = eventManager.getEventTrap(), doc = (eventTrap.ownerDocument), compositionElement, async = new core.Async, FAKE_CONTENT = "b", processUpdates, pendingEvent = false, pendingData = "", events = new core.EventNotifier([gui.InputMethodEditor.signalCompositionStart, gui.InputMethodEditor.signalCompositionEnd]), lastCompositionData, filters = [], cleanup, isEditable = false;
|
|
this.subscribe = events.subscribe;
|
|
this.unsubscribe = events.unsubscribe;
|
|
function setCursorComposing(state) {
|
|
if(localCursor) {
|
|
if(state) {
|
|
localCursor.getNode().setAttributeNS(cursorns, "composing", "true")
|
|
}else {
|
|
localCursor.getNode().removeAttributeNS(cursorns, "composing");
|
|
compositionElement.textContent = ""
|
|
}
|
|
}
|
|
}
|
|
function flushEvent() {
|
|
if(pendingEvent) {
|
|
pendingEvent = false;
|
|
setCursorComposing(false);
|
|
events.emit(gui.InputMethodEditor.signalCompositionEnd, {data:pendingData});
|
|
pendingData = ""
|
|
}
|
|
}
|
|
function addCompositionData(data) {
|
|
pendingEvent = true;
|
|
pendingData += data;
|
|
processUpdates.trigger()
|
|
}
|
|
function resetWindowSelection() {
|
|
flushEvent();
|
|
if(localCursor && localCursor.getSelectedRange().collapsed) {
|
|
eventTrap.value = ""
|
|
}else {
|
|
eventTrap.value = FAKE_CONTENT
|
|
}
|
|
eventTrap.setSelectionRange(0, eventTrap.value.length)
|
|
}
|
|
function compositionStart() {
|
|
lastCompositionData = undefined;
|
|
processUpdates.cancel();
|
|
setCursorComposing(true);
|
|
if(!pendingEvent) {
|
|
events.emit(gui.InputMethodEditor.signalCompositionStart, {data:""})
|
|
}
|
|
}
|
|
function compositionEnd(e) {
|
|
lastCompositionData = e.data;
|
|
addCompositionData(e.data)
|
|
}
|
|
function textInput(e) {
|
|
if(e.data !== lastCompositionData) {
|
|
addCompositionData(e.data)
|
|
}
|
|
lastCompositionData = undefined
|
|
}
|
|
function synchronizeCompositionText() {
|
|
compositionElement.textContent = eventTrap.value
|
|
}
|
|
this.registerCursor = function(cursor) {
|
|
if(cursor.getMemberId() === inputMemberId) {
|
|
localCursor = cursor;
|
|
localCursor.getNode().appendChild(compositionElement);
|
|
eventManager.subscribe("input", synchronizeCompositionText);
|
|
eventManager.subscribe("compositionupdate", synchronizeCompositionText)
|
|
}
|
|
};
|
|
this.removeCursor = function(memberid) {
|
|
if(localCursor && memberid === inputMemberId) {
|
|
localCursor.getNode().removeChild(compositionElement);
|
|
eventManager.unsubscribe("input", synchronizeCompositionText);
|
|
eventManager.unsubscribe("compositionupdate", synchronizeCompositionText);
|
|
localCursor = null
|
|
}
|
|
};
|
|
function suppressFocus() {
|
|
eventManager.blur();
|
|
eventTrap.setAttribute("disabled", true)
|
|
}
|
|
function synchronizeEventStatus() {
|
|
var hasFocus = eventManager.hasFocus();
|
|
if(hasFocus) {
|
|
eventManager.blur()
|
|
}
|
|
if(isEditable) {
|
|
eventTrap.removeAttribute("disabled")
|
|
}else {
|
|
eventTrap.setAttribute("disabled", true)
|
|
}
|
|
if(hasFocus) {
|
|
eventManager.focus()
|
|
}
|
|
}
|
|
this.setEditing = function(editable) {
|
|
isEditable = editable;
|
|
synchronizeEventStatus()
|
|
};
|
|
this.destroy = function(callback) {
|
|
eventManager.unsubscribe("compositionstart", compositionStart);
|
|
eventManager.unsubscribe("compositionend", compositionEnd);
|
|
eventManager.unsubscribe("textInput", textInput);
|
|
eventManager.unsubscribe("keypress", flushEvent);
|
|
eventManager.unsubscribe("mousedown", suppressFocus);
|
|
eventManager.unsubscribe("mouseup", synchronizeEventStatus);
|
|
eventManager.unsubscribe("focus", resetWindowSelection);
|
|
async.destroyAll(cleanup, callback)
|
|
};
|
|
function init() {
|
|
eventManager.subscribe("compositionstart", compositionStart);
|
|
eventManager.subscribe("compositionend", compositionEnd);
|
|
eventManager.subscribe("textInput", textInput);
|
|
eventManager.subscribe("keypress", flushEvent);
|
|
eventManager.subscribe("mousedown", suppressFocus);
|
|
eventManager.subscribe("mouseup", synchronizeEventStatus);
|
|
eventManager.subscribe("focus", resetWindowSelection);
|
|
filters.push(new DetectSafariCompositionError(eventManager));
|
|
function getDestroy(filter) {
|
|
return filter.destroy
|
|
}
|
|
cleanup = filters.map(getDestroy);
|
|
compositionElement = doc.createElement("span");
|
|
compositionElement.setAttribute("id", "composer");
|
|
processUpdates = new core.ScheduledTask(resetWindowSelection, 1);
|
|
cleanup.push(processUpdates.destroy)
|
|
}
|
|
init()
|
|
};
|
|
gui.InputMethodEditor.signalCompositionStart = "input/compositionstart";
|
|
gui.InputMethodEditor.signalCompositionEnd = "input/compositionend";
|
|
return gui.InputMethodEditor
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.KeyboardHandler = function KeyboardHandler() {
|
|
var modifier = gui.KeyboardHandler.Modifier, defaultBinding = null, bindings = {};
|
|
function getModifiers(e) {
|
|
var modifiers = modifier.None;
|
|
if(e.metaKey) {
|
|
modifiers |= modifier.Meta
|
|
}
|
|
if(e.ctrlKey) {
|
|
modifiers |= modifier.Ctrl
|
|
}
|
|
if(e.altKey) {
|
|
modifiers |= modifier.Alt
|
|
}
|
|
if(e.shiftKey) {
|
|
modifiers |= modifier.Shift
|
|
}
|
|
return modifiers
|
|
}
|
|
function getKeyCombo(keyCode, modifiers) {
|
|
if(!modifiers) {
|
|
modifiers = modifier.None
|
|
}
|
|
return keyCode + ":" + modifiers
|
|
}
|
|
this.setDefault = function(callback) {
|
|
defaultBinding = callback
|
|
};
|
|
this.bind = function(keyCode, modifiers, callback, overwrite) {
|
|
var keyCombo = getKeyCombo(keyCode, modifiers);
|
|
runtime.assert(overwrite || bindings.hasOwnProperty(keyCombo) === false, "tried to overwrite the callback handler of key combo: " + keyCombo);
|
|
bindings[keyCombo] = callback
|
|
};
|
|
this.unbind = function(keyCode, modifiers) {
|
|
var keyCombo = getKeyCombo(keyCode, modifiers);
|
|
delete bindings[keyCombo]
|
|
};
|
|
this.reset = function() {
|
|
defaultBinding = null;
|
|
bindings = {}
|
|
};
|
|
this.handleEvent = function(e) {
|
|
var keyCombo = getKeyCombo(e.keyCode, getModifiers(e)), callback = bindings[keyCombo], handled = false;
|
|
if(callback) {
|
|
handled = callback()
|
|
}else {
|
|
if(defaultBinding !== null) {
|
|
handled = defaultBinding(e)
|
|
}
|
|
}
|
|
if(handled) {
|
|
if(e.preventDefault) {
|
|
e.preventDefault()
|
|
}else {
|
|
e.returnValue = false
|
|
}
|
|
}
|
|
}
|
|
};
|
|
gui.KeyboardHandler.Modifier = {None:0, Meta:1, Ctrl:2, Alt:4, CtrlAlt:6, Shift:8, MetaShift:9, CtrlShift:10, AltShift:12};
|
|
gui.KeyboardHandler.KeyCode = {Backspace:8, Tab:9, Clear:12, Enter:13, Ctrl:17, End:35, Home:36, Left:37, Up:38, Right:39, Down:40, Delete:46, A:65, B:66, C:67, D:68, E:69, F:70, G:71, H:72, I:73, J:74, K:75, L:76, M:77, N:78, O:79, P:80, Q:81, R:82, S:83, T:84, U:85, V:86, W:87, X:88, Y:89, Z:90, LeftMeta:91, MetaInMozilla:224};
|
|
(function() {
|
|
return gui.KeyboardHandler
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.PlainTextPasteboard = function PlainTextPasteboard(odtDocument, inputMemberId) {
|
|
function createOp(op, data) {
|
|
op.init(data);
|
|
return op
|
|
}
|
|
this.createPasteOps = function(data) {
|
|
var originalCursorPosition = odtDocument.getCursorPosition(inputMemberId), cursorPosition = originalCursorPosition, operations = [], paragraphs;
|
|
paragraphs = data.replace(/\r/g, "").split("\n");
|
|
paragraphs.forEach(function(text) {
|
|
operations.push(createOp(new ops.OpSplitParagraph, {memberid:inputMemberId, position:cursorPosition, moveCursor:true}));
|
|
cursorPosition += 1;
|
|
operations.push(createOp(new ops.OpInsertText, {memberid:inputMemberId, position:cursorPosition, text:text, moveCursor:true}));
|
|
cursorPosition += text.length
|
|
});
|
|
operations.push(createOp(new ops.OpRemoveText, {memberid:inputMemberId, position:originalCursorPosition, length:1}));
|
|
return operations
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
odf.WordBoundaryFilter = function WordBoundaryFilter(odtDocument, includeWhitespace) {
|
|
var TEXT_NODE = Node.TEXT_NODE, ELEMENT_NODE = Node.ELEMENT_NODE, odfUtils = new odf.OdfUtils, punctuation = /[!-#%-*,-\/:-;?-@\[-\]_{}\u00a1\u00ab\u00b7\u00bb\u00bf;\u00b7\u055a-\u055f\u0589-\u058a\u05be\u05c0\u05c3\u05c6\u05f3-\u05f4\u0609-\u060a\u060c-\u060d\u061b\u061e-\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0964-\u0965\u0970\u0df4\u0e4f\u0e5a-\u0e5b\u0f04-\u0f12\u0f3a-\u0f3d\u0f85\u0fd0-\u0fd4\u104a-\u104f\u10fb\u1361-\u1368\u166d-\u166e\u169b-\u169c\u16eb-\u16ed\u1735-\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u180a\u1944-\u1945\u19de-\u19df\u1a1e-\u1a1f\u1b5a-\u1b60\u1c3b-\u1c3f\u1c7e-\u1c7f\u2000-\u206e\u207d-\u207e\u208d-\u208e\u3008-\u3009\u2768-\u2775\u27c5-\u27c6\u27e6-\u27ef\u2983-\u2998\u29d8-\u29db\u29fc-\u29fd\u2cf9-\u2cfc\u2cfe-\u2cff\u2e00-\u2e7e\u3000-\u303f\u30a0\u30fb\ua60d-\ua60f\ua673\ua67e\ua874-\ua877\ua8ce-\ua8cf\ua92e-\ua92f\ua95f\uaa5c-\uaa5f\ufd3e-\ufd3f\ufe10-\ufe19\ufe30-\ufe52\ufe54-\ufe61\ufe63\ufe68\ufe6a-\ufe6b\uff01-\uff03\uff05-\uff0a\uff0c-\uff0f\uff1a-\uff1b\uff1f-\uff20\uff3b-\uff3d\uff3f\uff5b\uff5d\uff5f-\uff65]|\ud800[\udd00-\udd01\udf9f\udfd0]|\ud802[\udd1f\udd3f\ude50-\ude58]|\ud809[\udc00-\udc7e]/,
|
|
spacing = /\s/, FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT, FILTER_REJECT = core.PositionFilter.FilterResult.FILTER_REJECT, TRAILING = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, LEADING = odf.WordBoundaryFilter.IncludeWhitespace.LEADING, NeighborType = {NO_NEIGHBOUR:0, SPACE_CHAR:1, PUNCTUATION_CHAR:2, WORD_CHAR:3, OTHER:4};
|
|
function findHigherNeighborNode(node, direction, nodeFilter) {
|
|
var neighboringNode = null, rootNode = odtDocument.getRootNode(), unfilteredCandidate;
|
|
while(node !== rootNode && (node !== null && neighboringNode === null)) {
|
|
unfilteredCandidate = direction < 0 ? node.previousSibling : node.nextSibling;
|
|
if(nodeFilter(unfilteredCandidate) === NodeFilter.FILTER_ACCEPT) {
|
|
neighboringNode = unfilteredCandidate
|
|
}
|
|
node = node.parentNode
|
|
}
|
|
return neighboringNode
|
|
}
|
|
function typeOfNeighbor(node, getOffset) {
|
|
var neighboringChar;
|
|
if(node === null) {
|
|
return NeighborType.NO_NEIGHBOUR
|
|
}
|
|
if(odfUtils.isCharacterElement(node)) {
|
|
return NeighborType.SPACE_CHAR
|
|
}
|
|
if(node.nodeType === TEXT_NODE || (odfUtils.isTextSpan(node) || odfUtils.isHyperlink(node))) {
|
|
neighboringChar = node.textContent.charAt(getOffset());
|
|
if(spacing.test(neighboringChar)) {
|
|
return NeighborType.SPACE_CHAR
|
|
}
|
|
if(punctuation.test(neighboringChar)) {
|
|
return NeighborType.PUNCTUATION_CHAR
|
|
}
|
|
return NeighborType.WORD_CHAR
|
|
}
|
|
return NeighborType.OTHER
|
|
}
|
|
this.acceptPosition = function(iterator) {
|
|
var container = iterator.container(), leftNode = iterator.leftNode(), rightNode = iterator.rightNode(), getRightCharOffset = iterator.unfilteredDomOffset, getLeftCharOffset = function() {
|
|
return iterator.unfilteredDomOffset() - 1
|
|
}, leftNeighborType, rightNeighborType;
|
|
if(container.nodeType === ELEMENT_NODE) {
|
|
if(rightNode === null) {
|
|
rightNode = findHigherNeighborNode(container, 1, iterator.getNodeFilter())
|
|
}
|
|
if(leftNode === null) {
|
|
leftNode = findHigherNeighborNode(container, -1, iterator.getNodeFilter())
|
|
}
|
|
}
|
|
if(container !== rightNode) {
|
|
getRightCharOffset = function() {
|
|
return 0
|
|
}
|
|
}
|
|
if(container !== leftNode && leftNode !== null) {
|
|
getLeftCharOffset = function() {
|
|
return leftNode.textContent.length - 1
|
|
}
|
|
}
|
|
leftNeighborType = typeOfNeighbor(leftNode, getLeftCharOffset);
|
|
rightNeighborType = typeOfNeighbor(rightNode, getRightCharOffset);
|
|
if(leftNeighborType === NeighborType.WORD_CHAR && rightNeighborType === NeighborType.WORD_CHAR || (leftNeighborType === NeighborType.PUNCTUATION_CHAR && rightNeighborType === NeighborType.PUNCTUATION_CHAR || (includeWhitespace === TRAILING && (leftNeighborType !== NeighborType.NO_NEIGHBOUR && rightNeighborType === NeighborType.SPACE_CHAR) || includeWhitespace === LEADING && (leftNeighborType === NeighborType.SPACE_CHAR && rightNeighborType !== NeighborType.NO_NEIGHBOUR)))) {
|
|
return FILTER_REJECT
|
|
}
|
|
return FILTER_ACCEPT
|
|
}
|
|
};
|
|
odf.WordBoundaryFilter.IncludeWhitespace = {None:0, TRAILING:1, LEADING:2};
|
|
(function() {
|
|
return odf.WordBoundaryFilter
|
|
})();
|
|
gui.SelectionController = function SelectionController(session, inputMemberId) {
|
|
var odtDocument = session.getOdtDocument(), domUtils = new core.DomUtils, odfUtils = new odf.OdfUtils, baseFilter = odtDocument.getPositionFilter(), keyboardMovementsFilter = new core.PositionFilterChain, rootFilter = odtDocument.createRootFilter(inputMemberId), TRAILING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, LEADING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.LEADING;
|
|
function createKeyboardStepIterator() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), node = cursor.getNode();
|
|
return odtDocument.createStepIterator(node, 0, [baseFilter, rootFilter], odtDocument.getRootElement(node))
|
|
}
|
|
function createWordBoundaryStepIterator(node, offset, includeWhitespace) {
|
|
var wordBoundaryFilter = new odf.WordBoundaryFilter(odtDocument, includeWhitespace);
|
|
return odtDocument.createStepIterator(node, offset, [baseFilter, rootFilter, wordBoundaryFilter], odtDocument.getRootElement(node))
|
|
}
|
|
function constrain(lookup) {
|
|
return function(originalNode) {
|
|
var originalContainer = lookup(originalNode);
|
|
return function(step, node) {
|
|
return lookup(node) === originalContainer
|
|
}
|
|
}
|
|
}
|
|
function selectionToRange(selection) {
|
|
var hasForwardSelection = domUtils.comparePoints((selection.anchorNode), selection.anchorOffset, (selection.focusNode), selection.focusOffset) >= 0, range = selection.focusNode.ownerDocument.createRange();
|
|
if(hasForwardSelection) {
|
|
range.setStart(selection.anchorNode, selection.anchorOffset);
|
|
range.setEnd(selection.focusNode, selection.focusOffset)
|
|
}else {
|
|
range.setStart(selection.focusNode, selection.focusOffset);
|
|
range.setEnd(selection.anchorNode, selection.anchorOffset)
|
|
}
|
|
return{range:range, hasForwardSelection:hasForwardSelection}
|
|
}
|
|
this.selectionToRange = selectionToRange;
|
|
function rangeToSelection(range, hasForwardSelection) {
|
|
if(hasForwardSelection) {
|
|
return{anchorNode:(range.startContainer), anchorOffset:range.startOffset, focusNode:(range.endContainer), focusOffset:range.endOffset}
|
|
}
|
|
return{anchorNode:(range.endContainer), anchorOffset:range.endOffset, focusNode:(range.startContainer), focusOffset:range.startOffset}
|
|
}
|
|
this.rangeToSelection = rangeToSelection;
|
|
function createOpMoveCursor(position, length, selectionType) {
|
|
var op = new ops.OpMoveCursor;
|
|
op.init({memberid:inputMemberId, position:position, length:length || 0, selectionType:selectionType});
|
|
return op
|
|
}
|
|
function selectImage(frameNode) {
|
|
var frameRoot = odtDocument.getRootElement(frameNode), frameRootFilter = odtDocument.createRootFilter(frameRoot), stepIterator = odtDocument.createStepIterator(frameNode, 0, [frameRootFilter, odtDocument.getPositionFilter()], frameRoot), anchorNode, anchorOffset, newSelection, op;
|
|
if(!stepIterator.roundToPreviousStep()) {
|
|
runtime.assert(false, "No walkable position before frame")
|
|
}
|
|
anchorNode = stepIterator.container();
|
|
anchorOffset = stepIterator.offset();
|
|
stepIterator.setPosition(frameNode, frameNode.childNodes.length);
|
|
if(!stepIterator.roundToNextStep()) {
|
|
runtime.assert(false, "No walkable position after frame")
|
|
}
|
|
newSelection = odtDocument.convertDomToCursorRange({anchorNode:anchorNode, anchorOffset:anchorOffset, focusNode:stepIterator.container(), focusOffset:stepIterator.offset()});
|
|
op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RegionSelection);
|
|
session.enqueue([op])
|
|
}
|
|
this.selectImage = selectImage;
|
|
function expandToWordBoundaries(range) {
|
|
var stepIterator;
|
|
stepIterator = createWordBoundaryStepIterator((range.startContainer), range.startOffset, TRAILING_SPACE);
|
|
if(stepIterator.roundToPreviousStep()) {
|
|
range.setStart(stepIterator.container(), stepIterator.offset())
|
|
}
|
|
stepIterator = createWordBoundaryStepIterator((range.endContainer), range.endOffset, LEADING_SPACE);
|
|
if(stepIterator.roundToNextStep()) {
|
|
range.setEnd(stepIterator.container(), stepIterator.offset())
|
|
}
|
|
}
|
|
this.expandToWordBoundaries = expandToWordBoundaries;
|
|
function expandToParagraphBoundaries(range) {
|
|
var paragraphs = odfUtils.getParagraphElements(range), startParagraph = paragraphs[0], endParagraph = paragraphs[paragraphs.length - 1];
|
|
if(startParagraph) {
|
|
range.setStart(startParagraph, 0)
|
|
}
|
|
if(endParagraph) {
|
|
if(odfUtils.isParagraph(range.endContainer) && range.endOffset === 0) {
|
|
range.setEndBefore(endParagraph)
|
|
}else {
|
|
range.setEnd(endParagraph, endParagraph.childNodes.length)
|
|
}
|
|
}
|
|
}
|
|
this.expandToParagraphBoundaries = expandToParagraphBoundaries;
|
|
function selectRange(range, hasForwardSelection, clickCount) {
|
|
var canvasElement = odtDocument.getOdfCanvas().getElement(), validSelection, startInsideCanvas, endInsideCanvas, existingSelection, newSelection, op;
|
|
startInsideCanvas = domUtils.containsNode(canvasElement, range.startContainer);
|
|
endInsideCanvas = domUtils.containsNode(canvasElement, range.endContainer);
|
|
if(!startInsideCanvas && !endInsideCanvas) {
|
|
return
|
|
}
|
|
if(startInsideCanvas && endInsideCanvas) {
|
|
if(clickCount === 2) {
|
|
expandToWordBoundaries(range)
|
|
}else {
|
|
if(clickCount >= 3) {
|
|
expandToParagraphBoundaries(range)
|
|
}
|
|
}
|
|
}
|
|
validSelection = rangeToSelection(range, hasForwardSelection);
|
|
newSelection = odtDocument.convertDomToCursorRange(validSelection, constrain(odfUtils.getParagraphElement));
|
|
existingSelection = odtDocument.getCursorSelection(inputMemberId);
|
|
if(newSelection.position !== existingSelection.position || newSelection.length !== existingSelection.length) {
|
|
op = createOpMoveCursor(newSelection.position, newSelection.length, ops.OdtCursor.RangeSelection);
|
|
session.enqueue([op])
|
|
}
|
|
}
|
|
this.selectRange = selectRange;
|
|
function extendCursorByAdjustment(lengthAdjust) {
|
|
var selection = odtDocument.getCursorSelection(inputMemberId), stepCounter = odtDocument.getCursor(inputMemberId).getStepCounter(), newLength;
|
|
if(lengthAdjust !== 0) {
|
|
if(lengthAdjust > 0) {
|
|
lengthAdjust = stepCounter.convertForwardStepsBetweenFilters(lengthAdjust, keyboardMovementsFilter, baseFilter)
|
|
}else {
|
|
lengthAdjust = -stepCounter.convertBackwardStepsBetweenFilters(-lengthAdjust, keyboardMovementsFilter, baseFilter)
|
|
}
|
|
newLength = selection.length + lengthAdjust;
|
|
session.enqueue([createOpMoveCursor(selection.position, newLength)])
|
|
}
|
|
}
|
|
function extendSelection(advanceIterator) {
|
|
var stepIterator = createKeyboardStepIterator(), anchorNode = odtDocument.getCursor(inputMemberId).getAnchorNode(), newSelection;
|
|
if(advanceIterator(stepIterator)) {
|
|
newSelection = odtDocument.convertDomToCursorRange({anchorNode:anchorNode, anchorOffset:0, focusNode:stepIterator.container(), focusOffset:stepIterator.offset()});
|
|
session.enqueue([createOpMoveCursor(newSelection.position, newSelection.length)])
|
|
}
|
|
}
|
|
function moveCursorByAdjustment(positionAdjust) {
|
|
var position = odtDocument.getCursorPosition(inputMemberId), stepCounter = odtDocument.getCursor(inputMemberId).getStepCounter();
|
|
if(positionAdjust !== 0) {
|
|
positionAdjust = positionAdjust > 0 ? stepCounter.convertForwardStepsBetweenFilters(positionAdjust, keyboardMovementsFilter, baseFilter) : -stepCounter.convertBackwardStepsBetweenFilters(-positionAdjust, keyboardMovementsFilter, baseFilter);
|
|
position = position + positionAdjust;
|
|
session.enqueue([createOpMoveCursor(position, 0)])
|
|
}
|
|
}
|
|
function moveCursor(advanceIterator) {
|
|
var stepIterator = createKeyboardStepIterator(), position;
|
|
if(advanceIterator(stepIterator)) {
|
|
position = odtDocument.convertDomPointToCursorStep(stepIterator.container(), stepIterator.offset());
|
|
session.enqueue([createOpMoveCursor(position, 0)])
|
|
}
|
|
}
|
|
function moveCursorToLeft() {
|
|
moveCursor(function(iterator) {
|
|
return iterator.previousStep()
|
|
});
|
|
return true
|
|
}
|
|
this.moveCursorToLeft = moveCursorToLeft;
|
|
function moveCursorToRight() {
|
|
moveCursor(function(iterator) {
|
|
return iterator.nextStep()
|
|
});
|
|
return true
|
|
}
|
|
this.moveCursorToRight = moveCursorToRight;
|
|
function extendSelectionToLeft() {
|
|
extendSelection(function(iterator) {
|
|
return iterator.previousStep()
|
|
});
|
|
return true
|
|
}
|
|
this.extendSelectionToLeft = extendSelectionToLeft;
|
|
function extendSelectionToRight() {
|
|
extendSelection(function(iterator) {
|
|
return iterator.nextStep()
|
|
});
|
|
return true
|
|
}
|
|
this.extendSelectionToRight = extendSelectionToRight;
|
|
function moveCursorByLine(direction, extend) {
|
|
var paragraphNode = odtDocument.getParagraphElement(odtDocument.getCursor(inputMemberId).getNode()), steps;
|
|
runtime.assert(Boolean(paragraphNode), "SelectionController: Cursor outside paragraph");
|
|
steps = odtDocument.getCursor(inputMemberId).getStepCounter().countLinesSteps(direction, keyboardMovementsFilter);
|
|
if(extend) {
|
|
extendCursorByAdjustment(steps)
|
|
}else {
|
|
moveCursorByAdjustment(steps)
|
|
}
|
|
}
|
|
function moveCursorUp() {
|
|
moveCursorByLine(-1, false);
|
|
return true
|
|
}
|
|
this.moveCursorUp = moveCursorUp;
|
|
function moveCursorDown() {
|
|
moveCursorByLine(1, false);
|
|
return true
|
|
}
|
|
this.moveCursorDown = moveCursorDown;
|
|
function extendSelectionUp() {
|
|
moveCursorByLine(-1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionUp = extendSelectionUp;
|
|
function extendSelectionDown() {
|
|
moveCursorByLine(1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionDown = extendSelectionDown;
|
|
function moveCursorToLineBoundary(direction, extend) {
|
|
var steps = odtDocument.getCursor(inputMemberId).getStepCounter().countStepsToLineBoundary(direction, keyboardMovementsFilter);
|
|
if(extend) {
|
|
extendCursorByAdjustment(steps)
|
|
}else {
|
|
moveCursorByAdjustment(steps)
|
|
}
|
|
}
|
|
function moveCursorByWord(direction, extend) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), newSelection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), newCursorSelection, selectionUpdated, stepIterator = createWordBoundaryStepIterator(newSelection.focusNode, newSelection.focusOffset, TRAILING_SPACE);
|
|
if(direction >= 0) {
|
|
selectionUpdated = stepIterator.nextStep()
|
|
}else {
|
|
selectionUpdated = stepIterator.previousStep()
|
|
}
|
|
if(selectionUpdated) {
|
|
newSelection.focusNode = stepIterator.container();
|
|
newSelection.focusOffset = stepIterator.offset();
|
|
if(!extend) {
|
|
newSelection.anchorNode = newSelection.focusNode;
|
|
newSelection.anchorOffset = newSelection.focusOffset
|
|
}
|
|
newCursorSelection = odtDocument.convertDomToCursorRange(newSelection);
|
|
session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)])
|
|
}
|
|
}
|
|
function moveCursorBeforeWord() {
|
|
moveCursorByWord(-1, false);
|
|
return true
|
|
}
|
|
this.moveCursorBeforeWord = moveCursorBeforeWord;
|
|
function moveCursorPastWord() {
|
|
moveCursorByWord(1, false);
|
|
return true
|
|
}
|
|
this.moveCursorPastWord = moveCursorPastWord;
|
|
function extendSelectionBeforeWord() {
|
|
moveCursorByWord(-1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionBeforeWord = extendSelectionBeforeWord;
|
|
function extendSelectionPastWord() {
|
|
moveCursorByWord(1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionPastWord = extendSelectionPastWord;
|
|
function moveCursorToLineStart() {
|
|
moveCursorToLineBoundary(-1, false);
|
|
return true
|
|
}
|
|
this.moveCursorToLineStart = moveCursorToLineStart;
|
|
function moveCursorToLineEnd() {
|
|
moveCursorToLineBoundary(1, false);
|
|
return true
|
|
}
|
|
this.moveCursorToLineEnd = moveCursorToLineEnd;
|
|
function extendSelectionToLineStart() {
|
|
moveCursorToLineBoundary(-1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionToLineStart = extendSelectionToLineStart;
|
|
function extendSelectionToLineEnd() {
|
|
moveCursorToLineBoundary(1, true);
|
|
return true
|
|
}
|
|
this.extendSelectionToLineEnd = extendSelectionToLineEnd;
|
|
function extendCursorToNodeBoundary(direction, getContainmentNode) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), node = getContainmentNode(cursor.getNode()), selection = rangeToSelection(cursor.getSelectedRange(), cursor.hasForwardSelection()), newCursorSelection;
|
|
runtime.assert(Boolean(node), "SelectionController: Cursor outside root");
|
|
if(direction < 0) {
|
|
selection.focusNode = (node);
|
|
selection.focusOffset = 0
|
|
}else {
|
|
selection.focusNode = (node);
|
|
selection.focusOffset = node.childNodes.length
|
|
}
|
|
newCursorSelection = odtDocument.convertDomToCursorRange(selection, constrain(getContainmentNode));
|
|
session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)])
|
|
}
|
|
function extendSelectionToParagraphStart() {
|
|
extendCursorToNodeBoundary(-1, odtDocument.getParagraphElement);
|
|
return true
|
|
}
|
|
this.extendSelectionToParagraphStart = extendSelectionToParagraphStart;
|
|
function extendSelectionToParagraphEnd() {
|
|
extendCursorToNodeBoundary(1, odtDocument.getParagraphElement);
|
|
return true
|
|
}
|
|
this.extendSelectionToParagraphEnd = extendSelectionToParagraphEnd;
|
|
function moveCursorToRootBoundary(direction) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), root = odtDocument.getRootElement(cursor.getNode()), newPosition;
|
|
runtime.assert(Boolean(root), "SelectionController: Cursor outside root");
|
|
if(direction < 0) {
|
|
newPosition = odtDocument.convertDomPointToCursorStep(root, 0, function(step) {
|
|
return step === ops.StepsTranslator.NEXT_STEP
|
|
})
|
|
}else {
|
|
newPosition = odtDocument.convertDomPointToCursorStep(root, root.childNodes.length)
|
|
}
|
|
session.enqueue([createOpMoveCursor(newPosition, 0)]);
|
|
return true
|
|
}
|
|
function moveCursorToDocumentStart() {
|
|
moveCursorToRootBoundary(-1);
|
|
return true
|
|
}
|
|
this.moveCursorToDocumentStart = moveCursorToDocumentStart;
|
|
function moveCursorToDocumentEnd() {
|
|
moveCursorToRootBoundary(1);
|
|
return true
|
|
}
|
|
this.moveCursorToDocumentEnd = moveCursorToDocumentEnd;
|
|
function extendSelectionToDocumentStart() {
|
|
extendCursorToNodeBoundary(-1, odtDocument.getRootElement);
|
|
return true
|
|
}
|
|
this.extendSelectionToDocumentStart = extendSelectionToDocumentStart;
|
|
function extendSelectionToDocumentEnd() {
|
|
extendCursorToNodeBoundary(1, odtDocument.getRootElement);
|
|
return true
|
|
}
|
|
this.extendSelectionToDocumentEnd = extendSelectionToDocumentEnd;
|
|
function extendSelectionToEntireDocument() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), root = odtDocument.getRootElement(cursor.getNode()), newSelection, newCursorSelection;
|
|
runtime.assert(Boolean(root), "SelectionController: Cursor outside root");
|
|
newSelection = {anchorNode:root, anchorOffset:0, focusNode:root, focusOffset:root.childNodes.length};
|
|
newCursorSelection = odtDocument.convertDomToCursorRange(newSelection, constrain(odtDocument.getRootElement));
|
|
session.enqueue([createOpMoveCursor(newCursorSelection.position, newCursorSelection.length)]);
|
|
return true
|
|
}
|
|
this.extendSelectionToEntireDocument = extendSelectionToEntireDocument;
|
|
function init() {
|
|
keyboardMovementsFilter.addFilter(baseFilter);
|
|
keyboardMovementsFilter.addFilter(odtDocument.createRootFilter(inputMemberId))
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.TextController = function TextController(session, inputMemberId, directStyleOp, paragraphStyleOps) {
|
|
var odtDocument = session.getOdtDocument(), FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT;
|
|
function createOpRemoveSelection(selection) {
|
|
var op = new ops.OpRemoveText;
|
|
op.init({memberid:inputMemberId, position:selection.position, length:selection.length});
|
|
return op
|
|
}
|
|
function toForwardSelection(selection) {
|
|
if(selection.length < 0) {
|
|
selection.position += selection.length;
|
|
selection.length = -selection.length
|
|
}
|
|
return selection
|
|
}
|
|
this.enqueueParagraphSplittingOps = function() {
|
|
var selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op, operations = [], styleOps;
|
|
if(selection.length > 0) {
|
|
op = createOpRemoveSelection(selection);
|
|
operations.push(op)
|
|
}
|
|
op = new ops.OpSplitParagraph;
|
|
op.init({memberid:inputMemberId, position:selection.position, moveCursor:true});
|
|
operations.push(op);
|
|
if(paragraphStyleOps) {
|
|
styleOps = paragraphStyleOps(selection.position + 1);
|
|
operations = operations.concat(styleOps)
|
|
}
|
|
session.enqueue(operations);
|
|
return true
|
|
};
|
|
function hasPositionInDirection(cursorNode, forward) {
|
|
var rootConstrainedFilter = new core.PositionFilterChain, iterator = gui.SelectionMover.createPositionIterator(odtDocument.getRootElement(cursorNode)), nextPosition = (forward ? iterator.nextPosition : iterator.previousPosition);
|
|
rootConstrainedFilter.addFilter(odtDocument.getPositionFilter());
|
|
rootConstrainedFilter.addFilter(odtDocument.createRootFilter(inputMemberId));
|
|
iterator.setUnfilteredPosition(cursorNode, 0);
|
|
while(nextPosition()) {
|
|
if(rootConstrainedFilter.acceptPosition(iterator) === FILTER_ACCEPT) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
this.removeTextByBackspaceKey = function() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op = null;
|
|
if(selection.length === 0) {
|
|
if(hasPositionInDirection(cursor.getNode(), false)) {
|
|
op = new ops.OpRemoveText;
|
|
op.init({memberid:inputMemberId, position:selection.position - 1, length:1});
|
|
session.enqueue([op])
|
|
}
|
|
}else {
|
|
op = createOpRemoveSelection(selection);
|
|
session.enqueue([op])
|
|
}
|
|
return op !== null
|
|
};
|
|
this.removeTextByDeleteKey = function() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op = null;
|
|
if(selection.length === 0) {
|
|
if(hasPositionInDirection(cursor.getNode(), true)) {
|
|
op = new ops.OpRemoveText;
|
|
op.init({memberid:inputMemberId, position:selection.position, length:1});
|
|
session.enqueue([op])
|
|
}
|
|
}else {
|
|
op = createOpRemoveSelection(selection);
|
|
session.enqueue([op])
|
|
}
|
|
return op !== null
|
|
};
|
|
this.removeCurrentSelection = function() {
|
|
var selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op;
|
|
if(selection.length !== 0) {
|
|
op = createOpRemoveSelection(selection);
|
|
session.enqueue([op])
|
|
}
|
|
return true
|
|
};
|
|
function insertText(text) {
|
|
var selection = toForwardSelection(odtDocument.getCursorSelection(inputMemberId)), op, stylingOp, operations = [], useCachedStyle = false;
|
|
if(selection.length > 0) {
|
|
op = createOpRemoveSelection(selection);
|
|
operations.push(op);
|
|
useCachedStyle = true
|
|
}
|
|
op = new ops.OpInsertText;
|
|
op.init({memberid:inputMemberId, position:selection.position, text:text, moveCursor:true});
|
|
operations.push(op);
|
|
if(directStyleOp) {
|
|
stylingOp = directStyleOp(selection.position, text.length, useCachedStyle);
|
|
if(stylingOp) {
|
|
operations.push(stylingOp)
|
|
}
|
|
}
|
|
session.enqueue(operations)
|
|
}
|
|
this.insertText = insertText
|
|
};
|
|
(function() {
|
|
return gui.TextController
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.UndoManager = function UndoManager() {
|
|
};
|
|
gui.UndoManager.prototype.subscribe = function(signal, callback) {
|
|
};
|
|
gui.UndoManager.prototype.unsubscribe = function(signal, callback) {
|
|
};
|
|
gui.UndoManager.prototype.setDocument = function(newDocument) {
|
|
};
|
|
gui.UndoManager.prototype.setInitialState = function() {
|
|
};
|
|
gui.UndoManager.prototype.initialize = function() {
|
|
};
|
|
gui.UndoManager.prototype.purgeInitialState = function() {
|
|
};
|
|
gui.UndoManager.prototype.setPlaybackFunction = function(playback_func) {
|
|
};
|
|
gui.UndoManager.prototype.hasUndoStates = function() {
|
|
};
|
|
gui.UndoManager.prototype.hasRedoStates = function() {
|
|
};
|
|
gui.UndoManager.prototype.moveForward = function(states) {
|
|
};
|
|
gui.UndoManager.prototype.moveBackward = function(states) {
|
|
};
|
|
gui.UndoManager.prototype.onOperationExecuted = function(op) {
|
|
};
|
|
gui.UndoManager.signalUndoStackChanged = "undoStackChanged";
|
|
gui.UndoManager.signalUndoStateCreated = "undoStateCreated";
|
|
gui.UndoManager.signalUndoStateModified = "undoStateModified";
|
|
(function() {
|
|
return gui.UndoManager
|
|
})();
|
|
(function() {
|
|
var FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT;
|
|
gui.SessionController = function SessionController(session, inputMemberId, shadowCursor, args) {
|
|
var window = (runtime.getWindow()), odtDocument = session.getOdtDocument(), async = new core.Async, domUtils = new core.DomUtils, odfUtils = new odf.OdfUtils, mimeDataExporter = new gui.MimeDataExporter, clipboard = new gui.Clipboard(mimeDataExporter), keyDownHandler = new gui.KeyboardHandler, keyPressHandler = new gui.KeyboardHandler, keyUpHandler = new gui.KeyboardHandler, clickStartedWithinCanvas = false, objectNameGenerator = new odf.ObjectNameGenerator(odtDocument.getOdfCanvas().odfContainer(),
|
|
inputMemberId), isMouseMoved = false, mouseDownRootFilter = null, handleMouseClickTimeoutId, undoManager = null, eventManager = new gui.EventManager(odtDocument), annotationController = new gui.AnnotationController(session, inputMemberId), directFormattingController = new gui.DirectFormattingController(session, inputMemberId, objectNameGenerator, args.directParagraphStylingEnabled), createCursorStyleOp = (directFormattingController.createCursorStyleOp), createParagraphStyleOps = (directFormattingController.createParagraphStyleOps),
|
|
textController = new gui.TextController(session, inputMemberId, createCursorStyleOp, createParagraphStyleOps), imageController = new gui.ImageController(session, inputMemberId, objectNameGenerator), imageSelector = new gui.ImageSelector(odtDocument.getOdfCanvas()), shadowCursorIterator = gui.SelectionMover.createPositionIterator(odtDocument.getRootNode()), drawShadowCursorTask, redrawRegionSelectionTask, pasteHandler = new gui.PlainTextPasteboard(odtDocument, inputMemberId), inputMethodEditor =
|
|
new gui.InputMethodEditor(inputMemberId, eventManager), clickCount = 0, hyperlinkClickHandler = new gui.HyperlinkClickHandler(odtDocument.getRootNode), hyperlinkController = new gui.HyperlinkController(session, inputMemberId), selectionController = new gui.SelectionController(session, inputMemberId), modifier = gui.KeyboardHandler.Modifier, keyCode = gui.KeyboardHandler.KeyCode, isMacOS = window.navigator.appVersion.toLowerCase().indexOf("mac") !== -1, isIOS = ["iPad", "iPod", "iPhone"].indexOf(window.navigator.platform) !==
|
|
-1, iOSSafariSupport;
|
|
runtime.assert(window !== null, "Expected to be run in an environment which has a global window, like a browser.");
|
|
function getTarget(e) {
|
|
return(e.target) || (e.srcElement || null)
|
|
}
|
|
function cancelEvent(event) {
|
|
if(event.preventDefault) {
|
|
event.preventDefault()
|
|
}else {
|
|
event.returnValue = false
|
|
}
|
|
}
|
|
function caretPositionFromPoint(x, y) {
|
|
var doc = odtDocument.getDOMDocument(), c, result = null;
|
|
if(doc.caretRangeFromPoint) {
|
|
c = doc.caretRangeFromPoint(x, y);
|
|
result = {container:(c.startContainer), offset:c.startOffset}
|
|
}else {
|
|
if(doc.caretPositionFromPoint) {
|
|
c = doc.caretPositionFromPoint(x, y);
|
|
if(c && c.offsetNode) {
|
|
result = {container:c.offsetNode, offset:c.offset}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
function redrawRegionSelection() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), imageElement;
|
|
if(cursor && cursor.getSelectionType() === ops.OdtCursor.RegionSelection) {
|
|
imageElement = odfUtils.getImageElements(cursor.getSelectedRange())[0];
|
|
if(imageElement) {
|
|
imageSelector.select((imageElement.parentNode));
|
|
return
|
|
}
|
|
}
|
|
imageSelector.clearSelection()
|
|
}
|
|
function stringFromKeyPress(event) {
|
|
if(event.which === null || event.which === undefined) {
|
|
return String.fromCharCode(event.keyCode)
|
|
}
|
|
if(event.which !== 0 && event.charCode !== 0) {
|
|
return String.fromCharCode(event.which)
|
|
}
|
|
return null
|
|
}
|
|
function handleCut(e) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
|
|
if(selectedRange.collapsed) {
|
|
e.preventDefault();
|
|
return
|
|
}
|
|
if(clipboard.setDataFromRange(e, selectedRange)) {
|
|
textController.removeCurrentSelection()
|
|
}else {
|
|
runtime.log("Cut operation failed")
|
|
}
|
|
}
|
|
function handleBeforeCut() {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
|
|
return selectedRange.collapsed !== false
|
|
}
|
|
function handleCopy(e) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
|
|
if(selectedRange.collapsed) {
|
|
e.preventDefault();
|
|
return
|
|
}
|
|
if(!clipboard.setDataFromRange(e, selectedRange)) {
|
|
runtime.log("Copy operation failed")
|
|
}
|
|
}
|
|
function handlePaste(e) {
|
|
var plainText;
|
|
if(window.clipboardData && window.clipboardData.getData) {
|
|
plainText = window.clipboardData.getData("Text")
|
|
}else {
|
|
if(e.clipboardData && e.clipboardData.getData) {
|
|
plainText = e.clipboardData.getData("text/plain")
|
|
}
|
|
}
|
|
if(plainText) {
|
|
textController.removeCurrentSelection();
|
|
session.enqueue(pasteHandler.createPasteOps(plainText))
|
|
}
|
|
cancelEvent(e)
|
|
}
|
|
function handleBeforePaste() {
|
|
return false
|
|
}
|
|
function updateUndoStack(op) {
|
|
if(undoManager) {
|
|
undoManager.onOperationExecuted(op)
|
|
}
|
|
}
|
|
function forwardUndoStackChange(e) {
|
|
odtDocument.emit(ops.OdtDocument.signalUndoStackChanged, e)
|
|
}
|
|
function undo() {
|
|
var eventTrap = eventManager.getEventTrap(), sizer, hadFocusBefore;
|
|
if(undoManager) {
|
|
hadFocusBefore = eventManager.hasFocus();
|
|
undoManager.moveBackward(1);
|
|
sizer = odtDocument.getOdfCanvas().getSizer();
|
|
if(!domUtils.containsNode(sizer, eventTrap)) {
|
|
sizer.appendChild(eventTrap);
|
|
if(hadFocusBefore) {
|
|
eventManager.focus()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
this.undo = undo;
|
|
function redo() {
|
|
var hadFocusBefore;
|
|
if(undoManager) {
|
|
hadFocusBefore = eventManager.hasFocus();
|
|
undoManager.moveForward(1);
|
|
if(hadFocusBefore) {
|
|
eventManager.focus()
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
this.redo = redo;
|
|
function updateShadowCursor() {
|
|
var selection = window.getSelection(), selectionRange = selection.rangeCount > 0 && selectionController.selectionToRange(selection);
|
|
if(clickStartedWithinCanvas && selectionRange) {
|
|
isMouseMoved = true;
|
|
imageSelector.clearSelection();
|
|
shadowCursorIterator.setUnfilteredPosition((selection.focusNode), selection.focusOffset);
|
|
if(mouseDownRootFilter.acceptPosition(shadowCursorIterator) === FILTER_ACCEPT) {
|
|
if(clickCount === 2) {
|
|
selectionController.expandToWordBoundaries(selectionRange.range)
|
|
}else {
|
|
if(clickCount >= 3) {
|
|
selectionController.expandToParagraphBoundaries(selectionRange.range)
|
|
}
|
|
}
|
|
shadowCursor.setSelectedRange(selectionRange.range, selectionRange.hasForwardSelection);
|
|
odtDocument.emit(ops.Document.signalCursorMoved, shadowCursor)
|
|
}
|
|
}
|
|
}
|
|
function synchronizeWindowSelection(cursor) {
|
|
var selection = window.getSelection(), range = cursor.getSelectedRange();
|
|
if(selection.extend) {
|
|
if(cursor.hasForwardSelection()) {
|
|
selection.collapse(range.startContainer, range.startOffset);
|
|
selection.extend(range.endContainer, range.endOffset)
|
|
}else {
|
|
selection.collapse(range.endContainer, range.endOffset);
|
|
selection.extend(range.startContainer, range.startOffset)
|
|
}
|
|
}else {
|
|
selection.removeAllRanges();
|
|
selection.addRange(range.cloneRange())
|
|
}
|
|
}
|
|
function handleMouseDown(e) {
|
|
var target = getTarget(e), cursor = odtDocument.getCursor(inputMemberId);
|
|
clickStartedWithinCanvas = target !== null && domUtils.containsNode(odtDocument.getOdfCanvas().getElement(), target);
|
|
if(clickStartedWithinCanvas) {
|
|
isMouseMoved = false;
|
|
mouseDownRootFilter = odtDocument.createRootFilter((target));
|
|
clickCount = e.detail;
|
|
if(cursor && e.shiftKey) {
|
|
window.getSelection().collapse(cursor.getAnchorNode(), 0)
|
|
}else {
|
|
synchronizeWindowSelection(cursor)
|
|
}
|
|
if(clickCount > 1) {
|
|
updateShadowCursor()
|
|
}
|
|
}
|
|
}
|
|
function mutableSelection(selection) {
|
|
if(selection) {
|
|
return{anchorNode:selection.anchorNode, anchorOffset:selection.anchorOffset, focusNode:selection.focusNode, focusOffset:selection.focusOffset}
|
|
}
|
|
return null
|
|
}
|
|
function getNextWalkablePosition(node) {
|
|
var root = odtDocument.getRootElement(node), rootFilter = odtDocument.createRootFilter(root), stepIterator = odtDocument.createStepIterator(node, 0, [rootFilter, odtDocument.getPositionFilter()], root);
|
|
stepIterator.setPosition(node, node.childNodes.length);
|
|
if(!stepIterator.roundToNextStep()) {
|
|
return null
|
|
}
|
|
return{container:stepIterator.container(), offset:stepIterator.offset()}
|
|
}
|
|
function moveByMouseClickEvent(event) {
|
|
var selection = mutableSelection(window.getSelection()), position, selectionRange, rect, frameNode;
|
|
if(!selection.anchorNode && !selection.focusNode) {
|
|
position = caretPositionFromPoint(event.clientX, event.clientY);
|
|
if(position) {
|
|
selection.anchorNode = (position.container);
|
|
selection.anchorOffset = position.offset;
|
|
selection.focusNode = selection.anchorNode;
|
|
selection.focusOffset = selection.anchorOffset
|
|
}
|
|
}
|
|
if(odfUtils.isImage(selection.focusNode) && (selection.focusOffset === 0 && odfUtils.isCharacterFrame(selection.focusNode.parentNode))) {
|
|
frameNode = (selection.focusNode.parentNode);
|
|
rect = frameNode.getBoundingClientRect();
|
|
if(event.clientX > rect.right) {
|
|
position = getNextWalkablePosition(frameNode);
|
|
if(position) {
|
|
selection.anchorNode = selection.focusNode = position.container;
|
|
selection.anchorOffset = selection.focusOffset = position.offset
|
|
}
|
|
}
|
|
}else {
|
|
if(odfUtils.isImage(selection.focusNode.firstChild) && (selection.focusOffset === 1 && odfUtils.isCharacterFrame(selection.focusNode))) {
|
|
position = getNextWalkablePosition(selection.focusNode);
|
|
if(position) {
|
|
selection.anchorNode = selection.focusNode = position.container;
|
|
selection.anchorOffset = selection.focusOffset = position.offset
|
|
}
|
|
}
|
|
}
|
|
if(selection.anchorNode && selection.focusNode) {
|
|
selectionRange = selectionController.selectionToRange(selection);
|
|
selectionController.selectRange(selectionRange.range, selectionRange.hasForwardSelection, event.detail)
|
|
}
|
|
eventManager.focus()
|
|
}
|
|
function handleMouseClickEvent(event) {
|
|
var target = getTarget(event), range, wasCollapsed, frameNode, pos;
|
|
drawShadowCursorTask.processRequests();
|
|
if(odfUtils.isImage(target) && (odfUtils.isCharacterFrame(target.parentNode) && window.getSelection().isCollapsed)) {
|
|
selectionController.selectImage((target.parentNode));
|
|
eventManager.focus()
|
|
}else {
|
|
if(imageSelector.isSelectorElement(target)) {
|
|
eventManager.focus()
|
|
}else {
|
|
if(clickStartedWithinCanvas) {
|
|
if(isMouseMoved) {
|
|
range = shadowCursor.getSelectedRange();
|
|
wasCollapsed = range.collapsed;
|
|
if(odfUtils.isImage(range.endContainer) && (range.endOffset === 0 && odfUtils.isCharacterFrame(range.endContainer.parentNode))) {
|
|
frameNode = (range.endContainer.parentNode);
|
|
pos = getNextWalkablePosition(frameNode);
|
|
if(pos) {
|
|
range.setEnd(pos.container, pos.offset);
|
|
if(wasCollapsed) {
|
|
range.collapse(false)
|
|
}
|
|
}
|
|
}
|
|
selectionController.selectRange(range, shadowCursor.hasForwardSelection(), event.detail);
|
|
eventManager.focus()
|
|
}else {
|
|
if(isIOS) {
|
|
moveByMouseClickEvent(event)
|
|
}else {
|
|
handleMouseClickTimeoutId = runtime.setTimeout(function() {
|
|
moveByMouseClickEvent(event)
|
|
}, 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
clickCount = 0;
|
|
clickStartedWithinCanvas = false;
|
|
isMouseMoved = false
|
|
}
|
|
function handleDragStart(e) {
|
|
var cursor = odtDocument.getCursor(inputMemberId), selectedRange = cursor.getSelectedRange();
|
|
if(selectedRange.collapsed) {
|
|
return
|
|
}
|
|
mimeDataExporter.exportRangeToDataTransfer((e.dataTransfer), selectedRange)
|
|
}
|
|
function handleDragEnd() {
|
|
if(clickStartedWithinCanvas) {
|
|
eventManager.focus()
|
|
}
|
|
clickCount = 0;
|
|
clickStartedWithinCanvas = false;
|
|
isMouseMoved = false
|
|
}
|
|
function handleContextMenu(e) {
|
|
handleMouseClickEvent(e)
|
|
}
|
|
function handleMouseUp(event) {
|
|
var target = (getTarget(event)), annotationNode = null;
|
|
if(target.className === "annotationRemoveButton") {
|
|
annotationNode = domUtils.getElementsByTagNameNS((target.parentNode), odf.Namespaces.officens, "annotation")[0];
|
|
annotationController.removeAnnotation(annotationNode);
|
|
eventManager.focus()
|
|
}else {
|
|
handleMouseClickEvent(event)
|
|
}
|
|
}
|
|
function insertNonEmptyData(e) {
|
|
var input = e.data;
|
|
if(input) {
|
|
textController.insertText(input)
|
|
}
|
|
}
|
|
function returnTrue(fn) {
|
|
return function() {
|
|
fn();
|
|
return true
|
|
}
|
|
}
|
|
function rangeSelectionOnly(fn) {
|
|
return function(e) {
|
|
var selectionType = odtDocument.getCursor(inputMemberId).getSelectionType();
|
|
if(selectionType === ops.OdtCursor.RangeSelection) {
|
|
return fn(e)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
function insertLocalCursor() {
|
|
runtime.assert(session.getOdtDocument().getCursor(inputMemberId) === undefined, "Inserting local cursor a second time.");
|
|
var op = new ops.OpAddCursor;
|
|
op.init({memberid:inputMemberId});
|
|
session.enqueue([op]);
|
|
eventManager.focus()
|
|
}
|
|
this.insertLocalCursor = insertLocalCursor;
|
|
function removeLocalCursor() {
|
|
runtime.assert(session.getOdtDocument().getCursor(inputMemberId) !== undefined, "Removing local cursor without inserting before.");
|
|
var op = new ops.OpRemoveCursor;
|
|
op.init({memberid:inputMemberId});
|
|
session.enqueue([op])
|
|
}
|
|
this.removeLocalCursor = removeLocalCursor;
|
|
this.startEditing = function() {
|
|
inputMethodEditor.subscribe(gui.InputMethodEditor.signalCompositionStart, textController.removeCurrentSelection);
|
|
inputMethodEditor.subscribe(gui.InputMethodEditor.signalCompositionEnd, insertNonEmptyData);
|
|
eventManager.subscribe("beforecut", handleBeforeCut);
|
|
eventManager.subscribe("cut", handleCut);
|
|
eventManager.subscribe("beforepaste", handleBeforePaste);
|
|
eventManager.subscribe("paste", handlePaste);
|
|
window.addEventListener("focus", hyperlinkClickHandler.showTextCursor, false);
|
|
if(undoManager) {
|
|
undoManager.initialize()
|
|
}
|
|
inputMethodEditor.setEditing(true);
|
|
hyperlinkClickHandler.setModifier(isMacOS ? gui.HyperlinkClickHandler.Modifier.Meta : gui.HyperlinkClickHandler.Modifier.Ctrl);
|
|
keyDownHandler.bind(keyCode.Backspace, modifier.None, returnTrue(textController.removeTextByBackspaceKey), true);
|
|
keyDownHandler.bind(keyCode.Delete, modifier.None, textController.removeTextByDeleteKey);
|
|
keyDownHandler.bind(keyCode.Tab, modifier.None, rangeSelectionOnly(function() {
|
|
textController.insertText("\t");
|
|
return true
|
|
}));
|
|
if(isMacOS) {
|
|
keyDownHandler.bind(keyCode.Clear, modifier.None, textController.removeCurrentSelection);
|
|
keyDownHandler.bind(keyCode.B, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleBold));
|
|
keyDownHandler.bind(keyCode.I, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleItalic));
|
|
keyDownHandler.bind(keyCode.U, modifier.Meta, rangeSelectionOnly(directFormattingController.toggleUnderline));
|
|
keyDownHandler.bind(keyCode.L, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphLeft));
|
|
keyDownHandler.bind(keyCode.E, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphCenter));
|
|
keyDownHandler.bind(keyCode.R, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphRight));
|
|
keyDownHandler.bind(keyCode.J, modifier.MetaShift, rangeSelectionOnly(directFormattingController.alignParagraphJustified));
|
|
keyDownHandler.bind(keyCode.C, modifier.MetaShift, annotationController.addAnnotation);
|
|
keyDownHandler.bind(keyCode.Z, modifier.Meta, undo);
|
|
keyDownHandler.bind(keyCode.Z, modifier.MetaShift, redo);
|
|
keyDownHandler.bind(keyCode.LeftMeta, modifier.Meta, hyperlinkClickHandler.showPointerCursor);
|
|
keyDownHandler.bind(keyCode.MetaInMozilla, modifier.Meta, hyperlinkClickHandler.showPointerCursor);
|
|
keyUpHandler.bind(keyCode.LeftMeta, modifier.None, hyperlinkClickHandler.showTextCursor);
|
|
keyUpHandler.bind(keyCode.MetaInMozilla, modifier.None, hyperlinkClickHandler.showTextCursor)
|
|
}else {
|
|
keyDownHandler.bind(keyCode.B, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleBold));
|
|
keyDownHandler.bind(keyCode.I, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleItalic));
|
|
keyDownHandler.bind(keyCode.U, modifier.Ctrl, rangeSelectionOnly(directFormattingController.toggleUnderline));
|
|
keyDownHandler.bind(keyCode.L, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphLeft));
|
|
keyDownHandler.bind(keyCode.E, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphCenter));
|
|
keyDownHandler.bind(keyCode.R, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphRight));
|
|
keyDownHandler.bind(keyCode.J, modifier.CtrlShift, rangeSelectionOnly(directFormattingController.alignParagraphJustified));
|
|
keyDownHandler.bind(keyCode.C, modifier.CtrlAlt, annotationController.addAnnotation);
|
|
keyDownHandler.bind(keyCode.Z, modifier.Ctrl, undo);
|
|
keyDownHandler.bind(keyCode.Z, modifier.CtrlShift, redo);
|
|
keyDownHandler.bind(keyCode.Ctrl, modifier.Ctrl, hyperlinkClickHandler.showPointerCursor);
|
|
keyUpHandler.bind(keyCode.Ctrl, modifier.None, hyperlinkClickHandler.showTextCursor)
|
|
}
|
|
function handler(e) {
|
|
var text = stringFromKeyPress(e);
|
|
if(text && !(e.altKey || (e.ctrlKey || e.metaKey))) {
|
|
textController.insertText(text);
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
keyPressHandler.setDefault(rangeSelectionOnly(handler));
|
|
keyPressHandler.bind(keyCode.Enter, modifier.None, rangeSelectionOnly(textController.enqueueParagraphSplittingOps))
|
|
};
|
|
this.endEditing = function() {
|
|
inputMethodEditor.unsubscribe(gui.InputMethodEditor.signalCompositionStart, textController.removeCurrentSelection);
|
|
inputMethodEditor.unsubscribe(gui.InputMethodEditor.signalCompositionEnd, insertNonEmptyData);
|
|
eventManager.unsubscribe("cut", handleCut);
|
|
eventManager.unsubscribe("beforecut", handleBeforeCut);
|
|
eventManager.unsubscribe("paste", handlePaste);
|
|
eventManager.unsubscribe("beforepaste", handleBeforePaste);
|
|
window.removeEventListener("focus", hyperlinkClickHandler.showTextCursor, false);
|
|
inputMethodEditor.setEditing(false);
|
|
hyperlinkClickHandler.setModifier(gui.HyperlinkClickHandler.Modifier.None);
|
|
keyDownHandler.bind(keyCode.Backspace, modifier.None, function() {
|
|
return true
|
|
}, true);
|
|
keyDownHandler.unbind(keyCode.Delete, modifier.None);
|
|
keyDownHandler.unbind(keyCode.Tab, modifier.None);
|
|
if(isMacOS) {
|
|
keyDownHandler.unbind(keyCode.Clear, modifier.None);
|
|
keyDownHandler.unbind(keyCode.B, modifier.Meta);
|
|
keyDownHandler.unbind(keyCode.I, modifier.Meta);
|
|
keyDownHandler.unbind(keyCode.U, modifier.Meta);
|
|
keyDownHandler.unbind(keyCode.L, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.E, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.R, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.J, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.C, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.Z, modifier.Meta);
|
|
keyDownHandler.unbind(keyCode.Z, modifier.MetaShift);
|
|
keyDownHandler.unbind(keyCode.LeftMeta, modifier.Meta);
|
|
keyDownHandler.unbind(keyCode.MetaInMozilla, modifier.Meta);
|
|
keyUpHandler.unbind(keyCode.LeftMeta, modifier.None);
|
|
keyUpHandler.unbind(keyCode.MetaInMozilla, modifier.None)
|
|
}else {
|
|
keyDownHandler.unbind(keyCode.B, modifier.Ctrl);
|
|
keyDownHandler.unbind(keyCode.I, modifier.Ctrl);
|
|
keyDownHandler.unbind(keyCode.U, modifier.Ctrl);
|
|
keyDownHandler.unbind(keyCode.L, modifier.CtrlShift);
|
|
keyDownHandler.unbind(keyCode.E, modifier.CtrlShift);
|
|
keyDownHandler.unbind(keyCode.R, modifier.CtrlShift);
|
|
keyDownHandler.unbind(keyCode.J, modifier.CtrlShift);
|
|
keyDownHandler.unbind(keyCode.C, modifier.CtrlAlt);
|
|
keyDownHandler.unbind(keyCode.Z, modifier.Ctrl);
|
|
keyDownHandler.unbind(keyCode.Z, modifier.CtrlShift);
|
|
keyDownHandler.unbind(keyCode.Ctrl, modifier.Ctrl);
|
|
keyUpHandler.unbind(keyCode.Ctrl, modifier.None)
|
|
}
|
|
keyPressHandler.setDefault(null);
|
|
keyPressHandler.unbind(keyCode.Enter, modifier.None)
|
|
};
|
|
this.getInputMemberId = function() {
|
|
return inputMemberId
|
|
};
|
|
this.getSession = function() {
|
|
return session
|
|
};
|
|
this.setUndoManager = function(manager) {
|
|
if(undoManager) {
|
|
undoManager.unsubscribe(gui.UndoManager.signalUndoStackChanged, forwardUndoStackChange)
|
|
}
|
|
undoManager = manager;
|
|
if(undoManager) {
|
|
undoManager.setDocument(odtDocument);
|
|
undoManager.setPlaybackFunction(session.enqueue);
|
|
undoManager.subscribe(gui.UndoManager.signalUndoStackChanged, forwardUndoStackChange)
|
|
}
|
|
};
|
|
this.getUndoManager = function() {
|
|
return undoManager
|
|
};
|
|
this.getAnnotationController = function() {
|
|
return annotationController
|
|
};
|
|
this.getDirectFormattingController = function() {
|
|
return directFormattingController
|
|
};
|
|
this.getHyperlinkController = function() {
|
|
return hyperlinkController
|
|
};
|
|
this.getImageController = function() {
|
|
return imageController
|
|
};
|
|
this.getSelectionController = function() {
|
|
return selectionController
|
|
};
|
|
this.getTextController = function() {
|
|
return textController
|
|
};
|
|
this.getEventManager = function() {
|
|
return eventManager
|
|
};
|
|
this.getKeyboardHandlers = function() {
|
|
return{keydown:keyDownHandler, keypress:keyPressHandler}
|
|
};
|
|
function destroy(callback) {
|
|
eventManager.unsubscribe("keydown", keyDownHandler.handleEvent);
|
|
eventManager.unsubscribe("keypress", keyPressHandler.handleEvent);
|
|
eventManager.unsubscribe("keyup", keyUpHandler.handleEvent);
|
|
eventManager.unsubscribe("copy", handleCopy);
|
|
eventManager.unsubscribe("mousedown", handleMouseDown);
|
|
eventManager.unsubscribe("mousemove", drawShadowCursorTask.trigger);
|
|
eventManager.unsubscribe("mouseup", handleMouseUp);
|
|
eventManager.unsubscribe("contextmenu", handleContextMenu);
|
|
eventManager.unsubscribe("dragstart", handleDragStart);
|
|
eventManager.unsubscribe("dragend", handleDragEnd);
|
|
eventManager.unsubscribe("click", hyperlinkClickHandler.handleClick);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, redrawRegionSelectionTask.trigger);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorAdded, inputMethodEditor.registerCursor);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorRemoved, inputMethodEditor.removeCursor);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalOperationEnd, updateUndoStack);
|
|
callback()
|
|
}
|
|
this.destroy = function(callback) {
|
|
var destroyCallbacks = [];
|
|
if(iOSSafariSupport) {
|
|
destroyCallbacks.push(iOSSafariSupport.destroy)
|
|
}
|
|
destroyCallbacks = destroyCallbacks.concat([drawShadowCursorTask.destroy, redrawRegionSelectionTask.destroy, directFormattingController.destroy, inputMethodEditor.destroy, eventManager.destroy, destroy]);
|
|
runtime.clearTimeout(handleMouseClickTimeoutId);
|
|
async.destroyAll(destroyCallbacks, callback)
|
|
};
|
|
function init() {
|
|
drawShadowCursorTask = new core.ScheduledTask(updateShadowCursor, 0);
|
|
redrawRegionSelectionTask = new core.ScheduledTask(redrawRegionSelection, 0);
|
|
keyDownHandler.bind(keyCode.Left, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLeft));
|
|
keyDownHandler.bind(keyCode.Right, modifier.None, rangeSelectionOnly(selectionController.moveCursorToRight));
|
|
keyDownHandler.bind(keyCode.Up, modifier.None, rangeSelectionOnly(selectionController.moveCursorUp));
|
|
keyDownHandler.bind(keyCode.Down, modifier.None, rangeSelectionOnly(selectionController.moveCursorDown));
|
|
keyDownHandler.bind(keyCode.Left, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLeft));
|
|
keyDownHandler.bind(keyCode.Right, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToRight));
|
|
keyDownHandler.bind(keyCode.Up, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionUp));
|
|
keyDownHandler.bind(keyCode.Down, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionDown));
|
|
keyDownHandler.bind(keyCode.Home, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLineStart));
|
|
keyDownHandler.bind(keyCode.End, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLineEnd));
|
|
keyDownHandler.bind(keyCode.Home, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorToDocumentStart));
|
|
keyDownHandler.bind(keyCode.End, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorToDocumentEnd));
|
|
keyDownHandler.bind(keyCode.Home, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLineStart));
|
|
keyDownHandler.bind(keyCode.End, modifier.Shift, rangeSelectionOnly(selectionController.extendSelectionToLineEnd));
|
|
keyDownHandler.bind(keyCode.Up, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphStart));
|
|
keyDownHandler.bind(keyCode.Down, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphEnd));
|
|
keyDownHandler.bind(keyCode.Home, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentStart));
|
|
keyDownHandler.bind(keyCode.End, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentEnd));
|
|
if(isMacOS) {
|
|
keyDownHandler.bind(keyCode.Left, modifier.Alt, rangeSelectionOnly(selectionController.moveCursorBeforeWord));
|
|
keyDownHandler.bind(keyCode.Right, modifier.Alt, rangeSelectionOnly(selectionController.moveCursorPastWord));
|
|
keyDownHandler.bind(keyCode.Left, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToLineStart));
|
|
keyDownHandler.bind(keyCode.Right, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToLineEnd));
|
|
keyDownHandler.bind(keyCode.Home, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToDocumentStart));
|
|
keyDownHandler.bind(keyCode.End, modifier.Meta, rangeSelectionOnly(selectionController.moveCursorToDocumentEnd));
|
|
keyDownHandler.bind(keyCode.Left, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionBeforeWord));
|
|
keyDownHandler.bind(keyCode.Right, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionPastWord));
|
|
keyDownHandler.bind(keyCode.Left, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToLineStart));
|
|
keyDownHandler.bind(keyCode.Right, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToLineEnd));
|
|
keyDownHandler.bind(keyCode.Up, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphStart));
|
|
keyDownHandler.bind(keyCode.Down, modifier.AltShift, rangeSelectionOnly(selectionController.extendSelectionToParagraphEnd));
|
|
keyDownHandler.bind(keyCode.Up, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentStart));
|
|
keyDownHandler.bind(keyCode.Down, modifier.MetaShift, rangeSelectionOnly(selectionController.extendSelectionToDocumentEnd));
|
|
keyDownHandler.bind(keyCode.A, modifier.Meta, rangeSelectionOnly(selectionController.extendSelectionToEntireDocument))
|
|
}else {
|
|
keyDownHandler.bind(keyCode.Left, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorBeforeWord));
|
|
keyDownHandler.bind(keyCode.Right, modifier.Ctrl, rangeSelectionOnly(selectionController.moveCursorPastWord));
|
|
keyDownHandler.bind(keyCode.Left, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionBeforeWord));
|
|
keyDownHandler.bind(keyCode.Right, modifier.CtrlShift, rangeSelectionOnly(selectionController.extendSelectionPastWord));
|
|
keyDownHandler.bind(keyCode.A, modifier.Ctrl, rangeSelectionOnly(selectionController.extendSelectionToEntireDocument))
|
|
}
|
|
if(isIOS) {
|
|
iOSSafariSupport = new gui.IOSSafariSupport(eventManager)
|
|
}
|
|
eventManager.subscribe("keydown", keyDownHandler.handleEvent);
|
|
eventManager.subscribe("keypress", keyPressHandler.handleEvent);
|
|
eventManager.subscribe("keyup", keyUpHandler.handleEvent);
|
|
eventManager.subscribe("copy", handleCopy);
|
|
eventManager.subscribe("mousedown", handleMouseDown);
|
|
eventManager.subscribe("mousemove", drawShadowCursorTask.trigger);
|
|
eventManager.subscribe("mouseup", handleMouseUp);
|
|
eventManager.subscribe("contextmenu", handleContextMenu);
|
|
eventManager.subscribe("dragstart", handleDragStart);
|
|
eventManager.subscribe("dragend", handleDragEnd);
|
|
eventManager.subscribe("click", hyperlinkClickHandler.handleClick);
|
|
odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, redrawRegionSelectionTask.trigger);
|
|
odtDocument.subscribe(ops.Document.signalCursorAdded, inputMethodEditor.registerCursor);
|
|
odtDocument.subscribe(ops.Document.signalCursorRemoved, inputMethodEditor.removeCursor);
|
|
odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, updateUndoStack)
|
|
}
|
|
init()
|
|
};
|
|
return gui.SessionController
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.CaretManager = function CaretManager(sessionController) {
|
|
var carets = {}, async = new core.Async, window = runtime.getWindow(), ensureCaretVisibleTimeoutId, scrollIntoViewScheduled = false;
|
|
function getCaret(memberId) {
|
|
return carets.hasOwnProperty(memberId) ? carets[memberId] : null
|
|
}
|
|
function getCarets() {
|
|
return Object.keys(carets).map(function(memberid) {
|
|
return carets[memberid]
|
|
})
|
|
}
|
|
function removeCaret(memberId) {
|
|
var caret = carets[memberId];
|
|
if(caret) {
|
|
caret.destroy(function() {
|
|
});
|
|
delete carets[memberId]
|
|
}
|
|
}
|
|
function refreshLocalCaretBlinking(cursor) {
|
|
var caret, memberId = cursor.getMemberId();
|
|
if(memberId === sessionController.getInputMemberId()) {
|
|
caret = getCaret(memberId);
|
|
if(caret) {
|
|
caret.refreshCursorBlinking()
|
|
}
|
|
}
|
|
}
|
|
function executeEnsureCaretVisible() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
scrollIntoViewScheduled = false;
|
|
if(caret) {
|
|
caret.ensureVisible()
|
|
}
|
|
}
|
|
function scheduleCaretVisibilityCheck() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
if(caret) {
|
|
caret.handleUpdate();
|
|
if(!scrollIntoViewScheduled) {
|
|
scrollIntoViewScheduled = true;
|
|
ensureCaretVisibleTimeoutId = runtime.setTimeout(executeEnsureCaretVisible, 50)
|
|
}
|
|
}
|
|
}
|
|
function ensureLocalCaretVisible(info) {
|
|
if(info.memberId === sessionController.getInputMemberId()) {
|
|
scheduleCaretVisibilityCheck()
|
|
}
|
|
}
|
|
function focusLocalCaret() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
if(caret) {
|
|
caret.setFocus()
|
|
}
|
|
}
|
|
function blurLocalCaret() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
if(caret) {
|
|
caret.removeFocus()
|
|
}
|
|
}
|
|
function showLocalCaret() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
if(caret) {
|
|
caret.show()
|
|
}
|
|
}
|
|
function hideLocalCaret() {
|
|
var caret = getCaret(sessionController.getInputMemberId());
|
|
if(caret) {
|
|
caret.hide()
|
|
}
|
|
}
|
|
this.registerCursor = function(cursor, caretAvatarInitiallyVisible, blinkOnRangeSelect) {
|
|
var memberid = cursor.getMemberId(), caret = new gui.Caret(cursor, caretAvatarInitiallyVisible, blinkOnRangeSelect), eventManager = sessionController.getEventManager();
|
|
carets[memberid] = caret;
|
|
if(memberid === sessionController.getInputMemberId()) {
|
|
runtime.log("Starting to track input on new cursor of " + memberid);
|
|
cursor.subscribe(ops.OdtCursor.signalCursorUpdated, scheduleCaretVisibilityCheck);
|
|
caret.setOverlayElement(eventManager.getEventTrap())
|
|
}else {
|
|
cursor.subscribe(ops.OdtCursor.signalCursorUpdated, caret.handleUpdate)
|
|
}
|
|
return caret
|
|
};
|
|
this.getCaret = getCaret;
|
|
this.getCarets = getCarets;
|
|
this.destroy = function(callback) {
|
|
var odtDocument = sessionController.getSession().getOdtDocument(), eventManager = sessionController.getEventManager(), caretCleanup = getCarets().map(function(caret) {
|
|
return caret.destroy
|
|
});
|
|
runtime.clearTimeout(ensureCaretVisibleTimeoutId);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, ensureLocalCaretVisible);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorMoved, refreshLocalCaretBlinking);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorRemoved, removeCaret);
|
|
eventManager.unsubscribe("focus", focusLocalCaret);
|
|
eventManager.unsubscribe("blur", blurLocalCaret);
|
|
window.removeEventListener("focus", showLocalCaret, false);
|
|
window.removeEventListener("blur", hideLocalCaret, false);
|
|
carets = {};
|
|
async.destroyAll(caretCleanup, callback)
|
|
};
|
|
function init() {
|
|
var odtDocument = sessionController.getSession().getOdtDocument(), eventManager = sessionController.getEventManager();
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, ensureLocalCaretVisible);
|
|
odtDocument.subscribe(ops.Document.signalCursorMoved, refreshLocalCaretBlinking);
|
|
odtDocument.subscribe(ops.Document.signalCursorRemoved, removeCaret);
|
|
eventManager.subscribe("focus", focusLocalCaret);
|
|
eventManager.subscribe("blur", blurLocalCaret);
|
|
window.addEventListener("focus", showLocalCaret, false);
|
|
window.addEventListener("blur", hideLocalCaret, false)
|
|
}
|
|
init()
|
|
};
|
|
gui.EditInfoHandle = function EditInfoHandle(parentElement) {
|
|
var edits = [], handle, document = (parentElement.ownerDocument), htmlns = document.documentElement.namespaceURI, editinfons = "urn:webodf:names:editinfo";
|
|
function renderEdits() {
|
|
var i, infoDiv, colorSpan, authorSpan, timeSpan;
|
|
handle.innerHTML = "";
|
|
for(i = 0;i < edits.length;i += 1) {
|
|
infoDiv = document.createElementNS(htmlns, "div");
|
|
infoDiv.className = "editInfo";
|
|
colorSpan = document.createElementNS(htmlns, "span");
|
|
colorSpan.className = "editInfoColor";
|
|
colorSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
|
|
authorSpan = document.createElementNS(htmlns, "span");
|
|
authorSpan.className = "editInfoAuthor";
|
|
authorSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
|
|
timeSpan = document.createElementNS(htmlns, "span");
|
|
timeSpan.className = "editInfoTime";
|
|
timeSpan.setAttributeNS(editinfons, "editinfo:memberid", edits[i].memberid);
|
|
timeSpan.innerHTML = edits[i].time;
|
|
infoDiv.appendChild(colorSpan);
|
|
infoDiv.appendChild(authorSpan);
|
|
infoDiv.appendChild(timeSpan);
|
|
handle.appendChild(infoDiv)
|
|
}
|
|
}
|
|
this.setEdits = function(editArray) {
|
|
edits = editArray;
|
|
renderEdits()
|
|
};
|
|
this.show = function() {
|
|
handle.style.display = "block"
|
|
};
|
|
this.hide = function() {
|
|
handle.style.display = "none"
|
|
};
|
|
this.destroy = function(callback) {
|
|
parentElement.removeChild(handle);
|
|
callback()
|
|
};
|
|
function init() {
|
|
handle = (document.createElementNS(htmlns, "div"));
|
|
handle.setAttribute("class", "editInfoHandle");
|
|
handle.style.display = "none";
|
|
parentElement.appendChild(handle)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012 KO GmbH <aditya.bhatt@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.EditInfo = function EditInfo(container, odtDocument) {
|
|
var editInfoNode, editHistory = {};
|
|
function sortEdits() {
|
|
var arr = [], memberid;
|
|
for(memberid in editHistory) {
|
|
if(editHistory.hasOwnProperty(memberid)) {
|
|
arr.push({"memberid":memberid, "time":editHistory[memberid].time})
|
|
}
|
|
}
|
|
arr.sort(function(a, b) {
|
|
return a.time - b.time
|
|
});
|
|
return arr
|
|
}
|
|
this.getNode = function() {
|
|
return editInfoNode
|
|
};
|
|
this.getOdtDocument = function() {
|
|
return odtDocument
|
|
};
|
|
this.getEdits = function() {
|
|
return editHistory
|
|
};
|
|
this.getSortedEdits = function() {
|
|
return sortEdits()
|
|
};
|
|
this.addEdit = function(memberid, timestamp) {
|
|
editHistory[memberid] = {time:timestamp}
|
|
};
|
|
this.clearEdits = function() {
|
|
editHistory = {}
|
|
};
|
|
this.destroy = function(callback) {
|
|
if(container.parentNode) {
|
|
container.removeChild(editInfoNode)
|
|
}
|
|
callback()
|
|
};
|
|
function init() {
|
|
var editInfons = "urn:webodf:names:editinfo", dom = odtDocument.getDOMDocument();
|
|
editInfoNode = dom.createElementNS(editInfons, "editinfo");
|
|
container.insertBefore(editInfoNode, container.firstChild)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.EditInfoMarker = function EditInfoMarker(editInfo, initialVisibility) {
|
|
var self = this, editInfoNode, handle, marker, editinfons = "urn:webodf:names:editinfo", decayTimer0, decayTimer1, decayTimer2, decayTimeStep = 1E4;
|
|
function applyDecay(opacity, delay) {
|
|
return runtime.setTimeout(function() {
|
|
marker.style.opacity = opacity
|
|
}, delay)
|
|
}
|
|
function deleteDecay(timerId) {
|
|
runtime.clearTimeout(timerId)
|
|
}
|
|
function setLastAuthor(memberid) {
|
|
marker.setAttributeNS(editinfons, "editinfo:memberid", memberid)
|
|
}
|
|
this.addEdit = function(memberid, timestamp) {
|
|
var age = Date.now() - timestamp;
|
|
editInfo.addEdit(memberid, timestamp);
|
|
handle.setEdits(editInfo.getSortedEdits());
|
|
setLastAuthor(memberid);
|
|
deleteDecay(decayTimer1);
|
|
deleteDecay(decayTimer2);
|
|
if(age < decayTimeStep) {
|
|
decayTimer0 = applyDecay(1, 0);
|
|
decayTimer1 = applyDecay(0.5, decayTimeStep - age);
|
|
decayTimer2 = applyDecay(0.2, decayTimeStep * 2 - age)
|
|
}else {
|
|
if(age >= decayTimeStep && age < decayTimeStep * 2) {
|
|
decayTimer0 = applyDecay(0.5, 0);
|
|
decayTimer2 = applyDecay(0.2, decayTimeStep * 2 - age)
|
|
}else {
|
|
decayTimer0 = applyDecay(0.2, 0)
|
|
}
|
|
}
|
|
};
|
|
this.getEdits = function() {
|
|
return editInfo.getEdits()
|
|
};
|
|
this.clearEdits = function() {
|
|
editInfo.clearEdits();
|
|
handle.setEdits([]);
|
|
if(marker.hasAttributeNS(editinfons, "editinfo:memberid")) {
|
|
marker.removeAttributeNS(editinfons, "editinfo:memberid")
|
|
}
|
|
};
|
|
this.getEditInfo = function() {
|
|
return editInfo
|
|
};
|
|
this.show = function() {
|
|
marker.style.display = "block"
|
|
};
|
|
this.hide = function() {
|
|
self.hideHandle();
|
|
marker.style.display = "none"
|
|
};
|
|
this.showHandle = function() {
|
|
handle.show()
|
|
};
|
|
this.hideHandle = function() {
|
|
handle.hide()
|
|
};
|
|
this.destroy = function(callback) {
|
|
deleteDecay(decayTimer0);
|
|
deleteDecay(decayTimer1);
|
|
deleteDecay(decayTimer2);
|
|
editInfoNode.removeChild(marker);
|
|
handle.destroy(function(err) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
editInfo.destroy(callback)
|
|
}
|
|
})
|
|
};
|
|
function init() {
|
|
var dom = editInfo.getOdtDocument().getDOMDocument(), htmlns = dom.documentElement.namespaceURI;
|
|
marker = (dom.createElementNS(htmlns, "div"));
|
|
marker.setAttribute("class", "editInfoMarker");
|
|
marker.onmouseover = function() {
|
|
self.showHandle()
|
|
};
|
|
marker.onmouseout = function() {
|
|
self.hideHandle()
|
|
};
|
|
editInfoNode = editInfo.getNode();
|
|
editInfoNode.appendChild(marker);
|
|
handle = new gui.EditInfoHandle(editInfoNode);
|
|
if(!initialVisibility) {
|
|
self.hide()
|
|
}
|
|
}
|
|
init()
|
|
};
|
|
gui.ShadowCursor = function ShadowCursor(document) {
|
|
var selectedRange = (document.getDOMDocument().createRange()), forwardSelection = true;
|
|
this.removeFromDocument = function() {
|
|
};
|
|
this.getMemberId = function() {
|
|
return gui.ShadowCursor.ShadowCursorMemberId
|
|
};
|
|
this.getSelectedRange = function() {
|
|
return selectedRange
|
|
};
|
|
this.setSelectedRange = function(range, isForwardSelection) {
|
|
selectedRange = range;
|
|
forwardSelection = isForwardSelection !== false
|
|
};
|
|
this.hasForwardSelection = function() {
|
|
return forwardSelection
|
|
};
|
|
this.getDocument = function() {
|
|
return document
|
|
};
|
|
this.getSelectionType = function() {
|
|
return ops.OdtCursor.RangeSelection
|
|
};
|
|
function init() {
|
|
selectedRange.setStart(document.getRootNode(), 0)
|
|
}
|
|
init()
|
|
};
|
|
gui.ShadowCursor.ShadowCursorMemberId = "";
|
|
(function() {
|
|
return gui.ShadowCursor
|
|
})();
|
|
gui.SelectionView = function SelectionView(cursor) {
|
|
};
|
|
gui.SelectionView.prototype.rerender = function() {
|
|
};
|
|
gui.SelectionView.prototype.show = function() {
|
|
};
|
|
gui.SelectionView.prototype.hide = function() {
|
|
};
|
|
gui.SelectionView.prototype.destroy = function(callback) {
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.SelectionViewManager = function SelectionViewManager(SelectionView) {
|
|
var selectionViews = {};
|
|
function getSelectionView(memberId) {
|
|
return selectionViews.hasOwnProperty(memberId) ? selectionViews[memberId] : null
|
|
}
|
|
this.getSelectionView = getSelectionView;
|
|
function getSelectionViews() {
|
|
return Object.keys(selectionViews).map(function(memberid) {
|
|
return selectionViews[memberid]
|
|
})
|
|
}
|
|
this.getSelectionViews = getSelectionViews;
|
|
function removeSelectionView(memberId) {
|
|
if(selectionViews.hasOwnProperty(memberId)) {
|
|
selectionViews[memberId].destroy(function() {
|
|
});
|
|
delete selectionViews[memberId]
|
|
}
|
|
}
|
|
this.removeSelectionView = removeSelectionView;
|
|
function hideSelectionView(memberId) {
|
|
if(selectionViews.hasOwnProperty(memberId)) {
|
|
selectionViews[memberId].hide()
|
|
}
|
|
}
|
|
this.hideSelectionView = hideSelectionView;
|
|
function showSelectionView(memberId) {
|
|
if(selectionViews.hasOwnProperty(memberId)) {
|
|
selectionViews[memberId].show()
|
|
}
|
|
}
|
|
this.showSelectionView = showSelectionView;
|
|
this.rerenderSelectionViews = function() {
|
|
Object.keys(selectionViews).forEach(function(memberId) {
|
|
selectionViews[memberId].rerender()
|
|
})
|
|
};
|
|
this.registerCursor = function(cursor, virtualSelectionsInitiallyVisible) {
|
|
var memberId = cursor.getMemberId(), selectionView = new SelectionView(cursor);
|
|
if(virtualSelectionsInitiallyVisible) {
|
|
selectionView.show()
|
|
}else {
|
|
selectionView.hide()
|
|
}
|
|
selectionViews[memberId] = selectionView;
|
|
return selectionView
|
|
};
|
|
this.destroy = function(callback) {
|
|
var selectionViewArray = getSelectionViews();
|
|
function destroySelectionView(i, err) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
if(i < selectionViewArray.length) {
|
|
selectionViewArray[i].destroy(function(err) {
|
|
destroySelectionView(i + 1, err)
|
|
})
|
|
}else {
|
|
callback()
|
|
}
|
|
}
|
|
}
|
|
destroySelectionView(0, undefined)
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.SessionViewOptions = function() {
|
|
this.editInfoMarkersInitiallyVisible = true;
|
|
this.caretAvatarsInitiallyVisible = true;
|
|
this.caretBlinksOnRangeSelect = true
|
|
};
|
|
(function() {
|
|
function configOption(userValue, defaultValue) {
|
|
return userValue !== undefined ? Boolean(userValue) : defaultValue
|
|
}
|
|
gui.SessionView = function SessionView(viewOptions, localMemberId, session, caretManager, selectionViewManager) {
|
|
var avatarInfoStyles, editInfons = "urn:webodf:names:editinfo", editInfoMap = {}, showEditInfoMarkers = configOption(viewOptions.editInfoMarkersInitiallyVisible, true), showCaretAvatars = configOption(viewOptions.caretAvatarsInitiallyVisible, true), blinkOnRangeSelect = configOption(viewOptions.caretBlinksOnRangeSelect, true);
|
|
function createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) {
|
|
return nodeName + '[editinfo|memberid="' + memberId + '"]' + pseudoClass
|
|
}
|
|
function getAvatarInfoStyle(nodeName, memberId, pseudoClass) {
|
|
var node = avatarInfoStyles.firstChild, nodeMatch = createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) + "{";
|
|
while(node) {
|
|
if(node.nodeType === Node.TEXT_NODE && (node).data.indexOf(nodeMatch) === 0) {
|
|
return node
|
|
}
|
|
node = node.nextSibling
|
|
}
|
|
return null
|
|
}
|
|
function setAvatarInfoStyle(memberId, name, color) {
|
|
function setStyle(nodeName, rule, pseudoClass) {
|
|
var styleRule = createAvatarInfoNodeMatch(nodeName, memberId, pseudoClass) + rule, styleNode = getAvatarInfoStyle(nodeName, memberId, pseudoClass);
|
|
if(styleNode) {
|
|
styleNode.data = styleRule
|
|
}else {
|
|
avatarInfoStyles.appendChild(document.createTextNode(styleRule))
|
|
}
|
|
}
|
|
setStyle("div.editInfoMarker", "{ background-color: " + color + "; }", "");
|
|
setStyle("span.editInfoColor", "{ background-color: " + color + "; }", "");
|
|
setStyle("span.editInfoAuthor", '{ content: "' + name + '"; }', ":before");
|
|
setStyle("dc|creator", "{ background-color: " + color + "; }", "");
|
|
setStyle(".selectionOverlay", "{ fill: " + color + "; stroke: " + color + ";}", "")
|
|
}
|
|
function highlightEdit(element, memberId, timestamp) {
|
|
var editInfo, editInfoMarker, id = "", editInfoNode = element.getElementsByTagNameNS(editInfons, "editinfo").item(0);
|
|
if(editInfoNode) {
|
|
id = (editInfoNode).getAttributeNS(editInfons, "id");
|
|
editInfoMarker = editInfoMap[id]
|
|
}else {
|
|
id = Math.random().toString();
|
|
editInfo = new ops.EditInfo(element, session.getOdtDocument());
|
|
editInfoMarker = new gui.EditInfoMarker(editInfo, showEditInfoMarkers);
|
|
editInfoNode = (element.getElementsByTagNameNS(editInfons, "editinfo").item(0));
|
|
editInfoNode.setAttributeNS(editInfons, "id", id);
|
|
editInfoMap[id] = editInfoMarker
|
|
}
|
|
editInfoMarker.addEdit(memberId, new Date(timestamp))
|
|
}
|
|
function setEditInfoMarkerVisibility(visible) {
|
|
var editInfoMarker, keyname;
|
|
for(keyname in editInfoMap) {
|
|
if(editInfoMap.hasOwnProperty(keyname)) {
|
|
editInfoMarker = editInfoMap[keyname];
|
|
if(visible) {
|
|
editInfoMarker.show()
|
|
}else {
|
|
editInfoMarker.hide()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function setCaretAvatarVisibility(visible) {
|
|
caretManager.getCarets().forEach(function(caret) {
|
|
if(visible) {
|
|
caret.showHandle()
|
|
}else {
|
|
caret.hideHandle()
|
|
}
|
|
})
|
|
}
|
|
this.showEditInfoMarkers = function() {
|
|
if(showEditInfoMarkers) {
|
|
return
|
|
}
|
|
showEditInfoMarkers = true;
|
|
setEditInfoMarkerVisibility(showEditInfoMarkers)
|
|
};
|
|
this.hideEditInfoMarkers = function() {
|
|
if(!showEditInfoMarkers) {
|
|
return
|
|
}
|
|
showEditInfoMarkers = false;
|
|
setEditInfoMarkerVisibility(showEditInfoMarkers)
|
|
};
|
|
this.showCaretAvatars = function() {
|
|
if(showCaretAvatars) {
|
|
return
|
|
}
|
|
showCaretAvatars = true;
|
|
setCaretAvatarVisibility(showCaretAvatars)
|
|
};
|
|
this.hideCaretAvatars = function() {
|
|
if(!showCaretAvatars) {
|
|
return
|
|
}
|
|
showCaretAvatars = false;
|
|
setCaretAvatarVisibility(showCaretAvatars)
|
|
};
|
|
this.getSession = function() {
|
|
return session
|
|
};
|
|
this.getCaret = function(memberid) {
|
|
return caretManager.getCaret(memberid)
|
|
};
|
|
function renderMemberData(member) {
|
|
var memberId = member.getMemberId(), properties = member.getProperties();
|
|
setAvatarInfoStyle(memberId, properties.fullName, properties.color);
|
|
if(localMemberId === memberId) {
|
|
setAvatarInfoStyle("", "", properties.color)
|
|
}
|
|
}
|
|
function onCursorAdded(cursor) {
|
|
var memberId = cursor.getMemberId(), properties = session.getOdtDocument().getMember(memberId).getProperties(), caret;
|
|
caretManager.registerCursor(cursor, showCaretAvatars, blinkOnRangeSelect);
|
|
selectionViewManager.registerCursor(cursor, true);
|
|
caret = caretManager.getCaret(memberId);
|
|
if(caret) {
|
|
caret.setAvatarImageUrl(properties.imageUrl);
|
|
caret.setColor(properties.color)
|
|
}
|
|
runtime.log("+++ View here +++ eagerly created an Caret for '" + memberId + "'! +++")
|
|
}
|
|
function onCursorMoved(cursor) {
|
|
var memberId = cursor.getMemberId(), localSelectionView = selectionViewManager.getSelectionView(localMemberId), shadowSelectionView = selectionViewManager.getSelectionView(gui.ShadowCursor.ShadowCursorMemberId), localCaret = caretManager.getCaret(localMemberId);
|
|
if(memberId === localMemberId) {
|
|
shadowSelectionView.hide();
|
|
if(localSelectionView) {
|
|
localSelectionView.show()
|
|
}
|
|
if(localCaret) {
|
|
localCaret.show()
|
|
}
|
|
}else {
|
|
if(memberId === gui.ShadowCursor.ShadowCursorMemberId) {
|
|
shadowSelectionView.show();
|
|
if(localSelectionView) {
|
|
localSelectionView.hide()
|
|
}
|
|
if(localCaret) {
|
|
localCaret.hide()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function onCursorRemoved(memberid) {
|
|
selectionViewManager.removeSelectionView(memberid)
|
|
}
|
|
function onParagraphChanged(info) {
|
|
highlightEdit(info.paragraphElement, info.memberId, info.timeStamp)
|
|
}
|
|
this.destroy = function(callback) {
|
|
var odtDocument = session.getOdtDocument(), editInfoArray = Object.keys(editInfoMap).map(function(keyname) {
|
|
return editInfoMap[keyname]
|
|
});
|
|
odtDocument.unsubscribe(ops.Document.signalMemberAdded, renderMemberData);
|
|
odtDocument.unsubscribe(ops.Document.signalMemberUpdated, renderMemberData);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
|
|
odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews);
|
|
odtDocument.unsubscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews);
|
|
avatarInfoStyles.parentNode.removeChild(avatarInfoStyles);
|
|
(function destroyEditInfo(i, err) {
|
|
if(err) {
|
|
callback(err)
|
|
}else {
|
|
if(i < editInfoArray.length) {
|
|
editInfoArray[i].destroy(function(err) {
|
|
destroyEditInfo(i + 1, err)
|
|
})
|
|
}else {
|
|
callback()
|
|
}
|
|
}
|
|
})(0, undefined)
|
|
};
|
|
function init() {
|
|
var odtDocument = session.getOdtDocument(), head = document.getElementsByTagName("head").item(0);
|
|
odtDocument.subscribe(ops.Document.signalMemberAdded, renderMemberData);
|
|
odtDocument.subscribe(ops.Document.signalMemberUpdated, renderMemberData);
|
|
odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded);
|
|
odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved);
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, onParagraphChanged);
|
|
odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved);
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews);
|
|
odtDocument.subscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews);
|
|
odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews);
|
|
avatarInfoStyles = (document.createElementNS(head.namespaceURI, "style"));
|
|
avatarInfoStyles.type = "text/css";
|
|
avatarInfoStyles.media = "screen, print, handheld, projection";
|
|
avatarInfoStyles.appendChild(document.createTextNode("@namespace editinfo url(urn:webodf:names:editinfo);"));
|
|
avatarInfoStyles.appendChild(document.createTextNode("@namespace dc url(http://purl.org/dc/elements/1.1/);"));
|
|
head.appendChild(avatarInfoStyles)
|
|
}
|
|
init()
|
|
}
|
|
})();
|
|
gui.SvgSelectionView = function SvgSelectionView(cursor) {
|
|
var document = cursor.getDocument(), documentRoot, root, doc = document.getDOMDocument(), async = new core.Async, svgns = "http://www.w3.org/2000/svg", overlay = doc.createElementNS(svgns, "svg"), polygon = doc.createElementNS(svgns, "polygon"), odfUtils = new odf.OdfUtils, domUtils = new core.DomUtils, isVisible = true, positionIterator = gui.SelectionMover.createPositionIterator(document.getRootNode()), FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT, FILTER_REJECT = NodeFilter.FILTER_REJECT, renderTask;
|
|
function addOverlay() {
|
|
var newDocumentRoot = document.getRootNode();
|
|
if(documentRoot !== newDocumentRoot) {
|
|
documentRoot = newDocumentRoot;
|
|
root = (documentRoot.parentNode.parentNode.parentNode);
|
|
root.appendChild(overlay);
|
|
overlay.setAttribute("class", "selectionOverlay");
|
|
overlay.appendChild(polygon)
|
|
}
|
|
}
|
|
function translateRect(rect) {
|
|
var rootRect = domUtils.getBoundingClientRect(root), zoomLevel = document.getCanvas().getZoomLevel(), resultRect = {};
|
|
resultRect.top = domUtils.adaptRangeDifferenceToZoomLevel(rect.top - rootRect.top, zoomLevel);
|
|
resultRect.left = domUtils.adaptRangeDifferenceToZoomLevel(rect.left - rootRect.left, zoomLevel);
|
|
resultRect.bottom = domUtils.adaptRangeDifferenceToZoomLevel(rect.bottom - rootRect.top, zoomLevel);
|
|
resultRect.right = domUtils.adaptRangeDifferenceToZoomLevel(rect.right - rootRect.left, zoomLevel);
|
|
resultRect.width = domUtils.adaptRangeDifferenceToZoomLevel(rect.width, zoomLevel);
|
|
resultRect.height = domUtils.adaptRangeDifferenceToZoomLevel(rect.height, zoomLevel);
|
|
return resultRect
|
|
}
|
|
function isRangeVisible(range) {
|
|
var bcr = range.getBoundingClientRect();
|
|
return Boolean(bcr && bcr.height !== 0)
|
|
}
|
|
function lastVisibleRect(range, nodes) {
|
|
var nextNodeIndex = nodes.length - 1, node = nodes[nextNodeIndex], startOffset, endOffset;
|
|
if(range.endContainer === node) {
|
|
startOffset = range.endOffset
|
|
}else {
|
|
if(node.nodeType === Node.TEXT_NODE) {
|
|
startOffset = node.length
|
|
}else {
|
|
startOffset = node.childNodes.length
|
|
}
|
|
}
|
|
endOffset = startOffset;
|
|
range.setStart(node, startOffset);
|
|
range.setEnd(node, endOffset);
|
|
while(!isRangeVisible(range)) {
|
|
if(node.nodeType === Node.ELEMENT_NODE && startOffset > 0) {
|
|
startOffset = 0
|
|
}else {
|
|
if(node.nodeType === Node.TEXT_NODE && startOffset > 0) {
|
|
startOffset -= 1
|
|
}else {
|
|
if(nodes[nextNodeIndex]) {
|
|
node = nodes[nextNodeIndex];
|
|
nextNodeIndex -= 1;
|
|
startOffset = endOffset = node.length || node.childNodes.length
|
|
}else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
range.setStart(node, startOffset);
|
|
range.setEnd(node, endOffset)
|
|
}
|
|
return true
|
|
}
|
|
function firstVisibleRect(range, nodes) {
|
|
var nextNodeIndex = 0, node = nodes[nextNodeIndex], startOffset = range.startContainer === node ? range.startOffset : 0, endOffset = startOffset;
|
|
range.setStart(node, startOffset);
|
|
range.setEnd(node, endOffset);
|
|
while(!isRangeVisible(range)) {
|
|
if(node.nodeType === Node.ELEMENT_NODE && endOffset < node.childNodes.length) {
|
|
endOffset = node.childNodes.length
|
|
}else {
|
|
if(node.nodeType === Node.TEXT_NODE && endOffset < node.length) {
|
|
endOffset += 1
|
|
}else {
|
|
if(nodes[nextNodeIndex]) {
|
|
node = nodes[nextNodeIndex];
|
|
nextNodeIndex += 1;
|
|
startOffset = endOffset = 0
|
|
}else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
range.setStart(node, startOffset);
|
|
range.setEnd(node, endOffset)
|
|
}
|
|
return true
|
|
}
|
|
function getExtremeRanges(range) {
|
|
var nodes = odfUtils.getTextElements(range, true, false), firstRange = (range.cloneRange()), lastRange = (range.cloneRange()), fillerRange = range.cloneRange();
|
|
if(!nodes.length) {
|
|
return null
|
|
}
|
|
if(!firstVisibleRect(firstRange, nodes)) {
|
|
return null
|
|
}
|
|
if(!lastVisibleRect(lastRange, nodes)) {
|
|
return null
|
|
}
|
|
fillerRange.setStart(firstRange.startContainer, firstRange.startOffset);
|
|
fillerRange.setEnd(lastRange.endContainer, lastRange.endOffset);
|
|
return{firstRange:firstRange, lastRange:lastRange, fillerRange:fillerRange}
|
|
}
|
|
function getBoundingRect(rect1, rect2) {
|
|
var resultRect = {};
|
|
resultRect.top = Math.min(rect1.top, rect2.top);
|
|
resultRect.left = Math.min(rect1.left, rect2.left);
|
|
resultRect.right = Math.max(rect1.right, rect2.right);
|
|
resultRect.bottom = Math.max(rect1.bottom, rect2.bottom);
|
|
resultRect.width = resultRect.right - resultRect.left;
|
|
resultRect.height = resultRect.bottom - resultRect.top;
|
|
return resultRect
|
|
}
|
|
function checkAndGrowOrCreateRect(originalRect, newRect) {
|
|
if(newRect && (newRect.width > 0 && newRect.height > 0)) {
|
|
if(!originalRect) {
|
|
originalRect = newRect
|
|
}else {
|
|
originalRect = getBoundingRect(originalRect, newRect)
|
|
}
|
|
}
|
|
return originalRect
|
|
}
|
|
function getFillerRect(fillerRange) {
|
|
var containerNode = fillerRange.commonAncestorContainer, firstNode = (fillerRange.startContainer), lastNode = (fillerRange.endContainer), firstOffset = fillerRange.startOffset, lastOffset = fillerRange.endOffset, currentNode, lastMeasuredNode, firstSibling, lastSibling, grownRect = null, currentRect, range = doc.createRange(), rootFilter, odfNodeFilter = new odf.OdfNodeFilter, treeWalker;
|
|
function acceptNode(node) {
|
|
positionIterator.setUnfilteredPosition(node, 0);
|
|
if(odfNodeFilter.acceptNode(node) === FILTER_ACCEPT && rootFilter.acceptPosition(positionIterator) === FILTER_ACCEPT) {
|
|
return FILTER_ACCEPT
|
|
}
|
|
return FILTER_REJECT
|
|
}
|
|
function getRectFromNodeAfterFiltering(node) {
|
|
var rect = null;
|
|
if(acceptNode(node) === FILTER_ACCEPT) {
|
|
rect = domUtils.getBoundingClientRect(node)
|
|
}
|
|
return rect
|
|
}
|
|
if(firstNode === containerNode || lastNode === containerNode) {
|
|
range = fillerRange.cloneRange();
|
|
grownRect = range.getBoundingClientRect();
|
|
range.detach();
|
|
return grownRect
|
|
}
|
|
firstSibling = firstNode;
|
|
while(firstSibling.parentNode !== containerNode) {
|
|
firstSibling = firstSibling.parentNode
|
|
}
|
|
lastSibling = lastNode;
|
|
while(lastSibling.parentNode !== containerNode) {
|
|
lastSibling = lastSibling.parentNode
|
|
}
|
|
rootFilter = document.createRootFilter(firstNode);
|
|
currentNode = firstSibling.nextSibling;
|
|
while(currentNode && currentNode !== lastSibling) {
|
|
currentRect = getRectFromNodeAfterFiltering(currentNode);
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
|
|
currentNode = currentNode.nextSibling
|
|
}
|
|
if(odfUtils.isParagraph(firstSibling)) {
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, domUtils.getBoundingClientRect(firstSibling))
|
|
}else {
|
|
if(firstSibling.nodeType === Node.TEXT_NODE) {
|
|
currentNode = firstSibling;
|
|
range.setStart(currentNode, firstOffset);
|
|
range.setEnd(currentNode, currentNode === lastSibling ? lastOffset : (currentNode).length);
|
|
currentRect = range.getBoundingClientRect();
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, currentRect)
|
|
}else {
|
|
treeWalker = doc.createTreeWalker(firstSibling, NodeFilter.SHOW_TEXT, acceptNode, false);
|
|
currentNode = treeWalker.currentNode = firstNode;
|
|
while(currentNode && currentNode !== lastNode) {
|
|
range.setStart(currentNode, firstOffset);
|
|
range.setEnd(currentNode, (currentNode).length);
|
|
currentRect = range.getBoundingClientRect();
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
|
|
lastMeasuredNode = currentNode;
|
|
firstOffset = 0;
|
|
currentNode = treeWalker.nextNode()
|
|
}
|
|
}
|
|
}
|
|
if(!lastMeasuredNode) {
|
|
lastMeasuredNode = firstNode
|
|
}
|
|
if(odfUtils.isParagraph(lastSibling)) {
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, domUtils.getBoundingClientRect(lastSibling))
|
|
}else {
|
|
if(lastSibling.nodeType === Node.TEXT_NODE) {
|
|
currentNode = lastSibling;
|
|
range.setStart(currentNode, currentNode === firstSibling ? firstOffset : 0);
|
|
range.setEnd(currentNode, lastOffset);
|
|
currentRect = range.getBoundingClientRect();
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, currentRect)
|
|
}else {
|
|
treeWalker = doc.createTreeWalker(lastSibling, NodeFilter.SHOW_TEXT, acceptNode, false);
|
|
currentNode = treeWalker.currentNode = lastNode;
|
|
while(currentNode && currentNode !== lastMeasuredNode) {
|
|
range.setStart(currentNode, 0);
|
|
range.setEnd(currentNode, lastOffset);
|
|
currentRect = range.getBoundingClientRect();
|
|
grownRect = checkAndGrowOrCreateRect(grownRect, currentRect);
|
|
currentNode = treeWalker.previousNode();
|
|
if(currentNode) {
|
|
lastOffset = (currentNode).length
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return grownRect
|
|
}
|
|
function getCollapsedRectOfTextRange(range, useRightEdge) {
|
|
var clientRect = range.getBoundingClientRect(), collapsedRect = {};
|
|
collapsedRect.width = 0;
|
|
collapsedRect.top = clientRect.top;
|
|
collapsedRect.bottom = clientRect.bottom;
|
|
collapsedRect.height = clientRect.height;
|
|
collapsedRect.left = collapsedRect.right = useRightEdge ? clientRect.right : clientRect.left;
|
|
return collapsedRect
|
|
}
|
|
function setPoints(points) {
|
|
var pointsString = "", i;
|
|
for(i = 0;i < points.length;i += 1) {
|
|
pointsString += points[i].x + "," + points[i].y + " "
|
|
}
|
|
polygon.setAttribute("points", pointsString)
|
|
}
|
|
function repositionOverlays(selectedRange) {
|
|
var extremes = getExtremeRanges(selectedRange), firstRange, lastRange, fillerRange, firstRect, fillerRect, lastRect, left, right, top, bottom;
|
|
if(extremes) {
|
|
firstRange = extremes.firstRange;
|
|
lastRange = extremes.lastRange;
|
|
fillerRange = extremes.fillerRange;
|
|
firstRect = translateRect(getCollapsedRectOfTextRange(firstRange, false));
|
|
lastRect = translateRect(getCollapsedRectOfTextRange(lastRange, true));
|
|
fillerRect = getFillerRect(fillerRange);
|
|
if(!fillerRect) {
|
|
fillerRect = getBoundingRect(firstRect, lastRect)
|
|
}else {
|
|
fillerRect = translateRect(fillerRect)
|
|
}
|
|
left = fillerRect.left;
|
|
right = firstRect.left + Math.max(0, fillerRect.width - (firstRect.left - fillerRect.left));
|
|
top = Math.min(firstRect.top, lastRect.top);
|
|
bottom = lastRect.top + lastRect.height;
|
|
setPoints([{x:firstRect.left, y:top + firstRect.height}, {x:firstRect.left, y:top}, {x:right, y:top}, {x:right, y:bottom - lastRect.height}, {x:lastRect.right, y:bottom - lastRect.height}, {x:lastRect.right, y:bottom}, {x:left, y:bottom}, {x:left, y:top + firstRect.height}, {x:firstRect.left, y:top + firstRect.height}]);
|
|
firstRange.detach();
|
|
lastRange.detach();
|
|
fillerRange.detach()
|
|
}
|
|
return Boolean(extremes)
|
|
}
|
|
function rerender() {
|
|
var range = cursor.getSelectedRange(), shouldShow;
|
|
shouldShow = isVisible && (cursor.getSelectionType() === ops.OdtCursor.RangeSelection && !range.collapsed);
|
|
if(shouldShow) {
|
|
addOverlay();
|
|
shouldShow = repositionOverlays(range)
|
|
}
|
|
if(shouldShow) {
|
|
overlay.style.display = "block"
|
|
}else {
|
|
overlay.style.display = "none"
|
|
}
|
|
}
|
|
this.rerender = function() {
|
|
if(isVisible) {
|
|
renderTask.trigger()
|
|
}
|
|
};
|
|
this.show = function() {
|
|
isVisible = true;
|
|
renderTask.trigger()
|
|
};
|
|
this.hide = function() {
|
|
isVisible = false;
|
|
renderTask.trigger()
|
|
};
|
|
function handleCursorMove(movedCursor) {
|
|
if(isVisible && movedCursor === cursor) {
|
|
renderTask.trigger()
|
|
}
|
|
}
|
|
function destroy(callback) {
|
|
root.removeChild(overlay);
|
|
cursor.getDocument().unsubscribe(ops.Document.signalCursorMoved, handleCursorMove);
|
|
callback()
|
|
}
|
|
this.destroy = function(callback) {
|
|
async.destroyAll([renderTask.destroy, destroy], callback)
|
|
};
|
|
function init() {
|
|
var editinfons = "urn:webodf:names:editinfo", memberid = cursor.getMemberId();
|
|
renderTask = new core.ScheduledTask(rerender, 0);
|
|
addOverlay();
|
|
overlay.setAttributeNS(editinfons, "editinfo:memberid", memberid);
|
|
cursor.getDocument().subscribe(ops.Document.signalCursorMoved, handleCursorMove)
|
|
}
|
|
init()
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.UndoStateRules = function UndoStateRules() {
|
|
function ReverseIterator(array, predicate) {
|
|
var index = array.length;
|
|
this.previous = function() {
|
|
for(index = index - 1;index >= 0;index -= 1) {
|
|
if(predicate(array[index])) {
|
|
return array[index]
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
function getOpType(op) {
|
|
return op.spec().optype
|
|
}
|
|
function getOpPosition(op) {
|
|
var key = "position", spec = op.spec(), value;
|
|
if(spec.hasOwnProperty(key)) {
|
|
value = (spec[key])
|
|
}
|
|
return value
|
|
}
|
|
function isEditOperation(op) {
|
|
return op.isEdit
|
|
}
|
|
this.isEditOperation = isEditOperation;
|
|
function canAggregateOperation(op) {
|
|
switch(getOpType(op)) {
|
|
case "RemoveText":
|
|
;
|
|
case "InsertText":
|
|
return true;
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
function isSameDirectionOfTravel(thisOp, lastEditOp, secondLastEditOp) {
|
|
var thisPosition = getOpPosition(thisOp), lastPosition = getOpPosition(lastEditOp), secondLastPosition = getOpPosition(secondLastEditOp), diffLastToSecondLast = lastPosition - secondLastPosition, diffThisToLast = thisPosition - lastPosition;
|
|
return diffThisToLast === diffLastToSecondLast
|
|
}
|
|
function isAdjacentOperation(thisOp, lastEditOp) {
|
|
var positionDifference = getOpPosition(thisOp) - getOpPosition(lastEditOp);
|
|
return positionDifference === 0 || Math.abs(positionDifference) === 1
|
|
}
|
|
function continuesOperations(thisOp, lastEditOp, secondLastEditOp) {
|
|
if(!secondLastEditOp) {
|
|
return isAdjacentOperation(thisOp, lastEditOp)
|
|
}
|
|
return isSameDirectionOfTravel(thisOp, lastEditOp, secondLastEditOp)
|
|
}
|
|
function continuesMostRecentEditOperation(thisOp, recentOperations) {
|
|
var thisOpType = getOpType(thisOp), editOpsFinder = new ReverseIterator(recentOperations, isEditOperation), lastEditOp = editOpsFinder.previous();
|
|
runtime.assert(Boolean(lastEditOp), "No edit operations found in state");
|
|
if(thisOpType === getOpType((lastEditOp))) {
|
|
return continuesOperations(thisOp, (lastEditOp), editOpsFinder.previous())
|
|
}
|
|
return false
|
|
}
|
|
function continuesMostRecentEditGroup(thisOp, recentOperations) {
|
|
var thisOpType = getOpType(thisOp), editOpsFinder = new ReverseIterator(recentOperations, isEditOperation), candidateOp = editOpsFinder.previous(), lastEditOp, secondLastEditOp = null, inspectedGroupsCount, groupId;
|
|
runtime.assert(Boolean(candidateOp), "No edit operations found in state");
|
|
groupId = candidateOp.group;
|
|
runtime.assert(groupId !== undefined, "Operation has no group");
|
|
inspectedGroupsCount = 1;
|
|
while(candidateOp && candidateOp.group === groupId) {
|
|
if(thisOpType === getOpType(candidateOp)) {
|
|
lastEditOp = candidateOp;
|
|
break
|
|
}
|
|
candidateOp = editOpsFinder.previous()
|
|
}
|
|
if(lastEditOp) {
|
|
candidateOp = editOpsFinder.previous();
|
|
while(candidateOp) {
|
|
if(candidateOp.group !== groupId) {
|
|
if(inspectedGroupsCount === 2) {
|
|
break
|
|
}
|
|
groupId = candidateOp.group;
|
|
inspectedGroupsCount += 1
|
|
}
|
|
if(thisOpType === getOpType(candidateOp)) {
|
|
secondLastEditOp = candidateOp;
|
|
break
|
|
}
|
|
candidateOp = editOpsFinder.previous()
|
|
}
|
|
return continuesOperations(thisOp, (lastEditOp), secondLastEditOp)
|
|
}
|
|
return false
|
|
}
|
|
function isPartOfOperationSet(operation, recentOperations) {
|
|
var areOperationsGrouped = operation.group !== undefined, lastOperation;
|
|
if(!isEditOperation(operation)) {
|
|
return true
|
|
}
|
|
if(recentOperations.length === 0) {
|
|
return true
|
|
}
|
|
lastOperation = recentOperations[recentOperations.length - 1];
|
|
if(areOperationsGrouped && operation.group === lastOperation.group) {
|
|
return true
|
|
}
|
|
if(canAggregateOperation(operation) && recentOperations.some(isEditOperation)) {
|
|
if(areOperationsGrouped) {
|
|
return continuesMostRecentEditGroup(operation, recentOperations)
|
|
}
|
|
return continuesMostRecentEditOperation(operation, recentOperations)
|
|
}
|
|
return false
|
|
}
|
|
this.isPartOfOperationSet = isPartOfOperationSet
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) {
|
|
var self = this, cursorns = "urn:webodf:names:cursor", domUtils = new core.DomUtils, initialDoc, initialState = [], playFunc, document, currentUndoState = [], undoStates = [], redoStates = [], eventNotifier = new core.EventNotifier([gui.UndoManager.signalUndoStackChanged, gui.UndoManager.signalUndoStateCreated, gui.UndoManager.signalUndoStateModified, gui.TrivialUndoManager.signalDocumentRootReplaced]), undoRules = defaultRules || new gui.UndoStateRules, isExecutingOps = false;
|
|
function executeOperations(operations) {
|
|
if(operations.length > 0) {
|
|
isExecutingOps = true;
|
|
playFunc(operations);
|
|
isExecutingOps = false
|
|
}
|
|
}
|
|
function emitStackChange() {
|
|
eventNotifier.emit(gui.UndoManager.signalUndoStackChanged, {undoAvailable:self.hasUndoStates(), redoAvailable:self.hasRedoStates()})
|
|
}
|
|
function mostRecentUndoState() {
|
|
return undoStates[undoStates.length - 1]
|
|
}
|
|
function completeCurrentUndoState() {
|
|
if(currentUndoState !== initialState && currentUndoState !== mostRecentUndoState()) {
|
|
undoStates.push(currentUndoState)
|
|
}
|
|
}
|
|
function removeNode(node) {
|
|
var sibling = node.previousSibling || node.nextSibling;
|
|
node.parentNode.removeChild(node);
|
|
domUtils.normalizeTextNodes(sibling)
|
|
}
|
|
function removeCursors(root) {
|
|
domUtils.getElementsByTagNameNS(root, cursorns, "cursor").forEach(removeNode);
|
|
domUtils.getElementsByTagNameNS(root, cursorns, "anchor").forEach(removeNode)
|
|
}
|
|
function values(obj) {
|
|
return Object.keys(obj).map(function(key) {
|
|
return obj[key]
|
|
})
|
|
}
|
|
function extractCursorStates(undoStates) {
|
|
var addCursor = {}, moveCursor = {}, requiredAddOps = {}, remainingAddOps, operations = undoStates.pop();
|
|
document.getMemberIds().forEach(function(memberid) {
|
|
requiredAddOps[memberid] = true
|
|
});
|
|
remainingAddOps = Object.keys(requiredAddOps).length;
|
|
function processOp(op) {
|
|
var spec = op.spec();
|
|
if(!requiredAddOps[spec.memberid]) {
|
|
return
|
|
}
|
|
switch(spec.optype) {
|
|
case "AddCursor":
|
|
if(!addCursor[spec.memberid]) {
|
|
addCursor[spec.memberid] = op;
|
|
delete requiredAddOps[spec.memberid];
|
|
remainingAddOps -= 1
|
|
}
|
|
break;
|
|
case "MoveCursor":
|
|
if(!moveCursor[spec.memberid]) {
|
|
moveCursor[spec.memberid] = op
|
|
}
|
|
break
|
|
}
|
|
}
|
|
while(operations && remainingAddOps > 0) {
|
|
operations.reverse();
|
|
operations.forEach(processOp);
|
|
operations = undoStates.pop()
|
|
}
|
|
return values(addCursor).concat(values(moveCursor))
|
|
}
|
|
this.subscribe = function(signal, callback) {
|
|
eventNotifier.subscribe(signal, callback)
|
|
};
|
|
this.unsubscribe = function(signal, callback) {
|
|
eventNotifier.unsubscribe(signal, callback)
|
|
};
|
|
this.hasUndoStates = function() {
|
|
return undoStates.length > 0
|
|
};
|
|
this.hasRedoStates = function() {
|
|
return redoStates.length > 0
|
|
};
|
|
this.setDocument = function(newDocument) {
|
|
document = newDocument
|
|
};
|
|
this.purgeInitialState = function() {
|
|
undoStates.length = 0;
|
|
redoStates.length = 0;
|
|
initialState.length = 0;
|
|
currentUndoState.length = 0;
|
|
initialDoc = null;
|
|
emitStackChange()
|
|
};
|
|
function setInitialState() {
|
|
initialDoc = document.cloneDocumentElement();
|
|
removeCursors(initialDoc);
|
|
completeCurrentUndoState();
|
|
currentUndoState = initialState = extractCursorStates([initialState].concat(undoStates));
|
|
undoStates.length = 0;
|
|
redoStates.length = 0;
|
|
emitStackChange()
|
|
}
|
|
this.setInitialState = setInitialState;
|
|
this.initialize = function() {
|
|
if(!initialDoc) {
|
|
setInitialState()
|
|
}
|
|
};
|
|
this.setPlaybackFunction = function(playback_func) {
|
|
playFunc = playback_func
|
|
};
|
|
this.onOperationExecuted = function(op) {
|
|
if(isExecutingOps) {
|
|
return
|
|
}
|
|
if(undoRules.isEditOperation(op) && (currentUndoState === initialState || redoStates.length > 0) || !undoRules.isPartOfOperationSet(op, currentUndoState)) {
|
|
redoStates.length = 0;
|
|
completeCurrentUndoState();
|
|
currentUndoState = [op];
|
|
undoStates.push(currentUndoState);
|
|
eventNotifier.emit(gui.UndoManager.signalUndoStateCreated, {operations:currentUndoState});
|
|
emitStackChange()
|
|
}else {
|
|
currentUndoState.push(op);
|
|
eventNotifier.emit(gui.UndoManager.signalUndoStateModified, {operations:currentUndoState})
|
|
}
|
|
};
|
|
this.moveForward = function(states) {
|
|
var moved = 0, redoOperations;
|
|
while(states && redoStates.length) {
|
|
redoOperations = redoStates.pop();
|
|
undoStates.push(redoOperations);
|
|
executeOperations(redoOperations);
|
|
states -= 1;
|
|
moved += 1
|
|
}
|
|
if(moved) {
|
|
currentUndoState = mostRecentUndoState();
|
|
emitStackChange()
|
|
}
|
|
return moved
|
|
};
|
|
this.moveBackward = function(states) {
|
|
var moved = 0;
|
|
while(states && undoStates.length) {
|
|
redoStates.push(undoStates.pop());
|
|
states -= 1;
|
|
moved += 1
|
|
}
|
|
if(moved) {
|
|
document.setDocumentElement((initialDoc.cloneNode(true)));
|
|
eventNotifier.emit(gui.TrivialUndoManager.signalDocumentRootReplaced, {});
|
|
document.getMemberIds().forEach(function(memberid) {
|
|
document.removeCursor(memberid)
|
|
});
|
|
executeOperations(initialState);
|
|
undoStates.forEach(executeOperations);
|
|
currentUndoState = mostRecentUndoState() || initialState;
|
|
emitStackChange()
|
|
}
|
|
return moved
|
|
}
|
|
};
|
|
gui.TrivialUndoManager.signalDocumentRootReplaced = "documentRootReplaced";
|
|
(function() {
|
|
return gui.TrivialUndoManager
|
|
})();
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OperationTransformMatrix = function OperationTransformMatrix() {
|
|
function invertMoveCursorSpecRange(moveCursorSpec) {
|
|
moveCursorSpec.position = moveCursorSpec.position + moveCursorSpec.length;
|
|
moveCursorSpec.length *= -1
|
|
}
|
|
function invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec) {
|
|
var isBackwards = moveCursorSpec.length < 0;
|
|
if(isBackwards) {
|
|
invertMoveCursorSpecRange(moveCursorSpec)
|
|
}
|
|
return isBackwards
|
|
}
|
|
function getStyleReferencingAttributes(setProperties, styleName) {
|
|
var attributes = [];
|
|
function check(attributeName) {
|
|
if(setProperties[attributeName] === styleName) {
|
|
attributes.push(attributeName)
|
|
}
|
|
}
|
|
if(setProperties) {
|
|
["style:parent-style-name", "style:next-style-name"].forEach(check)
|
|
}
|
|
return attributes
|
|
}
|
|
function dropStyleReferencingAttributes(setProperties, deletedStyleName) {
|
|
function del(attributeName) {
|
|
if(setProperties[attributeName] === deletedStyleName) {
|
|
delete setProperties[attributeName]
|
|
}
|
|
}
|
|
if(setProperties) {
|
|
["style:parent-style-name", "style:next-style-name"].forEach(del)
|
|
}
|
|
}
|
|
function cloneOpspec(opspec) {
|
|
var result = {};
|
|
Object.keys(opspec).forEach(function(key) {
|
|
if(typeof opspec[key] === "object") {
|
|
result[key] = cloneOpspec(opspec[key])
|
|
}else {
|
|
result[key] = opspec[key]
|
|
}
|
|
});
|
|
return result
|
|
}
|
|
function dropOverruledAndUnneededAttributes(minorSetProperties, minorRemovedProperties, majorSetProperties, majorRemovedProperties) {
|
|
var i, name, majorChanged = false, minorChanged = false, removedPropertyNames, majorRemovedPropertyNames = [];
|
|
if(majorRemovedProperties && majorRemovedProperties.attributes) {
|
|
majorRemovedPropertyNames = majorRemovedProperties.attributes.split(",")
|
|
}
|
|
if(minorSetProperties && (majorSetProperties || majorRemovedPropertyNames.length > 0)) {
|
|
Object.keys(minorSetProperties).forEach(function(key) {
|
|
var value = minorSetProperties[key], overrulingPropertyValue;
|
|
if(typeof value !== "object") {
|
|
if(majorSetProperties) {
|
|
overrulingPropertyValue = majorSetProperties[key]
|
|
}
|
|
if(overrulingPropertyValue !== undefined) {
|
|
delete minorSetProperties[key];
|
|
minorChanged = true;
|
|
if(overrulingPropertyValue === value) {
|
|
delete majorSetProperties[key];
|
|
majorChanged = true
|
|
}
|
|
}else {
|
|
if(majorRemovedPropertyNames.indexOf(key) !== -1) {
|
|
delete minorSetProperties[key];
|
|
minorChanged = true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if(minorRemovedProperties && (minorRemovedProperties.attributes && (majorSetProperties || majorRemovedPropertyNames.length > 0))) {
|
|
removedPropertyNames = minorRemovedProperties.attributes.split(",");
|
|
for(i = 0;i < removedPropertyNames.length;i += 1) {
|
|
name = removedPropertyNames[i];
|
|
if(majorSetProperties && majorSetProperties[name] !== undefined || majorRemovedPropertyNames && majorRemovedPropertyNames.indexOf(name) !== -1) {
|
|
removedPropertyNames.splice(i, 1);
|
|
i -= 1;
|
|
minorChanged = true
|
|
}
|
|
}
|
|
if(removedPropertyNames.length > 0) {
|
|
minorRemovedProperties.attributes = removedPropertyNames.join(",")
|
|
}else {
|
|
delete minorRemovedProperties.attributes
|
|
}
|
|
}
|
|
return{majorChanged:majorChanged, minorChanged:minorChanged}
|
|
}
|
|
function hasProperties(properties) {
|
|
var key;
|
|
for(key in properties) {
|
|
if(properties.hasOwnProperty(key)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
function hasRemovedProperties(properties) {
|
|
var key;
|
|
for(key in properties) {
|
|
if(properties.hasOwnProperty(key)) {
|
|
if(key !== "attributes" || properties.attributes.length > 0) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
function dropOverruledAndUnneededProperties(minorSet, minorRem, majorSet, majorRem, propertiesName) {
|
|
var minorSP = minorSet ? minorSet[propertiesName] : null, minorRP = minorRem ? minorRem[propertiesName] : null, majorSP = majorSet ? majorSet[propertiesName] : null, majorRP = majorRem ? majorRem[propertiesName] : null, result;
|
|
result = dropOverruledAndUnneededAttributes(minorSP, minorRP, majorSP, majorRP);
|
|
if(minorSP && !hasProperties(minorSP)) {
|
|
delete minorSet[propertiesName]
|
|
}
|
|
if(minorRP && !hasRemovedProperties(minorRP)) {
|
|
delete minorRem[propertiesName]
|
|
}
|
|
if(majorSP && !hasProperties(majorSP)) {
|
|
delete majorSet[propertiesName]
|
|
}
|
|
if(majorRP && !hasRemovedProperties(majorRP)) {
|
|
delete majorRem[propertiesName]
|
|
}
|
|
return result
|
|
}
|
|
function transformAddStyleRemoveStyle(addStyleSpec, removeStyleSpec) {
|
|
var setAttributes, helperOpspec, addStyleSpecResult = [addStyleSpec], removeStyleSpecResult = [removeStyleSpec];
|
|
if(addStyleSpec.styleFamily === removeStyleSpec.styleFamily) {
|
|
setAttributes = getStyleReferencingAttributes(addStyleSpec.setProperties, removeStyleSpec.styleName);
|
|
if(setAttributes.length > 0) {
|
|
helperOpspec = {optype:"UpdateParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, styleName:addStyleSpec.styleName, removedProperties:{attributes:setAttributes.join(",")}};
|
|
removeStyleSpecResult.unshift(helperOpspec)
|
|
}
|
|
dropStyleReferencingAttributes(addStyleSpec.setProperties, removeStyleSpec.styleName)
|
|
}
|
|
return{opSpecsA:addStyleSpecResult, opSpecsB:removeStyleSpecResult}
|
|
}
|
|
function transformApplyDirectStylingApplyDirectStyling(applyDirectStylingSpecA, applyDirectStylingSpecB, hasAPriority) {
|
|
var majorSpec, minorSpec, majorSpecResult, minorSpecResult, majorSpecEnd, minorSpecEnd, dropResult, originalMajorSpec, originalMinorSpec, helperOpspecBefore, helperOpspecAfter, applyDirectStylingSpecAResult = [applyDirectStylingSpecA], applyDirectStylingSpecBResult = [applyDirectStylingSpecB];
|
|
if(!(applyDirectStylingSpecA.position + applyDirectStylingSpecA.length <= applyDirectStylingSpecB.position || applyDirectStylingSpecA.position >= applyDirectStylingSpecB.position + applyDirectStylingSpecB.length)) {
|
|
majorSpec = hasAPriority ? applyDirectStylingSpecA : applyDirectStylingSpecB;
|
|
minorSpec = hasAPriority ? applyDirectStylingSpecB : applyDirectStylingSpecA;
|
|
if(applyDirectStylingSpecA.position !== applyDirectStylingSpecB.position || applyDirectStylingSpecA.length !== applyDirectStylingSpecB.length) {
|
|
originalMajorSpec = cloneOpspec(majorSpec);
|
|
originalMinorSpec = cloneOpspec(minorSpec)
|
|
}
|
|
dropResult = dropOverruledAndUnneededProperties(minorSpec.setProperties, null, majorSpec.setProperties, null, "style:text-properties");
|
|
if(dropResult.majorChanged || dropResult.minorChanged) {
|
|
majorSpecResult = [];
|
|
minorSpecResult = [];
|
|
majorSpecEnd = majorSpec.position + majorSpec.length;
|
|
minorSpecEnd = minorSpec.position + minorSpec.length;
|
|
if(minorSpec.position < majorSpec.position) {
|
|
if(dropResult.minorChanged) {
|
|
helperOpspecBefore = cloneOpspec((originalMinorSpec));
|
|
helperOpspecBefore.length = majorSpec.position - minorSpec.position;
|
|
minorSpecResult.push(helperOpspecBefore);
|
|
minorSpec.position = majorSpec.position;
|
|
minorSpec.length = minorSpecEnd - minorSpec.position
|
|
}
|
|
}else {
|
|
if(majorSpec.position < minorSpec.position) {
|
|
if(dropResult.majorChanged) {
|
|
helperOpspecBefore = cloneOpspec((originalMajorSpec));
|
|
helperOpspecBefore.length = minorSpec.position - majorSpec.position;
|
|
majorSpecResult.push(helperOpspecBefore);
|
|
majorSpec.position = minorSpec.position;
|
|
majorSpec.length = majorSpecEnd - majorSpec.position
|
|
}
|
|
}
|
|
}
|
|
if(minorSpecEnd > majorSpecEnd) {
|
|
if(dropResult.minorChanged) {
|
|
helperOpspecAfter = originalMinorSpec;
|
|
helperOpspecAfter.position = majorSpecEnd;
|
|
helperOpspecAfter.length = minorSpecEnd - majorSpecEnd;
|
|
minorSpecResult.push(helperOpspecAfter);
|
|
minorSpec.length = majorSpecEnd - minorSpec.position
|
|
}
|
|
}else {
|
|
if(majorSpecEnd > minorSpecEnd) {
|
|
if(dropResult.majorChanged) {
|
|
helperOpspecAfter = originalMajorSpec;
|
|
helperOpspecAfter.position = minorSpecEnd;
|
|
helperOpspecAfter.length = majorSpecEnd - minorSpecEnd;
|
|
majorSpecResult.push(helperOpspecAfter);
|
|
majorSpec.length = minorSpecEnd - majorSpec.position
|
|
}
|
|
}
|
|
}
|
|
if(majorSpec.setProperties && hasProperties(majorSpec.setProperties)) {
|
|
majorSpecResult.push(majorSpec)
|
|
}
|
|
if(minorSpec.setProperties && hasProperties(minorSpec.setProperties)) {
|
|
minorSpecResult.push(minorSpec)
|
|
}
|
|
if(hasAPriority) {
|
|
applyDirectStylingSpecAResult = majorSpecResult;
|
|
applyDirectStylingSpecBResult = minorSpecResult
|
|
}else {
|
|
applyDirectStylingSpecAResult = minorSpecResult;
|
|
applyDirectStylingSpecBResult = majorSpecResult
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:applyDirectStylingSpecAResult, opSpecsB:applyDirectStylingSpecBResult}
|
|
}
|
|
function transformApplyDirectStylingInsertText(applyDirectStylingSpec, insertTextSpec) {
|
|
if(insertTextSpec.position <= applyDirectStylingSpec.position) {
|
|
applyDirectStylingSpec.position += insertTextSpec.text.length
|
|
}else {
|
|
if(insertTextSpec.position <= applyDirectStylingSpec.position + applyDirectStylingSpec.length) {
|
|
applyDirectStylingSpec.length += insertTextSpec.text.length
|
|
}
|
|
}
|
|
return{opSpecsA:[applyDirectStylingSpec], opSpecsB:[insertTextSpec]}
|
|
}
|
|
function transformApplyDirectStylingRemoveText(applyDirectStylingSpec, removeTextSpec) {
|
|
var applyDirectStylingSpecEnd = applyDirectStylingSpec.position + applyDirectStylingSpec.length, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, applyDirectStylingSpecResult = [applyDirectStylingSpec], removeTextSpecResult = [removeTextSpec];
|
|
if(removeTextSpecEnd <= applyDirectStylingSpec.position) {
|
|
applyDirectStylingSpec.position -= removeTextSpec.length
|
|
}else {
|
|
if(removeTextSpec.position < applyDirectStylingSpecEnd) {
|
|
if(applyDirectStylingSpec.position < removeTextSpec.position) {
|
|
if(removeTextSpecEnd < applyDirectStylingSpecEnd) {
|
|
applyDirectStylingSpec.length -= removeTextSpec.length
|
|
}else {
|
|
applyDirectStylingSpec.length = removeTextSpec.position - applyDirectStylingSpec.position
|
|
}
|
|
}else {
|
|
applyDirectStylingSpec.position = removeTextSpec.position;
|
|
if(removeTextSpecEnd < applyDirectStylingSpecEnd) {
|
|
applyDirectStylingSpec.length = applyDirectStylingSpecEnd - removeTextSpecEnd
|
|
}else {
|
|
applyDirectStylingSpecResult = []
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:applyDirectStylingSpecResult, opSpecsB:removeTextSpecResult}
|
|
}
|
|
function transformApplyDirectStylingSplitParagraph(applyDirectStylingSpec, splitParagraphSpec) {
|
|
if(splitParagraphSpec.position < applyDirectStylingSpec.position) {
|
|
applyDirectStylingSpec.position += 1
|
|
}else {
|
|
if(splitParagraphSpec.position < applyDirectStylingSpec.position + applyDirectStylingSpec.length) {
|
|
applyDirectStylingSpec.length += 1
|
|
}
|
|
}
|
|
return{opSpecsA:[applyDirectStylingSpec], opSpecsB:[splitParagraphSpec]}
|
|
}
|
|
function transformInsertTextInsertText(insertTextSpecA, insertTextSpecB, hasAPriority) {
|
|
if(insertTextSpecA.position < insertTextSpecB.position) {
|
|
insertTextSpecB.position += insertTextSpecA.text.length
|
|
}else {
|
|
if(insertTextSpecA.position > insertTextSpecB.position) {
|
|
insertTextSpecA.position += insertTextSpecB.text.length
|
|
}else {
|
|
if(hasAPriority) {
|
|
insertTextSpecB.position += insertTextSpecA.text.length
|
|
}else {
|
|
insertTextSpecA.position += insertTextSpecB.text.length
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:[insertTextSpecA], opSpecsB:[insertTextSpecB]}
|
|
}
|
|
function transformInsertTextMoveCursor(insertTextSpec, moveCursorSpec) {
|
|
var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec);
|
|
if(insertTextSpec.position < moveCursorSpec.position) {
|
|
moveCursorSpec.position += insertTextSpec.text.length
|
|
}else {
|
|
if(insertTextSpec.position < moveCursorSpec.position + moveCursorSpec.length) {
|
|
moveCursorSpec.length += insertTextSpec.text.length
|
|
}
|
|
}
|
|
if(isMoveCursorSpecRangeInverted) {
|
|
invertMoveCursorSpecRange(moveCursorSpec)
|
|
}
|
|
return{opSpecsA:[insertTextSpec], opSpecsB:[moveCursorSpec]}
|
|
}
|
|
function transformInsertTextRemoveText(insertTextSpec, removeTextSpec) {
|
|
var helperOpspec, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, insertTextSpecResult = [insertTextSpec], removeTextSpecResult = [removeTextSpec];
|
|
if(removeTextSpecEnd <= insertTextSpec.position) {
|
|
insertTextSpec.position -= removeTextSpec.length
|
|
}else {
|
|
if(insertTextSpec.position <= removeTextSpec.position) {
|
|
removeTextSpec.position += insertTextSpec.text.length
|
|
}else {
|
|
removeTextSpec.length = insertTextSpec.position - removeTextSpec.position;
|
|
helperOpspec = {optype:"RemoveText", memberid:removeTextSpec.memberid, timestamp:removeTextSpec.timestamp, position:insertTextSpec.position + insertTextSpec.text.length, length:removeTextSpecEnd - insertTextSpec.position};
|
|
removeTextSpecResult.unshift(helperOpspec);
|
|
insertTextSpec.position = removeTextSpec.position
|
|
}
|
|
}
|
|
return{opSpecsA:insertTextSpecResult, opSpecsB:removeTextSpecResult}
|
|
}
|
|
function transformInsertTextSplitParagraph(insertTextSpec, splitParagraphSpec, hasAPriority) {
|
|
if(insertTextSpec.position < splitParagraphSpec.position) {
|
|
splitParagraphSpec.position += insertTextSpec.text.length
|
|
}else {
|
|
if(insertTextSpec.position > splitParagraphSpec.position) {
|
|
insertTextSpec.position += 1
|
|
}else {
|
|
if(hasAPriority) {
|
|
splitParagraphSpec.position += insertTextSpec.text.length
|
|
}else {
|
|
insertTextSpec.position += 1
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
return{opSpecsA:[insertTextSpec], opSpecsB:[splitParagraphSpec]}
|
|
}
|
|
function transformUpdateParagraphStyleUpdateParagraphStyle(updateParagraphStyleSpecA, updateParagraphStyleSpecB, hasAPriority) {
|
|
var majorSpec, minorSpec, updateParagraphStyleSpecAResult = [updateParagraphStyleSpecA], updateParagraphStyleSpecBResult = [updateParagraphStyleSpecB];
|
|
if(updateParagraphStyleSpecA.styleName === updateParagraphStyleSpecB.styleName) {
|
|
majorSpec = hasAPriority ? updateParagraphStyleSpecA : updateParagraphStyleSpecB;
|
|
minorSpec = hasAPriority ? updateParagraphStyleSpecB : updateParagraphStyleSpecA;
|
|
dropOverruledAndUnneededProperties(minorSpec.setProperties, minorSpec.removedProperties, majorSpec.setProperties, majorSpec.removedProperties, "style:paragraph-properties");
|
|
dropOverruledAndUnneededProperties(minorSpec.setProperties, minorSpec.removedProperties, majorSpec.setProperties, majorSpec.removedProperties, "style:text-properties");
|
|
dropOverruledAndUnneededAttributes(minorSpec.setProperties || null, (minorSpec.removedProperties) || null, majorSpec.setProperties || null, (majorSpec.removedProperties) || null);
|
|
if(!(majorSpec.setProperties && hasProperties(majorSpec.setProperties)) && !(majorSpec.removedProperties && hasRemovedProperties(majorSpec.removedProperties))) {
|
|
if(hasAPriority) {
|
|
updateParagraphStyleSpecAResult = []
|
|
}else {
|
|
updateParagraphStyleSpecBResult = []
|
|
}
|
|
}
|
|
if(!(minorSpec.setProperties && hasProperties(minorSpec.setProperties)) && !(minorSpec.removedProperties && hasRemovedProperties(minorSpec.removedProperties))) {
|
|
if(hasAPriority) {
|
|
updateParagraphStyleSpecBResult = []
|
|
}else {
|
|
updateParagraphStyleSpecAResult = []
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:updateParagraphStyleSpecAResult, opSpecsB:updateParagraphStyleSpecBResult}
|
|
}
|
|
function transformUpdateMetadataUpdateMetadata(updateMetadataSpecA, updateMetadataSpecB, hasAPriority) {
|
|
var majorSpec, minorSpec, updateMetadataSpecAResult = [updateMetadataSpecA], updateMetadataSpecBResult = [updateMetadataSpecB];
|
|
majorSpec = hasAPriority ? updateMetadataSpecA : updateMetadataSpecB;
|
|
minorSpec = hasAPriority ? updateMetadataSpecB : updateMetadataSpecA;
|
|
dropOverruledAndUnneededAttributes(minorSpec.setProperties || null, minorSpec.removedProperties || null, majorSpec.setProperties || null, majorSpec.removedProperties || null);
|
|
if(!(majorSpec.setProperties && hasProperties(majorSpec.setProperties)) && !(majorSpec.removedProperties && hasRemovedProperties(majorSpec.removedProperties))) {
|
|
if(hasAPriority) {
|
|
updateMetadataSpecAResult = []
|
|
}else {
|
|
updateMetadataSpecBResult = []
|
|
}
|
|
}
|
|
if(!(minorSpec.setProperties && hasProperties(minorSpec.setProperties)) && !(minorSpec.removedProperties && hasRemovedProperties(minorSpec.removedProperties))) {
|
|
if(hasAPriority) {
|
|
updateMetadataSpecBResult = []
|
|
}else {
|
|
updateMetadataSpecAResult = []
|
|
}
|
|
}
|
|
return{opSpecsA:updateMetadataSpecAResult, opSpecsB:updateMetadataSpecBResult}
|
|
}
|
|
function transformSplitParagraphSplitParagraph(splitParagraphSpecA, splitParagraphSpecB, hasAPriority) {
|
|
if(splitParagraphSpecA.position < splitParagraphSpecB.position) {
|
|
splitParagraphSpecB.position += 1
|
|
}else {
|
|
if(splitParagraphSpecA.position > splitParagraphSpecB.position) {
|
|
splitParagraphSpecA.position += 1
|
|
}else {
|
|
if(splitParagraphSpecA.position === splitParagraphSpecB.position) {
|
|
if(hasAPriority) {
|
|
splitParagraphSpecB.position += 1
|
|
}else {
|
|
splitParagraphSpecA.position += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:[splitParagraphSpecA], opSpecsB:[splitParagraphSpecB]}
|
|
}
|
|
function transformMoveCursorRemoveCursor(moveCursorSpec, removeCursorSpec) {
|
|
var isSameCursorRemoved = moveCursorSpec.memberid === removeCursorSpec.memberid;
|
|
return{opSpecsA:isSameCursorRemoved ? [] : [moveCursorSpec], opSpecsB:[removeCursorSpec]}
|
|
}
|
|
function transformMoveCursorRemoveText(moveCursorSpec, removeTextSpec) {
|
|
var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec), moveCursorSpecEnd = moveCursorSpec.position + moveCursorSpec.length, removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length;
|
|
if(removeTextSpecEnd <= moveCursorSpec.position) {
|
|
moveCursorSpec.position -= removeTextSpec.length
|
|
}else {
|
|
if(removeTextSpec.position < moveCursorSpecEnd) {
|
|
if(moveCursorSpec.position < removeTextSpec.position) {
|
|
if(removeTextSpecEnd < moveCursorSpecEnd) {
|
|
moveCursorSpec.length -= removeTextSpec.length
|
|
}else {
|
|
moveCursorSpec.length = removeTextSpec.position - moveCursorSpec.position
|
|
}
|
|
}else {
|
|
moveCursorSpec.position = removeTextSpec.position;
|
|
if(removeTextSpecEnd < moveCursorSpecEnd) {
|
|
moveCursorSpec.length = moveCursorSpecEnd - removeTextSpecEnd
|
|
}else {
|
|
moveCursorSpec.length = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(isMoveCursorSpecRangeInverted) {
|
|
invertMoveCursorSpecRange(moveCursorSpec)
|
|
}
|
|
return{opSpecsA:[moveCursorSpec], opSpecsB:[removeTextSpec]}
|
|
}
|
|
function transformMoveCursorSplitParagraph(moveCursorSpec, splitParagraphSpec) {
|
|
var isMoveCursorSpecRangeInverted = invertMoveCursorSpecRangeOnNegativeLength(moveCursorSpec);
|
|
if(splitParagraphSpec.position < moveCursorSpec.position) {
|
|
moveCursorSpec.position += 1
|
|
}else {
|
|
if(splitParagraphSpec.position < moveCursorSpec.position + moveCursorSpec.length) {
|
|
moveCursorSpec.length += 1
|
|
}
|
|
}
|
|
if(isMoveCursorSpecRangeInverted) {
|
|
invertMoveCursorSpecRange(moveCursorSpec)
|
|
}
|
|
return{opSpecsA:[moveCursorSpec], opSpecsB:[splitParagraphSpec]}
|
|
}
|
|
function transformRemoveCursorRemoveCursor(removeCursorSpecA, removeCursorSpecB) {
|
|
var isSameMemberid = removeCursorSpecA.memberid === removeCursorSpecB.memberid;
|
|
return{opSpecsA:isSameMemberid ? [] : [removeCursorSpecA], opSpecsB:isSameMemberid ? [] : [removeCursorSpecB]}
|
|
}
|
|
function transformRemoveStyleRemoveStyle(removeStyleSpecA, removeStyleSpecB) {
|
|
var isSameStyle = removeStyleSpecA.styleName === removeStyleSpecB.styleName && removeStyleSpecA.styleFamily === removeStyleSpecB.styleFamily;
|
|
return{opSpecsA:isSameStyle ? [] : [removeStyleSpecA], opSpecsB:isSameStyle ? [] : [removeStyleSpecB]}
|
|
}
|
|
function transformRemoveStyleSetParagraphStyle(removeStyleSpec, setParagraphStyleSpec) {
|
|
var helperOpspec, removeStyleSpecResult = [removeStyleSpec], setParagraphStyleSpecResult = [setParagraphStyleSpec];
|
|
if(removeStyleSpec.styleFamily === "paragraph" && removeStyleSpec.styleName === setParagraphStyleSpec.styleName) {
|
|
helperOpspec = {optype:"SetParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, position:setParagraphStyleSpec.position, styleName:""};
|
|
removeStyleSpecResult.unshift(helperOpspec);
|
|
setParagraphStyleSpec.styleName = ""
|
|
}
|
|
return{opSpecsA:removeStyleSpecResult, opSpecsB:setParagraphStyleSpecResult}
|
|
}
|
|
function transformRemoveStyleUpdateParagraphStyle(removeStyleSpec, updateParagraphStyleSpec) {
|
|
var setAttributes, helperOpspec, removeStyleSpecResult = [removeStyleSpec], updateParagraphStyleSpecResult = [updateParagraphStyleSpec];
|
|
if(removeStyleSpec.styleFamily === "paragraph") {
|
|
setAttributes = getStyleReferencingAttributes(updateParagraphStyleSpec.setProperties, removeStyleSpec.styleName);
|
|
if(setAttributes.length > 0) {
|
|
helperOpspec = {optype:"UpdateParagraphStyle", memberid:removeStyleSpec.memberid, timestamp:removeStyleSpec.timestamp, styleName:updateParagraphStyleSpec.styleName, removedProperties:{attributes:setAttributes.join(",")}};
|
|
removeStyleSpecResult.unshift(helperOpspec)
|
|
}
|
|
if(removeStyleSpec.styleName === updateParagraphStyleSpec.styleName) {
|
|
updateParagraphStyleSpecResult = []
|
|
}else {
|
|
dropStyleReferencingAttributes(updateParagraphStyleSpec.setProperties, removeStyleSpec.styleName)
|
|
}
|
|
}
|
|
return{opSpecsA:removeStyleSpecResult, opSpecsB:updateParagraphStyleSpecResult}
|
|
}
|
|
function transformRemoveTextRemoveText(removeTextSpecA, removeTextSpecB) {
|
|
var removeTextSpecAEnd = removeTextSpecA.position + removeTextSpecA.length, removeTextSpecBEnd = removeTextSpecB.position + removeTextSpecB.length, removeTextSpecAResult = [removeTextSpecA], removeTextSpecBResult = [removeTextSpecB];
|
|
if(removeTextSpecBEnd <= removeTextSpecA.position) {
|
|
removeTextSpecA.position -= removeTextSpecB.length
|
|
}else {
|
|
if(removeTextSpecAEnd <= removeTextSpecB.position) {
|
|
removeTextSpecB.position -= removeTextSpecA.length
|
|
}else {
|
|
if(removeTextSpecB.position < removeTextSpecAEnd) {
|
|
if(removeTextSpecA.position < removeTextSpecB.position) {
|
|
if(removeTextSpecBEnd < removeTextSpecAEnd) {
|
|
removeTextSpecA.length = removeTextSpecA.length - removeTextSpecB.length
|
|
}else {
|
|
removeTextSpecA.length = removeTextSpecB.position - removeTextSpecA.position
|
|
}
|
|
if(removeTextSpecAEnd < removeTextSpecBEnd) {
|
|
removeTextSpecB.position = removeTextSpecA.position;
|
|
removeTextSpecB.length = removeTextSpecBEnd - removeTextSpecAEnd
|
|
}else {
|
|
removeTextSpecBResult = []
|
|
}
|
|
}else {
|
|
if(removeTextSpecAEnd < removeTextSpecBEnd) {
|
|
removeTextSpecB.length = removeTextSpecB.length - removeTextSpecA.length
|
|
}else {
|
|
if(removeTextSpecB.position < removeTextSpecA.position) {
|
|
removeTextSpecB.length = removeTextSpecA.position - removeTextSpecB.position
|
|
}else {
|
|
removeTextSpecBResult = []
|
|
}
|
|
}
|
|
if(removeTextSpecBEnd < removeTextSpecAEnd) {
|
|
removeTextSpecA.position = removeTextSpecB.position;
|
|
removeTextSpecA.length = removeTextSpecAEnd - removeTextSpecBEnd
|
|
}else {
|
|
removeTextSpecAResult = []
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return{opSpecsA:removeTextSpecAResult, opSpecsB:removeTextSpecBResult}
|
|
}
|
|
function transformRemoveTextSplitParagraph(removeTextSpec, splitParagraphSpec) {
|
|
var removeTextSpecEnd = removeTextSpec.position + removeTextSpec.length, helperOpspec, removeTextSpecResult = [removeTextSpec], splitParagraphSpecResult = [splitParagraphSpec];
|
|
if(splitParagraphSpec.position <= removeTextSpec.position) {
|
|
removeTextSpec.position += 1
|
|
}else {
|
|
if(splitParagraphSpec.position < removeTextSpecEnd) {
|
|
removeTextSpec.length = splitParagraphSpec.position - removeTextSpec.position;
|
|
helperOpspec = {optype:"RemoveText", memberid:removeTextSpec.memberid, timestamp:removeTextSpec.timestamp, position:splitParagraphSpec.position + 1, length:removeTextSpecEnd - splitParagraphSpec.position};
|
|
removeTextSpecResult.unshift(helperOpspec)
|
|
}
|
|
}
|
|
if(removeTextSpec.position + removeTextSpec.length <= splitParagraphSpec.position) {
|
|
splitParagraphSpec.position -= removeTextSpec.length
|
|
}else {
|
|
if(removeTextSpec.position < splitParagraphSpec.position) {
|
|
splitParagraphSpec.position = removeTextSpec.position
|
|
}
|
|
}
|
|
return{opSpecsA:removeTextSpecResult, opSpecsB:splitParagraphSpecResult}
|
|
}
|
|
function passUnchanged(opSpecA, opSpecB) {
|
|
return{opSpecsA:[opSpecA], opSpecsB:[opSpecB]}
|
|
}
|
|
var transformations;
|
|
transformations = {"AddCursor":{"AddCursor":passUnchanged, "AddMember":passUnchanged, "AddStyle":passUnchanged, "ApplyDirectStyling":passUnchanged, "InsertText":passUnchanged, "MoveCursor":passUnchanged, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "AddMember":{"AddStyle":passUnchanged,
|
|
"InsertText":passUnchanged, "MoveCursor":passUnchanged, "RemoveCursor":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "AddStyle":{"AddStyle":passUnchanged, "ApplyDirectStyling":passUnchanged, "InsertText":passUnchanged, "MoveCursor":passUnchanged, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":transformAddStyleRemoveStyle,
|
|
"RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "ApplyDirectStyling":{"ApplyDirectStyling":transformApplyDirectStylingApplyDirectStyling, "InsertText":transformApplyDirectStylingInsertText, "MoveCursor":passUnchanged, "RemoveCursor":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformApplyDirectStylingRemoveText, "SetParagraphStyle":passUnchanged,
|
|
"SplitParagraph":transformApplyDirectStylingSplitParagraph, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "InsertText":{"InsertText":transformInsertTextInsertText, "MoveCursor":transformInsertTextMoveCursor, "RemoveCursor":passUnchanged, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformInsertTextRemoveText, "SplitParagraph":transformInsertTextSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged},
|
|
"MoveCursor":{"MoveCursor":passUnchanged, "RemoveCursor":transformMoveCursorRemoveCursor, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged, "RemoveText":transformMoveCursorRemoveText, "SetParagraphStyle":passUnchanged, "SplitParagraph":transformMoveCursorSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveCursor":{"RemoveCursor":transformRemoveCursorRemoveCursor, "RemoveMember":passUnchanged, "RemoveStyle":passUnchanged,
|
|
"RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveMember":{"RemoveStyle":passUnchanged, "RemoveText":passUnchanged, "SetParagraphStyle":passUnchanged, "SplitParagraph":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "RemoveStyle":{"RemoveStyle":transformRemoveStyleRemoveStyle, "RemoveText":passUnchanged, "SetParagraphStyle":transformRemoveStyleSetParagraphStyle,
|
|
"SplitParagraph":passUnchanged, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":transformRemoveStyleUpdateParagraphStyle}, "RemoveText":{"RemoveText":transformRemoveTextRemoveText, "SplitParagraph":transformRemoveTextSplitParagraph, "UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "SetParagraphStyle":{"UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "SplitParagraph":{"SplitParagraph":transformSplitParagraphSplitParagraph,
|
|
"UpdateMember":passUnchanged, "UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "UpdateMember":{"UpdateMetadata":passUnchanged, "UpdateParagraphStyle":passUnchanged}, "UpdateMetadata":{"UpdateMetadata":transformUpdateMetadataUpdateMetadata, "UpdateParagraphStyle":passUnchanged}, "UpdateParagraphStyle":{"UpdateParagraphStyle":transformUpdateParagraphStyleUpdateParagraphStyle}};
|
|
this.passUnchanged = passUnchanged;
|
|
this.extendTransformations = function(moreTransformations) {
|
|
Object.keys(moreTransformations).forEach(function(optypeA) {
|
|
var moreTransformationsOptypeAMap = moreTransformations[optypeA], optypeAMap, isExtendingOptypeAMap = transformations.hasOwnProperty(optypeA);
|
|
runtime.log((isExtendingOptypeAMap ? "Extending" : "Adding") + " map for optypeA: " + optypeA);
|
|
if(!isExtendingOptypeAMap) {
|
|
transformations[optypeA] = {}
|
|
}
|
|
optypeAMap = transformations[optypeA];
|
|
Object.keys(moreTransformationsOptypeAMap).forEach(function(optypeB) {
|
|
var isOverwritingOptypeBEntry = optypeAMap.hasOwnProperty(optypeB);
|
|
runtime.assert(optypeA <= optypeB, "Wrong order:" + optypeA + ", " + optypeB);
|
|
runtime.log(" " + (isOverwritingOptypeBEntry ? "Overwriting" : "Adding") + " entry for optypeB: " + optypeB);
|
|
optypeAMap[optypeB] = moreTransformationsOptypeAMap[optypeB]
|
|
})
|
|
})
|
|
};
|
|
this.transformOpspecVsOpspec = function(opSpecA, opSpecB) {
|
|
var isOptypeAAlphaNumericSmaller = opSpecA.optype <= opSpecB.optype, helper, transformationFunctionMap, transformationFunction, result;
|
|
runtime.log("Crosstransforming:");
|
|
runtime.log(runtime.toJson(opSpecA));
|
|
runtime.log(runtime.toJson(opSpecB));
|
|
if(!isOptypeAAlphaNumericSmaller) {
|
|
helper = opSpecA;
|
|
opSpecA = opSpecB;
|
|
opSpecB = helper
|
|
}
|
|
transformationFunctionMap = transformations[opSpecA.optype];
|
|
transformationFunction = transformationFunctionMap && transformationFunctionMap[opSpecB.optype];
|
|
if(transformationFunction) {
|
|
result = transformationFunction(opSpecA, opSpecB, !isOptypeAAlphaNumericSmaller);
|
|
if(!isOptypeAAlphaNumericSmaller && result !== null) {
|
|
result = {opSpecsA:result.opSpecsB, opSpecsB:result.opSpecsA}
|
|
}
|
|
}else {
|
|
result = null
|
|
}
|
|
runtime.log("result:");
|
|
if(result) {
|
|
runtime.log(runtime.toJson(result.opSpecsA));
|
|
runtime.log(runtime.toJson(result.opSpecsB))
|
|
}else {
|
|
runtime.log("null")
|
|
}
|
|
return result
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
This file is part of WebODF.
|
|
|
|
WebODF is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version.
|
|
|
|
WebODF is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
|
@licend
|
|
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.OperationTransformer = function OperationTransformer() {
|
|
var operationFactory, operationTransformMatrix = new ops.OperationTransformMatrix;
|
|
function operations(opspecs) {
|
|
var ops = [];
|
|
opspecs.forEach(function(opspec) {
|
|
ops.push(operationFactory.create(opspec))
|
|
});
|
|
return ops
|
|
}
|
|
function transformOpVsOp(opSpecA, opSpecB) {
|
|
return operationTransformMatrix.transformOpspecVsOpspec(opSpecA, opSpecB)
|
|
}
|
|
function transformOpListVsOp(opSpecsA, opSpecB) {
|
|
var transformResult, transformListResult, transformedOpspecsA = [], transformedOpspecsB = [];
|
|
while(opSpecsA.length > 0 && opSpecB) {
|
|
transformResult = transformOpVsOp(opSpecsA.shift(), opSpecB);
|
|
if(!transformResult) {
|
|
return null
|
|
}
|
|
transformedOpspecsA = transformedOpspecsA.concat(transformResult.opSpecsA);
|
|
if(transformResult.opSpecsB.length === 0) {
|
|
transformedOpspecsA = transformedOpspecsA.concat(opSpecsA);
|
|
opSpecB = null;
|
|
break
|
|
}
|
|
while(transformResult.opSpecsB.length > 1) {
|
|
transformListResult = transformOpListVsOp(opSpecsA, transformResult.opSpecsB.shift());
|
|
if(!transformListResult) {
|
|
return null
|
|
}
|
|
transformedOpspecsB = transformedOpspecsB.concat(transformListResult.opSpecsB);
|
|
opSpecsA = transformListResult.opSpecsA
|
|
}
|
|
opSpecB = transformResult.opSpecsB.pop()
|
|
}
|
|
if(opSpecB) {
|
|
transformedOpspecsB.push(opSpecB)
|
|
}
|
|
return{opSpecsA:transformedOpspecsA, opSpecsB:transformedOpspecsB}
|
|
}
|
|
this.setOperationFactory = function(f) {
|
|
operationFactory = f
|
|
};
|
|
this.getOperationTransformMatrix = function() {
|
|
return operationTransformMatrix
|
|
};
|
|
this.transform = function(opSpecsA, opSpecsB) {
|
|
var transformResult, transformedOpspecsB = [];
|
|
while(opSpecsB.length > 0) {
|
|
transformResult = transformOpListVsOp(opSpecsA, opSpecsB.shift());
|
|
if(!transformResult) {
|
|
return null
|
|
}
|
|
opSpecsA = transformResult.opSpecsA;
|
|
transformedOpspecsB = transformedOpspecsB.concat(transformResult.opSpecsB)
|
|
}
|
|
return{opsA:operations(opSpecsA), opsB:operations(transformedOpspecsB)}
|
|
}
|
|
};
|
|
/*
|
|
|
|
Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
|
|
|
@licstart
|
|
The JavaScript code in this page is free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU Affero General Public License
|
|
(GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. The code is distributed
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
As additional permission under GNU AGPL version 3 section 7, you
|
|
may distribute non-source (e.g., minimized or compacted) forms of
|
|
that code without the copy of the GNU GPL normally required by
|
|
section 4, provided you include this license notice and a URL
|
|
through which recipients can access the Corresponding Source.
|
|
|
|
As a special exception to the AGPL, any HTML file which merely makes function
|
|
calls to this code, and for that purpose includes it by reference shall be
|
|
deemed a separate work for copyright law purposes. In addition, the copyright
|
|
holders of this code give you permission to combine this code with free
|
|
software libraries that are released under the GNU LGPL. You may copy and
|
|
distribute such a system following the terms of the GNU AGPL for this code
|
|
and the LGPL for the libraries. If you modify this code, you may extend this
|
|
exception to your version of the code, but you are not obligated to do so.
|
|
If you do not wish to do so, delete this exception statement from your
|
|
version.
|
|
|
|
This license applies to this entire compilation.
|
|
@licend
|
|
@source: http://www.webodf.org/
|
|
@source: https://github.com/kogmbh/WebODF/
|
|
*/
|
|
ops.Server = function Server() {
|
|
};
|
|
ops.Server.prototype.connect = function(timeout, cb) {
|
|
};
|
|
ops.Server.prototype.networkStatus = function() {
|
|
};
|
|
ops.Server.prototype.login = function(login, password, successCb, failCb) {
|
|
};
|
|
ops.Server.prototype.joinSession = function(userId, sessionId, successCb, failCb) {
|
|
};
|
|
ops.Server.prototype.leaveSession = function(sessionId, memberId, successCb, failCb) {
|
|
};
|
|
ops.Server.prototype.getGenesisUrl = function(sessionId) {
|
|
};
|
|
var webodf_css = '@namespace draw url(urn:oasis:names:tc:opendocument:xmlns:drawing:1.0);\n@namespace fo url(urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0);\n@namespace office url(urn:oasis:names:tc:opendocument:xmlns:office:1.0);\n@namespace presentation url(urn:oasis:names:tc:opendocument:xmlns:presentation:1.0);\n@namespace style url(urn:oasis:names:tc:opendocument:xmlns:style:1.0);\n@namespace svg url(urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0);\n@namespace table url(urn:oasis:names:tc:opendocument:xmlns:table:1.0);\n@namespace text url(urn:oasis:names:tc:opendocument:xmlns:text:1.0);\n@namespace webodfhelper url(urn:webodf:names:helper);\n@namespace cursor url(urn:webodf:names:cursor);\n@namespace editinfo url(urn:webodf:names:editinfo);\n@namespace annotation url(urn:webodf:names:annotation);\n@namespace dc url(http://purl.org/dc/elements/1.1/);\n@namespace svgns url(http://www.w3.org/2000/svg);\n\noffice|document > *, office|document-content > * {\n display: none;\n}\noffice|body, office|document {\n display: inline-block;\n position: relative;\n}\n\ntext|p, text|h {\n display: block;\n padding: 0;\n margin: 0;\n line-height: normal;\n position: relative;\n min-height: 1.3em; /* prevent empty paragraphs and headings from collapsing if they are empty */\n}\n*[webodfhelper|containsparagraphanchor] {\n position: relative;\n}\ntext|s {\n white-space: pre;\n}\ntext|tab {\n display: inline;\n white-space: pre;\n}\ntext|tracked-changes {\n /*Consumers that do not support change tracking, should ignore changes.*/\n display: none;\n}\noffice|binary-data {\n display: none;\n}\noffice|text {\n display: block;\n text-align: left;\n overflow: visible;\n word-wrap: break-word;\n}\n\noffice|text::selection {\n /** Let\'s not draw selection highlight that overflows into the office|text\n * node when selecting content across several paragraphs\n */\n background: transparent;\n}\n\noffice|document *::selection {\n background: transparent;\n}\noffice|document *::-moz-selection {\n background: transparent;\n}\n\noffice|text * draw|text-box {\n/** only for text documents */\n display: block;\n border: 1px solid #d3d3d3;\n}\ndraw|frame {\n /** make sure frames are above the main body. */\n z-index: 1;\n}\noffice|spreadsheet {\n display: block;\n border-collapse: collapse;\n empty-cells: show;\n font-family: sans-serif;\n font-size: 10pt;\n text-align: left;\n page-break-inside: avoid;\n overflow: hidden;\n}\noffice|presentation {\n display: inline-block;\n text-align: left;\n}\n#shadowContent {\n display: inline-block;\n text-align: left;\n}\ndraw|page {\n display: block;\n position: relative;\n overflow: hidden;\n}\npresentation|notes, presentation|footer-decl, presentation|date-time-decl {\n display: none;\n}\n@media print {\n draw|page {\n border: 1pt solid black;\n page-break-inside: avoid;\n }\n presentation|notes {\n /*TODO*/\n }\n}\noffice|spreadsheet text|p {\n border: 0px;\n padding: 1px;\n margin: 0px;\n}\noffice|spreadsheet table|table {\n margin: 3px;\n}\noffice|spreadsheet table|table:after {\n /* show sheet name the end of the sheet */\n /*content: attr(table|name);*/ /* gives parsing error in opera */\n}\noffice|spreadsheet table|table-row {\n counter-increment: row;\n}\noffice|spreadsheet table|table-row:before {\n width: 3em;\n background: #cccccc;\n border: 1px solid black;\n text-align: center;\n content: counter(row);\n display: table-cell;\n}\noffice|spreadsheet table|table-cell {\n border: 1px solid #cccccc;\n}\ntable|table {\n display: table;\n}\ndraw|frame table|table {\n width: 100%;\n height: 100%;\n background: white;\n}\ntable|table-header-rows {\n display: table-header-group;\n}\ntable|table-row {\n display: table-row;\n}\ntable|table-column {\n display: table-column;\n}\ntable|table-cell {\n width: 0.889in;\n display: table-cell;\n word-break: break-all; /* prevent long words from extending out the table cell */\n}\ndraw|frame {\n display: block;\n}\ndraw|image {\n display: block;\n width: 100%;\n height: 100%;\n top: 0px;\n left: 0px;\n background-repeat: no-repeat;\n background-size: 100% 100%;\n -moz-background-size: 100% 100%;\n}\n/* only show the first image in frame */\ndraw|frame > draw|image:nth-of-type(n+2) {\n display: none;\n}\ntext|list:before {\n display: none;\n content:"";\n}\ntext|list {\n counter-reset: list;\n}\ntext|list-item {\n display: block;\n}\ntext|number {\n display:none;\n}\n\ntext|a {\n color: blue;\n text-decoration: underline;\n cursor: pointer;\n}\noffice|text[webodfhelper|links="inactive"] text|a {\n cursor: text;\n}\ntext|note-citation {\n vertical-align: super;\n font-size: smaller;\n}\ntext|note-body {\n display: none;\n}\ntext|note:hover text|note-citation {\n background: #dddddd;\n}\ntext|note:hover text|note-body {\n display: block;\n left:1em;\n max-width: 80%;\n position: absolute;\n background: #ffffaa;\n}\nsvg|title, svg|desc {\n display: none;\n}\nvideo {\n width: 100%;\n height: 100%\n}\n\n/* below set up the cursor */\ncursor|cursor {\n display: inline;\n width: 0;\n height: 1em;\n /* making the position relative enables the avatar to use\n the cursor as reference for its absolute position */\n position: relative;\n z-index: 1;\n pointer-events: none;\n}\n\ncursor|cursor > .caret {\n /* IMPORTANT: when changing these values ensure DEFAULT_CARET_TOP and DEFAULT_CARET_HEIGHT\n in Caret.js remain in sync */\n display: inline;\n position: absolute;\n top: 5%; /* push down the caret; 0px can do the job, 5% looks better, 10% is a bit over */\n height: 1em;\n border-left: 2px solid black;\n outline: none;\n}\n\ncursor|cursor > .handle {\n padding: 3px;\n box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);\n border: none !important;\n border-radius: 5px;\n opacity: 0.3;\n}\n\ncursor|cursor > .handle > img {\n border-radius: 5px;\n}\n\ncursor|cursor > .handle.active {\n opacity: 0.8;\n}\n\ncursor|cursor > .handle:after {\n content: \' \';\n position: absolute;\n width: 0px;\n height: 0px;\n border-style: solid;\n border-width: 8.7px 5px 0 5px;\n border-color: black transparent transparent transparent;\n\n top: 100%;\n left: 43%;\n}\n\n/** Input Method Editor input pane & behaviours */\n/* not within a cursor */\n#eventTrap {\n height: auto;\n display: block;\n position: absolute;\n width: 1px;\n outline: none;\n opacity: 0;\n color: rgba(255, 255, 255, 0); /* hide the blinking caret by setting the colour to fully transparent */\n overflow: hidden; /* The overflow visibility is used to hide and show characters being entered */\n pointer-events: none;\n}\n\n/* within a cursor */\ncursor|cursor > #composer {\n text-decoration: underline;\n}\n\ncursor|cursor[cursor|composing="true"] > #composer {\n display: inline-block;\n height: auto;\n width: auto;\n}\n\ncursor|cursor[cursor|composing="true"] {\n display: inline-block;\n width: auto;\n height: inherit;\n}\n\ncursor|cursor[cursor|composing="true"] > .caret {\n /* during composition, the caret should be pushed along by the composition text, inline with the text */\n position: static;\n /* as it is now part of an inline-block, it will no longer need correct to top or height values to align properly */\n height: auto !important;\n top: auto !important;\n}\n\neditinfo|editinfo {\n /* Empty or invisible display:inline elements respond very badly to mouse selection.\n Inline blocks are much more reliably selectable in Chrome & friends */\n display: inline-block;\n}\n\n.editInfoMarker {\n position: absolute;\n width: 10px;\n height: 100%;\n left: -20px;\n opacity: 0.8;\n top: 0;\n border-radius: 5px;\n background-color: transparent;\n box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);\n}\n.editInfoMarker:hover {\n box-shadow: 0px 0px 8px rgba(0, 0, 0, 1);\n}\n\n.editInfoHandle {\n position: absolute;\n background-color: black;\n padding: 5px;\n border-radius: 5px;\n opacity: 0.8;\n box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);\n bottom: 100%;\n margin-bottom: 10px;\n z-index: 3;\n left: -25px;\n}\n.editInfoHandle:after {\n content: \' \';\n position: absolute;\n width: 0px;\n height: 0px;\n border-style: solid;\n border-width: 8.7px 5px 0 5px;\n border-color: black transparent transparent transparent;\n\n top: 100%;\n left: 5px;\n}\n.editInfo {\n font-family: sans-serif;\n font-weight: normal;\n font-style: normal;\n text-decoration: none;\n color: white;\n width: 100%;\n height: 12pt;\n}\n.editInfoColor {\n float: left;\n width: 10pt;\n height: 10pt;\n border: 1px solid white;\n}\n.editInfoAuthor {\n float: left;\n margin-left: 5pt;\n font-size: 10pt;\n text-align: left;\n height: 12pt;\n line-height: 12pt;\n}\n.editInfoTime {\n float: right;\n margin-left: 30pt;\n font-size: 8pt;\n font-style: italic;\n color: yellow;\n height: 12pt;\n line-height: 12pt;\n}\n\n.annotationWrapper {\n display: inline;\n position: relative;\n}\n\n.annotationRemoveButton:before {\n content: \'\u00d7\';\n color: white;\n padding: 5px;\n line-height: 1em;\n}\n\n.annotationRemoveButton {\n width: 20px;\n height: 20px;\n border-radius: 10px;\n background-color: black;\n box-shadow: 0px 0px 5px rgba(50, 50, 50, 0.75);\n position: absolute;\n top: -10px;\n left: -10px;\n z-index: 3;\n text-align: center;\n font-family: sans-serif;\n font-style: normal;\n font-weight: normal;\n text-decoration: none;\n font-size: 15px;\n}\n.annotationRemoveButton:hover {\n cursor: pointer;\n box-shadow: 0px 0px 5px rgba(0, 0, 0, 1);\n}\n\n.annotationNote {\n width: 4cm;\n position: absolute;\n display: inline;\n z-index: 10;\n}\n.annotationNote > office|annotation {\n display: block;\n text-align: left;\n}\n\n.annotationConnector {\n position: absolute;\n display: inline;\n z-index: 2;\n border-top: 1px dashed brown;\n}\n.annotationConnector.angular {\n -moz-transform-origin: left top;\n -webkit-transform-origin: left top;\n -ms-transform-origin: left top;\n transform-origin: left top;\n}\n.annotationConnector.horizontal {\n left: 0;\n}\n.annotationConnector.horizontal:before {\n content: \'\';\n display: inline;\n position: absolute;\n width: 0px;\n height: 0px;\n border-style: solid;\n border-width: 8.7px 5px 0 5px;\n border-color: brown transparent transparent transparent;\n top: -1px;\n left: -5px;\n}\n\noffice|annotation {\n width: 100%;\n height: 100%;\n display: none;\n background: rgb(198, 238, 184);\n background: -moz-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);\n background: -webkit-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);\n background: -o-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);\n background: -ms-linear-gradient(90deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);\n background: linear-gradient(180deg, rgb(198, 238, 184) 30%, rgb(180, 196, 159) 100%);\n box-shadow: 0 3px 4px -3px #ccc;\n}\n\noffice|annotation > dc|creator {\n display: block;\n font-size: 10pt;\n font-weight: normal;\n font-style: normal;\n font-family: sans-serif;\n color: white;\n background-color: brown;\n padding: 4px;\n}\noffice|annotation > dc|date {\n display: block;\n font-size: 10pt;\n font-weight: normal;\n font-style: normal;\n font-family: sans-serif;\n border: 4px solid transparent;\n color: black;\n}\noffice|annotation > text|list {\n display: block;\n padding: 5px;\n}\n\n/* This is very temporary CSS. This must go once\n * we start bundling webodf-default ODF styles for annotations.\n */\noffice|annotation text|p {\n font-size: 10pt;\n color: black;\n font-weight: normal;\n font-style: normal;\n text-decoration: none;\n font-family: sans-serif;\n}\n\ndc|*::selection {\n background: transparent;\n}\ndc|*::-moz-selection {\n background: transparent;\n}\n\n#annotationsPane {\n background-color: #EAEAEA;\n width: 4cm;\n height: 100%;\n display: none;\n position: absolute;\n outline: 1px solid #ccc;\n}\n\n.annotationHighlight {\n background-color: yellow;\n position: relative;\n}\n\n.selectionOverlay {\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 15;\n}\n.selectionOverlay > polygon {\n fill-opacity: 0.3;\n stroke-opacity: 0.8;\n stroke-width: 1;\n fill-rule: evenodd;\n}\n\n#imageSelector {\n display: none;\n position: absolute;\n border-style: solid;\n border-color: black;\n}\n\n#imageSelector > div {\n width: 5px;\n height: 5px;\n display: block;\n position: absolute;\n border: 1px solid black;\n background-color: #ffffff;\n}\n\n#imageSelector > .topLeft {\n top: -4px;\n left: -4px;\n}\n\n#imageSelector > .topRight {\n top: -4px;\n right: -4px;\n}\n\n#imageSelector > .bottomRight {\n right: -4px;\n bottom: -4px;\n}\n\n#imageSelector > .bottomLeft {\n bottom: -4px;\n left: -4px;\n}\n\n#imageSelector > .topMiddle {\n top: -4px;\n left: 50%;\n margin-left: -2.5px; /* half of the width defined in #imageSelector > div */\n}\n\n#imageSelector > .rightMiddle {\n top: 50%;\n right: -4px;\n margin-top: -2.5px; /* half of the height defined in #imageSelector > div */\n}\n\n#imageSelector > .bottomMiddle {\n bottom: -4px;\n left: 50%;\n margin-left: -2.5px; /* half of the width defined in #imageSelector > div */\n}\n\n#imageSelector > .leftMiddle {\n top: 50%;\n left: -4px;\n margin-top: -2.5px; /* half of the height defined in #imageSelector > div */\n}\n\ndiv.customScrollbars::-webkit-scrollbar\n{\n width: 8px;\n height: 8px;\n background-color: transparent;\n}\n\ndiv.customScrollbars::-webkit-scrollbar-track\n{\n background-color: transparent;\n}\n\ndiv.customScrollbars::-webkit-scrollbar-thumb\n{\n background-color: #444;\n border-radius: 4px;\n}\n';
|
|
|