/
admin.js
741 lines (615 loc) · 19.1 KB
/
admin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
/**
* @package assets
*/
/**
* The Symphony object provides language, message and context management.
*
* @class
*/
var Symphony = {};
(function($) {
Symphony = {
/**
* Initialize the Symphony object
*/
init: function() {
var html = $('html'),
user = $('#usr li:first a');
// Set JavaScript status
html.addClass('active');
// Set basic context information
Symphony.Context.add('user', {
fullname: user.text(),
name: user.data('name'),
type: user.data('type'),
id: user.data('id')
});
Symphony.Context.add('lang', html.attr('lang'));
// Set browser support information
Symphony.Support.localStorage = ('localStorage' in window && window['localStorage'] !== null) ? true : false;
// Deep copy jQuery.support
$.extend(true, Symphony.Support, $.support);
// Initialise language
Symphony.Language.add({
'Add item': false,
'Remove selected items': false,
'Are you sure you want to proceed?': false,
'Reordering was unsuccessful.': false,
'Password': false,
'Change Password': false,
'Remove File': false,
'at': false,
'just now': false,
'a minute ago': false,
'{$minutes} minutes ago': false,
'about 1 hour ago': false,
'about {$hours} hours ago': false
});
/**
* @deprecated You should now use Symphony.Context.get('root')
*/
Symphony.WEBSITE = Symphony.Context.get('root');
/**
* @deprecated You should now use Symphony.Context.get('lang')
*/
Symphony.Language.NAME = Symphony.Context.get('lang');
},
/**
* The Context object contains general information about the system,
* the backend, the current user. It includes an add and a get function.
*
* @class
*/
Context: {
/**
* This object is private, use Symphony.Context.add() and
* Symphony.Context.get() to interact with the dictionary.
*
* @private
*/
Storage: {},
/**
* Add data to the Context object
*
* @param {String} group
* Name of the data group
* @param {String|Object} values
* Object or string to be stored
*/
add: function(group, values) {
// Extend existing group
if(Symphony.Context.Storage[group] && $.type(values) !== 'string') {
Symphony.Context.Storage[group] = $.extend(Symphony.Context.Storage[group], values);
}
// Add new group
else {
Symphony.Context.Storage[group] = values;
}
},
/**
* Get data from the Context object
*
* @param {String} group
* Name of the group to be returned
*/
get: function(group) {
// Return full context, if no group is set
if(!group) {
return Symphony.Context.Storage;
}
// Return context group
else {
return Symphony.Context.Storage[group];
}
}
},
/**
* The Language object stores the dictionary with all needed translations.
* It offers public functions to add strings and get their translation and
* it offers private functions to handle variables and get the translations via
* an synchronous AJAX request.
*
* @class
*/
Language: {
/**
* This object is private, use Symphony.Language.add() to add and Symphony.Language.get()
* to interact with the dictionary.
*
* @private
*/
Dictionary: {},
/**
* Add strings to the Dictionary
*
* @param {Object} strings
* Object with English string as key, value should be false
*/
add: function(strings) {
// Don't process empty strings
if($.isEmptyObject(strings)) {
return true;
}
// Set key as value
$.each(strings, function(key, value) {
strings[key] = key;
});
// Save English strings
if(Symphony.Context.get('lang') == 'en') {
Symphony.Language.Dictionary = $.extend(Symphony.Language.Dictionary, strings);
}
// Translate strings
else {
Symphony.Language.translate(strings);
}
},
/**
* Get translated string from the Dictionary.
* The function replaces variables like {$name} with the a specified value if
* an object of inserts is passed in the function call.
*
* @param {String} string
* English string to be translated
* @param {Object} inserts
* Object with variable name and value pairs
* @return {String}
* Returns the translated string
*/
get: function(string, inserts) {
// Get translated string
var translatedString = Symphony.Language.Dictionary[string];
// Return string if it cannot be found in the dictionary
if(translatedString !== false) {
string = translatedString;
}
// Insert variables
if(inserts !== undefined) {
string = Symphony.Language.insert(string, inserts);
}
// Return translated string
return string;
},
/**
* This private function replaces variables with a specified value.
* It should not be called directly.
*
* @param {String} string
* Translated string with variables
* @param {Object} inserts
* Object with variable name and value pairs
* @return {String}
* Returns translated strings with all variables replaced by their actual value
*/
insert: function(string, inserts) {
// Replace variables
$.each(inserts, function(index, value) {
string = string.replace('{$' + index + '}', value);
});
return string;
},
/**
* This private function sends a synchronous AJAX request to fetch the translations
* for the English strings in the dictionary. It should not be called directly
*
* @param {Object} strings
* Object of strings to be translated
* @return {Object}
* Object with original string and translation pairs
*/
translate: function(strings) {
// Load translations synchronous
$.ajax({
async: false,
type: 'GET',
url: Symphony.Context.get('root') + '/symphony/ajax/translate/',
data: strings,
dataType: 'json',
success: function(result) {
Symphony.Language.Dictionary = $.extend(Symphony.Language.Dictionary, result);
},
error: function() {
Symphony.Language.Dictionary = $.extend(Symphony.Language.Dictionary, strings);
}
});
}
},
/**
* The message object handles system messages that should be displayed on the fly.
* It offers a post and a clear function to set and remove messages. Absolute dates
* and times will be replaced by a representation relative to the user's system time.
*
* @class
*/
Message: {
/**
* This array is private and should not be accessed directly.
*
* @private
*/
Queue: [],
/**
* Post system message
*
* @param {String} message
* Message to be shown
* @param {String} type
* Message type to be used as class name
*/
post: function(message, type) {
// Store previous message
Symphony.Message.Queue = Symphony.Message.Queue.concat($('#notice').remove().get());
// Add new message
$('h1').before('<div id="notice" class="' + type + '">' + message + '</div>');
},
/**
* Clear message by type
*
* @param {String} type
* Message type
*/
clear: function(type) {
var message = $('#notice');
// Remove messages of specified type
message.filter('.' + type).remove();
Symphony.Message.Queue = $(Symphony.Message.Queue).filter(':not(.' + type + ')').get();
// Show previous message
if(message.size() > 0 && Symphony.Message.Queue.length > 0) {
$(Symphony.Message.Queue.pop()).insertBefore('h1');
}
},
/**
* Fade message highlight color to grey
*/
fade: function(newclass, delay) {
var notice = $('#notice.success').addClass(newclass),
styles = {
'color': notice.css('color'),
'backgroundColor': notice.css('background-color'),
'borderTopColor': notice.css('border-top-color'),
'borderRightColor': notice.css('border-right-color'),
'borderBottomColor': notice.css('border-bottom-color'),
'borderLeftColor': notice.css('border-left-color')
};
// Delayed animation to new styles
if(notice.is(':visible')) {
notice.removeClass(newclass).delay(delay).animate(styles, 'slow', 'linear', function() {
$(this).removeClass('success');
});
}
},
/**
* Convert absolute message time to relative time and update continuously
*/
timer: function() {
var time = Date.parse($('abbr.timeago').attr('title')),
to = new Date(),
from = new Date();
// Set time
from.setTime(time);
// Set relative time
$('abbr.timeago').text(this.distance(from, to));
// Update continuously
window.setTimeout("Symphony.Message.timer()", 60000);
},
/**
* Calculate relative time.
*
* @param {Date} from
* Starting date
* @param {Date} to
* Current date
*/
distance: function(from, to) {
// Calculate time difference
var distance = to - from;
// Convert time to minutes
var time = Math.floor(distance / 60000);
// Return relative date based on passed time
if(time < 1) {
return Symphony.Language.get('just now');
}
if(time < 2) {
return Symphony.Language.get('a minute ago');
}
if(time < 45) {
return Symphony.Language.get('{$minutes} minutes ago', {
'minutes': time
});
}
if(time < 90) {
return Symphony.Language.get('about 1 hour ago');
}
else {
return Symphony.Language.get('about {$hours} hours ago', {
'hours': Math.floor(time / 60)
});
}
}
},
/**
* A collection of properties that represent the presence of
* different browser features and also contains the test results
* from jQuery.support.
*
* @class
*/
Support: {
/**
* Does the browser have support for the HTML5 localStorage API
* @type Boolean
* @default false*
* @example
if(Symphony.Support.localStorage) { ... }
*/
localStorage: false
}
};
/**
* Symphony core interactions
*/
$(document).ready(function() {
// Initialize Symphony
Symphony.init();
// Tags
$('.tags').symphonyTags();
// Pickers
$('.picker').symphonyPickable();
// Selectable
var selectable = $('table.selectable');
selectable.symphonySelectable();
selectable.find('a').mousedown(function(event) {
event.stopPropagation();
});
// Orderable list
$('ul.orderable').symphonyOrderable();
// Orderable tables
var old_sorting, orderable = $('table.orderable');
orderable.symphonyOrderable({
items: 'tr',
handles: 'td'
});
// Don't start ordering while clicking on links
orderable.find('a').mousedown(function(event) {
event.stopPropagation();
});
// Store current sort order
orderable.live('orderstart', function() {
old_sorting = orderable.find('input').map(function(e, i) { return this.name + '=' + (e + 1); }).get().join('&');
});
// Process sort order
orderable.live('orderstop', function() {
orderable.addClass('busy');
// Get new sort order
var new_sorting = orderable.find('input').map(function(e, i) { return this.name + '=' + (e + 1); }).get().join('&');
// Store new sort order
if(new_sorting != old_sorting) {
// Update items
orderable.trigger('orderchange');
// Send request
$.ajax({
type: 'POST',
url: Symphony.Context.get('root') + '/symphony/ajax/reorder' + location.href.slice(Symphony.Context.get('root').length + 9),
data: new_sorting,
success: function() {
Symphony.Message.clear('reorder');
},
error: function() {
Symphony.Message.post(Symphony.Language.get('Reordering was unsuccessful.'), 'reorder error');
},
complete: function() {
orderable.removeClass('busy').find('tr').removeClass('selected');
old_sorting = '';
}
});
}
else {
orderable.removeClass('busy');
}
});
// Duplicators
$('.filters-duplicator').symphonyDuplicator();
// Collapsible duplicators
var duplicator = $('#fields-duplicator');
duplicator.symphonyDuplicator({
orderable: true,
collapsible: true
});
duplicator.bind('collapsestop', function(event, item) {
var instance = $(item);
instance.find('.header > span:not(:has(i))').append(
$('<i />').text(instance.find('label:first input').attr('value'))
);
});
duplicator.bind('expandstop', function(event, item) {
$(item).find('.header > span > i').remove();
});
duplicator.trigger('restorestate');
// Dim system messages
Symphony.Message.fade('silence', 10000);
// Relative times in system messages
$('abbr.timeago').each(function() {
var time = $(this).parent();
time.html(time.html().replace(Symphony.Language.get('at') + ' ', ''));
});
Symphony.Message.timer();
// XSLT utilities
$('textarea').blur(function() {
var source = $(this).val(),
utilities = $('#utilities li');
// Remove current selection
utilities.removeClass('selected');
// Get utitities names
utilities.find('a').each(function() {
var utility = $(this),
expression = new RegExp('href=["\']?(?:\\.{2}/utilities/)?' + utility.text());
// Check for utility occurrences
if(expression.test(source)) {
utility.parent().addClass('selected');
}
});
}).blur();
// Clickable utilities in the XSLT editor
$('#utilities li').click(function(event) {
if ($(event.target).is('a')) return;
var editor = $('textarea.code'),
lines = editor.val().split('\n'),
link = $(this).find('a').text(),
statement = '<xsl:import href="../utilities/' + link + '"/>',
regexp = '^<xsl:import href="(?:\.\./utilities/)?' + link + '"',
newLine = '\n',
numberOfNewLines = 1;
if ($(this).hasClass('selected')) {
for (var i = 0; i < lines.length; i++) {
if ($.trim(lines[i]).match(regexp) != null) {
(lines[i + 1] === '' && $.trim(lines[i - 1]).substring(0, 11) !== '<xsl:import') ? lines.splice(i, 2) : lines.splice(i, 1);
break;
}
}
editor.val(lines.join(newLine));
$(this).removeClass('selected');
}
else {
for (var i = 0; i < lines.length; i++) {
if ($.trim(lines[i]).substring(0, 4) === '<!--' || $.trim(lines[i]).match('^<xsl:(?:import|variable|output|comment|template)')) {
numberOfNewLines = $.trim(lines[i]).substring(0, 11) === '<xsl:import' ? 1 : 2;
if (Symphony.Context.get('env')[0] != 'template') {
lines[i] = statement.replace('../utilities/', '') + Array(numberOfNewLines + 1).join(newLine) + lines[i];
}
else {
// we are inside the page template editor
lines[i] = statement + Array(numberOfNewLines + 1).join(newLine) + lines[i];
}
break;
}
}
editor.val(lines.join(newLine));
$(this).addClass('selected');
}
});
// Change user password
$('#change-password').each(function() {
var password = $(this),
labels = password.find('label'),
help = password.next('p.help'),
placeholder = $('<label>' + Symphony.Language.get('Password') + ' <span class="frame"><button>' + Symphony.Language.get('Change Password') + '</button></span></label>'),
invalid = password.has('.invalid');
if(invalid.size() == 0) {
// Hide password fields
password.removeClass();
labels.hide();
help.hide();
// Add placeholder
password.append(placeholder).find('button').click(function(event) {
event.preventDefault();
// Hide placeholder
placeholder.hide();
// Shwo password fields
password.addClass('group');
if(password.find('input[name="fields[old-password]"]').length) password.addClass('triple');
labels.show();
help.show();
});
}
});
// Confirm actions
$('button.confirm').live('click', function() {
var button = $(this),
name = document.title.split(/[\u2013]\s*/g)[2],
message = button.attr('data-message');
// Set default message
if(!message) {
message = Symphony.Language.get('Are you sure you want to proceed?');
}
return confirm(message);
});
// Confirm with selected actions
$('form').submit(function(event) {
var select = $('select[name="with-selected"]'),
option = select.find('option:selected'),
input = $('table input:checked'),
count = input.size(),
message = option.attr('data-message');
// Needs confirmation
if(option.is('.confirm')) {
// Set default message
if(!message) {
message = Symphony.Language.get('Are you sure you want to proceed?');
}
return confirm(message);
}
});
// Data source manager options
$('select.filtered > optgroup').each(function() {
var optgroup = $(this),
select = optgroup.parents('select'),
label = optgroup.attr('label'),
options = optgroup.remove().find('option').addClass('optgroup');
// Fix for Webkit browsers to initially show the options
if (select.attr('multiple')) {
select.scrollTop(0);
}
// Show only relevant options based on context
$('#context').change(function() {
if($(this).find('option:selected').text() == label) {
select.find('option.optgroup').remove();
select.append(options.clone(true));
}
});
});
// Data source manager context
$('*.contextual').each(function() {
var area = $(this);
$('#context').change(function() {
var select = $(this),
optgroup = select.find('option:selected').parent(),
value = select.val().replace(/\W+/g, '_'),
group = optgroup.attr('label').replace(/\W+/g, '_');
// Show only relevant interface components based on context
area[(area.hasClass(value) || area.hasClass(group)) ^ area.hasClass('inverse') ? 'removeClass' : 'addClass']('irrelevant');
});
});
// Set data source manager context
$('#context').change();
// Once pagination is disabled, max_records and page_number are disabled too
var max_record = $('input[name*=max_records]'),
page_number = $('input[name*=page_number]');
$('input[name*=paginate_results]').change(function(event) {
// Turn on pagination
if($(this).is(':checked')) {
max_record.attr('disabled', false);
page_number.attr('disabled', false);
}
// Turn off pagination
else {
max_record.attr('disabled', true);
page_number.attr('disabled', true);
}
}).change();
// Disable paginate_results checking/unchecking when clicking on either max_records or page_number
max_record.add(page_number).click(function(event) {
event.preventDefault();
});
// Enabled fields on submit
$('form').bind('submit', function() {
max_record.attr('disabled', false);
page_number.attr('disabled', false);
});
// Upload fields
$('<em>' + Symphony.Language.get('Remove File') + '</em>').appendTo('label.file:has(a) span').click(function(event) {
var span = $(this).parent(),
name = span.find('input').attr('name');
// Prevent clicktrough
event.preventDefault();
// Add new empty file input
span.empty().append('<input name="' + name + '" type="file">');
});
// Focus first text-input or textarea when creating entries
if(Symphony.Context.get('env') != null && (Symphony.Context.get('env')[0] == 'new' || Symphony.Context.get('env').page == 'new')) {
$('input[type="text"], textarea').first().focus();
}
// Accessible navigation
$('#nav').delegate('a', 'focus blur', function() {
$(this).parents('li').eq(1).toggleClass('current');
});
});
})(jQuery.noConflict());