showoff 0.17.2 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
data/public/js/showoff.js CHANGED
@@ -26,6 +26,10 @@ var loadSlidesPrefix
26
26
 
27
27
  var mode = { track: true, follow: true };
28
28
 
29
+ // a dummy websocket object to make standalone presentations easier.
30
+ var ws = {}
31
+ ws.send = function() { /* no-op */ }
32
+
29
33
  // since javascript doesn't have a built-in way to get to cookies easily,
30
34
  // let's just add our own data structure.
31
35
  document.cookieHash = {}
@@ -51,6 +55,7 @@ function setupPreso(load_slides, prefix) {
51
55
  preso_started = true;
52
56
 
53
57
  if (! cssPropertySupported('flex') ) {
58
+ // TODO: translate this this page!
54
59
  window.location = 'unsupported.html';
55
60
  }
56
61
 
@@ -68,7 +73,11 @@ function setupPreso(load_slides, prefix) {
68
73
 
69
74
  setupSideMenu();
70
75
 
71
- doDebugStuff();
76
+ // Set up the language selector
77
+ $('#languageSelector').change(function(e) { chooseLanguage(e.target.value); });
78
+ chooseLanguage(null);
79
+
80
+ doDebugStuff();
72
81
 
73
82
  // bind event handlers
74
83
  toggleKeybinding('on');
@@ -92,6 +101,11 @@ function setupPreso(load_slides, prefix) {
92
101
  // yes, this is a global
93
102
  annotations = new Annotate();
94
103
 
104
+ // must be defined using [] syntax for a variable button name on IE.
105
+ var closeLabel = I18n.t('help.close');
106
+ var buttons = {};
107
+ buttons[closeLabel] = function() { $(this).dialog( "close" ); };
108
+
95
109
  $("#help-modal").dialog({
96
110
  autoOpen: false,
97
111
  dialogClass: "no-close",
@@ -100,22 +114,25 @@ function setupPreso(load_slides, prefix) {
100
114
  modal: true,
101
115
  resizable: false,
102
116
  width: 640,
103
- buttons: {
104
- Close: function() {
105
- $( this ).dialog( "close" );
106
- }
107
- }
117
+ buttons: buttons
108
118
  });
109
119
 
110
120
  // wait until the presentation is loaded to hook up the previews.
111
121
  $("body").bind("showoff:loaded", function (event) {
122
+ var target = $('#navigationHover');
123
+
112
124
  $('#navigation li a.navItem').hover(function() {
113
125
  var position = $(this).position();
114
- $('#navigationHover').css({top: position.top, left: position.left + $('#navigation').width() + 5})
115
- $('#navigationHover').html(slides.eq($(this).attr('rel')).html());
116
- $('#navigationHover').show();
126
+ var source = slides.eq($(this).attr('rel'));
127
+
128
+ target.css({top: position.top, left: position.left + $('#navigation').width() + 5})
129
+ target.html(source.html());
130
+
131
+ copyBackground(source, target);
132
+
133
+ target.show();
117
134
  },function() {
118
- $('#navigationHover').hide();
135
+ target.hide();
119
136
  });
120
137
  });
121
138
 
@@ -218,13 +235,17 @@ function initializePresentation(prefix) {
218
235
  });
219
236
 
220
237
  $(".content form div.tools input.display").click(function(e) {
238
+ var form = $(this).closest('form');
239
+ var formID = form.attr('id');
240
+
241
+ ws.send(JSON.stringify({ message: 'answerkey', formID: formID}));
221
242
  try {
222
243
  // If we're a presenter, try to bust open the slave display
223
- slaveWindow.renderForm($(this).closest('form').attr('id'));
244
+ slaveWindow.renderForm(formID);
224
245
  }
225
246
  catch (e) {
226
247
  console.log(e);
227
- renderForm($(this).closest('form'));
248
+ renderForm(form);
228
249
  }
229
250
  });
230
251
 
@@ -238,9 +259,46 @@ function initializePresentation(prefix) {
238
259
  // initialize mermaid, but don't render yet since the slide sizes are indeterminate
239
260
  mermaid.initialize({startOnLoad:false});
240
261
 
262
+ // translate SVG images, inlining them first if needed.
263
+ $('img').simpleStrings({strings: user_translations});
264
+ $('svg').simpleStrings({strings: user_translations});
265
+ $('.translate').simpleStrings({strings: user_translations});
266
+
241
267
  $("#preso").trigger("showoff:loaded");
242
268
  }
243
269
 
270
+ function copyBackground(source, target) {
271
+ // to get this to properly copy over in Firefox, we need to iterate each property instead of using shorthand
272
+ ['background-color',
273
+ 'background-image',
274
+ 'background-repeat',
275
+ 'background-position',
276
+ 'background-attachment'].forEach(function(property) {
277
+ target.css(property, source.css(property));
278
+ })
279
+
280
+ // we have to do this separately so we can transform it
281
+ var bgsize = source.css('background-size');
282
+
283
+ var regex = /^(\d+)(\S{1,2})(?: (\d+)(\S{1,2}))?$/;
284
+ var match = regex.exec(bgsize);
285
+ if(match) {
286
+ var width = match[1];
287
+ var unit_w = match[2];
288
+ var height = match[3] || '';
289
+ var unit_h = match[4] || '';
290
+
291
+ if(unit_w != '%' ) { width /= 2 };
292
+ if(unit_h != '%' && height != '' ) { height /= 2 };
293
+
294
+ target.css('background-size', width+unit_w+' '+height+unit_h);
295
+ }
296
+ else {
297
+ // contain, cover, etc
298
+ target.css('background-size', bgsize);
299
+ }
300
+ }
301
+
244
302
  function zoom(presenter) {
245
303
  var preso = $("#preso");
246
304
  var hSlide = parseFloat(preso.height());
@@ -320,7 +378,7 @@ function setupSideMenu() {
320
378
  var question = $("#question").val()
321
379
  var qid = askQuestion(question);
322
380
 
323
- feedback_response(this, "Sending...");
381
+ feedback_response(this, I18n.t('menu.sending'));
324
382
  $("#question").val('');
325
383
 
326
384
  var questionItem = $('<li/>').text(question).attr('id', qid);
@@ -474,9 +532,49 @@ function setupMenu() {
474
532
  $("#navigation").append(nav);
475
533
  }
476
534
 
535
+
536
+ // this function generates an object that consumes the JSON form of translations
537
+ // provided by the i18n gem. It provides pretty nearly the same calling syntax
538
+ // as the Ruby library's dot-form.
539
+ //
540
+ // var I18n = new translation(data);
541
+ // console.log(I18n.t('some.key.to.translate'));
542
+ function translation(data) {
543
+ this.localized = data;
544
+ this.translate = function(key) {
545
+ var item = this.localized;
546
+ try {
547
+ key.split('.').forEach(function(val) {
548
+ item = item[val];
549
+ });
550
+ if(typeof(item) != 'string') {
551
+ item = null;
552
+ }
553
+ }
554
+ catch(e) {
555
+ item = null;
556
+ }
557
+ return item || ("No translation for " + key);
558
+ }
559
+ this.t = function(key) { return this.translate(key); }
560
+ }
561
+
562
+ function chooseLanguage(locale) {
563
+ // yay for half-baked data storage schemes
564
+ newlocale = locale || document.cookieHash['locale'] || 'auto';
565
+
566
+ if(locale){
567
+ document.cookie = "locale="+newlocale;
568
+ location.reload(false);
569
+ } else {
570
+ $('#languageSelector').val(newlocale);
571
+ }
572
+ }
573
+
477
574
  // at some point this should get more sophisticated. Our needs are pretty minimal so far.
478
575
  function clearCookies() {
479
576
  document.cookie = "sidebar=;expires=Thu, 21 Sep 1979 00:00:01 UTC;";
577
+ document.cookie = "locale=;expires=Thu, 21 Sep 1979 00:00:01 UTC;";
480
578
  document.cookie = "layout=;expires=Thu, 21 Sep 1979 00:00:01 UTC;";
481
579
  document.cookie = "notes=;expires=Thu, 21 Sep 1979 00:00:01 UTC;";
482
580
  }
@@ -688,6 +786,16 @@ function getSlideProgress()
688
786
  return (slidenum + 1) + '/' + slideTotal
689
787
  }
690
788
 
789
+ function getAllSections()
790
+ {
791
+ memo = []
792
+ $("div.notes-section").each(function() {
793
+ section = $(this).attr('class').split(' ').filter(function(x) { return x != 'notes-section'; })[0];
794
+ if(! memo.includes(section)) { memo.push(section) }
795
+ });
796
+ return memo
797
+ }
798
+
691
799
  function getCurrentSections()
692
800
  {
693
801
  return currentSlide.find("div.notes-section").map(function() {
@@ -800,6 +908,15 @@ function enableForm(element) {
800
908
  activityIncomplete = true;
801
909
  }
802
910
 
911
+ function showFormAnswers(form) {
912
+ // If we have any correct options, find the parent element, then tag all descendants as incorrect
913
+ $('.slide.form\\='+form+' label.correct').parents('.form.element').find('label.response,option').addClass('incorrect');
914
+ // Then remove the double tag from the correct answers.
915
+ $('.slide.form\\='+form+' label.correct').removeClass('incorrect');
916
+ // finally, style the slide so we can see the effects
917
+ $('.slide.form\\='+form).addClass('answerkey')
918
+ }
919
+
803
920
  function renderFormWatcher(element) {
804
921
  var form = element.attr('title');
805
922
  var action = $('.content form#'+form).attr('action');
@@ -957,10 +1074,6 @@ function connectControlChannel() {
957
1074
  ws.onclose = function() { disconnected(); }
958
1075
  ws.onmessage = function(m) { parseMessage(m.data); };
959
1076
  }
960
- else {
961
- ws = {}
962
- ws.send = function() { /* no-op */ }
963
- }
964
1077
  }
965
1078
 
966
1079
  // This exists as an intermediary simply so the presenter view can override it
@@ -1019,6 +1132,10 @@ function parseMessage(data) {
1019
1132
  follow(command["current"], command["increment"]);
1020
1133
  break;
1021
1134
 
1135
+ case 'answerkey':
1136
+ showFormAnswers(command["formID"]);
1137
+ break;
1138
+
1022
1139
  case 'complete':
1023
1140
  completeQuestion(command["questionID"]);
1024
1141
  break;
@@ -1307,11 +1424,14 @@ function toggleFollow()
1307
1424
  mode.follow = ! mode.follow;
1308
1425
 
1309
1426
  if(mode.follow) {
1310
- $("#followMode").show().text('Follow Mode:');
1427
+ $("#followMode").addClass('fa-check-circle');
1428
+ $("#followMode").removeClass('fa-ban');
1311
1429
  getPosition();
1312
1430
  } else {
1313
- $("#followMode").hide();
1431
+ $("#followMode").addClass('fa-ban');
1432
+ $("#followMode").removeClass('fa-check-circle');
1314
1433
  }
1434
+ showFooter();
1315
1435
  }
1316
1436
 
1317
1437
  function debug(data)
@@ -1429,11 +1549,10 @@ function toggleDebug () {
1429
1549
 
1430
1550
  function reloadSlides (hard) {
1431
1551
  if(hard) {
1432
- var message = 'Are you sure you want to reload Showoff?';
1552
+ var message = I18n.t('reload');
1433
1553
  }
1434
1554
  else {
1435
- var message = "Are you sure you want to refresh the slide content?\n\n";
1436
- message += '(Use `RELOAD` to fully reload the entire UI)';
1555
+ var message = I18n.t('refresh');
1437
1556
  }
1438
1557
 
1439
1558
  if (confirm(message)) {
@@ -1445,6 +1564,19 @@ function toggleFooter() {
1445
1564
  $('#footer').toggle()
1446
1565
  }
1447
1566
 
1567
+ function showFooter(timeout) {
1568
+ timeout = (typeof timeout !== 'undefined') ? timeout : 5000;
1569
+
1570
+ if($('#footer').is(':hidden')) {
1571
+ $('#footer').show(200);
1572
+
1573
+ window.setTimeout(function() {
1574
+ $('#footer').hide(200);
1575
+ }, timeout);
1576
+ }
1577
+
1578
+ }
1579
+
1448
1580
  function toggleHelp () {
1449
1581
  var help = $("#help-modal");
1450
1582
  help.dialog("isOpen") ? help.dialog("close") : help.dialog("open");
@@ -1599,7 +1731,7 @@ function togglePreShow() {
1599
1731
  }
1600
1732
 
1601
1733
  } else {
1602
- var seconds = parseFloat(prompt("Minutes from now to start") * 60);
1734
+ var seconds = parseFloat(prompt(I18n.t('preshow.prompt')) * 60);
1603
1735
 
1604
1736
  try {
1605
1737
  slaveWindow.setupPreShow(seconds);
@@ -1658,7 +1790,7 @@ function startPreShow() {
1658
1790
  }
1659
1791
 
1660
1792
  function addPreShowTips(secondsLeft) {
1661
- $('#preshow_timer').text('Resuming in: ' + secondsToTime(secondsLeft));
1793
+ $('#preshow_timer').text(I18n.t('preshow.resume') + ' ' + secondsToTime(secondsLeft));
1662
1794
  var des = preshow_des && preshow_des[tmpImg.attr("ref")];
1663
1795
  if(des) {
1664
1796
  $('#tips').show();
@@ -1749,7 +1881,7 @@ function setupStats(data)
1749
1881
  if (viewers) {
1750
1882
  if (viewers.length == 1 && viewers[0][3] == 'current') {
1751
1883
  $("#viewers").removeClass('zoomline');
1752
- $("#viewers").text("All audience members are viewing the presenter's slide.");
1884
+ $("#viewers").text(I18n.t('stats.allcurrent'));
1753
1885
  }
1754
1886
  else {
1755
1887
  $("#viewers").zoomline({
@@ -0,0 +1,96 @@
1
+ /* Example usage:
2
+ var translations = {
3
+ en: {
4
+ 'greeting': 'Hello there!',
5
+ 'farewell': "Goodbye.'
6
+ },
7
+ fr: {
8
+ 'greeting': 'Bonjour!',
9
+ 'farewell': "Au revoir.'}.
10
+ es: {
11
+ 'greeting': 'Hola!',
12
+ 'farewell': "Adios amigo.'
13
+ }
14
+ };
15
+
16
+ $(document).ready(function(){
17
+ var lang = translations.es;
18
+
19
+ $('img').simpleStrings({strings: lang});
20
+ $('svg').simpleStrings({strings: lang});
21
+ $('.translate').simpleStrings({strings: lang}); // matches tags like <span class="translate">{{greeting}}</span>
22
+ });
23
+
24
+ */
25
+
26
+ (function ( $ ) {
27
+ $.fn.simpleStrings = function(options) {
28
+ var settings = $.extend({
29
+ strings: {}
30
+ }, options );
31
+
32
+ function translate(item) {
33
+ item = $(item);
34
+ var text = item.text();
35
+
36
+ if(matches = text.match(/^{{(.*)}}$/) ) {
37
+ keyword = matches[1];
38
+
39
+ if(keyword in settings.strings) {
40
+ item.text(settings.strings[keyword]);
41
+ }
42
+ }
43
+
44
+ return item;
45
+ }
46
+
47
+ function inline_svg(img, callback) {
48
+ var source = img.attr('src');
49
+ var imgId = img.attr('id');
50
+ var klass = img.attr('class');
51
+
52
+ $.get(source, function( data ) {
53
+ var svg = $(data).find('svg');
54
+ svg.attr('id', imgId);
55
+ svg.attr('class', klass);
56
+
57
+ if (typeof callback === 'function') {
58
+ callback.call(svg);
59
+ }
60
+
61
+ img.replaceWith(svg);
62
+ console.log( "Inlined SVG image: " + source);
63
+ });
64
+
65
+ }
66
+
67
+ return this.each(function() {
68
+ var item = $(this);
69
+
70
+ // we can only translate img tags if they're referencing svg images
71
+ if(item.is('img')) {
72
+ // nested if because we don't want images to match the final else
73
+ if(item.attr('src').match(/.*\.svg$/i)) {
74
+ inline_svg(item, function(){
75
+ $(this).find('text, p').each(function(){
76
+ translate(this);
77
+ });
78
+ });
79
+ }
80
+ }
81
+ else if(item.is('svg')) {
82
+ // svg images already inlined. Translate by finding all texty elements
83
+ item.find('text, p').each(function(){
84
+ translate(this);
85
+ });
86
+ }
87
+ else {
88
+ // everything else. We'll try to translate, as long as there's .text()
89
+ translate(item);
90
+ }
91
+
92
+ return this;
93
+ });
94
+ };
95
+
96
+ }(jQuery));
data/views/404.erb CHANGED
@@ -8,7 +8,7 @@
8
8
  <body id="download">
9
9
 
10
10
  <div id="preso">
11
- <h1>File Not Found!</h1>
11
+ <h1><%= I18n.t('error.file_not_found') %></h1>
12
12
  <h2><%= @env['REQUEST_PATH'] %>
13
13
  </div>
14
14
  <div id="footer">
data/views/download.erb CHANGED
@@ -6,8 +6,8 @@
6
6
  </head>
7
7
 
8
8
  <body id="download">
9
- <div id="wrapper"> <!-- wrapper needed for presenterPopupToggle() and $.get() -->
10
- <h1>File Downloads</h1>
9
+ <div id="wrapper"> <!-- wrapper needed for presenterPopupToggle() and $.get() -->
10
+ <h1><%= I18n.t('downloads.title') %></h1>
11
11
  <% if @downloads %>
12
12
  <%# [ enabled, slide name, [array, of, files] ] %>
13
13
  <% @downloads.sort.map do |key, value| %>
data/views/header.erb CHANGED
@@ -15,17 +15,18 @@
15
15
  <link rel="stylesheet" type="text/css" href="<%= @asset_path %>/css/zoomline-0.0.1.css">
16
16
 
17
17
  <script type="text/javascript" src="<%= @asset_path %>/js/jquery-2.1.4.min.js"></script>
18
+ <script type="text/javascript" src="<%= @asset_path %>/js/jquery-ui-1.12.1.js"></script>
18
19
 
19
20
  <script type="text/javascript" src="<%= @asset_path %>/js/jquery.cycle.all-2.8.0.js"></script>
20
21
  <script type="text/javascript" src="<%= @asset_path %>/js/jquery.batchImageLoad-1.0.0.js"></script>
21
22
  <script type="text/javascript" src="<%= @asset_path %>/js/jquery.parsequery.min-6a20f83.js"></script>
22
23
  <script type="text/javascript" src="<%= @asset_path %>/js/jquery.doubletap-4ff02c5.js"></script>
24
+ <script type="text/javascript" src="<%= @asset_path %>/js/highlight.pack-9.2.0.js"></script>
23
25
  <script type="text/javascript" src="<%= @asset_path %>/js/jTypeWriter-1.1.js"></script>
24
26
  <script type="text/javascript" src="<%= @asset_path %>/js/bigtext-0.1.8.js"></script>
25
27
  <script type="text/javascript" src="<%= @asset_path %>/js/zoomline-0.0.1.js"></script>
26
- <script type="text/javascript" src="<%= @asset_path %>/js/highlight.pack-9.2.0.js"></script>
28
+ <script type="text/javascript" src="<%= @asset_path %>/js/simpleStrings-0.0.1.js"></script>
27
29
  <script type="text/javascript" src="<%= @asset_path %>/js/mermaid-6.0.0-min.js"></script>
28
- <script type="text/javascript" src="<%= @asset_path %>/js/jquery-ui-1.12.1.js"></script>
29
30
 
30
31
  <script type="text/javascript" src="<%= @asset_path %>/js/coffee-script-1.1.3-pre.js"></script>
31
32
 
@@ -47,9 +48,13 @@
47
48
 
48
49
  editUrl = "<%= @edit %>";
49
50
  interactive = <%= @interactive %>;
51
+ master = <%= master_presenter? %>;
50
52
 
51
53
  keymap = <%= JSON.pretty_generate @keymap %>;
52
54
  keycode_dictionary = <%= JSON.pretty_generate @keycode_dictionary %>;
53
55
  keycode_shifted_keys = <%= JSON.pretty_generate @keycode_shifted_keys %>;
56
+ user_translations = <%= JSON.pretty_generate user_translations %>;
57
+
58
+ I18n = new translation(<%= @language.to_json %>);
54
59
 
55
60
  </script>