not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

584 lines
24 KiB

  1. // Journal object chooser dialog
  2. define(['picoModal','sugar-web/datastore','sugar-web/graphics/icon','mustache','sugar-web/env','sugar-web/graphics/radiobuttonsgroup'], function(picoModal,datastore,icon,mustache,env,radioButtonsGroup) {
  3. var chooser = {};
  4. // Load settings
  5. var userSettings = null;
  6. var activities = [];
  7. env.getEnvironment(function(err, environment) {
  8. userSettings = environment.user;
  9. for (var i = 0 ; userSettings.activities && i < userSettings.activities.length ; i++) {
  10. var activity = userSettings.activities[i];
  11. activities[activity.id] = activity;
  12. }
  13. });
  14. // Chooser feature to search in the Local Journal
  15. var featureLocalJournal = {};
  16. featureLocalJournal.id = "journal-button";
  17. featureLocalJournal.title = "$titleJournal";
  18. featureLocalJournal.placeholder = "$holderSearchJournal";
  19. featureLocalJournal.icon = "lib/sugar-web/graphics/icons/actions/activity-journal.svg";
  20. featureLocalJournal.beforeActivate = function() {
  21. featureLocalJournal.isFavorite = false;
  22. document.getElementById('favorite-button').style.backgroundImage = "url(lib/sugar-web/graphics/icons/emblems/favorite.svg)";
  23. journalFill.apply(null, featureLocalJournal.filters);
  24. };
  25. featureLocalJournal.beforeUnactivate = function() {
  26. };
  27. featureLocalJournal.onFavorite = function() {
  28. var favorite = document.getElementById('favorite-button');
  29. if (!featureLocalJournal.isFavorite) {
  30. icon.colorize(favorite, userSettings.colorvalue, function() {});
  31. } else {
  32. favorite.style.backgroundImage = "url(lib/sugar-web/graphics/icons/emblems/favorite.svg)";
  33. }
  34. featureLocalJournal.isFavorite = !featureLocalJournal.isFavorite;
  35. journalFill.apply(null, featureLocalJournal.filters);
  36. };
  37. featureLocalJournal.onScroll = function() {};
  38. featureLocalJournal.onSearch = function() {
  39. journalFill.apply(null, featureLocalJournal.filters);
  40. };
  41. featureLocalJournal.onCancelSearch = function() {
  42. journalFill.apply(null, featureLocalJournal.filters);
  43. };
  44. // Chooser feature to search in Abecedarium database
  45. var featureAbecedarium = {};
  46. featureAbecedarium.id = "abecedarium-button";
  47. featureAbecedarium.title = "$titleAbecedarium";
  48. featureAbecedarium.placeholder = "$holderSearchAbecedarium";
  49. featureAbecedarium.icon = "lib/sugar-web/graphics/icons/actions/activity-abecedarium.svg";
  50. featureAbecedarium.beforeActivate = function() {
  51. document.getElementById('favorite-button').style.visibility = "hidden";
  52. abecedariumInit(abecedariumFill);
  53. };
  54. featureAbecedarium.beforeUnactivate = function() {
  55. document.getElementById('favorite-button').style.visibility = "visible";
  56. };
  57. featureAbecedarium.onFavorite = function() {};
  58. featureAbecedarium.onScroll = function() {
  59. var container = document.getElementById('journal-container');
  60. var scrollValue = (container.scrollTop) / (container.scrollHeight - container.clientHeight);
  61. if (scrollValue > 0.90) {
  62. featureAbecedarium.resultCount += 30;
  63. abecedariumDisplay(featureAbecedarium.results, featureAbecedarium.resultCount);
  64. }
  65. };
  66. featureAbecedarium.onSearch = function() {
  67. abecedariumFill();
  68. };
  69. featureAbecedarium.onCancelSearch = function() {
  70. abecedariumFill();
  71. };
  72. // Init feature list: overload it if you want to change the feature list at init
  73. var features = [];
  74. var currentFeature = -1;
  75. chooser.init = function() {
  76. features = [featureLocalJournal];
  77. currentFeature = 0;
  78. }
  79. // Display object chooser dialog with journal content
  80. // Each filter has the form = {title: "%hysics", creation_time: "<12222", activity: ["org.olpcfrance.Paint","org.olpcfrance.Record"]}
  81. // When more than one filter is provided, an entry is valid if it match one filter or more. Usage examples:
  82. // chooser.show();
  83. // chooser.show({title: 'Paint Activity', creation_time: '>'+now.getTime()});
  84. // chooser.show({title: ['Paint Activity', '%hysics']});
  85. // chooser.show({keep: 1});
  86. // chooser.show({activity: 'org.olpcfrance.PaintActivity'}, {mimetype: 'image/png'})
  87. var modal;
  88. var result;
  89. chooser.show = function(callback, filter1, orFilter2, orFilter3, orFilter4) {
  90. result = null;
  91. chooser.init();
  92. var imageType = "image/png";
  93. var soundType = /Android/i.test(navigator.userAgent)?"audio/ogg":"audio/mpeg";
  94. var filters = [filter1, orFilter2, orFilter3, orFilter4];
  95. for (var i = 0 ; i < filters.length ; i++) {
  96. if (filters[i]) {
  97. if (filters[i].mimetype == imageType) {
  98. featureAbecedarium.fileformat = ".png";
  99. featureAbecedarium.mimetype = imageType;
  100. featureAbecedarium.filelocation = "images/database/";
  101. features.push(featureAbecedarium);
  102. break;
  103. } else if (filters[i].mimetype == soundType) {
  104. featureAbecedarium.fileformat = (soundType == "audio/ogg"?".ogg":".mp3");
  105. featureAbecedarium.mimetype = soundType;
  106. featureAbecedarium.filelocation = "audio/{{lang}}/database/";
  107. features.push(featureAbecedarium);
  108. break;
  109. }
  110. }
  111. }
  112. var contentHeader = "<div id='pictotoolbar' class='toolbar' style='padding: 0'>";
  113. for (var i = 0 ; i < features.length ; i++) {
  114. contentHeader += "<button class='toolbutton"+(i==0?" active":"")+"' id='"+features[i].id+"' title='"+features[i].title+"' style='background-image: url("+features[i].icon+")'></button>";
  115. }
  116. contentHeader += "<button class='toolbutton pull-right' id='close-button' title='$titleClose' style='background-image: url(lib/sugar-web/graphics/icons/actions/dialog-cancel.svg)'></button>";
  117. contentHeader += "<div style='position: relative; display: inline-block; margin-left: 10px; vertical-align:middle; height:55px'>$titleChoose</div></div>";
  118. modal = picoModal({
  119. content: doLocalize(contentHeader+"\
  120. <div class='toolbar' style='border-top-style: solid; border-color: #c0c0c0; border-width: 1px'>\
  121. <span class='icon-input' style='vertical-align:top;'>\
  122. <input id='search-text' type='text' style='width: 200px; font-size: 10pt'/>\
  123. <button id='cancel-search' class='cancel right'></button>\
  124. </span>\
  125. <button class='toolbutton' id='favorite-button' title='$titleFavorite' style='background-image: url(lib/sugar-web/graphics/icons/emblems/favorite.svg)'></button>\
  126. </div>\
  127. <div id='journal-empty' style='position:absolute;width:100%;top:50%;left:50%'>\
  128. <div style='width:60px;height:60px;background-repeat: no-repeat;background-image: url(lib/sugar-web/graphics/icons/actions/activity-journal.svg)'></div>\
  129. <div style='text-align:left!important;margin-left:-30px;color:#808080;text-align:center;font-size:14px;'>$noMatchingEntries</div>\
  130. </div>\
  131. <div id='journal-container' style='width: 100%; overflow:auto'></div>"),
  132. closeButton: false,
  133. modalStyles: {
  134. backgroundColor: "white",
  135. height: "400px",
  136. width: "600px",
  137. maxWidth: "90%"
  138. }
  139. })
  140. .afterShow(function(modal) {
  141. var color = userSettings.colorvalue;
  142. icon.colorize(document.getElementById(features[currentFeature].id), color, function() {});
  143. var radios = [];
  144. for (var i = 0 ; i < features.length ; i++) {
  145. var radio = document.getElementById(features[i].id);
  146. radios.push(radio);
  147. radio.addEventListener("click", function(e) {
  148. var index = -1;
  149. for (var j = 0 ; j < features.length ; j++) {
  150. if (features[j].id == e.srcElement.id) {
  151. index = j;
  152. break;
  153. }
  154. }
  155. if (index != currentFeature) {
  156. features[currentFeature].beforeUnactivate();
  157. document.getElementById(features[currentFeature].id).style.backgroundImage = "url("+features[currentFeature].icon+")";
  158. document.getElementById('journal-container').innerHTML = "";
  159. document.getElementById('search-text').value = '';
  160. currentFeature = index;
  161. features[currentFeature].filters = [filter1, orFilter2, orFilter3, orFilter4];
  162. features[currentFeature].beforeActivate();
  163. icon.colorize(document.getElementById(features[currentFeature].id), color, function() {});
  164. document.getElementById('search-text').placeholder=doLocalize(features[currentFeature].placeholder);
  165. }
  166. })
  167. }
  168. new radioButtonsGroup.RadioButtonsGroup(radios);
  169. var targetHeight = document.getElementById("pictotoolbar").parentNode.offsetHeight - 55*2;
  170. document.getElementById('journal-container').style.height = targetHeight + "px";
  171. document.getElementById('journal-container').addEventListener("scroll", function() {
  172. features[currentFeature].onScroll();
  173. });
  174. var favorite = document.getElementById('favorite-button');
  175. favorite.addEventListener('click', function() {
  176. features[currentFeature].onFavorite();
  177. });
  178. document.getElementById('search-text').addEventListener('keyup', function() {
  179. features[currentFeature].onSearch();
  180. });
  181. document.getElementById('cancel-search').addEventListener('click', function() {
  182. document.getElementById('search-text').value = '';
  183. features[currentFeature].onCancelSearch();
  184. });
  185. document.getElementById('close-button').addEventListener('click', function() {
  186. result = null;
  187. modal.close();
  188. });
  189. document.getElementById('search-text').placeholder=doLocalize(features[currentFeature].placeholder);
  190. features[currentFeature].filters = [filter1, orFilter2, orFilter3, orFilter4];
  191. features[currentFeature].beforeActivate();
  192. })
  193. .afterClose(function(modal) {
  194. modal.destroy();
  195. if (callback) {
  196. callback(result);
  197. }
  198. })
  199. .show();
  200. }
  201. // --- Journal feature functions
  202. // Get journal entries and fill dialog with entries
  203. function journalFill(filter1, orFilter2, orFilter3, orFilter4) {
  204. // Get filters
  205. var filters = [];
  206. if (filter1) { filters.push(filter1); }
  207. if (orFilter2) { filters.push(orFilter2); }
  208. if (orFilter3) { filters.push(orFilter3); }
  209. if (orFilter4) { filters.push(orFilter4); }
  210. // Compute filtering
  211. var journal = datastore.find();
  212. var rawJournal = [];
  213. for (var i = 0 ; i < journal.length ; i++) {
  214. var entry = journal[i];
  215. var match = true;
  216. if (featureLocalJournal.isFavorite) {
  217. match = match && entry.metadata.keep;
  218. }
  219. var title = document.getElementById('search-text').value;
  220. if (title && title.length > 0) {
  221. match = match && (entry.metadata.title && (entry.metadata.title.indexOf(title) != -1));
  222. }
  223. if (match) {
  224. rawJournal.push(entry)
  225. }
  226. }
  227. var length = filters.length;
  228. var filteredJournal = rawJournal;
  229. if (length > 0) {
  230. filteredJournal = [];
  231. for(var i = 0 ; i < rawJournal.length ; i++) {
  232. var entry = rawJournal[i];
  233. var match = false;
  234. for (var j = 0 ; j < length ; j++) {
  235. match = match || journalFilterMatch(entry, filters[j]);
  236. }
  237. if (match) {
  238. filteredJournal.push(entry);
  239. }
  240. }
  241. }
  242. // Get entries
  243. var journal = {entries: filteredJournal.sort(function(e0, e1) {
  244. return parseInt(e1.metadata.timestamp) - parseInt(e0.metadata.timestamp);
  245. })};
  246. // Add style properties
  247. for (var i = 0 ; i < journal.entries.length ; i++) {
  248. var entry = journal.entries[i];
  249. var activity = activities[entry.metadata.activity];
  250. entry.imageUrl = "../../" + activity.directory + "/" + activity.icon;
  251. entry.index = i;
  252. entry.ago = timestampToElapsedString(entry.metadata.creation_time);
  253. }
  254. // Draw it
  255. var template = "\
  256. {{#entries}}\
  257. <div id='entry_{{index}}' style='height:60px'>\
  258. <div id='eicon_{{index}}' class='toolbutton' style='background-image:url({{imageUrl}});background-size:40px 40px;width:40px;height:40px;display:inline-block;margin-left:20px;margin-top:5px;'></div>\
  259. <div id='etext_{{index}}' style='color:black;display:inline-block;vertical-align:middle;margin-left:30px;height:60px;margin-top:10px;font-weight:bold;font-size:14px;'>{{metadata.title}}</div>\
  260. <div id='edate_{{index}}' style='color:black;vertical-align:baseline;text-align:right;float:right;height:45px;padding-top:15px;margin-right:10px;clear:right;font-weight:normal;font-size:14px;'>{{ago}}</div>\
  261. </div>\
  262. {{/entries}}\
  263. ";
  264. var render = mustache.render(template, journal);
  265. document.getElementById('journal-container').innerHTML = render;
  266. // Handle click
  267. for (var i = 0 ; i < journal.entries.length ; i++) {
  268. var entry = document.getElementById('entry_'+i);
  269. entry.addEventListener('click', function(e) {
  270. var id = e.target.id;
  271. id = id.substr(id.indexOf("_")+1);
  272. var line = document.getElementById('entry_'+id);
  273. line.style.backgroundColor = "#808080";
  274. result = journal.entries[id];
  275. delete result['ago'];
  276. delete result['index'];
  277. delete result['imageUrl'];
  278. window.setTimeout(function() {
  279. modal.close(result);
  280. }, 200);
  281. });
  282. }
  283. document.getElementById('journal-empty').style.visibility = (journal.entries.length != 0 ? 'hidden' : 'visible');
  284. }
  285. // Test if an entry match a value
  286. function journalEntryMatch(entry, name, value) {
  287. var fieldValue = entry[name];
  288. if (!fieldValue) { return false; }
  289. var firstChar = value[0];
  290. if (firstChar == '%') {
  291. return fieldValue.indexOf(value.substr(1)) != -1;
  292. } else if (firstChar == '>') {
  293. return parseInt(fieldValue) > parseInt(value.substr(1));
  294. } else if (firstChar == '<') {
  295. return parseInt(fieldValue) < parseInt(value.substr(1));
  296. } else {
  297. return fieldValue == value;
  298. }
  299. }
  300. // Test if an entry match a filter
  301. function journalFilterMatch(entry, filter) {
  302. var metadata = entry.metadata;
  303. var keys = Object.keys(filter);
  304. var match = true;
  305. for (var j = 0 ; j < keys.length ; j++) {
  306. var field = keys[j];
  307. var value = filter[field];
  308. if (value instanceof Array) {
  309. var or = false;
  310. for (var k = 0 ; k < value.length ; k++) {
  311. or = or || journalEntryMatch(metadata, field, value[k]);
  312. }
  313. match = match && or;
  314. } else {
  315. match = match && journalEntryMatch(metadata, field, value);
  316. }
  317. }
  318. return match;
  319. }
  320. // --- Abecedarium feature functions
  321. // Load Abecedarium database
  322. function abecedariumInit(callback) {
  323. featureAbecedarium.database = {};
  324. document.getElementById('journal-empty').style.visibility = 'visible';
  325. featureAbecedarium.baseURL = document.location.href.substr(0, document.location.href.indexOf("/activities/"))+"/activities/Abecedarium.activity/";
  326. featureAbecedarium.lang = (["en","es","fr"].indexOf(userSettings.language)!=-1)?userSettings.language:"en";
  327. featureAbecedarium.filelocation = featureAbecedarium.filelocation.replace("{{lang}}",featureAbecedarium.lang);
  328. var count = 0;
  329. var loadDatabase = function(file, entry) {
  330. var client = new XMLHttpRequest();
  331. var source = featureAbecedarium.baseURL+file;
  332. client.onload = function() {
  333. if (entry == "ping") {
  334. featureAbecedarium.database[entry]=(this.status == 0 || (this.status >= 200 && this.status < 300));
  335. return;
  336. }
  337. if (this.status == 0 || (this.status >= 200 && this.status < 300)) {
  338. featureAbecedarium.database[entry]=JSON.parse(this.responseText);
  339. if (++count == 3) {
  340. var len = featureAbecedarium.database.meta.length;
  341. var entries = [];
  342. for (var i = 0 ; i < len; i++) {
  343. if (featureAbecedarium.database.meta[i][featureAbecedarium.lang]) {
  344. entries.push({"code":featureAbecedarium.database.meta[i]["code"],"text":featureAbecedarium.database.words[featureAbecedarium.database.meta[i]["text"]]});
  345. }
  346. }
  347. entries.sort(function(a,b) {
  348. return a.text.localeCompare(b.text, 'en', {sensitivity: 'base'});
  349. });
  350. var sortedEntries = [];
  351. for (var i = 0 ; i < entries.length ; i++) {
  352. entries[i].i = sortedEntries.length;
  353. sortedEntries.push(entries[i]);
  354. }
  355. featureAbecedarium.database.content = sortedEntries;
  356. delete featureAbecedarium.database.meta;
  357. delete featureAbecedarium.database.words;
  358. callback();
  359. }
  360. }
  361. };
  362. client.open("GET", source);
  363. client.send();
  364. };
  365. loadDatabase("database/db_url.json", "url");
  366. loadDatabase("database/db_meta.json", "meta");
  367. loadDatabase("database/db_"+featureAbecedarium.lang+".json", "words");
  368. loadDatabase("images/database/_ping.png?"+(new Date()).getTime(), "ping");
  369. }
  370. // Fill popup with Abecedarium items
  371. function abecedariumFill() {
  372. // Find entries matching search field
  373. var content = featureAbecedarium.database.content;
  374. var title = document.getElementById('search-text').value.toLowerCase();
  375. if (title.length) {
  376. content = [];
  377. for (var i = 0 ; i < featureAbecedarium.database.content.length ; i++) {
  378. if (featureAbecedarium.database.content[i].text.toLowerCase().indexOf(title) != -1) {
  379. content.push(featureAbecedarium.database.content[i]);
  380. }
  381. }
  382. }
  383. // Display result
  384. featureAbecedarium.results = content;
  385. featureAbecedarium.resultCount = 30;
  386. abecedariumDisplay(content, featureAbecedarium.resultCount);
  387. }
  388. function abecedariumDisplay(content, count) {
  389. // Display entries
  390. var template = "\
  391. {{#items}}\
  392. <div id='entry_{{i}}' style='height:60px'>\
  393. <div id='eicon_{{i}}' class='toolbutton' style='background-image:url(../../activities/MediaViewer.activity/activity/activity-icon.svg);background-size:40px 40px;width:40px;height:40px;display:inline-block;margin-left:20px;margin-top:5px;'></div>\
  394. <div id='etext_{{i}}' style='color:black;display:inline-block;vertical-align:middle;margin-left:30px;height:60px;margin-top:10px;font-weight:bold;font-size:14px;'>{{text}}</div>\
  395. </div>\
  396. {{/items}}\
  397. ";
  398. var items = {items: content.slice(0, count)};
  399. var render = mustache.render(template, items);
  400. document.getElementById('journal-empty').style.visibility = (content.length != 0 ? 'hidden' : 'visible');
  401. document.getElementById('journal-container').innerHTML = render;
  402. // Handle click
  403. var len = Math.min(count, content.length);
  404. for (var i = 0 ; i < len; i++) {
  405. var entry = document.getElementById('entry_'+content[i].i);
  406. entry.addEventListener('click', function(e) {
  407. var id = e.target.id;
  408. id = id.substr(id.indexOf("_")+1);
  409. var line = document.getElementById('entry_'+id);
  410. line.style.backgroundColor = "#808080";
  411. abecedariumCreateEntry(featureAbecedarium.database.content[id], function() {
  412. modal.close(result);
  413. });
  414. });
  415. if (featureAbecedarium.mimetype == "image/png") {
  416. document.getElementById('eicon_'+content[i].i).style.backgroundImage="url("+(featureAbecedarium.database.ping?featureAbecedarium.baseURL:featureAbecedarium.database.url)+"images/database/"+content[i].code+".png"+")";
  417. }
  418. }
  419. }
  420. // Create a record in Journal for the entry
  421. function abecedariumCreateEntry(entry, callback) {
  422. var url = featureAbecedarium.database.ping?featureAbecedarium.baseURL:featureAbecedarium.database.url;
  423. var mimetype = featureAbecedarium.mimetype;
  424. var request = new XMLHttpRequest();
  425. request.open("GET",url+featureAbecedarium.filelocation+entry.code+featureAbecedarium.fileformat,true);
  426. request.setRequestHeader("Content-type",mimetype);
  427. request.responseType = "arraybuffer";
  428. var that = this;
  429. request.onload = function() {
  430. if (request.status == 200 || request.status == 0) {
  431. var blob = new Uint8Array(this.response);
  432. var base64 = "data:"+mimetype+";base64,"+toBase64(blob);
  433. var metadata = {
  434. mimetype: mimetype,
  435. title: entry.text+featureAbecedarium.fileformat,
  436. activity: "org.olpcfrance.MediaViewerActivity",
  437. timestamp: new Date().getTime(),
  438. creation_time: new Date().getTime(),
  439. file_size: 0
  440. };
  441. datastore.create(metadata, function(err, objectId) {
  442. console.log("Entry '"+entry.text+"' saved in journal.");
  443. result = {metadata:metadata, objectId: objectId};
  444. callback();
  445. }, base64);
  446. } else {
  447. console.log("Error loading entry '"+entry.code+"'.");
  448. callback();
  449. }
  450. };
  451. request.onerror = function() {
  452. console.log("Error loading entry '"+entry.code+"'.");
  453. callback();
  454. };
  455. request.send();
  456. }
  457. // --- Utility functions
  458. // Localize content - currently means only localize in English
  459. var l10n = {
  460. titleJournal: {en: 'Journal', fr: 'Journal', es: 'Diario', pt: 'Diário'},
  461. titleAbecedarium: {en: 'Abecedarium', fr: 'Abecedarium', es: 'Abecedarium', pt: 'Abecedarium'},
  462. titleClose: {en: 'Cancel', fr: 'Annuler', es: 'Cancelar', pt: 'Cancelar'},
  463. titleChoose: {en: 'Choose an object', fr: 'Choisir un objet', es: 'Elige un objeto', pt: 'Escolher um objeto'},
  464. holderSearchJournal: {en: 'Search in Journal', fr: 'Recherche dans le journal', es: 'Buscar en el diario', pt: 'Pesquisar no diário'},
  465. holderSearchAbecedarium: {en: 'Search in Abecedarium', fr: 'Recherche dans Abecedarium', es: 'Buscar en Abecedarium', pt: 'Pesquisar no Abecedarium'},
  466. noMatchingEntries: {en: 'No matching entries', fr: 'Aucune entrée correspondante', es: 'No hay actividades coincidentes', pt: 'Sem atividades correspondentes'},
  467. SecondsAgo: {en: 'Seconds ago', fr: "A l'instant", es: 'Segundos atrás', pt: 'Segundos atrás'},
  468. Ago: {en: '{{time}} ago', fr: 'il y a {{time}}', es: '{{time}} atrás', pt: '{{time}} atrás'},
  469. Minutes_one: {en: 'minute', fr: 'minute', es: 'minuto', pt: 'minuto'},
  470. Minutes_other: {en: 'minutes', fr: 'minutes', es: 'minutos', pt: 'minutos'},
  471. Hours_one: {en: 'hour', fr: 'heure', es: 'hora', pt: 'hora'},
  472. Hours_other: {en: 'hours', fr: 'heures', es: 'horas', pt: 'horas'},
  473. Days_one: {en: 'day', fr: 'jour', es: 'día', pt: 'dia'},
  474. Days_other: {en: 'days', fr: 'jours', es: 'días', pt: 'dias'},
  475. Weeks_one: {en: 'week', fr: 'semaine', es: 'semana', pt: 'semana'},
  476. Weeks_other: {en: 'weeks', fr: 'semaines', es: 'semanas', pt: 'semanas'},
  477. Months_one: {en: 'month', fr: 'mois', es: 'mes', pt: 'mês'},
  478. Months_other: {en: 'months', fr: 'mois', es: 'meses', pt: 'meses'},
  479. Years_one: {en: 'year', fr: 'année', es: 'año', pt: 'ano'},
  480. Years_other: {en: 'years', fr: 'années', es: 'años', pt: 'anos'},
  481. };
  482. function doLocalize(str, params) {
  483. var lang = (["en","fr","es", "pt"].indexOf(userSettings.language)!=-1)?userSettings.language:"en";
  484. var out = str;
  485. for(var current in l10n) {
  486. out = out.replace('$'+current, l10n[current][lang]);
  487. }
  488. for(var param in params) {
  489. out = out.replace('{{'+param+'}}', params[param]);
  490. }
  491. return out;
  492. }
  493. // Compute elapsed time as a string
  494. function timestampToElapsedString(timestamp) {
  495. var units = [{name:'Years', factor:356 * 24 * 60 * 60},
  496. {name:'Months', factor:30 * 24 * 60 * 60},
  497. {name:'Weeks', factor:7 * 24 * 60 * 60},
  498. {name:'Days', factor:24 * 60 * 60},
  499. {name:'Hours', factor:60 * 60},
  500. {name:'Minutes', factor:60}];
  501. var maxlevel = 1;
  502. var levels = 0;
  503. var time_period = '';
  504. var time_stamp = (new Date(timestamp).getTime());
  505. var elapsed_seconds = ((new Date().getTime()) - time_stamp)/1000;
  506. for (var i = 0; i < units.length ; i++) {
  507. var factor = units[i].factor;
  508. var elapsed_units = Math.floor(elapsed_seconds / factor);
  509. if (elapsed_units > 0) {
  510. if (levels > 0)
  511. time_period += ',';
  512. time_period += ' '+elapsed_units+" "+(elapsed_units==1?doLocalize("$"+units[i].name+"_one"):doLocalize("$"+units[i].name+"_other"));
  513. elapsed_seconds -= elapsed_units * factor;
  514. }
  515. if (time_period != '')
  516. levels += 1;
  517. if (levels == maxlevel)
  518. break;
  519. }
  520. if (levels == 0) {
  521. return doLocalize("$SecondsAgo");
  522. }
  523. return doLocalize("$Ago", {time: time_period});
  524. }
  525. // Encoding functions taken from
  526. // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
  527. function uint6ToB64(nUint6) {
  528. return nUint6 < 26 ?
  529. nUint6 + 65 : nUint6 < 52 ?
  530. nUint6 + 71 : nUint6 < 62 ?
  531. nUint6 - 4 : nUint6 === 62 ?
  532. 43 : nUint6 === 63 ?
  533. 47 : 65;
  534. }
  535. function toBase64(aBytes) {
  536. var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";
  537. for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
  538. nMod3 = nIdx % 3;
  539. nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
  540. if (nMod3 === 2 || aBytes.length - nIdx === 1) {
  541. sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
  542. nUint24 = 0;
  543. }
  544. }
  545. return eqLen === 0 ? sB64Enc : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");
  546. }
  547. return chooser;
  548. });