Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
8386 lines
No EOL
323 KiB
JavaScript
8386 lines
No EOL
323 KiB
JavaScript
var requirejs, require, define;
|
||
(function (global, undef) {
|
||
var topReq, dataMain, src, subPath, bootstrapConfig = requirejs || require, hasOwn = Object.prototype.hasOwnProperty, contexts = {}, queue = [], currDirRegExp = /^\.\//, urlRegExp = /^\/|\:|\?|\.js$/, commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/;
|
||
if (typeof requirejs === 'function') {
|
||
return;
|
||
}
|
||
function hasProp(obj, prop) {
|
||
return hasOwn.call(obj, prop);
|
||
}
|
||
function getOwn(obj, prop) {
|
||
return obj && hasProp(obj, prop) && obj[prop];
|
||
}
|
||
function eachProp(obj, func) {
|
||
var prop;
|
||
for (prop in obj) {
|
||
if (hasProp(obj, prop)) {
|
||
if (func(obj[prop], prop)) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function mixin(target, source, force, deepStringMixin) {
|
||
if (source) {
|
||
eachProp(source, function (value, prop) {
|
||
if (force || !hasProp(target, prop)) {
|
||
if (deepStringMixin && typeof value === 'object' && value && !Array.isArray(value) && typeof value !== 'function' && !(value instanceof RegExp)) {
|
||
if (!target[prop]) {
|
||
target[prop] = {};
|
||
}
|
||
mixin(target[prop], value, force, deepStringMixin);
|
||
} else {
|
||
target[prop] = value;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
return target;
|
||
}
|
||
function getGlobal(value) {
|
||
if (!value) {
|
||
return value;
|
||
}
|
||
var g = global;
|
||
value.split('.').forEach(function (part) {
|
||
g = g[part];
|
||
});
|
||
return g;
|
||
}
|
||
function newContext(contextName) {
|
||
var req, main, makeMap, callDep, handlers, checkingLater, load, context, defined = {}, waiting = {}, config = {
|
||
waitSeconds: 7,
|
||
baseUrl: './',
|
||
paths: {},
|
||
bundles: {},
|
||
pkgs: {},
|
||
shim: {},
|
||
config: {}
|
||
}, mapCache = {}, requireDeferreds = [], deferreds = {}, calledDefine = {}, calledPlugin = {}, loadCount = 0, startTime = new Date().getTime(), errCount = 0, trackedErrors = {}, urlFetched = {}, bundlesMap = {};
|
||
var nextMicroTaskPass;
|
||
(function () {
|
||
'use strict';
|
||
var waitingResolving, waiting = [];
|
||
function callWaiting() {
|
||
waitingResolving = null;
|
||
var w = waiting;
|
||
waiting = [];
|
||
while (w.length) {
|
||
w.shift()();
|
||
}
|
||
}
|
||
nextMicroTaskPass = function (fn) {
|
||
waiting.push(fn);
|
||
if (!waitingResolving) {
|
||
waitingResolving = new Promise(function (resolve, reject) {
|
||
resolve();
|
||
}).then(callWaiting).catch(delayedError);
|
||
}
|
||
};
|
||
}());
|
||
function trimDots(ary) {
|
||
var i, part, length = ary.length;
|
||
for (i = 0; i < length; i++) {
|
||
part = ary[i];
|
||
if (part === '.') {
|
||
ary.splice(i, 1);
|
||
i -= 1;
|
||
} else if (part === '..') {
|
||
if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
|
||
break;
|
||
} else if (i > 0) {
|
||
ary.splice(i - 1, 2);
|
||
i -= 2;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function normalize(name, baseName, applyMap) {
|
||
var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, foundMap, foundI, foundStarMap, starI, baseParts = baseName && baseName.split('/'), normalizedBaseParts = baseParts, map = config.map, starMap = map && map['*'];
|
||
if (name && name.charAt(0) === '.') {
|
||
if (baseName) {
|
||
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
||
name = name.split('/');
|
||
lastIndex = name.length - 1;
|
||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||
}
|
||
name = normalizedBaseParts.concat(name);
|
||
trimDots(name);
|
||
name = name.join('/');
|
||
} else if (name.indexOf('./') === 0) {
|
||
name = name.substring(2);
|
||
}
|
||
}
|
||
if (applyMap && map && (baseParts || starMap)) {
|
||
nameParts = name.split('/');
|
||
outerLoop:
|
||
for (i = nameParts.length; i > 0; i -= 1) {
|
||
nameSegment = nameParts.slice(0, i).join('/');
|
||
if (baseParts) {
|
||
for (j = baseParts.length; j > 0; j -= 1) {
|
||
mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
|
||
if (mapValue) {
|
||
mapValue = getOwn(mapValue, nameSegment);
|
||
if (mapValue) {
|
||
foundMap = mapValue;
|
||
foundI = i;
|
||
break outerLoop;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
|
||
foundStarMap = getOwn(starMap, nameSegment);
|
||
starI = i;
|
||
}
|
||
}
|
||
if (!foundMap && foundStarMap) {
|
||
foundMap = foundStarMap;
|
||
foundI = starI;
|
||
}
|
||
if (foundMap) {
|
||
nameParts.splice(0, foundI, foundMap);
|
||
name = nameParts.join('/');
|
||
}
|
||
}
|
||
pkgMain = getOwn(config.pkgs, name);
|
||
return pkgMain ? pkgMain : name;
|
||
}
|
||
function makeShimExports(value) {
|
||
function fn() {
|
||
var ret;
|
||
if (value.init) {
|
||
ret = value.init.apply(global, arguments);
|
||
}
|
||
return ret || value.exports && getGlobal(value.exports);
|
||
}
|
||
return fn;
|
||
}
|
||
function takeQueue(anonId) {
|
||
var i, id, args, shim;
|
||
for (i = 0; i < queue.length; i += 1) {
|
||
if (typeof queue[i][0] !== 'string') {
|
||
if (anonId) {
|
||
queue[i].unshift(anonId);
|
||
anonId = undef;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
args = queue.shift();
|
||
id = args[0];
|
||
i -= 1;
|
||
if (!hasProp(defined, id) && !hasProp(waiting, id)) {
|
||
if (hasProp(deferreds, id)) {
|
||
main.apply(undef, args);
|
||
} else {
|
||
waiting[id] = args;
|
||
}
|
||
}
|
||
}
|
||
if (anonId) {
|
||
shim = getOwn(config.shim, anonId) || {};
|
||
main(anonId, shim.deps || [], shim.exportsFn);
|
||
}
|
||
}
|
||
function makeRequire(relName, topLevel) {
|
||
var req = function (deps, callback, errback, alt) {
|
||
var name, cfg;
|
||
if (topLevel) {
|
||
takeQueue();
|
||
}
|
||
if (typeof deps === 'string') {
|
||
if (handlers[deps]) {
|
||
return handlers[deps](relName);
|
||
}
|
||
name = makeMap(deps, relName, true).id;
|
||
if (!hasProp(defined, name)) {
|
||
throw new Error('Not loaded: ' + name);
|
||
}
|
||
return defined[name];
|
||
} else if (deps && !Array.isArray(deps)) {
|
||
cfg = deps;
|
||
deps = undef;
|
||
if (Array.isArray(callback)) {
|
||
deps = callback;
|
||
callback = errback;
|
||
errback = alt;
|
||
}
|
||
if (topLevel) {
|
||
return req.config(cfg)(deps, callback, errback);
|
||
}
|
||
}
|
||
callback = callback || function () {
|
||
};
|
||
nextMicroTaskPass(function () {
|
||
takeQueue();
|
||
main(undef, deps || [], callback, errback, relName);
|
||
});
|
||
return req;
|
||
};
|
||
req.isBrowser = typeof document !== 'undefined' && typeof navigator !== 'undefined';
|
||
req.nameToUrl = function (moduleName, ext, skipExt) {
|
||
var paths, syms, i, parentModule, url, parentPath, bundleId, pkgMain = getOwn(config.pkgs, moduleName);
|
||
if (pkgMain) {
|
||
moduleName = pkgMain;
|
||
}
|
||
bundleId = getOwn(bundlesMap, moduleName);
|
||
if (bundleId) {
|
||
return req.nameToUrl(bundleId, ext, skipExt);
|
||
}
|
||
if (urlRegExp.test(moduleName)) {
|
||
url = moduleName + (ext || '');
|
||
} else {
|
||
paths = config.paths;
|
||
syms = moduleName.split('/');
|
||
for (i = syms.length; i > 0; i -= 1) {
|
||
parentModule = syms.slice(0, i).join('/');
|
||
parentPath = getOwn(paths, parentModule);
|
||
if (parentPath) {
|
||
if (Array.isArray(parentPath)) {
|
||
parentPath = parentPath[0];
|
||
}
|
||
syms.splice(0, i, parentPath);
|
||
break;
|
||
}
|
||
}
|
||
url = syms.join('/');
|
||
url += ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js');
|
||
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
|
||
}
|
||
return config.urlArgs ? url + ((url.indexOf('?') === -1 ? '?' : '&') + config.urlArgs) : url;
|
||
};
|
||
req.toUrl = function (moduleNamePlusExt) {
|
||
var ext, index = moduleNamePlusExt.lastIndexOf('.'), segment = moduleNamePlusExt.split('/')[0], isRelative = segment === '.' || segment === '..';
|
||
if (index !== -1 && (!isRelative || index > 1)) {
|
||
ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
|
||
moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
|
||
}
|
||
return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true);
|
||
};
|
||
req.defined = function (id) {
|
||
return hasProp(defined, makeMap(id, relName, true).id);
|
||
};
|
||
req.specified = function (id) {
|
||
id = makeMap(id, relName, true).id;
|
||
return hasProp(defined, id) || hasProp(deferreds, id);
|
||
};
|
||
return req;
|
||
}
|
||
function resolve(name, d, value) {
|
||
if (name) {
|
||
defined[name] = value;
|
||
if (requirejs.onResourceLoad) {
|
||
requirejs.onResourceLoad(context, d.map, d.deps);
|
||
}
|
||
}
|
||
d.finished = true;
|
||
d.resolve(value);
|
||
}
|
||
function reject(d, err) {
|
||
d.finished = true;
|
||
d.rejected = true;
|
||
d.reject(err);
|
||
}
|
||
function makeNormalize(relName) {
|
||
return function (name) {
|
||
return normalize(name, relName, true);
|
||
};
|
||
}
|
||
function defineModule(d) {
|
||
var name = d.map.id, ret = d.factory.apply(defined[name], d.values);
|
||
if (name) {
|
||
if (ret === undef) {
|
||
if (d.cjsModule) {
|
||
ret = d.cjsModule.exports;
|
||
} else if (d.usingExports) {
|
||
ret = defined[name];
|
||
}
|
||
}
|
||
} else {
|
||
requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
|
||
}
|
||
resolve(name, d, ret);
|
||
}
|
||
function depFinished(val, i) {
|
||
if (!this.rejected && !this.depDefined[i]) {
|
||
this.depDefined[i] = true;
|
||
this.depCount += 1;
|
||
this.values[i] = val;
|
||
if (!this.depending && this.depCount === this.depMax) {
|
||
defineModule(this);
|
||
}
|
||
}
|
||
}
|
||
function makeDefer(name) {
|
||
var d = {};
|
||
d.promise = new Promise(function (resolve, reject) {
|
||
d.resolve = resolve;
|
||
d.reject = reject;
|
||
});
|
||
d.map = name ? makeMap(name, null, true) : {};
|
||
d.depCount = 0;
|
||
d.depMax = 0;
|
||
d.values = [];
|
||
d.depDefined = [];
|
||
d.depFinished = depFinished;
|
||
if (d.map.pr) {
|
||
d.deps = [makeMap(d.map.pr)];
|
||
}
|
||
return d;
|
||
}
|
||
function getDefer(name) {
|
||
var d;
|
||
if (name) {
|
||
d = hasProp(deferreds, name) && deferreds[name];
|
||
if (!d) {
|
||
d = deferreds[name] = makeDefer(name);
|
||
}
|
||
} else {
|
||
d = makeDefer();
|
||
requireDeferreds.push(d);
|
||
}
|
||
return d;
|
||
}
|
||
function makeErrback(d, name) {
|
||
return function (err) {
|
||
if (!d.rejected) {
|
||
if (!err.dynaId) {
|
||
err.dynaId = 'id' + (errCount += 1);
|
||
err.requireModules = [name];
|
||
}
|
||
reject(d, err);
|
||
}
|
||
};
|
||
}
|
||
function waitForDep(depMap, relName, d, i) {
|
||
d.depMax += 1;
|
||
callDep(depMap, relName).then(function (val) {
|
||
d.depFinished(val, i);
|
||
}, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id));
|
||
}
|
||
function makeLoad(id) {
|
||
var fromTextCalled;
|
||
function load(value) {
|
||
if (!fromTextCalled) {
|
||
resolve(id, getDefer(id), value);
|
||
}
|
||
}
|
||
load.error = function (err) {
|
||
getDefer(id).reject(err);
|
||
};
|
||
load.fromText = function (text, textAlt) {
|
||
var d = getDefer(id), map = makeMap(makeMap(id).n), plainId = map.id;
|
||
fromTextCalled = true;
|
||
d.factory = function (p, val) {
|
||
return val;
|
||
};
|
||
if (textAlt) {
|
||
text = textAlt;
|
||
}
|
||
if (hasProp(config.config, id)) {
|
||
config.config[plainId] = config.config[id];
|
||
}
|
||
try {
|
||
req.exec(text);
|
||
} catch (e) {
|
||
reject(d, new Error('fromText eval for ' + plainId + ' failed: ' + e));
|
||
}
|
||
takeQueue(plainId);
|
||
d.deps = [map];
|
||
waitForDep(map, null, d, d.deps.length);
|
||
};
|
||
return load;
|
||
}
|
||
load = typeof importScripts === 'function' ? function (map) {
|
||
var url = map.url;
|
||
if (urlFetched[url]) {
|
||
return;
|
||
}
|
||
urlFetched[url] = true;
|
||
getDefer(map.id);
|
||
importScripts(url);
|
||
takeQueue(map.id);
|
||
} : function (map) {
|
||
var script, id = map.id, url = map.url;
|
||
if (urlFetched[url]) {
|
||
return;
|
||
}
|
||
urlFetched[url] = true;
|
||
script = document.createElement('script');
|
||
script.setAttribute('data-requiremodule', id);
|
||
script.type = config.scriptType || 'text/javascript';
|
||
script.charset = 'utf-8';
|
||
script.async = true;
|
||
loadCount += 1;
|
||
script.addEventListener('load', function () {
|
||
loadCount -= 1;
|
||
takeQueue(id);
|
||
}, false);
|
||
script.addEventListener('error', function () {
|
||
loadCount -= 1;
|
||
var err, pathConfig = getOwn(config.paths, id), d = getOwn(deferreds, id);
|
||
if (pathConfig && Array.isArray(pathConfig) && pathConfig.length > 1) {
|
||
script.parentNode.removeChild(script);
|
||
pathConfig.shift();
|
||
d.map = makeMap(id);
|
||
load(d.map);
|
||
} else {
|
||
err = new Error('Load failed: ' + id + ': ' + script.src);
|
||
err.requireModules = [id];
|
||
getDefer(id).reject(err);
|
||
}
|
||
}, false);
|
||
script.src = url;
|
||
document.head.appendChild(script);
|
||
};
|
||
function callPlugin(plugin, map, relName) {
|
||
plugin.load(map.n, makeRequire(relName), makeLoad(map.id), {});
|
||
}
|
||
callDep = function (map, relName) {
|
||
var args, bundleId, name = map.id, shim = config.shim[name];
|
||
if (hasProp(waiting, name)) {
|
||
args = waiting[name];
|
||
delete waiting[name];
|
||
main.apply(undef, args);
|
||
} else if (!hasProp(deferreds, name)) {
|
||
if (map.pr) {
|
||
if (bundleId = getOwn(bundlesMap, name)) {
|
||
map.url = req.nameToUrl(bundleId);
|
||
load(map);
|
||
} else {
|
||
return callDep(makeMap(map.pr)).then(function (plugin) {
|
||
var newMap = makeMap(name, relName, true), newId = newMap.id, shim = getOwn(config.shim, newId);
|
||
if (!hasProp(calledPlugin, newId)) {
|
||
calledPlugin[newId] = true;
|
||
if (shim && shim.deps) {
|
||
req(shim.deps, function () {
|
||
callPlugin(plugin, newMap, relName);
|
||
});
|
||
} else {
|
||
callPlugin(plugin, newMap, relName);
|
||
}
|
||
}
|
||
return getDefer(newId).promise;
|
||
});
|
||
}
|
||
} else if (shim && shim.deps) {
|
||
req(shim.deps, function () {
|
||
load(map);
|
||
});
|
||
} else {
|
||
load(map);
|
||
}
|
||
}
|
||
return getDefer(name).promise;
|
||
};
|
||
function splitPrefix(name) {
|
||
var prefix, index = name ? name.indexOf('!') : -1;
|
||
if (index > -1) {
|
||
prefix = name.substring(0, index);
|
||
name = name.substring(index + 1, name.length);
|
||
}
|
||
return [
|
||
prefix,
|
||
name
|
||
];
|
||
}
|
||
makeMap = function (name, relName, applyMap) {
|
||
if (typeof name !== 'string') {
|
||
return name;
|
||
}
|
||
var plugin, url, parts, prefix, result, cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap;
|
||
parts = splitPrefix(name);
|
||
prefix = parts[0];
|
||
name = parts[1];
|
||
if (!prefix && hasProp(mapCache, cacheKey)) {
|
||
return mapCache[cacheKey];
|
||
}
|
||
if (prefix) {
|
||
prefix = normalize(prefix, relName, applyMap);
|
||
plugin = hasProp(defined, prefix) && defined[prefix];
|
||
}
|
||
if (prefix) {
|
||
if (plugin && plugin.normalize) {
|
||
name = plugin.normalize(name, makeNormalize(relName));
|
||
} else {
|
||
name = normalize(name, relName, applyMap);
|
||
}
|
||
} else {
|
||
name = normalize(name, relName, applyMap);
|
||
parts = splitPrefix(name);
|
||
prefix = parts[0];
|
||
name = parts[1];
|
||
url = req.nameToUrl(name);
|
||
}
|
||
result = {
|
||
id: prefix ? prefix + '!' + name : name,
|
||
n: name,
|
||
pr: prefix,
|
||
url: url
|
||
};
|
||
if (!prefix) {
|
||
mapCache[cacheKey] = result;
|
||
}
|
||
return result;
|
||
};
|
||
handlers = {
|
||
require: function (name) {
|
||
return makeRequire(name);
|
||
},
|
||
exports: function (name) {
|
||
var e = defined[name];
|
||
if (typeof e !== 'undefined') {
|
||
return e;
|
||
} else {
|
||
return defined[name] = {};
|
||
}
|
||
},
|
||
module: function (name) {
|
||
return {
|
||
id: name,
|
||
uri: '',
|
||
exports: handlers.exports(name),
|
||
config: function () {
|
||
return getOwn(config.config, name) || {};
|
||
}
|
||
};
|
||
}
|
||
};
|
||
function breakCycle(d, traced, processed) {
|
||
var id = d.map.id;
|
||
traced[id] = true;
|
||
if (!d.finished && d.deps) {
|
||
d.deps.forEach(function (depMap) {
|
||
var depId = depMap.id, dep = !hasProp(handlers, depId) && getDefer(depId);
|
||
if (dep && !dep.finished && !processed[depId]) {
|
||
if (hasProp(traced, depId)) {
|
||
d.deps.forEach(function (depMap, i) {
|
||
if (depMap.id === depId) {
|
||
d.depFinished(defined[depId], i);
|
||
}
|
||
});
|
||
} else {
|
||
breakCycle(dep, traced, processed);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
processed[id] = true;
|
||
}
|
||
function check(d) {
|
||
var err, notFinished = [], waitInterval = config.waitSeconds * 1000, expired = waitInterval && startTime + waitInterval < new Date().getTime();
|
||
if (loadCount === 0) {
|
||
if (d) {
|
||
if (!d.finished) {
|
||
breakCycle(d, {}, {});
|
||
}
|
||
} else if (requireDeferreds.length) {
|
||
requireDeferreds.forEach(function (d) {
|
||
breakCycle(d, {}, {});
|
||
});
|
||
}
|
||
}
|
||
if (expired) {
|
||
eachProp(deferreds, function (d) {
|
||
if (!d.finished) {
|
||
notFinished.push(d.map.id);
|
||
}
|
||
});
|
||
err = new Error('Timeout for modules: ' + notFinished);
|
||
err.requireModules = notFinished;
|
||
req.onError(err);
|
||
} else if (loadCount || requireDeferreds.length) {
|
||
if (!checkingLater) {
|
||
checkingLater = true;
|
||
setTimeout(function () {
|
||
checkingLater = false;
|
||
check();
|
||
}, 70);
|
||
}
|
||
}
|
||
}
|
||
function delayedError(e) {
|
||
setTimeout(function () {
|
||
if (!e.dynaId || !trackedErrors[e.dynaId]) {
|
||
trackedErrors[e.dynaId] = true;
|
||
req.onError(e);
|
||
}
|
||
});
|
||
}
|
||
main = function (name, deps, factory, errback, relName) {
|
||
if (name && hasProp(calledDefine, name)) {
|
||
return;
|
||
}
|
||
calledDefine[name] = true;
|
||
var d = getDefer(name);
|
||
if (deps && !Array.isArray(deps)) {
|
||
factory = deps;
|
||
deps = [];
|
||
}
|
||
d.promise.catch(errback || delayedError);
|
||
relName = relName || name;
|
||
if (typeof factory === 'function') {
|
||
if (!deps.length && factory.length) {
|
||
factory.toString().replace(commentRegExp, '').replace(cjsRequireRegExp, function (match, dep) {
|
||
deps.push(dep);
|
||
});
|
||
deps = (factory.length === 1 ? ['require'] : [
|
||
'require',
|
||
'exports',
|
||
'module'
|
||
]).concat(deps);
|
||
}
|
||
d.factory = factory;
|
||
d.deps = deps;
|
||
d.depending = true;
|
||
deps.forEach(function (depName, i) {
|
||
var depMap;
|
||
deps[i] = depMap = makeMap(depName, relName, true);
|
||
depName = depMap.id;
|
||
if (depName === 'require') {
|
||
d.values[i] = handlers.require(name);
|
||
} else if (depName === 'exports') {
|
||
d.values[i] = handlers.exports(name);
|
||
d.usingExports = true;
|
||
} else if (depName === 'module') {
|
||
d.values[i] = d.cjsModule = handlers.module(name);
|
||
} else if (depName === undefined) {
|
||
d.values[i] = undefined;
|
||
} else {
|
||
waitForDep(depMap, relName, d, i);
|
||
}
|
||
});
|
||
d.depending = false;
|
||
if (d.depCount === d.depMax) {
|
||
defineModule(d);
|
||
}
|
||
} else if (name) {
|
||
resolve(name, d, factory);
|
||
}
|
||
startTime = new Date().getTime();
|
||
if (!name) {
|
||
check(d);
|
||
}
|
||
};
|
||
req = makeRequire(null, true);
|
||
req.config = function (cfg) {
|
||
if (cfg.context && cfg.context !== contextName) {
|
||
return newContext(cfg.context).config(cfg);
|
||
}
|
||
mapCache = {};
|
||
if (cfg.baseUrl) {
|
||
if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
|
||
cfg.baseUrl += '/';
|
||
}
|
||
}
|
||
var shim = config.shim, objs = {
|
||
paths: true,
|
||
bundles: true,
|
||
config: true,
|
||
map: true
|
||
};
|
||
eachProp(cfg, function (value, prop) {
|
||
if (objs[prop]) {
|
||
if (!config[prop]) {
|
||
config[prop] = {};
|
||
}
|
||
mixin(config[prop], value, true, true);
|
||
} else {
|
||
config[prop] = value;
|
||
}
|
||
});
|
||
if (cfg.bundles) {
|
||
eachProp(cfg.bundles, function (value, prop) {
|
||
value.forEach(function (v) {
|
||
if (v !== prop) {
|
||
bundlesMap[v] = prop;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
if (cfg.shim) {
|
||
eachProp(cfg.shim, function (value, id) {
|
||
if (Array.isArray(value)) {
|
||
value = { deps: value };
|
||
}
|
||
if ((value.exports || value.init) && !value.exportsFn) {
|
||
value.exportsFn = makeShimExports(value);
|
||
}
|
||
shim[id] = value;
|
||
});
|
||
config.shim = shim;
|
||
}
|
||
if (cfg.packages) {
|
||
cfg.packages.forEach(function (pkgObj) {
|
||
var location, name;
|
||
pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
|
||
name = pkgObj.name;
|
||
location = pkgObj.location;
|
||
if (location) {
|
||
config.paths[name] = pkgObj.location;
|
||
}
|
||
config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main').replace(currDirRegExp, '').replace(jsSuffixRegExp, '');
|
||
});
|
||
}
|
||
if (cfg.deps || cfg.callback) {
|
||
req(cfg.deps, cfg.callback);
|
||
}
|
||
return req;
|
||
};
|
||
req.onError = function (err) {
|
||
throw err;
|
||
};
|
||
context = {
|
||
id: contextName,
|
||
defined: defined,
|
||
waiting: waiting,
|
||
config: config,
|
||
deferreds: deferreds
|
||
};
|
||
contexts[contextName] = context;
|
||
return req;
|
||
}
|
||
requirejs = topReq = newContext('_');
|
||
if (typeof require !== 'function') {
|
||
require = topReq;
|
||
}
|
||
topReq.exec = function (text) {
|
||
return eval(text);
|
||
};
|
||
topReq.contexts = contexts;
|
||
define = function () {
|
||
queue.push([].slice.call(arguments, 0));
|
||
};
|
||
define.amd = { jQuery: true };
|
||
if (bootstrapConfig) {
|
||
topReq.config(bootstrapConfig);
|
||
}
|
||
if (topReq.isBrowser && !contexts._.config.skipDataMain) {
|
||
dataMain = document.querySelectorAll('script[data-main]')[0];
|
||
dataMain = dataMain && dataMain.getAttribute('data-main');
|
||
if (dataMain) {
|
||
dataMain = dataMain.replace(jsSuffixRegExp, '');
|
||
if (!bootstrapConfig || !bootstrapConfig.baseUrl) {
|
||
src = dataMain.split('/');
|
||
dataMain = src.pop();
|
||
subPath = src.length ? src.join('/') + '/' : './';
|
||
topReq.config({ baseUrl: subPath });
|
||
}
|
||
topReq([dataMain]);
|
||
}
|
||
}
|
||
}(this));
|
||
define('alameda', function () {
|
||
});
|
||
(function () {
|
||
function consoleHelper() {
|
||
var msg = arguments[0] + ':';
|
||
for (var i = 1; i < arguments.length; i++) {
|
||
msg += ' ' + arguments[i];
|
||
}
|
||
msg += '\x1B[0m\n';
|
||
dump(msg);
|
||
}
|
||
if ('mozTCPSocket' in window.navigator) {
|
||
window.console = {
|
||
log: consoleHelper.bind(null, '\x1B[32mLOG'),
|
||
error: consoleHelper.bind(null, '\x1B[31mERR'),
|
||
info: consoleHelper.bind(null, '\x1B[36mINF'),
|
||
warn: consoleHelper.bind(null, '\x1B[33mWAR')
|
||
};
|
||
}
|
||
window.onerror = function errHandler(msg, url, line) {
|
||
console.error('onerror reporting:', msg, '@', url, ':', line);
|
||
return false;
|
||
};
|
||
}());
|
||
define('console_hook', function () {
|
||
});
|
||
define('evt', [], function () {
|
||
var evt, slice = Array.prototype.slice, props = [
|
||
'_events',
|
||
'_pendingEvents',
|
||
'on',
|
||
'once',
|
||
'latest',
|
||
'latestOnce',
|
||
'removeListener',
|
||
'emitWhenListener',
|
||
'emit'
|
||
];
|
||
function Emitter() {
|
||
this._events = {};
|
||
this._pendingEvents = {};
|
||
}
|
||
Emitter.prototype = {
|
||
on: function (id, fn) {
|
||
var listeners = this._events[id], pending = this._pendingEvents[id];
|
||
if (!listeners) {
|
||
listeners = this._events[id] = [];
|
||
}
|
||
listeners.push(fn);
|
||
if (pending) {
|
||
pending.forEach(function (args) {
|
||
fn.apply(null, args);
|
||
});
|
||
delete this._pendingEvents[id];
|
||
}
|
||
return this;
|
||
},
|
||
once: function (id, fn) {
|
||
var self = this, fired = false;
|
||
function one() {
|
||
if (fired) {
|
||
return;
|
||
}
|
||
fired = true;
|
||
fn.apply(null, arguments);
|
||
setTimeout(function () {
|
||
self.removeListener(id, one);
|
||
});
|
||
}
|
||
return this.on(id, one);
|
||
},
|
||
latest: function (id, fn) {
|
||
if (this[id] && !this._pendingEvents[id]) {
|
||
fn(this[id]);
|
||
}
|
||
this.on(id, fn);
|
||
},
|
||
latestOnce: function (id, fn) {
|
||
if (this[id] && !this._pendingEvents[id]) {
|
||
fn(this[id]);
|
||
} else {
|
||
this.once(id, fn);
|
||
}
|
||
},
|
||
removeListener: function (id, fn) {
|
||
var i, listeners = this._events[id];
|
||
if (listeners) {
|
||
i = listeners.indexOf(fn);
|
||
if (i !== -1) {
|
||
listeners.splice(i, 1);
|
||
}
|
||
if (listeners.length === 0) {
|
||
delete this._events[id];
|
||
}
|
||
}
|
||
},
|
||
emitWhenListener: function (id) {
|
||
var listeners = this._events[id];
|
||
if (listeners) {
|
||
this.emit.apply(this, arguments);
|
||
} else {
|
||
if (!this._pendingEvents[id]) {
|
||
this._pendingEvents[id] = [];
|
||
}
|
||
this._pendingEvents[id].push(slice.call(arguments, 1));
|
||
}
|
||
},
|
||
emit: function (id) {
|
||
var args = slice.call(arguments, 1), listeners = this._events[id];
|
||
if (listeners) {
|
||
listeners.forEach(function (fn) {
|
||
try {
|
||
fn.apply(null, args);
|
||
} catch (e) {
|
||
setTimeout(function () {
|
||
throw e;
|
||
});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
evt = new Emitter();
|
||
evt.Emitter = Emitter;
|
||
evt.mix = function (obj) {
|
||
var e = new Emitter();
|
||
props.forEach(function (prop) {
|
||
if (obj.hasOwnProperty(prop)) {
|
||
throw new Error('Object already has a property "' + prop + '"');
|
||
}
|
||
obj[prop] = e[prop];
|
||
});
|
||
return obj;
|
||
};
|
||
return evt;
|
||
});
|
||
define('app_self', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'evt'
|
||
], function (require, exports, module) {
|
||
var evt = require('evt');
|
||
var appSelf = evt.mix({}), mozApps = navigator.mozApps, oldLatest = appSelf.latest, loaded = false;
|
||
if (!mozApps) {
|
||
appSelf.self = {};
|
||
loaded = true;
|
||
}
|
||
function loadSelf() {
|
||
mozApps.getSelf().onsuccess = function (event) {
|
||
loaded = true;
|
||
var app = event.target.result;
|
||
appSelf.self = app;
|
||
appSelf.emit('self', appSelf.self);
|
||
};
|
||
}
|
||
appSelf.latest = function (id) {
|
||
if (!loaded) {
|
||
loadSelf();
|
||
}
|
||
if (id !== 'self') {
|
||
throw new Error(module.id + ' only supports "self" property');
|
||
}
|
||
return oldLatest.apply(this, arguments);
|
||
};
|
||
return appSelf;
|
||
});
|
||
define('l10n', {
|
||
load: function (id, require, onload, config) {
|
||
if (config.isBuild) {
|
||
return onload();
|
||
}
|
||
require(['l10nbase'], function () {
|
||
navigator.mozL10n.once(function () {
|
||
var dir = navigator.mozL10n.language.direction, htmlNode = document.querySelector('html');
|
||
if (htmlNode.getAttribute('dir') !== dir) {
|
||
console.log('email l10n updating html dir to ' + dir);
|
||
htmlNode.setAttribute('dir', dir);
|
||
}
|
||
onload(navigator.mozL10n);
|
||
});
|
||
});
|
||
}
|
||
});
|
||
(function (window) {
|
||
'use strict';
|
||
window.NotificationHelper = {
|
||
getIconURI: function nh_getIconURI(app, entryPoint) {
|
||
var icons = app.manifest.icons;
|
||
if (entryPoint) {
|
||
icons = app.manifest.entry_points[entryPoint].icons;
|
||
}
|
||
if (!icons) {
|
||
return null;
|
||
}
|
||
var sizes = Object.keys(icons).map(function parse(str) {
|
||
return parseInt(str, 10);
|
||
});
|
||
sizes.sort(function (x, y) {
|
||
return y - x;
|
||
});
|
||
var HVGA = document.documentElement.clientWidth < 480;
|
||
var index = sizes[HVGA ? sizes.length - 1 : 0];
|
||
return app.installOrigin + icons[index];
|
||
},
|
||
send: function nh_send(titleL10n, options) {
|
||
return Promise.all([
|
||
titleL10n,
|
||
options.bodyL10n
|
||
].map(getL10n)).then(([title, body]) => {
|
||
if (body) {
|
||
options.body = body;
|
||
}
|
||
options.lang = document.documentElement.getAttribute('lang');
|
||
var notification = new window.Notification(title, options);
|
||
if (options.closeOnClick !== false) {
|
||
notification.addEventListener('click', function nh_click() {
|
||
notification.removeEventListener('click', nh_click);
|
||
notification.close();
|
||
});
|
||
}
|
||
return notification;
|
||
});
|
||
}
|
||
};
|
||
function getL10n(l10nAttrs) {
|
||
if (!l10nAttrs) {
|
||
return;
|
||
}
|
||
if (typeof l10nAttrs === 'string') {
|
||
return document.l10n.formatValue(l10nAttrs);
|
||
}
|
||
if (typeof l10nAttrs.raw === "string") {
|
||
return l10nAttrs.raw;
|
||
}
|
||
return document.l10n.formatValue(l10nAttrs.id, l10nAttrs.args);
|
||
}
|
||
}(this));
|
||
define('shared/js/notification_helper', function (global) {
|
||
return function () {
|
||
var ret, fn;
|
||
return ret || global.NotificationHelper;
|
||
};
|
||
}(this));
|
||
define('sync', [
|
||
'require',
|
||
'app_self',
|
||
'evt',
|
||
'l10n!',
|
||
'shared/js/notification_helper'
|
||
], function (require) {
|
||
var cronSyncStartTime, appSelf = require('app_self'), evt = require('evt'), mozL10n = require('l10n!'), notificationHelper = require('shared/js/notification_helper');
|
||
var notificationDataVersion = '1';
|
||
return function syncInit(model, api) {
|
||
var hasBeenVisible = !document.hidden, waitingOnCron = {};
|
||
if (hasBeenVisible) {
|
||
api.setInteractive();
|
||
}
|
||
document.addEventListener('visibilitychange', function onVisibilityChange() {
|
||
if (!document.hidden) {
|
||
hasBeenVisible = true;
|
||
api.setInteractive();
|
||
}
|
||
}, false);
|
||
function makeAccountKey(accountIds) {
|
||
return 'id' + accountIds.join(' ');
|
||
}
|
||
var sendNotification;
|
||
if (typeof Notification !== 'function') {
|
||
console.log('email: notifications not available');
|
||
sendNotification = function () {
|
||
};
|
||
} else {
|
||
sendNotification = function (notificationId, titleL10n, bodyL10n, iconUrl, data, behavior) {
|
||
console.log('Notification sent for ' + notificationId);
|
||
if (Notification.permission !== 'granted') {
|
||
console.log('email: notification skipped, permission: ' + Notification.permission);
|
||
return;
|
||
}
|
||
data = data || {};
|
||
var notificationOptions = {
|
||
bodyL10n: bodyL10n,
|
||
icon: iconUrl,
|
||
tag: notificationId,
|
||
data: data,
|
||
mozbehavior: { noscreen: true },
|
||
closeOnClick: false
|
||
};
|
||
if (behavior) {
|
||
Object.keys(behavior).forEach(function (key) {
|
||
notificationOptions.mozbehavior[key] = behavior[key];
|
||
});
|
||
}
|
||
notificationHelper.send(titleL10n, notificationOptions).then(function (notification) {
|
||
notification.onclick = function () {
|
||
evt.emit('notification', {
|
||
clicked: true,
|
||
imageURL: iconUrl,
|
||
tag: notificationId,
|
||
data: data
|
||
});
|
||
};
|
||
});
|
||
};
|
||
}
|
||
api.oncronsyncstart = function (accountIds) {
|
||
console.log('email oncronsyncstart: ' + accountIds);
|
||
cronSyncStartTime = Date.now();
|
||
var accountKey = makeAccountKey(accountIds);
|
||
waitingOnCron[accountKey] = true;
|
||
};
|
||
function fetchNotificationsData(ntype) {
|
||
if (typeof Notification !== 'function' || !Notification.get) {
|
||
return Promise.resolve({});
|
||
}
|
||
return Notification.get().then(function (notifications) {
|
||
var result = {};
|
||
notifications.forEach(function (notification) {
|
||
var data = notification.data;
|
||
if (!data.v || data.v !== notificationDataVersion) {
|
||
notification.close();
|
||
} else if (data.ntype === ntype) {
|
||
data.notification = notification;
|
||
result[data.accountId] = data;
|
||
}
|
||
});
|
||
return result;
|
||
}, function (err) {
|
||
console.error('email notification.get call failed: ' + err);
|
||
return {};
|
||
});
|
||
}
|
||
function getSyncEnv(fn) {
|
||
appSelf.latest('self', function (app) {
|
||
model.latestOnce('account', function (currentAccount) {
|
||
fetchNotificationsData('sync').then(function (existingNotificationsData) {
|
||
mozL10n.formatValue('senders-separation-sign').then(function (separator) {
|
||
var localized = { separator };
|
||
mozL10n.formatValue('notification-no-subject').then(function (noSubject) {
|
||
localized.noSubject = noSubject;
|
||
fn(app, currentAccount, existingNotificationsData, localized);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
}
|
||
function topUniqueFromNames(latestInfos, oldFromNames) {
|
||
var names = [], maxCount = 3;
|
||
latestInfos.sort(function (a, b) {
|
||
return b.date - a.date;
|
||
});
|
||
latestInfos.some(function (info) {
|
||
if (names.length > maxCount) {
|
||
return true;
|
||
}
|
||
if (names.indexOf(info.from) === -1) {
|
||
names.push(info.from);
|
||
}
|
||
});
|
||
oldFromNames.some(function (name) {
|
||
if (names.length > maxCount) {
|
||
return true;
|
||
}
|
||
if (names.indexOf(name) === -1) {
|
||
names.push(name);
|
||
}
|
||
});
|
||
return names;
|
||
}
|
||
api.oncronsyncstop = function (accountsResults) {
|
||
console.log('email oncronsyncstop: ' + accountsResults.accountIds);
|
||
function finishSync() {
|
||
evt.emit('cronSyncStop', accountsResults.accountIds);
|
||
var accountKey = makeAccountKey(accountsResults.accountIds);
|
||
waitingOnCron[accountKey] = false;
|
||
var stillWaiting = Object.keys(waitingOnCron).some(function (key) {
|
||
return !!waitingOnCron[key];
|
||
});
|
||
if (!hasBeenVisible && !stillWaiting) {
|
||
console.log('sync completed in ' + (Date.now() - cronSyncStartTime) / 1000 + ' seconds, closing mail app');
|
||
window.close();
|
||
}
|
||
}
|
||
if (!accountsResults.updates) {
|
||
finishSync();
|
||
return;
|
||
}
|
||
getSyncEnv(function (app, currentAccount, existingNotificationsData, localized) {
|
||
var iconUrl = notificationHelper.getIconURI(app);
|
||
accountsResults.updates.forEach(function (result) {
|
||
if (currentAccount.id === result.id && !document.hidden) {
|
||
model.notifyInboxMessages(result);
|
||
return;
|
||
}
|
||
if (!model.getAccount(result.id).notifyOnNew || typeof Notification !== 'function') {
|
||
return;
|
||
}
|
||
var dataObject, subjectL10n, bodyL10n, behavior, count = result.count, oldFromNames = [];
|
||
var existingData = existingNotificationsData[result.id];
|
||
if (existingData) {
|
||
if (existingData.count) {
|
||
count += parseInt(existingData.count, 10);
|
||
}
|
||
if (existingData.fromNames) {
|
||
oldFromNames = existingData.fromNames;
|
||
}
|
||
}
|
||
if (count > 1) {
|
||
var newFromNames = topUniqueFromNames(result.latestMessageInfos, oldFromNames);
|
||
dataObject = {
|
||
v: notificationDataVersion,
|
||
ntype: 'sync',
|
||
type: 'message_list',
|
||
accountId: result.id,
|
||
count: count,
|
||
fromNames: newFromNames
|
||
};
|
||
if (existingData && existingData.count) {
|
||
behavior = {
|
||
soundFile: 'does-not-exist-to-simulate-silent',
|
||
vibrationPattern: [1]
|
||
};
|
||
}
|
||
if (model.getAccountCount() === 1) {
|
||
subjectL10n = {
|
||
id: 'new-emails-notify-one-account',
|
||
args: { n: count }
|
||
};
|
||
} else {
|
||
subjectL10n = {
|
||
id: 'new-emails-notify-multiple-accounts',
|
||
args: {
|
||
n: count,
|
||
accountName: result.address
|
||
}
|
||
};
|
||
}
|
||
bodyL10n = { raw: newFromNames.join(localized.separator) };
|
||
} else {
|
||
var info = result.latestMessageInfos[0];
|
||
dataObject = {
|
||
v: notificationDataVersion,
|
||
ntype: 'sync',
|
||
type: 'message_reader',
|
||
accountId: info.accountId,
|
||
messageSuid: info.messageSuid,
|
||
count: 1,
|
||
fromNames: [info.from]
|
||
};
|
||
var rawSubject = info.subject || localized.noSubject;
|
||
if (model.getAccountCount() === 1) {
|
||
subjectL10n = { raw: rawSubject };
|
||
bodyL10n = { raw: info.from };
|
||
} else {
|
||
subjectL10n = {
|
||
id: 'new-emails-notify-multiple-accounts',
|
||
args: {
|
||
n: count,
|
||
accountName: result.address
|
||
}
|
||
};
|
||
bodyL10n = {
|
||
id: 'new-emails-notify-multiple-accounts-body',
|
||
args: {
|
||
from: info.from,
|
||
subject: rawSubject
|
||
}
|
||
};
|
||
}
|
||
}
|
||
sendNotification(result.id, subjectL10n, bodyL10n, iconUrl, dataObject, behavior);
|
||
});
|
||
finishSync();
|
||
});
|
||
};
|
||
var BACKGROUND_SEND_NOTIFICATION_ID = 'backgroundSendFailed';
|
||
var sentAudio = null;
|
||
api.onbackgroundsendstatus = function (data) {
|
||
console.log('outbox: Message', data.suid, 'status =', JSON.stringify({
|
||
state: data.state,
|
||
err: data.err,
|
||
sendFailures: data.sendFailures,
|
||
emitNotifications: data.emitNotifications
|
||
}));
|
||
var descId;
|
||
switch (data.state) {
|
||
case 'pending':
|
||
descId = 'background-send-pending';
|
||
break;
|
||
case 'sending':
|
||
descId = 'background-send-sending';
|
||
break;
|
||
case 'success':
|
||
descId = 'background-send-success';
|
||
break;
|
||
case 'error':
|
||
if (data.badAddresses && data.badAddresses.length || data.err === 'bad-recipient') {
|
||
descId = 'background-send-error-recipients';
|
||
} else {
|
||
descId = 'background-send-error';
|
||
}
|
||
break;
|
||
case 'syncDone':
|
||
break;
|
||
default:
|
||
console.error('No state description for background send state "' + data.state + '"');
|
||
return;
|
||
}
|
||
if (data.state === 'success') {
|
||
model.latestOnce('acctsSlice', function () {
|
||
var account = model.getAccount(data.accountId);
|
||
if (!account) {
|
||
console.error('Invalid account ID', data.accountId, 'for a background send notification.');
|
||
return;
|
||
}
|
||
if (account.playSoundOnSend) {
|
||
if (!sentAudio) {
|
||
sentAudio = new Audio('/sounds/firefox_sent.opus');
|
||
sentAudio.mozAudioChannelType = 'notification';
|
||
}
|
||
sentAudio.play();
|
||
}
|
||
}.bind(this));
|
||
}
|
||
if (!document.hidden) {
|
||
mozL10n.formatValue(descId).then(function (localizedDescription) {
|
||
data.localizedDescription = localizedDescription;
|
||
model.notifyBackgroundSendStatus(data);
|
||
});
|
||
} else if (data.state === 'error' && data.emitNotifications) {
|
||
appSelf.latest('self', function (app) {
|
||
var iconUrl = notificationHelper.getIconURI(app);
|
||
var dataObject = {
|
||
v: notificationDataVersion,
|
||
ntype: 'outbox',
|
||
type: 'message_reader',
|
||
folderType: 'outbox',
|
||
accountId: data.accountId,
|
||
messageSuid: data.suid
|
||
};
|
||
sendNotification(BACKGROUND_SEND_NOTIFICATION_ID, 'background-send-error-title', descId, iconUrl, dataObject);
|
||
});
|
||
}
|
||
};
|
||
evt.on('inboxShown', function (accountId) {
|
||
fetchNotificationsData('sync').then(function (notificationsData) {
|
||
if (notificationsData.hasOwnProperty(accountId)) {
|
||
notificationsData[accountId].notification.close();
|
||
}
|
||
});
|
||
});
|
||
};
|
||
});
|
||
define('model_init', [
|
||
'require',
|
||
'sync',
|
||
'evt',
|
||
'l10n!'
|
||
], function (require) {
|
||
return function modelInit(model, api) {
|
||
require('sync')(model, api);
|
||
var evt = require('evt'), mozL10n = require('l10n!');
|
||
api.onbadlogin = function (account, problem, whichSide) {
|
||
evt.emitWhenListener('apiBadLogin', account, problem, whichSide);
|
||
};
|
||
api.useLocalizedStrings({
|
||
wrote: mozL10n.get('reply-quoting-wrote'),
|
||
originalMessage: mozL10n.get('forward-original-message'),
|
||
forwardHeaderLabels: {
|
||
subject: mozL10n.get('forward-header-subject'),
|
||
date: mozL10n.get('forward-header-date'),
|
||
from: mozL10n.get('forward-header-from'),
|
||
replyTo: mozL10n.get('forward-header-reply-to'),
|
||
to: mozL10n.get('forward-header-to'),
|
||
cc: mozL10n.get('forward-header-cc')
|
||
},
|
||
folderNames: {
|
||
inbox: mozL10n.get('folder-inbox'),
|
||
outbox: mozL10n.get('folder-outbox'),
|
||
sent: mozL10n.get('folder-sent'),
|
||
drafts: mozL10n.get('folder-drafts'),
|
||
trash: mozL10n.get('folder-trash'),
|
||
queue: mozL10n.get('folder-queue'),
|
||
junk: mozL10n.get('folder-junk'),
|
||
archives: mozL10n.get('folder-archives'),
|
||
localdrafts: mozL10n.get('folder-localdrafts')
|
||
}
|
||
});
|
||
};
|
||
});
|
||
define('model_create', [
|
||
'require',
|
||
'evt',
|
||
'model_init'
|
||
], function (require) {
|
||
var evt = require('evt'), modelInit = require('model_init');
|
||
function dieOnFatalError(msg) {
|
||
console.error('FATAL:', msg);
|
||
throw new Error(msg);
|
||
}
|
||
function saveHasAccount(acctsSlice) {
|
||
localStorage.setItem('data_has_account', acctsSlice.items.length ? 'yes' : 'no');
|
||
console.log('WRITING LOCAL STORAGE ITEM: ' + 'data_has_account', acctsSlice.items.length ? 'yes' : 'no');
|
||
}
|
||
function Model() {
|
||
evt.Emitter.call(this);
|
||
}
|
||
Model.prototype = {
|
||
acctsSlice: null,
|
||
account: null,
|
||
foldersSlice: null,
|
||
folder: null,
|
||
_callEmit: function (id) {
|
||
this.emit(id, this[id]);
|
||
},
|
||
inited: false,
|
||
hasAccount: function () {
|
||
return this.getAccountCount() > 0;
|
||
},
|
||
getAccount: function (id) {
|
||
if (!this.acctsSlice || !this.acctsSlice.items) {
|
||
throw new Error('No acctsSlice available');
|
||
}
|
||
var targetAccount;
|
||
this.acctsSlice.items.some(function (account) {
|
||
if (account.id === id) {
|
||
return !!(targetAccount = account);
|
||
}
|
||
});
|
||
return targetAccount;
|
||
},
|
||
getAccountCount: function () {
|
||
var count = 0;
|
||
if (this.acctsSlice && this.acctsSlice.items && this.acctsSlice.items.length) {
|
||
count = this.acctsSlice.items.length;
|
||
}
|
||
return count;
|
||
},
|
||
init: function (showLatest, callback) {
|
||
require(['api'], function (api) {
|
||
if (this === modelCreate.defaultModel) {
|
||
modelInit(this, api);
|
||
}
|
||
this.api = api;
|
||
this.die();
|
||
var acctsSlice = api.viewAccounts(false);
|
||
acctsSlice.oncomplete = function () {
|
||
this.acctsSlice = acctsSlice;
|
||
saveHasAccount(acctsSlice);
|
||
if (acctsSlice.items.length) {
|
||
var account = showLatest ? acctsSlice.items.slice(-1)[0] : acctsSlice.defaultAccount;
|
||
this.changeAccount(account, callback);
|
||
} else if (callback) {
|
||
callback();
|
||
}
|
||
this.inited = true;
|
||
this._callEmit('acctsSlice');
|
||
if (this === modelCreate.defaultModel) {
|
||
evt.emitWhenListener('metrics:apiDone');
|
||
}
|
||
}.bind(this);
|
||
acctsSlice.onchange = function () {
|
||
saveHasAccount(acctsSlice);
|
||
}.bind(this);
|
||
}.bind(this));
|
||
},
|
||
changeAccount: function (account, callback) {
|
||
if (this.account && this.account.id === account.id) {
|
||
if (callback) {
|
||
callback();
|
||
}
|
||
return;
|
||
}
|
||
this._dieFolders();
|
||
this.account = account;
|
||
this._callEmit('account');
|
||
var foldersSlice = this.api.viewFolders('account', account);
|
||
foldersSlice.oncomplete = function () {
|
||
this.foldersSlice = foldersSlice;
|
||
this.foldersSlice.onchange = this.notifyFoldersSliceOnChange.bind(this);
|
||
this.selectInbox(callback);
|
||
this._callEmit('foldersSlice');
|
||
}.bind(this);
|
||
},
|
||
changeAccountFromId: function (accountId, callback) {
|
||
if (!this.acctsSlice || !this.acctsSlice.items.length) {
|
||
throw new Error('No accounts available');
|
||
}
|
||
this.acctsSlice.items.some(function (account) {
|
||
if (account.id === accountId) {
|
||
this.changeAccount(account, callback);
|
||
return true;
|
||
}
|
||
}.bind(this));
|
||
},
|
||
changeFolder: function (folder) {
|
||
if (folder && (!this.folder || folder.id !== this.folder.id)) {
|
||
this.folder = folder;
|
||
this._callEmit('folder');
|
||
}
|
||
},
|
||
selectInbox: function (callback) {
|
||
this.selectFirstFolderWithType('inbox', callback);
|
||
},
|
||
selectFirstFolderWithType: function (folderType, callback) {
|
||
if (!this.foldersSlice) {
|
||
throw new Error('No foldersSlice available');
|
||
}
|
||
var folder = this.foldersSlice.getFirstFolderWithType(folderType);
|
||
if (!folder) {
|
||
dieOnFatalError('We have an account without a folderType ' + folderType + '!', this.foldersSlice.items);
|
||
}
|
||
if (this.folder && this.folder.id === folder.id) {
|
||
if (callback) {
|
||
callback();
|
||
}
|
||
} else {
|
||
if (callback) {
|
||
this.once('folder', callback);
|
||
}
|
||
this.changeFolder(folder);
|
||
}
|
||
},
|
||
notifyInboxMessages: function (accountUpdate) {
|
||
if (accountUpdate.id === this.account.id) {
|
||
this.emit('newInboxMessages', accountUpdate.count);
|
||
}
|
||
},
|
||
notifyFoldersSliceOnChange: function (folder) {
|
||
this.emit('foldersSliceOnChange', folder);
|
||
},
|
||
notifyBackgroundSendStatus: function (data) {
|
||
this.emit('backgroundSendStatus', data);
|
||
},
|
||
_dieFolders: function () {
|
||
if (this.foldersSlice) {
|
||
this.foldersSlice.die();
|
||
}
|
||
this.foldersSlice = null;
|
||
this.folder = null;
|
||
},
|
||
die: function () {
|
||
if (this.acctsSlice) {
|
||
this.acctsSlice.die();
|
||
}
|
||
this.acctsSlice = null;
|
||
this.account = null;
|
||
this._dieFolders();
|
||
}
|
||
};
|
||
evt.mix(Model.prototype);
|
||
function modelCreate() {
|
||
return new Model();
|
||
}
|
||
modelCreate.defaultModel = new Model();
|
||
return modelCreate;
|
||
});
|
||
var MimeMapper = {
|
||
_typeToExtensionMap: {
|
||
'image/jpeg': 'jpg',
|
||
'image/png': 'png',
|
||
'image/gif': 'gif',
|
||
'image/bmp': 'bmp',
|
||
'audio/mpeg': 'mp3',
|
||
'audio/mp4': 'm4a',
|
||
'audio/ogg': 'ogg',
|
||
'audio/webm': 'webm',
|
||
'audio/3gpp': '3gp',
|
||
'audio/amr': 'amr',
|
||
'video/mp4': 'mp4',
|
||
'video/mpeg': 'mpg',
|
||
'video/ogg': 'ogg',
|
||
'video/webm': 'webm',
|
||
'video/3gpp': '3gp',
|
||
'video/3gpp2': '3g2',
|
||
'application/pdf': 'pdf',
|
||
'application/vcard': 'vcf',
|
||
'text/vcard': 'vcf',
|
||
'text/x-vcard': 'vcf'
|
||
},
|
||
_extensionToTypeMap: {
|
||
'jpg': 'image/jpeg',
|
||
'jpeg': 'image/jpeg',
|
||
'jpe': 'image/jpeg',
|
||
'png': 'image/png',
|
||
'gif': 'image/gif',
|
||
'bmp': 'image/bmp',
|
||
'mp3': 'audio/mpeg',
|
||
'm4a': 'audio/mp4',
|
||
'm4b': 'audio/mp4',
|
||
'm4p': 'audio/mp4',
|
||
'm4r': 'audio/mp4',
|
||
'aac': 'audio/aac',
|
||
'opus': 'audio/ogg',
|
||
'amr': 'audio/amr',
|
||
'mp4': 'video/mp4',
|
||
'mpeg': 'video/mpeg',
|
||
'mpg': 'video/mpeg',
|
||
'ogv': 'video/ogg',
|
||
'ogx': 'video/ogg',
|
||
'webm': 'video/webm',
|
||
'3gp': 'video/3gpp',
|
||
'3g2': 'video/3gpp2',
|
||
'ogg': 'video/ogg',
|
||
'pdf': 'application/pdf',
|
||
'vcf': 'text/vcard'
|
||
},
|
||
_parseExtension: function (filename) {
|
||
var array = filename.split('.');
|
||
return array.length > 1 ? array.pop() : '';
|
||
},
|
||
isSupportedType: function (mimetype) {
|
||
return mimetype in this._typeToExtensionMap;
|
||
},
|
||
isSupportedExtension: function (extension) {
|
||
return extension in this._extensionToTypeMap;
|
||
},
|
||
isFilenameMatchesType: function (filename, mimetype) {
|
||
var extension = this._parseExtension(filename);
|
||
var guessedType = this.guessTypeFromExtension(extension);
|
||
return guessedType == mimetype;
|
||
},
|
||
guessExtensionFromType: function (mimetype) {
|
||
return this._typeToExtensionMap[mimetype];
|
||
},
|
||
guessTypeFromExtension: function (extension) {
|
||
return this._extensionToTypeMap[extension];
|
||
},
|
||
guessTypeFromFileProperties: function (filename, mimetype) {
|
||
var extension = this._parseExtension(filename);
|
||
var type = this.isSupportedType(mimetype) ? mimetype : this.guessTypeFromExtension(extension);
|
||
return type || '';
|
||
},
|
||
ensureFilenameMatchesType: function (filename, mimetype) {
|
||
if (!this.isFilenameMatchesType(filename, mimetype)) {
|
||
var guessedExt = this.guessExtensionFromType(mimetype);
|
||
if (guessedExt) {
|
||
filename += '.' + guessedExt;
|
||
}
|
||
}
|
||
return filename;
|
||
}
|
||
};
|
||
define('shared/js/mime_mapper', function (global) {
|
||
return function () {
|
||
var ret, fn;
|
||
return ret || global.MimeMapper;
|
||
};
|
||
}(this));
|
||
define('attachment_name', [
|
||
'require',
|
||
'l10n!',
|
||
'shared/js/mime_mapper'
|
||
], function (require) {
|
||
var mozL10n = require('l10n!'), mapper = require('shared/js/mime_mapper');
|
||
var attachmentName = {
|
||
ensureName: function (blob, name, count) {
|
||
if (!name) {
|
||
count = count || 1;
|
||
var suffix = mapper.guessExtensionFromType(blob.type);
|
||
name = mozL10n.get('default-attachment-filename', { n: count }) + (suffix ? '.' + suffix : '');
|
||
}
|
||
return name;
|
||
},
|
||
ensureNameList: function (blobs, names) {
|
||
for (var i = 0; i < blobs.length; i++) {
|
||
names[i] = attachmentName.ensureName(blobs[i], names[i], i + 1);
|
||
}
|
||
}
|
||
};
|
||
return attachmentName;
|
||
});
|
||
define('query_uri', [], function () {
|
||
'use strict';
|
||
function decode(value) {
|
||
try {
|
||
return decodeURIComponent(value);
|
||
} catch (err) {
|
||
console.error('Skipping "' + value + '", decodeURIComponent error: ' + err);
|
||
return '';
|
||
}
|
||
}
|
||
function queryURI(uri) {
|
||
function addressesToArray(addresses) {
|
||
if (!addresses) {
|
||
return [];
|
||
}
|
||
addresses = addresses.split(/[,;]/);
|
||
var addressesArray = addresses.filter(function notEmpty(addr) {
|
||
return addr.trim() !== '';
|
||
});
|
||
return addressesArray;
|
||
}
|
||
var mailtoReg = /^mailto:(.*)/i;
|
||
var obj = {};
|
||
if (uri && uri.match(mailtoReg)) {
|
||
uri = uri.match(mailtoReg)[1];
|
||
var parts = uri.split('?');
|
||
var subjectReg = /(?:^|&)subject=([^\&]*)/i, bodyReg = /(?:^|&)body=([^\&]*)/i, ccReg = /(?:^|&)cc=([^\&]*)/i, bccReg = /(?:^|&)bcc=([^\&]*)/i;
|
||
obj.to = parts[0] ? addressesToArray(decode(parts[0])) : [];
|
||
if (parts.length == 2) {
|
||
var data = parts[1];
|
||
if (data.match(subjectReg)) {
|
||
obj.subject = decode(data.match(subjectReg)[1]);
|
||
}
|
||
if (data.match(bodyReg)) {
|
||
obj.body = decode(data.match(bodyReg)[1]);
|
||
}
|
||
if (data.match(ccReg)) {
|
||
obj.cc = addressesToArray(decode(data.match(ccReg)[1]));
|
||
}
|
||
if (parts[1].match(bccReg)) {
|
||
obj.bcc = addressesToArray(decode(data.match(bccReg)[1]));
|
||
}
|
||
}
|
||
}
|
||
return obj;
|
||
}
|
||
return queryURI;
|
||
});
|
||
define('activity_composer_data', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'attachment_name',
|
||
'query_uri'
|
||
], function (require, exports, module) {
|
||
var attachmentName = require('attachment_name'), queryUri = require('query_uri');
|
||
return function activityComposeR(rawActivity) {
|
||
var source = rawActivity.source;
|
||
var data = source.data;
|
||
var activityName = source.name;
|
||
var dataType = data.type;
|
||
var url = data.url || data.URI;
|
||
var attachData;
|
||
if (dataType === 'url' && activityName === 'share') {
|
||
attachData = { body: url };
|
||
} else {
|
||
attachData = queryUri(url);
|
||
attachData.attachmentBlobs = data.blobs || [];
|
||
attachData.attachmentNames = data.filenames || [];
|
||
attachmentName.ensureNameList(attachData.attachmentBlobs, attachData.attachmentNames);
|
||
}
|
||
return {
|
||
onComposer: function (composer, composeCard) {
|
||
var attachmentBlobs = attachData.attachmentBlobs;
|
||
if (attachData.to) {
|
||
composer.to = attachData.to;
|
||
}
|
||
if (attachData.subject) {
|
||
composer.subject = attachData.subject;
|
||
}
|
||
if (attachData.body) {
|
||
composer.body = { text: attachData.body + composer.body.text };
|
||
}
|
||
if (attachData.cc) {
|
||
composer.cc = attachData.cc;
|
||
}
|
||
if (attachData.bcc) {
|
||
composer.bcc = attachData.bcc;
|
||
}
|
||
if (attachmentBlobs) {
|
||
var attachmentsToAdd = [];
|
||
for (var iBlob = 0; iBlob < attachmentBlobs.length; iBlob++) {
|
||
attachmentsToAdd.push({
|
||
name: attachData.attachmentNames[iBlob],
|
||
blob: attachmentBlobs[iBlob]
|
||
});
|
||
}
|
||
composeCard.addAttachmentsSubjectToSizeLimits(attachmentsToAdd);
|
||
}
|
||
}
|
||
};
|
||
};
|
||
});
|
||
define('cards_init', ['require'], function (require) {
|
||
return function cardsInit(cards) {
|
||
cards.pushDefaultCard = function (onPushed) {
|
||
require(['model_create'], function (modelCreate) {
|
||
cards.pushCard('message_list', 'none', {
|
||
model: modelCreate.defaultModel,
|
||
onPushed: onPushed
|
||
}, 'left');
|
||
});
|
||
};
|
||
};
|
||
});
|
||
define('html_cache', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'l10n!'
|
||
], function (require, exports) {
|
||
var mozL10n = require('l10n!');
|
||
exports.cloneAsInertNodeAvoidingCustomElementHorrors = function (node) {
|
||
var templateNode = document.createElement('template');
|
||
var cacheDoc = templateNode.content.ownerDocument;
|
||
return cacheDoc.importNode(node, true);
|
||
};
|
||
exports.save = function htmlCacheSave(moduleId, html) {
|
||
var id = exports.moduleIdToKey(moduleId);
|
||
var langDir = document.querySelector('html').getAttribute('dir');
|
||
html = window.HTML_CACHE_VERSION + (langDir ? ',' + langDir : '') + ':' + html;
|
||
localStorage.setItem('html_cache_' + id, html);
|
||
console.log('htmlCache.save ' + id + ': ' + html.length + ', lang dir: ' + langDir);
|
||
};
|
||
exports.reset = function () {
|
||
localStorage.clear();
|
||
var expiry = Date.now() + 20 * 365 * 24 * 60 * 60 * 1000;
|
||
expiry = new Date(expiry).toUTCString();
|
||
for (var i = 0; i < 40; i++) {
|
||
document.cookie = 'htmlc' + i + '=; expires=' + expiry;
|
||
}
|
||
console.log('htmlCache reset');
|
||
};
|
||
window.addEventListener('languagechange', exports.reset);
|
||
exports.moduleIdToKey = function moduleIdToKey(moduleId) {
|
||
return moduleId.replace(/^cards\//, '').replace(/-/g, '_');
|
||
};
|
||
exports.nodeToKey = function nodeToKey(node) {
|
||
return node.nodeName.toLowerCase().replace(/^cards-/, '').replace(/-/g, '_');
|
||
};
|
||
exports.cloneAndSave = function cloneAndSave(moduleId, node) {
|
||
var cachedNode = exports.cloneAsInertNodeAvoidingCustomElementHorrors(node);
|
||
mozL10n.translateFragment(cachedNode);
|
||
cachedNode.dataset.cached = 'cached';
|
||
exports.delayedSaveFromNode(moduleId, cachedNode);
|
||
};
|
||
exports.saveFromNode = function saveFromNode(moduleId, node) {
|
||
var cl = node.classList;
|
||
cl.remove('before');
|
||
cl.remove('after');
|
||
cl.add('center');
|
||
var nodes = node.querySelectorAll('.email-ce');
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
nodes[i].dataset.cached = 'cached';
|
||
}
|
||
if (node.classList.contains('email-ce')) {
|
||
node.dataset.cached = 'cached';
|
||
}
|
||
var html = node.outerHTML;
|
||
exports.save(moduleId, html);
|
||
};
|
||
var delayedSaveId = 0;
|
||
var delayedNode = '';
|
||
exports.delayedSaveFromNode = function delayedSaveFromNode(moduleId, node) {
|
||
delayedNode = node;
|
||
if (!delayedSaveId) {
|
||
delayedSaveId = setTimeout(function () {
|
||
delayedSaveId = 0;
|
||
exports.saveFromNode(moduleId, delayedNode);
|
||
delayedNode = null;
|
||
}, 500);
|
||
}
|
||
};
|
||
});
|
||
define('tmpl', ['l10n!'], function (mozL10n) {
|
||
var tmpl = {
|
||
pluginBuilder: './tmpl_builder',
|
||
toDom: function (text) {
|
||
var temp = document.createElement('div');
|
||
temp.innerHTML = text;
|
||
var node = temp.children[0];
|
||
mozL10n.translateFragment(node);
|
||
return node;
|
||
},
|
||
load: function (id, require, onload, config) {
|
||
require(['text!' + id], function (text) {
|
||
var node = tmpl.toDom(text);
|
||
onload(node);
|
||
});
|
||
}
|
||
};
|
||
return tmpl;
|
||
});
|
||
define('tmpl!cards/toaster.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<section role="status" class="toaster collapsed">\n <p class="toaster-text"></p>\n <div class="toaster-action-target"><button class="toaster-action"></button></div>\n</section>\n');
|
||
});
|
||
define('transition_end', [
|
||
'require',
|
||
'exports',
|
||
'module'
|
||
], function (require, exports, module) {
|
||
return function transitionEnd(node, fn, capturing) {
|
||
function asyncFn(event) {
|
||
Promise.resolve().then(function () {
|
||
fn(event);
|
||
}).catch(function (error) {
|
||
console.error(error);
|
||
});
|
||
}
|
||
node.addEventListener('transitionend', asyncFn, capturing);
|
||
return asyncFn;
|
||
};
|
||
});
|
||
define('toaster', [
|
||
'require',
|
||
'l10n!',
|
||
'tmpl!./cards/toaster.html',
|
||
'transition_end'
|
||
], function (require) {
|
||
var mozL10n = require('l10n!');
|
||
var toasterNode = require('tmpl!./cards/toaster.html');
|
||
var transitionEnd = require('transition_end');
|
||
var toaster = {
|
||
defaultTimeout: 5000,
|
||
_previousActionClass: undefined,
|
||
init: function (parentEl) {
|
||
this.el = toasterNode;
|
||
parentEl.appendChild(this.el);
|
||
this.text = this.el.querySelector('.toaster-text');
|
||
this.actionButton = this.el.querySelector('.toaster-action');
|
||
this.el.addEventListener('click', this.hide.bind(this));
|
||
transitionEnd(this.el, this.hide.bind(this));
|
||
this.el.querySelector('.toaster-action-target').addEventListener('click', this.onAction.bind(this));
|
||
this.currentToast = null;
|
||
},
|
||
toastOperation: function (op) {
|
||
if (!op || !op.affectedCount) {
|
||
return;
|
||
}
|
||
var type = op.operation;
|
||
var canUndo = op.undo && type !== 'move' && type !== 'delete';
|
||
this.toast({
|
||
text: mozL10n.get('toaster-message-' + type, { n: op.affectedCount }),
|
||
actionLabel: mozL10n.get('toaster-undo'),
|
||
actionClass: 'undo',
|
||
action: canUndo && op.undo.bind(op)
|
||
});
|
||
},
|
||
onAction: function () {
|
||
var actionFunction = this.currentToast && this.currentToast.action;
|
||
this.hide();
|
||
if (actionFunction) {
|
||
actionFunction();
|
||
}
|
||
},
|
||
toast: function (opts) {
|
||
opts = opts || {};
|
||
console.log('Showing toast:', JSON.stringify(opts));
|
||
this.hide();
|
||
this.currentToast = opts;
|
||
this.text.textContent = opts.text;
|
||
this.actionButton.textContent = opts.actionLabel;
|
||
if (this._previousActionClass) {
|
||
this.actionButton.classList.remove(this._previousActionClass);
|
||
this._previousActionClass = undefined;
|
||
}
|
||
if (opts.actionClass) {
|
||
this._previousActionClass = opts.actionClass;
|
||
this.actionButton.classList.add(this._previousActionClass);
|
||
}
|
||
this.el.classList.toggle('actionable', !opts.action);
|
||
this.actionButton.disabled = !opts.action;
|
||
this.el.classList.remove('collapsed');
|
||
this._fadeTimeout = setTimeout(function () {
|
||
this.el.classList.add('fadeout');
|
||
}.bind(this), opts.timeout || this.defaultTimeout);
|
||
},
|
||
isShowing: function () {
|
||
return !this.el.classList.contains('collapsed');
|
||
},
|
||
hide: function () {
|
||
this.currentToast = null;
|
||
this.el.classList.add('collapsed');
|
||
this.el.classList.remove('fadeout');
|
||
window.clearTimeout(this._fadeTimeout);
|
||
this._fadeTimeout = null;
|
||
}
|
||
};
|
||
return toaster;
|
||
});
|
||
define('input_areas', [
|
||
'require',
|
||
'exports',
|
||
'module'
|
||
], function (require, exports) {
|
||
var slice = Array.prototype.slice;
|
||
return function hookupInputAreaResetButtons(e) {
|
||
var selector = 'form p input + button[type="reset"],' + 'form p textarea + button[type="reset"]';
|
||
var resetButtons = slice.call(e.querySelectorAll(selector));
|
||
resetButtons.forEach(function (resetButton) {
|
||
resetButton.addEventListener('mousedown', function (e) {
|
||
e.preventDefault();
|
||
});
|
||
resetButton.addEventListener('click', function (e) {
|
||
e.target.previousElementSibling.value = '';
|
||
e.preventDefault();
|
||
});
|
||
});
|
||
};
|
||
});
|
||
define('cards', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards_init',
|
||
'html_cache',
|
||
'l10n!',
|
||
'evt',
|
||
'toaster',
|
||
'transition_end',
|
||
'input_areas'
|
||
], function (require, exports, module) {
|
||
var cardsInit = require('cards_init'), htmlCache = require('html_cache'), mozL10n = require('l10n!'), evt = require('evt'), toaster = require('toaster'), transitionEnd = require('transition_end'), hookupInputAreaResetButtons = require('input_areas');
|
||
function addClass(domNode, name) {
|
||
if (domNode) {
|
||
domNode.classList.add(name);
|
||
}
|
||
}
|
||
function removeClass(domNode, name) {
|
||
if (domNode) {
|
||
domNode.classList.remove(name);
|
||
}
|
||
}
|
||
var cards = {
|
||
_cardDefs: {},
|
||
_cardStack: [],
|
||
activeCardIndex: -1,
|
||
_pendingPush: null,
|
||
_zIndex: 0,
|
||
_rootNode: null,
|
||
_containerNode: null,
|
||
_cardsNode: null,
|
||
_animatingDeadDomNodes: [],
|
||
_transitionCount: 0,
|
||
_startupEventsEmitted: false,
|
||
_popupActive: null,
|
||
_eatingEventsUntilNextCard: false,
|
||
init: function () {
|
||
this._rootNode = document.body;
|
||
this._containerNode = document.getElementById('cardContainer');
|
||
this._cardsNode = document.getElementById('cards');
|
||
this._statusColorMeta = document.querySelector('meta[name="theme-color"]');
|
||
toaster.init(this._containerNode);
|
||
this._containerNode.addEventListener('click', this._onMaybeIntercept.bind(this), true);
|
||
transitionEnd(this._cardsNode, this._onTransitionEnd.bind(this), false);
|
||
document.addEventListener('visibilitychange', function (evt) {
|
||
var card = this._cardStack[this.activeCardIndex];
|
||
if (card && card.onCurrentCardDocumentVisibilityChange) {
|
||
card.onCurrentCardDocumentVisibilityChange(document.hidden);
|
||
}
|
||
}.bind(this));
|
||
cardsInit(this);
|
||
},
|
||
_onMaybeIntercept: function (event) {
|
||
if (this._eatingEventsUntilNextCard) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
return;
|
||
}
|
||
if (this._popupActive) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
this._popupActive.close();
|
||
return;
|
||
}
|
||
var cardNode = event.target;
|
||
for (cardNode = event.target; cardNode; cardNode = cardNode.parentElement) {
|
||
if (cardNode.classList.contains('card')) {
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
pushCard: function (type, showMethod, args, placement) {
|
||
var cardDef = this._cardDefs[type];
|
||
args = args || {};
|
||
if (!cardDef) {
|
||
var cbArgs = Array.slice(arguments);
|
||
this._pendingPush = [type];
|
||
if (showMethod !== 'none') {
|
||
this.eatEventsUntilNextCard();
|
||
}
|
||
require(['element!cards/' + type], function (Ctor) {
|
||
this._cardDefs[type] = Ctor;
|
||
this.pushCard.apply(this, cbArgs);
|
||
}.bind(this));
|
||
return;
|
||
}
|
||
this._pendingPush = null;
|
||
console.log('pushCard for type: ' + type);
|
||
var domNode = args.cachedNode || new cardDef();
|
||
if (args && domNode.onArgs) {
|
||
domNode.onArgs(args);
|
||
}
|
||
var cardIndex, insertBuddy;
|
||
if (!placement) {
|
||
cardIndex = this._cardStack.length;
|
||
insertBuddy = null;
|
||
domNode.classList.add(cardIndex === 0 ? 'before' : 'after');
|
||
} else if (placement === 'left') {
|
||
cardIndex = this.activeCardIndex++;
|
||
insertBuddy = this._cardsNode.children[cardIndex];
|
||
domNode.classList.add('before');
|
||
} else if (placement === 'right') {
|
||
cardIndex = this.activeCardIndex + 1;
|
||
if (cardIndex >= this._cardStack.length) {
|
||
insertBuddy = null;
|
||
} else {
|
||
insertBuddy = this._cardsNode.children[cardIndex];
|
||
}
|
||
domNode.classList.add('after');
|
||
}
|
||
this._cardStack.splice(cardIndex, 0, domNode);
|
||
if (!args.cachedNode) {
|
||
this._cardsNode.insertBefore(domNode, insertBuddy);
|
||
}
|
||
hookupInputAreaResetButtons(domNode);
|
||
if (!domNode.callHeaderFontSize) {
|
||
window.dispatchEvent(new CustomEvent('lazyload', { detail: domNode }));
|
||
}
|
||
if ('postInsert' in domNode) {
|
||
domNode.postInsert();
|
||
}
|
||
if (showMethod !== 'none') {
|
||
if (!args.cachedNode) {
|
||
domNode.clientWidth;
|
||
}
|
||
this._showCard(cardIndex, showMethod, 'forward');
|
||
}
|
||
if (args.onPushed) {
|
||
args.onPushed(domNode);
|
||
}
|
||
},
|
||
pushOrTellCard: function (type, showMethod, args, placement) {
|
||
var query = type;
|
||
if (this.hasCard(query)) {
|
||
this.tellCard(query, args);
|
||
return false;
|
||
} else {
|
||
this.pushCard.apply(this, Array.slice(arguments));
|
||
return true;
|
||
}
|
||
},
|
||
setStatusColor: function (element) {
|
||
var color;
|
||
if (!element) {
|
||
element = this._cardStack[this.activeCardIndex];
|
||
}
|
||
var statusElement = element.dataset.statuscolor ? element : element.querySelector('[data-statuscolor]');
|
||
if (statusElement) {
|
||
color = statusElement.dataset.statuscolor;
|
||
if (color === 'default') {
|
||
color = null;
|
||
} else if (color === 'background') {
|
||
color = getComputedStyle(statusElement).backgroundColor;
|
||
}
|
||
} else {
|
||
color = getComputedStyle(element).backgroundColor;
|
||
}
|
||
if (color && color.indexOf('rgb') !== 0 && color.indexOf('#') !== 0) {
|
||
color = null;
|
||
}
|
||
color = color || this._statusColorMeta.dataset.statuscolor;
|
||
var existingColor = this._statusColorMeta.getAttribute('content');
|
||
if (color !== existingColor) {
|
||
this._statusColorMeta.setAttribute('content', color);
|
||
}
|
||
},
|
||
_findCardUsingType: function (type) {
|
||
for (var i = 0; i < this._cardStack.length; i++) {
|
||
var domNode = this._cardStack[i];
|
||
if (htmlCache.nodeToKey(domNode) === type) {
|
||
return i;
|
||
}
|
||
}
|
||
},
|
||
_findCard: function (query, skipFail) {
|
||
var result;
|
||
if (typeof query === 'string') {
|
||
result = this._findCardUsingType(query, skipFail);
|
||
} else if (typeof query === 'number') {
|
||
result = query;
|
||
} else {
|
||
result = this._cardStack.indexOf(query);
|
||
}
|
||
if (result > -1) {
|
||
return result;
|
||
} else if (!skipFail) {
|
||
throw new Error('Unable to find card with query:', query);
|
||
} else {
|
||
return undefined;
|
||
}
|
||
},
|
||
hasCard: function (query) {
|
||
if (this._pendingPush && this._pendingPush === query) {
|
||
return true;
|
||
}
|
||
return this._findCard(query, true) > -1;
|
||
},
|
||
isVisible: function (domNode) {
|
||
return !!(domNode && domNode.classList.contains('center'));
|
||
},
|
||
findCardObject: function (query) {
|
||
return this._cardStack[this._findCard(query)];
|
||
},
|
||
getCurrentCardType: function () {
|
||
var result = null, card = this._cardStack[this.activeCardIndex];
|
||
if (this._pendingPush) {
|
||
result = this._pendingPush;
|
||
} else if (card) {
|
||
result = htmlCache.nodeToKey(card);
|
||
}
|
||
return result;
|
||
},
|
||
folderSelector: function (model, callback, filter) {
|
||
var self = this;
|
||
require(['value_selector'], function (ValueSelector) {
|
||
if (!self.folderPrompt) {
|
||
var selectorTitle = mozL10n.get('messages-folder-select');
|
||
self.folderPrompt = new ValueSelector(selectorTitle);
|
||
}
|
||
model.latestOnce('foldersSlice', function (foldersSlice) {
|
||
var folders = foldersSlice.items;
|
||
folders.forEach(function (folder) {
|
||
var isMatch = !filter || filter(folder);
|
||
if (folder.neededForHierarchy || isMatch) {
|
||
self.folderPrompt.addToList(folder.name, folder.depth, isMatch, function (folder) {
|
||
return function () {
|
||
self.folderPrompt.hide();
|
||
callback(folder);
|
||
};
|
||
}(folder));
|
||
}
|
||
});
|
||
self.folderPrompt.show();
|
||
});
|
||
});
|
||
},
|
||
moveToCard: function (query, showMethod) {
|
||
this._showCard(this._findCard(query), showMethod || 'animate');
|
||
},
|
||
tellCard: function (query, what) {
|
||
var cardIndex = this._findCard(query), domNode = this._cardStack[cardIndex];
|
||
if (!('told' in domNode)) {
|
||
console.warn('Tried to tell a card that\'s not listening!', query, what);
|
||
} else {
|
||
domNode.told(what);
|
||
}
|
||
},
|
||
removeCardAndSuccessors: function (cardDomNode, showMethod, numCards, nextCardSpec, skipDefault) {
|
||
if (!this._cardStack.length) {
|
||
return;
|
||
}
|
||
if (cardDomNode && this._cardStack.length === 1 && !skipDefault) {
|
||
return cards.pushDefaultCard(function () {
|
||
this.removeCardAndSuccessors(cardDomNode, showMethod, numCards, nextCardSpec);
|
||
}.bind(this));
|
||
}
|
||
var firstIndex, iCard, domNode;
|
||
if (cardDomNode === undefined) {
|
||
throw new Error('undefined is not a valid card spec!');
|
||
} else if (cardDomNode === null) {
|
||
firstIndex = 0;
|
||
this._zIndex = 0;
|
||
} else {
|
||
for (iCard = this._cardStack.length - 1; iCard >= 0; iCard--) {
|
||
domNode = this._cardStack[iCard];
|
||
if (domNode === cardDomNode) {
|
||
firstIndex = iCard;
|
||
break;
|
||
}
|
||
}
|
||
if (firstIndex === undefined) {
|
||
throw new Error('No card represented by that DOM node');
|
||
}
|
||
}
|
||
if (!numCards) {
|
||
numCards = this._cardStack.length - firstIndex;
|
||
}
|
||
if (showMethod === 'none') {
|
||
if (cardDomNode && cardDomNode.classList.contains('anim-overlay')) {
|
||
this._zIndex -= 10;
|
||
}
|
||
} else {
|
||
var nextCardIndex = -1;
|
||
if (nextCardSpec) {
|
||
nextCardIndex = this._findCard(nextCardSpec);
|
||
} else if (this._cardStack.length) {
|
||
nextCardIndex = Math.min(firstIndex - 1, this._cardStack.length - 1);
|
||
}
|
||
if (nextCardIndex > -1) {
|
||
this._showCard(nextCardIndex, showMethod, 'back');
|
||
}
|
||
}
|
||
if (firstIndex <= this.activeCardIndex) {
|
||
this.activeCardIndex -= numCards;
|
||
if (this.activeCardIndex < -1) {
|
||
this.activeCardIndex = -1;
|
||
}
|
||
}
|
||
var deadDomNodes = this._cardStack.splice(firstIndex, numCards);
|
||
for (iCard = 0; iCard < deadDomNodes.length; iCard++) {
|
||
domNode = deadDomNodes[iCard];
|
||
try {
|
||
domNode.die();
|
||
} catch (ex) {
|
||
console.warn('Problem cleaning up card:', ex, '\n', ex.stack);
|
||
}
|
||
switch (showMethod) {
|
||
case 'animate':
|
||
case 'immediate':
|
||
this._animatingDeadDomNodes.push(domNode);
|
||
break;
|
||
case 'none':
|
||
domNode.parentNode.removeChild(domNode);
|
||
break;
|
||
}
|
||
}
|
||
this._setScreenReaderVisibility();
|
||
},
|
||
removeAllCards: function () {
|
||
return this.removeCardAndSuccessors(null, 'none');
|
||
},
|
||
_showCard: function (cardIndex, showMethod, navDirection) {
|
||
if (cardIndex === this.activeCardIndex) {
|
||
return;
|
||
}
|
||
var activeElement = document.activeElement;
|
||
if (activeElement && activeElement.blur) {
|
||
activeElement.blur();
|
||
}
|
||
if (cardIndex > this._cardStack.length - 1) {
|
||
cardIndex = this._cardStack.length - 1;
|
||
}
|
||
if (this.activeCardIndex > this._cardStack.length - 1) {
|
||
this.activeCardIndex = -1;
|
||
}
|
||
if (this.activeCardIndex === -1) {
|
||
this.activeCardIndex = cardIndex === 0 ? cardIndex : cardIndex - 1;
|
||
}
|
||
var domNode = cardIndex !== null ? this._cardStack[cardIndex] : null;
|
||
var beginNode = this._cardStack[this.activeCardIndex];
|
||
var endNode = this._cardStack[cardIndex];
|
||
var isForward = navDirection === 'forward';
|
||
if (this._cardStack.length === 1) {
|
||
this._zIndex = 0;
|
||
}
|
||
if (isForward && endNode.classList.contains('anim-overlay')) {
|
||
beginNode = null;
|
||
this._zIndex += 10;
|
||
}
|
||
if (beginNode && beginNode.classList.contains('anim-overlay')) {
|
||
if (isForward) {
|
||
if (showMethod !== 'immediate') {
|
||
if (beginNode.classList.contains('anim-vertical')) {
|
||
removeClass(beginNode, 'anim-vertical');
|
||
addClass(beginNode, 'disabled-anim-vertical');
|
||
} else if (beginNode.classList.contains('anim-fade')) {
|
||
removeClass(beginNode, 'anim-fade');
|
||
addClass(beginNode, 'disabled-anim-fade');
|
||
}
|
||
}
|
||
} else {
|
||
this.setStatusColor(endNode);
|
||
endNode = null;
|
||
this._zIndex -= 10;
|
||
}
|
||
}
|
||
if (endNode && isForward && this._zIndex) {
|
||
endNode.style.zIndex = this._zIndex;
|
||
}
|
||
var cardsNode = this._cardsNode;
|
||
if (endNode) {
|
||
this.setStatusColor(endNode);
|
||
}
|
||
if (showMethod === 'immediate') {
|
||
addClass(beginNode, 'no-anim');
|
||
addClass(endNode, 'no-anim');
|
||
cardsNode.clientWidth;
|
||
this._eatingEventsUntilNextCard = false;
|
||
} else if (showMethod === 'none') {
|
||
} else {
|
||
this._transitionCount = beginNode && endNode ? 2 : 1;
|
||
this._eatingEventsUntilNextCard = true;
|
||
}
|
||
if (this.activeCardIndex === cardIndex) {
|
||
removeClass(beginNode, 'before');
|
||
removeClass(beginNode, 'after');
|
||
addClass(beginNode, 'center');
|
||
} else if (this.activeCardIndex > cardIndex) {
|
||
removeClass(beginNode, 'center');
|
||
addClass(beginNode, 'after');
|
||
removeClass(endNode, 'before');
|
||
addClass(endNode, 'center');
|
||
} else {
|
||
removeClass(beginNode, 'center');
|
||
addClass(beginNode, 'before');
|
||
removeClass(endNode, 'after');
|
||
addClass(endNode, 'center');
|
||
}
|
||
if (showMethod === 'immediate') {
|
||
cardsNode.clientWidth;
|
||
removeClass(beginNode, 'no-anim');
|
||
removeClass(endNode, 'no-anim');
|
||
this._onCardVisible(domNode);
|
||
}
|
||
toaster.hide();
|
||
this.activeCardIndex = cardIndex;
|
||
this._setScreenReaderVisibility();
|
||
},
|
||
_setScreenReaderVisibility: function () {
|
||
this._cardStack.forEach(function (card, index) {
|
||
card.setAttribute('aria-hidden', index !== this.activeCardIndex);
|
||
}, this);
|
||
},
|
||
_onTransitionEnd: function (event) {
|
||
if (!event.target.classList.contains('card')) {
|
||
return;
|
||
}
|
||
var activeCard = this._cardStack[this.activeCardIndex];
|
||
if (!activeCard) {
|
||
return;
|
||
}
|
||
if (this._transitionCount > 0) {
|
||
this._transitionCount -= 1;
|
||
}
|
||
if (this._transitionCount === 0) {
|
||
if (this._eatingEventsUntilNextCard) {
|
||
this._eatingEventsUntilNextCard = false;
|
||
}
|
||
if (this._animatingDeadDomNodes.length) {
|
||
setTimeout(function () {
|
||
this._animatingDeadDomNodes.forEach(function (domNode) {
|
||
if (domNode.parentNode) {
|
||
domNode.parentNode.removeChild(domNode);
|
||
}
|
||
});
|
||
this._animatingDeadDomNodes = [];
|
||
}.bind(this), 100);
|
||
}
|
||
var endNode = activeCard;
|
||
if (endNode.classList.contains('disabled-anim-vertical')) {
|
||
removeClass(endNode, 'disabled-anim-vertical');
|
||
addClass(endNode, 'anim-vertical');
|
||
} else if (endNode.classList.contains('disabled-anim-fade')) {
|
||
removeClass(endNode, 'disabled-anim-fade');
|
||
addClass(endNode, 'anim-fade');
|
||
}
|
||
if (this._afterTransitionAction) {
|
||
var afterTransitionAction = this._afterTransitionAction;
|
||
this._afterTransitionAction = null;
|
||
afterTransitionAction();
|
||
}
|
||
this._onCardVisible(activeCard);
|
||
var nextCards = activeCard.nextCards;
|
||
if (nextCards) {
|
||
console.log('Preloading cards: ' + nextCards);
|
||
require(nextCards.map(function (id) {
|
||
return 'cards/' + id;
|
||
}));
|
||
}
|
||
}
|
||
},
|
||
_onCardVisible: function (domNode) {
|
||
if (domNode.onCardVisible) {
|
||
domNode.onCardVisible();
|
||
}
|
||
this._emitStartupEvents(domNode.skipEmitContentEvents);
|
||
},
|
||
_emitStartupEvents: function (skipEmitContentEvents) {
|
||
if (!this._startupEventsEmitted) {
|
||
if (window.startupCacheEventsSent) {
|
||
window.performance.mark('contentInteractive');
|
||
} else {
|
||
window.performance.mark('navigationLoaded');
|
||
}
|
||
window.performance.mark('navigationInteractive');
|
||
if (!skipEmitContentEvents) {
|
||
evt.emit('metrics:contentDone');
|
||
}
|
||
this._startupEventsEmitted = true;
|
||
}
|
||
},
|
||
eatEventsUntilNextCard: function () {
|
||
this._eatingEventsUntilNextCard = true;
|
||
},
|
||
stopEatingEvents: function () {
|
||
this._eatingEventsUntilNextCard = false;
|
||
},
|
||
assertNoCards: function () {
|
||
if (this._cardStack.length) {
|
||
throw new Error('There are ' + this._cardStack.length + ' cards but' + ' there should be ZERO');
|
||
}
|
||
}
|
||
};
|
||
return cards;
|
||
});
|
||
define('array', ['require'], function (require) {
|
||
var array = {
|
||
indexOfGeneric: function (array, callback, thisObject) {
|
||
var result = -1;
|
||
array.some(function (value, index) {
|
||
if (callback.call(thisObject, value)) {
|
||
result = index;
|
||
return true;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
};
|
||
return array;
|
||
});
|
||
define('header_cursor', [
|
||
'require',
|
||
'array',
|
||
'evt'
|
||
], function (require) {
|
||
var array = require('array'), evt = require('evt');
|
||
function makeListener(type, obj) {
|
||
return function () {
|
||
var args = Array.slice(arguments);
|
||
this.emit.apply(this, ['messages_' + type].concat(args));
|
||
}.bind(obj);
|
||
}
|
||
function HeaderCursor(model) {
|
||
evt.Emitter.call(this);
|
||
this.model = model;
|
||
this.onLatestFolder = this.onLatestFolder.bind(this);
|
||
this.searchMode = 'nonsearch';
|
||
}
|
||
HeaderCursor.prototype = evt.mix({
|
||
currentMessage: null,
|
||
messagesSlice: null,
|
||
expectingMessageSuid: null,
|
||
sliceEvents: [
|
||
'splice',
|
||
'change',
|
||
'status',
|
||
'remove',
|
||
'complete'
|
||
],
|
||
_inited: false,
|
||
init: function () {
|
||
this._inited = true;
|
||
this.on('messages_splice', this.onMessagesSplice.bind(this));
|
||
this.on('messages_remove', this.onMessagesSpliceRemove.bind(this));
|
||
this.on('messages_complete', function () {
|
||
if (this.messagesSlice) {
|
||
this.messagesSlice.oncomplete = makeListener('complete', this);
|
||
}
|
||
}.bind(this));
|
||
this.model.latest('folder', this.onLatestFolder);
|
||
},
|
||
advance: function (direction) {
|
||
var index = this.indexOfMessageById(this.currentMessage.header.id);
|
||
switch (direction) {
|
||
case 'previous':
|
||
index -= 1;
|
||
break;
|
||
case 'next':
|
||
index += 1;
|
||
break;
|
||
}
|
||
var messages = this.messagesSlice.items;
|
||
if (index < 0 || index >= messages.length) {
|
||
return;
|
||
}
|
||
this.setCurrentMessageByIndex(index);
|
||
},
|
||
setCurrentMessageBySuid: function (messageSuid) {
|
||
this.expectingMessageSuid = messageSuid;
|
||
this.checkExpectingMessageSuid();
|
||
},
|
||
checkExpectingMessageSuid: function (eventIfNotFound) {
|
||
var messageSuid = this.expectingMessageSuid;
|
||
var model = this.model;
|
||
if (!messageSuid || !model.folder || model.folder.type !== 'inbox') {
|
||
return;
|
||
}
|
||
var index = this.indexOfMessageById(messageSuid);
|
||
if (index > -1) {
|
||
this.expectingMessageSuid = null;
|
||
return this.setCurrentMessageByIndex(index);
|
||
}
|
||
if (eventIfNotFound) {
|
||
console.error('header_cursor could not find messageSuid ' + messageSuid + ', emitting messageSuidNotFound');
|
||
this.emit('messageSuidNotFound', messageSuid);
|
||
}
|
||
},
|
||
setCurrentMessage: function (header) {
|
||
if (!header) {
|
||
return;
|
||
}
|
||
this.setCurrentMessageByIndex(this.indexOfMessageById(header.id));
|
||
},
|
||
setCurrentMessageByIndex: function (index) {
|
||
var messages = this.messagesSlice.items;
|
||
if (index === -1 || index > messages.length - 1) {
|
||
return;
|
||
}
|
||
var header = messages[index];
|
||
if ('header' in header) {
|
||
header = header.header;
|
||
}
|
||
var currentMessage = new CurrentMessage(header, {
|
||
hasPrevious: index !== 0,
|
||
hasNext: index !== messages.length - 1
|
||
});
|
||
this.emit('currentMessage', currentMessage, index);
|
||
this.currentMessage = currentMessage;
|
||
},
|
||
indexOfMessageById: function (id) {
|
||
var messages = this.messagesSlice && this.messagesSlice.items || [];
|
||
return array.indexOfGeneric(messages, function (message) {
|
||
var other = 'header' in message ? message.header.id : message.id;
|
||
return other === id;
|
||
});
|
||
},
|
||
onLatestFolder: function (folder) {
|
||
if (!this.model.foldersSlice) {
|
||
return;
|
||
}
|
||
this.freshMessagesSlice();
|
||
},
|
||
startSearch: function (phrase, whatToSearch) {
|
||
this.searchMode = 'search';
|
||
this.bindToSlice(this.model.api.searchFolderMessages(this.model.folder, phrase, whatToSearch));
|
||
},
|
||
endSearch: function () {
|
||
this.resetMessageSlice();
|
||
this.searchMode = 'nonsearch';
|
||
this.freshMessagesSlice();
|
||
},
|
||
freshMessagesSlice: function () {
|
||
this.bindToSlice(this.model.api.viewFolderMessages(this.model.folder));
|
||
},
|
||
bindToSlice: function (messagesSlice) {
|
||
this.resetMessageSlice();
|
||
this.messagesSlice = messagesSlice;
|
||
this.sliceEvents.forEach(function (type) {
|
||
messagesSlice['on' + type] = makeListener(type, this);
|
||
}.bind(this));
|
||
},
|
||
onMessagesSplice: function (index, howMany, addedItems, requested, moreExpected) {
|
||
if (!this.messagesSlice) {
|
||
return;
|
||
}
|
||
if (this.messagesSlice.atTop && this.expectingMessageSuid && this.messagesSlice.items && this.messagesSlice.items.length) {
|
||
this.checkExpectingMessageSuid(true);
|
||
}
|
||
},
|
||
onMessagesSpliceRemove: function (removedHeader, removedFromIndex) {
|
||
if (this.currentMessage !== removedHeader) {
|
||
return this.setCurrentMessage(this.currentMessage);
|
||
}
|
||
var messages = this.messagesSlice.items;
|
||
if (messages.length === 0) {
|
||
return this.currentMessage = null;
|
||
}
|
||
var index = Math.min(removedFromIndex, messages.length - 1);
|
||
var message = this.messagesSlice.items[index];
|
||
this.setCurrentMessage(message);
|
||
},
|
||
resetMessageSlice: function () {
|
||
if (this.messagesSlice) {
|
||
this.messagesSlice.die();
|
||
this.messagesSlice = null;
|
||
}
|
||
this.currentMessage = null;
|
||
},
|
||
die: function () {
|
||
this.model.removeListener('folder', this.onLatestFolder);
|
||
this.resetMessageSlice();
|
||
}
|
||
});
|
||
var oldOn = HeaderCursor.prototype.on;
|
||
HeaderCursor.prototype.on = function () {
|
||
if (!this._inited) {
|
||
this.init();
|
||
}
|
||
return oldOn.apply(this, arguments);
|
||
};
|
||
function CurrentMessage(header, siblings) {
|
||
this.header = header;
|
||
this.siblings = siblings;
|
||
}
|
||
CurrentMessage.prototype = {
|
||
header: null,
|
||
siblings: null
|
||
};
|
||
HeaderCursor.CurrentMessage = CurrentMessage;
|
||
return HeaderCursor;
|
||
});
|
||
(function (exports) {
|
||
'use strict';
|
||
const HEADER_SIZES = [
|
||
16,
|
||
17,
|
||
18,
|
||
19,
|
||
20,
|
||
21,
|
||
22,
|
||
23
|
||
];
|
||
var FontSizeUtils = {
|
||
_cachedContexts: {},
|
||
_getCachedContext: function (fontSize, fontFamily, fontStyle) {
|
||
fontStyle = fontStyle || 'italic';
|
||
var cache = this._cachedContexts;
|
||
var ctx = cache[fontSize] && cache[fontSize][fontFamily] ? cache[fontSize][fontFamily][fontStyle] : null;
|
||
if (!ctx) {
|
||
var canvas = document.createElement('canvas');
|
||
canvas.setAttribute('moz-opaque', 'true');
|
||
canvas.setAttribute('width', '1');
|
||
canvas.setAttribute('height', '1');
|
||
ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||
ctx.font = fontStyle + ' ' + fontSize + 'px ' + fontFamily;
|
||
if (!cache[fontSize]) {
|
||
cache[fontSize] = {};
|
||
}
|
||
if (!cache[fontSize][fontFamily]) {
|
||
cache[fontSize][fontFamily] = {};
|
||
}
|
||
cache[fontSize][fontFamily][fontStyle] = ctx;
|
||
}
|
||
return ctx;
|
||
},
|
||
resetCache: function () {
|
||
this._cachedContexts = {};
|
||
},
|
||
_textChangeObserver: null,
|
||
_handleTextChanges: function (mutations) {
|
||
for (var i = 0; i < mutations.length; i++) {
|
||
this._reformatHeaderText(mutations[i].target);
|
||
}
|
||
},
|
||
_getTextChangeObserver: function () {
|
||
if (!this._textChangeObserver) {
|
||
this._textChangeObserver = new MutationObserver(this._handleTextChanges.bind(this));
|
||
}
|
||
return this._textChangeObserver;
|
||
},
|
||
_observeHeaderChanges: function (element) {
|
||
var observer = this._getTextChangeObserver();
|
||
observer.observe(element, { childList: true });
|
||
},
|
||
_reformatHeaderText: function (header) {
|
||
if (header.textContent.trim() === '') {
|
||
return;
|
||
}
|
||
this.resetCentering(header);
|
||
var style = this.getStyleProperties(header);
|
||
style.textWidth = this.autoResizeElement(header, style);
|
||
this.centerTextToScreen(header, style);
|
||
},
|
||
_registerHeadersInSubtree: function (domNode) {
|
||
if (!domNode) {
|
||
return;
|
||
}
|
||
var headers = domNode.querySelectorAll('header > h1');
|
||
for (var i = 0; i < headers.length; i++) {
|
||
window.requestAnimationFrame(function (header) {
|
||
this._reformatHeaderText(header);
|
||
this._observeHeaderChanges(header);
|
||
}.bind(this, headers[i]));
|
||
}
|
||
},
|
||
getFontWidth: function (string, fontSize, fontFamily, fontStyle) {
|
||
var ctx = this._getCachedContext(fontSize, fontFamily, fontStyle);
|
||
return ctx.measureText(string).width;
|
||
},
|
||
getMaxFontSizeInfo: function (string, allowedSizes, fontFamily, maxWidth) {
|
||
var fontSize;
|
||
var resultWidth;
|
||
var i = allowedSizes.length - 1;
|
||
do {
|
||
fontSize = allowedSizes[i];
|
||
resultWidth = this.getFontWidth(string, fontSize, fontFamily);
|
||
i--;
|
||
} while (resultWidth > maxWidth && i >= 0);
|
||
return {
|
||
fontSize: fontSize,
|
||
overflow: resultWidth > maxWidth,
|
||
textWidth: resultWidth
|
||
};
|
||
},
|
||
_overflowCountCache: -1,
|
||
getOverflowCount: function (string, fontSize, fontFamily, maxWidth) {
|
||
var substring;
|
||
var resultWidth;
|
||
var overflowCount = -1;
|
||
if (string.length > this._overflowCountCache) {
|
||
substring = string.substr(0, string.length - this._overflowCountCache);
|
||
resultWidth = this.getFontWidth(substring, fontSize, fontFamily);
|
||
if (resultWidth > maxWidth) {
|
||
overflowCount = this._overflowCountCache;
|
||
}
|
||
}
|
||
do {
|
||
overflowCount++;
|
||
substring = string.substr(0, string.length - overflowCount);
|
||
resultWidth = this.getFontWidth(substring, fontSize, fontFamily);
|
||
} while (substring.length > 0 && resultWidth > maxWidth);
|
||
this._overflowCountCache = overflowCount;
|
||
return overflowCount;
|
||
},
|
||
getAllowedSizes: function (element) {
|
||
if (element.tagName === 'H1' && element.parentNode.tagName === 'HEADER') {
|
||
return HEADER_SIZES;
|
||
}
|
||
return [];
|
||
},
|
||
getContentWidth: function (style) {
|
||
var width = parseInt(style.width, 10);
|
||
if (style.boxSizing === 'border-box') {
|
||
width -= parseInt(style.paddingRight, 10) + parseInt(style.paddingLeft, 10);
|
||
}
|
||
return width;
|
||
},
|
||
getStyleProperties: function (element) {
|
||
var style = window.getComputedStyle(element);
|
||
var contentWidth = this.getContentWidth(style);
|
||
if (isNaN(contentWidth)) {
|
||
contentWidth = 0;
|
||
}
|
||
return {
|
||
fontFamily: style.fontFamily,
|
||
contentWidth: contentWidth,
|
||
paddingRight: parseInt(style.paddingRight, 10),
|
||
paddingLeft: parseInt(style.paddingLeft, 10),
|
||
offsetLeft: element.offsetLeft
|
||
};
|
||
},
|
||
autoResizeElement: function (element, styleOptions) {
|
||
var allowedSizes = this.getAllowedSizes(element);
|
||
if (allowedSizes.length === 0) {
|
||
return 0;
|
||
}
|
||
var contentWidth = styleOptions.contentWidth || this.getContentWidth(element);
|
||
var fontFamily = styleOptions.fontFamily || getComputedStyle(element).fontFamily;
|
||
var info = this.getMaxFontSizeInfo(element.textContent.trim(), allowedSizes, fontFamily, contentWidth);
|
||
element.style.fontSize = info.fontSize + 'px';
|
||
return info.textWidth;
|
||
},
|
||
resetCentering: function (element) {
|
||
element.style.marginLeft = element.style.marginRight = '0';
|
||
},
|
||
centerTextToScreen: function (element, styleOptions) {
|
||
var minHeaderWidth = styleOptions.textWidth + styleOptions.paddingRight + styleOptions.paddingLeft;
|
||
var sideSpaceLeft = styleOptions.offsetLeft;
|
||
var sideSpaceRight = this.getWindowWidth() - sideSpaceLeft - styleOptions.contentWidth - styleOptions.paddingRight - styleOptions.paddingLeft;
|
||
if (sideSpaceLeft === sideSpaceRight) {
|
||
return;
|
||
}
|
||
var margin = Math.max(sideSpaceLeft, sideSpaceRight);
|
||
if (minHeaderWidth + margin * 2 < this.getWindowWidth() - 1) {
|
||
element.style.marginLeft = element.style.marginRight = margin + 'px';
|
||
}
|
||
},
|
||
_initHeaderFormatting: function () {
|
||
if (navigator.mozL10n) {
|
||
navigator.mozL10n.once(function () {
|
||
this._registerHeadersInSubtree(document.body);
|
||
}.bind(this));
|
||
} else {
|
||
this._registerHeadersInSubtree(document.body);
|
||
}
|
||
},
|
||
init: function () {
|
||
window.addEventListener('lazyload', function (evt) {
|
||
this._registerHeadersInSubtree(evt.detail);
|
||
}.bind(this));
|
||
if (document.readyState === 'loading') {
|
||
window.addEventListener('DOMContentLoaded', function () {
|
||
this._initHeaderFormatting();
|
||
}.bind(this));
|
||
} else {
|
||
this._initHeaderFormatting();
|
||
}
|
||
},
|
||
getWindowWidth: function () {
|
||
return window.innerWidth;
|
||
}
|
||
};
|
||
FontSizeUtils.init();
|
||
exports.FontSizeUtils = FontSizeUtils;
|
||
}(this));
|
||
define('font_size_utils', function () {
|
||
});
|
||
define('metrics', [
|
||
'require',
|
||
'evt'
|
||
], function (require) {
|
||
var evt = require('evt'), apiDone = false, contentDone = false;
|
||
function checkAppLoaded() {
|
||
if (apiDone && contentDone) {
|
||
window.performance.mark('fullyLoaded');
|
||
}
|
||
}
|
||
evt.once('metrics:apiDone', function onApiDone() {
|
||
apiDone = true;
|
||
checkAppLoaded();
|
||
});
|
||
evt.once('metrics:contentDone', function () {
|
||
contentDone = true;
|
||
if (!window.startupCacheEventsSent) {
|
||
window.performance.mark('visuallyLoaded');
|
||
window.performance.mark('contentInteractive');
|
||
}
|
||
checkAppLoaded();
|
||
});
|
||
});
|
||
define('wake_locks', [
|
||
'require',
|
||
'evt'
|
||
], function (require) {
|
||
'use strict';
|
||
var lockTimeouts = {}, evt = require('evt'), allLocks = {}, dataOps = {}, dataOpsTimeoutId = 0, maxLockInterval = 45000, dataOpsTimeout = 5000;
|
||
function close() {
|
||
dataOps = {};
|
||
dataOpsTimeoutId = 0;
|
||
if (document.hidden) {
|
||
console.log('email: cronsync wake locks expired, force closing app');
|
||
window.close();
|
||
} else {
|
||
console.log('email: cronsync wake locks expired, but app visible, ' + 'not force closing');
|
||
Object.keys(allLocks).forEach(function (accountKey) {
|
||
clearLocks(accountKey);
|
||
});
|
||
}
|
||
}
|
||
function closeIfNoDataOps() {
|
||
var dataOpsKeys = Object.keys(dataOps);
|
||
if (!dataOpsKeys.length) {
|
||
return close();
|
||
}
|
||
console.log('email: cronsync wake lock force shutdown waiting on email ' + 'data operations: ' + dataOpsKeys.join(', '));
|
||
dataOpsTimeoutId = setTimeout(close, dataOpsTimeout);
|
||
}
|
||
evt.on('uiDataOperationStart', function (dataId) {
|
||
dataOps[dataId] = true;
|
||
});
|
||
evt.on('uiDataOperationStop', function (dataId) {
|
||
delete dataOps[dataId];
|
||
if (dataOpsTimeoutId && !Object.keys(dataOps).length) {
|
||
clearTimeout(dataOpsTimeoutId);
|
||
close();
|
||
}
|
||
});
|
||
function clearLocks(accountKey) {
|
||
console.log('email: clearing wake locks for "' + accountKey + '"');
|
||
var lockTimeoutId = lockTimeouts[accountKey];
|
||
if (lockTimeoutId) {
|
||
clearTimeout(lockTimeoutId);
|
||
}
|
||
lockTimeouts[accountKey] = 0;
|
||
var locks = allLocks[accountKey];
|
||
allLocks[accountKey] = null;
|
||
if (locks) {
|
||
locks.forEach(function (lock) {
|
||
lock.unlock();
|
||
});
|
||
}
|
||
}
|
||
function makeAccountKey(accountIds) {
|
||
return 'id' + accountIds.join(' ');
|
||
}
|
||
function onCronStop(accountIds) {
|
||
clearLocks(makeAccountKey(accountIds));
|
||
}
|
||
evt.on('cronSyncWakeLocks', function (accountKey, locks) {
|
||
if (lockTimeouts[accountKey]) {
|
||
clearLocks(accountKey);
|
||
}
|
||
allLocks[accountKey] = locks;
|
||
lockTimeouts[accountKey] = setTimeout(closeIfNoDataOps, maxLockInterval);
|
||
});
|
||
evt.on('cronSyncStop', onCronStop);
|
||
});
|
||
define('mail_app', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'l10n!',
|
||
'activity_composer_data',
|
||
'cards',
|
||
'evt',
|
||
'model_create',
|
||
'header_cursor',
|
||
'html_cache',
|
||
'font_size_utils',
|
||
'metrics',
|
||
'wake_locks'
|
||
], function (require, exports, module) {
|
||
var mozL10n = require('l10n!'), activityComposerData = require('activity_composer_data'), cards = require('cards'), evt = require('evt'), model = require('model_create').defaultModel, HeaderCursor = require('header_cursor'), htmlCache = require('html_cache'), waitingRawActivity, activityCallback;
|
||
require('font_size_utils');
|
||
require('metrics');
|
||
require('wake_locks');
|
||
var started = false;
|
||
function pushStartCard(id, addedArgs) {
|
||
var args = { model: model };
|
||
if (addedArgs) {
|
||
Object.keys(addedArgs).forEach(function (key) {
|
||
args[key] = addedArgs[key];
|
||
});
|
||
}
|
||
if (!started) {
|
||
var cachedNode = cards._cardsNode.children[0];
|
||
if (cachedNode && id === htmlCache.nodeToKey(cachedNode)) {
|
||
mozL10n.translateFragment(cachedNode);
|
||
args.cachedNode = cachedNode;
|
||
}
|
||
document.body.classList.add('content-visible');
|
||
}
|
||
cards.pushCard(id, 'immediate', args);
|
||
started = true;
|
||
}
|
||
document.addEventListener('visibilitychange', function onVisibilityChange() {
|
||
if (!document.hidden && !started && startupData && startupData.entry === 'alarm') {
|
||
pushStartCard('message_list');
|
||
}
|
||
}, false);
|
||
function isCurrentCardMessageList() {
|
||
var cardType = cards.getCurrentCardType();
|
||
return cardType && cardType === 'message_list';
|
||
}
|
||
evt.on('addAccount', function () {
|
||
cards.removeAllCards();
|
||
pushStartCard('setup_account_info', { allowBack: true });
|
||
});
|
||
function resetApp() {
|
||
activityCallback = waitingRawActivity = undefined;
|
||
cards.removeAllCards();
|
||
model.init(false, function () {
|
||
var cardId = model.hasAccount() ? 'message_list' : 'setup_account_info';
|
||
pushStartCard(cardId);
|
||
});
|
||
}
|
||
evt.on('accountDeleted', resetApp);
|
||
evt.on('resetApp', resetApp);
|
||
evt.on('setupAccountCanceled', function (fromCard) {
|
||
if (waitingRawActivity) {
|
||
waitingRawActivity.postError('cancelled');
|
||
}
|
||
if (!model.foldersSlice) {
|
||
evt.emit('resetApp');
|
||
} else {
|
||
cards.removeCardAndSuccessors(fromCard, 'animate', 1);
|
||
}
|
||
});
|
||
evt.on('showLatestAccount', function () {
|
||
cards.removeAllCards();
|
||
model.latestOnce('acctsSlice', function (acctsSlice) {
|
||
var account = acctsSlice.items[acctsSlice.items.length - 1];
|
||
model.changeAccount(account, function () {
|
||
pushStartCard('message_list', {
|
||
onPushed: function () {
|
||
if (activityCallback) {
|
||
var activityCb = activityCallback;
|
||
activityCallback = null;
|
||
activityCb();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
});
|
||
});
|
||
});
|
||
});
|
||
evt.on('apiBadLogin', function (account, problem, whichSide) {
|
||
switch (problem) {
|
||
case 'bad-user-or-pass':
|
||
cards.pushCard('setup_fix_password', 'animate', {
|
||
account: account,
|
||
whichSide: whichSide,
|
||
restoreCard: cards.activeCardIndex
|
||
}, 'right');
|
||
break;
|
||
case 'imap-disabled':
|
||
case 'pop3-disabled':
|
||
cards.pushCard('setup_fix_gmail', 'animate', {
|
||
account: account,
|
||
restoreCard: cards.activeCardIndex
|
||
}, 'right');
|
||
break;
|
||
case 'needs-app-pass':
|
||
cards.pushCard('setup_fix_gmail_twofactor', 'animate', {
|
||
account: account,
|
||
restoreCard: cards.activeCardIndex
|
||
}, 'right');
|
||
break;
|
||
case 'needs-oauth-reauth':
|
||
cards.pushCard('setup_fix_oauth2', 'animate', {
|
||
account: account,
|
||
restoreCard: cards.activeCardIndex
|
||
}, 'right');
|
||
break;
|
||
}
|
||
});
|
||
cards.init();
|
||
if (!model.inited) {
|
||
model.init();
|
||
}
|
||
var startupData = globalOnAppMessage({
|
||
activity: function (rawActivity) {
|
||
if (!isCurrentCardMessageList()) {
|
||
cards.removeAllCards();
|
||
}
|
||
function activityCompose() {
|
||
var cardArgs = {
|
||
activity: rawActivity,
|
||
composerData: activityComposerData(rawActivity)
|
||
};
|
||
pushStartCard('compose', cardArgs);
|
||
}
|
||
if (globalOnAppMessage.hasAccount()) {
|
||
activityCompose();
|
||
} else {
|
||
activityCallback = activityCompose;
|
||
waitingRawActivity = rawActivity;
|
||
pushStartCard('setup_account_info', {
|
||
allowBack: true,
|
||
launchedFromActivity: true
|
||
});
|
||
}
|
||
},
|
||
notification: function (data) {
|
||
data = data || {};
|
||
var type = data.type || '';
|
||
var folderType = data.folderType || 'inbox';
|
||
model.latestOnce('foldersSlice', function latestFolderSlice() {
|
||
function onCorrectFolder() {
|
||
if (!isCurrentCardMessageList()) {
|
||
cards.removeAllCards();
|
||
}
|
||
if (type === 'message_list') {
|
||
pushStartCard('message_list', {});
|
||
} else if (type === 'message_reader') {
|
||
var headerCursor = new HeaderCursor(model);
|
||
headerCursor.setCurrentMessageBySuid(data.messageSuid);
|
||
pushStartCard(type, {
|
||
messageSuid: data.messageSuid,
|
||
headerCursor: headerCursor
|
||
});
|
||
} else {
|
||
console.error('unhandled notification type: ' + type);
|
||
}
|
||
}
|
||
var acctsSlice = model.acctsSlice, accountId = data.accountId;
|
||
if (model.account.id === accountId) {
|
||
return model.selectFirstFolderWithType(folderType, onCorrectFolder);
|
||
} else {
|
||
var newAccount;
|
||
acctsSlice.items.some(function (account) {
|
||
if (account.id === accountId) {
|
||
newAccount = account;
|
||
return true;
|
||
}
|
||
});
|
||
if (newAccount) {
|
||
model.changeAccount(newAccount, function () {
|
||
model.selectFirstFolderWithType(folderType, onCorrectFolder);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
console.log('startupData: ' + JSON.stringify(startupData, null, ' '));
|
||
if (startupData.entry === 'default' || startupData.entry === 'alarm' && !document.hidden) {
|
||
pushStartCard(startupData.view);
|
||
}
|
||
});
|
||
if (typeof TestUrlResolver === 'undefined') {
|
||
requirejs.config({
|
||
waitSeconds: 0,
|
||
baseUrl: 'js',
|
||
paths: {
|
||
l10nbase: '../shared/js/l10n',
|
||
moz_intl: '../shared/js/moz_intl',
|
||
style: '../style',
|
||
shared: '../shared'
|
||
},
|
||
map: { '*': { 'api': 'ext/main-frame-setup' } },
|
||
shim: {
|
||
moz_intl: {
|
||
deps: ['l10nbase'],
|
||
exports: 'mozIntl'
|
||
},
|
||
'shared/js/mime_mapper': { exports: 'MimeMapper' },
|
||
'shared/js/notification_helper': { exports: 'NotificationHelper' },
|
||
'shared/js/accessibility_helper': { exports: 'AccessibilityHelper' },
|
||
'shared/js/gesture_detector': { exports: 'GestureDetector' }
|
||
},
|
||
config: {
|
||
template: {
|
||
tagToId: function (tag) {
|
||
return tag.replace(/^cards-/, 'cards/').replace(/^lst-/, 'cards/lst/').replace(/^msg-/, 'cards/msg/').replace(/^cmp-/, 'cards/cmp/').replace(/-/g, '_');
|
||
}
|
||
},
|
||
element: {
|
||
idToTag: function (id) {
|
||
return id.toLowerCase().replace(/^cards\/lst\//, 'lst-').replace(/^cards\/msg\//, 'msg-').replace(/^cards\/cmp\//, 'cmp-').replace(/[^a-z]/g, '-');
|
||
}
|
||
}
|
||
},
|
||
definePrim: 'prim'
|
||
});
|
||
}
|
||
if (navigator.mozAudioChannelManager) {
|
||
navigator.mozAudioChannelManager.volumeControlChannel = 'notification';
|
||
}
|
||
if (window.startupOnModelLoaded) {
|
||
requirejs([
|
||
'console_hook',
|
||
'model_create'
|
||
], function (hook, modelCreate) {
|
||
var model = modelCreate.defaultModel;
|
||
model.init();
|
||
window.startupOnModelLoaded(model, function () {
|
||
require(['mail_app']);
|
||
});
|
||
});
|
||
} else {
|
||
requirejs([
|
||
'console_hook',
|
||
'mail_app'
|
||
]);
|
||
}
|
||
;
|
||
define('config', function () {
|
||
});
|
||
(function (window, undefined) {
|
||
'use strict';
|
||
function L10nError(message, id, loc) {
|
||
this.name = 'L10nError';
|
||
this.message = message;
|
||
this.id = id;
|
||
this.loc = loc;
|
||
}
|
||
L10nError.prototype = Object.create(Error.prototype);
|
||
L10nError.prototype.constructor = L10nError;
|
||
var io = {
|
||
_load: function (type, url, callback, sync) {
|
||
var xhr = new XMLHttpRequest();
|
||
var needParse;
|
||
if (xhr.overrideMimeType) {
|
||
xhr.overrideMimeType(type);
|
||
}
|
||
xhr.open('GET', url, !sync);
|
||
if (type === 'application/json') {
|
||
if (sync) {
|
||
needParse = true;
|
||
} else {
|
||
xhr.responseType = 'json';
|
||
}
|
||
}
|
||
xhr.addEventListener('load', function io_onload(e) {
|
||
if (e.target.status === 200 || e.target.status === 0) {
|
||
var res = e.target.response || e.target.responseText;
|
||
callback(null, needParse ? JSON.parse(res) : res);
|
||
} else {
|
||
callback(new L10nError('Not found: ' + url));
|
||
}
|
||
});
|
||
xhr.addEventListener('error', callback);
|
||
xhr.addEventListener('timeout', callback);
|
||
try {
|
||
xhr.send(null);
|
||
} catch (e) {
|
||
if (e.name === 'NS_ERROR_FILE_NOT_FOUND') {
|
||
callback(new L10nError('Not found: ' + url));
|
||
} else {
|
||
throw e;
|
||
}
|
||
}
|
||
},
|
||
load: function (url, callback, sync) {
|
||
return io._load('text/plain', url, callback, sync);
|
||
},
|
||
loadJSON: function (url, callback, sync) {
|
||
return io._load('application/json', url, callback, sync);
|
||
}
|
||
};
|
||
function EventEmitter() {
|
||
}
|
||
EventEmitter.prototype.emit = function ee_emit() {
|
||
if (!this._listeners) {
|
||
return;
|
||
}
|
||
var args = Array.prototype.slice.call(arguments);
|
||
var type = args.shift();
|
||
if (!this._listeners[type]) {
|
||
return;
|
||
}
|
||
var typeListeners = this._listeners[type].slice();
|
||
for (var i = 0; i < typeListeners.length; i++) {
|
||
typeListeners[i].apply(this, args);
|
||
}
|
||
};
|
||
EventEmitter.prototype.addEventListener = function ee_add(type, listener) {
|
||
if (!this._listeners) {
|
||
this._listeners = {};
|
||
}
|
||
if (!(type in this._listeners)) {
|
||
this._listeners[type] = [];
|
||
}
|
||
this._listeners[type].push(listener);
|
||
};
|
||
EventEmitter.prototype.removeEventListener = function ee_rm(type, listener) {
|
||
if (!this._listeners) {
|
||
return;
|
||
}
|
||
var typeListeners = this._listeners[type];
|
||
var pos = typeListeners.indexOf(listener);
|
||
if (pos === -1) {
|
||
return;
|
||
}
|
||
typeListeners.splice(pos, 1);
|
||
};
|
||
function getPluralRule(lang) {
|
||
var locales2rules = {
|
||
'af': 3,
|
||
'ak': 4,
|
||
'am': 4,
|
||
'ar': 1,
|
||
'asa': 3,
|
||
'az': 0,
|
||
'be': 11,
|
||
'bem': 3,
|
||
'bez': 3,
|
||
'bg': 3,
|
||
'bh': 4,
|
||
'bm': 0,
|
||
'bn': 3,
|
||
'bo': 0,
|
||
'br': 20,
|
||
'brx': 3,
|
||
'bs': 11,
|
||
'ca': 3,
|
||
'cgg': 3,
|
||
'chr': 3,
|
||
'cs': 12,
|
||
'cy': 17,
|
||
'da': 3,
|
||
'de': 3,
|
||
'dv': 3,
|
||
'dz': 0,
|
||
'ee': 3,
|
||
'el': 3,
|
||
'en': 3,
|
||
'eo': 3,
|
||
'es': 3,
|
||
'et': 3,
|
||
'eu': 3,
|
||
'fa': 0,
|
||
'ff': 5,
|
||
'fi': 3,
|
||
'fil': 4,
|
||
'fo': 3,
|
||
'fr': 5,
|
||
'fur': 3,
|
||
'fy': 3,
|
||
'ga': 8,
|
||
'gd': 24,
|
||
'gl': 3,
|
||
'gsw': 3,
|
||
'gu': 3,
|
||
'guw': 4,
|
||
'gv': 23,
|
||
'ha': 3,
|
||
'haw': 3,
|
||
'he': 2,
|
||
'hi': 4,
|
||
'hr': 11,
|
||
'hu': 0,
|
||
'id': 0,
|
||
'ig': 0,
|
||
'ii': 0,
|
||
'is': 3,
|
||
'it': 3,
|
||
'iu': 7,
|
||
'ja': 0,
|
||
'jmc': 3,
|
||
'jv': 0,
|
||
'ka': 0,
|
||
'kab': 5,
|
||
'kaj': 3,
|
||
'kcg': 3,
|
||
'kde': 0,
|
||
'kea': 0,
|
||
'kk': 3,
|
||
'kl': 3,
|
||
'km': 0,
|
||
'kn': 0,
|
||
'ko': 0,
|
||
'ksb': 3,
|
||
'ksh': 21,
|
||
'ku': 3,
|
||
'kw': 7,
|
||
'lag': 18,
|
||
'lb': 3,
|
||
'lg': 3,
|
||
'ln': 4,
|
||
'lo': 0,
|
||
'lt': 10,
|
||
'lv': 6,
|
||
'mas': 3,
|
||
'mg': 4,
|
||
'mk': 16,
|
||
'ml': 3,
|
||
'mn': 3,
|
||
'mo': 9,
|
||
'mr': 3,
|
||
'ms': 0,
|
||
'mt': 15,
|
||
'my': 0,
|
||
'nah': 3,
|
||
'naq': 7,
|
||
'nb': 3,
|
||
'nd': 3,
|
||
'ne': 3,
|
||
'nl': 3,
|
||
'nn': 3,
|
||
'no': 3,
|
||
'nr': 3,
|
||
'nso': 4,
|
||
'ny': 3,
|
||
'nyn': 3,
|
||
'om': 3,
|
||
'or': 3,
|
||
'pa': 3,
|
||
'pap': 3,
|
||
'pl': 13,
|
||
'ps': 3,
|
||
'pt': 3,
|
||
'rm': 3,
|
||
'ro': 9,
|
||
'rof': 3,
|
||
'ru': 11,
|
||
'rwk': 3,
|
||
'sah': 0,
|
||
'saq': 3,
|
||
'se': 7,
|
||
'seh': 3,
|
||
'ses': 0,
|
||
'sg': 0,
|
||
'sh': 11,
|
||
'shi': 19,
|
||
'sk': 12,
|
||
'sl': 14,
|
||
'sma': 7,
|
||
'smi': 7,
|
||
'smj': 7,
|
||
'smn': 7,
|
||
'sms': 7,
|
||
'sn': 3,
|
||
'so': 3,
|
||
'sq': 3,
|
||
'sr': 11,
|
||
'ss': 3,
|
||
'ssy': 3,
|
||
'st': 3,
|
||
'sv': 3,
|
||
'sw': 3,
|
||
'syr': 3,
|
||
'ta': 3,
|
||
'te': 3,
|
||
'teo': 3,
|
||
'th': 0,
|
||
'ti': 4,
|
||
'tig': 3,
|
||
'tk': 3,
|
||
'tl': 4,
|
||
'tn': 3,
|
||
'to': 0,
|
||
'tr': 0,
|
||
'ts': 3,
|
||
'tzm': 22,
|
||
'uk': 11,
|
||
'ur': 3,
|
||
've': 3,
|
||
'vi': 0,
|
||
'vun': 3,
|
||
'wa': 4,
|
||
'wae': 3,
|
||
'wo': 0,
|
||
'xh': 3,
|
||
'xog': 3,
|
||
'yo': 0,
|
||
'zh': 0,
|
||
'zu': 3
|
||
};
|
||
function isIn(n, list) {
|
||
return list.indexOf(n) !== -1;
|
||
}
|
||
function isBetween(n, start, end) {
|
||
return typeof n === typeof start && start <= n && n <= end;
|
||
}
|
||
var pluralRules = {
|
||
'0': function () {
|
||
return 'other';
|
||
},
|
||
'1': function (n) {
|
||
if (isBetween(n % 100, 3, 10)) {
|
||
return 'few';
|
||
}
|
||
if (n === 0) {
|
||
return 'zero';
|
||
}
|
||
if (isBetween(n % 100, 11, 99)) {
|
||
return 'many';
|
||
}
|
||
if (n === 2) {
|
||
return 'two';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'2': function (n) {
|
||
if (n !== 0 && n % 10 === 0) {
|
||
return 'many';
|
||
}
|
||
if (n === 2) {
|
||
return 'two';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'3': function (n) {
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'4': function (n) {
|
||
if (isBetween(n, 0, 1)) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'5': function (n) {
|
||
if (isBetween(n, 0, 2) && n !== 2) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'6': function (n) {
|
||
if (n === 0) {
|
||
return 'zero';
|
||
}
|
||
if (n % 10 === 1 && n % 100 !== 11) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'7': function (n) {
|
||
if (n === 2) {
|
||
return 'two';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'8': function (n) {
|
||
if (isBetween(n, 3, 6)) {
|
||
return 'few';
|
||
}
|
||
if (isBetween(n, 7, 10)) {
|
||
return 'many';
|
||
}
|
||
if (n === 2) {
|
||
return 'two';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'9': function (n) {
|
||
if (n === 0 || n !== 1 && isBetween(n % 100, 1, 19)) {
|
||
return 'few';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'10': function (n) {
|
||
if (isBetween(n % 10, 2, 9) && !isBetween(n % 100, 11, 19)) {
|
||
return 'few';
|
||
}
|
||
if (n % 10 === 1 && !isBetween(n % 100, 11, 19)) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'11': function (n) {
|
||
if (isBetween(n % 10, 2, 4) && !isBetween(n % 100, 12, 14)) {
|
||
return 'few';
|
||
}
|
||
if (n % 10 === 0 || isBetween(n % 10, 5, 9) || isBetween(n % 100, 11, 14)) {
|
||
return 'many';
|
||
}
|
||
if (n % 10 === 1 && n % 100 !== 11) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'12': function (n) {
|
||
if (isBetween(n, 2, 4)) {
|
||
return 'few';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'13': function (n) {
|
||
if (isBetween(n % 10, 2, 4) && !isBetween(n % 100, 12, 14)) {
|
||
return 'few';
|
||
}
|
||
if (n !== 1 && isBetween(n % 10, 0, 1) || isBetween(n % 10, 5, 9) || isBetween(n % 100, 12, 14)) {
|
||
return 'many';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'14': function (n) {
|
||
if (isBetween(n % 100, 3, 4)) {
|
||
return 'few';
|
||
}
|
||
if (n % 100 === 2) {
|
||
return 'two';
|
||
}
|
||
if (n % 100 === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'15': function (n) {
|
||
if (n === 0 || isBetween(n % 100, 2, 10)) {
|
||
return 'few';
|
||
}
|
||
if (isBetween(n % 100, 11, 19)) {
|
||
return 'many';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'16': function (n) {
|
||
if (n % 10 === 1 && n !== 11) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'17': function (n) {
|
||
if (n === 3) {
|
||
return 'few';
|
||
}
|
||
if (n === 0) {
|
||
return 'zero';
|
||
}
|
||
if (n === 6) {
|
||
return 'many';
|
||
}
|
||
if (n === 2) {
|
||
return 'two';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'18': function (n) {
|
||
if (n === 0) {
|
||
return 'zero';
|
||
}
|
||
if (isBetween(n, 0, 2) && n !== 0 && n !== 2) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'19': function (n) {
|
||
if (isBetween(n, 2, 10)) {
|
||
return 'few';
|
||
}
|
||
if (isBetween(n, 0, 1)) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'20': function (n) {
|
||
if ((isBetween(n % 10, 3, 4) || n % 10 === 9) && !(isBetween(n % 100, 10, 19) || isBetween(n % 100, 70, 79) || isBetween(n % 100, 90, 99))) {
|
||
return 'few';
|
||
}
|
||
if (n % 1000000 === 0 && n !== 0) {
|
||
return 'many';
|
||
}
|
||
if (n % 10 === 2 && !isIn(n % 100, [
|
||
12,
|
||
72,
|
||
92
|
||
])) {
|
||
return 'two';
|
||
}
|
||
if (n % 10 === 1 && !isIn(n % 100, [
|
||
11,
|
||
71,
|
||
91
|
||
])) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'21': function (n) {
|
||
if (n === 0) {
|
||
return 'zero';
|
||
}
|
||
if (n === 1) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'22': function (n) {
|
||
if (isBetween(n, 0, 1) || isBetween(n, 11, 99)) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'23': function (n) {
|
||
if (isBetween(n % 10, 1, 2) || n % 20 === 0) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
},
|
||
'24': function (n) {
|
||
if (isBetween(n, 3, 10) || isBetween(n, 13, 19)) {
|
||
return 'few';
|
||
}
|
||
if (isIn(n, [
|
||
2,
|
||
12
|
||
])) {
|
||
return 'two';
|
||
}
|
||
if (isIn(n, [
|
||
1,
|
||
11
|
||
])) {
|
||
return 'one';
|
||
}
|
||
return 'other';
|
||
}
|
||
};
|
||
var index = locales2rules[lang.replace(/-.*$/, '')];
|
||
if (!(index in pluralRules)) {
|
||
return function () {
|
||
return 'other';
|
||
};
|
||
}
|
||
return pluralRules[index];
|
||
}
|
||
var MAX_PLACEABLES = 100;
|
||
var PropertiesParser = {
|
||
patterns: null,
|
||
entryIds: null,
|
||
init: function () {
|
||
this.patterns = {
|
||
comment: /^\s*#|^\s*$/,
|
||
entity: /^([^=\s]+)\s*=\s*(.*)$/,
|
||
multiline: /[^\\]\\$/,
|
||
index: /\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,
|
||
unicode: /\\u([0-9a-fA-F]{1,4})/g,
|
||
entries: /[^\r\n]+/g,
|
||
controlChars: /\\([\\\n\r\t\b\f\{\}\"\'])/g,
|
||
placeables: /\{\{\s*([^\s]*?)\s*\}\}/
|
||
};
|
||
},
|
||
parse: function (ctx, source) {
|
||
if (!this.patterns) {
|
||
this.init();
|
||
}
|
||
var ast = [];
|
||
this.entryIds = Object.create(null);
|
||
var entries = source.match(this.patterns.entries);
|
||
if (!entries) {
|
||
return ast;
|
||
}
|
||
for (var i = 0; i < entries.length; i++) {
|
||
var line = entries[i];
|
||
if (this.patterns.comment.test(line)) {
|
||
continue;
|
||
}
|
||
while (this.patterns.multiline.test(line) && i < entries.length) {
|
||
line = line.slice(0, -1) + entries[++i].trim();
|
||
}
|
||
var entityMatch = line.match(this.patterns.entity);
|
||
if (entityMatch) {
|
||
try {
|
||
this.parseEntity(entityMatch[1], entityMatch[2], ast);
|
||
} catch (e) {
|
||
if (ctx) {
|
||
ctx._emitter.emit('parseerror', e);
|
||
} else {
|
||
throw e;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return ast;
|
||
},
|
||
parseEntity: function (id, value, ast) {
|
||
var name, key;
|
||
var pos = id.indexOf('[');
|
||
if (pos !== -1) {
|
||
name = id.substr(0, pos);
|
||
key = id.substring(pos + 1, id.length - 1);
|
||
} else {
|
||
name = id;
|
||
key = null;
|
||
}
|
||
var nameElements = name.split('.');
|
||
if (nameElements.length > 2) {
|
||
throw new L10nError('Error in ID: "' + name + '".' + ' Nested attributes are not supported.');
|
||
}
|
||
var attr;
|
||
if (nameElements.length > 1) {
|
||
name = nameElements[0];
|
||
attr = nameElements[1];
|
||
if (attr[0] === '$') {
|
||
throw new L10nError('Attribute can\'t start with "$"', id);
|
||
}
|
||
} else {
|
||
attr = null;
|
||
}
|
||
this.setEntityValue(name, attr, key, this.unescapeString(value), ast);
|
||
},
|
||
setEntityValue: function (id, attr, key, rawValue, ast) {
|
||
var pos, v;
|
||
var value = rawValue.indexOf('{{') > -1 ? this.parseString(rawValue) : rawValue;
|
||
if (attr) {
|
||
pos = this.entryIds[id];
|
||
if (pos === undefined) {
|
||
v = { $i: id };
|
||
if (key) {
|
||
v[attr] = {};
|
||
v[attr][key] = value;
|
||
} else {
|
||
v[attr] = value;
|
||
}
|
||
ast.push(v);
|
||
this.entryIds[id] = ast.length - 1;
|
||
return;
|
||
}
|
||
if (key) {
|
||
if (typeof ast[pos][attr] === 'string') {
|
||
ast[pos][attr] = {
|
||
$x: this.parseIndex(ast[pos][attr]),
|
||
$v: {}
|
||
};
|
||
}
|
||
ast[pos][attr].$v[key] = value;
|
||
return;
|
||
}
|
||
ast[pos][attr] = value;
|
||
return;
|
||
}
|
||
if (key) {
|
||
pos = this.entryIds[id];
|
||
if (pos === undefined) {
|
||
v = {};
|
||
v[key] = value;
|
||
ast.push({
|
||
$i: id,
|
||
$v: v
|
||
});
|
||
this.entryIds[id] = ast.length - 1;
|
||
return;
|
||
}
|
||
if (typeof ast[pos].$v === 'string') {
|
||
ast[pos].$x = this.parseIndex(ast[pos].$v);
|
||
ast[pos].$v = {};
|
||
}
|
||
ast[pos].$v[key] = value;
|
||
return;
|
||
}
|
||
ast.push({
|
||
$i: id,
|
||
$v: value
|
||
});
|
||
this.entryIds[id] = ast.length - 1;
|
||
},
|
||
parseString: function (str) {
|
||
var chunks = str.split(this.patterns.placeables);
|
||
var complexStr = [];
|
||
var len = chunks.length;
|
||
var placeablesCount = (len - 1) / 2;
|
||
if (placeablesCount >= MAX_PLACEABLES) {
|
||
throw new L10nError('Too many placeables (' + placeablesCount + ', max allowed is ' + MAX_PLACEABLES + ')');
|
||
}
|
||
for (var i = 0; i < chunks.length; i++) {
|
||
if (chunks[i].length === 0) {
|
||
continue;
|
||
}
|
||
if (i % 2 === 1) {
|
||
complexStr.push({
|
||
t: 'idOrVar',
|
||
v: chunks[i]
|
||
});
|
||
} else {
|
||
complexStr.push(chunks[i]);
|
||
}
|
||
}
|
||
return complexStr;
|
||
},
|
||
unescapeString: function (str) {
|
||
if (str.lastIndexOf('\\') !== -1) {
|
||
str = str.replace(this.patterns.controlChars, '$1');
|
||
}
|
||
return str.replace(this.patterns.unicode, function (match, token) {
|
||
return unescape('%u' + '0000'.slice(token.length) + token);
|
||
});
|
||
},
|
||
parseIndex: function (str) {
|
||
var match = str.match(this.patterns.index);
|
||
if (!match) {
|
||
throw new L10nError('Malformed index');
|
||
}
|
||
if (match[2]) {
|
||
return [
|
||
{
|
||
t: 'idOrVar',
|
||
v: match[1]
|
||
},
|
||
match[2]
|
||
];
|
||
} else {
|
||
return [{
|
||
t: 'idOrVar',
|
||
v: match[1]
|
||
}];
|
||
}
|
||
}
|
||
};
|
||
var KNOWN_MACROS = ['plural'];
|
||
var MAX_PLACEABLE_LENGTH = 2500;
|
||
var rePlaceables = /\{\{\s*(.+?)\s*\}\}/g;
|
||
var nonLatin1 = /[^\x01-\xFF]/;
|
||
var FSI = '\u2068';
|
||
var PDI = '\u2069';
|
||
function createEntry(node, env) {
|
||
var keys = Object.keys(node);
|
||
if (typeof node.$v === 'string' && keys.length === 2) {
|
||
return node.$v;
|
||
}
|
||
var attrs;
|
||
for (var i = 0, key; key = keys[i]; i++) {
|
||
if (key[0] === '$') {
|
||
continue;
|
||
}
|
||
if (!attrs) {
|
||
attrs = Object.create(null);
|
||
}
|
||
attrs[key] = createAttribute(node[key], env, node.$i + '.' + key);
|
||
}
|
||
return {
|
||
id: node.$i,
|
||
value: node.$v !== undefined ? node.$v : null,
|
||
index: node.$x || null,
|
||
attrs: attrs || null,
|
||
env: env,
|
||
dirty: false
|
||
};
|
||
}
|
||
function createAttribute(node, env, id) {
|
||
if (typeof node === 'string') {
|
||
return node;
|
||
}
|
||
return {
|
||
id: id,
|
||
value: node.$v || (node !== undefined ? node : null),
|
||
index: node.$x || null,
|
||
env: env,
|
||
dirty: false
|
||
};
|
||
}
|
||
function format(args, entity) {
|
||
if (typeof entity === 'string') {
|
||
return [
|
||
{},
|
||
entity
|
||
];
|
||
}
|
||
if (entity.dirty) {
|
||
throw new L10nError('Cyclic reference detected: ' + entity.id);
|
||
}
|
||
entity.dirty = true;
|
||
var rv;
|
||
try {
|
||
rv = resolveValue({}, args, entity.env, entity.value, entity.index);
|
||
} finally {
|
||
entity.dirty = false;
|
||
}
|
||
return rv;
|
||
}
|
||
function resolveIdentifier(args, env, id) {
|
||
if (KNOWN_MACROS.indexOf(id) > -1) {
|
||
return [
|
||
{},
|
||
env['__' + id]
|
||
];
|
||
}
|
||
if (args && args.hasOwnProperty(id)) {
|
||
if (typeof args[id] === 'string' || typeof args[id] === 'number' && !isNaN(args[id])) {
|
||
return [
|
||
{},
|
||
args[id]
|
||
];
|
||
} else {
|
||
throw new L10nError('Arg must be a string or a number: ' + id);
|
||
}
|
||
}
|
||
if (id in env && id !== '__proto__') {
|
||
return format(args, env[id]);
|
||
}
|
||
throw new L10nError('Unknown reference: ' + id);
|
||
}
|
||
function subPlaceable(locals, args, env, id) {
|
||
var res;
|
||
try {
|
||
res = resolveIdentifier(args, env, id);
|
||
} catch (err) {
|
||
return [
|
||
{ error: err },
|
||
'{{ ' + id + ' }}'
|
||
];
|
||
}
|
||
var value = res[1];
|
||
if (typeof value === 'number') {
|
||
return res;
|
||
}
|
||
if (typeof value === 'string') {
|
||
if (value.length >= MAX_PLACEABLE_LENGTH) {
|
||
throw new L10nError('Too many characters in placeable (' + value.length + ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')');
|
||
}
|
||
if (locals.contextIsNonLatin1 || value.match(nonLatin1)) {
|
||
res[1] = FSI + value + PDI;
|
||
}
|
||
return res;
|
||
}
|
||
return [
|
||
{},
|
||
'{{ ' + id + ' }}'
|
||
];
|
||
}
|
||
function interpolate(locals, args, env, arr) {
|
||
return arr.reduce(function (prev, cur) {
|
||
if (typeof cur === 'string') {
|
||
return [
|
||
prev[0],
|
||
prev[1] + cur
|
||
];
|
||
} else if (cur.t === 'idOrVar') {
|
||
var placeable = subPlaceable(locals, args, env, cur.v);
|
||
return [
|
||
prev[0],
|
||
prev[1] + placeable[1]
|
||
];
|
||
}
|
||
}, [
|
||
locals,
|
||
''
|
||
]);
|
||
}
|
||
function resolveSelector(args, env, expr, index) {
|
||
var selectorName = index[0].v;
|
||
var selector = resolveIdentifier(args, env, selectorName)[1];
|
||
if (typeof selector !== 'function') {
|
||
return selector;
|
||
}
|
||
var argValue = index[1] ? resolveIdentifier(args, env, index[1])[1] : undefined;
|
||
if (selector === env.__plural) {
|
||
if (argValue === 0 && 'zero' in expr) {
|
||
return 'zero';
|
||
}
|
||
if (argValue === 1 && 'one' in expr) {
|
||
return 'one';
|
||
}
|
||
if (argValue === 2 && 'two' in expr) {
|
||
return 'two';
|
||
}
|
||
}
|
||
return selector(argValue);
|
||
}
|
||
function resolveValue(locals, args, env, expr, index) {
|
||
if (!expr) {
|
||
return [
|
||
locals,
|
||
expr
|
||
];
|
||
}
|
||
if (typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') {
|
||
return [
|
||
locals,
|
||
expr
|
||
];
|
||
}
|
||
if (Array.isArray(expr)) {
|
||
locals.contextIsNonLatin1 = expr.some(function ($_) {
|
||
return typeof $_ === 'string' && $_.match(nonLatin1);
|
||
});
|
||
return interpolate(locals, args, env, expr);
|
||
}
|
||
if (index) {
|
||
var selector = resolveSelector(args, env, expr, index);
|
||
if (expr.hasOwnProperty(selector)) {
|
||
return resolveValue(locals, args, env, expr[selector]);
|
||
}
|
||
}
|
||
if ('other' in expr) {
|
||
return resolveValue(locals, args, env, expr.other);
|
||
}
|
||
throw new L10nError('Unresolvable value');
|
||
}
|
||
var Resolver = {
|
||
createEntry: createEntry,
|
||
format: format,
|
||
rePlaceables: rePlaceables
|
||
};
|
||
function walkContent(node, fn) {
|
||
if (typeof node === 'string') {
|
||
return fn(node);
|
||
}
|
||
if (node.t === 'idOrVar') {
|
||
return node;
|
||
}
|
||
var rv = Array.isArray(node) ? [] : {};
|
||
var keys = Object.keys(node);
|
||
for (var i = 0, key; key = keys[i]; i++) {
|
||
if (key === '$i' || key === '$x') {
|
||
rv[key] = node[key];
|
||
} else {
|
||
rv[key] = walkContent(node[key], fn);
|
||
}
|
||
}
|
||
return rv;
|
||
}
|
||
var reAlphas = /[a-zA-Z]/g;
|
||
var reVowels = /[aeiouAEIOU]/g;
|
||
var ACCENTED_MAP = 'ȦƁƇḒḖƑƓĦĪ' + 'ĴĶĿḾȠǾƤɊŘ' + 'ŞŦŬṼẆẊẎẐ' + '[\\]^_`' + 'ȧƀƈḓḗƒɠħī' + 'ĵķŀḿƞǿƥɋř' + 'şŧŭṽẇẋẏẑ';
|
||
var FLIPPED_MAP = '\u2200ԐↃpƎɟפHIſ' + 'Ӽ\u02E5WNOԀÒᴚS\u22A5\u2229Ʌ' + 'MXʎZ' + '[\\]ᵥ_,' + 'ɐqɔpǝɟƃɥıɾ' + 'ʞʅɯuodbɹsʇnʌʍxʎz';
|
||
function makeLonger(val) {
|
||
return val.replace(reVowels, function (match) {
|
||
return match + match.toLowerCase();
|
||
});
|
||
}
|
||
function replaceChars(map, val) {
|
||
return val.replace(reAlphas, function (match) {
|
||
return map.charAt(match.charCodeAt(0) - 65);
|
||
});
|
||
}
|
||
var reWords = /[^\W0-9_]+/g;
|
||
function makeRTL(val) {
|
||
return val.replace(reWords, function (match) {
|
||
return '\u202E' + match + '\u202C';
|
||
});
|
||
}
|
||
var reExcluded = /(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;
|
||
function mapContent(fn, val) {
|
||
if (!val) {
|
||
return val;
|
||
}
|
||
var parts = val.split(reExcluded);
|
||
var modified = parts.map(function (part) {
|
||
if (reExcluded.test(part)) {
|
||
return part;
|
||
}
|
||
return fn(part);
|
||
});
|
||
return modified.join('');
|
||
}
|
||
function Pseudo(id, name, charMap, modFn) {
|
||
this.id = id;
|
||
this.translate = mapContent.bind(null, function (val) {
|
||
return replaceChars(charMap, modFn(val));
|
||
});
|
||
this.name = this.translate(name);
|
||
}
|
||
var PSEUDO = {
|
||
'fr-x-psaccent': new Pseudo('fr-x-psaccent', 'Runtime Accented', ACCENTED_MAP, makeLonger),
|
||
'ar-x-psbidi': new Pseudo('ar-x-psbidi', 'Runtime Bidi', FLIPPED_MAP, makeRTL)
|
||
};
|
||
function Locale(id, ctx) {
|
||
this.id = id;
|
||
this.ctx = ctx;
|
||
this.isReady = false;
|
||
this.entries = Object.create(null);
|
||
this.entries.__plural = getPluralRule(this.isPseudo() ? this.ctx.defaultLocale : id);
|
||
}
|
||
Locale.prototype.isPseudo = function () {
|
||
return this.ctx.qps.indexOf(this.id) !== -1;
|
||
};
|
||
var bindingsIO = {
|
||
extra: function (id, ver, path, type, callback, errback) {
|
||
if (type === 'properties') {
|
||
type = 'text';
|
||
}
|
||
navigator.mozApps.getLocalizationResource(id, ver, path, type).then(callback.bind(null, null), errback);
|
||
},
|
||
app: function (id, ver, path, type, callback, errback, sync) {
|
||
switch (type) {
|
||
case 'properties':
|
||
io.load(path, callback, sync);
|
||
break;
|
||
case 'json':
|
||
io.loadJSON(path, callback, sync);
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
Locale.prototype.build = function L_build(callback) {
|
||
var sync = !callback;
|
||
var ctx = this.ctx;
|
||
var self = this;
|
||
var l10nLoads = ctx.resLinks.length;
|
||
function onL10nLoaded(err) {
|
||
if (err) {
|
||
ctx._emitter.emit('fetcherror', err);
|
||
}
|
||
if (--l10nLoads <= 0) {
|
||
self.isReady = true;
|
||
if (callback) {
|
||
callback();
|
||
}
|
||
}
|
||
}
|
||
if (l10nLoads === 0) {
|
||
onL10nLoaded();
|
||
return;
|
||
}
|
||
function onJSONLoaded(err, json) {
|
||
if (!err && json) {
|
||
self.addAST(json);
|
||
}
|
||
onL10nLoaded(err);
|
||
}
|
||
function onPropLoaded(err, source) {
|
||
if (!err && source) {
|
||
var ast = PropertiesParser.parse(ctx, source);
|
||
self.addAST(ast);
|
||
}
|
||
onL10nLoaded(err);
|
||
}
|
||
var idToFetch = this.isPseudo() ? ctx.defaultLocale : this.id;
|
||
var appVersion = null;
|
||
var source = 'app';
|
||
if (typeof navigator !== 'undefined') {
|
||
source = navigator.mozL10n._config.localeSources[this.id] || 'app';
|
||
appVersion = navigator.mozL10n._config.appVersion;
|
||
}
|
||
for (var i = 0; i < ctx.resLinks.length; i++) {
|
||
var resLink = decodeURI(ctx.resLinks[i]);
|
||
var path = resLink.replace('{locale}', idToFetch);
|
||
var type = path.substr(path.lastIndexOf('.') + 1);
|
||
var cb;
|
||
switch (type) {
|
||
case 'json':
|
||
cb = onJSONLoaded;
|
||
break;
|
||
case 'properties':
|
||
cb = onPropLoaded;
|
||
break;
|
||
}
|
||
bindingsIO[source](this.id, appVersion, path, type, cb, onL10nLoaded, sync);
|
||
}
|
||
};
|
||
function createPseudoEntry(node, entries) {
|
||
return Resolver.createEntry(walkContent(node, PSEUDO[this.id].translate), entries);
|
||
}
|
||
Locale.prototype.addAST = function (ast) {
|
||
var createEntry = this.isPseudo() ? createPseudoEntry.bind(this) : Resolver.createEntry;
|
||
for (var i = 0; i < ast.length; i++) {
|
||
this.entries[ast[i].$i] = createEntry(ast[i], this.entries);
|
||
}
|
||
};
|
||
function Context(id) {
|
||
this.id = id;
|
||
this.isReady = false;
|
||
this.isLoading = false;
|
||
this.defaultLocale = 'en-US';
|
||
this.availableLocales = [];
|
||
this.supportedLocales = [];
|
||
this.qps = [];
|
||
this.resLinks = [];
|
||
this.locales = {};
|
||
this._emitter = new EventEmitter();
|
||
this._ready = new Promise(this.once.bind(this));
|
||
}
|
||
function reportMissing(id, err) {
|
||
this._emitter.emit('notfounderror', err);
|
||
return id;
|
||
}
|
||
function getWithFallback(id) {
|
||
var cur = 0;
|
||
var loc;
|
||
var locale;
|
||
while (loc = this.supportedLocales[cur]) {
|
||
locale = this.getLocale(loc);
|
||
if (!locale.isReady) {
|
||
locale.build(null);
|
||
}
|
||
var entry = locale.entries[id];
|
||
if (entry === undefined) {
|
||
cur++;
|
||
reportMissing.call(this, id, new L10nError('"' + id + '"' + ' not found in ' + loc + ' in ' + this.id, id, loc));
|
||
continue;
|
||
}
|
||
return entry;
|
||
}
|
||
throw new L10nError('"' + id + '"' + ' missing from all supported locales in ' + this.id, id);
|
||
}
|
||
function formatTuple(args, entity) {
|
||
try {
|
||
return Resolver.format(args, entity);
|
||
} catch (err) {
|
||
this._emitter.emit('resolveerror', err);
|
||
var locals = { error: err };
|
||
return [
|
||
locals,
|
||
entity.id
|
||
];
|
||
}
|
||
}
|
||
function formatValue(args, entity) {
|
||
if (typeof entity === 'string') {
|
||
return entity;
|
||
}
|
||
return formatTuple.call(this, args, entity)[1];
|
||
}
|
||
function formatEntity(args, entity) {
|
||
var entityTuple = formatTuple.call(this, args, entity);
|
||
var value = entityTuple[1];
|
||
var formatted = {
|
||
value: value,
|
||
attrs: null
|
||
};
|
||
if (entity.attrs) {
|
||
formatted.attrs = Object.create(null);
|
||
}
|
||
for (var key in entity.attrs) {
|
||
var attrTuple = formatTuple.call(this, args, entity.attrs[key]);
|
||
formatted.attrs[key] = attrTuple[1];
|
||
}
|
||
return formatted;
|
||
}
|
||
function formatAsync(fn, id, args) {
|
||
return this._ready.then(getWithFallback.bind(this, id)).then(fn.bind(this, args), reportMissing.bind(this, id));
|
||
}
|
||
Context.prototype.formatValue = function (id, args) {
|
||
return formatAsync.call(this, formatValue, id, args);
|
||
};
|
||
Context.prototype.formatEntity = function (id, args) {
|
||
return formatAsync.call(this, formatEntity, id, args);
|
||
};
|
||
function legacyGet(fn, id, args) {
|
||
if (!this.isReady) {
|
||
throw new L10nError('Context not ready');
|
||
}
|
||
var entry;
|
||
try {
|
||
entry = getWithFallback.call(this, id);
|
||
} catch (err) {
|
||
if (err.loc) {
|
||
throw err;
|
||
}
|
||
reportMissing.call(this, id, err);
|
||
return '';
|
||
}
|
||
return fn.call(this, args, entry);
|
||
}
|
||
Context.prototype.get = function (id, args) {
|
||
return legacyGet.call(this, formatValue, id, args);
|
||
};
|
||
Context.prototype.getEntity = function (id, args) {
|
||
return legacyGet.call(this, formatEntity, id, args);
|
||
};
|
||
Context.prototype.getLocale = function getLocale(code) {
|
||
var locales = this.locales;
|
||
if (locales[code]) {
|
||
return locales[code];
|
||
}
|
||
return locales[code] = new Locale(code, this);
|
||
};
|
||
function negotiate(available, requested, defaultLocale) {
|
||
var supportedLocale;
|
||
for (var i = 0; i < requested.length; i++) {
|
||
var locale = requested[i];
|
||
if (available.indexOf(locale) !== -1) {
|
||
supportedLocale = locale;
|
||
break;
|
||
}
|
||
}
|
||
if (!supportedLocale || supportedLocale === defaultLocale) {
|
||
return [defaultLocale];
|
||
}
|
||
return [
|
||
supportedLocale,
|
||
defaultLocale
|
||
];
|
||
}
|
||
function freeze(supported) {
|
||
var locale = this.getLocale(supported[0]);
|
||
if (locale.isReady) {
|
||
setReady.call(this, supported);
|
||
} else {
|
||
locale.build(setReady.bind(this, supported));
|
||
}
|
||
}
|
||
function setReady(supported) {
|
||
this.supportedLocales = supported;
|
||
this.isReady = true;
|
||
this._emitter.emit('ready');
|
||
}
|
||
Context.prototype.registerLocales = function (defLocale, available) {
|
||
if (defLocale) {
|
||
this.defaultLocale = defLocale;
|
||
}
|
||
this.availableLocales = [this.defaultLocale];
|
||
this.qps = Object.keys(PSEUDO);
|
||
if (available) {
|
||
for (var i = 0, loc; loc = available[i]; i++) {
|
||
if (this.availableLocales.indexOf(loc) === -1) {
|
||
this.availableLocales.push(loc);
|
||
var pos = this.qps.indexOf(loc);
|
||
if (pos !== -1) {
|
||
this.qps.splice(pos, 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
Context.prototype.requestLocales = function requestLocales() {
|
||
if (this.isLoading && !this.isReady) {
|
||
throw new L10nError('Context not ready');
|
||
}
|
||
this.isLoading = true;
|
||
var requested = Array.prototype.slice.call(arguments);
|
||
if (requested.length === 0) {
|
||
throw new L10nError('No locales requested');
|
||
}
|
||
var supported = negotiate(this.availableLocales.concat(this.qps), requested, this.defaultLocale);
|
||
if (this.supportedLocales[0] !== supported[0]) {
|
||
freeze.call(this, supported);
|
||
}
|
||
};
|
||
Context.prototype.addEventListener = function (type, listener) {
|
||
this._emitter.addEventListener(type, listener);
|
||
};
|
||
Context.prototype.removeEventListener = function (type, listener) {
|
||
this._emitter.removeEventListener(type, listener);
|
||
};
|
||
Context.prototype.ready = function (callback) {
|
||
if (this.isReady) {
|
||
setTimeout(callback);
|
||
}
|
||
this.addEventListener('ready', callback);
|
||
};
|
||
Context.prototype.once = function (callback) {
|
||
if (this.isReady) {
|
||
setTimeout(callback);
|
||
return;
|
||
}
|
||
var callAndRemove = function () {
|
||
this.removeEventListener('ready', callAndRemove);
|
||
callback();
|
||
}.bind(this);
|
||
this.addEventListener('ready', callAndRemove);
|
||
};
|
||
var allowed = {
|
||
elements: [
|
||
'a',
|
||
'em',
|
||
'strong',
|
||
'small',
|
||
's',
|
||
'cite',
|
||
'q',
|
||
'dfn',
|
||
'abbr',
|
||
'data',
|
||
'time',
|
||
'code',
|
||
'var',
|
||
'samp',
|
||
'kbd',
|
||
'sub',
|
||
'sup',
|
||
'i',
|
||
'b',
|
||
'u',
|
||
'mark',
|
||
'ruby',
|
||
'rt',
|
||
'rp',
|
||
'bdi',
|
||
'bdo',
|
||
'span',
|
||
'br',
|
||
'wbr'
|
||
],
|
||
attributes: {
|
||
global: [
|
||
'title',
|
||
'aria-label',
|
||
'aria-valuetext',
|
||
'aria-moz-hint'
|
||
],
|
||
a: ['download'],
|
||
area: [
|
||
'download',
|
||
'alt'
|
||
],
|
||
input: [
|
||
'alt',
|
||
'placeholder'
|
||
],
|
||
menuitem: ['label'],
|
||
menu: ['label'],
|
||
optgroup: ['label'],
|
||
option: ['label'],
|
||
track: ['label'],
|
||
img: ['alt'],
|
||
textarea: ['placeholder'],
|
||
th: ['abbr']
|
||
}
|
||
};
|
||
var rtlList = [
|
||
'ar',
|
||
'he',
|
||
'fa',
|
||
'ps',
|
||
'ar-x-psbidi',
|
||
'ur'
|
||
];
|
||
var nodeObserver = null;
|
||
var pendingElements = null;
|
||
var moConfig = {
|
||
attributes: true,
|
||
characterData: false,
|
||
childList: true,
|
||
subtree: true,
|
||
attributeFilter: [
|
||
'data-l10n-id',
|
||
'data-l10n-args'
|
||
]
|
||
};
|
||
navigator.mozL10n = {
|
||
ctx: null,
|
||
get: function get(id, ctxdata) {
|
||
return navigator.mozL10n.ctx.get(id, ctxdata);
|
||
},
|
||
formatValue: function (id, ctxdata) {
|
||
return navigator.mozL10n.ctx.formatValue(id, ctxdata);
|
||
},
|
||
formatEntity: function (id, ctxdata) {
|
||
return navigator.mozL10n.ctx.formatEntity(id, ctxdata);
|
||
},
|
||
translateFragment: function (fragment) {
|
||
return translateFragment.call(navigator.mozL10n, fragment);
|
||
},
|
||
setAttributes: setL10nAttributes,
|
||
getAttributes: getL10nAttributes,
|
||
ready: function ready(callback) {
|
||
return navigator.mozL10n.ctx.ready(callback);
|
||
},
|
||
once: function once(callback) {
|
||
return navigator.mozL10n.ctx.once(callback);
|
||
},
|
||
get readyState() {
|
||
return navigator.mozL10n.ctx.isReady ? 'complete' : 'loading';
|
||
},
|
||
language: {
|
||
set code(lang) {
|
||
navigator.mozL10n.ctx.requestLocales(lang);
|
||
},
|
||
get code() {
|
||
return navigator.mozL10n.ctx.supportedLocales[0];
|
||
},
|
||
get direction() {
|
||
return getDirection(navigator.mozL10n.ctx.supportedLocales[0]);
|
||
}
|
||
},
|
||
qps: PSEUDO,
|
||
_config: {
|
||
appVersion: null,
|
||
localeSources: Object.create(null),
|
||
isPretranslated: false
|
||
},
|
||
_getInternalAPI: function () {
|
||
return {
|
||
Error: L10nError,
|
||
Context: Context,
|
||
Locale: Locale,
|
||
Resolver: Resolver,
|
||
getPluralRule: getPluralRule,
|
||
rePlaceables: rePlaceables,
|
||
translateDocument: translateDocument,
|
||
onMetaInjected: onMetaInjected,
|
||
PropertiesParser: PropertiesParser,
|
||
walkContent: walkContent,
|
||
buildLocaleList: buildLocaleList
|
||
};
|
||
}
|
||
};
|
||
function getDirection(lang) {
|
||
return rtlList.indexOf(lang) >= 0 ? 'rtl' : 'ltr';
|
||
}
|
||
var readyStates = {
|
||
loading: 0,
|
||
interactive: 1,
|
||
complete: 2
|
||
};
|
||
function whenInteractive(callback) {
|
||
if (readyStates[document.readyState] >= readyStates.interactive) {
|
||
callback();
|
||
return;
|
||
}
|
||
document.addEventListener('readystatechange', function l10n_onrsc() {
|
||
if (readyStates[document.readyState] >= readyStates.interactive) {
|
||
document.removeEventListener('readystatechange', l10n_onrsc);
|
||
callback();
|
||
}
|
||
});
|
||
}
|
||
function initObserver() {
|
||
nodeObserver = new MutationObserver(onMutations.bind(navigator.mozL10n));
|
||
nodeObserver.observe(document, moConfig);
|
||
}
|
||
function init(pretranslate) {
|
||
if (!pretranslate) {
|
||
initObserver();
|
||
}
|
||
initResources.call(navigator.mozL10n);
|
||
}
|
||
function initResources() {
|
||
var meta = {};
|
||
var nodes = document.head.querySelectorAll('link[rel="localization"],' + 'meta[name="availableLanguages"],' + 'meta[name="defaultLanguage"],' + 'meta[name="appVersion"],' + 'script[type="application/l10n"]');
|
||
for (var i = 0, node; node = nodes[i]; i++) {
|
||
var type = node.getAttribute('rel') || node.nodeName.toLowerCase();
|
||
switch (type) {
|
||
case 'localization':
|
||
this.ctx.resLinks.push(node.getAttribute('href'));
|
||
break;
|
||
case 'meta':
|
||
onMetaInjected.call(this, node, meta);
|
||
break;
|
||
case 'script':
|
||
onScriptInjected.call(this, node);
|
||
break;
|
||
}
|
||
}
|
||
var additionalLanguagesPromise;
|
||
if (navigator.mozApps && navigator.mozApps.getAdditionalLanguages) {
|
||
additionalLanguagesPromise = navigator.mozApps.getAdditionalLanguages().catch(function (e) {
|
||
console.error('Error while loading getAdditionalLanguages', e);
|
||
});
|
||
document.addEventListener('additionallanguageschange', function (evt) {
|
||
registerLocales.call(this, meta, evt.detail);
|
||
this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]);
|
||
}.bind(this));
|
||
} else {
|
||
additionalLanguagesPromise = Promise.resolve();
|
||
}
|
||
additionalLanguagesPromise.then(function (extraLangs) {
|
||
registerLocales.call(this, meta, extraLangs);
|
||
initLocale.call(this);
|
||
}.bind(this));
|
||
}
|
||
function registerLocales(meta, extraLangs) {
|
||
var locales = buildLocaleList.call(this, meta, extraLangs);
|
||
navigator.mozL10n._config.localeSources = locales[1];
|
||
this.ctx.registerLocales(locales[0], Object.keys(locales[1]));
|
||
}
|
||
function getMatchingLangpack(appVersion, langpacks) {
|
||
for (var i = 0, langpack; langpack = langpacks[i]; i++) {
|
||
if (langpack.target === appVersion) {
|
||
return langpack;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function buildLocaleList(meta, extraLangs) {
|
||
var loc, lp;
|
||
var localeSources = Object.create(null);
|
||
var defaultLocale = meta.defaultLanguage || this.ctx.defaultLocale;
|
||
if (meta.availableLanguages) {
|
||
for (loc in meta.availableLanguages) {
|
||
localeSources[loc] = 'app';
|
||
}
|
||
}
|
||
if (extraLangs) {
|
||
for (loc in extraLangs) {
|
||
lp = getMatchingLangpack(this._config.appVersion, extraLangs[loc]);
|
||
if (!lp) {
|
||
continue;
|
||
}
|
||
if (!(loc in localeSources) || !meta.availableLanguages[loc] || parseInt(lp.revision) > meta.availableLanguages[loc]) {
|
||
localeSources[loc] = 'extra';
|
||
}
|
||
}
|
||
}
|
||
if (!(defaultLocale in localeSources)) {
|
||
localeSources[defaultLocale] = 'app';
|
||
}
|
||
return [
|
||
defaultLocale,
|
||
localeSources
|
||
];
|
||
}
|
||
function splitAvailableLanguagesString(str) {
|
||
var langs = {};
|
||
str.split(',').forEach(function (lang) {
|
||
lang = lang.trim().split(':');
|
||
langs[lang[0]] = parseInt(lang[1]);
|
||
});
|
||
return langs;
|
||
}
|
||
function onMetaInjected(node, meta) {
|
||
switch (node.getAttribute('name')) {
|
||
case 'availableLanguages':
|
||
meta.availableLanguages = splitAvailableLanguagesString(node.getAttribute('content'));
|
||
break;
|
||
case 'defaultLanguage':
|
||
meta.defaultLanguage = node.getAttribute('content');
|
||
break;
|
||
case 'appVersion':
|
||
navigator.mozL10n._config.appVersion = node.getAttribute('content');
|
||
break;
|
||
}
|
||
}
|
||
function onScriptInjected(node) {
|
||
var lang = node.getAttribute('lang');
|
||
var locale = this.ctx.getLocale(lang);
|
||
locale.addAST(JSON.parse(node.textContent));
|
||
}
|
||
function initLocale() {
|
||
this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]);
|
||
window.addEventListener('languagechange', function l10n_langchange() {
|
||
this.ctx.requestLocales.apply(this.ctx, navigator.languages || [navigator.language]);
|
||
}.bind(this));
|
||
}
|
||
function localizeMutations(mutations) {
|
||
var mutation;
|
||
var targets = new Set();
|
||
for (var i = 0; i < mutations.length; i++) {
|
||
mutation = mutations[i];
|
||
if (mutation.type === 'childList') {
|
||
var addedNode;
|
||
for (var j = 0; j < mutation.addedNodes.length; j++) {
|
||
addedNode = mutation.addedNodes[j];
|
||
if (addedNode.nodeType !== Node.ELEMENT_NODE) {
|
||
continue;
|
||
}
|
||
targets.add(addedNode);
|
||
}
|
||
}
|
||
if (mutation.type === 'attributes') {
|
||
targets.add(mutation.target);
|
||
}
|
||
}
|
||
targets.forEach(function (target) {
|
||
if (target.childElementCount) {
|
||
translateFragment.call(this, target);
|
||
} else if (target.hasAttribute('data-l10n-id')) {
|
||
translateElement.call(this, target);
|
||
}
|
||
}, this);
|
||
}
|
||
function onMutations(mutations, self) {
|
||
self.disconnect();
|
||
localizeMutations.call(this, mutations);
|
||
self.observe(document, moConfig);
|
||
}
|
||
function onReady() {
|
||
if (!navigator.mozL10n._config.isPretranslated) {
|
||
translateDocument.call(this);
|
||
}
|
||
navigator.mozL10n._config.isPretranslated = false;
|
||
if (pendingElements) {
|
||
for (var i = 0, element; element = pendingElements[i]; i++) {
|
||
translateElement.call(this, element);
|
||
}
|
||
pendingElements = null;
|
||
}
|
||
if (!nodeObserver) {
|
||
initObserver();
|
||
}
|
||
fireLocalizedEvent.call(this);
|
||
}
|
||
function fireLocalizedEvent() {
|
||
var event = new CustomEvent('localized', {
|
||
'bubbles': false,
|
||
'cancelable': false,
|
||
'detail': { 'language': this.ctx.supportedLocales[0] }
|
||
});
|
||
window.dispatchEvent(event);
|
||
}
|
||
var reOverlay = /<|&#?\w+;/;
|
||
var reHtml = /[&<>]/g;
|
||
var htmlEntities = {
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>'
|
||
};
|
||
function translateDocument() {
|
||
document.documentElement.lang = this.language.code;
|
||
document.documentElement.dir = this.language.direction;
|
||
translateFragment.call(this, document.documentElement);
|
||
}
|
||
function translateFragment(element) {
|
||
if (typeof element.hasAttribute === 'function' && element.hasAttribute('data-l10n-id')) {
|
||
translateElement.call(this, element);
|
||
}
|
||
var nodes = getTranslatableChildren(element);
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
translateElement.call(this, nodes[i]);
|
||
}
|
||
}
|
||
function setL10nAttributes(element, id, args) {
|
||
element.setAttribute('data-l10n-id', id);
|
||
if (args) {
|
||
element.setAttribute('data-l10n-args', JSON.stringify(args));
|
||
}
|
||
}
|
||
function getL10nAttributes(element) {
|
||
return {
|
||
id: element.getAttribute('data-l10n-id'),
|
||
args: JSON.parse(element.getAttribute('data-l10n-args'))
|
||
};
|
||
}
|
||
function getTranslatableChildren(element) {
|
||
return element ? element.querySelectorAll('*[data-l10n-id]') : [];
|
||
}
|
||
function camelCaseToDashed(string) {
|
||
if (string === 'ariaValueText') {
|
||
return 'aria-valuetext';
|
||
}
|
||
return string.replace(/[A-Z]/g, function (match) {
|
||
return '-' + match.toLowerCase();
|
||
}).replace(/^-/, '');
|
||
}
|
||
function escapeL10nArgs(match) {
|
||
return htmlEntities[match];
|
||
}
|
||
function translateElement(element) {
|
||
if (!this.ctx.isReady) {
|
||
if (!pendingElements) {
|
||
pendingElements = [];
|
||
}
|
||
pendingElements.push(element);
|
||
return;
|
||
}
|
||
var l10nId = element.getAttribute('data-l10n-id');
|
||
if (!l10nId) {
|
||
return false;
|
||
}
|
||
var l10nArgs = element.getAttribute('data-l10n-args');
|
||
var entity = this.ctx.getEntity(l10nId, l10nArgs ? JSON.parse(l10nArgs.replace(reHtml, escapeL10nArgs)) : undefined);
|
||
var value = entity.value;
|
||
if (typeof value === 'string') {
|
||
if (!reOverlay.test(value)) {
|
||
element.textContent = value;
|
||
} else {
|
||
var translation = element.ownerDocument.createElement('template');
|
||
translation.innerHTML = value;
|
||
overlayElement(element, translation.content);
|
||
}
|
||
}
|
||
for (var key in entity.attrs) {
|
||
var attrName = camelCaseToDashed(key);
|
||
if (isAttrAllowed({ name: attrName }, element)) {
|
||
element.setAttribute(attrName, entity.attrs[key]);
|
||
}
|
||
}
|
||
}
|
||
function overlayElement(sourceElement, translationElement) {
|
||
var result = translationElement.ownerDocument.createDocumentFragment();
|
||
var k, attr;
|
||
var childElement;
|
||
while (childElement = translationElement.childNodes[0]) {
|
||
translationElement.removeChild(childElement);
|
||
if (childElement.nodeType === Node.TEXT_NODE) {
|
||
result.appendChild(childElement);
|
||
continue;
|
||
}
|
||
var index = getIndexOfType(childElement);
|
||
var sourceChild = getNthElementOfType(sourceElement, childElement, index);
|
||
if (sourceChild) {
|
||
overlayElement(sourceChild, childElement);
|
||
result.appendChild(sourceChild);
|
||
continue;
|
||
}
|
||
if (isElementAllowed(childElement)) {
|
||
var sanitizedChild = childElement.ownerDocument.createElement(childElement.nodeName);
|
||
overlayElement(sanitizedChild, childElement);
|
||
result.appendChild(sanitizedChild);
|
||
continue;
|
||
}
|
||
result.appendChild(document.createTextNode(childElement.textContent));
|
||
}
|
||
sourceElement.textContent = '';
|
||
sourceElement.appendChild(result);
|
||
if (translationElement.attributes) {
|
||
for (k = 0, attr; attr = translationElement.attributes[k]; k++) {
|
||
if (isAttrAllowed(attr, sourceElement)) {
|
||
sourceElement.setAttribute(attr.name, attr.value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function isElementAllowed(element) {
|
||
return allowed.elements.indexOf(element.tagName.toLowerCase()) !== -1;
|
||
}
|
||
function isAttrAllowed(attr, element) {
|
||
var attrName = attr.name.toLowerCase();
|
||
var tagName = element.tagName.toLowerCase();
|
||
if (allowed.attributes.global.indexOf(attrName) !== -1) {
|
||
return true;
|
||
}
|
||
if (!allowed.attributes[tagName]) {
|
||
return false;
|
||
}
|
||
if (allowed.attributes[tagName].indexOf(attrName) !== -1) {
|
||
return true;
|
||
}
|
||
if (tagName === 'input' && attrName === 'value') {
|
||
var type = element.type.toLowerCase();
|
||
if (type === 'submit' || type === 'button' || type === 'reset') {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
function getNthElementOfType(context, element, index) {
|
||
var nthOfType = 0;
|
||
for (var i = 0, child; child = context.children[i]; i++) {
|
||
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === element.tagName) {
|
||
if (nthOfType === index) {
|
||
return child;
|
||
}
|
||
nthOfType++;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
function getIndexOfType(element) {
|
||
var index = 0;
|
||
var child;
|
||
while (child = element.previousElementSibling) {
|
||
if (child.tagName === element.tagName) {
|
||
index++;
|
||
}
|
||
}
|
||
return index;
|
||
}
|
||
var DEBUG = false;
|
||
navigator.mozL10n.ctx = new Context(window.document ? document.URL : null);
|
||
navigator.mozL10n.ctx.ready(onReady.bind(navigator.mozL10n));
|
||
navigator.mozL10n.ctx.addEventListener('notfounderror', function reportMissingEntity(e) {
|
||
if (DEBUG || e.loc === 'en-US') {
|
||
console.warn(e.toString());
|
||
}
|
||
});
|
||
if (DEBUG) {
|
||
navigator.mozL10n.ctx.addEventListener('fetcherror', console.error.bind(console));
|
||
navigator.mozL10n.ctx.addEventListener('parseerror', console.error.bind(console));
|
||
navigator.mozL10n.ctx.addEventListener('resolveerror', console.error.bind(console));
|
||
}
|
||
if (window.document) {
|
||
navigator.mozL10n._config.isPretranslated = document.documentElement.lang === navigator.language;
|
||
var forcePretranslate = !navigator.mozL10n._config.isPretranslated;
|
||
whenInteractive(init.bind(navigator.mozL10n, forcePretranslate));
|
||
}
|
||
document.l10n = {
|
||
setAttributes: navigator.mozL10n.setAttributes,
|
||
getAttributes: navigator.mozL10n.getAttributes,
|
||
formatValue: function (id, args) {
|
||
return navigator.mozL10n.formatValue(id, args);
|
||
},
|
||
translateFragment: function (frag) {
|
||
return Promise.resolve(navigator.mozL10n.translateFragment(frag));
|
||
},
|
||
ready: new Promise(function (resolve) {
|
||
navigator.mozL10n.once(resolve);
|
||
}),
|
||
formatValues: function () {
|
||
var keys = arguments;
|
||
var resp = keys.map(function (key) {
|
||
if (Array.isArray(key)) {
|
||
return navigator.mozL10n.formatValue(key[0], key[1]);
|
||
}
|
||
return navigator.mozL10n.formatValue(key);
|
||
});
|
||
return Promise.all(resp);
|
||
},
|
||
requestLanguages: function (langs) {
|
||
navigator.mozL10n.ctx.requestLocales.apply(navigator.mozL10n.ctx, langs);
|
||
},
|
||
pseudo: {
|
||
'fr-x-psaccent': {
|
||
getName: function () {
|
||
return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].name);
|
||
},
|
||
processString: function (s) {
|
||
return Promise.resolve(navigator.mozL10n.qps['fr-x-psaccent'].translate(s));
|
||
}
|
||
},
|
||
'ar-x-psbidi': {
|
||
getName: function () {
|
||
return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].name);
|
||
},
|
||
processString: function (s) {
|
||
return Promise.resolve(navigator.mozL10n.qps['ar-x-psbidi'].translate(s));
|
||
}
|
||
}
|
||
}
|
||
};
|
||
navigator.mozL10n.ready(function () {
|
||
document.documentElement.setAttribute('langs', navigator.mozL10n.ctx.supportedLocales.join(' '));
|
||
});
|
||
navigator.mozL10n.once(function () {
|
||
window.addEventListener('localized', function () {
|
||
document.dispatchEvent(new CustomEvent('DOMRetranslated', {
|
||
bubbles: false,
|
||
cancelable: false
|
||
}));
|
||
});
|
||
});
|
||
}(this));
|
||
define('l10nbase', function () {
|
||
});
|
||
(function (global) {
|
||
'use strict';
|
||
global.mozIntl = {
|
||
formatList: function (list) {
|
||
return navigator.mozL10n.formatValue('listSeparator_middle').then(sep => list.join(sep));
|
||
},
|
||
DateTimeFormat: function (locales, options) {
|
||
const resolvedOptions = Object.assign({}, options);
|
||
if (resolvedOptions.dayperiod) {
|
||
if (resolvedOptions.hour === undefined) {
|
||
resolvedOptions.hour = 'numeric';
|
||
}
|
||
}
|
||
if (resolvedOptions.hour !== undefined && resolvedOptions.hour12 === undefined) {
|
||
resolvedOptions.hour12 = navigator.mozHour12;
|
||
}
|
||
if (resolvedOptions.dayperiod === undefined && resolvedOptions.hour12 === true) {
|
||
resolvedOptions.dayperiod = true;
|
||
}
|
||
var intlFormat = new Intl.DateTimeFormat(locales, resolvedOptions);
|
||
resolvedOptions.locale = intlFormat.resolvedOptions().locale;
|
||
resolvedOptions.hour12 = intlFormat.resolvedOptions().hour12;
|
||
var hourFormatter;
|
||
if (resolvedOptions.dayperiod !== undefined && resolvedOptions.hour12 === true) {
|
||
hourFormatter = Intl.DateTimeFormat(locales, {
|
||
hour: 'numeric',
|
||
hour12: false
|
||
});
|
||
}
|
||
return {
|
||
resolvedOptions() {
|
||
return resolvedOptions;
|
||
},
|
||
format: function (date, tokenFormats) {
|
||
var dayPeriod;
|
||
var string = intlFormat.format(date);
|
||
if (resolvedOptions.dayperiod === false && resolvedOptions.hour12 === true) {
|
||
dayPeriod = getDayPeriodTokenForDate(date, hourFormatter);
|
||
string = string.replace(dayPeriod, '').trim();
|
||
} else if (resolvedOptions.dayperiod === true && options.hour === undefined) {
|
||
dayPeriod = getDayPeriodTokenForDate(date, hourFormatter);
|
||
const hour = date.toLocaleString(navigator.languages, {
|
||
hour12: true,
|
||
hour: 'numeric'
|
||
}).replace(dayPeriod, '').trim();
|
||
string = string.replace(hour, '').trim();
|
||
}
|
||
for (var token in tokenFormats) {
|
||
if (token === 'dayperiod' && resolvedOptions.hour12 === false) {
|
||
continue;
|
||
}
|
||
const localOptions = { [token]: resolvedOptions[token] };
|
||
var formatter = global.mozIntl.DateTimeFormat(navigator.languages, localOptions);
|
||
var tokenString = formatter.format(date);
|
||
string = string.replace(tokenString, tokenFormats[token]);
|
||
}
|
||
return string;
|
||
}
|
||
};
|
||
},
|
||
calendarInfo: function (token) {
|
||
switch (token) {
|
||
case 'firstDayOfTheWeek':
|
||
return navigator.mozL10n.formatValue('firstDayOfTheWeek').then(firstDayOfTheWeek => parseInt(firstDayOfTheWeek) % 7);
|
||
default:
|
||
throw new Error('Unknown token: ' + token);
|
||
}
|
||
},
|
||
DurationFormat: function (locales = navigator.languages, options = {}) {
|
||
const resolvedOptions = Object.assign({
|
||
locale: locales[0],
|
||
maxUnit: 'hour',
|
||
minUnit: 'second'
|
||
}, options);
|
||
const numFormatter = Intl.NumberFormat(locales, {
|
||
style: 'decimal',
|
||
useGrouping: false,
|
||
minimumIntegerDigits: 2
|
||
});
|
||
const maxUnitIdx = getDurationUnitIdx(resolvedOptions.maxUnit, 0);
|
||
const minUnitIdx = getDurationUnitIdx(resolvedOptions.minUnit, durationFormatOrder.length - 1);
|
||
return navigator.mozL10n.formatValue('durationPattern').then(fmt => ({
|
||
resolvedOptions: function () {
|
||
return resolvedOptions;
|
||
},
|
||
format: function (input) {
|
||
const minValue = durationFormatElements[resolvedOptions.minUnit].value;
|
||
input = Math.round(input / minValue) * minValue;
|
||
const duration = splitIntoTimeUnits(input, maxUnitIdx, minUnitIdx);
|
||
var string = trimDurationPattern(fmt, resolvedOptions.maxUnit, resolvedOptions.minUnit);
|
||
for (var unit in duration) {
|
||
const token = durationFormatElements[unit].token;
|
||
string = string.replace(token, numFormatter.format(duration[unit]));
|
||
}
|
||
if (input < 0) {
|
||
return '-' + string;
|
||
}
|
||
return string;
|
||
}
|
||
}));
|
||
},
|
||
RelativeTimeFormat: function (locales, options) {
|
||
return {
|
||
resolvedOptions: function () {
|
||
return options;
|
||
},
|
||
format: function (x) {
|
||
const {unit, value} = relativeTimeFormatId(x, options);
|
||
return navigator.mozL10n.formatValue(unit, { value });
|
||
}
|
||
};
|
||
},
|
||
UnitFormat: function (locales, options) {
|
||
const unitGroup = getUnitFormatGroupName(options.unit);
|
||
if (unitGroup === undefined) {
|
||
throw new RangeError(`invalid value ${ options.unit } for option unit`);
|
||
}
|
||
if (!unitFormatGroups[unitGroup].styles.includes(options.style)) {
|
||
throw new RangeError(`invalid value ${ options.style } for option style`);
|
||
}
|
||
const unit = `${ unitGroup }-${ options.unit }-${ options.style }`;
|
||
return {
|
||
format: function (x) {
|
||
return document.l10n.formatValue(unit, { value: x });
|
||
}
|
||
};
|
||
},
|
||
_gaia: {
|
||
relativePart: function (milliseconds) {
|
||
const units = computeTimeUnits(milliseconds);
|
||
const unit = getBestMatchUnit(units);
|
||
return {
|
||
unit: unit + 's',
|
||
value: Math.abs(units[unit])
|
||
};
|
||
},
|
||
RelativeDate: function (locales, options) {
|
||
const style = options && options.style || 'long';
|
||
const maxFormatter = Intl.DateTimeFormat(locales, {
|
||
year: 'numeric',
|
||
month: 'numeric',
|
||
day: 'numeric'
|
||
});
|
||
const relativeFmtOptions = {
|
||
unit: 'bestFit',
|
||
style: style,
|
||
minUnit: 'minute'
|
||
};
|
||
return {
|
||
format: function (time, maxDiff) {
|
||
maxDiff = maxDiff || 86400 * 10;
|
||
const secDiff = (Date.now() - time) / 1000;
|
||
if (isNaN(secDiff)) {
|
||
return navigator.mozL10n.formatValue('incorrectDate');
|
||
}
|
||
if (secDiff > maxDiff) {
|
||
return Promise.resolve(maxFormatter.format(time));
|
||
}
|
||
const {unit, value} = relativeTimeFormatId(time, relativeFmtOptions);
|
||
return navigator.mozL10n.formatValue(unit, { value });
|
||
},
|
||
formatElement: function (element, time, maxDiff) {
|
||
maxDiff = maxDiff || 86400 * 10;
|
||
const secDiff = (Date.now() - time) / 1000;
|
||
if (isNaN(secDiff)) {
|
||
element.setAttribute('data-l10n-id', 'incorrectDate');
|
||
}
|
||
element.removeAttribute('data-l10n-id');
|
||
if (secDiff > maxDiff) {
|
||
element.textContent = maxFormatter.format(time);
|
||
}
|
||
const {unit, value} = relativeTimeFormatId(time, relativeFmtOptions);
|
||
navigator.mozL10n.setAttributes(element, unit, { value });
|
||
}
|
||
};
|
||
},
|
||
getFormattedUnit: function (type, style, v) {
|
||
if (!unitFormatData.hasOwnProperty(type)) {
|
||
throw new RangeError(`invalid type ${ type }`);
|
||
}
|
||
if (!unitFormatGroups[type].styles.includes(style)) {
|
||
throw new RangeError(`invalid style ${ style } for type ${ type }`);
|
||
}
|
||
var units = unitFormatData[type];
|
||
var scale = 0;
|
||
for (let i = 1; i < units.length; i++) {
|
||
if (v < units[i].value * unitFormatGroups[type].rounding) {
|
||
scale = i - 1;
|
||
break;
|
||
} else if (i === units.length - 1) {
|
||
scale = i;
|
||
}
|
||
}
|
||
var value = Math.round(v / units[scale].value * 100) / 100;
|
||
return global.mozIntl.UnitFormat(navigator.languages, {
|
||
unit: units[scale].name,
|
||
style: style
|
||
}).format(value);
|
||
}
|
||
}
|
||
};
|
||
const durationFormatOrder = [
|
||
'hour',
|
||
'minute',
|
||
'second',
|
||
'millisecond'
|
||
];
|
||
const durationFormatElements = {
|
||
'hour': {
|
||
value: 3600000,
|
||
token: 'hh'
|
||
},
|
||
'minute': {
|
||
value: 60000,
|
||
token: 'mm'
|
||
},
|
||
'second': {
|
||
value: 1000,
|
||
token: 'ss'
|
||
},
|
||
'millisecond': {
|
||
value: 10,
|
||
token: 'SS'
|
||
}
|
||
};
|
||
const unitFormatData = {
|
||
'duration': [
|
||
{
|
||
'name': 'second',
|
||
'value': 1
|
||
},
|
||
{
|
||
'name': 'minute',
|
||
'value': 60
|
||
},
|
||
{
|
||
'name': 'hour',
|
||
'value': 60 * 60
|
||
},
|
||
{
|
||
'name': 'day',
|
||
'value': 24 * 60 * 60
|
||
},
|
||
{
|
||
'name': 'month',
|
||
'value': 30 * 24 * 60 * 60
|
||
}
|
||
],
|
||
'digital': [
|
||
{
|
||
'name': 'byte',
|
||
'value': 1
|
||
},
|
||
{
|
||
'name': 'kilobyte',
|
||
'value': 1024
|
||
},
|
||
{
|
||
'name': 'megabyte',
|
||
'value': 1024 * 1024
|
||
},
|
||
{
|
||
'name': 'gigabyte',
|
||
'value': 1024 * 1024 * 1024
|
||
},
|
||
{
|
||
'name': 'terabyte',
|
||
'value': 1024 * 1024 * 1024 * 1024
|
||
}
|
||
]
|
||
};
|
||
const unitFormatGroups = {
|
||
'duration': {
|
||
'units': [
|
||
'second',
|
||
'minute',
|
||
'hour',
|
||
'day',
|
||
'month'
|
||
],
|
||
'styles': ['narrow'],
|
||
'rounding': 1
|
||
},
|
||
'digital': {
|
||
'units': [
|
||
'byte',
|
||
'kilobyte',
|
||
'megabyte',
|
||
'gigabyte',
|
||
'terabyte'
|
||
],
|
||
'styles': ['short'],
|
||
'rounding': 0.8
|
||
}
|
||
};
|
||
function getDurationUnitIdx(name, defaultValue) {
|
||
if (!name) {
|
||
return defaultValue;
|
||
}
|
||
const pos = durationFormatOrder.indexOf(name);
|
||
if (pos === -1) {
|
||
throw new Error('Unknown unit type: ' + name);
|
||
}
|
||
return pos;
|
||
}
|
||
function splitIntoTimeUnits(v, maxUnitIdx, minUnitIdx) {
|
||
const units = {};
|
||
var input = Math.abs(v);
|
||
for (var i = maxUnitIdx; i <= minUnitIdx; i++) {
|
||
const key = durationFormatOrder[i];
|
||
const {value} = durationFormatElements[key];
|
||
units[key] = i == minUnitIdx ? Math.round(input / value) : Math.floor(input / value);
|
||
input -= units[key] * value;
|
||
}
|
||
return units;
|
||
}
|
||
function trimDurationPattern(string, maxUnit, minUnit) {
|
||
const maxToken = durationFormatElements[maxUnit].token;
|
||
const minToken = durationFormatElements[minUnit].token;
|
||
string = string.substring(string.indexOf(maxToken), string.indexOf(minToken) + minToken.length);
|
||
return string;
|
||
}
|
||
function getDayPeriodTokenForDate(date, hourFormatter) {
|
||
const hourToken = hourFormatter.format(date);
|
||
const newDate = new Date(date);
|
||
newDate.setHours(parseInt(hourToken));
|
||
return new Intl.DateTimeFormat(navigator.language, {hour:'numeric', hour12:true}).formatToParts(newDate).find(part => part.type === 'dayPeriod').value;
|
||
}
|
||
function computeTimeUnits(v) {
|
||
const units = {};
|
||
const millisecond = Math.round(v);
|
||
const second = Math.round(millisecond / 1000);
|
||
const minute = Math.round(second / 60);
|
||
const hour = Math.round(minute / 60);
|
||
const day = Math.round(hour / 24);
|
||
const rawYear = day * 400 / 146097;
|
||
units.millisecond = millisecond;
|
||
units.second = second;
|
||
units.minute = minute;
|
||
units.hour = hour;
|
||
units.day = day;
|
||
units.week = Math.round(day / 7);
|
||
units.month = Math.round(rawYear * 12);
|
||
units.quarter = Math.round(rawYear * 4);
|
||
units.year = Math.round(rawYear);
|
||
return units;
|
||
}
|
||
function getBestMatchUnit(units) {
|
||
if (Math.abs(units.minute) < 45) {
|
||
return 'minute';
|
||
}
|
||
if (Math.abs(units.hour) < 22) {
|
||
return 'hour';
|
||
}
|
||
if (Math.abs(units.day) < 7) {
|
||
return 'day';
|
||
}
|
||
if (Math.abs(units.week) < 4) {
|
||
return 'week';
|
||
}
|
||
if (Math.abs(units.month) < 11) {
|
||
return 'month';
|
||
}
|
||
return 'year';
|
||
}
|
||
function relativeTimeFormatId(x, options) {
|
||
const ms = x - Date.now();
|
||
const units = computeTimeUnits(ms);
|
||
const unit = options.unit === 'bestFit' ? getBestMatchUnit(units) : options.unit;
|
||
const v = units[unit];
|
||
const tl = v < 0 ? '-ago' : '-until';
|
||
const style = options.style || 'long';
|
||
const entry = unit + 's' + tl + '-' + style;
|
||
return {
|
||
unit: entry,
|
||
value: Math.abs(v)
|
||
};
|
||
}
|
||
function getUnitFormatGroupName(unitName) {
|
||
for (let groupName in unitFormatGroups) {
|
||
if (unitFormatGroups[groupName].units.includes(unitName)) {
|
||
return groupName;
|
||
}
|
||
}
|
||
return undefined;
|
||
}
|
||
}(this));
|
||
define('moz_intl', ['l10nbase'], function (global) {
|
||
return function () {
|
||
var ret, fn;
|
||
return ret || global.mozIntl;
|
||
};
|
||
}(this));
|
||
define('text', {
|
||
load: function (name, req, onload, config) {
|
||
var url = req.toUrl(name), xhr = new XMLHttpRequest();
|
||
xhr.open('GET', url, true);
|
||
xhr.onreadystatechange = function (evt) {
|
||
var status, err;
|
||
if (xhr.readyState === 4) {
|
||
status = xhr.status;
|
||
if (status > 399 && status < 600) {
|
||
err = new Error(url + ' HTTP status: ' + status);
|
||
err.xhr = xhr;
|
||
onload.error(err);
|
||
} else {
|
||
onload(xhr.responseText);
|
||
}
|
||
}
|
||
};
|
||
xhr.responseType = 'text';
|
||
xhr.send(null);
|
||
}
|
||
});
|
||
define('folder_depth_classes', [], function () {
|
||
return [
|
||
'fld-folder-depth0',
|
||
'fld-folder-depth1',
|
||
'fld-folder-depth2',
|
||
'fld-folder-depth3',
|
||
'fld-folder-depth4',
|
||
'fld-folder-depth5',
|
||
'fld-folder-depthmax'
|
||
];
|
||
});
|
||
define('tmpl!cards/value_selector.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<form class="email-value-selector collapsed" role="dialog" data-type="value-selector">\n <section class="scrollable">\n <h1></h1>\n <ol role="listbox">\n </ol>\n </section>\n <menu>\n <button class="full"\n data-l10n-id="message-multiedit-cancel"></button>\n </menu>\n</form>');
|
||
});
|
||
define('tmpl!cards/vsl/item.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<li role="option"><label role="presentation"> <span></span></label></li>');
|
||
});
|
||
define('value_selector', [
|
||
'require',
|
||
'cards',
|
||
'folder_depth_classes',
|
||
'tmpl!cards/value_selector.html',
|
||
'tmpl!cards/vsl/item.html'
|
||
], function (require) {
|
||
'use strict';
|
||
var cards = require('cards'), FOLDER_DEPTH_CLASSES = require('folder_depth_classes'), formNode = require('tmpl!cards/value_selector.html'), itemTemplateNode = require('tmpl!cards/vsl/item.html');
|
||
function noop() {
|
||
}
|
||
function ValueSelector(title, list) {
|
||
var init, show, hide, render, setTitle, emptyList, addToList, data;
|
||
init = function () {
|
||
data = {
|
||
title: 'No Title',
|
||
list: [{
|
||
label: 'Dummy element',
|
||
callback: function () {
|
||
alert('Define an action here!');
|
||
}
|
||
}]
|
||
};
|
||
document.body.appendChild(formNode);
|
||
var btnCancel = formNode.querySelector('button');
|
||
btnCancel.addEventListener('click', function (event) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
hide();
|
||
});
|
||
emptyList();
|
||
if (typeof title === 'string') {
|
||
setTitle(title);
|
||
}
|
||
if (Array.isArray(list)) {
|
||
data.list = list;
|
||
}
|
||
};
|
||
show = function () {
|
||
render();
|
||
cards.setStatusColor(formNode);
|
||
formNode.classList.remove('collapsed');
|
||
};
|
||
hide = function () {
|
||
cards.setStatusColor();
|
||
formNode.classList.add('collapsed');
|
||
emptyList();
|
||
};
|
||
render = function () {
|
||
var title = formNode.querySelector('h1'), list = formNode.querySelector('ol');
|
||
title.textContent = data.title;
|
||
list.innerHTML = '';
|
||
data.list.forEach(function (listItem) {
|
||
var node = itemTemplateNode.cloneNode(true);
|
||
node.querySelector('span').textContent = listItem.label;
|
||
var depthIdx = listItem.depth;
|
||
depthIdx = Math.min(FOLDER_DEPTH_CLASSES.length - 1, depthIdx);
|
||
node.classList.add(FOLDER_DEPTH_CLASSES[depthIdx]);
|
||
var callback = listItem.selectable ? listItem.callback : noop;
|
||
node.addEventListener('click', callback, false);
|
||
list.appendChild(node);
|
||
});
|
||
};
|
||
setTitle = function (str) {
|
||
data.title = str;
|
||
};
|
||
emptyList = function () {
|
||
data.list = [];
|
||
};
|
||
addToList = function (label, depth, selectable, callback) {
|
||
data.list.push({
|
||
label: label,
|
||
depth: depth,
|
||
selectable: selectable,
|
||
callback: callback
|
||
});
|
||
};
|
||
init();
|
||
return {
|
||
init: init,
|
||
show: show,
|
||
hide: hide,
|
||
setTitle: setTitle,
|
||
addToList: addToList,
|
||
List: list
|
||
};
|
||
}
|
||
return ValueSelector;
|
||
});
|
||
var GestureDetector = function () {
|
||
function GD(e, options) {
|
||
this.element = e;
|
||
this.options = options || {};
|
||
this.options.panThreshold = this.options.panThreshold || GD.PAN_THRESHOLD;
|
||
this.state = initialState;
|
||
this.timers = {};
|
||
}
|
||
GD.prototype.startDetecting = function () {
|
||
var self = this;
|
||
eventtypes.forEach(function (t) {
|
||
self.element.addEventListener(t, self);
|
||
});
|
||
};
|
||
GD.prototype.stopDetecting = function () {
|
||
var self = this;
|
||
eventtypes.forEach(function (t) {
|
||
self.element.removeEventListener(t, self);
|
||
});
|
||
};
|
||
GD.prototype.handleEvent = function (e) {
|
||
var handler = this.state[e.type];
|
||
if (!handler) {
|
||
return;
|
||
}
|
||
if (e.changedTouches) {
|
||
for (var i = 0; i < e.changedTouches.length; i++) {
|
||
handler(this, e, e.changedTouches[i]);
|
||
handler = this.state[e.type];
|
||
if (!handler) {
|
||
return;
|
||
}
|
||
}
|
||
} else {
|
||
handler(this, e);
|
||
}
|
||
};
|
||
GD.prototype.startTimer = function (type, time) {
|
||
this.clearTimer(type);
|
||
var self = this;
|
||
this.timers[type] = setTimeout(function () {
|
||
self.timers[type] = null;
|
||
var handler = self.state[type];
|
||
if (handler) {
|
||
handler(self, type);
|
||
}
|
||
}, time);
|
||
};
|
||
GD.prototype.clearTimer = function (type) {
|
||
if (this.timers[type]) {
|
||
clearTimeout(this.timers[type]);
|
||
this.timers[type] = null;
|
||
}
|
||
};
|
||
GD.prototype.switchTo = function (state, event, touch) {
|
||
this.state = state;
|
||
if (state.init) {
|
||
state.init(this, event, touch);
|
||
}
|
||
};
|
||
GD.prototype.emitEvent = function (type, detail) {
|
||
if (!this.target) {
|
||
console.error('Attempt to emit event with no target');
|
||
return;
|
||
}
|
||
var event = this.element.ownerDocument.createEvent('CustomEvent');
|
||
event.initCustomEvent(type, true, true, detail);
|
||
this.target.dispatchEvent(event);
|
||
};
|
||
GD.HOLD_INTERVAL = 1000;
|
||
GD.PAN_THRESHOLD = 20;
|
||
GD.DOUBLE_TAP_DISTANCE = 50;
|
||
GD.DOUBLE_TAP_TIME = 500;
|
||
GD.VELOCITY_SMOOTHING = 0.5;
|
||
GD.SCALE_THRESHOLD = 20;
|
||
GD.ROTATE_THRESHOLD = 22.5;
|
||
GD.THRESHOLD_SMOOTHING = 0.9;
|
||
var abs = Math.abs, floor = Math.floor, sqrt = Math.sqrt, atan2 = Math.atan2;
|
||
var PI = Math.PI;
|
||
var eventtypes = [
|
||
'touchstart',
|
||
'touchmove',
|
||
'touchend'
|
||
];
|
||
function eventTime(e) {
|
||
var ts = e.timeStamp;
|
||
if (ts > 2 * Date.now()) {
|
||
return Math.floor(ts / 1000);
|
||
} else {
|
||
return ts;
|
||
}
|
||
}
|
||
function coordinates(e, t) {
|
||
return Object.freeze({
|
||
screenX: t.screenX,
|
||
screenY: t.screenY,
|
||
clientX: t.clientX,
|
||
clientY: t.clientY,
|
||
timeStamp: eventTime(e)
|
||
});
|
||
}
|
||
function midpoints(e, t1, t2) {
|
||
return Object.freeze({
|
||
screenX: floor((t1.screenX + t2.screenX) / 2),
|
||
screenY: floor((t1.screenY + t2.screenY) / 2),
|
||
clientX: floor((t1.clientX + t2.clientX) / 2),
|
||
clientY: floor((t1.clientY + t2.clientY) / 2),
|
||
timeStamp: eventTime(e)
|
||
});
|
||
}
|
||
function between(c1, c2) {
|
||
var r = GD.THRESHOLD_SMOOTHING;
|
||
return Object.freeze({
|
||
screenX: floor(c1.screenX + r * (c2.screenX - c1.screenX)),
|
||
screenY: floor(c1.screenY + r * (c2.screenY - c1.screenY)),
|
||
clientX: floor(c1.clientX + r * (c2.clientX - c1.clientX)),
|
||
clientY: floor(c1.clientY + r * (c2.clientY - c1.clientY)),
|
||
timeStamp: floor(c1.timeStamp + r * (c2.timeStamp - c1.timeStamp))
|
||
});
|
||
}
|
||
function touchDistance(t1, t2) {
|
||
var dx = t2.screenX - t1.screenX;
|
||
var dy = t2.screenY - t1.screenY;
|
||
return sqrt(dx * dx + dy * dy);
|
||
}
|
||
function touchDirection(t1, t2) {
|
||
return atan2(t2.screenY - t1.screenY, t2.screenX - t1.screenX) * 180 / PI;
|
||
}
|
||
function touchRotation(d1, d2) {
|
||
var angle = d2 - d1;
|
||
if (angle > 180) {
|
||
angle -= 360;
|
||
} else if (angle <= -180) {
|
||
angle += 360;
|
||
}
|
||
return angle;
|
||
}
|
||
function isDoubleTap(lastTap, thisTap) {
|
||
var dx = abs(thisTap.screenX - lastTap.screenX);
|
||
var dy = abs(thisTap.screenY - lastTap.screenY);
|
||
var dt = thisTap.timeStamp - lastTap.timeStamp;
|
||
return dx < GD.DOUBLE_TAP_DISTANCE && dy < GD.DOUBLE_TAP_DISTANCE && dt < GD.DOUBLE_TAP_TIME;
|
||
}
|
||
var initialState = {
|
||
name: 'initialState',
|
||
init: function (d, e, t) {
|
||
d.target = null;
|
||
d.start = d.last = null;
|
||
d.touch1 = d.touch2 = null;
|
||
d.vx = d.vy = null;
|
||
d.startDistance = d.lastDistance = null;
|
||
d.startDirection = d.lastDirection = null;
|
||
d.lastMidpoint = null;
|
||
d.scaled = d.rotated = null;
|
||
if (e && t && e.type === 'touchstart') {
|
||
initialState.touchstart(d, e, t);
|
||
}
|
||
},
|
||
touchstart: function (d, e, t) {
|
||
d.switchTo(touchStartedState, e, t);
|
||
}
|
||
};
|
||
var touchStartedState = {
|
||
name: 'touchStartedState',
|
||
init: function (d, e, t) {
|
||
d.target = e.target;
|
||
d.touch1 = t.identifier;
|
||
d.start = d.last = coordinates(e, t);
|
||
if (d.options.holdEvents) {
|
||
d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
|
||
}
|
||
},
|
||
touchstart: function (d, e, t) {
|
||
d.clearTimer('holdtimeout');
|
||
if (e.touches.length > 1) {
|
||
d.switchTo(transformState, e, t);
|
||
} else {
|
||
console.warn('Ignoring missing touchend event. See bug 1162771.');
|
||
d.switchTo(initialState, e, t);
|
||
}
|
||
},
|
||
touchmove: function (d, e, t) {
|
||
if (t.identifier !== d.touch1) {
|
||
return;
|
||
}
|
||
if (abs(t.screenX - d.start.screenX) > d.options.panThreshold || abs(t.screenY - d.start.screenY) > d.options.panThreshold) {
|
||
d.clearTimer('holdtimeout');
|
||
d.switchTo(panStartedState, e, t);
|
||
}
|
||
},
|
||
touchend: function (d, e, t) {
|
||
if (t.identifier !== d.touch1) {
|
||
return;
|
||
}
|
||
if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
|
||
d.emitEvent('tap', d.start);
|
||
d.emitEvent('dbltap', d.start);
|
||
d.lastTap = null;
|
||
} else {
|
||
d.emitEvent('tap', d.start);
|
||
d.lastTap = coordinates(e, t);
|
||
}
|
||
d.clearTimer('holdtimeout');
|
||
d.switchTo(initialState);
|
||
},
|
||
holdtimeout: function (d) {
|
||
d.switchTo(holdState);
|
||
}
|
||
};
|
||
var panStartedState = {
|
||
name: 'panStartedState',
|
||
init: function (d, e, t) {
|
||
d.start = d.last = between(d.start, coordinates(e, t));
|
||
if (e.type === 'touchmove') {
|
||
panStartedState.touchmove(d, e, t);
|
||
}
|
||
},
|
||
touchmove: function (d, e, t) {
|
||
if (t.identifier !== d.touch1) {
|
||
return;
|
||
}
|
||
var current = coordinates(e, t);
|
||
d.emitEvent('pan', {
|
||
absolute: {
|
||
dx: current.screenX - d.start.screenX,
|
||
dy: current.screenY - d.start.screenY
|
||
},
|
||
relative: {
|
||
dx: current.screenX - d.last.screenX,
|
||
dy: current.screenY - d.last.screenY
|
||
},
|
||
position: current
|
||
});
|
||
var dt = current.timeStamp - d.last.timeStamp;
|
||
var vx = (current.screenX - d.last.screenX) / dt;
|
||
var vy = (current.screenY - d.last.screenY) / dt;
|
||
if (d.vx == null) {
|
||
d.vx = vx;
|
||
d.vy = vy;
|
||
} else {
|
||
d.vx = d.vx * GD.VELOCITY_SMOOTHING + vx * (1 - GD.VELOCITY_SMOOTHING);
|
||
d.vy = d.vy * GD.VELOCITY_SMOOTHING + vy * (1 - GD.VELOCITY_SMOOTHING);
|
||
}
|
||
d.last = current;
|
||
},
|
||
touchend: function (d, e, t) {
|
||
if (t.identifier !== d.touch1) {
|
||
return;
|
||
}
|
||
var current = coordinates(e, t);
|
||
var dx = current.screenX - d.start.screenX;
|
||
var dy = current.screenY - d.start.screenY;
|
||
var angle = atan2(dy, dx) * 180 / PI;
|
||
if (angle < 0) {
|
||
angle += 360;
|
||
}
|
||
var direction;
|
||
if (angle >= 315 || angle < 45) {
|
||
direction = 'right';
|
||
} else if (angle >= 45 && angle < 135) {
|
||
direction = 'down';
|
||
} else if (angle >= 135 && angle < 225) {
|
||
direction = 'left';
|
||
} else if (angle >= 225 && angle < 315) {
|
||
direction = 'up';
|
||
}
|
||
d.emitEvent('swipe', {
|
||
start: d.start,
|
||
end: current,
|
||
dx: dx,
|
||
dy: dy,
|
||
dt: e.timeStamp - d.start.timeStamp,
|
||
vx: d.vx,
|
||
vy: d.vy,
|
||
direction: direction,
|
||
angle: angle
|
||
});
|
||
d.switchTo(initialState);
|
||
}
|
||
};
|
||
var holdState = {
|
||
name: 'holdState',
|
||
init: function (d) {
|
||
d.emitEvent('holdstart', d.start);
|
||
},
|
||
touchmove: function (d, e, t) {
|
||
var current = coordinates(e, t);
|
||
d.emitEvent('holdmove', {
|
||
absolute: {
|
||
dx: current.screenX - d.start.screenX,
|
||
dy: current.screenY - d.start.screenY
|
||
},
|
||
relative: {
|
||
dx: current.screenX - d.last.screenX,
|
||
dy: current.screenY - d.last.screenY
|
||
},
|
||
position: current
|
||
});
|
||
d.last = current;
|
||
},
|
||
touchend: function (d, e, t) {
|
||
var current = coordinates(e, t);
|
||
d.emitEvent('holdend', {
|
||
start: d.start,
|
||
end: current,
|
||
dx: current.screenX - d.start.screenX,
|
||
dy: current.screenY - d.start.screenY
|
||
});
|
||
d.switchTo(initialState);
|
||
}
|
||
};
|
||
var transformState = {
|
||
name: 'transformState',
|
||
init: function (d, e, t) {
|
||
d.touch2 = t.identifier;
|
||
var t1 = e.touches.identifiedTouch(d.touch1);
|
||
var t2 = e.touches.identifiedTouch(d.touch2);
|
||
d.startDistance = d.lastDistance = touchDistance(t1, t2);
|
||
d.startDirection = d.lastDirection = touchDirection(t1, t2);
|
||
d.scaled = d.rotated = false;
|
||
},
|
||
touchmove: function (d, e, t) {
|
||
if (t.identifier !== d.touch1 && t.identifier !== d.touch2) {
|
||
return;
|
||
}
|
||
var t1 = e.touches.identifiedTouch(d.touch1);
|
||
var t2 = e.touches.identifiedTouch(d.touch2);
|
||
var midpoint = midpoints(e, t1, t2);
|
||
var distance = touchDistance(t1, t2);
|
||
var direction = touchDirection(t1, t2);
|
||
var rotation = touchRotation(d.startDirection, direction);
|
||
if (!d.scaled) {
|
||
if (abs(distance - d.startDistance) > GD.SCALE_THRESHOLD) {
|
||
d.scaled = true;
|
||
d.startDistance = d.lastDistance = floor(d.startDistance + GD.THRESHOLD_SMOOTHING * (distance - d.startDistance));
|
||
} else {
|
||
distance = d.startDistance;
|
||
}
|
||
}
|
||
if (!d.rotated) {
|
||
if (abs(rotation) > GD.ROTATE_THRESHOLD) {
|
||
d.rotated = true;
|
||
} else {
|
||
direction = d.startDirection;
|
||
}
|
||
}
|
||
if (d.scaled || d.rotated) {
|
||
d.emitEvent('transform', {
|
||
absolute: {
|
||
scale: distance / d.startDistance,
|
||
rotate: touchRotation(d.startDirection, direction)
|
||
},
|
||
relative: {
|
||
scale: distance / d.lastDistance,
|
||
rotate: touchRotation(d.lastDirection, direction)
|
||
},
|
||
midpoint: midpoint
|
||
});
|
||
d.lastDistance = distance;
|
||
d.lastDirection = direction;
|
||
d.lastMidpoint = midpoint;
|
||
}
|
||
},
|
||
touchend: function (d, e, t) {
|
||
if (t.identifier === d.touch2) {
|
||
d.touch2 = null;
|
||
} else if (t.identifier === d.touch1) {
|
||
d.touch1 = d.touch2;
|
||
d.touch2 = null;
|
||
} else {
|
||
return;
|
||
}
|
||
if (d.scaled || d.rotated) {
|
||
d.emitEvent('transformend', {
|
||
absolute: {
|
||
scale: d.lastDistance / d.startDistance,
|
||
rotate: touchRotation(d.startDirection, d.lastDirection)
|
||
},
|
||
relative: {
|
||
scale: 1,
|
||
rotate: 0
|
||
},
|
||
midpoint: d.lastMidpoint
|
||
});
|
||
}
|
||
d.switchTo(afterTransformState);
|
||
}
|
||
};
|
||
var afterTransformState = {
|
||
name: 'afterTransformState',
|
||
touchstart: function (d, e, t) {
|
||
d.switchTo(transformState, e, t);
|
||
},
|
||
touchend: function (d, e, t) {
|
||
if (t.identifier === d.touch1) {
|
||
d.switchTo(initialState);
|
||
}
|
||
}
|
||
};
|
||
return GD;
|
||
}();
|
||
define('shared/js/gesture_detector', function (global) {
|
||
return function () {
|
||
var ret, fn;
|
||
return ret || global.GestureDetector;
|
||
};
|
||
}(this));
|
||
define('iframe_shims', ['shared/js/gesture_detector'], function (GestureDetector) {
|
||
var DEFAULT_STYLE_TAG = '<style type="text/css">\n' + 'blockquote {' + 'margin: 0; ' + '-moz-border-start: 0.2rem solid gray;' + 'padding: 0; -moz-padding-start: 0.5rem; ' + '}\n' + 'html, body { max-width: 120rem; word-wrap: break-word;' + ' overflow: hidden; padding: 0; margin: 0; }\n' + 'pre { white-space: pre-wrap; word-wrap: break-word; }\n' + '.moz-external-link { color: #00aac5; cursor: pointer; }\n' + '</style>';
|
||
var iframeShimsOpts = {
|
||
zoomDelayMS: 200,
|
||
initialScale: null,
|
||
resizeLimit: 4,
|
||
initialResizePollIntervalMS: 200,
|
||
noResizePollIntervalMS: 250,
|
||
didResizePollIntervalMS: 300,
|
||
pictureDelayPollIntervalMS: 200
|
||
};
|
||
function createAndInsertIframeForContent(htmlStr, scrollContainer, parentNode, beforeNode, interactiveMode, clickHandler) {
|
||
var scrollPad = 0;
|
||
var viewportWidth = parentNode.offsetWidth - scrollPad;
|
||
var viewport = document.createElement('div');
|
||
viewport.setAttribute('style', 'padding: 0; border-width: 0; margin: 0; ' + 'overflow: hidden;');
|
||
viewport.style.width = viewportWidth + 'px';
|
||
var iframe = document.createElement('iframe');
|
||
iframe.setAttribute('sandbox', 'allow-same-origin');
|
||
iframe.setAttribute('style', 'padding: 0; border-width: 0; margin: 0; ' + 'overflow: hidden; ' + 'transform-origin: top ' + (document.documentElement.dir === 'rtl' ? 'right' : 'left') + '; ' + 'pointer-events: none;');
|
||
if (iframeShimsOpts.tapTransform) {
|
||
iframe.style.transform = 'scale(1)';
|
||
}
|
||
iframe.style.width = viewportWidth + 'px';
|
||
viewport.appendChild(iframe);
|
||
parentNode.insertBefore(viewport, beforeNode);
|
||
iframe.contentDocument.open();
|
||
iframe.contentDocument.write('<!doctype html><html><head>');
|
||
iframe.contentDocument.write(DEFAULT_STYLE_TAG);
|
||
iframe.contentDocument.write('</head><body>');
|
||
iframe.contentDocument.write(htmlStr);
|
||
iframe.contentDocument.write('</body>');
|
||
iframe.contentDocument.close();
|
||
var iframeBody = iframe.contentDocument.body;
|
||
var scrollWidth = iframeBody.scrollWidth;
|
||
var scrollHeight = iframeBody.scrollHeight;
|
||
var baseScale = Math.min(1, viewportWidth / scrollWidth), lastRequestedScale = iframeShimsOpts.initialScale || baseScale, scale = lastRequestedScale, lastDoubleTapScale = scale, scaleMode = 0;
|
||
viewport.style.width = Math.ceil(scrollWidth * scale) + 'px';
|
||
viewport.style.height = Math.ceil(scrollHeight * scale) + 'px';
|
||
iframe.style.width = scrollWidth + 'px';
|
||
var resizeFrame = function (why) {
|
||
if (why === 'initial' || why === 'poll') {
|
||
scrollWidth = iframeBody.scrollWidth;
|
||
scrollHeight = iframeBody.scrollHeight;
|
||
var oldBaseScale = baseScale;
|
||
baseScale = Math.min(1, viewportWidth / scrollWidth);
|
||
if (scale === oldBaseScale) {
|
||
scale = baseScale;
|
||
}
|
||
iframe.style.width = scrollWidth + 'px';
|
||
console.log('iframe_shims: recalculating height / width because', why, 'sw', scrollWidth, 'sh', scrollHeight, 'bs', baseScale);
|
||
}
|
||
console.log('iframe_shims: scale:', scale);
|
||
iframe.style.transform = 'scale(' + scale + ')';
|
||
iframe.style.height = scrollHeight * Math.max(1, scale) + scrollPad + 'px';
|
||
viewport.style.width = Math.ceil(scrollWidth * scale) + 'px';
|
||
viewport.style.height = Math.ceil(scrollHeight * scale) + scrollPad + 'px';
|
||
};
|
||
resizeFrame('initial');
|
||
var activeZoom = false, lastCenterX, lastCenterY;
|
||
var zoomFrame = function (newScale, centerX, centerY) {
|
||
if (newScale === scale) {
|
||
return;
|
||
}
|
||
lastRequestedScale = newScale;
|
||
lastCenterX = centerX;
|
||
lastCenterY = centerY;
|
||
if (activeZoom) {
|
||
return;
|
||
}
|
||
activeZoom = true;
|
||
var iframeScrolledTop = scrollContainer.scrollTop - extraHeight, iframeScrolledLeft = scrollContainer.scrollLeft;
|
||
var ix = centerX + iframeScrolledLeft, iy = centerY + iframeScrolledTop;
|
||
var scaleDelta = newScale / scale;
|
||
var vertScrollDelta = Math.ceil(iy * scaleDelta), horizScrollDelta = Math.ceil(ix * scaleDelta);
|
||
scale = newScale;
|
||
resizeFrame('zoom');
|
||
scrollContainer.scrollTop = vertScrollDelta + extraHeight - centerY;
|
||
scrollContainer.scrollLeft = horizScrollDelta - centerX;
|
||
window.setTimeout(clearActiveZoom, iframeShimsOpts.zoomDelayMS);
|
||
};
|
||
var clearActiveZoom = function () {
|
||
activeZoom = false;
|
||
if (scale !== lastRequestedScale) {
|
||
window.requestAnimationFrame(function () {
|
||
console.log('delayed zoomFrame timeout, probably causing a mem-spike');
|
||
zoomFrame(lastRequestedScale, lastCenterX, lastCenterY);
|
||
});
|
||
}
|
||
};
|
||
var resizePollerTimeout = null;
|
||
var resizePollCount = 0;
|
||
var pollResize = function () {
|
||
var opts = iframeShimsOpts;
|
||
var desiredScrollWidth = iframeBody.scrollWidth;
|
||
var desiredScrollHeight = iframeBody.scrollHeight;
|
||
var resized = false;
|
||
if (desiredScrollWidth > scrollWidth || desiredScrollHeight > scrollHeight) {
|
||
resizeFrame('poll');
|
||
resized = true;
|
||
}
|
||
if (++resizePollCount < opts.resizeLimit) {
|
||
resizePollerTimeout = window.setTimeout(pollResize, resized ? opts.didResizePollIntervalMS : opts.noResizePollIntervalMS);
|
||
} else {
|
||
resizePollerTimeout = null;
|
||
}
|
||
};
|
||
resizePollerTimeout = window.setTimeout(pollResize, iframeShimsOpts.initialResizePollIntervalMS);
|
||
var iframeShims = {
|
||
iframe: iframe,
|
||
resizeHandler: function () {
|
||
resizePollCount = 0;
|
||
if (resizePollerTimeout) {
|
||
window.clearTimeout(resizePollerTimeout);
|
||
}
|
||
resizePollerTimeout = window.setTimeout(pollResize, iframeShimsOpts.pictureDelayPollIntervalMS);
|
||
}
|
||
};
|
||
if (interactiveMode !== 'interactive') {
|
||
return iframeShims;
|
||
}
|
||
var detectorTarget = viewport;
|
||
var detector = new GestureDetector(detectorTarget);
|
||
detector.startDetecting();
|
||
if (clickHandler) {
|
||
viewport.removeEventListener('click', clickHandler);
|
||
bindSanitizedClickHandler(viewport, clickHandler, null, iframe);
|
||
}
|
||
var title = document.getElementsByClassName('msg-reader-header')[0];
|
||
var header = document.getElementsByClassName('msg-envelope-bar')[0];
|
||
var extraHeight = title.clientHeight + header.clientHeight;
|
||
detectorTarget.addEventListener('dbltap', function (e) {
|
||
var newScale = scale;
|
||
if (lastDoubleTapScale === lastRequestedScale) {
|
||
scaleMode = (scaleMode + 1) % 3;
|
||
switch (scaleMode) {
|
||
case 0:
|
||
newScale = baseScale;
|
||
break;
|
||
case 1:
|
||
newScale = 1;
|
||
break;
|
||
case 2:
|
||
newScale = 2;
|
||
break;
|
||
}
|
||
console.log('already in double-tap, deciding on new scale', newScale);
|
||
} else {
|
||
if (lastRequestedScale > 1) {
|
||
newScale = lastDoubleTapScale;
|
||
scaleMode = 0;
|
||
} else {
|
||
newScale = 2;
|
||
scaleMode = 2;
|
||
}
|
||
console.log('user was not in double-tap switching to double-tap with', newScale);
|
||
}
|
||
lastDoubleTapScale = newScale;
|
||
try {
|
||
zoomFrame(newScale, e.detail.clientX, e.detail.clientY);
|
||
} catch (ex) {
|
||
console.error('zoom bug!', ex, '\n', ex.stack);
|
||
}
|
||
});
|
||
var transformDone = false;
|
||
detectorTarget.addEventListener('transformend', function (e) {
|
||
transformDone = false;
|
||
});
|
||
detectorTarget.addEventListener('transform', function (e) {
|
||
if (transformDone) {
|
||
return;
|
||
}
|
||
var scaleFactor = e.detail.absolute.scale;
|
||
var newScale = lastRequestedScale;
|
||
if (scaleFactor > 1.15) {
|
||
transformDone = true;
|
||
if (lastRequestedScale < 1) {
|
||
newScale = 1;
|
||
} else if (lastRequestedScale < 1.5) {
|
||
newScale = 1.5;
|
||
} else if (lastRequestedScale < 2) {
|
||
newScale = 2;
|
||
} else {
|
||
return;
|
||
}
|
||
} else if (scaleFactor < 0.9) {
|
||
transformDone = true;
|
||
if (lastRequestedScale > 1.5) {
|
||
newScale = 1.5;
|
||
} else if (lastRequestedScale > 1) {
|
||
newScale = 1;
|
||
} else if (lastRequestedScale > baseScale) {
|
||
newScale = baseScale;
|
||
} else {
|
||
return;
|
||
}
|
||
} else {
|
||
return;
|
||
}
|
||
zoomFrame(newScale, e.detail.midpoint.clientX, e.detail.midpoint.clientY);
|
||
});
|
||
return iframeShims;
|
||
}
|
||
function bindSanitizedClickHandler(target, clickHandler, topNode, iframe) {
|
||
var eventType, node;
|
||
var root, title, header, attachmentsContainer, msgBodyContainer, titleHeight, headerHeight, attachmentsHeight, msgBodyMarginTop, msgBodyMarginLeft, attachmentsMarginTop, iframeDoc, inputStyle, loadBar, loadBarHeight;
|
||
if (iframe) {
|
||
root = document.getElementsByClassName('scrollregion-horizontal-too')[0];
|
||
title = document.getElementsByClassName('msg-reader-header')[0];
|
||
header = document.getElementsByClassName('msg-envelope-bar')[0];
|
||
attachmentsContainer = document.getElementsByClassName('msg-attachments-container')[0];
|
||
loadBar = document.getElementsByClassName('msg-reader-load-infobar')[0];
|
||
msgBodyContainer = document.getElementsByClassName('msg-body-container')[0];
|
||
inputStyle = window.getComputedStyle(msgBodyContainer);
|
||
msgBodyMarginTop = parseInt(inputStyle.marginTop);
|
||
msgBodyMarginLeft = parseInt(inputStyle.marginLeft);
|
||
titleHeight = title.clientHeight;
|
||
headerHeight = header.clientHeight;
|
||
eventType = 'tap';
|
||
iframeDoc = iframe.contentDocument;
|
||
} else {
|
||
eventType = 'click';
|
||
}
|
||
target.addEventListener(eventType, function clicked(event) {
|
||
if (iframe) {
|
||
loadBarHeight = loadBar.clientHeight;
|
||
attachmentsHeight = attachmentsContainer.clientHeight;
|
||
inputStyle = window.getComputedStyle(attachmentsContainer);
|
||
attachmentsMarginTop = attachmentsHeight ? parseInt(inputStyle.marginTop) : 0;
|
||
var dx, dy;
|
||
var transform = iframe.style.transform || 'scale(1)';
|
||
var scale = transform.match(/(\d|\.)+/g)[0];
|
||
if (document.dir === 'rtl') {
|
||
dx = event.detail.clientX - msgBodyMarginLeft + root.scrollWidth + root.scrollLeft - root.clientWidth;
|
||
} else {
|
||
dx = event.detail.clientX + root.scrollLeft - msgBodyMarginLeft;
|
||
}
|
||
dy = event.detail.clientY + root.scrollTop - titleHeight - headerHeight - loadBarHeight - attachmentsHeight - attachmentsMarginTop - msgBodyMarginTop;
|
||
node = iframeDoc.elementFromPoint(dx / scale, dy / scale);
|
||
} else {
|
||
node = event.originalTarget;
|
||
}
|
||
while (node !== topNode) {
|
||
if (node.nodeName === 'A') {
|
||
if (node.hasAttribute('ext-href')) {
|
||
clickHandler(event, node, node.getAttribute('ext-href'), node.textContent);
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
return;
|
||
}
|
||
}
|
||
node = node.parentNode;
|
||
}
|
||
});
|
||
}
|
||
return {
|
||
createAndInsertIframeForContent: createAndInsertIframeForContent,
|
||
bindSanitizedClickHandler: bindSanitizedClickHandler,
|
||
iframeShimsOpts: iframeShimsOpts
|
||
};
|
||
});
|
||
define('cards/editor_mixins', ['require'], function (require) {
|
||
return {
|
||
_bindEditor: function (textNode) {
|
||
this._editorNode = textNode;
|
||
textNode.addEventListener('paste', function (event) {
|
||
event.preventDefault();
|
||
var text = event.clipboardData.getData('text/plain');
|
||
if (text) {
|
||
document.execCommand('insertText', false, text);
|
||
}
|
||
});
|
||
},
|
||
populateEditor: function (value) {
|
||
var lines = value.split('\n');
|
||
var frag = document.createDocumentFragment();
|
||
for (var i = 0, len = lines.length; i < len; i++) {
|
||
if (i) {
|
||
frag.appendChild(document.createElement('br'));
|
||
}
|
||
if (lines[i]) {
|
||
frag.appendChild(document.createTextNode(lines[i]));
|
||
}
|
||
}
|
||
if (!frag.childNodes.length) {
|
||
frag.appendChild(document.createTextNode(''));
|
||
}
|
||
this._editorNode.appendChild(frag);
|
||
},
|
||
fromEditor: function (value) {
|
||
var content = '';
|
||
var len = this._editorNode.childNodes.length;
|
||
for (var i = 0; i < len; i++) {
|
||
var node = this._editorNode.childNodes[i];
|
||
if (node.nodeName === 'BR' && node.getAttribute('type') !== '_moz') {
|
||
content += '\n';
|
||
} else {
|
||
content += node.textContent;
|
||
}
|
||
}
|
||
return content;
|
||
}
|
||
};
|
||
});
|
||
define('date', [
|
||
'require',
|
||
'moz_intl'
|
||
], function (require) {
|
||
const mozIntl = require('moz_intl');
|
||
var shortRelativeDateFmt, longRelativeDateFmt;
|
||
function setRelativeDateFormatters() {
|
||
shortRelativeDateFmt = mozIntl._gaia.RelativeDate(navigator.languages, { style: 'short' });
|
||
longRelativeDateFmt = mozIntl._gaia.RelativeDate(navigator.languages, { style: 'long' });
|
||
}
|
||
setRelativeDateFormatters();
|
||
window.addEventListener('languagechange', setRelativeDateFormatters);
|
||
var date = {
|
||
relativeDateElement: function (element, time, useCompactFormat) {
|
||
if (time) {
|
||
var f = useCompactFormat ? shortRelativeDateFmt : longRelativeDateFmt;
|
||
f.formatElement(element, time);
|
||
} else {
|
||
element.textContent = '';
|
||
}
|
||
},
|
||
setPrettyNodeDate: function (node, timestamp) {
|
||
if (timestamp) {
|
||
node.dataset.time = timestamp.valueOf();
|
||
node.dataset.compactFormat = true;
|
||
date.relativeDateElement(node, timestamp, true);
|
||
} else {
|
||
node.textContent = '';
|
||
node.removeAttribute('data-time');
|
||
}
|
||
}
|
||
};
|
||
return date;
|
||
});
|
||
define('cards/lst/default_vscroll_data', {
|
||
'isPlaceholderData': true,
|
||
'id': 'INVALID',
|
||
'author': {
|
||
'name': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583',
|
||
'address': '',
|
||
'contactId': null
|
||
},
|
||
'to': [{
|
||
'name': ' ',
|
||
'address': ' ',
|
||
'contactId': null
|
||
}],
|
||
'cc': null,
|
||
'bcc': null,
|
||
'date': '0',
|
||
'hasAttachments': false,
|
||
'snippet': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583',
|
||
'isRead': true,
|
||
'isStarred': false,
|
||
'sendStatus': {},
|
||
'subject': '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583' + '\u2583\u2583\u2583\u2583\u2583\u2583\u2583\u2583'
|
||
});
|
||
define('message_list_topbar', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'l10n!',
|
||
'transition_end'
|
||
], function (require, exports, module) {
|
||
var mozL10n = require('l10n!'), transitionEnd = require('transition_end');
|
||
var proto = {
|
||
domNode: null,
|
||
_scrollContainer: null,
|
||
_vScroll: null,
|
||
_delayedState: null,
|
||
_newEmailCount: 0,
|
||
_scrollTop: 0,
|
||
_thresholdMultiplier: 2,
|
||
visibleOffset: 0,
|
||
createdCallback: function () {
|
||
this.domNode.addEventListener('click', this._onClick.bind(this));
|
||
transitionEnd(this.domNode, this._onTransitionEnd.bind(this));
|
||
},
|
||
resetNodeForCache: function (node) {
|
||
node.classList.remove('closing');
|
||
node.textContent = '';
|
||
node.dataset.state = '';
|
||
this.domNode.style.left = '';
|
||
},
|
||
bindToElements: function (scrollContainer, vScroll) {
|
||
this._scrollContainer = scrollContainer;
|
||
this._scrollContainer.addEventListener('scroll', this._onScroll.bind(this));
|
||
this._scrollTop = this._scrollContainer.scrollTop;
|
||
this._vScroll = vScroll;
|
||
},
|
||
showNewEmailCount: function (newEmailCount) {
|
||
if (this._scrollTop <= this.visibleOffset) {
|
||
return;
|
||
}
|
||
this._newEmailCount = newEmailCount;
|
||
this._showState('message');
|
||
},
|
||
_getState: function () {
|
||
return this.domNode.dataset.state;
|
||
},
|
||
_showState: function (state) {
|
||
var nodeState = this._getState();
|
||
if (nodeState === state) {
|
||
return;
|
||
} else if (!nodeState || !state) {
|
||
this._animateState(state);
|
||
} else if (nodeState !== state && (nodeState !== 'message' || state !== 'top')) {
|
||
this._delayedState = state;
|
||
if (!this._animating) {
|
||
this._animateState('');
|
||
}
|
||
}
|
||
},
|
||
_animateState: function (state) {
|
||
this._animating = true;
|
||
if (!state && this._getState()) {
|
||
this.domNode.classList.add('closing');
|
||
} else {
|
||
this.domNode.classList.add('no-anim');
|
||
this.domNode.classList.toggle('horiz-message', state === 'message');
|
||
this.domNode.classList.toggle('horiz-top', state === 'top');
|
||
this.domNode.clientWidth;
|
||
if (state === 'message') {
|
||
mozL10n.setAttributes(this.domNode, 'new-messages', { n: this._newEmailCount });
|
||
} else if (state === 'top') {
|
||
mozL10n.setAttributes(this.domNode, 'message-list-top-action');
|
||
}
|
||
this.domNode.classList.remove('no-anim');
|
||
this.domNode.clientWidth;
|
||
this.domNode.dataset.state = state;
|
||
}
|
||
},
|
||
_onTransitionEnd: function (evt) {
|
||
this._animating = false;
|
||
if (this.domNode.classList.contains('closing')) {
|
||
this.domNode.classList.remove('closing');
|
||
this.domNode.dataset.state = '';
|
||
this.domNode.style.left = '';
|
||
}
|
||
if (this._delayedState) {
|
||
this._animateState(this._delayedState);
|
||
this._delayedState = null;
|
||
}
|
||
},
|
||
_onScroll: function (evt) {
|
||
if (!this._topThreshold) {
|
||
var rect = this._scrollContainer.getBoundingClientRect();
|
||
this._topThreshold = rect.height * this._thresholdMultiplier;
|
||
this.domNode.style.top = rect.top + 'px';
|
||
}
|
||
if (this._vScroll.lastScrollTopSetTime && this._vScroll.lastScrollTopSetTime + 500 > Date.now()) {
|
||
return;
|
||
}
|
||
var scrollTop = this._scrollContainer.scrollTop, scrollingDown = scrollTop > this._scrollTop, nodeState = this._getState();
|
||
if (scrollTop !== this._scrollTop) {
|
||
if (scrollTop <= this.visibleOffset) {
|
||
this._showState('');
|
||
} else if (scrollingDown) {
|
||
this._showState('');
|
||
} else if (nodeState !== 'top' && scrollTop > this._topThreshold) {
|
||
this._showState('top');
|
||
}
|
||
}
|
||
this._scrollTop = scrollTop;
|
||
},
|
||
_onClick: function (evt) {
|
||
if (this._vScroll) {
|
||
this._vScroll.jumpToIndex(0);
|
||
this._showState('');
|
||
}
|
||
}
|
||
};
|
||
function MessageListTopBar(domNode) {
|
||
this.domNode = domNode;
|
||
this.createdCallback();
|
||
}
|
||
MessageListTopBar.prototype = proto;
|
||
return MessageListTopBar;
|
||
});
|
||
define('message_display', [
|
||
'require',
|
||
'l10n!'
|
||
], function (require) {
|
||
var mozL10n = require('l10n!');
|
||
return {
|
||
subject: function (subjectNode, message) {
|
||
var subject = message.subject && message.subject.trim();
|
||
if (subject) {
|
||
subjectNode.textContent = subject;
|
||
subjectNode.classList.remove('msg-no-subject');
|
||
subjectNode.removeAttribute('data-l10n-id');
|
||
} else {
|
||
mozL10n.setAttributes(subjectNode, 'message-no-subject');
|
||
subjectNode.classList.add('msg-no-subject');
|
||
}
|
||
}
|
||
};
|
||
});
|
||
define('cards/lst/peep_dom', [], function () {
|
||
return {
|
||
update: function (peep) {
|
||
peep.element.textContent = peep.name || peep.address;
|
||
}
|
||
};
|
||
});
|
||
define('vscroll', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'evt'
|
||
], function (require, exports, module) {
|
||
var evt = require('evt'), slice = Array.prototype.slice, useTransform = false;
|
||
function setTop(node, value) {
|
||
if (useTransform) {
|
||
node.style.transform = 'translateY(' + value + 'px)';
|
||
} else {
|
||
node.style.top = value + 'px';
|
||
}
|
||
}
|
||
function VScroll(container, scrollingContainer, template, defaultData) {
|
||
evt.Emitter.call(this);
|
||
this.container = container;
|
||
this.scrollingContainer = scrollingContainer;
|
||
this.template = template;
|
||
this.defaultData = defaultData;
|
||
this._inited = false;
|
||
this._capturedScreenMetrics = false;
|
||
this.firstRenderedIndex = 0;
|
||
this._limited = false;
|
||
this.nodes = [];
|
||
this.nodesDataIndices = {};
|
||
this.nodesIndex = -1;
|
||
this.scrollTop = 0;
|
||
this.visibleOffset = 0;
|
||
this.oldListSize = 0;
|
||
this._lastEventTime = 0;
|
||
this.onEvent = this.onEvent.bind(this);
|
||
this.onChange = this.onChange.bind(this);
|
||
this._scrollTimeoutPoll = this._scrollTimeoutPoll.bind(this);
|
||
}
|
||
VScroll.nodeClassName = 'vscroll-node';
|
||
VScroll.trimMessagesForCache = function (container, itemLimit) {
|
||
var nodes = slice.call(container.querySelectorAll('.' + VScroll.nodeClassName));
|
||
nodes.forEach(function (node) {
|
||
var index = parseInt(node.dataset.index, 10);
|
||
delete node.dataset.index;
|
||
if (index > itemLimit - 1) {
|
||
container.removeChild(node);
|
||
}
|
||
});
|
||
};
|
||
VScroll.prototype = {
|
||
eventRateLimitMillis: 0,
|
||
itemsPerScreen: undefined,
|
||
prerenderScreens: 3,
|
||
prefetchScreens: 2,
|
||
retainExtraRenderedScreens: 3,
|
||
recalculatePaddingScreens: 1.5,
|
||
lastScrollTopSetTime: 0,
|
||
prerenderItemCount: undefined,
|
||
prefetchItemCount: undefined,
|
||
recalculatePaddingItemCount: undefined,
|
||
itemDefaultDataClass: 'default-data',
|
||
prepareData: function (highAbsoluteIndex) {
|
||
},
|
||
bindData: function (model, node) {
|
||
},
|
||
setData: function (list) {
|
||
this.list = list;
|
||
if (this._inited) {
|
||
if (!this.waitingForRecalculate) {
|
||
this._recalculate(0);
|
||
}
|
||
this.emit('dataChanged');
|
||
} else {
|
||
this._init();
|
||
this.renderCurrentPosition();
|
||
}
|
||
},
|
||
updateDataBind: function (index, dataList, removedCount) {
|
||
if (!this._inited) {
|
||
return;
|
||
}
|
||
if (this.oldListSize !== this.list.size() || removedCount) {
|
||
if (!this.waitingForRecalculate) {
|
||
this.waitingForRecalculate = true;
|
||
this.once('scrollStopped', function () {
|
||
this._recalculate(index);
|
||
}.bind(this));
|
||
}
|
||
return;
|
||
}
|
||
for (var i = 0; i < dataList.length; i++) {
|
||
var absoluteIndex = index + i;
|
||
var node = this._getNodeFromDataIndex(absoluteIndex);
|
||
if (node) {
|
||
this.bindData(dataList[i], node);
|
||
}
|
||
}
|
||
},
|
||
onEvent: function () {
|
||
this._lastEventTime = Date.now();
|
||
if (!this.eventRateLimitMillis) {
|
||
this.onChange();
|
||
return;
|
||
}
|
||
if (this._limited) {
|
||
return;
|
||
}
|
||
this._limited = true;
|
||
setTimeout(this.onChange, this.eventRateLimitMillis);
|
||
},
|
||
onChange: function () {
|
||
this._limited = false;
|
||
if (!this._inited) {
|
||
return;
|
||
}
|
||
if (this.lastScrollTopSetTime) {
|
||
if (this.lastScrollTopSetTime + 1000 < Date.now()) {
|
||
this.lastScrollTopSetTime = 0;
|
||
}
|
||
}
|
||
var startIndex, endIndex, scrollTop = this.scrollingContainer.scrollTop, scrollingDown = scrollTop >= this.scrollTop;
|
||
this.scrollTop = scrollTop;
|
||
var visibleRange = this.getVisibleIndexRange();
|
||
if (scrollingDown) {
|
||
startIndex = visibleRange[0];
|
||
endIndex = visibleRange[1] + this.prerenderItemCount;
|
||
this.prepareData(endIndex + this.prefetchItemCount);
|
||
} else {
|
||
startIndex = visibleRange[0] - this.prerenderItemCount;
|
||
endIndex = visibleRange[1];
|
||
}
|
||
this._render(startIndex, endIndex);
|
||
this._startScrollStopPolling();
|
||
},
|
||
nowVisible: function () {
|
||
if (!this._inited && this.list) {
|
||
this._init();
|
||
this.onChange();
|
||
}
|
||
},
|
||
renderCurrentPosition: function () {
|
||
if (!this._inited) {
|
||
return;
|
||
}
|
||
var scrollTop = this.scrollingContainer.scrollTop;
|
||
this.scrollTop = scrollTop;
|
||
var visibleRange = this.getVisibleIndexRange();
|
||
var startIndex = visibleRange[0] - this.recalculatePaddingItemCount;
|
||
var endIndex = visibleRange[1] + this.recalculatePaddingItemCount;
|
||
this._render(startIndex, endIndex);
|
||
this.prepareData(endIndex);
|
||
},
|
||
indexAtScrollPosition: function (position) {
|
||
var top = position - this.visibleOffset;
|
||
if (top < 0) {
|
||
top = 0;
|
||
}
|
||
return this.itemHeight ? Math.floor(top / this.itemHeight) : 0;
|
||
},
|
||
getVisibleIndexRange: function () {
|
||
if (this.itemHeight === undefined) {
|
||
return undefined;
|
||
}
|
||
var top = this.scrollTop;
|
||
return [
|
||
this.indexAtScrollPosition(top),
|
||
this.indexAtScrollPosition(top + this.innerHeight)
|
||
];
|
||
},
|
||
jumpToIndex: function (index) {
|
||
this._setContainerScrollTop(index * this.itemHeight + this.visibleOffset);
|
||
},
|
||
clearDisplay: function () {
|
||
this.container.innerHTML = '';
|
||
this.container.style.height = '0px';
|
||
this.oldListSize = 0;
|
||
},
|
||
destroy: function () {
|
||
this.scrollingContainer.removeEventListener('scroll', this.onEvent);
|
||
if (this._scrollTimeoutPoll) {
|
||
clearTimeout(this._scrollTimeoutPoll);
|
||
this._scrollTimeoutPoll = 0;
|
||
}
|
||
},
|
||
_setContainerScrollTop: function (value) {
|
||
this.scrollingContainer.scrollTop = value;
|
||
this.lastScrollTopSetTime = Date.now();
|
||
},
|
||
_render: function (startIndex, endIndex) {
|
||
var i, listSize = this.list.size();
|
||
if (startIndex < 0) {
|
||
startIndex = 0;
|
||
}
|
||
if (endIndex >= listSize) {
|
||
endIndex = listSize - 1;
|
||
}
|
||
this.firstRenderedIndex = startIndex;
|
||
if (!this._inited) {
|
||
this._init();
|
||
}
|
||
for (i = startIndex; i <= endIndex; i++) {
|
||
if (this._getNodeFromDataIndex(i)) {
|
||
continue;
|
||
}
|
||
var node = this._nextAvailableNode(startIndex, endIndex), data = this.list(i);
|
||
if (!data) {
|
||
data = this.defaultData;
|
||
}
|
||
if (node.parentNode) {
|
||
node.parentNode.removeChild(node);
|
||
}
|
||
setTop(node, i * this.itemHeight);
|
||
this._setNodeDataIndex(this.nodesIndex, i);
|
||
this.bindData(data, node);
|
||
this.container.appendChild(node);
|
||
}
|
||
},
|
||
_setNodeDataIndex: function (nodesIndex, dataIndex) {
|
||
var oldDataIndex = this.nodes[nodesIndex].vScrollDataIndex;
|
||
if (oldDataIndex > -1) {
|
||
this.nodesDataIndices[oldDataIndex] = -1;
|
||
}
|
||
var node = this.nodes[nodesIndex];
|
||
node.vScrollDataIndex = dataIndex;
|
||
node.dataset.index = dataIndex;
|
||
this.nodesDataIndices[dataIndex] = nodesIndex;
|
||
},
|
||
_getNodeFromDataIndex: function (dataIndex) {
|
||
var index = this.nodesDataIndices[dataIndex];
|
||
if (index === undefined) {
|
||
index = -1;
|
||
}
|
||
return index === -1 ? null : this.nodes[index];
|
||
},
|
||
captureScreenMetrics: function () {
|
||
if (this._capturedScreenMetrics) {
|
||
return;
|
||
}
|
||
this.innerHeight = this.scrollingContainer.getBoundingClientRect().height;
|
||
if (this.innerHeight > 0) {
|
||
this._capturedScreenMetrics = true;
|
||
}
|
||
},
|
||
_init: function () {
|
||
if (this._inited) {
|
||
return;
|
||
}
|
||
this.container.innerHTML = '';
|
||
var node = this.template.cloneNode(true);
|
||
this.container.appendChild(node);
|
||
this.itemHeight = node.clientHeight;
|
||
this.container.removeChild(node);
|
||
this.captureScreenMetrics();
|
||
if (!this.itemHeight || !this.innerHeight) {
|
||
return;
|
||
}
|
||
this.scrollingContainer.addEventListener('scroll', this.onEvent);
|
||
this.itemsPerScreen = Math.ceil(this.innerHeight / this.itemHeight);
|
||
this.prerenderItemCount = Math.ceil(this.itemsPerScreen * this.prerenderScreens);
|
||
this.prefetchItemCount = Math.ceil(this.itemsPerScreen * this.prefetchScreens);
|
||
this.recalculatePaddingItemCount = Math.ceil(this.itemsPerScreen * this.recalculatePaddingScreens);
|
||
this.nodeCount = this.itemsPerScreen + this.prerenderItemCount + Math.ceil(this.retainExtraRenderedScreens * this.itemsPerScreen);
|
||
for (var i = 0; i < this.nodeCount; i++) {
|
||
node = this.template.cloneNode(true);
|
||
node.classList.add(VScroll.nodeClassName);
|
||
setTop(node, -1 * this.itemHeight);
|
||
this.nodes.push(node);
|
||
this._setNodeDataIndex(i, -1);
|
||
}
|
||
this._calculateTotalHeight();
|
||
this._inited = true;
|
||
this.emit('inited');
|
||
},
|
||
_nextAvailableNode: function (beginIndex, endIndex) {
|
||
var i, node, vScrollDataIndex, count = 0;
|
||
for (i = this.nodesIndex + 1; count < this.nodes.length; count++, i++) {
|
||
if (i > this.nodes.length - 1) {
|
||
i = 0;
|
||
}
|
||
node = this.nodes[i];
|
||
vScrollDataIndex = node.vScrollDataIndex;
|
||
if (vScrollDataIndex < beginIndex || vScrollDataIndex > endIndex) {
|
||
this.nodesIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
return node;
|
||
},
|
||
_recalculate: function (refIndex) {
|
||
if (!this._inited) {
|
||
return;
|
||
}
|
||
var node, index = this.indexAtScrollPosition(this.scrollTop), remainder = this.scrollTop % this.itemHeight, sizeDiff = this.list.size() - this.oldListSize;
|
||
if (refIndex && refIndex < index && sizeDiff > 0 && this.oldListSize !== 0 && index !== 0) {
|
||
index += sizeDiff;
|
||
}
|
||
console.log('VSCROLL scrollTop: ' + this.scrollTop + ', RECALCULATE: ' + index + ', ' + remainder);
|
||
this._calculateTotalHeight();
|
||
for (var i = 0; i < this.nodeCount; i++) {
|
||
node = this.nodes[i];
|
||
setTop(node, -1 * this.itemHeight);
|
||
this._setNodeDataIndex(i, -1);
|
||
}
|
||
this.waitingForRecalculate = false;
|
||
this._setContainerScrollTop(this.itemHeight * index + remainder);
|
||
this.renderCurrentPosition();
|
||
this.emit('recalculated', index === 0, refIndex);
|
||
},
|
||
_calculateTotalHeight: function () {
|
||
var newListSize = this.list.size();
|
||
if (this.oldListSize !== newListSize || parseInt(this.container.style.height, 10) === 0) {
|
||
this.totalHeight = this.itemHeight * newListSize;
|
||
this.container.style.height = this.totalHeight + 'px';
|
||
this.oldListSize = newListSize;
|
||
}
|
||
},
|
||
_scrollTimeoutPoll: function () {
|
||
this._scrollStopTimeout = 0;
|
||
if (Date.now() > this._lastEventTime + 300) {
|
||
this.emit('scrollStopped');
|
||
} else {
|
||
this._scrollStopTimeout = setTimeout(this._scrollTimeoutPoll, 300);
|
||
}
|
||
},
|
||
_startScrollStopPolling: function () {
|
||
if (!this._scrollStopTimeout) {
|
||
this._scrollStopTimeout = setTimeout(this._scrollTimeoutPoll, 300);
|
||
}
|
||
}
|
||
};
|
||
evt.mix(VScroll.prototype);
|
||
var originalOn = VScroll.prototype.on;
|
||
VScroll.prototype.on = function (id, fn) {
|
||
if (id === 'scrollStopped') {
|
||
this._startScrollStopPolling();
|
||
}
|
||
return originalOn.apply(this, slice.call(arguments));
|
||
};
|
||
return VScroll;
|
||
});
|
||
define('cards/mixins/data-prop', [], function () {
|
||
return {
|
||
templateInsertedCallback: function () {
|
||
var node, parent, nodes = this.querySelectorAll('[data-prop]'), length = nodes.length;
|
||
for (var i = 0; i < length; i++) {
|
||
node = nodes[i];
|
||
parent = node;
|
||
while (parent = parent.parentNode) {
|
||
if (parent.nodeName.indexOf('-') !== -1) {
|
||
if (parent !== this) {
|
||
node = null;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
if (node && parent) {
|
||
this[node.dataset.prop] = nodes[i];
|
||
}
|
||
}
|
||
}
|
||
};
|
||
});
|
||
define('cards/mixins/data-event', [], function () {
|
||
var slice = Array.prototype.slice;
|
||
return {
|
||
templateInsertedCallback: function () {
|
||
slice.call(this.querySelectorAll('[data-event]')).forEach(function (node) {
|
||
var parent = node;
|
||
while (parent = parent.parentNode) {
|
||
if (parent.nodeName.indexOf('-') !== -1) {
|
||
if (parent !== this) {
|
||
return;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
if (!parent) {
|
||
return;
|
||
}
|
||
node.dataset.event.split(',').forEach(function (pair) {
|
||
var evtName, method, parts = pair.split(':');
|
||
if (!parts[1]) {
|
||
parts[1] = parts[0];
|
||
}
|
||
evtName = parts[0].trim();
|
||
method = parts[1].trim();
|
||
if (typeof this[method] !== 'function') {
|
||
throw new Error('"' + method + '" is not a function, cannot bind with data-event');
|
||
}
|
||
node.addEventListener(evtName, function (evt) {
|
||
evt.stopPropagation();
|
||
return this[method](evt);
|
||
}.bind(this), false);
|
||
}.bind(this));
|
||
}.bind(this));
|
||
}
|
||
};
|
||
});
|
||
define('cards/base', [
|
||
'require',
|
||
'evt',
|
||
'./mixins/data-prop',
|
||
'./mixins/data-event'
|
||
], function (require) {
|
||
var Emitter = require('evt').Emitter;
|
||
return function base(templateMixins) {
|
||
return [
|
||
templateMixins ? templateMixins : {},
|
||
require('./mixins/data-prop'),
|
||
require('./mixins/data-event'),
|
||
Emitter.prototype,
|
||
{
|
||
createdCallback: function () {
|
||
this.classList.add('email-ce');
|
||
Emitter.call(this);
|
||
},
|
||
emitDomEvent: function (eventName, detail) {
|
||
this.dispatchEvent(new CustomEvent(eventName, { detail: detail }));
|
||
}
|
||
}
|
||
];
|
||
};
|
||
});
|
||
define('cards/base_card', [
|
||
'require',
|
||
'date',
|
||
'l10n!',
|
||
'./base'
|
||
], function (require) {
|
||
var date = require('date'), mozL10n = require('l10n!'), base = require('./base');
|
||
(function () {
|
||
var updatePrettyDate = function updatePrettyDate() {
|
||
var labels = [...document.querySelectorAll('[data-time]')];
|
||
labels.forEach(label => {
|
||
date.relativeDateElement(label, label.dataset.time);
|
||
});
|
||
};
|
||
var timer = setInterval(updatePrettyDate, 60 * 1000);
|
||
function updatePrettyDateOnEvent() {
|
||
clearInterval(timer);
|
||
updatePrettyDate();
|
||
timer = setInterval(updatePrettyDate, 60 * 1000);
|
||
}
|
||
mozL10n.ready(updatePrettyDateOnEvent);
|
||
document.addEventListener('visibilitychange', function () {
|
||
if (document && !document.hidden) {
|
||
updatePrettyDateOnEvent();
|
||
} else {
|
||
clearInterval(timer);
|
||
}
|
||
});
|
||
}());
|
||
return function baseCard(templateMixins) {
|
||
return [
|
||
base(templateMixins),
|
||
{
|
||
createdCallback: function () {
|
||
if (this.extraClasses) {
|
||
this.classList.add.apply(this.classList, this.extraClasses);
|
||
}
|
||
this.classList.add('card');
|
||
},
|
||
batchAddClass: function (searchClass, classToAdd) {
|
||
var nodes = this.getElementsByClassName(searchClass);
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
nodes[i].classList.add(classToAdd);
|
||
}
|
||
}
|
||
}
|
||
];
|
||
};
|
||
});
|
||
define('element', [
|
||
'require',
|
||
'exports',
|
||
'module'
|
||
], function (require, exports, module) {
|
||
'use strict';
|
||
var slice = Array.prototype.slice, callbackSuffix = 'Callback', callbackSuffixLength = callbackSuffix.length, charRegExp = /[^a-z]/g, idToTag = function (id) {
|
||
return id.toLowerCase().replace(charRegExp, '-');
|
||
}, moduleConfig = module.config();
|
||
if (moduleConfig.hasOwnProperty('idToTag')) {
|
||
idToTag = moduleConfig.idToTag;
|
||
}
|
||
function makePropName(attrName) {
|
||
var parts = attrName.split('-');
|
||
for (var i = 1; i < parts.length; i++) {
|
||
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
|
||
}
|
||
return parts.join('');
|
||
}
|
||
function setPropFromAttr(instance, attrName, attrValue) {
|
||
var proto = Object.getPrototypeOf(instance), propName = makePropName(attrName), descriptor = Object.getOwnPropertyDescriptor(proto, propName);
|
||
if (descriptor && descriptor.set) {
|
||
instance[propName] = attrValue;
|
||
}
|
||
}
|
||
function makePropFn(prop) {
|
||
return function () {
|
||
var i, ret, args = slice.call(arguments), fns = this._element.props[prop];
|
||
for (i = 0; i < fns.length; i++) {
|
||
ret = fns[i].apply(this, args);
|
||
}
|
||
return ret;
|
||
};
|
||
}
|
||
function mixFnProp(proto, prop, value, operation) {
|
||
if (proto.hasOwnProperty(prop)) {
|
||
var existing = proto._element.props[prop];
|
||
if (!existing) {
|
||
existing = proto._element.props[prop] = [proto[prop]];
|
||
proto[prop] = makePropFn(prop);
|
||
}
|
||
operation = operation || 'push';
|
||
existing[operation](value);
|
||
} else {
|
||
proto[prop] = value;
|
||
}
|
||
}
|
||
function mix(proto, mixin) {
|
||
if (Array.isArray(mixin)) {
|
||
mixin.forEach(function (mixin) {
|
||
mix(proto, mixin);
|
||
});
|
||
return;
|
||
}
|
||
Object.keys(mixin).forEach(function (key) {
|
||
var suffixIndex, descriptor = Object.getOwnPropertyDescriptor(mixin, key);
|
||
suffixIndex = key.indexOf(callbackSuffix);
|
||
if (suffixIndex > 0 && suffixIndex === key.length - callbackSuffixLength) {
|
||
mixFnProp(proto, key, descriptor.value);
|
||
} else {
|
||
Object.defineProperty(proto, key, descriptor);
|
||
}
|
||
});
|
||
}
|
||
var element = {
|
||
load: function (id, req, onload, config) {
|
||
req([id], function (mod) {
|
||
if (config.isBuild || !mod || typeof mod === 'function') {
|
||
return onload();
|
||
}
|
||
var proto = Object.create(HTMLElement.prototype);
|
||
Object.defineProperty(proto, '_element', {
|
||
enumerable: false,
|
||
configurable: false,
|
||
writable: false,
|
||
value: {}
|
||
});
|
||
proto._element.props = {};
|
||
mix(proto, mod);
|
||
mixFnProp(proto, 'createdCallback', function attrCreated() {
|
||
var i, item, attrs = this.attributes;
|
||
for (i = 0; i < attrs.length; i++) {
|
||
item = attrs.item(i);
|
||
setPropFromAttr(this, item.nodeName, item.value);
|
||
}
|
||
}, 'unshift');
|
||
mixFnProp(proto, 'attributeChangedCallback', function attrChanged(name, oldValue, newValue) {
|
||
setPropFromAttr(this, name, newValue);
|
||
}, 'unshift');
|
||
var tagId = idToTag(id);
|
||
onload(document.registerElement(tagId, { prototype: proto }));
|
||
});
|
||
}
|
||
};
|
||
return element;
|
||
});
|
||
define('template', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'element'
|
||
], function (require, exports, module) {
|
||
var template, fetchText, isReady = false, readyQueue = [], tagRegExp = /<(\w+-[\w-]+)(\s|>)/g, commentRegExp = /<!--*.?-->/g, attrIdRegExp = /\s(hrefid|srcid)="([^"]+)"/g, buildProtocol = 'build:', moduleConfig = module.config(), depPrefix = 'element!', buildMap = {}, tagToId = function (tag) {
|
||
return tag;
|
||
};
|
||
require('element');
|
||
if (moduleConfig.hasOwnProperty('depPrefix')) {
|
||
depPrefix = moduleConfig.depPrefix;
|
||
}
|
||
if (moduleConfig.hasOwnProperty('tagToId')) {
|
||
tagToId = moduleConfig.tagToId;
|
||
}
|
||
function onReady() {
|
||
isReady = true;
|
||
var bodyTemplate = document.querySelector('template#body');
|
||
if (bodyTemplate) {
|
||
bodyTemplate.parentNode.removeChild(bodyTemplate);
|
||
document.body.innerHTML = bodyTemplate.innerHTML;
|
||
}
|
||
readyQueue.forEach(function (fn) {
|
||
fn();
|
||
});
|
||
readyQueue = [];
|
||
}
|
||
function makeFullId(id, refId) {
|
||
if (id.indexOf('.') === 0 && refId) {
|
||
var parts = refId.split('/');
|
||
parts.pop();
|
||
refId = parts.join('/');
|
||
id = (refId ? refId + '/' : '') + id;
|
||
}
|
||
return id;
|
||
}
|
||
function templateCreatedCallback() {
|
||
if (this.dataset.cached === 'cached' || this.template) {
|
||
if (this.dataset.cached !== 'cached' && this.template) {
|
||
this.innerHTML = '';
|
||
this.appendChild(this.template());
|
||
}
|
||
if (this.templateInsertedCallback) {
|
||
this.templateInsertedCallback();
|
||
}
|
||
}
|
||
}
|
||
if (typeof XMLHttpRequest !== 'undefined') {
|
||
fetchText = function (url, onload, onerror) {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('GET', url, true);
|
||
xhr.onreadystatechange = function () {
|
||
var status, err;
|
||
if (xhr.readyState === 4) {
|
||
status = xhr.status;
|
||
if (status > 399 && status < 600) {
|
||
err = new Error(url + ' HTTP status: ' + status);
|
||
err.xhr = xhr;
|
||
onerror(err);
|
||
} else {
|
||
onload(xhr.responseText);
|
||
}
|
||
}
|
||
};
|
||
xhr.responseType = 'text';
|
||
xhr.send(null);
|
||
};
|
||
} else {
|
||
fetchText = function (url, onload) {
|
||
onload(requirejs._readFile(url));
|
||
};
|
||
}
|
||
template = {
|
||
fetchText: fetchText,
|
||
ready: function (fn) {
|
||
if (isReady) {
|
||
setTimeout(fn);
|
||
} else {
|
||
readyQueue.push(fn);
|
||
}
|
||
},
|
||
makeFullId: makeFullId,
|
||
makeTemplateFn: function (text) {
|
||
return function () {
|
||
var e, frag = document.createDocumentFragment(), templateDiv = document.createElement('div');
|
||
templateDiv.innerHTML = text;
|
||
while (e = templateDiv.firstChild) {
|
||
frag.appendChild(e);
|
||
}
|
||
return frag;
|
||
};
|
||
},
|
||
idsToUrls: function (text, refId) {
|
||
text = text.replace(attrIdRegExp, function (match, type, id) {
|
||
id = makeFullId(id, refId);
|
||
var attr = type === 'hrefid' ? 'href' : 'src';
|
||
return ' ' + attr + '="' + require.toUrl(id) + '"';
|
||
});
|
||
return text;
|
||
},
|
||
depsFromText: function (text) {
|
||
var match, noCommentText, deps = [];
|
||
noCommentText = text.replace(commentRegExp, '');
|
||
tagRegExp.lastIndex = 0;
|
||
while (match = tagRegExp.exec(noCommentText)) {
|
||
deps.push(depPrefix + tagToId(match[1]));
|
||
}
|
||
return deps;
|
||
},
|
||
textToTemplate: function (text, id, skipTranslateIds) {
|
||
var obj, deps = template.depsFromText(text);
|
||
obj = {
|
||
id: id,
|
||
deps: deps,
|
||
text: text
|
||
};
|
||
if (!skipTranslateIds) {
|
||
obj.text = template.idsToUrls(text, id);
|
||
obj.fn = template.makeTemplateFn(obj.text);
|
||
}
|
||
return obj;
|
||
},
|
||
objToFn: function (obj) {
|
||
var text = template.idsToUrls(obj.text, obj.id);
|
||
return template.makeTemplateFn(text);
|
||
},
|
||
templateCreatedCallback: templateCreatedCallback,
|
||
load: function (id, req, onload, config) {
|
||
var isBuild = config.isBuild;
|
||
if (id.indexOf(buildProtocol) === 0 && isBuild) {
|
||
id = id.substring(buildProtocol.length);
|
||
var idList = id.split(','), count = 0, buildIdDone = function () {
|
||
count += 1;
|
||
if (count === idList.length) {
|
||
onload();
|
||
}
|
||
};
|
||
buildIdDone.__requireJsBuild = true;
|
||
id.split(',').forEach(function (moduleId) {
|
||
var path = req.toUrl(moduleId);
|
||
require(template.depsFromText(requirejs._readFile(path)), buildIdDone);
|
||
});
|
||
} else {
|
||
fetchText(req.toUrl(id), function (text) {
|
||
var templateObj = template.textToTemplate(text, id, isBuild);
|
||
req(templateObj.deps, function () {
|
||
if (isBuild) {
|
||
buildMap[id] = templateObj;
|
||
}
|
||
onload({
|
||
createdCallback: templateCreatedCallback,
|
||
template: templateObj.fn
|
||
});
|
||
});
|
||
}, onload.error);
|
||
}
|
||
},
|
||
write: function (pluginName, id, write) {
|
||
if (buildMap.hasOwnProperty(id)) {
|
||
var obj = buildMap[id], depString = JSON.stringify(obj.deps);
|
||
depString = depString.replace(/^\s*\[/, '').replace(/\]\s*$/, '').trim();
|
||
if (depString) {
|
||
depString = ', ' + depString;
|
||
}
|
||
write.asModule(pluginName + '!' + id, 'define([\'' + module.id + '\'' + depString + '], function(template) { return {\n' + 'createdCallback: template.templateCreatedCallback,\n' + 'template: template.objToFn(' + JSON.stringify(buildMap[id]) + ')}; });\n');
|
||
}
|
||
}
|
||
};
|
||
if (typeof document !== 'undefined') {
|
||
var onDom, onDomDone = false;
|
||
onDom = function () {
|
||
if (onDomDone) {
|
||
return;
|
||
}
|
||
onDomDone = true;
|
||
var converted = template.textToTemplate(document.body.innerHTML);
|
||
require(converted.deps, onReady);
|
||
};
|
||
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
||
onDom();
|
||
} else {
|
||
window.addEventListener('DOMContentLoaded', onDom);
|
||
}
|
||
}
|
||
return template;
|
||
});
|
||
define('template!cards/lst/edit_header.html', ['template'], function (template) {
|
||
return {
|
||
createdCallback: template.templateCreatedCallback,
|
||
template: template.objToFn({
|
||
'id': 'cards/lst/edit_header.html',
|
||
'deps': [],
|
||
'text': '<section role="region">\n <header>\n <a href="#"\n data-event="click:domEvt"\n data-domevt-name="editHeaderClose"\n role="button"\n data-l10n-id="close-button">\n <span class="icon icon-close"></span>\n </a>\n <h1 data-prop="headerNode"></h1>\n </header>\n</section>\n'
|
||
})
|
||
};
|
||
});
|
||
define('cards/mixins/dom_evt', [], function () {
|
||
return {
|
||
domEvt: function (event) {
|
||
var target = event.currentTarget, eventName = target.dataset.domevtName, eventData = {}, eventDetail = target.dataset.domevtDetail;
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
if (!eventName) {
|
||
throw new Error('No data-domevt-name on element <' + target.nodeName + ' class="' + target.className + '">');
|
||
}
|
||
if (eventDetail) {
|
||
eventData.detail = eventDetail;
|
||
}
|
||
this.dispatchEvent(new CustomEvent(eventName, eventData));
|
||
}
|
||
};
|
||
});
|
||
define('cards/lst/edit_header', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'l10n!',
|
||
'../base',
|
||
'template!./edit_header.html',
|
||
'../mixins/dom_evt'
|
||
], function (require, exports) {
|
||
var mozL10n = require('l10n!');
|
||
return [
|
||
require('../base')(require('template!./edit_header.html')),
|
||
require('../mixins/dom_evt'),
|
||
{
|
||
updateDomHeaderCount: function (count) {
|
||
mozL10n.setAttributes(this.headerNode, 'message-multiedit-header', { n: count });
|
||
}
|
||
}
|
||
];
|
||
});
|
||
define('container_listen', [], function () {
|
||
return function containerListen(containerNode, eventName, func) {
|
||
containerNode.addEventListener(eventName, function (event) {
|
||
var node = event.target;
|
||
if (node === containerNode) {
|
||
return;
|
||
}
|
||
while (node && node.parentNode !== containerNode) {
|
||
node = node.parentNode;
|
||
}
|
||
if (node) {
|
||
func(node, event);
|
||
}
|
||
}, false);
|
||
};
|
||
});
|
||
define('tmpl!cards/msg/header_item.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<a class="msg-header-item" role="option" tabindex="0">\n <label class="pack-checkbox negative" aria-hidden="true">\n <input type="checkbox"><span></span>\n </label>\n <div class="msg-header-details-section">\n <span dir="auto" class="msg-header-author"></span>\n <span dir="auto" class="msg-header-subject"></span>\n <span dir="auto" class="msg-header-date"></span>\n <span dir="auto" class="msg-header-snippet"></span>\n </div>\n <div class="msg-header-syncing-section"></div>\n <div class="msg-header-unread-section"\n data-l10n-id="message-header-unread"></div>\n <div class="msg-header-icons-section">\n <span class="msg-header-star" data-l10n-id="message-header-starred"></span>\n <span class="msg-header-attachments"\n data-l10n-id="message-header-attachments"></span>\n </div><div class="msg-header-avatar-section" aria-hidden="true">\n </div></a>\n');
|
||
});
|
||
define('template!cards/lst/msg_vscroll.html', ['template'], function (template) {
|
||
return {
|
||
createdCallback: template.templateCreatedCallback,
|
||
template: template.objToFn({
|
||
'id': 'cards/lst/msg_vscroll.html',
|
||
'deps': [],
|
||
'text': '<div data-prop="vScrollContainer" class="msg-vscroll-container"></div>\n<!-- maintain vertical space for the syncing/sync more div\'s\n regardless of their displayed status so we don\'t scroll them\n out of the way -->\n<div class="msg-messages-sync-container">\n <p data-prop="syncingNode" class="msg-messages-syncing collapsed"\n role="progressbar" data-l10n-id="messages-syncing-progressbar">\n <span data-l10n-id="messages-syncing"></span>\n </p>\n <p data-prop="syncMoreNode"\n data-event="click:onGetMoreMessages"\n class="msg-messages-sync-more collapsed"\n role="button">\n <span data-l10n-id="messages-load-more"></span>\n </p>\n</div>\n<div data-prop="messageEmptyContainer"\n class="msg-list-empty-container collapsed">\n <p data-prop="messageEmptyText"\n class="msg-list-empty-message-text"\n data-l10n-id="messages-folder-empty"></p>\n</div>'
|
||
})
|
||
};
|
||
});
|
||
define('cards/lst/msg_vscroll', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards',
|
||
'container_listen',
|
||
'l10n!',
|
||
'tmpl!../msg/header_item.html',
|
||
'toaster',
|
||
'vscroll',
|
||
'../base',
|
||
'template!./msg_vscroll.html'
|
||
], function (require, exports) {
|
||
var cards = require('cards'), containerListen = require('container_listen'), mozL10n = require('l10n!'), msgHeaderItemNode = require('tmpl!../msg/header_item.html'), toaster = require('toaster'), VScroll = require('vscroll');
|
||
var sliceEvents = [
|
||
'splice',
|
||
'change',
|
||
'status',
|
||
'complete'
|
||
];
|
||
var MINIMUM_ITEMS_FOR_SCROLL_CALC = 10;
|
||
var MAXIMUM_MS_BETWEEN_SNIPPET_REQUEST = 6000;
|
||
var MAXIMUM_BYTES_PER_MESSAGE_DURING_SCROLL = 4 * 1024;
|
||
return [
|
||
require('../base')(require('template!./msg_vscroll.html')),
|
||
{
|
||
createdCallback: function () {
|
||
this.setAttribute('role', 'listbox');
|
||
this.setAttribute('aria-multiselectable', 'true');
|
||
mozL10n.setAttributes(this.messageEmptyText, this.dataset.emptyL10nId);
|
||
containerListen(this.vScrollContainer, 'click', this.onClickMessage.bind(this));
|
||
},
|
||
init: function (scrollContainer, bindData, defaultVScrollData) {
|
||
this.scrollContainer = scrollContainer;
|
||
var listFunc = function (index) {
|
||
return this.headerCursor.messagesSlice.items[index];
|
||
}.bind(this);
|
||
listFunc.size = function () {
|
||
var slice = this.headerCursor.messagesSlice;
|
||
return Math.max(slice.headerCount || 0, slice.items.length);
|
||
}.bind(this);
|
||
this.listFunc = listFunc;
|
||
this.waitingOnChunk = true;
|
||
this.desiredHighAbsoluteIndex = 0;
|
||
this._needVScrollData = false;
|
||
this.vScroll = new VScroll(this.vScrollContainer, this.scrollContainer, msgHeaderItemNode, defaultVScrollData);
|
||
this.vScroll.bindData = bindData;
|
||
this.vScroll.prepareData = function (highAbsoluteIndex) {
|
||
var items = this.headerCursor.messagesSlice && this.headerCursor.messagesSlice.items, headerCount = this.headerCursor.messagesSlice.headerCount;
|
||
if (!items || !headerCount) {
|
||
return;
|
||
}
|
||
if (highAbsoluteIndex > headerCount - 1) {
|
||
highAbsoluteIndex = headerCount - 1;
|
||
}
|
||
if (highAbsoluteIndex < items.length) {
|
||
return;
|
||
}
|
||
this.loadNextChunk(highAbsoluteIndex);
|
||
}.bind(this);
|
||
},
|
||
setHeaderCursor: function (headerCursor) {
|
||
if (this.headerCursor) {
|
||
throw new Error('headerCursor already set');
|
||
}
|
||
this.headerCursor = headerCursor;
|
||
sliceEvents.forEach(function (type) {
|
||
var name = 'messages_' + type;
|
||
this[name] = this[name].bind(this);
|
||
headerCursor.on(name, this[name]);
|
||
}.bind(this));
|
||
this.onCurrentMessage = this.onCurrentMessage.bind(this);
|
||
headerCursor.on('currentMessage', this.onCurrentMessage);
|
||
var parent = this;
|
||
while (parent = parent.parentNode) {
|
||
if (parent.classList.contains('card')) {
|
||
break;
|
||
}
|
||
}
|
||
this.cardParent = parent;
|
||
this._onVScrollStopped = this._onVScrollStopped.bind(this);
|
||
this.vScroll.on('scrollStopped', this._onVScrollStopped);
|
||
},
|
||
removeMessagesHtml: function () {
|
||
this.vScrollContainer.innerHTML = '';
|
||
},
|
||
onClickMessage: function (node, event) {
|
||
this.emitDomEvent('messageClick', node);
|
||
},
|
||
_onVScrollStopped: function () {
|
||
if (!this.headerCursor.messagesSlice || this.headerCursor.messagesSlice.pendingRequestCount) {
|
||
return;
|
||
}
|
||
if (cards.isVisible(this.cardParent) && !this._hasSnippetRequest()) {
|
||
this._requestSnippets();
|
||
}
|
||
},
|
||
onGetMoreMessages: function () {
|
||
if (!this.headerCursor.messagesSlice) {
|
||
return;
|
||
}
|
||
this.vScroll.once('recalculated', function (calledFromTop, refIndex) {
|
||
this.vScrollContainer.querySelector('[data-index="' + refIndex + '"]').focus();
|
||
}.bind(this));
|
||
this.headerCursor.messagesSlice.requestGrowth(1, true);
|
||
},
|
||
isEmpty: function () {
|
||
return this.headerCursor.messagesSlice.items.length === 0;
|
||
},
|
||
showEmptyLayout: function () {
|
||
this.messageEmptyContainer.classList.remove('collapsed');
|
||
this.emit('emptyLayoutShown');
|
||
},
|
||
hideEmptyLayout: function () {
|
||
this.messageEmptyContainer.classList.add('collapsed');
|
||
this.emit('emptyLayoutHidden');
|
||
},
|
||
messages_status: function (newStatus) {
|
||
var syncInProgress = true;
|
||
if (newStatus === 'synchronizing' || newStatus === 'syncblocked') {
|
||
this.syncingNode.classList.remove('collapsed');
|
||
this.syncMoreNode.classList.add('collapsed');
|
||
this.hideEmptyLayout();
|
||
} else if (newStatus === 'syncfailed' || newStatus === 'synced') {
|
||
syncInProgress = false;
|
||
if (newStatus === 'syncfailed') {
|
||
toaster.toast({ text: mozL10n.get('toaster-retryable-syncfailed') });
|
||
}
|
||
this.syncingNode.classList.add('collapsed');
|
||
}
|
||
this.emit('syncInProgress', syncInProgress);
|
||
},
|
||
messages_complete: function (newEmailCount) {
|
||
var headerCursor = this.headerCursor;
|
||
console.log('message_list complete:', headerCursor.messagesSlice.items.length, 'items of', headerCursor.messagesSlice.headerCount, 'alleged known headers. canGrow:', headerCursor.messagesSlice.userCanGrowDownwards);
|
||
if (headerCursor.messagesSlice.userCanGrowDownwards && headerCursor.messagesSlice.headerCount) {
|
||
this.syncMoreNode.classList.remove('collapsed');
|
||
} else {
|
||
this.syncMoreNode.classList.add('collapsed');
|
||
}
|
||
if (headerCursor.messagesSlice.items.length === 0) {
|
||
this.showEmptyLayout();
|
||
}
|
||
this.waitingOnChunk = false;
|
||
if (this.desiredHighAbsoluteIndex) {
|
||
this.loadNextChunk(this.desiredHighAbsoluteIndex);
|
||
this.desiredHighAbsoluteIndex = 0;
|
||
}
|
||
this.vScroll.updateDataBind(0, [], 0);
|
||
this.emit('messagesComplete', newEmailCount);
|
||
},
|
||
messages_splice: function (index, howMany, addedItems, requested, moreExpected) {
|
||
var headerCursor = this.headerCursor;
|
||
if (index === 0 && howMany === 0 && !addedItems.length) {
|
||
return;
|
||
}
|
||
this.emit('messagesSpliceStart', index, howMany, addedItems, requested, moreExpected);
|
||
if (this._needVScrollData) {
|
||
this.vScroll.setData(this.listFunc);
|
||
this._needVScrollData = false;
|
||
}
|
||
this.vScroll.updateDataBind(index, addedItems, howMany);
|
||
if (addedItems.length > 0) {
|
||
this.hideEmptyLayout();
|
||
}
|
||
if (!headerCursor.messagesSlice.headerCount) {
|
||
this.vScroll.once('scrollStopped', function () {
|
||
if (!headerCursor.messagesSlice.headerCount) {
|
||
this.showEmptyLayout();
|
||
}
|
||
}.bind(this));
|
||
}
|
||
this.emit('messagesSpliceEnd', index, howMany, addedItems, requested, moreExpected);
|
||
},
|
||
messages_change: function (message, index) {
|
||
this.emit('messagesChange', message, index);
|
||
},
|
||
loadNextChunk: function (desiredHighAbsoluteIndex) {
|
||
if (this.vScroll.waitingForRecalculate) {
|
||
return;
|
||
}
|
||
if (this.waitingOnChunk) {
|
||
this.desiredHighAbsoluteIndex = desiredHighAbsoluteIndex;
|
||
return;
|
||
}
|
||
var headerCursor = this.headerCursor;
|
||
if (desiredHighAbsoluteIndex >= headerCursor.messagesSlice.headerCount) {
|
||
desiredHighAbsoluteIndex = headerCursor.messagesSlice.headerCount - 1;
|
||
}
|
||
var items = headerCursor.messagesSlice.items;
|
||
var curHighAbsoluteIndex = items.length - 1;
|
||
var amount = desiredHighAbsoluteIndex - curHighAbsoluteIndex;
|
||
if (amount > 0) {
|
||
console.log('message_list loadNextChunk growing', amount, amount === 1 ? '(will get boosted to 15!) to' : 'to', desiredHighAbsoluteIndex + 1, 'items out of', headerCursor.messagesSlice.headerCount, 'alleged known');
|
||
headerCursor.messagesSlice.requestGrowth(amount, false);
|
||
this.waitingOnChunk = true;
|
||
}
|
||
},
|
||
onCurrentMessage: function (currentMessage, index) {
|
||
if (!currentMessage) {
|
||
return;
|
||
}
|
||
var visibleIndices = this.vScroll.getVisibleIndexRange();
|
||
if (visibleIndices && (index < visibleIndices[0] || index > visibleIndices[1])) {
|
||
this.vScroll.jumpToIndex(index);
|
||
}
|
||
},
|
||
_hasSnippetRequest: function () {
|
||
var max = MAXIMUM_MS_BETWEEN_SNIPPET_REQUEST;
|
||
var now = Date.now();
|
||
var beforeTimeout = this._lastSnippetRequest + max > now;
|
||
if (this._snippetRequestPending && beforeTimeout) {
|
||
return true;
|
||
}
|
||
return false;
|
||
},
|
||
_pendingSnippetRequest: function () {
|
||
this._snippetRequestPending = true;
|
||
this._lastSnippetRequest = Date.now();
|
||
},
|
||
_clearSnippetRequest: function () {
|
||
this._snippetRequestPending = false;
|
||
},
|
||
_requestSnippets: function () {
|
||
var headerCursor = this.headerCursor;
|
||
var items = headerCursor.messagesSlice.items;
|
||
var len = items.length;
|
||
if (!len) {
|
||
return;
|
||
}
|
||
var clearSnippets = this._clearSnippetRequest.bind(this);
|
||
var options = { maximumBytesToFetch: MAXIMUM_BYTES_PER_MESSAGE_DURING_SCROLL };
|
||
if (len < MINIMUM_ITEMS_FOR_SCROLL_CALC) {
|
||
this._pendingSnippetRequest();
|
||
headerCursor.messagesSlice.maybeRequestBodies(0, MINIMUM_ITEMS_FOR_SCROLL_CALC - 1, options, clearSnippets);
|
||
return;
|
||
}
|
||
var visibleIndices = this.vScroll.getVisibleIndexRange();
|
||
if (visibleIndices) {
|
||
this._pendingSnippetRequest();
|
||
headerCursor.messagesSlice.maybeRequestBodies(visibleIndices[0], visibleIndices[1], options, clearSnippets);
|
||
}
|
||
},
|
||
die: function () {
|
||
sliceEvents.forEach(function (type) {
|
||
var name = 'messages_' + type;
|
||
this.headerCursor.removeListener(name, this[name]);
|
||
}.bind(this));
|
||
this.headerCursor.removeListener('currentMessage', this.onCurrentMessage);
|
||
this.vScroll.destroy();
|
||
}
|
||
}
|
||
];
|
||
});
|
||
define('template!cards/lst/edit_toolbar.html', ['template'], function (template) {
|
||
return {
|
||
createdCallback: template.templateCreatedCallback,
|
||
template: template.objToFn({
|
||
'id': 'cards/lst/edit_toolbar.html',
|
||
'deps': [],
|
||
'text': '<!-- Toolbar for multi-edit state -->\n<ul class="bb-tablist" role="toolbar">\n <li role="presentation">\n <button data-prop="deleteBtn"\n data-event="click:domEvt"\n data-domevt-name="onDeleteMessages"\n class="icon msg-delete-btn"\n data-l10n-id="message-delete-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="starBtn"\n data-event="click:domEvt"\n data-domevt-name="onStarMessages"\n class="icon msg-star-btn"\n data-l10n-id="message-star-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="readBtn"\n data-event="click:domEvt"\n data-domevt-name="onMarkMessagesRead"\n class="icon msg-mark-read-btn"\n data-l10n-id="message-mark-read-button"></button>\n </li>\n <li role="presentation">\n <button data-prop="moveBtn"\n data-event="click:domEvt"\n data-domevt-name="onMoveMessages"\n class="icon msg-move-btn"\n data-l10n-id="message-move-button"></button>\n </li>\n</ul>'
|
||
})
|
||
};
|
||
});
|
||
define('cards/lst/edit_toolbar', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'l10n!',
|
||
'../base',
|
||
'template!./edit_toolbar.html',
|
||
'../mixins/dom_evt'
|
||
], function (require, exports) {
|
||
var mozL10n = require('l10n!'), toolbarEditButtonNames = [
|
||
'starBtn',
|
||
'readBtn',
|
||
'deleteBtn',
|
||
'moveBtn'
|
||
];
|
||
return [
|
||
require('../base')(require('template!./edit_toolbar.html')),
|
||
require('../mixins/dom_evt'),
|
||
{
|
||
updateDomFolderType: function (folderType) {
|
||
this.moveBtn.classList.toggle('collapsed', folderType === 'localdrafts' || folderType === 'outbox');
|
||
this.starBtn.classList.toggle('collapsed', folderType === 'outbox');
|
||
this.readBtn.classList.toggle('collapsed', folderType === 'outbox');
|
||
},
|
||
updateDomStartButton: function (isStarred) {
|
||
mozL10n.setAttributes(this.starBtn, isStarred ? 'message-star-button' : 'message-unstar-button');
|
||
},
|
||
updateDomReadButton: function (hasUnread) {
|
||
this.readBtn.classList.toggle('unread', hasUnread);
|
||
mozL10n.setAttributes(this.readBtn, hasUnread ? 'message-mark-unread-button' : 'message-mark-read-button');
|
||
},
|
||
updateDomEditButtons: function (hasMessages) {
|
||
toolbarEditButtonNames.forEach(function (key) {
|
||
this[key].disabled = !hasMessages;
|
||
}.bind(this));
|
||
}
|
||
}
|
||
];
|
||
});
|
||
define('template!cards/message_list.html', [
|
||
'template',
|
||
'element!cards/lst/edit_header',
|
||
'element!cards/lst/msg_vscroll',
|
||
'element!cards/lst/edit_toolbar'
|
||
], function (template) {
|
||
return {
|
||
createdCallback: template.templateCreatedCallback,
|
||
template: template.objToFn({
|
||
'id': 'cards/message_list.html',
|
||
'deps': [
|
||
'element!cards/lst/edit_header',
|
||
'element!cards/lst/msg_vscroll',
|
||
'element!cards/lst/edit_toolbar'
|
||
],
|
||
'text': '<!-- Non-search header -->\n<section data-prop="normalHeader"\n class="msg-list-header msg-nonsearch-only"\n data-statuscolor="default"\n role="region">\n <header>\n <!-- Unlike a generic back button that navigates to a different screen,\n folder list header button triggers the folders and settings overlay. Thus\n the screen reader user requires more context as to what activating the\n button would do. -->\n <a href="#" class="msg-folder-list-btn" data-event="click:onShowFolders"\n aria-expanded="false" aria-controls="cards-folder-picker"\n role="button" data-l10n-id="message-list-menu">\n <span class="icon icon-menu"></span>\n </a>\n <menu data-prop="headerMenuNode" type="toolbar" class="anim-opacity">\n <a href="#" class="msg-compose-btn" data-event="click:onCompose"\n data-l10n-id="message-list-compose">\n <span class="icon icon-compose"></span>\n </a>\n </menu>\n <h1 data-prop="folderLabel"\n class="msg-list-header-folder-label header-label">\n <span data-prop="folderNameNode"\n dir="auto"\n class="msg-list-header-folder-name"></span>\n <span data-prop="folderUnread"\n class="msg-list-header-folder-unread collapsed"></span>\n </h1>\n </header>\n</section>\n\n<!-- Multi-edit state header -->\n<lst-edit-header data-prop="editHeader"\n data-event="editHeaderClose"></lst-edit-header>\n<!-- Scroll region -->\n<div data-prop="scrollContainer" class="msg-list-scrollouter">\n <!-- exists so we can force a minimum height -->\n <div class="msg-list-scrollinner">\n <!-- The search textbox hides under the lip of the messages.\n As soon as any typing happens in it, we push the search\n controls card. -->\n <form role="search" data-prop="searchBar"\n class="msg-search-tease-bar msg-nonsearch-only">\n <p>\n <input data-event="focus:onSearchButton"\n data-prop="searchTextTease"\n class="msg-search-text-tease" type="text"\n dir="auto"\n data-l10n-id="message-search-input" />\n </p>\n </form>\n <lst-msg-vscroll data-prop="msgVScroll"\n data-event="messageClick:onClickMessage"\n data-empty-l10n-id="messages-folder-empty">\n </lst-msg-vscroll>\n </div>\n</div>\n\n<!-- New email notification bar -->\n<div class="message-list-topbar"></div>\n\n<!-- Toolbar for non-multi-edit state -->\n<ul data-prop="normalToolbar" class="bb-tablist msg-list-action-toolbar"\n role="toolbar">\n <li role="presentation" class="msg-nonsearch-only">\n <button data-prop="refreshBtn" data-event="click:onRefresh"\n class="icon msg-refresh-btn" data-state="synchronized"\n data-l10n-id="messages-refresh-button">\n </button>\n </li>\n <li role="status" class="msg-nonsearch-only msg-last-sync">\n <span data-prop="lastSyncedLabel"\n class="msg-last-synced-label"\n data-l10n-id="folder-last-synced-label"></span>\n <span data-prop="lastSyncedAtNode"\n class="msg-last-synced-value"></span>\n </li>\n <li role="presentation">\n <button data-prop="editBtn" data-event="click:setEditModeStart"\n class="icon msg-edit-btn" data-l10n-id="edit-button"></button>\n </li>\n</ul>\n\n<lst-edit-toolbar data-prop="editToolbar"\n data-event="onDeleteMessages,onStarMessages,\n onMarkMessagesRead,onMoveMessages">\n</lst-edit-toolbar>\n\n'
|
||
})
|
||
};
|
||
});
|
||
define('template!cards/confirm_dialog.html', ['template'], function (template) {
|
||
return {
|
||
createdCallback: template.templateCreatedCallback,
|
||
template: template.objToFn({
|
||
'id': 'cards/confirm_dialog.html',
|
||
'deps': [],
|
||
'text': '<div class="card-confirm-dialog card">\n <form data-statuscolor="background"\n role="dialog" data-type="confirm" class="collapsed confirm-dialog-form">\n <section>\n <h1 data-l10n-id="confirm-dialog-title"></h1>\n <p class="confirm-dialog-message"></p>\n </section>\n <menu>\n <button class="confirm-dialog-cancel" data-l10n-id="message-multiedit-cancel"></button>\n <button class="confirm-dialog-ok recommend" data-l10n-id="dialog-button-ok"></button>\n </menu>\n </form>\n</div>\n'
|
||
})
|
||
};
|
||
});
|
||
define('cards/confirm_dialog', [
|
||
'require',
|
||
'cards',
|
||
'./base_card',
|
||
'template!./confirm_dialog.html'
|
||
], function (require) {
|
||
var cards = require('cards');
|
||
return [
|
||
require('./base_card')(require('template!./confirm_dialog.html')),
|
||
{
|
||
onArgs: function (args) {
|
||
var dialogBodyNode = args.dialogBodyNode, confirm = args.confirm, cancel = args.cancel, callback = args.callback;
|
||
if (dialogBodyNode) {
|
||
this.appendChild(dialogBodyNode);
|
||
} else {
|
||
dialogBodyNode = this.querySelector('.confirm-dialog-form');
|
||
dialogBodyNode.querySelector('.confirm-dialog-message').textContent = args.message;
|
||
dialogBodyNode.classList.remove('collapsed');
|
||
confirm = {
|
||
handler: function () {
|
||
callback(true);
|
||
}
|
||
};
|
||
cancel = {
|
||
handler: function () {
|
||
callback(false);
|
||
}
|
||
};
|
||
}
|
||
dialogBodyNode.addEventListener('submit', function (evt) {
|
||
evt.preventDefault();
|
||
evt.stopPropagation();
|
||
this.hide();
|
||
var target = evt.explicitOriginalTarget, targetId = target.id, isOk = target.classList.contains('confirm-dialog-ok'), isCancel = target.classList.contains('confirm-dialog-cancel');
|
||
if ((isOk || targetId === confirm.id) && confirm.handler) {
|
||
confirm.handler();
|
||
} else if ((isCancel || targetId === cancel.id) && cancel.handler) {
|
||
cancel.handler();
|
||
}
|
||
}.bind(this));
|
||
},
|
||
hide: function () {
|
||
cards.removeCardAndSuccessors(this, 'immediate', 1, null, true);
|
||
},
|
||
die: function () {
|
||
}
|
||
}
|
||
];
|
||
});
|
||
define('confirm_dialog', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards',
|
||
'element!cards/confirm_dialog'
|
||
], function (require, exports) {
|
||
var cards = require('cards'), ConfirmDialog = require('element!cards/confirm_dialog');
|
||
ConfirmDialog.show = function (message, callback, cancel) {
|
||
var dialogBodyNode;
|
||
if (typeof message !== 'string') {
|
||
dialogBodyNode = message;
|
||
message = null;
|
||
}
|
||
cards.pushCard('confirm_dialog', 'immediate', {
|
||
dialogBodyNode: dialogBodyNode,
|
||
message: message,
|
||
confirm: callback,
|
||
callback: callback,
|
||
cancel: cancel
|
||
}, 'right');
|
||
};
|
||
return ConfirmDialog;
|
||
});
|
||
define('tmpl!cards/msg/delete_confirm.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<form role="dialog" class="msg-delete-confirm" data-type="confirm">\n <section>\n <h1 data-l10n-id="confirm-dialog-title"></h1>\n <p></p>\n </section>\n <menu>\n <button id="msg-delete-cancel" data-l10n-id="message-multiedit-cancel"></button>\n <button id="msg-delete-ok" class="danger" data-l10n-id="message-edit-menu-delete"></button>\n </menu>\n</form>');
|
||
});
|
||
define('cards/lst/edit_controller', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards',
|
||
'confirm_dialog',
|
||
'tmpl!../msg/delete_confirm.html',
|
||
'l10n!',
|
||
'toaster'
|
||
], function (require, exports) {
|
||
var cards = require('cards'), ConfirmDialog = require('confirm_dialog'), deleteConfirmMsgNode = require('tmpl!../msg/delete_confirm.html'), mozL10n = require('l10n!'), toaster = require('toaster');
|
||
return {
|
||
createdCallback: function () {
|
||
this.editMode = false;
|
||
this.selectedMessages = null;
|
||
this.editModeEnabled = false;
|
||
},
|
||
setEditMode: function (editMode) {
|
||
if (!this.editModeEnabled) {
|
||
return;
|
||
}
|
||
this._setEditMode(editMode);
|
||
},
|
||
_setEditMode: function (editMode) {
|
||
var i;
|
||
this.editMode = editMode;
|
||
if (editMode) {
|
||
this.classList.add('show-edit');
|
||
this.selectedMessages = [];
|
||
this.updateDomEditControls();
|
||
} else {
|
||
this.classList.remove('show-edit');
|
||
this.selectedMessages = null;
|
||
}
|
||
var msgNodes = this.msgVScroll.querySelectorAll('.msg-header-item');
|
||
for (i = 0; i < msgNodes.length; i++) {
|
||
this.updateDomMessageChecked(msgNodes[i], false);
|
||
}
|
||
if (this.editModeChanged) {
|
||
this.editModeChanged(editMode);
|
||
}
|
||
},
|
||
setEditModeStart: function () {
|
||
this.setEditMode(true);
|
||
},
|
||
editHeaderClose: function () {
|
||
this.setEditMode(false);
|
||
},
|
||
toggleSelection: function (msgNode) {
|
||
var header = msgNode.message;
|
||
var idx = this.selectedMessages.indexOf(header);
|
||
if (idx !== -1) {
|
||
this.selectedMessages.splice(idx, 1);
|
||
} else {
|
||
this.selectedMessages.push(header);
|
||
}
|
||
this.updateDomMessageChecked(msgNode, idx === -1);
|
||
this.updateDomEditControls();
|
||
},
|
||
updateDomSelectState: function (msgNode, message) {
|
||
if (this.editMode) {
|
||
this.updateDomMessageChecked(msgNode, this.selectedMessages.indexOf(message) !== -1);
|
||
} else {
|
||
msgNode.removeAttribute('aria-selected');
|
||
}
|
||
},
|
||
updateDomMessageChecked: function (msgNode, checked) {
|
||
var checkbox = msgNode.querySelector('input[type=checkbox]');
|
||
checkbox.checked = checked;
|
||
msgNode.setAttribute('aria-selected', checked);
|
||
},
|
||
updateDomEditControls: function () {
|
||
this.editHeader.updateDomHeaderCount(this.selectedMessages.length);
|
||
this.editToolbar.updateDomEditButtons(this.selectedMessages.length > 0);
|
||
var numStarred = 0, numRead = 0;
|
||
for (var i = 0; i < this.selectedMessages.length; i++) {
|
||
var msg = this.selectedMessages[i];
|
||
if (msg.isStarred) {
|
||
numStarred++;
|
||
}
|
||
if (msg.isRead) {
|
||
numRead++;
|
||
}
|
||
}
|
||
this.setAsStarred = !(numStarred && numStarred === this.selectedMessages.length);
|
||
this.editToolbar.updateDomStartButton(this.setAsStarred);
|
||
this.setAsRead = !!this.selectedMessages.length && numRead === 0;
|
||
this.editToolbar.updateDomReadButton(numRead > 0);
|
||
},
|
||
onDeleteMessages: function () {
|
||
if (this.selectedMessages.length === 0) {
|
||
return this.setEditMode(false);
|
||
}
|
||
var dialog = deleteConfirmMsgNode.cloneNode(true);
|
||
var content = dialog.getElementsByTagName('p')[0];
|
||
mozL10n.setAttributes(content, 'message-multiedit-delete-confirm', { n: this.selectedMessages.length });
|
||
ConfirmDialog.show(dialog, {
|
||
id: 'msg-delete-ok',
|
||
handler: function () {
|
||
var op = this.model.api.deleteMessages(this.selectedMessages);
|
||
toaster.toastOperation(op);
|
||
this.setEditMode(false);
|
||
}.bind(this)
|
||
}, {
|
||
id: 'msg-delete-cancel',
|
||
handler: null
|
||
});
|
||
},
|
||
onStarMessages: function () {
|
||
var op = this.model.api.markMessagesStarred(this.selectedMessages, this.setAsStarred);
|
||
this.setEditMode(false);
|
||
toaster.toastOperation(op);
|
||
},
|
||
onMarkMessagesRead: function () {
|
||
var op = this.model.api.markMessagesRead(this.selectedMessages, this.setAsRead);
|
||
this.setEditMode(false);
|
||
toaster.toastOperation(op);
|
||
},
|
||
onMoveMessages: function () {
|
||
cards.folderSelector(this.model, function (folder) {
|
||
var op = this.model.api.moveMessages(this.selectedMessages, folder);
|
||
toaster.toastOperation(op);
|
||
this.setEditMode(false);
|
||
}.bind(this), function (folder) {
|
||
return folder.isValidMoveTarget;
|
||
});
|
||
}
|
||
};
|
||
});
|
||
define('tmpl!cards/msg/large_message_confirm.html', ['tmpl'], function (tmpl) {
|
||
return tmpl.toDom('<form role="dialog" class="msg-large-message-confirm" data-type="confirm">\n <section>\n <h1 data-l10n-id="confirm-dialog-title"></h1>\n <p><span data-l10n-id="message-large-message-confirm"></span></p>\n </section>\n <menu>\n <button id="msg-large-message-cancel" data-l10n-id="message-large-message-cancel"></button>\n <button id="msg-large-message-ok" data-l10n-id="message-large-message-ok"></button>\n </menu>\n</form>\n');
|
||
});
|
||
define('cards/lst/msg_click', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards',
|
||
'confirm_dialog',
|
||
'tmpl!../msg/large_message_confirm.html'
|
||
], function (require, exports) {
|
||
var cards = require('cards'), ConfirmDialog = require('confirm_dialog'), largeMsgConfirmMsgNode = require('tmpl!../msg/large_message_confirm.html');
|
||
function showLargeMessageWarning(size, cb) {
|
||
var dialog = largeMsgConfirmMsgNode.cloneNode(true);
|
||
ConfirmDialog.show(dialog, {
|
||
id: 'msg-large-message-ok',
|
||
handler: function () {
|
||
cb(true);
|
||
}
|
||
}, {
|
||
id: 'msg-large-message-cancel',
|
||
handler: function () {
|
||
cb(false);
|
||
}
|
||
});
|
||
}
|
||
return {
|
||
onClickMessage: function (event) {
|
||
var messageNode = event.detail;
|
||
if (this.curFolder && this.curFolder.type === 'outbox' && this.outboxSyncInProgress) {
|
||
return;
|
||
}
|
||
var header = messageNode.message;
|
||
if (header && header.isPlaceholderData) {
|
||
return;
|
||
}
|
||
if (this.editMode) {
|
||
this.toggleSelection(messageNode);
|
||
return;
|
||
}
|
||
if (this.curFolder && this.curFolder.type === 'localdrafts') {
|
||
var composer = header.editAsDraft(() => {
|
||
cards.pushCard('compose', 'animate', {
|
||
model: this.model,
|
||
composer
|
||
});
|
||
});
|
||
return;
|
||
}
|
||
if (this.curFolder && this.curFolder.type === 'outbox') {
|
||
if (header.sendStatus.state === 'sending') {
|
||
return;
|
||
}
|
||
var draftsFolder = this.model.foldersSlice.getFirstFolderWithType('localdrafts');
|
||
console.log('outbox: Moving message to localdrafts.');
|
||
this.model.api.moveMessages([header], draftsFolder, moveMap => {
|
||
header.id = moveMap[header.id];
|
||
console.log('outbox: Editing message in localdrafts.');
|
||
var composer = header.editAsDraft(() => {
|
||
cards.pushCard('compose', 'animate', {
|
||
model: this.model,
|
||
composer
|
||
});
|
||
});
|
||
});
|
||
return;
|
||
}
|
||
var model = this.model, headerCursor = this.headerCursor;
|
||
function pushMessageCard() {
|
||
cards.pushCard('message_reader', 'animate', {
|
||
model,
|
||
headerCursor,
|
||
header,
|
||
messageSuid: messageNode.dataset.id
|
||
});
|
||
}
|
||
if (header) {
|
||
this.headerCursor.setCurrentMessage(header);
|
||
} else if (messageNode.dataset.id) {
|
||
this.headerCursor.setCurrentMessageBySuid(messageNode.dataset.id);
|
||
} else {
|
||
return;
|
||
}
|
||
var LARGE_MESSAGE_SIZE = 1 * 1024 * 1024;
|
||
if (header && header.bytesToDownloadForBodyDisplay > LARGE_MESSAGE_SIZE) {
|
||
showLargeMessageWarning(header.bytesToDownloadForBodyDisplay, function (result) {
|
||
if (result) {
|
||
pushMessageCard();
|
||
} else {
|
||
}
|
||
});
|
||
} else {
|
||
pushMessageCard();
|
||
}
|
||
}
|
||
};
|
||
});
|
||
define('cards/message_list', [
|
||
'require',
|
||
'exports',
|
||
'module',
|
||
'cards',
|
||
'date',
|
||
'./lst/default_vscroll_data',
|
||
'evt',
|
||
'toaster',
|
||
'header_cursor',
|
||
'html_cache',
|
||
'l10n!',
|
||
'message_list_topbar',
|
||
'message_display',
|
||
'./lst/peep_dom',
|
||
'vscroll',
|
||
'./base_card',
|
||
'template!./message_list.html',
|
||
'./lst/edit_controller',
|
||
'./lst/msg_click'
|
||
], function (require, exports, module) {
|
||
var cards = require('cards'), date = require('date'), defaultVScrollData = require('./lst/default_vscroll_data'), evt = require('evt'), toaster = require('toaster'), HeaderCursor = require('header_cursor'), htmlCache = require('html_cache'), mozL10n = require('l10n!'), MessageListTopBar = require('message_list_topbar'), messageDisplay = require('message_display'), updatePeepDom = require('./lst/peep_dom').update, VScroll = require('vscroll');
|
||
return [
|
||
require('./base_card')(require('template!./message_list.html')),
|
||
require('./lst/edit_controller'),
|
||
require('./lst/msg_click'),
|
||
{
|
||
createdCallback: function () {
|
||
this._needsSizeLastSync = true;
|
||
this.updateLastSynced();
|
||
this.curFolder = null;
|
||
this.isIncomingFolder = true;
|
||
this._hideSearchBoxByScrolling = this._hideSearchBoxByScrolling.bind(this);
|
||
this._folderChanged = this._folderChanged.bind(this);
|
||
this.onNewMail = this.onNewMail.bind(this);
|
||
this.onFoldersSliceChange = this.onFoldersSliceChange.bind(this);
|
||
this.usingCachedNode = this.dataset.cached === 'cached';
|
||
this.msgVScroll.on('messagesSpliceStart', function (index, howMany, addedItems, requested, moreExpected) {
|
||
this._clearCachedMessages();
|
||
}.bind(this));
|
||
this.msgVScroll.on('messagesSpliceEnd', function (index, howMany, addedItems, requested, moreExpected) {
|
||
if (addedItems.length || howMany) {
|
||
this._considerCacheDom(index);
|
||
}
|
||
}.bind(this));
|
||
this.msgVScroll.on('messagesChange', function (message, index) {
|
||
this.onMessagesChange(message, index);
|
||
}.bind(this));
|
||
this._emittedContentEvents = false;
|
||
this.msgVScroll.on('messagesComplete', function (newEmailCount) {
|
||
this.onNewMail(newEmailCount);
|
||
if (!this._emittedContentEvents) {
|
||
evt.emit('metrics:contentDone');
|
||
this._emittedContentEvents = true;
|
||
}
|
||
}.bind(this));
|
||
var oldMessagesStatus = this.msgVScroll.messages_status;
|
||
this.msgVScroll.messages_status = function (newStatus) {
|
||
if (!this.curFolder || this.curFolder.type === 'outbox') {
|
||
return;
|
||
}
|
||
return oldMessagesStatus.call(this.msgVScroll, newStatus);
|
||
}.bind(this);
|
||
this.msgVScroll.on('emptyLayoutShown', function () {
|
||
this._clearCachedMessages();
|
||
if (this.curFolder.type === 'outbox') {
|
||
this.refreshBtn.disabled = true;
|
||
}
|
||
this.editBtn.disabled = true;
|
||
this._hideSearchBoxByScrolling();
|
||
}.bind(this));
|
||
this.msgVScroll.on('emptyLayoutHidden', function () {
|
||
this.editBtn.disabled = false;
|
||
this.refreshBtn.disabled = false;
|
||
}.bind(this));
|
||
this.msgVScroll.on('syncInProgress', function (syncInProgress) {
|
||
if (syncInProgress) {
|
||
this.setRefreshState(true);
|
||
} else {
|
||
this.setRefreshState(false);
|
||
this._manuallyTriggeredSync = false;
|
||
}
|
||
}.bind(this));
|
||
var vScrollBindData = function bindNonSearch(model, node) {
|
||
model.element = node;
|
||
node.message = model;
|
||
this.updateMessageDom(model);
|
||
}.bind(this);
|
||
this.msgVScroll.init(this.scrollContainer, vScrollBindData, defaultVScrollData);
|
||
this.msgVScroll.vScroll.on('inited', this._hideSearchBoxByScrolling);
|
||
this.msgVScroll.vScroll.on('dataChanged', this._hideSearchBoxByScrolling);
|
||
this.msgVScroll.vScroll.on('recalculated', function (calledFromTop) {
|
||
if (calledFromTop) {
|
||
this._hideSearchBoxByScrolling();
|
||
}
|
||
}.bind(this));
|
||
this._topBar = new MessageListTopBar(this.querySelector('.message-list-topbar'));
|
||
this._topBar.bindToElements(this.scrollContainer, this.msgVScroll.vScroll);
|
||
this.onFolderPickerClosing = this.onFolderPickerClosing.bind(this);
|
||
evt.on('folderPickerClosing', this.onFolderPickerClosing);
|
||
},
|
||
onArgs: function (args) {
|
||
var model = this.model = args.model;
|
||
var headerCursor = this.headerCursor = args.headerCursor || new HeaderCursor(model);
|
||
this.msgVScroll.setHeaderCursor(headerCursor);
|
||
model.latest('folder', this._folderChanged);
|
||
model.on('newInboxMessages', this.onNewMail);
|
||
model.on('backgroundSendStatus', this.onBackgroundSendStatus.bind(this));
|
||
model.on('foldersSliceOnChange', this.onFoldersSliceChange);
|
||
if (this.curFolder) {
|
||
var items = headerCursor.messagesSlice && headerCursor.messagesSlice.items;
|
||
if (items && items.length) {
|
||
this.msgVScroll.messages_splice(0, 0, items);
|
||
this.msgVScroll.messages_complete(0);
|
||
}
|
||
}
|
||
},
|
||
skipEmitContentEvents: true,
|
||
postInsert: function () {
|
||
this._hideSearchBoxByScrolling();
|
||
this.msgVScroll.vScroll.visibleOffset = this.searchBar.getBoundingClientRect().height;
|
||
this._topBar.visibleOffset = this.msgVScroll.vScroll.visibleOffset;
|
||
this.msgVScroll.vScroll.captureScreenMetrics();
|
||
},
|
||
onSearchButton: function () {
|
||
if (!this.curFolder) {
|
||
return;
|
||
}
|
||
cards.pushCard('message_list_search', 'animate', {
|
||
model: this.model,
|
||
folder: this.curFolder
|
||
});
|
||
},
|
||
_hideSearchBoxByScrolling: function () {
|
||
var searchBar = this.searchBar, scrollContainer = this.scrollContainer;
|
||
if (searchBar.classList.contains('collapsed')) {
|
||
searchBar.classList.remove('collapsed');
|
||
scrollContainer.scrollTop += searchBar.offsetHeight;
|
||
}
|
||
if (scrollContainer.scrollTop === 0) {
|
||
scrollContainer.scrollTop = searchBar.offsetHeight;
|
||
}
|
||
},
|
||
onShowFolders: function () {
|
||
cards.pushCard('folder_picker', 'immediate', {
|
||
model: this.model,
|
||
onPushed: function () {
|
||
this.headerMenuNode.classList.add('transparent');
|
||
}.bind(this)
|
||
});
|
||
},
|
||
onCompose: function () {
|
||
cards.pushCard('compose', 'animate', { model: this.model });
|
||
},
|
||
sizeLastSync: function () {
|
||
if (this._needsSizeLastSync && this.lastSyncedLabel.scrollWidth) {
|
||
var label = this.lastSyncedLabel;
|
||
var overHalf = label.scrollWidth > label.parentNode.clientWidth / 2;
|
||
label.parentNode.classList[overHalf ? 'add' : 'remove']('long');
|
||
this._needsSizeLastSync = false;
|
||
}
|
||
},
|
||
updateLastSynced: function (value) {
|
||
var method = value ? 'remove' : 'add';
|
||
this.lastSyncedLabel.classList[method]('collapsed');
|
||
date.setPrettyNodeDate(this.lastSyncedAtNode, value);
|
||
this.sizeLastSync();
|
||
},
|
||
updateUnread: function (num) {
|
||
var content = '';
|
||
if (num > 0) {
|
||
content = num > 999 ? mozL10n.get('messages-folder-unread-max') : num;
|
||
}
|
||
this.folderUnread.textContent = content;
|
||
this.folderUnread.classList.toggle('collapsed', !content);
|
||
this.callHeaderFontSize();
|
||
},
|
||
onFoldersSliceChange: function (folder) {
|
||
if (folder === this.curFolder) {
|
||
this.updateUnread(folder.unread);
|
||
this.updateLastSynced(folder.lastSyncedAt);
|
||
}
|
||
},
|
||
callHeaderFontSize: function (node) {
|
||
requestAnimationFrame(function () {
|
||
FontSizeUtils._reformatHeaderText(this.folderLabel);
|
||
}.bind(this));
|
||
},
|
||
showFolder: function (folder, forceNewSlice) {
|
||
if (folder === this.curFolder && !forceNewSlice) {
|
||
return false;
|
||
}
|
||
if (!this.usingCachedNode) {
|
||
this.msgVScroll.vScroll.clearDisplay();
|
||
}
|
||
this.msgVScroll._needVScrollData = true;
|
||
this.curFolder = folder;
|
||
this.editModeEnabled = true;
|
||
switch (folder.type) {
|
||
case 'drafts':
|
||
case 'localdrafts':
|
||
case 'outbox':
|
||
case 'sent':
|
||
this.isIncomingFolder = false;
|
||
break;
|
||
default:
|
||
this.isIncomingFolder = true;
|
||
break;
|
||
}
|
||
this.folderNameNode.textContent = folder.name;
|
||
this.updateUnread(folder.unread);
|
||
this.msgVScroll.setAttribute('aria-label', folder.name);
|
||
this.msgVScroll.hideEmptyLayout();
|
||
this.refreshBtn.classList.toggle('collapsed', folder.type === 'localdrafts');
|
||
this.editToolbar.updateDomFolderType(folder.type);
|
||
this.updateLastSynced(folder.lastSyncedAt);
|
||
if (forceNewSlice) {
|
||
this.msgVScroll._snippetRequestPending = false;
|
||
this.headerCursor.freshMessagesSlice();
|
||
}
|
||
this.onFolderShown();
|
||
return true;
|
||
},
|
||
setEditMode: function (editMode) {
|
||
if (!this.editModeEnabled) {
|
||
return;
|
||
}
|
||
if (this.curFolder.type === 'outbox') {
|
||
if (editMode && this.outboxSyncInProgress) {
|
||
return;
|
||
}
|
||
var model = this.model;
|
||
model.api.setOutboxSyncEnabled(model.account, !editMode, function () {
|
||
this._setEditMode(editMode);
|
||
}.bind(this));
|
||
} else {
|
||
this._setEditMode(editMode);
|
||
}
|
||
},
|
||
setRefreshState: function (syncing) {
|
||
if (syncing) {
|
||
this.refreshBtn.dataset.state = 'synchronizing';
|
||
this.refreshBtn.setAttribute('role', 'progressbar');
|
||
mozL10n.setAttributes(this.refreshBtn, 'messages-refresh-progress');
|
||
} else {
|
||
this.refreshBtn.dataset.state = 'synchronized';
|
||
this.refreshBtn.removeAttribute('role');
|
||
mozL10n.setAttributes(this.refreshBtn, 'messages-refresh-button');
|
||
}
|
||
},
|
||
onNewMail: function (newEmailCount) {
|
||
var inboxFolder = this.model.foldersSlice.getFirstFolderWithType('inbox');
|
||
if (inboxFolder.id === this.curFolder.id && newEmailCount && newEmailCount > 0) {
|
||
if (!cards.isVisible(this)) {
|
||
this._whenVisible = this.onNewMail.bind(this, newEmailCount);
|
||
return;
|
||
}
|
||
if (this._manuallyTriggeredSync) {
|
||
this.msgVScroll.vScroll.jumpToIndex(0);
|
||
} else {
|
||
this._topBar.showNewEmailCount(newEmailCount);
|
||
}
|
||
}
|
||
},
|
||
onBackgroundSendStatus: function (data) {
|
||
if (this.curFolder.type === 'outbox') {
|
||
if (data.state === 'sending') {
|
||
this.toggleOutboxSyncingDisplay(true);
|
||
} else if (data.state === 'syncDone') {
|
||
this.toggleOutboxSyncingDisplay(false);
|
||
}
|
||
}
|
||
if (data.emitNotifications) {
|
||
toaster.toast({ text: data.localizedDescription });
|
||
}
|
||
},
|
||
_cacheListLimit: 7,
|
||
_cacheDomTimeoutId: 0,
|
||
_isCacheableCardState: function () {
|
||
return this.cacheableFolderId === this.curFolder.id && !this.editMode;
|
||
},
|
||
_cacheDom: function () {
|
||
this._cacheDomTimeoutId = 0;
|
||
if (!this._isCacheableCardState()) {
|
||
return;
|
||
}
|
||
var cacheNode = htmlCache.cloneAsInertNodeAvoidingCustomElementHorrors(this);
|
||
cacheNode.querySelector('menu[type="toolbar"]').classList.remove('transparent');
|
||
var removableCacheNode = cacheNode.querySelector('.msg-search-tease-bar');
|
||
if (removableCacheNode) {
|
||
removableCacheNode.classList.add('collapsed');
|
||
}
|
||
removableCacheNode = cacheNode.querySelector('.message-list-topbar');
|
||
if (removableCacheNode) {
|
||
this._topBar.resetNodeForCache(removableCacheNode);
|
||
}
|
||
var tempNode = cacheNode.querySelector('.msg-last-synced-label');
|
||
if (tempNode) {
|
||
tempNode.classList.add('collapsed');
|
||
}
|
||
tempNode = cacheNode.querySelector('.msg-last-synced-value');
|
||
if (tempNode) {
|
||
tempNode.innerHTML = '';
|
||
}
|
||
VScroll.trimMessagesForCache(cacheNode.querySelector('.msg-vscroll-container'), this._cacheListLimit);
|
||
htmlCache.saveFromNode(module.id, cacheNode);
|
||
},
|
||
_considerCacheDom: function (index) {
|
||
if (!this._cacheDomTimeoutId && this._isCacheableCardState() && this.msgVScroll.vScroll.firstRenderedIndex === 0 && (index || index === 0) && index < this._cacheListLimit) {
|
||
this._cacheDomTimeoutId = setTimeout(this._cacheDom.bind(this), 600);
|
||
}
|
||
},
|
||
_clearCachedMessages: function () {
|
||
if (this.usingCachedNode) {
|
||
this.msgVScroll.removeMessagesHtml();
|
||
this.usingCachedNode = false;
|
||
}
|
||
},
|
||
onMessagesChange: function (message, index) {
|
||
this.updateMessageDom(message);
|
||
this._considerCacheDom(index);
|
||
},
|
||
updateMessageDom: function (message) {
|
||
var msgNode = message.element;
|
||
if (!msgNode) {
|
||
return;
|
||
}
|
||
var classAction = message.isPlaceholderData ? 'add' : 'remove';
|
||
var defaultDataClass = this.msgVScroll.vScroll.itemDefaultDataClass;
|
||
msgNode.classList[classAction](defaultDataClass);
|
||
msgNode.dataset.id = message.id;
|
||
var dateNode = msgNode.querySelector('.msg-header-date');
|
||
var subjectNode = msgNode.querySelector('.msg-header-subject');
|
||
var snippetNode = msgNode.querySelector('.msg-header-snippet');
|
||
var listPerson;
|
||
if (this.isIncomingFolder) {
|
||
listPerson = message.author;
|
||
} else if (message.to && message.to.length) {
|
||
listPerson = message.to[0];
|
||
} else if (message.cc && message.cc.length) {
|
||
listPerson = message.cc[0];
|
||
} else if (message.bcc && message.bcc.length) {
|
||
listPerson = message.bcc[0];
|
||
} else {
|
||
listPerson = message.author;
|
||
}
|
||
listPerson.element = msgNode.querySelector('.msg-header-author');
|
||
listPerson.onchange = updatePeepDom;
|
||
listPerson.onchange(listPerson);
|
||
var dateTime = dateNode.dataset.time = message.date.valueOf();
|
||
date.relativeDateElement(dateNode, dateTime);
|
||
messageDisplay.subject(msgNode.querySelector('.msg-header-subject'), message);
|
||
var attachmentsNode = msgNode.querySelector('.msg-header-attachments');
|
||
attachmentsNode.classList.toggle('msg-header-attachments-yes', message.hasAttachments);
|
||
snippetNode.classList.toggle('icon-short', message.hasAttachments);
|
||
snippetNode.textContent = message.snippet;
|
||
msgNode.classList.toggle('unread', !message.isRead);
|
||
var starNode = msgNode.querySelector('.msg-header-star');
|
||
starNode.classList.toggle('msg-header-star-starred', message.isStarred);
|
||
subjectNode.classList.toggle('icon-short', message.isStarred);
|
||
var syncNode = msgNode.querySelector('.msg-header-syncing-section');
|
||
var sendState = message.sendStatus && message.sendStatus.state;
|
||
syncNode.classList.toggle('msg-header-syncing-section-syncing', sendState === 'sending');
|
||
syncNode.classList.toggle('msg-header-syncing-section-error', sendState === 'error');
|
||
if (sendState) {
|
||
mozL10n.setAttributes(syncNode, 'message-header-state-' + sendState);
|
||
} else {
|
||
syncNode.removeAttribute('data-l10n-id');
|
||
}
|
||
this.updateDomSelectState(msgNode, message);
|
||
},
|
||
onFolderPickerClosing: function () {
|
||
this.headerMenuNode.classList.remove('transparent');
|
||
},
|
||
onFolderShown: function () {
|
||
var model = this.model, account = model.account, foldersSlice = model.foldersSlice;
|
||
if (!document.hidden && account && foldersSlice && this.curFolder) {
|
||
var inboxFolder = foldersSlice.getFirstFolderWithType('inbox');
|
||
if (inboxFolder === this.curFolder) {
|
||
evt.emit('inboxShown', account.id);
|
||
}
|
||
if (document.activeElement === this.searchTextTease) {
|
||
this.onSearchButton();
|
||
}
|
||
}
|
||
},
|
||
onCurrentCardDocumentVisibilityChange: function () {
|
||
this.onFolderShown();
|
||
},
|
||
onCardVisible: function () {
|
||
if (this._whenVisible) {
|
||
var fn = this._whenVisible;
|
||
this._whenVisible = null;
|
||
fn();
|
||
}
|
||
this.msgVScroll.vScroll.nowVisible();
|
||
this.sizeLastSync();
|
||
},
|
||
toggleOutboxSyncingDisplay: function (syncing) {
|
||
if (syncing === this._outboxSyncing) {
|
||
return;
|
||
}
|
||
this._outboxSyncing = syncing;
|
||
var i;
|
||
var items = this.msgVScroll.getElementsByClassName('msg-header-syncing-section');
|
||
if (syncing) {
|
||
for (i = 0; i < items.length; i++) {
|
||
items[i].classList.add('msg-header-syncing-section-syncing');
|
||
items[i].classList.remove('msg-header-syncing-section-error');
|
||
}
|
||
this.editBtn.disabled = true;
|
||
} else {
|
||
this.editBtn.disabled = this.msgVScroll.isEmpty();
|
||
for (i = 0; i < items.length; i++) {
|
||
items[i].classList.remove('msg-header-syncing-section-syncing');
|
||
}
|
||
}
|
||
this.setRefreshState(syncing);
|
||
},
|
||
onRefresh: function () {
|
||
var headerCursor = this.headerCursor;
|
||
if (!headerCursor.messagesSlice) {
|
||
return;
|
||
}
|
||
if (this.curFolder.type === 'outbox') {
|
||
this.toggleOutboxSyncingDisplay(true);
|
||
} else {
|
||
switch (headerCursor.messagesSlice.status) {
|
||
case 'new':
|
||
case 'synchronizing':
|
||
break;
|
||
case 'synced':
|
||
this._manuallyTriggeredSync = true;
|
||
headerCursor.messagesSlice.refresh();
|
||
break;
|
||
case 'syncfailed':
|
||
if (headerCursor.messagesSlice.items.length) {
|
||
headerCursor.messagesSlice.refresh();
|
||
} else {
|
||
this.showFolder(this.curFolder, true);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
this.model.api.sendOutboxMessages(this.model.account);
|
||
},
|
||
_folderChanged: function (folder) {
|
||
if (!this.model.foldersSlice) {
|
||
return;
|
||
}
|
||
var model = this.model;
|
||
var inboxFolder = model.foldersSlice.getFirstFolderWithType('inbox');
|
||
this.cacheableFolderId = model.account === model.acctsSlice.defaultAccount ? inboxFolder.id : null;
|
||
if (this.showFolder(folder)) {
|
||
this._hideSearchBoxByScrolling();
|
||
}
|
||
},
|
||
die: function () {
|
||
this.msgVScroll.die();
|
||
evt.removeListener('folderPickerClosing', this.onFolderPickerClosing);
|
||
if (this.headerCursor) {
|
||
this.headerCursor.die();
|
||
}
|
||
var model = this.model;
|
||
model.removeListener('folder', this._folderChanged);
|
||
model.removeListener('newInboxMessages', this.onNewMail);
|
||
model.removeListener('foldersSliceOnChange', this.onFoldersSliceChange);
|
||
}
|
||
}
|
||
];
|
||
}); |