1
|
|
|
/** |
2
|
|
|
* Package: mivhak-js |
3
|
|
|
* URL: http://products.askupasoftware.com/mivhak-js |
4
|
|
|
* Version: 1.0.0 |
5
|
|
|
* Date: 2016-07-07 |
6
|
|
|
* Dependencies: jQuery Mousewheel, Ace Editor |
7
|
|
|
* License: GNU GENERAL PUBLIC LICENSE |
8
|
|
|
* |
9
|
|
|
* Developed by Askupa Software http://www.askupasoftware.com |
10
|
|
|
*/ |
11
|
|
|
/* test-code */ |
12
|
|
View Code Duplication |
var testapi = {}; |
|
|
|
|
13
|
|
|
/* end-test-code */ |
14
|
|
|
|
15
|
|
|
(function ( $ ) {// Ace global config |
16
|
|
|
ace.config.set('basePath', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/');/** |
|
|
|
|
17
|
|
|
* Converts a string to it's actual value, if applicable |
18
|
|
|
* |
19
|
|
|
* @param {String} str |
20
|
|
|
*/ |
21
|
|
|
function strToValue( str ) |
22
|
|
|
{ |
23
|
|
|
if('true' === str.toLowerCase()) return true; |
|
|
|
|
24
|
|
|
if('false' === str.toLowerCase()) return false; |
|
|
|
|
25
|
|
|
if(!isNaN(str)) return parseFloat(str); |
|
|
|
|
26
|
|
|
return str; |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Convert hyphened text to camelCase. |
31
|
|
|
* |
32
|
|
|
* @param {string} str |
33
|
|
|
* @returns {string} |
34
|
|
|
*/ |
35
|
|
|
function toCamelCase( str ) |
36
|
|
|
{ |
37
|
|
|
return str.replace(/-(.)/g,function(match){ |
38
|
|
|
return match[1].toUpperCase(); |
39
|
|
|
}); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Reads the element's 'miv-' attributes and returns their values as an object |
44
|
|
|
* |
45
|
|
|
* @param {DOMElement} el |
46
|
|
|
* @returns {Object} |
47
|
|
|
*/ |
48
|
|
|
function readAttributes( el ) |
49
|
|
|
{ |
50
|
|
|
var options = {}; |
51
|
|
|
$.each(el.attributes, function(i, attr){ |
52
|
|
|
if(/^miv-/.test(attr.name)) |
53
|
|
|
{ |
54
|
|
|
options[toCamelCase(attr.name.substr(4))] = strToValue(attr.value); |
55
|
|
|
} |
56
|
|
|
}); |
57
|
|
|
return options; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Get the average value of all elements in the given array. |
62
|
|
|
* |
63
|
|
|
* @param {Array} arr |
64
|
|
|
* @returns {Number} |
65
|
|
|
*/ |
66
|
|
|
function average( arr ) |
67
|
|
|
{ |
68
|
|
|
var i = arr.length, sum = 0; |
69
|
|
|
while(i--) sum += parseFloat(arr[i]); |
|
|
|
|
70
|
|
|
return sum/arr.length; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Get the maximum value of all elements in the given array. |
75
|
|
|
* |
76
|
|
|
* @param {Array} arr |
77
|
|
|
* @returns {Number} |
78
|
|
|
*/ |
79
|
|
|
function max( arr ) |
80
|
|
|
{ |
81
|
|
|
var i = arr.length, maxval = arr[--i]; |
82
|
|
|
while(i--) if(arr[i] > maxval) maxval = arr[i]; |
|
|
|
|
83
|
|
|
return maxval; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Get the minimum value of all elements in the given array. |
88
|
|
|
* |
89
|
|
|
* @param {Array} arr |
90
|
|
|
* @returns {Number} |
91
|
|
|
*/ |
92
|
|
|
function min( arr ) |
93
|
|
|
{ |
94
|
|
|
var i = arr.length, minval = arr[--i]; |
95
|
|
|
while(i--) if(arr[i] < minval) minval = arr[i]; |
|
|
|
|
96
|
|
|
return minval; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Calculate the editor's height based on the number of lines & line height. |
101
|
|
|
* |
102
|
|
|
* @param {jQuery} $editor Ther editor wrapper element (PRE) |
103
|
|
|
* @returns {Number} |
104
|
|
|
*/ |
105
|
|
|
function getEditorHeight( $editor ) |
106
|
|
|
{ |
107
|
|
|
var height = 0; |
108
|
|
|
$editor.find('.ace_text-layer').children().each(function(){ |
109
|
|
|
height += $(this).height(); |
110
|
|
|
}); |
111
|
|
|
return height; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Convert a string like "3, 5-7" into an array of ranges in to form of |
116
|
|
|
* [ |
117
|
|
|
* {start:2, end:2}, |
118
|
|
|
* {start:4, end:6}, |
119
|
|
|
* ] |
120
|
|
|
* The string should be given as a list if comma delimited 1 based ranges. |
121
|
|
|
* The result is given as a 0 based array of ranges. |
122
|
|
|
* |
123
|
|
|
* @param {string} str |
124
|
|
|
* @returns {Array} |
125
|
|
|
*/ |
126
|
|
|
function strToRange( str ) |
127
|
|
|
{ |
128
|
|
|
var range = str.replace(' ', '').split(','), |
129
|
|
|
i = range.length, |
130
|
|
|
ranges = [], |
131
|
|
|
start, end, splitted; |
132
|
|
|
|
133
|
|
|
while(i--) |
134
|
|
|
{ |
135
|
|
|
// Multiple lines highlight |
136
|
|
|
if( range[i].indexOf('-') > -1 ) |
137
|
|
|
{ |
138
|
|
|
splitted = range[i].split('-'); |
139
|
|
|
start = parseInt(splitted[0])-1; |
140
|
|
|
end = parseInt(splitted[1])-1; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// Single line highlight |
144
|
|
|
else |
145
|
|
|
{ |
146
|
|
|
start = parseInt(range[i])-1; |
147
|
|
|
end = start; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
ranges.unshift({start:start,end:end}); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return ranges; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Request animation frame. Uses setTimeout as a fallback if the browser does |
158
|
|
|
* not support requestAnimationFrame (based on 60 frames per second). |
159
|
|
|
* |
160
|
|
|
* @param {type} cb |
161
|
|
|
* @returns {Number} |
162
|
|
|
*/ |
163
|
|
|
var raf = window.requestAnimationFrame || |
164
|
|
|
window.webkitRequestAnimationFrame || |
165
|
|
|
window.mozRequestAnimationFrame || |
166
|
|
|
window.msRequestAnimationFrame || |
167
|
|
|
function(cb) { return window.setTimeout(cb, 1000 / 60); }; |
168
|
|
|
|
169
|
|
|
/* test-code */ |
170
|
|
|
testapi.strToValue = strToValue; |
171
|
|
|
testapi.toCamelCase = toCamelCase; |
172
|
|
|
testapi.readAttributes = readAttributes; |
173
|
|
|
testapi.average = average; |
174
|
|
|
testapi.max = max; |
175
|
|
|
testapi.min = min; |
176
|
|
|
testapi.getEditorHeight = getEditorHeight; |
177
|
|
|
testapi.strToRange = strToRange; |
178
|
|
|
testapi.raf = raf; |
179
|
|
|
/* end-test-code *//** |
180
|
|
|
* The constructor. |
181
|
|
|
* See Mivhal.defaults for available options. |
182
|
|
|
* |
183
|
|
|
* @param {DOMElement} selection |
184
|
|
|
* @param {Object} options |
185
|
|
|
*/ |
186
|
|
|
function Mivhak( selection, options ) |
187
|
|
|
{ |
188
|
|
|
// Bail if there are no resources |
189
|
|
|
if(!selection.getElementsByTagName('PRE').length) return; |
|
|
|
|
190
|
|
|
|
191
|
|
|
this.$selection = $( selection ); |
192
|
|
|
this.setOptions( options ); |
193
|
|
|
this.init(); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Check if a given string represents a supported method |
198
|
|
|
* @param {string} method |
199
|
|
|
*/ |
200
|
|
|
Mivhak.methodExists = function( method ) |
201
|
|
|
{ |
202
|
|
|
return typeof method === 'string' && Mivhak.methods[method]; |
203
|
|
|
}; |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Initiate the code viewer. |
207
|
|
|
*/ |
208
|
|
|
Mivhak.prototype.init = function() |
209
|
|
|
{ |
210
|
|
|
this.initState(); |
211
|
|
|
this.parseResources(); |
212
|
|
|
this.createUI(); |
213
|
|
|
this.applyOptions(); |
214
|
|
|
this.callMethod('showTab',0); // Show first tab initially |
215
|
|
|
}; |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Apply the options that were set by the user. This function is called when |
219
|
|
|
* Mivhak is initiated, and every time the options are updated. |
220
|
|
|
*/ |
221
|
|
|
Mivhak.prototype.applyOptions = function() |
222
|
|
|
{ |
223
|
|
|
this.callMethod('setHeight', this.options.height); |
224
|
|
|
this.callMethod('setAccentColor', this.options.accentColor); |
225
|
|
|
if(this.options.collapsed) this.callMethod('collapse'); |
|
|
|
|
226
|
|
|
if(!this.options.topbar) this.$selection.addClass('mivhak-no-topbar'); |
|
|
|
|
227
|
|
|
else this.$selection.removeClass('mivhak-no-topbar'); |
228
|
|
|
|
229
|
|
|
this.createCaption(); |
230
|
|
|
this.createLivePreview(); |
231
|
|
|
}; |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Initiate this instance's state. |
235
|
|
|
*/ |
236
|
|
|
Mivhak.prototype.initState = function() |
237
|
|
|
{ |
238
|
|
|
this.state = { |
239
|
|
|
lineWrap: true, |
240
|
|
|
collapsed: false, |
241
|
|
|
height: 0, |
242
|
|
|
activeTab: null, // Updated by tabs.showTab |
243
|
|
|
resources: [] // Generated by parseResources() |
244
|
|
|
}; |
245
|
|
|
}; |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Set or update this instance's options. |
249
|
|
|
* @param {object} options |
250
|
|
|
*/ |
251
|
|
|
Mivhak.prototype.setOptions = function( options ) |
252
|
|
|
{ |
253
|
|
|
// If options were already set, update them |
254
|
|
|
if( typeof this.options !== 'undefined' ) |
255
|
|
|
this.options = $.extend(true, {}, this.options, options, readAttributes(this.$selection[0])); |
|
|
|
|
256
|
|
|
|
257
|
|
|
// Otherwise, merge them with the defaults |
258
|
|
|
else this.options = $.extend(true, {}, Mivhak.defaults, options, readAttributes(this.$selection[0])); |
259
|
|
|
}; |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Call one of Mivhak's methods. See Mivhak.methods for available methods. |
263
|
|
|
* To apply additional arguments, simply pass the arguments after the methodName |
264
|
|
|
* i.e. callMethod('methodName', arg1, arg2). |
265
|
|
|
* This method is also called internally when making a method call through jQuery |
266
|
|
|
* i.e. $('#el').mivhak('methodName', arg1, arg2); |
267
|
|
|
* |
268
|
|
|
* @param {string} methodName |
269
|
|
|
*/ |
270
|
|
|
Mivhak.prototype.callMethod = function( methodName ) |
271
|
|
|
{ |
272
|
|
|
if(Mivhak.methodExists(methodName)) |
273
|
|
|
{ |
274
|
|
|
// Call the method with the original arguments, removing the method's name from the list |
275
|
|
|
var args = []; |
276
|
|
|
Array.prototype.push.apply( args, arguments ); |
277
|
|
|
args.shift(); |
278
|
|
|
Mivhak.methods[methodName].apply(this, args); |
279
|
|
|
} |
280
|
|
|
}; |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Create the user interface. |
284
|
|
|
*/ |
285
|
|
|
Mivhak.prototype.createUI = function() |
286
|
|
|
{ |
287
|
|
|
this.tabs = Mivhak.render('tabs',{mivhakInstance: this}); |
288
|
|
|
this.topbar = Mivhak.render('top-bar',{mivhakInstance: this}); |
289
|
|
|
this.notifier = Mivhak.render('notifier'); |
290
|
|
|
|
291
|
|
|
this.$selection.prepend(this.tabs.$el); |
292
|
|
|
this.$selection.prepend(this.topbar.$el); |
293
|
|
|
this.tabs.$el.prepend(this.notifier.$el); |
294
|
|
|
}; |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Calculate the height in pixels. |
298
|
|
|
* |
299
|
|
|
* auto: Automatically calculate the height based on the number of lines. |
300
|
|
|
* min: Calculate the height based on the height of the tab with the maximum number of lines |
301
|
|
|
* max: Calculate the height based on the height of the tab with the minimum number of lines |
302
|
|
|
* average: Calculate the height based on the average height of all tabs |
303
|
|
|
* |
304
|
|
|
* @param {string|number} h One of (auto|min|max|average) or a custom number |
305
|
|
|
* @returns {Number} |
306
|
|
|
*/ |
307
|
|
|
Mivhak.prototype.calculateHeight = function(h) |
308
|
|
|
{ |
309
|
|
|
var heights = [], |
310
|
|
|
padding = this.options.padding*2, |
311
|
|
|
i = this.tabs.tabs.length; |
312
|
|
|
|
313
|
|
|
while(i--) |
314
|
|
|
heights.push(getEditorHeight($(this.tabs.tabs[i].resource.pre))+padding); |
|
|
|
|
315
|
|
|
|
316
|
|
|
if('average' === h) return average(heights); |
|
|
|
|
317
|
|
|
if('shortest' === h) return min(heights); |
|
|
|
|
318
|
|
|
if('longest' === h) return max(heights); |
|
|
|
|
319
|
|
|
if('auto' === h) return getEditorHeight($(this.activeTab.resource.pre))+padding; |
|
|
|
|
320
|
|
|
if(!isNaN(h)) return parseInt(h); |
|
|
|
|
321
|
|
|
}; |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Loop through each PRE element inside this.$selection and store it's options |
325
|
|
|
* in this.resources, merging it with the default option values. |
326
|
|
|
*/ |
327
|
|
|
Mivhak.prototype.parseResources = function() |
328
|
|
|
{ |
329
|
|
|
var $this = this; |
330
|
|
|
|
331
|
|
|
this.resources = new Resources(); |
332
|
|
|
this.$selection.find('pre').each(function(){ |
333
|
|
|
$this.resources.add(this); |
334
|
|
|
}); |
335
|
|
|
}; |
336
|
|
|
|
337
|
|
|
Mivhak.prototype.createCaption = function() |
338
|
|
|
{ |
339
|
|
|
if(this.options.caption) |
340
|
|
|
{ |
341
|
|
|
if(!this.caption) |
342
|
|
|
{ |
343
|
|
|
this.caption = Mivhak.render('caption',{text: this.options.caption}); |
344
|
|
|
this.$selection.append(this.caption.$el); |
345
|
|
|
} |
346
|
|
|
else this.caption.setText(this.options.caption); |
|
|
|
|
347
|
|
|
} |
348
|
|
|
else this.$selection.addClass('mivhak-no-caption'); |
|
|
|
|
349
|
|
|
}; |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Create the live preview iframe window |
353
|
|
|
*/ |
354
|
|
|
Mivhak.prototype.createLivePreview = function() |
355
|
|
|
{ |
356
|
|
|
if(this.options.runnable && typeof this.preview === 'undefined') |
357
|
|
|
{ |
358
|
|
|
this.preview = Mivhak.render('live-preview',{resources: this.resources}); |
359
|
|
|
this.tabs.$el.append(this.preview.$el); |
360
|
|
|
} |
361
|
|
|
}; |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Remove all generated elements, data and events. |
365
|
|
|
* |
366
|
|
|
* TODO: keep initial HTML |
367
|
|
|
*/ |
368
|
|
|
Mivhak.prototype.destroy = function() |
369
|
|
|
{ |
370
|
|
|
this.$selection.empty(); |
371
|
|
|
}; |
372
|
|
|
|
373
|
|
|
/* test-code */ |
374
|
|
|
testapi.mivhak = Mivhak; |
375
|
|
|
/* end-test-code *//** |
376
|
|
|
* A list of Mivhak default options |
377
|
|
|
*/ |
378
|
|
|
Mivhak.defaults = { |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Whether to add a live preview (and a "play" button) to run the code |
382
|
|
|
* @type Boolean |
383
|
|
|
*/ |
384
|
|
|
runnable: false, |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Whther to allow the user to edit the code |
388
|
|
|
* @type Boolean |
389
|
|
|
*/ |
390
|
|
|
editable: false, |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Whether to show line numers on the left |
394
|
|
|
* @type Boolean |
395
|
|
|
*/ |
396
|
|
|
lineNumbers: false, |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* One of the supported CSS color values (HEX, RGB, etc...) to set as the |
400
|
|
|
* code viewer's accent color. Controls the scrollbars, tab navigation and |
401
|
|
|
* dropdown item colors. |
402
|
|
|
* @type String |
403
|
|
|
*/ |
404
|
|
|
accentColor: false, |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Whether to collapse the code viewer initially |
408
|
|
|
* @type Boolean |
409
|
|
|
*/ |
410
|
|
|
collapsed: false, |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Text/HTML string to be displayed at the bottom of the code viewer |
414
|
|
|
* @type Boolean|string |
415
|
|
|
*/ |
416
|
|
|
caption: false, |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* The code viewer's theme. One of (dark|light) |
420
|
|
|
* @type String |
421
|
|
|
*/ |
422
|
|
|
theme: 'light', |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* The code viewer's height. Either a number (for a custom height in pixels) |
426
|
|
|
* or one of (auto|min|max|average). |
427
|
|
|
* @type String|Number |
428
|
|
|
*/ |
429
|
|
|
height: 'average', |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* The surrounding padding between the code and the wrapper. |
433
|
|
|
* @type Number |
434
|
|
|
*/ |
435
|
|
|
padding: 15, |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* Whether to show/hide the top bar |
439
|
|
|
* @type Boolean |
440
|
|
|
*/ |
441
|
|
|
topbar: true, |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* An array of strings/objects for the settings dropdown menu |
445
|
|
|
* @type Array |
446
|
|
|
*/ |
447
|
|
|
buttons: ['wrap','copy','collapse','about'] |
448
|
|
|
}; |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* A list of Mivhak resource default settings (Mivhak resources are any <pre> |
452
|
|
|
* elements placed inside a Mivhak wrapper element). |
453
|
|
|
*/ |
454
|
|
|
Mivhak.resourceDefaults = { |
455
|
|
|
|
456
|
|
|
/** |
457
|
|
|
* The resource language (one of the supported Ace Editor languages) |
458
|
|
|
* @type string |
459
|
|
|
*/ |
460
|
|
|
lang: null, |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* How the resource should be treated in the preview window. One of (script|style|markup) |
464
|
|
|
* @type bool|string |
465
|
|
|
*/ |
466
|
|
|
runAs: false, |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* A URL to an external source |
470
|
|
|
* @type bool|string |
471
|
|
|
*/ |
472
|
|
|
source: false, |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* Whether to show this resource as a tab. Useful if you want to include |
476
|
|
|
* external libraries for the live preview and don't need to see their contents. |
477
|
|
|
* @type Boolean |
478
|
|
|
*/ |
479
|
|
|
visible: true, |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Mark/highlight a range of lines given as a string in the format '1, 3-4' |
483
|
|
|
* @type bool|string |
484
|
|
|
*/ |
485
|
|
|
mark: false, |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* Set the initial line number (1 based). |
489
|
|
|
* @type Number |
490
|
|
|
*/ |
491
|
|
|
startLine: 1 |
492
|
|
|
};var Resources = function() { |
493
|
|
|
this.data = []; |
494
|
|
|
}; |
495
|
|
|
|
496
|
|
|
Resources.prototype.count = function() { |
497
|
|
|
return this.data.length; |
498
|
|
|
}; |
499
|
|
|
|
500
|
|
|
Resources.prototype.add = function(pre) { |
501
|
|
|
this.data.push($.extend({}, |
502
|
|
|
Mivhak.resourceDefaults,{ |
503
|
|
|
pre:pre, |
504
|
|
|
content: pre.textContent |
505
|
|
|
}, |
506
|
|
|
readAttributes(pre) |
507
|
|
|
)); |
508
|
|
|
}; |
509
|
|
|
|
510
|
|
|
Resources.prototype.get = function(i) { |
511
|
|
|
return this.data[i]; |
512
|
|
|
}; |
513
|
|
|
|
514
|
|
|
Resources.prototype.update = function(i, content) { |
515
|
|
|
this.data[i].content = content; |
516
|
|
|
}; |
517
|
|
|
|
518
|
|
|
// Built-in buttons |
519
|
|
|
Mivhak.buttons = { |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* The wrap button features a toggle button and is used to toggle line wrap |
523
|
|
|
* on/off for the currently active tab |
524
|
|
|
*/ |
525
|
|
|
wrap: { |
526
|
|
|
text: 'Wrap Lines', |
527
|
|
|
toggle: true, |
528
|
|
|
click: function(e) { |
529
|
|
|
e.stopPropagation(); |
530
|
|
|
this.callMethod('toggleLineWrap'); |
531
|
|
|
} |
532
|
|
|
}, |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* The copy button copies the code in the currently active tab to clipboard |
536
|
|
|
* (except for Safari, where it selects the code and prompts the user to press command+c) |
537
|
|
|
*/ |
538
|
|
|
copy: { |
539
|
|
|
text: 'Copy', |
540
|
|
|
click: function(e) { |
|
|
|
|
541
|
|
|
this.callMethod('copyCode'); |
542
|
|
|
} |
543
|
|
|
}, |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* The collapse button toggles the entire code viewer into and out of its |
547
|
|
|
* collapsed state. |
548
|
|
|
*/ |
549
|
|
|
collapse: { |
550
|
|
|
text: 'Colllapse', |
551
|
|
|
click: function(e) { |
|
|
|
|
552
|
|
|
this.callMethod('collapse'); |
553
|
|
|
} |
554
|
|
|
}, |
555
|
|
|
|
556
|
|
|
/** |
557
|
|
|
* The about button shows the user information about Mivhak |
558
|
|
|
*/ |
559
|
|
|
about: { |
560
|
|
|
text: 'About Mivhak', |
561
|
|
|
click: function(e) { |
|
|
|
|
562
|
|
|
this.notifier.closableNotification('Mivhak.js v1.0.0'); |
563
|
|
|
} |
564
|
|
|
} |
565
|
|
|
};/** |
566
|
|
|
* jQuery plugin's methods. |
567
|
|
|
* In all methods, the 'this' keyword is pointing to the calling instance of Mivhak. |
568
|
|
|
* These functions serve as the plugin's public API. |
569
|
|
|
*/ |
570
|
|
|
Mivhak.methods = { |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* Toggle line wrap on/off for the currently active tab. Initially set to |
574
|
|
|
* on (true) by default. |
575
|
|
|
*/ |
576
|
|
|
toggleLineWrap: function() { |
577
|
|
|
var $this = this; |
578
|
|
|
this.state.lineWrap = !this.state.lineWrap; |
579
|
|
|
$.each(this.tabs.tabs, function(i,tab) { |
580
|
|
|
tab.editor.getSession().setUseWrapMode($this.state.lineWrap); |
581
|
|
|
tab.vscroll.refresh(); |
582
|
|
|
tab.hscroll.refresh(); |
583
|
|
|
}); |
584
|
|
|
}, |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* copy the code in the currently active tab to clipboard (works in all |
588
|
|
|
* browsers apart from Safari, where it selects the code and prompts the |
589
|
|
|
* user to press command+c) |
590
|
|
|
*/ |
591
|
|
|
copyCode: function() { |
592
|
|
|
var editor = this.activeTab.editor; |
593
|
|
|
editor.selection.selectAll(); |
594
|
|
|
editor.focus(); |
595
|
|
|
if(document.execCommand('copy')) { |
596
|
|
|
editor.selection.clearSelection(); |
597
|
|
|
this.notifier.timedNotification('Copied to clipboard!', 2000); |
598
|
|
|
} |
599
|
|
|
else this.notifier.timedNotification('Press ⌘+C to copy the code', 2000); |
|
|
|
|
600
|
|
|
}, |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* Collapse the code viewer and show a "Show Code" button. |
604
|
|
|
*/ |
605
|
|
|
collapse: function() { |
606
|
|
|
if(this.state.collapsed) return; |
|
|
|
|
607
|
|
|
var $this = this; |
608
|
|
|
this.state.collapsed = true; |
609
|
|
|
this.notifier.closableNotification('Show Code', function(){$this.callMethod('expand');}); |
610
|
|
|
this.$selection.addClass('mivhak-collapsed'); |
611
|
|
|
this.callMethod('setHeight',this.notifier.$el.outerHeight(true)); |
612
|
|
|
}, |
613
|
|
|
|
614
|
|
|
/** |
615
|
|
|
* Expand the code viewer if it's collapsed; |
616
|
|
|
*/ |
617
|
|
|
expand: function() { |
618
|
|
|
if(!this.state.collapsed) return; |
|
|
|
|
619
|
|
|
this.state.collapsed = false; |
620
|
|
|
this.notifier.hide(); // In case it's called by an external script |
621
|
|
|
this.$selection.removeClass('mivhak-collapsed'); |
622
|
|
|
this.callMethod('setHeight',this.options.height); |
623
|
|
|
}, |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Show/activate a tab by the given index (zero based). |
627
|
|
|
* @param {number} index |
628
|
|
|
*/ |
629
|
|
|
showTab: function(index) { |
630
|
|
|
this.tabs.showTab(index); |
631
|
|
|
this.topbar.activateNavTab(index); |
632
|
|
|
if(this.options.runnable) |
633
|
|
|
this.preview.hide(); |
|
|
|
|
634
|
|
|
}, |
635
|
|
|
|
636
|
|
|
/** |
637
|
|
|
* Set the height of the code viewer. One of (auto|min|max|average) or |
638
|
|
|
* a custom number. |
639
|
|
|
* @param {string|number} height |
640
|
|
|
*/ |
641
|
|
|
setHeight: function(height) { |
642
|
|
|
var $this = this; |
643
|
|
|
raf(function(){ |
644
|
|
|
$this.state.height = $this.calculateHeight(height); |
645
|
|
|
$this.tabs.$el.height($this.state.height); |
646
|
|
|
$.each($this.tabs.tabs, function(i,tab) { |
647
|
|
|
$(tab.resource.pre).height(height); |
648
|
|
|
tab.editor.resize(); |
649
|
|
|
tab.vscroll.refresh(); |
650
|
|
|
tab.hscroll.refresh(); |
651
|
|
|
}); |
652
|
|
|
}); |
653
|
|
|
}, |
654
|
|
|
|
655
|
|
|
/** |
656
|
|
|
* Set the code viewer's accent color. Applied to the nav-tabs text color, |
657
|
|
|
* underline, scrollbars and dropdown menu text color. |
658
|
|
|
* |
659
|
|
|
* @param {string} color |
660
|
|
|
*/ |
661
|
|
|
setAccentColor: function(color) { |
662
|
|
|
if(!color) return; |
|
|
|
|
663
|
|
|
this.topbar.$el.find('.mivhak-top-bar-button').css({'color': color}); |
664
|
|
|
this.topbar.$el.find('.mivhak-dropdown-button').css({'color': color}); |
665
|
|
|
this.topbar.$el.find('.mivhak-controls svg').css({'fill': color}); |
666
|
|
|
this.tabs.$el.find('.mivhak-scrollbar-thumb').css({'background-color': color}); |
667
|
|
|
this.topbar.line.css({'background-color': color}); |
668
|
|
|
} |
669
|
|
|
};Mivhak.icons = {}; |
670
|
|
|
|
671
|
|
|
// <div>Icons made by <a href="http://www.flaticon.com/authors/egor-rumyantsev" title="Egor Rumyantsev">Egor Rumyantsev</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> |
672
|
|
|
Mivhak.icons.play = '<svg viewBox="0 0 232.153 232.153"><g><path style="fill-rule:evenodd;clip-rule:evenodd;" d="M203.791,99.628L49.307,2.294c-4.567-2.719-10.238-2.266-14.521-2.266c-17.132,0-17.056,13.227-17.056,16.578v198.94c0,2.833-0.075,16.579,17.056,16.579c4.283,0,9.955,0.451,14.521-2.267l154.483-97.333c12.68-7.545,10.489-16.449,10.489-16.449S216.471,107.172,203.791,99.628z"/></g></svg>'; |
673
|
|
|
|
674
|
|
|
// <div>Icons made by <a href="http://www.flaticon.com/authors/dave-gandy" title="Dave Gandy">Dave Gandy</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div> |
675
|
|
|
Mivhak.icons.cog = '<svg viewbox="0 0 438.529 438.529"><g><path d="M436.25,181.438c-1.529-2.002-3.524-3.193-5.995-3.571l-52.249-7.992c-2.854-9.137-6.756-18.461-11.704-27.98c3.422-4.758,8.559-11.466,15.41-20.129c6.851-8.661,11.703-14.987,14.561-18.986c1.523-2.094,2.279-4.281,2.279-6.567c0-2.663-0.66-4.755-1.998-6.28c-6.848-9.708-22.552-25.885-47.106-48.536c-2.275-1.903-4.661-2.854-7.132-2.854c-2.857,0-5.14,0.855-6.854,2.567l-40.539,30.549c-7.806-3.999-16.371-7.52-25.693-10.565l-7.994-52.529c-0.191-2.474-1.287-4.521-3.285-6.139C255.95,0.806,253.623,0,250.954,0h-63.38c-5.52,0-8.947,2.663-10.278,7.993c-2.475,9.513-5.236,27.214-8.28,53.1c-8.947,2.86-17.607,6.476-25.981,10.853l-39.399-30.549c-2.474-1.903-4.948-2.854-7.422-2.854c-4.187,0-13.179,6.804-26.979,20.413c-13.8,13.612-23.169,23.841-28.122,30.69c-1.714,2.474-2.568,4.664-2.568,6.567c0,2.286,0.95,4.57,2.853,6.851c12.751,15.42,22.936,28.549,30.55,39.403c-4.759,8.754-8.47,17.511-11.132,26.265l-53.105,7.992c-2.093,0.382-3.9,1.621-5.424,3.715C0.76,182.531,0,184.722,0,187.002v63.383c0,2.478,0.76,4.709,2.284,6.708c1.524,1.998,3.521,3.195,5.996,3.572l52.25,7.71c2.663,9.325,6.564,18.743,11.704,28.257c-3.424,4.761-8.563,11.468-15.415,20.129c-6.851,8.665-11.709,14.989-14.561,18.986c-1.525,2.102-2.285,4.285-2.285,6.57c0,2.471,0.666,4.658,1.997,6.561c7.423,10.284,23.125,26.272,47.109,47.969c2.095,2.094,4.475,3.138,7.137,3.138c2.857,0,5.236-0.852,7.138-2.563l40.259-30.553c7.808,3.997,16.371,7.519,25.697,10.568l7.993,52.529c0.193,2.471,1.287,4.518,3.283,6.14c1.997,1.622,4.331,2.423,6.995,2.423h63.38c5.53,0,8.952-2.662,10.287-7.994c2.471-9.514,5.229-27.213,8.274-53.098c8.946-2.858,17.607-6.476,25.981-10.855l39.402,30.84c2.663,1.712,5.141,2.563,7.42,2.563c4.186,0,13.131-6.752,26.833-20.27c13.709-13.511,23.13-23.79,28.264-30.837c1.711-1.902,2.569-4.09,2.569-6.561c0-2.478-0.947-4.862-2.857-7.139c-13.698-16.754-23.883-29.882-30.546-39.402c3.806-7.043,7.519-15.701,11.136-25.98l52.817-7.988c2.279-0.383,4.189-1.622,5.708-3.716c1.523-2.098,2.279-4.288,2.279-6.571v-63.376C438.533,185.671,437.777,183.438,436.25,181.438z M270.946,270.939c-14.271,14.277-31.497,21.416-51.676,21.416c-20.177,0-37.401-7.139-51.678-21.416c-14.272-14.271-21.411-31.498-21.411-51.673c0-20.177,7.135-37.401,21.411-51.678c14.277-14.272,31.504-21.411,51.678-21.411c20.179,0,37.406,7.139,51.676,21.411c14.274,14.277,21.413,31.501,21.413,51.678C292.359,239.441,285.221,256.669,270.946,270.939z"/></g></svg>';/** |
676
|
|
|
* The list of registered components. |
677
|
|
|
* |
678
|
|
|
* @type Array |
679
|
|
|
*/ |
680
|
|
|
Mivhak.components = []; |
681
|
|
|
|
682
|
|
|
/** |
683
|
|
|
* Register a new component |
684
|
|
|
* |
685
|
|
|
* @param {string} name The components name |
686
|
|
|
* @param {Object} options A list of component properties |
687
|
|
|
*/ |
688
|
|
|
Mivhak.component = function(name, options) |
689
|
|
|
{ |
690
|
|
|
Mivhak.components[name] = options; |
691
|
|
|
}; |
692
|
|
|
|
693
|
|
|
/** |
694
|
|
|
* Render a new component |
695
|
|
|
* |
696
|
|
|
* TODO: move this into a seperate library |
697
|
|
|
* |
698
|
|
|
* @param {string} name The components name |
699
|
|
|
* @param {Object} props A list of component properties. |
700
|
|
|
* This overrides the component's initial property values. |
701
|
|
|
*/ |
702
|
|
|
Mivhak.render = function(name, props) |
703
|
|
|
{ |
704
|
|
|
var component = $.extend(true, {}, Mivhak.components[name]); |
705
|
|
|
var el = {}; |
706
|
|
|
|
707
|
|
|
// Create the element from the template |
708
|
|
|
el.$el = $(component.template); |
709
|
|
|
|
710
|
|
|
// Create all methods |
711
|
|
|
$.each(component.methods, function(name, method){ |
712
|
|
|
el[name] = function() {return method.apply(el,arguments);}; |
713
|
|
|
}); |
714
|
|
|
|
715
|
|
|
// Set properties |
716
|
|
|
$.each(component.props, function(name, prop){ |
717
|
|
|
el[name] = (typeof props !== 'undefined' && props.hasOwnProperty(name) ? props[name] : prop); |
718
|
|
|
}); |
719
|
|
|
|
720
|
|
|
// Bind events |
721
|
|
|
$.each(component.events, function(name, method){ |
722
|
|
|
el.$el.on(name, function() {return method.apply(el,arguments);}); |
723
|
|
|
}); |
724
|
|
|
|
725
|
|
|
// Call the 'created' function if exists |
726
|
|
|
if(component.hasOwnProperty('created')) component.created.call(el); |
|
|
|
|
727
|
|
|
|
728
|
|
|
return el; |
729
|
|
|
};Mivhak.component('caption', { |
730
|
|
|
template: '<div class="mivhak-caption"></div>', |
731
|
|
|
props: { |
732
|
|
|
text: null |
733
|
|
|
}, |
734
|
|
|
created: function() { |
735
|
|
|
this.setText(this.text); |
736
|
|
|
}, |
737
|
|
|
methods: { |
738
|
|
|
setText: function(text) { |
739
|
|
|
this.$el.html(text); |
740
|
|
|
} |
741
|
|
|
} |
742
|
|
|
});Mivhak.component('dropdown', { |
743
|
|
|
template: '<div class="mivhak-dropdown"></div>', |
744
|
|
|
props: { |
745
|
|
|
items: [], |
746
|
|
|
mivhakInstance: null, |
747
|
|
|
visible: false |
748
|
|
|
}, |
749
|
|
|
created: function() { |
750
|
|
|
var $this = this; |
751
|
|
|
$.each(this.items, function(i, item) { |
752
|
|
|
if( typeof item === 'string') item = Mivhak.buttons[item]; |
|
|
|
|
753
|
|
|
var button = $('<div>',{class: 'mivhak-dropdown-button', text: item.text, click: function(e){item.click.call($this.mivhakInstance,e);}}); |
754
|
|
|
if(item.toggle) |
755
|
|
|
{ |
756
|
|
|
button.$toggle = Mivhak.render('toggle'); |
757
|
|
|
|
758
|
|
|
// Toggle only if not clicking on the toggle itself (which makes it toggle as it is) |
759
|
|
|
button.click(function(e){if($(e.target).parents('.mivhak-dropdown-button').length !== 1)button.$toggle.toggle();}); |
|
|
|
|
760
|
|
|
button.append(button.$toggle.$el); |
761
|
|
|
} |
762
|
|
|
$this.$el.append(button); |
763
|
|
|
}); |
764
|
|
|
|
765
|
|
|
// Hide dropdown on outside click |
766
|
|
|
$(window).click(function(e){ |
767
|
|
|
if(!$(e.target).closest('.mivhak-icon-cog').length) { |
768
|
|
|
$this.$el.removeClass('mivhak-dropdown-visible'); |
769
|
|
|
} |
770
|
|
|
}); |
771
|
|
|
}, |
772
|
|
|
methods: { |
773
|
|
|
toggle: function() { |
774
|
|
|
this.visible = !this.visible; |
775
|
|
|
this.$el.toggleClass('mivhak-dropdown-visible'); |
776
|
|
|
} |
777
|
|
|
} |
778
|
|
|
});Mivhak.component('horizontal-scrollbar', { |
779
|
|
|
template: '<div class="mivhak-scrollbar mivhak-h-scrollbar"><div class="mivhak-scrollbar-thumb"></div></div>', |
780
|
|
|
props: { |
781
|
|
|
editor: null, |
782
|
|
|
$inner: null, |
783
|
|
|
$outer: null, |
784
|
|
|
mivhakInstance: null, |
785
|
|
|
minWidth: 50, |
786
|
|
|
state: { |
787
|
|
|
a: 0, // The total width of the editor |
788
|
|
|
b: 0, // The width of the viewport, excluding padding |
789
|
|
|
c: 0, // The width of the viewport, including padding |
790
|
|
|
d: 0, // The calculated width of the thumb |
791
|
|
|
l: 0 // The current left offset of the viewport |
792
|
|
|
}, |
793
|
|
|
initialized: false |
794
|
|
|
}, |
795
|
|
|
methods: { |
796
|
|
|
initialize: function() { |
797
|
|
|
if(!this.initialized) |
798
|
|
|
{ |
799
|
|
|
this.initialized = true; |
800
|
|
|
this.dragDealer(); |
801
|
|
|
var $this = this; |
802
|
|
|
$(window).resize(function(){ |
803
|
|
|
if(!$this.mivhakInstance.state.lineWrap) |
804
|
|
|
$this.refresh(); |
|
|
|
|
805
|
|
|
}); |
806
|
|
|
} |
807
|
|
|
this.refresh(); |
808
|
|
|
}, |
809
|
|
|
updateState: function() { |
810
|
|
|
var oldState = $.extend({}, this.state); |
811
|
|
|
this.state.a = this.getEditorWidth(); |
812
|
|
|
this.state.b = this.$outer.parent().width(); |
813
|
|
|
this.state.c = this.state.b - this.mivhakInstance.options.padding*2; |
814
|
|
|
this.state.d = Math.max(this.state.c*this.state.b/this.state.a,this.minWidth); |
815
|
|
|
this.state.l *= this.state.a/Math.max(oldState.a,1); // Math.max used to prevent division by zero |
816
|
|
|
return this.state.a !== oldState.a || this.state.b !== oldState.b; |
817
|
|
|
}, |
818
|
|
|
refresh: function() { |
819
|
|
|
var $this = this, oldLeft = this.state.l; |
820
|
|
|
raf(function(){ |
821
|
|
|
if($this.updateState()) |
822
|
|
|
{ |
823
|
|
|
if($this.getDifference() > 0) |
824
|
|
|
{ |
825
|
|
|
$this.doScroll('left',oldLeft-$this.state.l); |
826
|
|
|
$this.$el.css({width: $this.state.d + 'px', left: 0}); |
827
|
|
|
$this.moveBar(); |
828
|
|
|
} |
829
|
|
|
else |
830
|
|
|
{ |
831
|
|
|
$this.doScroll('left',$this.state.l); |
832
|
|
|
$this.$el.css({width: 0}); |
833
|
|
|
} |
834
|
|
|
} |
835
|
|
|
}); |
836
|
|
|
}, |
837
|
|
|
dragDealer: function(){ |
838
|
|
|
var $this = this, |
839
|
|
|
lastPageX; |
840
|
|
|
|
841
|
|
|
this.$el.on('mousedown.drag', function(e) { |
842
|
|
|
lastPageX = e.pageX; |
843
|
|
|
$this.$el.add(document.body).addClass('mivhak-scrollbar-grabbed'); |
844
|
|
|
$(document).on('mousemove.drag', drag).on('mouseup.drag', stop); |
845
|
|
|
return false; |
846
|
|
|
}); |
847
|
|
|
|
848
|
|
|
function drag(e){ |
849
|
|
|
var delta = e.pageX - lastPageX, |
850
|
|
|
didScroll; |
851
|
|
|
|
852
|
|
|
// Bail if the mouse hasn't moved |
853
|
|
|
if(!delta) return; |
|
|
|
|
854
|
|
|
|
855
|
|
|
lastPageX = e.pageX; |
856
|
|
|
|
857
|
|
|
raf(function(){ |
858
|
|
|
didScroll = $this.doScroll(delta > 0 ? 'right' : 'left', Math.abs(delta*$this.getEditorWidth()/$this.$outer.parent().width())); |
859
|
|
|
if(0 !== didScroll) $this.moveBar(); |
|
|
|
|
860
|
|
|
}); |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
function stop() { |
864
|
|
|
$this.$el.add(document.body).removeClass('mivhak-scrollbar-grabbed'); |
865
|
|
|
$(document).off("mousemove.drag mouseup.drag"); |
866
|
|
|
} |
867
|
|
|
}, |
868
|
|
|
moveBar: function() { |
869
|
|
|
this.$el.css({ |
870
|
|
|
left: (this.state.b-this.state.d)/(this.state.a-this.state.c)*this.state.l + 'px' |
871
|
|
|
}); |
872
|
|
|
}, |
873
|
|
|
|
874
|
|
|
/** |
875
|
|
|
* Scrolls the editor element in the direction given, provided that there |
876
|
|
|
* is remaining scroll space |
877
|
|
|
* @param {string} dir |
878
|
|
|
* @param {int} delta |
879
|
|
|
*/ |
880
|
|
|
doScroll: function(dir, delta) { |
881
|
|
|
var s = this.state, |
882
|
|
|
remaining, |
883
|
|
|
didScroll; |
884
|
|
|
|
885
|
|
|
if('left' === dir) |
886
|
|
|
{ |
887
|
|
|
remaining = s.l; |
888
|
|
|
didScroll = remaining > 0 ? Math.min(remaining,delta) : 0; |
889
|
|
|
s.l -= didScroll; |
890
|
|
|
} |
891
|
|
|
if('right' === dir) |
892
|
|
|
{ |
893
|
|
|
remaining = this.getDifference() - s.l; |
894
|
|
|
didScroll = remaining > 0 ? Math.min(remaining,delta) : 0; |
895
|
|
|
s.l += didScroll; |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
this.$inner.find('.ace_content').css({'margin-left': -s.l}); |
899
|
|
|
return didScroll; |
|
|
|
|
900
|
|
|
}, |
901
|
|
|
|
902
|
|
|
/** |
903
|
|
|
* Returns the difference between the containing div and the editor div |
904
|
|
|
*/ |
905
|
|
|
getDifference: function() |
906
|
|
|
{ |
907
|
|
|
return this.state.a - this.state.c; |
908
|
|
|
}, |
909
|
|
|
|
910
|
|
|
/** |
911
|
|
|
* Calculate the editor's width based on the number of lines |
912
|
|
|
*/ |
913
|
|
|
getEditorWidth: function() { |
914
|
|
|
return this.$inner.find('.ace_content').width(); |
915
|
|
|
} |
916
|
|
|
} |
917
|
|
|
});Mivhak.component('live-preview', { |
918
|
|
|
template: '<iframe class="mivhak-live-preview" allowtransparency="true" sandbox="allow-scripts allow-pointer-lock allow-same-origin allow-popups allow-modals allow-forms" frameborder="0"></iframe>', |
919
|
|
|
props: { |
920
|
|
|
resources: [] |
921
|
|
|
}, |
922
|
|
|
methods: { |
923
|
|
|
renderHTML: function() { |
924
|
|
|
var html = '<html>', |
925
|
|
|
head = '<head>', |
926
|
|
|
body = '<body>'; |
927
|
|
|
|
928
|
|
|
head += '<meta http-equiv="content-type" content="text/html; charset=UTF-8">'; |
929
|
|
|
head += '<meta name="robots" content="noindex, nofollow">'; |
930
|
|
|
head += '<meta name="googlebot" content="noindex, nofollow">'; |
931
|
|
|
|
932
|
|
|
for(var i = 0; i < this.resources.count(); i++) |
933
|
|
|
{ |
934
|
|
|
var source = this.resources.get(i); |
935
|
|
|
if('markup' === source.runAs) body += source.content; |
|
|
|
|
936
|
|
|
if('style' === source.runAs) head += this.createStyle(source.content, source.visible ? false : source.source); |
|
|
|
|
937
|
|
|
if('script' === source.runAs) head += this.createScript(source.content, source.visible ? false : source.source); |
|
|
|
|
938
|
|
|
} |
939
|
|
|
|
940
|
|
|
html += head+'</head>'+body+'</body></html>'; |
941
|
|
|
|
942
|
|
|
return html; |
943
|
|
|
}, |
944
|
|
|
createScript: function(content,src) { |
945
|
|
|
if(src) return '<script src="'+src+'" type="text/javascript"></script>'; |
|
|
|
|
946
|
|
|
return '<script>\n//<![CDATA[\nwindow.onload = function(){'+content+'};//]]>\n</script>'; // @see http://stackoverflow.com/questions/66837/when-is-a-cdata-section-necessary-within-a-script-tag |
947
|
|
|
}, |
948
|
|
|
createStyle: function(content,href) { |
949
|
|
|
if(href) return '<link href="'+href+'" rel="stylesheet">'; |
|
|
|
|
950
|
|
|
return '<style>'+content+'</style>'; |
951
|
|
|
}, |
952
|
|
|
show: function() { |
953
|
|
|
this.$el.addClass('mivhak-active'); |
954
|
|
|
this.run(); |
955
|
|
|
}, |
956
|
|
|
hide: function() { |
957
|
|
|
this.$el.removeClass('mivhak-active'); |
958
|
|
|
}, |
959
|
|
|
run: function() { |
960
|
|
|
var contents = this.$el.contents(), |
961
|
|
|
doc = contents[0]; |
962
|
|
|
|
963
|
|
|
doc.open(); |
964
|
|
|
doc.writeln(this.renderHTML()); |
965
|
|
|
doc.close(); |
966
|
|
|
} |
967
|
|
|
} |
968
|
|
|
});Mivhak.component('notifier', { |
969
|
|
|
template: '<div class="mivhak-notifier"></div>', |
970
|
|
|
methods: { |
971
|
|
|
notification: function(html) { |
972
|
|
|
if(!html) return; |
|
|
|
|
973
|
|
|
clearTimeout(this.timeout); |
974
|
|
|
this.$el.off('click'); |
975
|
|
|
this.$el.html(html); |
976
|
|
|
this.$el.addClass('mivhak-visible'); |
977
|
|
|
}, |
978
|
|
|
timedNotification: function(html, timeout) { |
979
|
|
|
var $this = this; |
980
|
|
|
this.notification(html); |
981
|
|
|
this.timeout = setTimeout(function(){ |
982
|
|
|
$this.hide(); |
983
|
|
|
},timeout); |
984
|
|
|
}, |
985
|
|
|
closableNotification: function(html, onclick) |
986
|
|
|
{ |
987
|
|
|
var $this = this; |
988
|
|
|
this.notification(html); |
989
|
|
|
this.$el.addClass('mivhak-button'); |
990
|
|
|
this.$el.click(function(e){ |
991
|
|
|
$this.hide(); |
992
|
|
|
if(typeof onclick !== 'undefined') |
993
|
|
|
onclick.call(null, e); |
|
|
|
|
994
|
|
|
}); |
995
|
|
|
}, |
996
|
|
|
hide: function() { |
997
|
|
|
this.$el.removeClass('mivhak-visible mivhak-button'); |
998
|
|
|
} |
999
|
|
|
} |
1000
|
|
|
});Mivhak.component('tab-pane', { |
1001
|
|
|
template: '<div class="mivhak-tab-pane"><div class="mivhak-tab-pane-inner"></div></div>', |
1002
|
|
|
props: { |
1003
|
|
|
resource: null, |
1004
|
|
|
editor: null, |
1005
|
|
|
index: null, |
1006
|
|
|
padding: 10, |
1007
|
|
|
mivhakInstance: null |
1008
|
|
|
}, |
1009
|
|
|
created: function() { |
1010
|
|
|
this.setEditor(); |
1011
|
|
|
this.fetchRemoteSource(); |
1012
|
|
|
this.markLines(); |
1013
|
|
|
|
1014
|
|
|
this.$el = $(this.resource.pre).wrap(this.$el).parent().parent(); |
1015
|
|
|
this.$el.find('.mivhak-tab-pane-inner').css({margin: this.mivhakInstance.options.padding}); |
1016
|
|
|
this.setScrollbars(); |
1017
|
|
|
|
1018
|
|
|
}, |
1019
|
|
|
methods: { |
1020
|
|
|
getTheme: function() { |
1021
|
|
|
return this.mivhakInstance.options.theme === 'light' ? 'clouds' : 'ambiance'; |
1022
|
|
|
}, |
1023
|
|
|
fetchRemoteSource: function() { |
1024
|
|
|
var $this = this; |
1025
|
|
|
if(this.resource.source) { |
1026
|
|
|
$.ajax(this.resource.source).done(function(res){ |
1027
|
|
|
$this.editor.setValue(res,-1); |
1028
|
|
|
|
1029
|
|
|
// Refresh code viewer height |
1030
|
|
|
$this.mivhakInstance.callMethod('setHeight',$this.mivhakInstance.options.height); |
1031
|
|
|
|
1032
|
|
|
// Refresh scrollbars |
1033
|
|
|
raf(function(){ |
1034
|
|
|
$this.vscroll.refresh(); |
1035
|
|
|
$this.hscroll.refresh(); |
1036
|
|
|
}); |
1037
|
|
|
}); |
1038
|
|
|
|
1039
|
|
|
} |
1040
|
|
|
}, |
1041
|
|
|
setScrollbars: function() { |
1042
|
|
|
var $inner = $(this.resource.pre), |
1043
|
|
|
$outer = this.$el.find('.mivhak-tab-pane-inner'); |
1044
|
|
|
|
1045
|
|
|
this.vscroll = Mivhak.render('vertical-scrollbar',{editor: this.editor, $inner: $inner, $outer: $outer, mivhakInstance: this.mivhakInstance}); |
1046
|
|
|
this.hscroll = Mivhak.render('horizontal-scrollbar',{editor: this.editor, $inner: $inner, $outer: $outer, mivhakInstance: this.mivhakInstance}); |
1047
|
|
|
|
1048
|
|
|
this.$el.append(this.vscroll.$el, this.hscroll.$el); |
1049
|
|
|
}, |
1050
|
|
|
show: function() { |
1051
|
|
|
this.$el.addClass('mivhak-tab-pane-active'); |
1052
|
|
|
this.editor.focus(); |
1053
|
|
|
this.editor.gotoLine(0); // Needed in order to get focus |
1054
|
|
|
|
1055
|
|
|
// Recalculate scrollbar positions based on the now visible element |
1056
|
|
|
this.vscroll.initialize(); |
1057
|
|
|
this.hscroll.initialize(); |
1058
|
|
|
}, |
1059
|
|
|
hide: function() { |
1060
|
|
|
this.$el.removeClass('mivhak-tab-pane-active'); |
1061
|
|
|
}, |
1062
|
|
|
setEditor: function() { |
1063
|
|
|
|
1064
|
|
|
// Remove redundant space from code |
1065
|
|
|
this.resource.pre.textContent = this.resource.pre.textContent.trim(); |
1066
|
|
|
|
1067
|
|
|
// Set editor options |
1068
|
|
|
this.editor = ace.edit(this.resource.pre); |
|
|
|
|
1069
|
|
|
this.editor.setReadOnly(!this.mivhakInstance.options.editable); |
1070
|
|
|
this.editor.setTheme("ace/theme/"+this.getTheme()); |
1071
|
|
|
this.editor.setShowPrintMargin(false); |
1072
|
|
|
this.editor.renderer.setShowGutter(this.mivhakInstance.options.lineNumbers); |
1073
|
|
|
this.editor.getSession().setMode("ace/mode/"+this.resource.lang); |
1074
|
|
|
this.editor.getSession().setUseWorker(false); // Disable syntax checking |
1075
|
|
|
this.editor.getSession().setUseWrapMode(true); // Set initial line wrapping |
1076
|
|
|
|
1077
|
|
|
this.editor.setOptions({ |
1078
|
|
|
maxLines: Infinity, |
1079
|
|
|
firstLineNumber: this.resource.startLine, |
1080
|
|
|
highlightActiveLine: false, |
1081
|
|
|
fontSize: parseInt(14) |
1082
|
|
|
}); |
1083
|
|
|
|
1084
|
|
|
// Update source content for the live preview |
1085
|
|
|
if(this.mivhakInstance.options.editable) |
1086
|
|
|
{ |
1087
|
|
|
var $this = this; |
1088
|
|
|
this.editor.getSession().on('change', function(a,b,c) { |
|
|
|
|
1089
|
|
|
$this.mivhakInstance.resources.update($this.index, $this.editor.getValue()); |
1090
|
|
|
}); |
1091
|
|
|
} |
1092
|
|
|
}, |
1093
|
|
|
markLines: function() |
1094
|
|
|
{ |
1095
|
|
|
if(!this.resource.mark) return; |
|
|
|
|
1096
|
|
|
var ranges = strToRange(this.resource.mark), |
1097
|
|
|
i = ranges.length, |
1098
|
|
|
AceRange = ace.require("ace/range").Range; |
|
|
|
|
1099
|
|
|
|
1100
|
|
|
while(i--) |
1101
|
|
|
{ |
1102
|
|
|
this.editor.session.addMarker( |
1103
|
|
|
new AceRange(ranges[i].start, 0, ranges[i].end, 1), // Define the range of the marker |
1104
|
|
|
"ace_active-line", // Set the CSS class for the marker |
1105
|
|
|
"fullLine" // Marker type |
1106
|
|
|
); |
1107
|
|
|
} |
1108
|
|
|
} |
1109
|
|
|
} |
1110
|
|
|
});Mivhak.component('tabs', { |
1111
|
|
|
template: '<div class="mivhak-tabs"></div>', |
1112
|
|
|
props: { |
1113
|
|
|
mivhakInstance: null, |
1114
|
|
|
activeTab: null, |
1115
|
|
|
tabs: [] |
1116
|
|
|
}, |
1117
|
|
|
created: function() { |
1118
|
|
|
var $this = this; |
1119
|
|
|
this.$el = this.mivhakInstance.$selection.find('pre').wrapAll(this.$el).parent(); |
1120
|
|
|
$.each(this.mivhakInstance.resources.data,function(i, resource){ |
1121
|
|
|
if(resource.visible) |
1122
|
|
|
$this.tabs.push(Mivhak.render('tab-pane',{ |
|
|
|
|
1123
|
|
|
resource: resource, |
1124
|
|
|
index: i, |
1125
|
|
|
mivhakInstance: $this.mivhakInstance |
1126
|
|
|
})); |
1127
|
|
|
}); |
1128
|
|
|
}, |
1129
|
|
|
methods: { |
1130
|
|
|
showTab: function(index){ |
1131
|
|
|
var $this = this; |
1132
|
|
|
$.each(this.tabs, function(i, tab){ |
1133
|
|
|
if(index === i) { |
1134
|
|
|
$this.mivhakInstance.activeTab = tab; |
1135
|
|
|
tab.show(); |
1136
|
|
|
} |
1137
|
|
|
else tab.hide(); |
|
|
|
|
1138
|
|
|
}); |
1139
|
|
|
} |
1140
|
|
|
} |
1141
|
|
|
});Mivhak.component('toggle', { |
1142
|
|
|
template: '<div class="mivhak-toggle"><div class="mivhak-toggle-knob"></div></div>', |
1143
|
|
|
props: { |
1144
|
|
|
on: true |
1145
|
|
|
}, |
1146
|
|
|
events: { |
1147
|
|
|
click: function() { |
1148
|
|
|
this.toggle(); |
1149
|
|
|
} |
1150
|
|
|
}, |
1151
|
|
|
created: function() { |
1152
|
|
|
this.$el.addClass('mivhak-toggle-'+(this.on?'on':'off')); |
1153
|
|
|
}, |
1154
|
|
|
methods: { |
1155
|
|
|
toggle: function() { |
1156
|
|
|
this.on = !this.on; |
1157
|
|
|
this.$el.toggleClass('mivhak-toggle-on').toggleClass('mivhak-toggle-off'); |
1158
|
|
|
} |
1159
|
|
|
} |
1160
|
|
|
});Mivhak.component('top-bar-button', { |
1161
|
|
|
template: '<div class="mivhak-top-bar-button"></div>', |
1162
|
|
|
props: { |
1163
|
|
|
text: null, |
1164
|
|
|
icon: null, |
1165
|
|
|
dropdown: null, |
1166
|
|
|
mivhakInstance: null, |
1167
|
|
|
onClick: function(){} |
1168
|
|
|
}, |
1169
|
|
|
events: { |
1170
|
|
|
click: function() { |
1171
|
|
|
this.onClick(); |
1172
|
|
|
} |
1173
|
|
|
}, |
1174
|
|
|
created: function() { |
1175
|
|
|
var $this = this; |
1176
|
|
|
this.$el.text(this.text); |
1177
|
|
|
if(this.icon) this.$el.addClass('mivhak-icon mivhak-icon-'+this.icon).append($(Mivhak.icons[this.icon])); |
|
|
|
|
1178
|
|
|
if(this.dropdown) |
1179
|
|
|
{ |
1180
|
|
|
$this.$el.append(this.dropdown.$el); |
1181
|
|
|
this.onClick = function() { |
1182
|
|
|
$this.toggleActivation(); |
1183
|
|
|
$this.dropdown.toggle(); |
1184
|
|
|
}; |
1185
|
|
|
} |
1186
|
|
|
}, |
1187
|
|
|
methods: { |
1188
|
|
|
activate: function() { |
1189
|
|
|
this.$el.addClass('mivhak-button-active'); |
1190
|
|
|
}, |
1191
|
|
|
deactivate: function() { |
1192
|
|
|
this.$el.removeClass('mivhak-button-active'); |
1193
|
|
|
}, |
1194
|
|
|
toggleActivation: function() { |
1195
|
|
|
this.$el.toggleClass('mivhak-button-active'); |
1196
|
|
|
}, |
1197
|
|
|
isActive: function() { |
1198
|
|
|
return this.$el.hasClass('mivhak-button-active'); |
1199
|
|
|
} |
1200
|
|
|
} |
1201
|
|
|
});Mivhak.component('top-bar', { |
1202
|
|
|
template: '<div class="mivhak-top-bar"><div class="mivhak-nav-tabs"></div><div class="mivhak-controls"></div><div class="mivhak-line"></div></div>', |
1203
|
|
|
props: { |
1204
|
|
|
mivhakInstance: null, |
1205
|
|
|
navTabs: [], |
1206
|
|
|
controls: [], |
1207
|
|
|
line: null |
1208
|
|
|
}, |
1209
|
|
|
created: function() { |
1210
|
|
|
this.line = this.$el.find('.mivhak-line'); |
1211
|
|
|
this.createTabNav(); |
1212
|
|
|
if(this.mivhakInstance.options.runnable) this.createPlayButton(); |
|
|
|
|
1213
|
|
|
this.createCogButton(); |
1214
|
|
|
}, |
1215
|
|
|
methods: { |
1216
|
|
|
activateNavTab: function(index) { |
1217
|
|
|
var button = this.navTabs[index]; |
1218
|
|
|
// Deactivate all tabs and activate this tab |
1219
|
|
|
$.each(this.navTabs, function(i,navTab){navTab.deactivate();}); |
1220
|
|
|
button.activate(); |
1221
|
|
|
|
1222
|
|
|
// Position the line |
1223
|
|
|
this.moveLine(button.$el); |
1224
|
|
|
}, |
1225
|
|
|
moveLine: function($el) { |
1226
|
|
|
if(typeof $el === 'undefined') { |
1227
|
|
|
this.line.removeClass('mivhak-visible'); |
1228
|
|
|
return; |
1229
|
|
|
} |
1230
|
|
|
this.line.width($el.width()); |
1231
|
|
|
this.line.css({left:$el.position().left + ($el.outerWidth() - $el.width())/2}); |
1232
|
|
|
this.line.addClass('mivhak-visible'); |
1233
|
|
|
}, |
1234
|
|
|
createTabNav: function() { |
1235
|
|
|
var source, i, pos = 0; |
1236
|
|
|
for(i = 0; i < this.mivhakInstance.resources.count(); i++) |
1237
|
|
|
{ |
1238
|
|
|
source = this.mivhakInstance.resources.get(i); |
1239
|
|
|
if(source.visible) this.createNavTabButton(pos++, source.lang); |
|
|
|
|
1240
|
|
|
} |
1241
|
|
|
}, |
1242
|
|
|
createNavTabButton: function(i, lang) { |
1243
|
|
|
var $this = this, |
1244
|
|
|
button = Mivhak.render('top-bar-button',{ |
1245
|
|
|
text: lang, |
1246
|
|
|
onClick: function() { |
1247
|
|
|
$this.mivhakInstance.callMethod('showTab',i); |
1248
|
|
|
} |
1249
|
|
|
}); |
1250
|
|
|
this.navTabs.push(button); |
1251
|
|
|
this.$el.find('.mivhak-nav-tabs').append(button.$el); |
1252
|
|
|
}, |
1253
|
|
|
createPlayButton: function() { |
1254
|
|
|
var $this = this; |
1255
|
|
|
var playBtn = Mivhak.render('top-bar-button',{ |
1256
|
|
|
icon: 'play', |
1257
|
|
|
onClick: function() { |
1258
|
|
|
$this.mivhakInstance.preview.show(); |
1259
|
|
|
$this.moveLine(); |
1260
|
|
|
} |
1261
|
|
|
}); |
1262
|
|
|
this.controls.push(playBtn); |
1263
|
|
|
this.$el.find('.mivhak-controls').append(playBtn.$el); |
1264
|
|
|
}, |
1265
|
|
|
createCogButton: function() { |
1266
|
|
|
var cogBtn = Mivhak.render('top-bar-button',{ |
1267
|
|
|
icon: 'cog', |
1268
|
|
|
mivhakInstance: this.mivhakInstance, |
1269
|
|
|
dropdown: Mivhak.render('dropdown',{ |
1270
|
|
|
mivhakInstance: this.mivhakInstance, |
1271
|
|
|
items: this.mivhakInstance.options.buttons |
1272
|
|
|
}) |
1273
|
|
|
}); |
1274
|
|
|
this.controls.push(cogBtn); |
1275
|
|
|
this.$el.find('.mivhak-controls').append(cogBtn.$el); |
1276
|
|
|
} |
1277
|
|
|
} |
1278
|
|
|
});Mivhak.component('vertical-scrollbar', { |
1279
|
|
|
template: '<div class="mivhak-scrollbar mivhak-v-scrollbar"><div class="mivhak-scrollbar-thumb"></div></div>', |
1280
|
|
|
props: { |
1281
|
|
|
editor: null, |
1282
|
|
|
$inner: null, |
1283
|
|
|
$outer: null, |
1284
|
|
|
mivhakInstance: null, |
1285
|
|
|
minHeight: 50, |
1286
|
|
|
state: { |
1287
|
|
|
a: 0, // The total height of the editor |
1288
|
|
|
b: 0, // The height of the viewport, excluding padding |
1289
|
|
|
c: 0, // The height of the viewport, including padding |
1290
|
|
|
d: 0, // The calculated thumb height |
1291
|
|
|
t: 0 // The current top offset of the viewport |
1292
|
|
|
}, |
1293
|
|
|
initialized: false |
1294
|
|
|
}, |
1295
|
|
|
methods: { |
1296
|
|
|
initialize: function() { |
1297
|
|
|
if(!this.initialized) |
1298
|
|
|
{ |
1299
|
|
|
this.initialized = true; |
1300
|
|
|
this.dragDealer(); |
1301
|
|
|
var $this = this; |
1302
|
|
|
this.$inner.on('mousewheel', function(e){$this.onScroll.call(this, e);}); |
1303
|
|
|
$(window).resize(function(){ |
1304
|
|
|
if($this.mivhakInstance.state.lineWrap) |
1305
|
|
|
$this.refresh(); |
|
|
|
|
1306
|
|
|
}); |
1307
|
|
|
} |
1308
|
|
|
// Refresh anytime initialize is called |
1309
|
|
|
this.refresh(); |
1310
|
|
|
}, |
1311
|
|
|
updateState: function() { |
1312
|
|
|
var oldState = $.extend({}, this.state); |
1313
|
|
|
this.state.a = getEditorHeight(this.$inner); |
1314
|
|
|
this.state.b = this.mivhakInstance.state.height; |
1315
|
|
|
this.state.c = this.mivhakInstance.state.height-this.mivhakInstance.options.padding*2; |
1316
|
|
|
this.state.d = Math.max(this.state.c*this.state.b/this.state.a,this.minHeight); |
1317
|
|
|
this.state.t *= this.state.a/Math.max(oldState.a,1); // Math.max used to prevent division by zero |
1318
|
|
|
return this.state.a !== oldState.a || this.state.b !== oldState.b; |
1319
|
|
|
}, |
1320
|
|
|
refresh: function() { |
1321
|
|
|
var $this = this, oldTop = this.state.t; |
1322
|
|
|
raf(function(){ |
1323
|
|
|
if($this.updateState()) |
1324
|
|
|
{ |
1325
|
|
|
if($this.getDifference() > 0) |
1326
|
|
|
{ |
1327
|
|
|
$this.doScroll('up',oldTop-$this.state.t); |
1328
|
|
|
$this.$el.css({height: $this.state.d + 'px', top: 0}); |
1329
|
|
|
$this.moveBar(); |
1330
|
|
|
} |
1331
|
|
|
else |
1332
|
|
|
{ |
1333
|
|
|
$this.doScroll('up',$this.state.t); |
1334
|
|
|
$this.$el.css({height: 0}); |
1335
|
|
|
} |
1336
|
|
|
} |
1337
|
|
|
}); |
1338
|
|
|
}, |
1339
|
|
|
onScroll: function(e) { |
1340
|
|
|
var didScroll; |
1341
|
|
|
|
1342
|
|
|
if(e.deltaY > 0) |
1343
|
|
|
didScroll = this.doScroll('up',e.deltaY*e.deltaFactor); |
|
|
|
|
1344
|
|
|
else |
1345
|
|
|
didScroll = this.doScroll('down',-e.deltaY*e.deltaFactor); |
1346
|
|
|
|
1347
|
|
|
if(0 !== didScroll) { |
1348
|
|
|
this.moveBar(); |
1349
|
|
|
e.preventDefault(); // Only prevent page scroll if the editor can be scrolled |
1350
|
|
|
} |
1351
|
|
|
}, |
1352
|
|
|
dragDealer: function(){ |
1353
|
|
|
var $this = this, |
1354
|
|
|
lastPageY; |
1355
|
|
|
|
1356
|
|
|
this.$el.on('mousedown.drag', function(e) { |
1357
|
|
|
lastPageY = e.pageY; |
1358
|
|
|
$this.$el.add(document.body).addClass('mivhak-scrollbar-grabbed'); |
1359
|
|
|
$(document).on('mousemove.drag', drag).on('mouseup.drag', stop); |
1360
|
|
|
return false; |
1361
|
|
|
}); |
1362
|
|
|
|
1363
|
|
|
function drag(e){ |
1364
|
|
|
var delta = e.pageY - lastPageY, |
1365
|
|
|
didScroll; |
1366
|
|
|
|
1367
|
|
|
// Bail if the mouse hasn't moved |
1368
|
|
|
if(!delta) return; |
|
|
|
|
1369
|
|
|
|
1370
|
|
|
lastPageY = e.pageY; |
1371
|
|
|
|
1372
|
|
|
raf(function(){ |
1373
|
|
|
didScroll = $this.doScroll(delta > 0 ? 'down' : 'up', Math.abs(delta*getEditorHeight($this.$inner)/$this.$outer.parent().height())); |
1374
|
|
|
if(0 !== didScroll) $this.moveBar(); |
|
|
|
|
1375
|
|
|
}); |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
function stop() { |
1379
|
|
|
$this.$el.add(document.body).removeClass('mivhak-scrollbar-grabbed'); |
1380
|
|
|
$(document).off("mousemove.drag mouseup.drag"); |
1381
|
|
|
} |
1382
|
|
|
}, |
1383
|
|
|
moveBar: function() { |
1384
|
|
|
this.$el.css({ |
1385
|
|
|
top: (this.state.b-this.state.d)/(this.state.a-this.state.c)*this.state.t + 'px' |
1386
|
|
|
}); |
1387
|
|
|
}, |
1388
|
|
|
|
1389
|
|
|
/** |
1390
|
|
|
* Scrolls the editor element in the direction given, provided that there |
1391
|
|
|
* is remaining scroll space |
1392
|
|
|
* @param {string} dir |
1393
|
|
|
* @param {int} delta |
1394
|
|
|
*/ |
1395
|
|
|
doScroll: function(dir, delta) { |
1396
|
|
|
var s = this.state, |
1397
|
|
|
remaining, |
1398
|
|
|
didScroll; |
1399
|
|
|
|
1400
|
|
|
if('up' === dir) |
1401
|
|
|
{ |
1402
|
|
|
remaining = s.t; |
1403
|
|
|
didScroll = remaining > 0 ? Math.min(remaining,delta) : 0; |
1404
|
|
|
s.t -= didScroll; |
1405
|
|
|
} |
1406
|
|
|
if('down' === dir) |
1407
|
|
|
{ |
1408
|
|
|
remaining = this.getDifference() - s.t; |
1409
|
|
|
didScroll = remaining > 0 ? Math.min(remaining,delta) : 0; |
1410
|
|
|
s.t += didScroll; |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
this.$inner.css({top: -s.t}); |
1414
|
|
|
return didScroll; |
|
|
|
|
1415
|
|
|
}, |
1416
|
|
|
|
1417
|
|
|
/** |
1418
|
|
|
* Returns the difference between the containing div and the editor div |
1419
|
|
|
*/ |
1420
|
|
|
getDifference: function() |
1421
|
|
|
{ |
1422
|
|
|
return this.state.a - this.state.c; |
1423
|
|
|
} |
1424
|
|
|
} |
1425
|
|
|
});/** |
1426
|
|
|
* Extends the functionality of jQuery to include Mivhak |
1427
|
|
|
* |
1428
|
|
|
* @param {Function|Object} methodOrOptions |
1429
|
|
|
* @returns {jQuery} |
1430
|
|
|
*/ |
1431
|
|
|
$.fn.mivhak = function( methodOrOptions ) { |
1432
|
|
|
|
1433
|
|
|
// Store arguments for use with methods |
1434
|
|
|
var args = arguments.length > 1 ? Array.apply(null, arguments).slice(1) : null; |
1435
|
|
|
|
1436
|
|
|
return this.each(function(){ |
1437
|
|
|
|
1438
|
|
|
// If this is an options object, set or update the options |
1439
|
|
|
if( typeof methodOrOptions === 'object' || !methodOrOptions ) |
1440
|
|
|
{ |
1441
|
|
|
// If this is the initial call for this element, instantiate a new Mivhak object |
1442
|
|
|
if( typeof $(this).data( 'mivhak' ) === 'undefined' ) { |
1443
|
|
|
var plugin = new Mivhak( this, methodOrOptions ); |
1444
|
|
|
$(this).data( 'mivhak', plugin ); |
1445
|
|
|
} |
1446
|
|
|
// Otherwise update existing settings (consequent calls will update, rather than recreate Mivhak) |
1447
|
|
|
else |
1448
|
|
|
{ |
1449
|
|
|
$(this).data('mivhak').setOptions( methodOrOptions ); |
1450
|
|
|
$(this).data('mivhak').applyOptions(); |
1451
|
|
|
} |
1452
|
|
|
} |
1453
|
|
|
|
1454
|
|
|
// If this is a method call, run the method (if it exists) |
1455
|
|
|
else if( Mivhak.methodExists( methodOrOptions ) ) |
1456
|
|
|
{ |
1457
|
|
|
Mivhak.methods[methodOrOptions].apply($(this).data('mivhak'), args); |
1458
|
|
|
} |
1459
|
|
|
}); |
1460
|
|
|
};}( jQuery )); |