Commit 2c5f0da3 authored by Vivien Alger's avatar Vivien Alger

etherpad-lite: Draft for promises

parent b2b75254
......@@ -16,13 +16,18 @@ extends =
../../component/git/buildout.cfg
../../component/nodejs/buildout.cfg
../../component/postgresql/buildout.cfg
../../component/phantomjs/buildout.cfg
parts =
postgresql
nodejs
phantomjs
etherpad-lite-repository
install-deps
template
template-html10n
template-index-promise-js
template-pad-promise-js
lxml-python
eggs
instance-recipe-egg
......@@ -59,16 +64,24 @@ mode = 0644
[instance-etherpad-lite]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/instance-etherpad-lite.cfg
md5sum = 64ae9271f20e432ddc0516fe1bb17076
md5sum = dea0ee49ff3a87bd146e3d6bd6d68167
output = ${buildout:directory}/template-etherpad-lite.cfg
mode = 0644
[template-html10n]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/templates/${:filename}
filename = html10n.js
mode = 0644
md5sum = 6f90afdcc50bb5020896c95162d83834
output = ${etherpad-lite-repository:location}/src/static/js/${:filename}
[template-conf]
recipe = slapos.recipe.download
url = ${:_profile_base_location_}/templates/${:filename}
mode = 0644
filename = settings.json.in
md5sum = f9baee09003676fc1141f9bf4481f6a3
md5sum = d95264e66e2691b094d40a65d88ce681
location = ${buildout:parts-directory}/${:_buildout_section_name_}
[template-run-script]
......@@ -96,5 +109,21 @@ recipe = plone.recipe.command
command = ${template-deps-script:output}
update-command = command
[template-index-promise-js]
recipe = slapos.recipe.download
url = ${:_profile_base_location_}/templates/${:filename}
filename = test-index.js.in
mode = 0644
md5sum = 7ee12b1c284c2c6260689b21bb35176e
location = ${buildout:parts-directory}/${:_buildout_section_name_}
[template-pad-promise-js]
recipe = slapos.recipe.download
url = ${:_profile_base_location_}/templates/${:filename}
filename = test-pad.js.in
mode = 0644
md5sum = 43dc2ee94e65cc7f5fa4c3d6a868eebe
location = ${buildout:parts-directory}/${:_buildout_section_name_}
[lxml-python]
python = python2.7
......@@ -2,6 +2,9 @@
parts =
etherpad-lite
publish-connection-informations
check-index-promise
# XXX-Vivien: postponed till we can make it fast enough
# check-pad-promise
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
......@@ -27,10 +30,11 @@ promises = $${rootdirectory:etc}/promise/
recipe = slapos.cookbook:mkdirectory
etherpad-conf = $${rootdirectory:etc}/etherpad/
etherpad-repository-location = $${buildout:directory}/parts/etherpad-lite-repository
promise-js = $${rootdirectory:etc}/js/
[publish-connection-informations]
recipe = slapos.cookbook:publish
url = $${request-frontend:connection-site_url}
url = $${request-frontend:config-url}
[etherpad-conf-generation]
recipe = slapos.recipe.template
......@@ -38,6 +42,7 @@ url = ${template-conf:location}/${template-conf:filename}
ip = $${slap-network-information:global-ipv6}
dirtydb-location = $${rootdirectory:var}/dirty.db
port = 9001
welcome-message = Bienvenue sur Etherpad!\n\nLe texte que vous écrivez est synchronisez en ce moment même, pour que tout le monde puisse voir la page avec le même texte. Cela vous permet de collaborer sur des documents sans aucun problème!\n
mode = 0644
output = $${directory:etherpad-conf}/settings.json
......@@ -67,5 +72,34 @@ name = Frontend
software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
slave = true
config = url
config-url = http://$${etherpad-conf-generation:ip}:$${etherpad-conf-generation:port}
config-url = http://[$${etherpad-conf-generation:ip}]:$${etherpad-conf-generation:port}
return = site_url
[index-promise-js]
recipe = slapos.recipe.template
url = ${template-index-promise-js:location}/${template-index-promise-js:filename}
content-url = $${publish-connection-informations:url}
mode = 0644
output = $${directory:promise-js}/test-index.js
[pad-promise-js]
recipe = slapos.recipe.template
url = ${template-pad-promise-js:location}/${template-pad-promise-js:filename}
content-url = $${publish-connection-informations:url}
welcome-message = $${etherpad-conf-generation:welcome-message}
mode = 0644
output = $${directory:promise-js}/test-pad.js
[check-index-promise]
recipe = slapos.cookbook:check_page_content_phantomjs
path = $${basedirectory:promises}/check-index-promise
dash-path = ${dash:location}/bin/dash
phantomjs-path = ${phantomjs:location}/phantomjs-slapos
script-path = $${index-promise-js:output}
[check-pad-promise]
recipe = slapos.cookbook:check_page_content_phantomjs
path = $${basedirectory:promises}/check-pad-promise
dash-path = ${dash:location}/bin/dash
phantomjs-path = ${phantomjs:location}/phantomjs-slapos
script-path = $${pad-promise-js:output}
\ No newline at end of file
......@@ -15,7 +15,8 @@ develop =
recipe = slapos.recipe.build:gitclone
repository = http://git.erp5.org/repos/slapos.git
git-executable = ${git:location}/bin/git
revision = 45243101321daeecaf755b9788b11259dd6bdfff
#revision = 45243101321daeecaf755b9788b11259dd6bdfff
branch = etherpad-lite
[slapos.toolbox-repository]
recipe = slapos.recipe.build:gitclone
......
/**
* Copyright (c) 2012 Marcel Klehr
* Copyright (c) 2011-2012 Fabien Cazenave, Mozilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
function html10n (window, document, undefined) {
// fix console
var console = null
function interceptConsole(method){
if (!console) return function() {}
var original = console[method]
// do sneaky stuff
if (original.bind){
// Do this for normal browsers
return original.bind(console)
}else{
return function() {
// Do this for IE
var message = Array.prototype.slice.apply(arguments).join(' ')
original(message)
}
}
}
var consoleLog = interceptConsole('log')
, consoleWarn = interceptConsole('warn')
, consoleError = interceptConsole('warn')
// fix Array#forEach in IE
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fn, scope) {
for(var i = 0, len = this.length; i < len; ++i) {
if (i in this) {
fn.call(scope, this[i], i, this);
}
}
};
}
// fix Array#indexOf in, guess what, IE! <3
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}
/**
* MicroEvent - to make any js object an event emitter (server or browser)
*/
var MicroEvent = function(){}
MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
}
}
};
/**
* mixin will delegate all MicroEvent.js function in the destination object
* @param {Object} the object which will support MicroEvent
*/
MicroEvent.mixin = function(destObject){
var props = ['bind', 'unbind', 'trigger'];
if(!destObject) return;
for(var i = 0; i < props.length; i ++){
destObject[props[i]] = MicroEvent.prototype[props[i]];
}
}
/**
* Loader
* The loader is responsible for loading
* and caching all necessary resources
*/
function Loader(resources) {
this.resources = resources
this.cache = {} // file => contents
this.langs = {} // lang => strings
}
Loader.prototype.load = function(lang, cb) {
if(this.langs[lang]) return cb()
if (this.resources.length > 0) {
var reqs = 0;
for (var i=0, n=this.resources.length; i < n; i++) {
this.fetch(this.resources[i], lang, function(e) {
reqs++;
if(e) consoleWarn(e)
if (reqs < n) return;// Call back once all reqs are completed
cb && cb()
})
}
}
}
Loader.prototype.fetch = function(href, lang, cb) {
var that = this
if (this.cache[href]) {
this.parse(lang, href, this.cache[href], cb)
return;
}
var xhr = new XMLHttpRequest()
xhr.open('GET', href, /*async: */true)
if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json; charset=utf-8');
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) {
var data = JSON.parse(xhr.responseText)
that.cache[href] = data
// Pass on the contents for parsing
that.parse(lang, href, data, cb)
} else {
cb(new Error('Failed to load '+href))
}
}
};
xhr.send(null);
}
Loader.prototype.parse = function(lang, currHref, data, cb) {
if ('object' != typeof data) {
cb(new Error('A file couldn\'t be parsed as json.'))
return
}
if (!data[lang]) {
cb(new Error('Couldn\'t find translations for '+lang))
return
}
if ('string' == typeof data[lang]) {
// Import rule
// absolute path
var importUrl = data[lang]
// relative path
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
importUrl = currHref+"/../"+data[lang]
}
this.fetch(importUrl, lang, cb)
return
}
if ('object' != typeof data[lang]) {
cb(new Error('Translations should be specified as JSON objects!'))
return
}
this.langs[lang] = data[lang]
// TODO: Also store accompanying langs
cb()
}
/**
* The html10n object
*/
var html10n =
{ language : null
}
MicroEvent.mixin(html10n)
html10n.macros = {}
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
/**
* Get rules for plural forms (shared with JetPack), see:
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
*
* @param {string} lang
* locale (language) used.
*
* @return {Function}
* returns a function that gives the plural form name for a given integer:
* var fun = getPluralRules('en');
* fun(1) -> 'one'
* fun(0) -> 'other'
* fun(1000) -> 'other'.
*/
function getPluralRules(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
};
// utility functions for plural rules methods
function isIn(n, list) {
return list.indexOf(n) !== -1;
}
function isBetween(n, start, end) {
return start <= n && n <= end;
}
// list of all plural rules methods:
// map an integer to the plural form name to use
var pluralRules = {
'0': function(n) {
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';
}
};
// return a function that gives the plural form name for a given integer
var index = locales2rules[lang.replace(/-.*$/, '')];
if (!(index in pluralRules)) {
consoleWarn('plural form unknown for [' + lang + ']');
return function() { return 'other'; };
}
return pluralRules[index];
}
/**
* pre-defined 'plural' macro
*/
html10n.macros.plural = function(key, param, opts) {
var str
, n = parseFloat(param);
if (isNaN(n))
return;
// initialize _pluralRules
if (!this._pluralRules)
this._pluralRules = getPluralRules(html10n.language);
var index = this._pluralRules(n);
// try to find a [zero|one|two] key if it's defined
if (n === 0 && ('zero') in opts) {
str = opts['zero'];
} else if (n == 1 && ('one') in opts) {
str = opts['one'];
} else if (n == 2 && ('two') in opts) {
str = opts['two'];
} else if (index in opts) {
str = opts[index];
}
return str;
};
/**
* Localize a document
* @param langs An array of lang codes defining fallbacks
*/
html10n.localize = function(langs) {
var that = this
// if only one string => create an array
if ('string' == typeof langs) langs = [langs]
// Expand two-part locale specs
var i=0
langs.forEach(function(lang) {
if(!lang) return
langs[i++] = lang
if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-'))
})
this.build(langs, function(er, translations) {
html10n.translations = translations
html10n.translateElement(translations)
that.trigger('localized')
})
}
/**
* Triggers the translation process
* for an element
* @param translations A hash of all translation strings
* @param element A DOM element, if omitted, the document element will be used
*/
html10n.translateElement = function(translations, element) {
element = element || document.documentElement
var children = element? getTranslatableChildren(element) : document.childNodes;
for (var i=0, n=children.length; i < n; i++) {
this.translateNode(translations, children[i])
}
// translate element itself if necessary
this.translateNode(translations, element)
}
function asyncForEach(list, iterator, cb) {
var i = 0
, n = list.length
iterator(list[i], i, function each(err) {
if(err) consoleLog(err)
i++
if (i < n) return iterator(list[i],i, each);
cb()
})
}
function getTranslatableChildren(element) {
if(!document.querySelectorAll) {
if (!element) return []
var nodes = element.getElementsByTagName('*')
, l10nElements = []
for (var i=0, n=nodes.length; i < n; i++) {
if (nodes[i].getAttribute('data-l10n-id'))
l10nElements.push(nodes[i]);
}
return l10nElements
}
return element.querySelectorAll('*[data-l10n-id]')
}
html10n.get = function(id, args) {
var translations = html10n.translations
if(!translations) return consoleWarn('No translations available (yet)')
if(!translations[id]) return consoleWarn('Could not find string '+id)
// apply macros
var str = translations[id]
str = substMacros(id, str, args)
// apply args
str = substArguments(str, args)
return str
}
// replace {{arguments}} with their values or the
// associated translation string (based on its key)
function substArguments(str, args) {
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
, match
while (match = reArgs.exec(str)) {
if (!match || match.length < 2)
return str // argument key not found
var arg = match[1]
, sub = ''
if (arg in args) {
sub = args[arg]
} else if (arg in translations) {
sub = translations[arg]
} else {
consoleWarn('Could not find argument {{' + arg + '}}')
return str
}
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length)
}
return str
}
// replace {[macros]} with their values
function substMacros(key, str, args) {
var regex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ //.exec('{[ plural(n) other: are {{n}}, one: is ]}')
, match
while(match = regex.exec(str)) {
// a macro has been found
// Note: at the moment, only one parameter is supported
var macroName = match[1]
, paramName = match[2]
, optv = match[3]
, opts = {}
if (!(macroName in html10n.macros)) continue
if(optv) {
optv.match(/(?=\s*)([a-zA-Z]+)\: ?([ a-zA-Z{}]+)(?=,?)/g).forEach(function(arg) {
var parts = arg.split(':')
, name = parts[0]
, value = parts[1].trim()
opts[name] = value
})
}
var param
if (args && paramName in args) {
param = args[paramName]
} else if (paramName in html10n.translations) {
param = translations[paramName]
}
// there's no macro parser: it has to be defined in html10n.macros
var macro = html10n.macros[macroName]
str = str.substr(0, match.index) + macro(key, param, opts) + str.substr(match.index+match[0].length)
}
return str
}
/**
* Applies translations to a DOM node (recursive)
*/
html10n.translateNode = function(translations, node) {
var str = {}
// get id
str.id = node.getAttribute('data-l10n-id')
if (!str.id) return
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id)
// get args
if(window.JSON) {
str.args = JSON.parse(node.getAttribute('data-l10n-args'))
}else{
try{
str.args = eval(node.getAttribute('data-l10n-args'))
}catch(e) {
consoleWarn('Couldn\'t parse args for '+str.id)
}
}
str.str = html10n.get(str.id, str.args)
// get attribute name to apply str to
var prop
, index = str.id.lastIndexOf('.')
, attrList = // allowed attributes
{ "title": 1
, "innerHTML": 1
, "alt": 1
, "textContent": 1
}
if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified
prop = str.id.substr(index + 1)
} else { // no attribute: assuming text content by default
prop = document.body.textContent ? 'textContent' : 'innerText'
}
// Apply translation
if (node.children.length === 0 || prop != 'textContent') {
node[prop] = str.str
} else {
var children = node.childNodes,
found = false
for (var i=0, n=children.length; i < n; i++) {
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
if (!found) {
children[i].nodeValue = str.str
found = true
} else {
children[i].nodeValue = ''
}
}
}
if (!found) {
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node)
}
}
}
/**
* Builds a translation object from a list of langs (loads the necessary translations)
* @param langs Array - a list of langs sorted by priority (default langs should go last)
*/
html10n.build = function(langs, cb) {
var that = this
, build = {}
asyncForEach(langs, function (lang, i, next) {
if(!lang) return next();
that.loader.load(lang, next)
}, function() {
var lang
langs.reverse()
// loop through priority array...
for (var i=0, n=langs.length; i < n; i++) {
lang = langs[i]
if(!lang || !(lang in that.loader.langs)) continue;
// ... and apply all strings of the current lang in the list
// to our build object
for (var string in that.loader.langs[lang]) {
build[string] = that.loader.langs[lang][string]
}
// the last applied lang will be exposed as the
// lang the page was translated to
that.language = lang
}
cb(null, build)
})
}
/**
* Returns the language that was last applied to the translations hash
* thus overriding most of the formerly applied langs
*/
html10n.getLanguage = function() {
return this.language;
}
/**
* Returns the direction of the language returned be html10n#getLanguage
*/
html10n.getDirection = function() {
if(!this.language) return
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'))
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'
}
/**
* Index all <link>s
*/
html10n.index = function () {
// Find all <link>s
var links = document.getElementsByTagName('link')
, resources = []
for (var i=0, n=links.length; i < n; i++) {
if (links[i].type != 'application/l10n+json')
continue;
resources.push(links[i].href)
}
this.loader = new Loader(resources)
this.trigger('indexed')
}
if (document.addEventListener) // modern browsers and IE9+
document.addEventListener('DOMContentLoaded', function() {
html10n.index()
}, false)
else if (window.attachEvent)
window.attachEvent('onload', function() {
html10n.index()
}, false)
// gettext-like shortcut
if (window._ === undefined)
window._ = html10n.get;
return html10n
}
window.html10n = html10n(window, document)
......@@ -52,7 +52,7 @@
*/
//the default text of a pad
"defaultPadText" : "Bienvenue sur Etherpad!\n\nLe texte que vous écrivez est synchronisez en ce moment même, pour que tout le monde puisse voir la page avec le même texte. Cela vous permet de collaborer sur des documents sans aucun problème!\n",
"defaultPadText" : "${:welcome-message}",
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
"requireSession" : false,
......
var page = require('webpage').create();
url = '${:content-url}'
page.open(url, function (status) {
var text = page.evaluate(function(){
return document.getElementById('button').textContent
});
if(text !== "" && text !== null) {
phantom.exit();
} else {
phantom.exit(1);
}
});
var page = require('webpage').create();
url = '${:content-url}/p/test'
page.open(url, function (status) {
setTimeout(function(){
var text = page.evaluate(function(){
var container = document.getElementById('editorcontainer');
var iframe = container.firstChild;
container = iframe.contentDocument.getElementById('outerdocbody');
iframe = container.firstChild;
container = iframe.contentDocument.getElementById('innerdocbody');
return container.textContent;
});
if (text === null) {
phantom.exit();
} else {
phantom.exit(1);
}
},2500);
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment