showoff 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>