rails-bootstrap-markdown 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/bootstrap-markdown.js +989 -0
- data/app/assets/javascripts/markdown.js +1642 -0
- data/app/assets/javascripts/to-markdown.js +184 -0
- data/app/assets/stylesheets/bootstrap-markdown.css.scss +62 -0
- data/lib/rails-bootstrap-markdown.rb +10 -0
- data/lib/rails-bootstrap-markdown/version.rb +7 -0
- data/rails-bootstrap-markdown.gemspec +18 -0
- metadata +63 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Danny Tatom
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Rails::Bootstrap::Markdown
|
2
|
+
|
3
|
+
A rails gem for [Bootstrap Markdown](http://toopay.github.io/bootstrap-markdown/), rewritten to SCSS for use with [bootstrap-sass](https://github.com/thomas-mcdonald/bootstrap-sass)
|
4
|
+
|
5
|
+
3rd party libraries included:
|
6
|
+
|
7
|
+
- http://toopay.github.io/bootstrap-markdown/
|
8
|
+
- https://github.com/domchristie/to-markdown
|
9
|
+
- https://github.com/evilstreak/markdown-js
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'rails-bootstrap-markdown'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install rails-bootstrap-markdown
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### CSS
|
28
|
+
|
29
|
+
```
|
30
|
+
*= require bootstrap-markdown
|
31
|
+
```
|
32
|
+
|
33
|
+
### Javascript
|
34
|
+
|
35
|
+
```
|
36
|
+
//= requrie bootstrap-markdown
|
37
|
+
```
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
1. Fork it
|
42
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
43
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
44
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
45
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,989 @@
|
|
1
|
+
/* ===================================================
|
2
|
+
* bootstrap-markdown.js v1.0.0
|
3
|
+
* http://github.com/toopay/bootstrap-markdown
|
4
|
+
* ===================================================
|
5
|
+
* Copyright 2013 Taufan Aditya
|
6
|
+
*
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
* you may not use this file except in compliance with the License.
|
9
|
+
* You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
*
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
* See the License for the specific language governing permissions and
|
17
|
+
* limitations under the License.
|
18
|
+
* ========================================================== */
|
19
|
+
|
20
|
+
//= require markdown
|
21
|
+
//= require to-markdown
|
22
|
+
|
23
|
+
!function ($) {
|
24
|
+
|
25
|
+
"use strict"; // jshint ;_;
|
26
|
+
|
27
|
+
|
28
|
+
/* MARKDOWN CLASS DEFINITION
|
29
|
+
* ========================== */
|
30
|
+
|
31
|
+
var Markdown = function (element, options) {
|
32
|
+
// Class Properties
|
33
|
+
this.$ns = 'bootstrap-markdown'
|
34
|
+
this.$element = $(element)
|
35
|
+
this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
|
36
|
+
this.$cloneEditor = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
|
37
|
+
this.$options = $.extend(true, {}, $.fn.markdown.defaults, options)
|
38
|
+
this.$oldContent = null
|
39
|
+
this.$isPreview = false
|
40
|
+
this.$editor = null
|
41
|
+
this.$textarea = null
|
42
|
+
this.$handler = []
|
43
|
+
this.$callback = []
|
44
|
+
this.$nextTab = []
|
45
|
+
|
46
|
+
this.showEditor()
|
47
|
+
}
|
48
|
+
|
49
|
+
Markdown.prototype = {
|
50
|
+
|
51
|
+
constructor: Markdown
|
52
|
+
|
53
|
+
, __alterButtons: function(name,alter) {
|
54
|
+
var handler = this.$handler, isAll = (name == 'all'),that = this
|
55
|
+
|
56
|
+
$.each(handler,function(k,v) {
|
57
|
+
var halt = true
|
58
|
+
if (isAll) {
|
59
|
+
halt = false
|
60
|
+
} else {
|
61
|
+
halt = v.indexOf(name) < 0
|
62
|
+
}
|
63
|
+
|
64
|
+
if (halt == false) {
|
65
|
+
alter(that.$editor.find('button[data-handler="'+v+'"]'))
|
66
|
+
}
|
67
|
+
})
|
68
|
+
}
|
69
|
+
|
70
|
+
, __buildButtons: function(buttonsArray, container) {
|
71
|
+
var i,
|
72
|
+
ns = this.$ns,
|
73
|
+
handler = this.$handler,
|
74
|
+
callback = this.$callback
|
75
|
+
|
76
|
+
for (i=0;i<buttonsArray.length;i++) {
|
77
|
+
// Build each group container
|
78
|
+
var y, btnGroups = buttonsArray[i]
|
79
|
+
for (y=0;y<btnGroups.length;y++) {
|
80
|
+
// Build each button group
|
81
|
+
var z,
|
82
|
+
buttons = btnGroups[y].data,
|
83
|
+
btnGroupContainer = $('<div/>', {
|
84
|
+
'class': 'btn-group'
|
85
|
+
})
|
86
|
+
|
87
|
+
for (z=0;z<buttons.length;z++) {
|
88
|
+
var button = buttons[z],
|
89
|
+
buttonHandler = ns+'-'+button.name,
|
90
|
+
btnText = button.btnText ? button.btnText : '',
|
91
|
+
btnClass = button.btnClass ? button.btnClass : 'btn'
|
92
|
+
|
93
|
+
// Attach the button object
|
94
|
+
btnGroupContainer.append('<button class="'
|
95
|
+
+btnClass
|
96
|
+
+' btn-small" title="'
|
97
|
+
+button.title
|
98
|
+
+'" data-provider="'
|
99
|
+
+ns
|
100
|
+
+'" data-handler="'
|
101
|
+
+buttonHandler
|
102
|
+
+'"><i class="'
|
103
|
+
+button.icon
|
104
|
+
+'"></i> '
|
105
|
+
+btnText
|
106
|
+
+'</button>')
|
107
|
+
|
108
|
+
// Register handler and callback
|
109
|
+
handler.push(buttonHandler)
|
110
|
+
callback.push(button.callback)
|
111
|
+
}
|
112
|
+
|
113
|
+
// Attach the button group into container dom
|
114
|
+
container.append(btnGroupContainer)
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
return container
|
119
|
+
}
|
120
|
+
, __setListener: function() {
|
121
|
+
// Set size and resizable Properties
|
122
|
+
var hasRows = typeof this.$textarea.attr('rows') != 'undefined',
|
123
|
+
maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
|
124
|
+
rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows
|
125
|
+
|
126
|
+
this.$textarea.attr('rows',rowsVal)
|
127
|
+
this.$textarea.css('resize','none')
|
128
|
+
|
129
|
+
this.$textarea
|
130
|
+
.on('focus', $.proxy(this.focus, this))
|
131
|
+
.on('keypress', $.proxy(this.keypress, this))
|
132
|
+
.on('keyup', $.proxy(this.keyup, this))
|
133
|
+
|
134
|
+
if (this.eventSupported('keydown')) {
|
135
|
+
this.$textarea.on('keydown', $.proxy(this.keydown, this))
|
136
|
+
}
|
137
|
+
|
138
|
+
// Re-attach markdown data
|
139
|
+
this.$textarea.data('markdown',this)
|
140
|
+
}
|
141
|
+
|
142
|
+
, __handle: function(e) {
|
143
|
+
var target = $(e.currentTarget),
|
144
|
+
handler = this.$handler,
|
145
|
+
callback = this.$callback,
|
146
|
+
handlerName = target.attr('data-handler'),
|
147
|
+
callbackIndex = handler.indexOf(handlerName),
|
148
|
+
callbackHandler = callback[callbackIndex]
|
149
|
+
|
150
|
+
// Trigger the focusin
|
151
|
+
$(e.currentTarget).focus()
|
152
|
+
|
153
|
+
callbackHandler(this)
|
154
|
+
|
155
|
+
// Unless it was the save handler,
|
156
|
+
// focusin the textarea
|
157
|
+
if (handlerName.indexOf('cmdSave') < 0) {
|
158
|
+
this.$textarea.focus()
|
159
|
+
}
|
160
|
+
|
161
|
+
e.preventDefault()
|
162
|
+
}
|
163
|
+
|
164
|
+
, showEditor: function() {
|
165
|
+
var instance = this,
|
166
|
+
textarea,
|
167
|
+
ns = this.$ns,
|
168
|
+
container = this.$element,
|
169
|
+
originalHeigth = container.css('height'),
|
170
|
+
originalWidth = container.css('width'),
|
171
|
+
editable = this.$editable,
|
172
|
+
handler = this.$handler,
|
173
|
+
callback = this.$callback,
|
174
|
+
options = this.$options,
|
175
|
+
editor = $( '<div/>', {
|
176
|
+
'class': 'md-editor',
|
177
|
+
click: function() {
|
178
|
+
instance.focus()
|
179
|
+
}
|
180
|
+
})
|
181
|
+
|
182
|
+
// Prepare the editor
|
183
|
+
if (this.$editor == null) {
|
184
|
+
// Create the panel
|
185
|
+
var editorHeader = $('<div/>', {
|
186
|
+
'class': 'md-header'
|
187
|
+
})
|
188
|
+
|
189
|
+
// Build the main buttons
|
190
|
+
if (options.buttons.length > 0) {
|
191
|
+
editorHeader = this.__buildButtons(options.buttons, editorHeader)
|
192
|
+
}
|
193
|
+
|
194
|
+
// Build the additional buttons
|
195
|
+
if (options.additionalButtons.length > 0) {
|
196
|
+
editorHeader = this.__buildButtons(options.additionalButtons, editorHeader)
|
197
|
+
}
|
198
|
+
|
199
|
+
editor.append(editorHeader)
|
200
|
+
|
201
|
+
// Wrap the textarea
|
202
|
+
if (container.is('textarea')) {
|
203
|
+
container.before(editor)
|
204
|
+
textarea = container
|
205
|
+
textarea.addClass('md-input')
|
206
|
+
editor.append(textarea)
|
207
|
+
} else {
|
208
|
+
var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
|
209
|
+
currentContent = $.trim(rawContent)
|
210
|
+
|
211
|
+
// This is some arbitrary content that could be edited
|
212
|
+
textarea = $('<textarea/>', {
|
213
|
+
'class': 'md-input',
|
214
|
+
'val' : currentContent
|
215
|
+
})
|
216
|
+
|
217
|
+
editor.append(textarea)
|
218
|
+
|
219
|
+
// Save the editable
|
220
|
+
editable.el = container
|
221
|
+
editable.type = container.prop('tagName').toLowerCase()
|
222
|
+
editable.content = container.html()
|
223
|
+
|
224
|
+
$(container[0].attributes).each(function(){
|
225
|
+
editable.attrKeys.push(this.nodeName)
|
226
|
+
editable.attrValues.push(this.nodeValue)
|
227
|
+
})
|
228
|
+
|
229
|
+
// Set editor to blocked the original container
|
230
|
+
container.replaceWith(editor)
|
231
|
+
}
|
232
|
+
|
233
|
+
// Create the footer if savable
|
234
|
+
if (options.savable) {
|
235
|
+
var editorFooter = $('<div/>', {
|
236
|
+
'class': 'md-footer'
|
237
|
+
}),
|
238
|
+
saveHandler = 'cmdSave'
|
239
|
+
|
240
|
+
// Register handler and callback
|
241
|
+
handler.push(saveHandler)
|
242
|
+
callback.push(options.onSave)
|
243
|
+
|
244
|
+
editorFooter.append('<button class="btn btn-success" data-provider="'
|
245
|
+
+ns
|
246
|
+
+'" data-handler="'
|
247
|
+
+saveHandler
|
248
|
+
+'"><i class="icon icon-white icon-ok"></i> Save</button>')
|
249
|
+
|
250
|
+
editor.append(editorFooter)
|
251
|
+
}
|
252
|
+
|
253
|
+
// Set width/height
|
254
|
+
$.each(['height','width'],function(k,attr){
|
255
|
+
if (options[attr] != 'inherit') {
|
256
|
+
if (jQuery.isNumeric(options[attr])) {
|
257
|
+
editor.css(attr,options[attr]+'px')
|
258
|
+
} else {
|
259
|
+
editor.addClass(options[attr])
|
260
|
+
}
|
261
|
+
}
|
262
|
+
})
|
263
|
+
|
264
|
+
// Reference
|
265
|
+
this.$editor = editor
|
266
|
+
this.$textarea = textarea
|
267
|
+
this.$editable = editable
|
268
|
+
this.$oldContent = this.getContent()
|
269
|
+
|
270
|
+
this.__setListener()
|
271
|
+
|
272
|
+
// Set editor attributes, data short-hand API and listener
|
273
|
+
this.$editor.attr('id',(new Date).getTime())
|
274
|
+
this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this))
|
275
|
+
|
276
|
+
} else {
|
277
|
+
this.$editor.show()
|
278
|
+
}
|
279
|
+
|
280
|
+
if (options.autofocus) {
|
281
|
+
this.$textarea.focus()
|
282
|
+
this.$editor.addClass('active')
|
283
|
+
}
|
284
|
+
|
285
|
+
// Trigger the onShow hook
|
286
|
+
options.onShow(this)
|
287
|
+
|
288
|
+
return this
|
289
|
+
}
|
290
|
+
|
291
|
+
, showPreview: function() {
|
292
|
+
var options = this.$options,
|
293
|
+
callbackContent = options.onPreview(this), // Try to get the content from callback
|
294
|
+
container = this.$textarea,
|
295
|
+
replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}),
|
296
|
+
cloneEditor = this.$cloneEditor,
|
297
|
+
content
|
298
|
+
|
299
|
+
// Give flag that tell the editor enter preview mode
|
300
|
+
this.$isPreview = true
|
301
|
+
// Disable all buttons
|
302
|
+
this.disableButtons('all').enableButtons('cmdPreview')
|
303
|
+
|
304
|
+
// Save the editor
|
305
|
+
cloneEditor.el = container
|
306
|
+
cloneEditor.type = container.prop('tagName').toLowerCase()
|
307
|
+
cloneEditor.content = container.val()
|
308
|
+
|
309
|
+
$(container[0].attributes).each(function(){
|
310
|
+
cloneEditor.attrKeys.push(this.nodeName)
|
311
|
+
cloneEditor.attrValues.push(this.nodeValue)
|
312
|
+
})
|
313
|
+
|
314
|
+
this.$cloneEditor = cloneEditor
|
315
|
+
|
316
|
+
if (typeof callbackContent == 'string') {
|
317
|
+
// Set the content based by callback content
|
318
|
+
content = callbackContent
|
319
|
+
} else {
|
320
|
+
// Set the content
|
321
|
+
content = (typeof markdown == 'object') ? markdown.toHTML(container.val()) : container.val()
|
322
|
+
}
|
323
|
+
|
324
|
+
// Build preview element and replace the editor temporarily
|
325
|
+
replacementContainer.html(content)
|
326
|
+
container.replaceWith(replacementContainer)
|
327
|
+
|
328
|
+
// Attach the editor instances
|
329
|
+
replacementContainer.data('markdown',this)
|
330
|
+
|
331
|
+
return this
|
332
|
+
}
|
333
|
+
|
334
|
+
, hidePreview: function() {
|
335
|
+
// Give flag that tell the editor quit preview mode
|
336
|
+
this.$isPreview = false
|
337
|
+
|
338
|
+
// Build the original element
|
339
|
+
var container = this.$editor.find('div[data-provider="markdown-preview"]'),
|
340
|
+
cloneEditor = this.$cloneEditor,
|
341
|
+
oldElement = $('<'+cloneEditor.type+'/>')
|
342
|
+
|
343
|
+
$(cloneEditor.attrKeys).each(function(k,v) {
|
344
|
+
oldElement.attr(cloneEditor.attrKeys[k],cloneEditor.attrValues[k])
|
345
|
+
})
|
346
|
+
|
347
|
+
// Set the editor content
|
348
|
+
oldElement.val(cloneEditor.content)
|
349
|
+
|
350
|
+
// Set the editor data
|
351
|
+
container.replaceWith(oldElement)
|
352
|
+
|
353
|
+
// Enable all buttons
|
354
|
+
this.enableButtons('all')
|
355
|
+
|
356
|
+
// Back to the editor
|
357
|
+
this.$textarea = oldElement
|
358
|
+
this.__setListener()
|
359
|
+
|
360
|
+
return this
|
361
|
+
}
|
362
|
+
|
363
|
+
, isDirty: function() {
|
364
|
+
return this.$oldContent != this.getContent()
|
365
|
+
}
|
366
|
+
|
367
|
+
, getContent: function() {
|
368
|
+
return (this.$isPreview) ? this.$cloneEditor.content : this.$textarea.val()
|
369
|
+
}
|
370
|
+
|
371
|
+
, setContent: function(content) {
|
372
|
+
this.$textarea.val(content)
|
373
|
+
|
374
|
+
return this
|
375
|
+
}
|
376
|
+
|
377
|
+
, findSelection: function(chunk) {
|
378
|
+
var content = this.getContent(), startChunkPosition
|
379
|
+
|
380
|
+
if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
|
381
|
+
var oldSelection = this.getSelection(), selection
|
382
|
+
|
383
|
+
this.setSelection(startChunkPosition,startChunkPosition+chunk.length)
|
384
|
+
selection = this.getSelection()
|
385
|
+
|
386
|
+
this.setSelection(oldSelection.start,oldSelection.end)
|
387
|
+
|
388
|
+
return selection
|
389
|
+
} else {
|
390
|
+
return null
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
394
|
+
, getSelection: function() {
|
395
|
+
|
396
|
+
var e = this.$textarea[0]
|
397
|
+
|
398
|
+
return (
|
399
|
+
|
400
|
+
('selectionStart' in e && function() {
|
401
|
+
var l = e.selectionEnd - e.selectionStart
|
402
|
+
return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) }
|
403
|
+
}) ||
|
404
|
+
|
405
|
+
/* browser not supported */
|
406
|
+
function() {
|
407
|
+
return null
|
408
|
+
}
|
409
|
+
|
410
|
+
)()
|
411
|
+
|
412
|
+
}
|
413
|
+
|
414
|
+
, setSelection: function(start,end) {
|
415
|
+
|
416
|
+
var e = this.$textarea[0]
|
417
|
+
|
418
|
+
return (
|
419
|
+
|
420
|
+
('selectionStart' in e && function() {
|
421
|
+
e.selectionStart = start
|
422
|
+
e.selectionEnd = end
|
423
|
+
return
|
424
|
+
}) ||
|
425
|
+
|
426
|
+
/* browser not supported */
|
427
|
+
function() {
|
428
|
+
return null
|
429
|
+
}
|
430
|
+
|
431
|
+
)()
|
432
|
+
|
433
|
+
}
|
434
|
+
|
435
|
+
, replaceSelection: function(text) {
|
436
|
+
|
437
|
+
var e = this.$textarea[0]
|
438
|
+
|
439
|
+
return (
|
440
|
+
|
441
|
+
('selectionStart' in e && function() {
|
442
|
+
e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length)
|
443
|
+
// Set cursor to the last replacement end
|
444
|
+
e.selectionStart = e.value.length
|
445
|
+
return this
|
446
|
+
}) ||
|
447
|
+
|
448
|
+
/* browser not supported */
|
449
|
+
function() {
|
450
|
+
e.value += text
|
451
|
+
return jQuery(e)
|
452
|
+
}
|
453
|
+
|
454
|
+
)()
|
455
|
+
|
456
|
+
}
|
457
|
+
|
458
|
+
, getNextTab: function() {
|
459
|
+
// Shift the nextTab
|
460
|
+
if (this.$nextTab.length == 0) {
|
461
|
+
return null
|
462
|
+
} else {
|
463
|
+
var nextTab, tab = this.$nextTab.shift()
|
464
|
+
|
465
|
+
if (typeof tab == 'function') {
|
466
|
+
nextTab = tab()
|
467
|
+
} else if (typeof tab == 'object' && tab.length > 0) {
|
468
|
+
nextTab = tab
|
469
|
+
}
|
470
|
+
|
471
|
+
return nextTab
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
, setNextTab: function(start,end) {
|
476
|
+
// Push new selection into nextTab collections
|
477
|
+
if (typeof start == 'string') {
|
478
|
+
var that = this
|
479
|
+
this.$nextTab.push(function(){
|
480
|
+
return that.findSelection(start)
|
481
|
+
})
|
482
|
+
} else if (typeof start == 'numeric' && typeof end == 'numeric') {
|
483
|
+
var oldSelection = this.getSelection()
|
484
|
+
|
485
|
+
this.setSelection(start,end)
|
486
|
+
this.$nextTab.push(this.getSelection())
|
487
|
+
|
488
|
+
this.setSelection(oldSelection.start,oldSelection.end)
|
489
|
+
}
|
490
|
+
|
491
|
+
return
|
492
|
+
}
|
493
|
+
|
494
|
+
, enableButtons: function(name) {
|
495
|
+
var alter = function (el) {
|
496
|
+
el.removeAttr('disabled')
|
497
|
+
}
|
498
|
+
|
499
|
+
this.__alterButtons(name,alter)
|
500
|
+
|
501
|
+
return this
|
502
|
+
}
|
503
|
+
|
504
|
+
, disableButtons: function(name) {
|
505
|
+
var alter = function (el) {
|
506
|
+
el.attr('disabled','disabled')
|
507
|
+
}
|
508
|
+
|
509
|
+
this.__alterButtons(name,alter)
|
510
|
+
|
511
|
+
return this
|
512
|
+
}
|
513
|
+
|
514
|
+
, eventSupported: function(eventName) {
|
515
|
+
var isSupported = eventName in this.$element
|
516
|
+
if (!isSupported) {
|
517
|
+
this.$element.setAttribute(eventName, 'return;')
|
518
|
+
isSupported = typeof this.$element[eventName] === 'function'
|
519
|
+
}
|
520
|
+
return isSupported
|
521
|
+
}
|
522
|
+
|
523
|
+
, keydown: function (e) {
|
524
|
+
this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
|
525
|
+
this.keyup(e)
|
526
|
+
}
|
527
|
+
|
528
|
+
, keypress: function (e) {
|
529
|
+
if (this.suppressKeyPressRepeat) return
|
530
|
+
this.keyup(e)
|
531
|
+
}
|
532
|
+
|
533
|
+
, keyup: function (e) {
|
534
|
+
var blocked = false
|
535
|
+
switch(e.keyCode) {
|
536
|
+
case 40: // down arrow
|
537
|
+
case 38: // up arrow
|
538
|
+
case 16: // shift
|
539
|
+
case 17: // ctrl
|
540
|
+
case 18: // alt
|
541
|
+
break
|
542
|
+
|
543
|
+
case 9: // tab
|
544
|
+
var nextTab
|
545
|
+
if (nextTab = this.getNextTab(),nextTab != null) {
|
546
|
+
// Get the nextTab if exists
|
547
|
+
var that = this
|
548
|
+
setTimeout(function(){
|
549
|
+
that.setSelection(nextTab.start,nextTab.end)
|
550
|
+
},500)
|
551
|
+
} else {
|
552
|
+
// Put the cursor to the end
|
553
|
+
this.setSelection(this.getContent().length,this.getContent().length)
|
554
|
+
}
|
555
|
+
|
556
|
+
blocked = true
|
557
|
+
break
|
558
|
+
|
559
|
+
case 13: // enter
|
560
|
+
case 27: // escape
|
561
|
+
blocked = false
|
562
|
+
break
|
563
|
+
|
564
|
+
default:
|
565
|
+
blocked = false
|
566
|
+
}
|
567
|
+
|
568
|
+
if (blocked) {
|
569
|
+
e.stopPropagation()
|
570
|
+
e.preventDefault()
|
571
|
+
}
|
572
|
+
}
|
573
|
+
|
574
|
+
, focus: function (e) {
|
575
|
+
var options = this.$options,
|
576
|
+
isHideable = options.hideable,
|
577
|
+
editor = this.$editor
|
578
|
+
|
579
|
+
editor.addClass('active')
|
580
|
+
|
581
|
+
// Blur other markdown(s)
|
582
|
+
$(document).find('.md-editor').each(function(){
|
583
|
+
if ($(this).attr('id') != editor.attr('id')) {
|
584
|
+
var attachedMarkdown
|
585
|
+
|
586
|
+
if (attachedMarkdown = $(this).find('textarea').data('markdown'),
|
587
|
+
attachedMarkdown == null) {
|
588
|
+
attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
|
589
|
+
}
|
590
|
+
|
591
|
+
if (attachedMarkdown) {
|
592
|
+
attachedMarkdown.blur()
|
593
|
+
}
|
594
|
+
}
|
595
|
+
})
|
596
|
+
|
597
|
+
return this
|
598
|
+
}
|
599
|
+
|
600
|
+
, blur: function (e) {
|
601
|
+
var options = this.$options,
|
602
|
+
isHideable = options.hideable,
|
603
|
+
editor = this.$editor,
|
604
|
+
editable = this.$editable
|
605
|
+
|
606
|
+
// Force to quit preview mode
|
607
|
+
if (this.$isPreview) {
|
608
|
+
this.hidePreview()
|
609
|
+
}
|
610
|
+
|
611
|
+
if (editor.hasClass('active') || this.$element.parent().length == 0) {
|
612
|
+
editor.removeClass('active')
|
613
|
+
|
614
|
+
if (isHideable) {
|
615
|
+
|
616
|
+
// Check for editable elements
|
617
|
+
if (editable.el != null) {
|
618
|
+
// Build the original element
|
619
|
+
var oldElement = $('<'+editable.type+'/>'),
|
620
|
+
content = this.getContent(),
|
621
|
+
currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content
|
622
|
+
|
623
|
+
$(editable.attrKeys).each(function(k,v) {
|
624
|
+
oldElement.attr(editable.attrKeys[k],editable.attrValues[k])
|
625
|
+
})
|
626
|
+
|
627
|
+
// Get the editor content
|
628
|
+
oldElement.html(currentContent)
|
629
|
+
|
630
|
+
editor.replaceWith(oldElement)
|
631
|
+
} else {
|
632
|
+
editor.hide()
|
633
|
+
|
634
|
+
}
|
635
|
+
}
|
636
|
+
|
637
|
+
// Trigger the onBlur hook
|
638
|
+
options.onBlur(this)
|
639
|
+
}
|
640
|
+
|
641
|
+
return this
|
642
|
+
}
|
643
|
+
|
644
|
+
}
|
645
|
+
|
646
|
+
/* MARKDOWN PLUGIN DEFINITION
|
647
|
+
* ========================== */
|
648
|
+
|
649
|
+
var old = $.fn.markdown
|
650
|
+
|
651
|
+
$.fn.markdown = function (option) {
|
652
|
+
return this.each(function () {
|
653
|
+
var $this = $(this)
|
654
|
+
, data = $this.data('markdown')
|
655
|
+
, options = typeof option == 'object' && option
|
656
|
+
if (!data) $this.data('markdown', (data = new Markdown(this, options)))
|
657
|
+
})
|
658
|
+
}
|
659
|
+
|
660
|
+
$.fn.markdown.defaults = {
|
661
|
+
/* Editor Properties */
|
662
|
+
autofocus: false,
|
663
|
+
hideable: false,
|
664
|
+
savable:false,
|
665
|
+
width: 'inherit',
|
666
|
+
height: 'inherit',
|
667
|
+
|
668
|
+
/* Buttons Properties */
|
669
|
+
buttons: [
|
670
|
+
[{
|
671
|
+
name: 'groupFont',
|
672
|
+
data: [{
|
673
|
+
name: 'cmdBold',
|
674
|
+
title: 'Bold',
|
675
|
+
icon: 'icon icon-bold',
|
676
|
+
callback: function(e){
|
677
|
+
// Give/remove ** surround the selection
|
678
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
679
|
+
|
680
|
+
if (selected.length == 0) {
|
681
|
+
// Give extra word
|
682
|
+
chunk = 'strong text'
|
683
|
+
} else {
|
684
|
+
chunk = selected.text
|
685
|
+
}
|
686
|
+
|
687
|
+
// transform selection and set the cursor into chunked text
|
688
|
+
if (content.substr(selected.start-2,2) == '**'
|
689
|
+
&& content.substr(selected.end,2) == '**' ) {
|
690
|
+
e.setSelection(selected.start-2,selected.end+2)
|
691
|
+
e.replaceSelection(chunk)
|
692
|
+
cursor = selected.start-2
|
693
|
+
} else {
|
694
|
+
e.replaceSelection('**'+chunk+'**')
|
695
|
+
cursor = selected.start+2
|
696
|
+
}
|
697
|
+
|
698
|
+
// Set the cursor
|
699
|
+
e.setSelection(cursor,cursor+chunk.length)
|
700
|
+
}
|
701
|
+
},{
|
702
|
+
name: 'cmdItalic',
|
703
|
+
title: 'Italic',
|
704
|
+
icon: 'icon icon-italic',
|
705
|
+
callback: function(e){
|
706
|
+
// Give/remove * surround the selection
|
707
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
708
|
+
|
709
|
+
if (selected.length == 0) {
|
710
|
+
// Give extra word
|
711
|
+
chunk = 'emphasized text'
|
712
|
+
} else {
|
713
|
+
chunk = selected.text
|
714
|
+
}
|
715
|
+
|
716
|
+
// transform selection and set the cursor into chunked text
|
717
|
+
if (content.substr(selected.start-1,1) == '*'
|
718
|
+
&& content.substr(selected.end,1) == '*' ) {
|
719
|
+
e.setSelection(selected.start-1,selected.end+1)
|
720
|
+
e.replaceSelection(chunk)
|
721
|
+
cursor = selected.start-1
|
722
|
+
} else {
|
723
|
+
e.replaceSelection('*'+chunk+'*')
|
724
|
+
cursor = selected.start+1
|
725
|
+
}
|
726
|
+
|
727
|
+
// Set the cursor
|
728
|
+
e.setSelection(cursor,cursor+chunk.length)
|
729
|
+
}
|
730
|
+
},{
|
731
|
+
name: 'cmdHeading',
|
732
|
+
title: 'Heading',
|
733
|
+
icon: 'icon icon-font',
|
734
|
+
callback: function(e){
|
735
|
+
// Append/remove ### surround the selection
|
736
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar
|
737
|
+
|
738
|
+
if (selected.length == 0) {
|
739
|
+
// Give extra word
|
740
|
+
chunk = 'heading text'
|
741
|
+
} else {
|
742
|
+
chunk = selected.text
|
743
|
+
}
|
744
|
+
|
745
|
+
// transform selection and set the cursor into chunked text
|
746
|
+
if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ')
|
747
|
+
|| (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) {
|
748
|
+
e.setSelection(selected.start-pointer,selected.end)
|
749
|
+
e.replaceSelection(chunk)
|
750
|
+
cursor = selected.start-pointer
|
751
|
+
} else if (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n') {
|
752
|
+
e.replaceSelection('\n\n### '+chunk+'\n')
|
753
|
+
cursor = selected.start+6
|
754
|
+
} else {
|
755
|
+
// Empty string before element
|
756
|
+
e.replaceSelection('### '+chunk+'\n')
|
757
|
+
cursor = selected.start+4
|
758
|
+
}
|
759
|
+
|
760
|
+
// Set the cursor
|
761
|
+
e.setSelection(cursor,cursor+chunk.length)
|
762
|
+
}
|
763
|
+
}]
|
764
|
+
},{
|
765
|
+
name: 'groupLink',
|
766
|
+
data: [{
|
767
|
+
name: 'cmdUrl',
|
768
|
+
title: 'URL/Link',
|
769
|
+
icon: 'icon icon-globe',
|
770
|
+
callback: function(e){
|
771
|
+
// Give [] surround the selection and prepend the link
|
772
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
|
773
|
+
|
774
|
+
if (selected.length == 0) {
|
775
|
+
// Give extra word
|
776
|
+
chunk = 'enter link description here'
|
777
|
+
} else {
|
778
|
+
chunk = selected.text
|
779
|
+
}
|
780
|
+
|
781
|
+
link = prompt('Insert Hyperlink','http://')
|
782
|
+
|
783
|
+
if (link != null) {
|
784
|
+
// transform selection and set the cursor into chunked text
|
785
|
+
e.replaceSelection('['+chunk+']('+link+')')
|
786
|
+
cursor = selected.start+1
|
787
|
+
|
788
|
+
// Set the cursor
|
789
|
+
e.setSelection(cursor,cursor+chunk.length)
|
790
|
+
}
|
791
|
+
}
|
792
|
+
},{
|
793
|
+
name: 'cmdImage',
|
794
|
+
title: 'Image',
|
795
|
+
icon: 'icon icon-picture',
|
796
|
+
callback: function(e){
|
797
|
+
// Give ![] surround the selection and prepend the image link
|
798
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
|
799
|
+
|
800
|
+
if (selected.length == 0) {
|
801
|
+
// Give extra word
|
802
|
+
chunk = 'enter image description here'
|
803
|
+
} else {
|
804
|
+
chunk = selected.text
|
805
|
+
}
|
806
|
+
|
807
|
+
link = prompt('Insert Image Hyperlink','http://')
|
808
|
+
|
809
|
+
if (link != null) {
|
810
|
+
// transform selection and set the cursor into chunked text
|
811
|
+
e.replaceSelection('')
|
812
|
+
cursor = selected.start+2
|
813
|
+
|
814
|
+
// Set the next tab
|
815
|
+
e.setNextTab('enter image title here')
|
816
|
+
|
817
|
+
// Set the cursor
|
818
|
+
e.setSelection(cursor,cursor+chunk.length)
|
819
|
+
}
|
820
|
+
}
|
821
|
+
}]
|
822
|
+
},{
|
823
|
+
name: 'groupMisc',
|
824
|
+
data: [{
|
825
|
+
name: 'cmdList',
|
826
|
+
title: 'List',
|
827
|
+
icon: 'icon icon-list',
|
828
|
+
callback: function(e){
|
829
|
+
// Prepend/Give - surround the selection
|
830
|
+
var chunk, cursor, selected = e.getSelection(), content = e.getContent()
|
831
|
+
|
832
|
+
// transform selection and set the cursor into chunked text
|
833
|
+
if (selected.length == 0) {
|
834
|
+
// Give extra word
|
835
|
+
chunk = 'list text here'
|
836
|
+
|
837
|
+
e.replaceSelection('- '+chunk)
|
838
|
+
|
839
|
+
// Set the cursor
|
840
|
+
cursor = selected.start+2
|
841
|
+
} else {
|
842
|
+
if (selected.text.indexOf('\n') < 0) {
|
843
|
+
chunk = selected.text
|
844
|
+
|
845
|
+
e.replaceSelection('- '+chunk)
|
846
|
+
|
847
|
+
// Set the cursor
|
848
|
+
cursor = selected.start+2
|
849
|
+
} else {
|
850
|
+
var list = []
|
851
|
+
|
852
|
+
list = selected.text.split('\n')
|
853
|
+
chunk = list[0]
|
854
|
+
|
855
|
+
$.each(list,function(k,v) {
|
856
|
+
list[k] = '- '+v
|
857
|
+
})
|
858
|
+
|
859
|
+
e.replaceSelection('\n\n'+list.join('\n'))
|
860
|
+
|
861
|
+
// Set the cursor
|
862
|
+
cursor = selected.start+4
|
863
|
+
}
|
864
|
+
}
|
865
|
+
|
866
|
+
|
867
|
+
|
868
|
+
// Set the cursor
|
869
|
+
e.setSelection(cursor,cursor+chunk.length)
|
870
|
+
}
|
871
|
+
}]
|
872
|
+
},{
|
873
|
+
name: 'groupUtil',
|
874
|
+
data: [{
|
875
|
+
name: 'cmdPreview',
|
876
|
+
title: 'Preview',
|
877
|
+
btnText: 'Preview',
|
878
|
+
btnClass: 'btn btn-inverse',
|
879
|
+
icon: 'icon icon-white icon-search',
|
880
|
+
callback: function(e){
|
881
|
+
// Check the preview mode and toggle based on this flag
|
882
|
+
var isPreview = e.$isPreview,content
|
883
|
+
|
884
|
+
if (isPreview == false) {
|
885
|
+
// Give flag that tell the editor enter preview mode
|
886
|
+
e.showPreview()
|
887
|
+
} else {
|
888
|
+
e.hidePreview()
|
889
|
+
}
|
890
|
+
}
|
891
|
+
}]
|
892
|
+
}]
|
893
|
+
],
|
894
|
+
additionalButtons:[], // Place to hook more buttons by code
|
895
|
+
|
896
|
+
/* Events hook */
|
897
|
+
onShow: function (e) {},
|
898
|
+
onPreview: function (e) {},
|
899
|
+
onSave: function (e) {},
|
900
|
+
onBlur: function (e) {}
|
901
|
+
}
|
902
|
+
|
903
|
+
$.fn.markdown.Constructor = Markdown
|
904
|
+
|
905
|
+
|
906
|
+
/* MARKDOWN NO CONFLICT
|
907
|
+
* ==================== */
|
908
|
+
|
909
|
+
$.fn.markdown.noConflict = function () {
|
910
|
+
$.fn.markdown = old
|
911
|
+
return this
|
912
|
+
}
|
913
|
+
|
914
|
+
/* MARKDOWN GLOBAL FUNCTION & DATA-API
|
915
|
+
* ==================================== */
|
916
|
+
var initMarkdown = function(el) {
|
917
|
+
var $this = el
|
918
|
+
|
919
|
+
if ($this.data('markdown')) {
|
920
|
+
$this.data('markdown').showEditor()
|
921
|
+
return
|
922
|
+
}
|
923
|
+
$this.markdown($this.data())
|
924
|
+
}
|
925
|
+
|
926
|
+
var analyzeMarkdown = function(e) {
|
927
|
+
var blurred = false,
|
928
|
+
el,
|
929
|
+
$docEditor = $(e.currentTarget)
|
930
|
+
|
931
|
+
// Check whether it was editor childs or not
|
932
|
+
if ((e.type == 'focusin' || e.type == 'click') && $docEditor.length == 1 && typeof $docEditor[0] == 'object'){
|
933
|
+
el = $docEditor[0].activeElement
|
934
|
+
if ( ! $(el).data('markdown')) {
|
935
|
+
if (typeof $(el).parent().parent().parent().attr('class') == "undefined"
|
936
|
+
|| $(el).parent().parent().parent().attr('class').indexOf('md-editor') < 0) {
|
937
|
+
if ( typeof $(el).parent().parent().attr('class') == "undefined"
|
938
|
+
|| $(el).parent().parent().attr('class').indexOf('md-editor') < 0) {
|
939
|
+
|
940
|
+
blurred = true
|
941
|
+
}
|
942
|
+
} else {
|
943
|
+
blurred = false
|
944
|
+
}
|
945
|
+
}
|
946
|
+
|
947
|
+
|
948
|
+
if (blurred) {
|
949
|
+
// Blur event
|
950
|
+
$(document).find('.md-editor').each(function(){
|
951
|
+
var parentMd = $(el).parent()
|
952
|
+
|
953
|
+
if ($(this).attr('id') != parentMd.attr('id')) {
|
954
|
+
var attachedMarkdown
|
955
|
+
|
956
|
+
if (attachedMarkdown = $(this).find('textarea').data('markdown'),
|
957
|
+
attachedMarkdown == null) {
|
958
|
+
attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
|
959
|
+
}
|
960
|
+
|
961
|
+
if (attachedMarkdown) {
|
962
|
+
attachedMarkdown.blur()
|
963
|
+
}
|
964
|
+
}
|
965
|
+
})
|
966
|
+
}
|
967
|
+
|
968
|
+
e.stopPropagation()
|
969
|
+
}
|
970
|
+
}
|
971
|
+
|
972
|
+
$(document)
|
973
|
+
.on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) {
|
974
|
+
initMarkdown($(this))
|
975
|
+
e.preventDefault()
|
976
|
+
})
|
977
|
+
.on('click', function (e) {
|
978
|
+
analyzeMarkdown(e)
|
979
|
+
})
|
980
|
+
.on('focusin', function (e) {
|
981
|
+
analyzeMarkdown(e)
|
982
|
+
})
|
983
|
+
.ready(function(){
|
984
|
+
$('textarea[data-provide="markdown"]').each(function(){
|
985
|
+
initMarkdown($(this))
|
986
|
+
})
|
987
|
+
})
|
988
|
+
|
989
|
+
}(window.jQuery);
|