showoff 0.17.2 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +37 -0
- data/bin/showoff +3 -0
- data/lib/showoff.rb +164 -43
- data/lib/showoff/version.rb +1 -1
- data/lib/showoff_utils.rb +26 -14
- data/locales/README.md +25 -0
- data/locales/de.yml +160 -0
- data/locales/en.yml +160 -0
- data/locales/es.yml +160 -0
- data/locales/fr.yml +160 -0
- data/locales/ja.yml +167 -0
- data/locales/nl.yml +160 -0
- data/locales/pt.yml +160 -0
- data/public/css/presenter.css +14 -1
- data/public/css/showoff.css +54 -4
- data/public/js/presenter.js +122 -49
- data/public/js/showoff.js +157 -25
- data/public/js/simpleStrings-0.0.1.js +96 -0
- data/views/404.erb +1 -1
- data/views/download.erb +2 -2
- data/views/header.erb +7 -2
- data/views/help.erb +16 -16
- data/views/index.erb +40 -26
- data/views/presenter.erb +86 -68
- data/views/stats.erb +7 -7
- metadata +39 -2
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
|
-
|
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
|
-
|
115
|
-
|
116
|
-
$('#
|
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
|
-
|
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(
|
244
|
+
slaveWindow.renderForm(formID);
|
224
245
|
}
|
225
246
|
catch (e) {
|
226
247
|
console.log(e);
|
227
|
-
renderForm(
|
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,
|
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").
|
1427
|
+
$("#followMode").addClass('fa-check-circle');
|
1428
|
+
$("#followMode").removeClass('fa-ban');
|
1311
1429
|
getPosition();
|
1312
1430
|
} else {
|
1313
|
-
$("#followMode").
|
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 = '
|
1552
|
+
var message = I18n.t('reload');
|
1433
1553
|
}
|
1434
1554
|
else {
|
1435
|
-
var message =
|
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(
|
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('
|
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(
|
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
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
|
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/
|
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>
|