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.
- 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>
|