epic-editor-rails 0.2.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/epic-editor-rails/version.rb +1 -1
- data/vendor/assets/javascripts/epiceditor.js.erb +1137 -379
- data/vendor/assets/stylesheets/base/epiceditor.css +51 -12
- metadata +3 -6
- data/vendor/assets/images/edit.png +0 -0
- data/vendor/assets/images/fullscreen.png +0 -0
- data/vendor/assets/images/preview.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1f61c947de147d8aa143a1547e78afbbda43d34
|
4
|
+
data.tar.gz: d1b706acb83e9d1ef6d6b80581aab2baf7e719db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 880a40b82f1ca2b17837a92ababfcfa22d3b8d6c3ab91102bda439ad0d77ce01a04f56851673a84f92848c3b7eadc06bc0c31da535f2d1ddaaea31fb84c8ba6a
|
7
|
+
data.tar.gz: c51b928266245d803e523d96a9e565d61d4b84f4b4999c4e233fbc5952e4bbfa9323d90642a4c6760a5e110c717e8868e6ee8229769ec007cd06f7b989269634
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
epic-editor-rails v0.
|
1
|
+
epic-editor-rails v0.2.2
|
2
2
|
=================
|
3
3
|
Gemfile: ```gem 'epic-editor-rails'```
|
4
4
|
Install: ```$ bundle install ```
|
@@ -18,5 +18,5 @@ application.css.(scss):
|
|
18
18
|
@import 'editor/epic-light';
|
19
19
|
```
|
20
20
|
|
21
|
-
EpicEditor v0.2.
|
21
|
+
EpicEditor v0.2.2
|
22
22
|
http://epiceditor.com/
|
@@ -51,7 +51,7 @@
|
|
51
51
|
}
|
52
52
|
|
53
53
|
/**
|
54
|
-
* Saves the current style state for the styles requested, then
|
54
|
+
* Saves the current style state for the styles requested, then applies styles
|
55
55
|
* to overwrite the existing one. The old styles are returned as an object so
|
56
56
|
* you can pass it back in when you want to revert back to the old style
|
57
57
|
* @param {object} el The element to get the styles of
|
@@ -101,7 +101,7 @@
|
|
101
101
|
function _outerHeight(el) {
|
102
102
|
var b = parseInt(_getStyle(el, 'border-top-width'), 10) + parseInt(_getStyle(el, 'border-bottom-width'), 10)
|
103
103
|
, p = parseInt(_getStyle(el, 'padding-top'), 10) + parseInt(_getStyle(el, 'padding-bottom'), 10)
|
104
|
-
, w = el
|
104
|
+
, w = parseInt(_getStyle(el, 'height'), 10)
|
105
105
|
, t;
|
106
106
|
// For IE in case no border is set and it defaults to "medium"
|
107
107
|
if (isNaN(b)) { b = 0; }
|
@@ -165,27 +165,38 @@
|
|
165
165
|
}
|
166
166
|
|
167
167
|
function _setText(el, content) {
|
168
|
-
//
|
169
|
-
//
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
el.innerHTML = content;
|
185
|
-
}
|
168
|
+
// Don't convert lt/gt characters as HTML when viewing the editor window
|
169
|
+
// TODO: Write a test to catch regressions for this
|
170
|
+
content = content.replace(/</g, '<');
|
171
|
+
content = content.replace(/>/g, '>');
|
172
|
+
content = content.replace(/\n/g, '<br>');
|
173
|
+
|
174
|
+
// Make sure to there aren't two spaces in a row (replace one with )
|
175
|
+
// If you find and replace every space with a text will not wrap.
|
176
|
+
// Hence the name (Non-Breaking-SPace).
|
177
|
+
// TODO: Probably need to test this somehow...
|
178
|
+
content = content.replace(/<br>\s/g, '<br> ')
|
179
|
+
content = content.replace(/\s\s\s/g, ' ')
|
180
|
+
content = content.replace(/\s\s/g, ' ')
|
181
|
+
content = content.replace(/^ /, ' ')
|
182
|
+
|
183
|
+
el.innerHTML = content;
|
186
184
|
return true;
|
187
185
|
}
|
188
186
|
|
187
|
+
/**
|
188
|
+
* Converts the 'raw' format of a file's contents into plaintext
|
189
|
+
* @param {string} content Contents of the file
|
190
|
+
* @returns {string} the sanitized content
|
191
|
+
*/
|
192
|
+
function _sanitizeRawContent(content) {
|
193
|
+
// Get this, 2 spaces in a content editable actually converts to:
|
194
|
+
// 0020 00a0, meaning, "space no-break space". So, manually convert
|
195
|
+
// no-break spaces to spaces again before handing to marked.
|
196
|
+
// Also, WebKit converts no-break to unicode equivalent and FF HTML.
|
197
|
+
return content.replace(/\u00a0/g, ' ').replace(/ /g, ' ');
|
198
|
+
}
|
199
|
+
|
189
200
|
/**
|
190
201
|
* Will return the version number if the browser is IE. If not will return -1
|
191
202
|
* TRY NEVER TO USE THIS AND USE FEATURE DETECTION IF POSSIBLE
|
@@ -216,6 +227,16 @@
|
|
216
227
|
return n.userAgent.indexOf('Safari') > -1 && n.userAgent.indexOf('Chrome') == -1;
|
217
228
|
}
|
218
229
|
|
230
|
+
/**
|
231
|
+
* Same as the isIE(), but simply returns a boolean
|
232
|
+
* THIS IS TERRIBLE ONLY USE IF ABSOLUTELY NEEDED
|
233
|
+
* @returns {Boolean} true if Safari
|
234
|
+
*/
|
235
|
+
function _isFirefox() {
|
236
|
+
var n = window.navigator;
|
237
|
+
return n.userAgent.indexOf('Firefox') > -1 && n.userAgent.indexOf('Seamonkey') == -1;
|
238
|
+
}
|
239
|
+
|
219
240
|
/**
|
220
241
|
* Determines if supplied value is a function
|
221
242
|
* @param {object} object to determine type
|
@@ -305,7 +326,8 @@
|
|
305
326
|
, _defaultFileSchema
|
306
327
|
, _defaultFile
|
307
328
|
, defaults = { container: 'epiceditor'
|
308
|
-
, basePath: '
|
329
|
+
, basePath: ''
|
330
|
+
, textarea: undefined
|
309
331
|
, clientSideStorage: true
|
310
332
|
, localStorageName: 'epiceditor'
|
311
333
|
, useNativeFullscreen: true
|
@@ -322,11 +344,29 @@
|
|
322
344
|
, fullscreen: 70 // f keycode
|
323
345
|
, preview: 80 // p keycode
|
324
346
|
}
|
347
|
+
, string: { togglePreview: 'Toggle Preview Mode'
|
348
|
+
, toggleEdit: 'Toggle Edit Mode'
|
349
|
+
, toggleFullscreen: 'Enter Fullscreen'
|
350
|
+
}
|
325
351
|
, parser: typeof marked == 'function' ? marked : null
|
352
|
+
, autogrow: false
|
353
|
+
, button: { fullscreen: true
|
354
|
+
, preview: true
|
355
|
+
, bar: "auto"
|
356
|
+
}
|
326
357
|
}
|
327
|
-
, defaultStorage
|
358
|
+
, defaultStorage
|
359
|
+
, autogrowDefaults = { minHeight: 80
|
360
|
+
, maxHeight: false
|
361
|
+
, scroll: true
|
362
|
+
};
|
328
363
|
|
329
364
|
self.settings = _mergeObjs(true, defaults, opts);
|
365
|
+
|
366
|
+
var buttons = self.settings.button;
|
367
|
+
self._fullscreenEnabled = typeof(buttons) === 'object' ? typeof buttons.fullscreen === 'undefined' || buttons.fullscreen : buttons === true;
|
368
|
+
self._editEnabled = typeof(buttons) === 'object' ? typeof buttons.edit === 'undefined' || buttons.edit : buttons === true;
|
369
|
+
self._previewEnabled = typeof(buttons) === 'object' ? typeof buttons.preview === 'undefined' || buttons.preview : buttons === true;
|
330
370
|
|
331
371
|
if (!(typeof self.settings.parser == 'function' && typeof self.settings.parser('TEST') == 'string')) {
|
332
372
|
self.settings.parser = function (str) {
|
@@ -334,6 +374,29 @@
|
|
334
374
|
}
|
335
375
|
}
|
336
376
|
|
377
|
+
if (self.settings.autogrow) {
|
378
|
+
if (self.settings.autogrow === true) {
|
379
|
+
self.settings.autogrow = autogrowDefaults;
|
380
|
+
}
|
381
|
+
else {
|
382
|
+
self.settings.autogrow = _mergeObjs(true, autogrowDefaults, self.settings.autogrow);
|
383
|
+
}
|
384
|
+
self._oldHeight = -1;
|
385
|
+
}
|
386
|
+
|
387
|
+
// If you put an absolute link as the path of any of the themes ignore the basePath
|
388
|
+
// preview theme
|
389
|
+
if (!self.settings.theme.preview.match(/^https?:\/\//)) {
|
390
|
+
self.settings.theme.preview = self.settings.basePath + self.settings.theme.preview;
|
391
|
+
}
|
392
|
+
// editor theme
|
393
|
+
if (!self.settings.theme.editor.match(/^https?:\/\//)) {
|
394
|
+
self.settings.theme.editor = self.settings.basePath + self.settings.theme.editor;
|
395
|
+
}
|
396
|
+
// base theme
|
397
|
+
if (!self.settings.theme.base.match(/^https?:\/\//)) {
|
398
|
+
self.settings.theme.base = self.settings.basePath + self.settings.theme.base;
|
399
|
+
}
|
337
400
|
|
338
401
|
// Grab the container element and save it to self.element
|
339
402
|
// if it's a string assume it's an ID and if it's an object
|
@@ -366,6 +429,14 @@
|
|
366
429
|
}
|
367
430
|
}
|
368
431
|
|
432
|
+
if (self.settings.button.bar === "show") {
|
433
|
+
self.settings.button.bar = true;
|
434
|
+
}
|
435
|
+
|
436
|
+
if (self.settings.button.bar === "hide") {
|
437
|
+
self.settings.button.bar = false;
|
438
|
+
}
|
439
|
+
|
369
440
|
// Protect the id and overwrite if passed in as an option
|
370
441
|
// TODO: Put underscrore to denote that this is private
|
371
442
|
self._instanceId = 'epiceditor-' + Math.round(Math.random() * 100000);
|
@@ -384,7 +455,6 @@
|
|
384
455
|
if (localStorage && self.settings.clientSideStorage) {
|
385
456
|
this._storage = localStorage;
|
386
457
|
if (this._storage[self.settings.localStorageName] && self.getFiles(self.settings.file.name) === undefined) {
|
387
|
-
_defaultFile = self.getFiles(self.settings.file.name);
|
388
458
|
_defaultFile = self._defaultFileSchema();
|
389
459
|
_defaultFile.content = self.settings.file.defaultContent;
|
390
460
|
}
|
@@ -397,6 +467,11 @@
|
|
397
467
|
this._storage[self.settings.localStorageName] = defaultStorage;
|
398
468
|
}
|
399
469
|
|
470
|
+
// A string to prepend files with to save draft versions of files
|
471
|
+
// and reset all preview drafts on each load!
|
472
|
+
self._previewDraftLocation = '__draft-';
|
473
|
+
self._storage[self._previewDraftLocation + self.settings.localStorageName] = self._storage[self.settings.localStorageName];
|
474
|
+
|
400
475
|
// This needs to replace the use of classes to check the state of EE
|
401
476
|
self._eeState = {
|
402
477
|
fullscreen: false
|
@@ -437,20 +512,32 @@
|
|
437
512
|
, _elementStates
|
438
513
|
, _isInEdit
|
439
514
|
, nativeFs = false
|
515
|
+
, nativeFsWebkit = false
|
516
|
+
, nativeFsMoz = false
|
517
|
+
, nativeFsW3C = false
|
440
518
|
, fsElement
|
441
519
|
, isMod = false
|
442
520
|
, isCtrl = false
|
443
521
|
, eventableIframes
|
444
|
-
, i
|
522
|
+
, i // i is reused for loops
|
523
|
+
, boundAutogrow;
|
524
|
+
|
525
|
+
// Startup is a way to check if this EpicEditor is starting up. Useful for
|
526
|
+
// checking and doing certain things before EpicEditor emits a load event.
|
527
|
+
self._eeState.startup = true;
|
445
528
|
|
446
529
|
if (self.settings.useNativeFullscreen) {
|
447
|
-
|
530
|
+
nativeFsWebkit = document.body.webkitRequestFullScreen ? true : false;
|
531
|
+
nativeFsMoz = document.body.mozRequestFullScreen ? true : false;
|
532
|
+
nativeFsW3C = document.body.requestFullscreen ? true : false;
|
533
|
+
nativeFs = nativeFsWebkit || nativeFsMoz || nativeFsW3C;
|
448
534
|
}
|
449
535
|
|
450
536
|
// Fucking Safari's native fullscreen works terribly
|
451
537
|
// REMOVE THIS IF SAFARI 7 WORKS BETTER
|
452
538
|
if (_isSafari()) {
|
453
539
|
nativeFs = false;
|
540
|
+
nativeFsWebkit = false;
|
454
541
|
}
|
455
542
|
|
456
543
|
// It opens edit mode by default (for now);
|
@@ -468,14 +555,15 @@
|
|
468
555
|
'<iframe frameborder="0" id="epiceditor-editor-frame"></iframe>' +
|
469
556
|
'<iframe frameborder="0" id="epiceditor-previewer-frame"></iframe>' +
|
470
557
|
'<div id="epiceditor-utilbar">' +
|
471
|
-
'<
|
472
|
-
'<
|
473
|
-
'<
|
558
|
+
(self._previewEnabled ? '<button title="' + this.settings.string.togglePreview + '" class="epiceditor-toggle-btn epiceditor-toggle-preview-btn"></button> ' : '') +
|
559
|
+
(self._editEnabled ? '<button title="' + this.settings.string.toggleEdit + '" class="epiceditor-toggle-btn epiceditor-toggle-edit-btn"></button> ' : '') +
|
560
|
+
(self._fullscreenEnabled ? '<button title="' + this.settings.string.toggleFullscreen + '" class="epiceditor-fullscreen-btn"></button>' : '') +
|
474
561
|
'</div>' +
|
475
562
|
'</div>'
|
476
563
|
|
477
564
|
// The previewer is just an empty box for the generated HTML to go into
|
478
565
|
, previewer: '<div id="epiceditor-preview"></div>'
|
566
|
+
, editor: '<!doctype HTML>'
|
479
567
|
};
|
480
568
|
|
481
569
|
// Write an iframe and then select it for the editor
|
@@ -506,7 +594,7 @@
|
|
506
594
|
self.editorIframeDocument = _getIframeInnards(self.editorIframe);
|
507
595
|
self.editorIframeDocument.open();
|
508
596
|
// Need something for... you guessed it, Firefox
|
509
|
-
self.editorIframeDocument.write(
|
597
|
+
self.editorIframeDocument.write(_HtmlTemplates.editor);
|
510
598
|
self.editorIframeDocument.close();
|
511
599
|
|
512
600
|
// Setup the previewer iframe
|
@@ -535,6 +623,10 @@
|
|
535
623
|
// Add a relative style to the overall wrapper to keep CSS relative to the editor
|
536
624
|
self.iframe.getElementById('epiceditor-wrapper').style.position = 'relative';
|
537
625
|
|
626
|
+
// Set the position to relative so we hide them with left: -999999px
|
627
|
+
self.editorIframe.style.position = 'absolute';
|
628
|
+
self.previewerIframe.style.position = 'absolute';
|
629
|
+
|
538
630
|
// Now grab the editor and previewer for later use
|
539
631
|
self.editor = self.editorIframeDocument.body;
|
540
632
|
self.previewer = self.previewerIframeDocument.getElementById('epiceditor-preview');
|
@@ -545,7 +637,10 @@
|
|
545
637
|
self.iframe.body.style.height = this.element.offsetHeight + 'px';
|
546
638
|
|
547
639
|
// Should actually check what mode it's in!
|
548
|
-
|
640
|
+
self.previewerIframe.style.left = '-999999px';
|
641
|
+
|
642
|
+
// Keep long lines from being longer than the editor
|
643
|
+
this.editorIframeDocument.body.style.wordWrap = 'break-word';
|
549
644
|
|
550
645
|
// FIXME figure out why it needs +2 px
|
551
646
|
if (_isIE() > -1) {
|
@@ -560,23 +655,53 @@
|
|
560
655
|
// iframe's ready state == complete, then we can focus on the contenteditable
|
561
656
|
self.iframe.addEventListener('readystatechange', function () {
|
562
657
|
if (self.iframe.readyState == 'complete') {
|
563
|
-
self.
|
658
|
+
self.focus();
|
564
659
|
}
|
565
660
|
});
|
566
661
|
}
|
567
662
|
|
663
|
+
// Because IE scrolls the whole window to hash links, we need our own
|
664
|
+
// method of scrolling the iframe to an ID from clicking a hash
|
665
|
+
self.previewerIframeDocument.addEventListener('click', function (e) {
|
666
|
+
var el = e.target
|
667
|
+
, body = self.previewerIframeDocument.body;
|
668
|
+
if (el.nodeName == 'A') {
|
669
|
+
// Make sure the link is a hash and the link is local to the iframe
|
670
|
+
if (el.hash && el.hostname == window.location.hostname) {
|
671
|
+
// Prevent the whole window from scrolling
|
672
|
+
e.preventDefault();
|
673
|
+
// Prevent opening a new window
|
674
|
+
el.target = '_self';
|
675
|
+
// Scroll to the matching element, if an element exists
|
676
|
+
if (body.querySelector(el.hash)) {
|
677
|
+
body.scrollTop = body.querySelector(el.hash).offsetTop;
|
678
|
+
}
|
679
|
+
}
|
680
|
+
}
|
681
|
+
});
|
682
|
+
|
568
683
|
utilBtns = self.iframe.getElementById('epiceditor-utilbar');
|
569
684
|
|
685
|
+
// TODO: Move into fullscreen setup function (_setupFullscreen)
|
570
686
|
_elementStates = {}
|
571
687
|
self._goFullscreen = function (el) {
|
572
|
-
|
688
|
+
this._fixScrollbars('auto');
|
689
|
+
|
573
690
|
if (self.is('fullscreen')) {
|
574
691
|
self._exitFullscreen(el);
|
575
692
|
return;
|
576
693
|
}
|
577
694
|
|
578
695
|
if (nativeFs) {
|
579
|
-
|
696
|
+
if (nativeFsWebkit) {
|
697
|
+
el.webkitRequestFullScreen();
|
698
|
+
}
|
699
|
+
else if (nativeFsMoz) {
|
700
|
+
el.mozRequestFullScreen();
|
701
|
+
}
|
702
|
+
else if (nativeFsW3C) {
|
703
|
+
el.requestFullscreen();
|
704
|
+
}
|
580
705
|
}
|
581
706
|
|
582
707
|
_isInEdit = self.is('edit');
|
@@ -608,6 +733,8 @@
|
|
608
733
|
, 'cssFloat': 'left' // FF
|
609
734
|
, 'styleFloat': 'left' // Older IEs
|
610
735
|
, 'display': 'block'
|
736
|
+
, 'position': 'static'
|
737
|
+
, 'left': ''
|
611
738
|
});
|
612
739
|
|
613
740
|
// the previewer
|
@@ -618,6 +745,8 @@
|
|
618
745
|
, 'cssFloat': 'right' // FF
|
619
746
|
, 'styleFloat': 'right' // Older IEs
|
620
747
|
, 'display': 'block'
|
748
|
+
, 'position': 'static'
|
749
|
+
, 'left': ''
|
621
750
|
});
|
622
751
|
|
623
752
|
// Setup the containing element CSS for fullscreen
|
@@ -650,12 +779,14 @@
|
|
650
779
|
|
651
780
|
self.preview();
|
652
781
|
|
653
|
-
self.
|
782
|
+
self.focus();
|
654
783
|
|
655
784
|
self.emit('fullscreenenter');
|
656
785
|
};
|
657
786
|
|
658
787
|
self._exitFullscreen = function (el) {
|
788
|
+
this._fixScrollbars();
|
789
|
+
|
659
790
|
_saveStyleState(self.element, 'apply', _elementStates.element);
|
660
791
|
_saveStyleState(self.iframeElement, 'apply', _elementStates.iframeElement);
|
661
792
|
_saveStyleState(self.editorIframe, 'apply', _elementStates.editorIframe);
|
@@ -669,15 +800,26 @@
|
|
669
800
|
|
670
801
|
utilBtns.style.visibility = 'visible';
|
671
802
|
|
803
|
+
// Put the editor back in the right state
|
804
|
+
// TODO: This is ugly... how do we make this nicer?
|
805
|
+
// setting fullscreen to false here prevents the
|
806
|
+
// native fs callback from calling this function again
|
807
|
+
self._eeState.fullscreen = false;
|
808
|
+
|
672
809
|
if (!nativeFs) {
|
673
810
|
document.body.style.overflow = 'auto';
|
674
811
|
}
|
675
812
|
else {
|
676
|
-
|
813
|
+
if (nativeFsWebkit) {
|
814
|
+
document.webkitCancelFullScreen();
|
815
|
+
}
|
816
|
+
else if (nativeFsMoz) {
|
817
|
+
document.mozCancelFullScreen();
|
818
|
+
}
|
819
|
+
else if (nativeFsW3C) {
|
820
|
+
document.exitFullscreen();
|
821
|
+
}
|
677
822
|
}
|
678
|
-
// Put the editor back in the right state
|
679
|
-
// TODO: This is ugly... how do we make this nicer?
|
680
|
-
self._eeState.fullscreen = false;
|
681
823
|
|
682
824
|
if (_isInEdit) {
|
683
825
|
self.edit();
|
@@ -720,18 +862,35 @@
|
|
720
862
|
});
|
721
863
|
|
722
864
|
// Sets up the NATIVE fullscreen editor/previewer for WebKit
|
723
|
-
if (
|
724
|
-
|
725
|
-
if (!document.webkitIsFullScreen) {
|
865
|
+
if (nativeFsWebkit) {
|
866
|
+
document.addEventListener('webkitfullscreenchange', function () {
|
867
|
+
if (!document.webkitIsFullScreen && self._eeState.fullscreen) {
|
868
|
+
self._exitFullscreen(fsElement);
|
869
|
+
}
|
870
|
+
}, false);
|
871
|
+
}
|
872
|
+
else if (nativeFsMoz) {
|
873
|
+
document.addEventListener('mozfullscreenchange', function () {
|
874
|
+
if (!document.mozFullScreen && self._eeState.fullscreen) {
|
875
|
+
self._exitFullscreen(fsElement);
|
876
|
+
}
|
877
|
+
}, false);
|
878
|
+
}
|
879
|
+
else if (nativeFsW3C) {
|
880
|
+
document.addEventListener('fullscreenchange', function () {
|
881
|
+
if (document.fullscreenElement == null && self._eeState.fullscreen) {
|
726
882
|
self._exitFullscreen(fsElement);
|
727
883
|
}
|
728
884
|
}, false);
|
729
885
|
}
|
730
886
|
|
887
|
+
// TODO: Move utilBar stuff into a utilBar setup function (_setupUtilBar)
|
731
888
|
utilBar = self.iframe.getElementById('epiceditor-utilbar');
|
732
889
|
|
733
890
|
// Hide it at first until they move their mouse
|
734
|
-
|
891
|
+
if (self.settings.button.bar !== true) {
|
892
|
+
utilBar.style.display = 'none';
|
893
|
+
}
|
735
894
|
|
736
895
|
utilBar.addEventListener('mouseover', function () {
|
737
896
|
if (utilBarTimer) {
|
@@ -740,6 +899,9 @@
|
|
740
899
|
});
|
741
900
|
|
742
901
|
function utilBarHandler(e) {
|
902
|
+
if (self.settings.button.bar !== "auto") {
|
903
|
+
return;
|
904
|
+
}
|
743
905
|
// Here we check if the mouse has moves more than 5px in any direction before triggering the mousemove code
|
744
906
|
// we do this for 2 reasons:
|
745
907
|
// 1. On Mac OS X lion when you scroll and it does the iOS like "jump" when it hits the top/bottom of the page itll fire off
|
@@ -768,15 +930,15 @@
|
|
768
930
|
// Check for alt+p and make sure were not in fullscreen - default shortcut to switch to preview
|
769
931
|
if (isMod === true && e.keyCode == self.settings.shortcut.preview && !self.is('fullscreen')) {
|
770
932
|
e.preventDefault();
|
771
|
-
if (self.is('edit')) {
|
933
|
+
if (self.is('edit') && self._previewEnabled) {
|
772
934
|
self.preview();
|
773
935
|
}
|
774
|
-
else {
|
936
|
+
else if (self._editEnabled) {
|
775
937
|
self.edit();
|
776
938
|
}
|
777
939
|
}
|
778
940
|
// Check for alt+f - default shortcut to make editor fullscreen
|
779
|
-
if (isMod === true && e.keyCode == self.settings.shortcut.fullscreen) {
|
941
|
+
if (isMod === true && e.keyCode == self.settings.shortcut.fullscreen && self._fullscreenEnabled) {
|
780
942
|
e.preventDefault();
|
781
943
|
self._goFullscreen(fsElement);
|
782
944
|
}
|
@@ -812,6 +974,29 @@
|
|
812
974
|
if (e.keyCode == 17) { isCtrl = false }
|
813
975
|
}
|
814
976
|
|
977
|
+
function pasteHandler(e) {
|
978
|
+
var content;
|
979
|
+
if (e.clipboardData) {
|
980
|
+
//FF 22, Webkit, "standards"
|
981
|
+
e.preventDefault();
|
982
|
+
content = e.clipboardData.getData("text/plain");
|
983
|
+
self.editorIframeDocument.execCommand("insertText", false, content);
|
984
|
+
}
|
985
|
+
else if (window.clipboardData) {
|
986
|
+
//IE, "nasty"
|
987
|
+
e.preventDefault();
|
988
|
+
content = window.clipboardData.getData("Text");
|
989
|
+
content = content.replace(/</g, '<');
|
990
|
+
content = content.replace(/>/g, '>');
|
991
|
+
content = content.replace(/\n/g, '<br>');
|
992
|
+
content = content.replace(/\r/g, ''); //fuck you, ie!
|
993
|
+
content = content.replace(/<br>\s/g, '<br> ')
|
994
|
+
content = content.replace(/\s\s\s/g, ' ')
|
995
|
+
content = content.replace(/\s\s/g, ' ')
|
996
|
+
self.editorIframeDocument.selection.createRange().pasteHTML(content);
|
997
|
+
}
|
998
|
+
}
|
999
|
+
|
815
1000
|
// Hide and show the util bar based on mouse movements
|
816
1001
|
eventableIframes = [self.previewerIframeDocument, self.editorIframeDocument];
|
817
1002
|
|
@@ -828,22 +1013,32 @@
|
|
828
1013
|
eventableIframes[i].addEventListener('keydown', function (e) {
|
829
1014
|
shortcutHandler(e);
|
830
1015
|
});
|
1016
|
+
eventableIframes[i].addEventListener('paste', function (e) {
|
1017
|
+
pasteHandler(e);
|
1018
|
+
});
|
831
1019
|
}
|
832
1020
|
|
833
1021
|
// Save the document every 100ms by default
|
1022
|
+
// TODO: Move into autosave setup function (_setupAutoSave)
|
834
1023
|
if (self.settings.file.autoSave) {
|
835
|
-
self.
|
1024
|
+
self._saveIntervalTimer = window.setInterval(function () {
|
836
1025
|
if (!self._canSave) {
|
837
1026
|
return;
|
838
1027
|
}
|
839
|
-
self.save();
|
1028
|
+
self.save(false, true);
|
840
1029
|
}, self.settings.file.autoSave);
|
841
1030
|
}
|
842
1031
|
|
1032
|
+
// Update a textarea automatically if a textarea is given so you don't need
|
1033
|
+
// AJAX to submit a form and instead fall back to normal form behavior
|
1034
|
+
if (self.settings.textarea) {
|
1035
|
+
self._setupTextareaSync();
|
1036
|
+
}
|
1037
|
+
|
843
1038
|
window.addEventListener('resize', function () {
|
844
1039
|
// If NOT webkit, and in fullscreen, we need to account for browser resizing
|
845
1040
|
// we don't care about webkit because you can't resize in webkit's fullscreen
|
846
|
-
if (
|
1041
|
+
if (self.is('fullscreen')) {
|
847
1042
|
_applyStyles(self.iframeElement, {
|
848
1043
|
'width': window.outerWidth + 'px'
|
849
1044
|
, 'height': window.innerHeight + 'px'
|
@@ -881,12 +1076,118 @@
|
|
881
1076
|
}
|
882
1077
|
|
883
1078
|
self.iframe.close();
|
1079
|
+
self._eeState.startup = false;
|
1080
|
+
|
1081
|
+
if (self.settings.autogrow) {
|
1082
|
+
self._fixScrollbars();
|
1083
|
+
|
1084
|
+
boundAutogrow = function () {
|
1085
|
+
setTimeout(function () {
|
1086
|
+
self._autogrow();
|
1087
|
+
}, 1);
|
1088
|
+
};
|
1089
|
+
|
1090
|
+
//for if autosave is disabled or very slow
|
1091
|
+
['keydown', 'keyup', 'paste', 'cut'].forEach(function (ev) {
|
1092
|
+
self.getElement('editor').addEventListener(ev, boundAutogrow);
|
1093
|
+
});
|
1094
|
+
|
1095
|
+
self.on('__update', boundAutogrow);
|
1096
|
+
self.on('edit', function () {
|
1097
|
+
setTimeout(boundAutogrow, 50)
|
1098
|
+
});
|
1099
|
+
self.on('preview', function () {
|
1100
|
+
setTimeout(boundAutogrow, 50)
|
1101
|
+
});
|
1102
|
+
|
1103
|
+
//for browsers that have rendering delays
|
1104
|
+
setTimeout(boundAutogrow, 50);
|
1105
|
+
boundAutogrow();
|
1106
|
+
}
|
1107
|
+
|
884
1108
|
// The callback and call are the same thing, but different ways to access them
|
885
1109
|
callback.call(this);
|
886
1110
|
this.emit('load');
|
887
1111
|
return this;
|
888
1112
|
}
|
889
1113
|
|
1114
|
+
EpicEditor.prototype._setupTextareaSync = function () {
|
1115
|
+
var self = this
|
1116
|
+
, textareaFileName = self.settings.file.name
|
1117
|
+
, _syncTextarea;
|
1118
|
+
|
1119
|
+
// Even if autoSave is false, we want to make sure to keep the textarea synced
|
1120
|
+
// with the editor's content. One bad thing about this tho is that we're
|
1121
|
+
// creating two timers now in some configurations. We keep the textarea synced
|
1122
|
+
// by saving and opening the textarea content from the draft file storage.
|
1123
|
+
self._textareaSaveTimer = window.setInterval(function () {
|
1124
|
+
if (!self._canSave) {
|
1125
|
+
return;
|
1126
|
+
}
|
1127
|
+
self.save(true);
|
1128
|
+
}, 100);
|
1129
|
+
|
1130
|
+
_syncTextarea = function () {
|
1131
|
+
// TODO: Figure out root cause for having to do this ||.
|
1132
|
+
// This only happens for draft files. Probably has something to do with
|
1133
|
+
// the fact draft files haven't been saved by the time this is called.
|
1134
|
+
// TODO: Add test for this case.
|
1135
|
+
self._textareaElement.value = self.exportFile(textareaFileName, 'text', true) || self.settings.file.defaultContent;
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
if (typeof self.settings.textarea == 'string') {
|
1139
|
+
self._textareaElement = document.getElementById(self.settings.textarea);
|
1140
|
+
}
|
1141
|
+
else if (typeof self.settings.textarea == 'object') {
|
1142
|
+
self._textareaElement = self.settings.textarea;
|
1143
|
+
}
|
1144
|
+
|
1145
|
+
// On page load, if there's content in the textarea that means one of two
|
1146
|
+
// different things:
|
1147
|
+
//
|
1148
|
+
// 1. The editor didn't load and the user was writing in the textarea and
|
1149
|
+
// now he refreshed the page or the JS loaded and the textarea now has
|
1150
|
+
// content. If this is the case the user probably expects his content is
|
1151
|
+
// moved into the editor and not lose what he typed.
|
1152
|
+
//
|
1153
|
+
// 2. The developer put content in the textarea from some server side
|
1154
|
+
// code. In this case, the textarea will take precedence.
|
1155
|
+
//
|
1156
|
+
// If the developer wants drafts to be recoverable they should check if
|
1157
|
+
// the local file in localStorage's modified date is newer than the server.
|
1158
|
+
if (self._textareaElement.value !== '') {
|
1159
|
+
self.importFile(textareaFileName, self._textareaElement.value);
|
1160
|
+
|
1161
|
+
// manually save draft after import so there is no delay between the
|
1162
|
+
// import and exporting in _syncTextarea. Without this, _syncTextarea
|
1163
|
+
// will pull the saved data from localStorage which will be <=100ms old.
|
1164
|
+
self.save(true);
|
1165
|
+
}
|
1166
|
+
|
1167
|
+
// Update the textarea on load and pull from drafts
|
1168
|
+
_syncTextarea();
|
1169
|
+
|
1170
|
+
// Make sure to keep it updated
|
1171
|
+
self.on('__update', _syncTextarea);
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
/**
|
1175
|
+
* Will NOT focus the editor if the editor is still starting up AND
|
1176
|
+
* focusOnLoad is set to false. This allows you to place this in code that
|
1177
|
+
* gets fired during .load() without worrying about it overriding the user's
|
1178
|
+
* option. For example use cases see preview() and edit().
|
1179
|
+
* @returns {undefined}
|
1180
|
+
*/
|
1181
|
+
|
1182
|
+
// Prevent focus when the user sets focusOnLoad to false by checking if the
|
1183
|
+
// editor is starting up AND if focusOnLoad is true
|
1184
|
+
EpicEditor.prototype._focusExceptOnLoad = function () {
|
1185
|
+
var self = this;
|
1186
|
+
if ((self._eeState.startup && self.settings.focusOnLoad) || !self._eeState.startup) {
|
1187
|
+
self.focus();
|
1188
|
+
}
|
1189
|
+
}
|
1190
|
+
|
890
1191
|
/**
|
891
1192
|
* Will remove the editor, but not offline files
|
892
1193
|
* @returns {object} EpicEditor will be returned
|
@@ -905,11 +1206,19 @@
|
|
905
1206
|
self._eeState.loaded = false;
|
906
1207
|
self._eeState.unloaded = true;
|
907
1208
|
callback = callback || function () {};
|
908
|
-
|
909
|
-
if (self.
|
910
|
-
|
1209
|
+
|
1210
|
+
if (self.settings.textarea) {
|
1211
|
+
self._textareaElement.value = "";
|
1212
|
+
self.removeListener('__update');
|
911
1213
|
}
|
912
|
-
|
1214
|
+
|
1215
|
+
if (self._saveIntervalTimer) {
|
1216
|
+
window.clearInterval(self._saveIntervalTimer);
|
1217
|
+
}
|
1218
|
+
if (self._textareaSaveTimer) {
|
1219
|
+
window.clearInterval(self._textareaSaveTimer);
|
1220
|
+
}
|
1221
|
+
|
913
1222
|
callback.call(this);
|
914
1223
|
self.emit('unload');
|
915
1224
|
return self;
|
@@ -919,47 +1228,65 @@
|
|
919
1228
|
* reflow allows you to dynamically re-fit the editor in the parent without
|
920
1229
|
* having to unload and then reload the editor again.
|
921
1230
|
*
|
922
|
-
*
|
923
|
-
*
|
1231
|
+
* reflow will also emit a `reflow` event and will return the new dimensions.
|
1232
|
+
* If it's called without params it'll return the new width and height and if
|
1233
|
+
* it's called with just width or just height it'll just return the width or
|
1234
|
+
* height. It's returned as an object like: { width: '100px', height: '1px' }
|
924
1235
|
*
|
1236
|
+
* @param {string|null} kind Can either be 'width' or 'height' or null
|
1237
|
+
* if null, both the height and width will be resized
|
1238
|
+
* @param {function} callback A function to fire after the reflow is finished.
|
1239
|
+
* Will return the width / height in an obj as the first param of the callback.
|
925
1240
|
* @returns {object} EpicEditor will be returned
|
926
1241
|
*/
|
927
|
-
EpicEditor.prototype.reflow = function (kind) {
|
1242
|
+
EpicEditor.prototype.reflow = function (kind, callback) {
|
928
1243
|
var self = this
|
929
1244
|
, widthDiff = _outerWidth(self.element) - self.element.offsetWidth
|
930
1245
|
, heightDiff = _outerHeight(self.element) - self.element.offsetHeight
|
931
1246
|
, elements = [self.iframeElement, self.editorIframe, self.previewerIframe]
|
1247
|
+
, eventData = {}
|
932
1248
|
, newWidth
|
933
1249
|
, newHeight;
|
934
1250
|
|
1251
|
+
if (typeof kind == 'function') {
|
1252
|
+
callback = kind;
|
1253
|
+
kind = null;
|
1254
|
+
}
|
1255
|
+
|
1256
|
+
if (!callback) {
|
1257
|
+
callback = function () {};
|
1258
|
+
}
|
935
1259
|
|
936
1260
|
for (var x = 0; x < elements.length; x++) {
|
937
1261
|
if (!kind || kind == 'width') {
|
938
1262
|
newWidth = self.element.offsetWidth - widthDiff + 'px';
|
939
1263
|
elements[x].style.width = newWidth;
|
940
1264
|
self._eeState.reflowWidth = newWidth;
|
1265
|
+
eventData.width = newWidth;
|
941
1266
|
}
|
942
1267
|
if (!kind || kind == 'height') {
|
943
1268
|
newHeight = self.element.offsetHeight - heightDiff + 'px';
|
944
1269
|
elements[x].style.height = newHeight;
|
945
1270
|
self._eeState.reflowHeight = newHeight
|
1271
|
+
eventData.height = newHeight;
|
946
1272
|
}
|
947
1273
|
}
|
1274
|
+
|
1275
|
+
self.emit('reflow', eventData);
|
1276
|
+
callback.call(this, eventData);
|
948
1277
|
return self;
|
949
1278
|
}
|
950
1279
|
|
951
1280
|
/**
|
952
1281
|
* Will take the markdown and generate a preview view based on the theme
|
953
|
-
* @param {string} theme The path to the theme you want to preview in
|
954
1282
|
* @returns {object} EpicEditor will be returned
|
955
1283
|
*/
|
956
|
-
EpicEditor.prototype.preview = function (
|
1284
|
+
EpicEditor.prototype.preview = function () {
|
957
1285
|
var self = this
|
958
1286
|
, x
|
1287
|
+
, theme = self.settings.theme.preview
|
959
1288
|
, anchors;
|
960
1289
|
|
961
|
-
theme = theme || self.settings.basePath + self.settings.theme.preview;
|
962
|
-
|
963
1290
|
_replaceClass(self.getElement('wrapper'), 'epiceditor-edit-mode', 'epiceditor-preview-mode');
|
964
1291
|
|
965
1292
|
// Check if no CSS theme link exists
|
@@ -970,33 +1297,45 @@
|
|
970
1297
|
self.previewerIframeDocument.getElementById('theme').href = theme;
|
971
1298
|
}
|
972
1299
|
|
973
|
-
//
|
974
|
-
self.
|
1300
|
+
// Save a preview draft since it might not be saved to the real file yet
|
1301
|
+
self.save(true);
|
975
1302
|
|
976
|
-
//
|
977
|
-
|
978
|
-
anchors = self.previewer.getElementsByTagName('a');
|
979
|
-
for (x in anchors) {
|
980
|
-
// If the link is a hash AND the links hostname is the same as the
|
981
|
-
// current window's hostname (same page) then set the target to self
|
982
|
-
if (anchors[x].hash && anchors[x].hostname == window.location.hostname) {
|
983
|
-
anchors[x].target = '_self';
|
984
|
-
}
|
985
|
-
}
|
1303
|
+
// Add the generated draft HTML into the previewer
|
1304
|
+
self.previewer.innerHTML = self.exportFile(null, 'html', true);
|
986
1305
|
|
987
1306
|
// Hide the editor and display the previewer
|
988
1307
|
if (!self.is('fullscreen')) {
|
989
|
-
self.editorIframe.style.
|
990
|
-
self.previewerIframe.style.
|
1308
|
+
self.editorIframe.style.left = '-999999px';
|
1309
|
+
self.previewerIframe.style.left = '';
|
991
1310
|
self._eeState.preview = true;
|
992
1311
|
self._eeState.edit = false;
|
993
|
-
self.
|
1312
|
+
self._focusExceptOnLoad();
|
994
1313
|
}
|
995
|
-
|
1314
|
+
|
996
1315
|
self.emit('preview');
|
997
1316
|
return self;
|
998
1317
|
}
|
999
1318
|
|
1319
|
+
/**
|
1320
|
+
* Helper to focus on the editor iframe. Will figure out which iframe to
|
1321
|
+
* focus on based on which one is active and will handle the cross browser
|
1322
|
+
* issues with focusing on the iframe vs the document body.
|
1323
|
+
* @returns {object} EpicEditor will be returned
|
1324
|
+
*/
|
1325
|
+
EpicEditor.prototype.focus = function (pageload) {
|
1326
|
+
var self = this
|
1327
|
+
, isPreview = self.is('preview')
|
1328
|
+
, focusElement = isPreview ? self.previewerIframeDocument.body
|
1329
|
+
: self.editorIframeDocument.body;
|
1330
|
+
|
1331
|
+
if (_isFirefox() && isPreview) {
|
1332
|
+
focusElement = self.previewerIframe;
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
focusElement.focus();
|
1336
|
+
return this;
|
1337
|
+
}
|
1338
|
+
|
1000
1339
|
/**
|
1001
1340
|
* Puts the editor into fullscreen mode
|
1002
1341
|
* @returns {object} EpicEditor will be returned
|
@@ -1026,9 +1365,9 @@
|
|
1026
1365
|
_replaceClass(self.getElement('wrapper'), 'epiceditor-preview-mode', 'epiceditor-edit-mode');
|
1027
1366
|
self._eeState.preview = false;
|
1028
1367
|
self._eeState.edit = true;
|
1029
|
-
self.editorIframe.style.
|
1030
|
-
self.previewerIframe.style.
|
1031
|
-
self.
|
1368
|
+
self.editorIframe.style.left = '';
|
1369
|
+
self.previewerIframe.style.left = '-999999px';
|
1370
|
+
self._focusExceptOnLoad();
|
1032
1371
|
self.emit('edit');
|
1033
1372
|
return this;
|
1034
1373
|
}
|
@@ -1077,6 +1416,10 @@
|
|
1077
1416
|
return self._eeState.edit;
|
1078
1417
|
case 'fullscreen':
|
1079
1418
|
return self._eeState.fullscreen;
|
1419
|
+
// TODO: This "works", but the tests are saying otherwise. Come back to this
|
1420
|
+
// and figure out how to fix it.
|
1421
|
+
// case 'focused':
|
1422
|
+
// return document.activeElement == self.iframeElement;
|
1080
1423
|
default:
|
1081
1424
|
return false;
|
1082
1425
|
}
|
@@ -1094,9 +1437,9 @@
|
|
1094
1437
|
name = name || self.settings.file.name;
|
1095
1438
|
self.settings.file.name = name;
|
1096
1439
|
if (this._storage[self.settings.localStorageName]) {
|
1097
|
-
fileObj = self.
|
1098
|
-
if (fileObj
|
1099
|
-
_setText(self.editor, fileObj
|
1440
|
+
fileObj = self.exportFile(name);
|
1441
|
+
if (fileObj !== undefined) {
|
1442
|
+
_setText(self.editor, fileObj);
|
1100
1443
|
self.emit('read');
|
1101
1444
|
}
|
1102
1445
|
else {
|
@@ -1114,40 +1457,62 @@
|
|
1114
1457
|
* Saves content for offline use
|
1115
1458
|
* @returns {object} EpicEditor will be returned
|
1116
1459
|
*/
|
1117
|
-
EpicEditor.prototype.save = function () {
|
1460
|
+
EpicEditor.prototype.save = function (_isPreviewDraft, _isAuto) {
|
1118
1461
|
var self = this
|
1119
1462
|
, storage
|
1120
1463
|
, isUpdate = false
|
1121
1464
|
, file = self.settings.file.name
|
1465
|
+
, previewDraftName = ''
|
1466
|
+
, data = this._storage[previewDraftName + self.settings.localStorageName]
|
1122
1467
|
, content = _getText(this.editor);
|
1123
1468
|
|
1469
|
+
if (_isPreviewDraft) {
|
1470
|
+
previewDraftName = self._previewDraftLocation;
|
1471
|
+
}
|
1472
|
+
|
1124
1473
|
// This could have been false but since we're manually saving
|
1125
1474
|
// we know it's save to start autoSaving again
|
1126
1475
|
this._canSave = true;
|
1127
1476
|
|
1128
|
-
storage
|
1477
|
+
// Guard against storage being wiped out without EpicEditor knowing
|
1478
|
+
// TODO: Emit saving error - storage seems to have been wiped
|
1479
|
+
if (data) {
|
1480
|
+
storage = JSON.parse(this._storage[previewDraftName + self.settings.localStorageName]);
|
1129
1481
|
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1482
|
+
// If the file doesn't exist we need to create it
|
1483
|
+
if (storage[file] === undefined) {
|
1484
|
+
storage[file] = self._defaultFileSchema();
|
1485
|
+
}
|
1134
1486
|
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1487
|
+
// If it does, we need to check if the content is different and
|
1488
|
+
// if it is, send the update event and update the timestamp
|
1489
|
+
else if (content !== storage[file].content) {
|
1490
|
+
storage[file].modified = new Date();
|
1491
|
+
isUpdate = true;
|
1492
|
+
}
|
1493
|
+
//don't bother autosaving if the content hasn't actually changed
|
1494
|
+
else if (_isAuto) {
|
1495
|
+
return;
|
1496
|
+
}
|
1141
1497
|
|
1142
|
-
|
1143
|
-
|
1498
|
+
storage[file].content = content;
|
1499
|
+
this._storage[previewDraftName + self.settings.localStorageName] = JSON.stringify(storage);
|
1144
1500
|
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1501
|
+
// After the content is actually changed, emit update so it emits the updated content
|
1502
|
+
if (isUpdate) {
|
1503
|
+
self.emit('update');
|
1504
|
+
// Emit a private update event so it can't get accidentally removed
|
1505
|
+
self.emit('__update');
|
1506
|
+
}
|
1507
|
+
|
1508
|
+
if (_isAuto) {
|
1509
|
+
this.emit('autosave');
|
1510
|
+
}
|
1511
|
+
else if (!_isPreviewDraft) {
|
1512
|
+
this.emit('save');
|
1513
|
+
}
|
1148
1514
|
}
|
1149
1515
|
|
1150
|
-
this.emit('save');
|
1151
1516
|
return this;
|
1152
1517
|
}
|
1153
1518
|
|
@@ -1224,16 +1589,43 @@
|
|
1224
1589
|
self.preview();
|
1225
1590
|
}
|
1226
1591
|
|
1592
|
+
//firefox has trouble with importing and working out the size right away
|
1593
|
+
if (self.settings.autogrow) {
|
1594
|
+
setTimeout(function () {
|
1595
|
+
self._autogrow();
|
1596
|
+
}, 50);
|
1597
|
+
}
|
1598
|
+
|
1227
1599
|
return this;
|
1228
1600
|
};
|
1229
1601
|
|
1602
|
+
/**
|
1603
|
+
* Gets the local filestore
|
1604
|
+
* @param {string} name Name of the file in the store
|
1605
|
+
* @returns {object|undefined} the local filestore, or a specific file in the store, if a name is given
|
1606
|
+
*/
|
1607
|
+
EpicEditor.prototype._getFileStore = function (name, _isPreviewDraft) {
|
1608
|
+
var previewDraftName = ''
|
1609
|
+
, store;
|
1610
|
+
if (_isPreviewDraft) {
|
1611
|
+
previewDraftName = this._previewDraftLocation;
|
1612
|
+
}
|
1613
|
+
store = JSON.parse(this._storage[previewDraftName + this.settings.localStorageName]);
|
1614
|
+
if (name) {
|
1615
|
+
return store[name];
|
1616
|
+
}
|
1617
|
+
else {
|
1618
|
+
return store;
|
1619
|
+
}
|
1620
|
+
}
|
1621
|
+
|
1230
1622
|
/**
|
1231
1623
|
* Exports a file as a string in a supported format
|
1232
1624
|
* @param {string} name Name of the file you want to export (case sensitive)
|
1233
|
-
* @param {string} kind Kind of file you want the content in (currently supports html and text)
|
1625
|
+
* @param {string} kind Kind of file you want the content in (currently supports html and text, default is the format the browser "wants")
|
1234
1626
|
* @returns {string|undefined} The content of the file in the content given or undefined if it doesn't exist
|
1235
1627
|
*/
|
1236
|
-
EpicEditor.prototype.exportFile = function (name, kind) {
|
1628
|
+
EpicEditor.prototype.exportFile = function (name, kind, _isPreviewDraft) {
|
1237
1629
|
var self = this
|
1238
1630
|
, file
|
1239
1631
|
, content;
|
@@ -1241,7 +1633,7 @@
|
|
1241
1633
|
name = name || self.settings.file.name;
|
1242
1634
|
kind = kind || 'text';
|
1243
1635
|
|
1244
|
-
file = self.
|
1636
|
+
file = self._getFileStore(name, _isPreviewDraft);
|
1245
1637
|
|
1246
1638
|
// If the file doesn't exist just return early with undefined
|
1247
1639
|
if (file === undefined) {
|
@@ -1252,27 +1644,53 @@
|
|
1252
1644
|
|
1253
1645
|
switch (kind) {
|
1254
1646
|
case 'html':
|
1255
|
-
|
1256
|
-
// 0020 00a0, meaning, "space no-break space". So, manually convert
|
1257
|
-
// no-break spaces to spaces again before handing to marked.
|
1258
|
-
// Also, WebKit converts no-break to unicode equivalent and FF HTML.
|
1259
|
-
content = content.replace(/\u00a0/g, ' ').replace(/ /g, ' ');
|
1647
|
+
content = _sanitizeRawContent(content);
|
1260
1648
|
return self.settings.parser(content);
|
1261
1649
|
case 'text':
|
1262
|
-
|
1650
|
+
return _sanitizeRawContent(content);
|
1651
|
+
case 'json':
|
1652
|
+
file.content = _sanitizeRawContent(file.content);
|
1653
|
+
return JSON.stringify(file);
|
1654
|
+
case 'raw':
|
1263
1655
|
return content;
|
1264
1656
|
default:
|
1265
1657
|
return content;
|
1266
1658
|
}
|
1267
1659
|
}
|
1268
1660
|
|
1269
|
-
|
1270
|
-
|
1661
|
+
/**
|
1662
|
+
* Gets the contents and metadata for files
|
1663
|
+
* @param {string} name Name of the file whose data you want (case sensitive)
|
1664
|
+
* @param {boolean} excludeContent whether the contents of files should be excluded
|
1665
|
+
* @returns {object} An object with the names and data of every file, or just the data of one file if a name was given
|
1666
|
+
*/
|
1667
|
+
EpicEditor.prototype.getFiles = function (name, excludeContent) {
|
1668
|
+
var file
|
1669
|
+
, data = this._getFileStore(name);
|
1670
|
+
|
1271
1671
|
if (name) {
|
1272
|
-
|
1672
|
+
if (data !== undefined) {
|
1673
|
+
if (excludeContent) {
|
1674
|
+
delete data.content;
|
1675
|
+
}
|
1676
|
+
else {
|
1677
|
+
data.content = _sanitizeRawContent(data.content);
|
1678
|
+
}
|
1679
|
+
}
|
1680
|
+
return data;
|
1273
1681
|
}
|
1274
1682
|
else {
|
1275
|
-
|
1683
|
+
for (file in data) {
|
1684
|
+
if (data.hasOwnProperty(file)) {
|
1685
|
+
if (excludeContent) {
|
1686
|
+
delete data[file].content;
|
1687
|
+
}
|
1688
|
+
else {
|
1689
|
+
data[file].content = _sanitizeRawContent(data[file].content);
|
1690
|
+
}
|
1691
|
+
}
|
1692
|
+
}
|
1693
|
+
return data;
|
1276
1694
|
}
|
1277
1695
|
}
|
1278
1696
|
|
@@ -1340,7 +1758,87 @@
|
|
1340
1758
|
return self;
|
1341
1759
|
}
|
1342
1760
|
|
1343
|
-
|
1761
|
+
/**
|
1762
|
+
* Handles autogrowing the editor
|
1763
|
+
*/
|
1764
|
+
EpicEditor.prototype._autogrow = function () {
|
1765
|
+
var editorHeight
|
1766
|
+
, newHeight
|
1767
|
+
, minHeight
|
1768
|
+
, maxHeight
|
1769
|
+
, el
|
1770
|
+
, style
|
1771
|
+
, maxedOut = false;
|
1772
|
+
|
1773
|
+
//autogrow in fullscreen is nonsensical
|
1774
|
+
if (!this.is('fullscreen')) {
|
1775
|
+
if (this.is('edit')) {
|
1776
|
+
el = this.getElement('editor').documentElement;
|
1777
|
+
}
|
1778
|
+
else {
|
1779
|
+
el = this.getElement('previewer').documentElement;
|
1780
|
+
}
|
1781
|
+
|
1782
|
+
editorHeight = _outerHeight(el);
|
1783
|
+
newHeight = editorHeight;
|
1784
|
+
|
1785
|
+
//handle minimum
|
1786
|
+
minHeight = this.settings.autogrow.minHeight;
|
1787
|
+
if (typeof minHeight === 'function') {
|
1788
|
+
minHeight = minHeight(this);
|
1789
|
+
}
|
1790
|
+
|
1791
|
+
if (minHeight && newHeight < minHeight) {
|
1792
|
+
newHeight = minHeight;
|
1793
|
+
}
|
1794
|
+
|
1795
|
+
//handle maximum
|
1796
|
+
maxHeight = this.settings.autogrow.maxHeight;
|
1797
|
+
if (typeof maxHeight === 'function') {
|
1798
|
+
maxHeight = maxHeight(this);
|
1799
|
+
}
|
1800
|
+
|
1801
|
+
if (maxHeight && newHeight > maxHeight) {
|
1802
|
+
newHeight = maxHeight;
|
1803
|
+
maxedOut = true;
|
1804
|
+
}
|
1805
|
+
|
1806
|
+
if (maxedOut) {
|
1807
|
+
this._fixScrollbars('auto');
|
1808
|
+
} else {
|
1809
|
+
this._fixScrollbars('hidden');
|
1810
|
+
}
|
1811
|
+
|
1812
|
+
//actual resize
|
1813
|
+
if (newHeight != this.oldHeight) {
|
1814
|
+
this.getElement('container').style.height = newHeight + 'px';
|
1815
|
+
this.reflow();
|
1816
|
+
if (this.settings.autogrow.scroll) {
|
1817
|
+
window.scrollBy(0, newHeight - this.oldHeight);
|
1818
|
+
}
|
1819
|
+
this.oldHeight = newHeight;
|
1820
|
+
}
|
1821
|
+
}
|
1822
|
+
}
|
1823
|
+
|
1824
|
+
/**
|
1825
|
+
* Shows or hides scrollbars based on the autogrow setting
|
1826
|
+
* @param {string} forceSetting a value to force the overflow to
|
1827
|
+
*/
|
1828
|
+
EpicEditor.prototype._fixScrollbars = function (forceSetting) {
|
1829
|
+
var setting;
|
1830
|
+
if (this.settings.autogrow) {
|
1831
|
+
setting = 'hidden';
|
1832
|
+
}
|
1833
|
+
else {
|
1834
|
+
setting = 'auto';
|
1835
|
+
}
|
1836
|
+
setting = forceSetting || setting;
|
1837
|
+
this.getElement('editor').documentElement.style.overflow = setting;
|
1838
|
+
this.getElement('previewer').documentElement.style.overflow = setting;
|
1839
|
+
}
|
1840
|
+
|
1841
|
+
EpicEditor.version = '0.2.2';
|
1344
1842
|
|
1345
1843
|
// Used to store information to be shared across editors
|
1346
1844
|
EpicEditor._data = {};
|
@@ -1349,8 +1847,9 @@
|
|
1349
1847
|
})(window);
|
1350
1848
|
|
1351
1849
|
/**
|
1352
|
-
* marked -
|
1353
|
-
* Copyright (c) 2011-
|
1850
|
+
* marked - a markdown parser
|
1851
|
+
* Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
|
1852
|
+
* https://github.com/chjj/marked
|
1354
1853
|
*/
|
1355
1854
|
|
1356
1855
|
;(function() {
|
@@ -1365,12 +1864,14 @@ var block = {
|
|
1365
1864
|
fences: noop,
|
1366
1865
|
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
1367
1866
|
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
|
1867
|
+
nptable: noop,
|
1368
1868
|
lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
|
1369
1869
|
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
|
1370
|
-
list: /^( *)(bull) [
|
1870
|
+
list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
|
1371
1871
|
html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
|
1372
1872
|
def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
1373
|
-
|
1873
|
+
table: noop,
|
1874
|
+
paragraph: /^([^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+\n*/,
|
1374
1875
|
text: /^[^\n]+/
|
1375
1876
|
};
|
1376
1877
|
|
@@ -1385,64 +1886,108 @@ block.list = replace(block.list)
|
|
1385
1886
|
('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
|
1386
1887
|
();
|
1387
1888
|
|
1889
|
+
block._tag = '(?!(?:'
|
1890
|
+
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
|
1891
|
+
+ '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
|
1892
|
+
+ '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
|
1893
|
+
|
1388
1894
|
block.html = replace(block.html)
|
1389
|
-
('comment', /<!--[
|
1390
|
-
('closed', /<(tag)[
|
1391
|
-
('closing', /<tag(
|
1392
|
-
(/tag/g,
|
1895
|
+
('comment', /<!--[\s\S]*?-->/)
|
1896
|
+
('closed', /<(tag)[\s\S]+?<\/\1>/)
|
1897
|
+
('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
|
1898
|
+
(/tag/g, block._tag)
|
1393
1899
|
();
|
1394
1900
|
|
1395
|
-
block.paragraph = (
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
(
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
('blockquote')
|
1408
|
-
('<' + tag())
|
1409
|
-
('def');
|
1410
|
-
|
1411
|
-
return new
|
1412
|
-
RegExp(paragraph.replace('body', body.join('|')));
|
1413
|
-
})();
|
1414
|
-
|
1415
|
-
block.normal = {
|
1416
|
-
fences: block.fences,
|
1417
|
-
paragraph: block.paragraph
|
1418
|
-
};
|
1901
|
+
block.paragraph = replace(block.paragraph)
|
1902
|
+
('hr', block.hr)
|
1903
|
+
('heading', block.heading)
|
1904
|
+
('lheading', block.lheading)
|
1905
|
+
('blockquote', block.blockquote)
|
1906
|
+
('tag', '<' + block._tag)
|
1907
|
+
('def', block.def)
|
1908
|
+
();
|
1909
|
+
|
1910
|
+
/**
|
1911
|
+
* Normal Block Grammar
|
1912
|
+
*/
|
1419
1913
|
|
1420
|
-
block.
|
1421
|
-
|
1914
|
+
block.normal = merge({}, block);
|
1915
|
+
|
1916
|
+
/**
|
1917
|
+
* GFM Block Grammar
|
1918
|
+
*/
|
1919
|
+
|
1920
|
+
block.gfm = merge({}, block.normal, {
|
1921
|
+
fences: /^ *(`{3,}|~{3,}) *(\w+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
|
1422
1922
|
paragraph: /^/
|
1423
|
-
};
|
1923
|
+
});
|
1424
1924
|
|
1425
1925
|
block.gfm.paragraph = replace(block.paragraph)
|
1426
|
-
('(?!', '(?!' + block.gfm.fences.source.replace(
|
1926
|
+
('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
|
1427
1927
|
();
|
1428
1928
|
|
1929
|
+
/**
|
1930
|
+
* GFM + Tables Block Grammar
|
1931
|
+
*/
|
1932
|
+
|
1933
|
+
block.tables = merge({}, block.gfm, {
|
1934
|
+
nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
|
1935
|
+
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
|
1936
|
+
});
|
1937
|
+
|
1429
1938
|
/**
|
1430
1939
|
* Block Lexer
|
1431
1940
|
*/
|
1432
1941
|
|
1433
|
-
|
1434
|
-
|
1942
|
+
function Lexer(options) {
|
1943
|
+
this.tokens = [];
|
1944
|
+
this.tokens.links = {};
|
1945
|
+
this.options = options || marked.defaults;
|
1946
|
+
this.rules = block.normal;
|
1435
1947
|
|
1436
|
-
|
1948
|
+
if (this.options.gfm) {
|
1949
|
+
if (this.options.tables) {
|
1950
|
+
this.rules = block.tables;
|
1951
|
+
} else {
|
1952
|
+
this.rules = block.gfm;
|
1953
|
+
}
|
1954
|
+
}
|
1955
|
+
}
|
1956
|
+
|
1957
|
+
/**
|
1958
|
+
* Expose Block Rules
|
1959
|
+
*/
|
1960
|
+
|
1961
|
+
Lexer.rules = block;
|
1437
1962
|
|
1963
|
+
/**
|
1964
|
+
* Static Lex Method
|
1965
|
+
*/
|
1966
|
+
|
1967
|
+
Lexer.lex = function(src, options) {
|
1968
|
+
var lexer = new Lexer(options);
|
1969
|
+
return lexer.lex(src);
|
1970
|
+
};
|
1971
|
+
|
1972
|
+
/**
|
1973
|
+
* Preprocessing
|
1974
|
+
*/
|
1975
|
+
|
1976
|
+
Lexer.prototype.lex = function(src) {
|
1438
1977
|
src = src
|
1439
1978
|
.replace(/\r\n|\r/g, '\n')
|
1440
|
-
.replace(/\t/g, ' ')
|
1979
|
+
.replace(/\t/g, ' ')
|
1980
|
+
.replace(/\u00a0/g, ' ')
|
1981
|
+
.replace(/\u2424/g, '\n');
|
1441
1982
|
|
1442
|
-
return
|
1983
|
+
return this.token(src, true);
|
1443
1984
|
};
|
1444
1985
|
|
1445
|
-
|
1986
|
+
/**
|
1987
|
+
* Lexing
|
1988
|
+
*/
|
1989
|
+
|
1990
|
+
Lexer.prototype.token = function(src, top) {
|
1446
1991
|
var src = src.replace(/^ +$/gm, '')
|
1447
1992
|
, next
|
1448
1993
|
, loose
|
@@ -1454,22 +1999,22 @@ block.token = function(src, tokens, top) {
|
|
1454
1999
|
|
1455
2000
|
while (src) {
|
1456
2001
|
// newline
|
1457
|
-
if (cap =
|
2002
|
+
if (cap = this.rules.newline.exec(src)) {
|
1458
2003
|
src = src.substring(cap[0].length);
|
1459
2004
|
if (cap[0].length > 1) {
|
1460
|
-
tokens.push({
|
2005
|
+
this.tokens.push({
|
1461
2006
|
type: 'space'
|
1462
2007
|
});
|
1463
2008
|
}
|
1464
2009
|
}
|
1465
2010
|
|
1466
2011
|
// code
|
1467
|
-
if (cap =
|
2012
|
+
if (cap = this.rules.code.exec(src)) {
|
1468
2013
|
src = src.substring(cap[0].length);
|
1469
2014
|
cap = cap[0].replace(/^ {4}/gm, '');
|
1470
|
-
tokens.push({
|
2015
|
+
this.tokens.push({
|
1471
2016
|
type: 'code',
|
1472
|
-
text: !options.pedantic
|
2017
|
+
text: !this.options.pedantic
|
1473
2018
|
? cap.replace(/\n+$/, '')
|
1474
2019
|
: cap
|
1475
2020
|
});
|
@@ -1477,20 +2022,20 @@ block.token = function(src, tokens, top) {
|
|
1477
2022
|
}
|
1478
2023
|
|
1479
2024
|
// fences (gfm)
|
1480
|
-
if (cap =
|
2025
|
+
if (cap = this.rules.fences.exec(src)) {
|
1481
2026
|
src = src.substring(cap[0].length);
|
1482
|
-
tokens.push({
|
2027
|
+
this.tokens.push({
|
1483
2028
|
type: 'code',
|
1484
|
-
lang: cap[
|
1485
|
-
text: cap[
|
2029
|
+
lang: cap[2],
|
2030
|
+
text: cap[3]
|
1486
2031
|
});
|
1487
2032
|
continue;
|
1488
2033
|
}
|
1489
2034
|
|
1490
2035
|
// heading
|
1491
|
-
if (cap =
|
2036
|
+
if (cap = this.rules.heading.exec(src)) {
|
1492
2037
|
src = src.substring(cap[0].length);
|
1493
|
-
tokens.push({
|
2038
|
+
this.tokens.push({
|
1494
2039
|
type: 'heading',
|
1495
2040
|
depth: cap[1].length,
|
1496
2041
|
text: cap[2]
|
@@ -1498,10 +2043,42 @@ block.token = function(src, tokens, top) {
|
|
1498
2043
|
continue;
|
1499
2044
|
}
|
1500
2045
|
|
2046
|
+
// table no leading pipe (gfm)
|
2047
|
+
if (top && (cap = this.rules.nptable.exec(src))) {
|
2048
|
+
src = src.substring(cap[0].length);
|
2049
|
+
|
2050
|
+
item = {
|
2051
|
+
type: 'table',
|
2052
|
+
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
|
2053
|
+
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
2054
|
+
cells: cap[3].replace(/\n$/, '').split('\n')
|
2055
|
+
};
|
2056
|
+
|
2057
|
+
for (i = 0; i < item.align.length; i++) {
|
2058
|
+
if (/^ *-+: *$/.test(item.align[i])) {
|
2059
|
+
item.align[i] = 'right';
|
2060
|
+
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
2061
|
+
item.align[i] = 'center';
|
2062
|
+
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
2063
|
+
item.align[i] = 'left';
|
2064
|
+
} else {
|
2065
|
+
item.align[i] = null;
|
2066
|
+
}
|
2067
|
+
}
|
2068
|
+
|
2069
|
+
for (i = 0; i < item.cells.length; i++) {
|
2070
|
+
item.cells[i] = item.cells[i].split(/ *\| */);
|
2071
|
+
}
|
2072
|
+
|
2073
|
+
this.tokens.push(item);
|
2074
|
+
|
2075
|
+
continue;
|
2076
|
+
}
|
2077
|
+
|
1501
2078
|
// lheading
|
1502
|
-
if (cap =
|
2079
|
+
if (cap = this.rules.lheading.exec(src)) {
|
1503
2080
|
src = src.substring(cap[0].length);
|
1504
|
-
tokens.push({
|
2081
|
+
this.tokens.push({
|
1505
2082
|
type: 'heading',
|
1506
2083
|
depth: cap[2] === '=' ? 1 : 2,
|
1507
2084
|
text: cap[1]
|
@@ -1510,19 +2087,19 @@ block.token = function(src, tokens, top) {
|
|
1510
2087
|
}
|
1511
2088
|
|
1512
2089
|
// hr
|
1513
|
-
if (cap =
|
2090
|
+
if (cap = this.rules.hr.exec(src)) {
|
1514
2091
|
src = src.substring(cap[0].length);
|
1515
|
-
tokens.push({
|
2092
|
+
this.tokens.push({
|
1516
2093
|
type: 'hr'
|
1517
2094
|
});
|
1518
2095
|
continue;
|
1519
2096
|
}
|
1520
2097
|
|
1521
2098
|
// blockquote
|
1522
|
-
if (cap =
|
2099
|
+
if (cap = this.rules.blockquote.exec(src)) {
|
1523
2100
|
src = src.substring(cap[0].length);
|
1524
2101
|
|
1525
|
-
tokens.push({
|
2102
|
+
this.tokens.push({
|
1526
2103
|
type: 'blockquote_start'
|
1527
2104
|
});
|
1528
2105
|
|
@@ -1531,9 +2108,9 @@ block.token = function(src, tokens, top) {
|
|
1531
2108
|
// Pass `top` to keep the current
|
1532
2109
|
// "toplevel" state. This is exactly
|
1533
2110
|
// how markdown.pl works.
|
1534
|
-
|
2111
|
+
this.token(cap, top);
|
1535
2112
|
|
1536
|
-
tokens.push({
|
2113
|
+
this.tokens.push({
|
1537
2114
|
type: 'blockquote_end'
|
1538
2115
|
});
|
1539
2116
|
|
@@ -1541,16 +2118,16 @@ block.token = function(src, tokens, top) {
|
|
1541
2118
|
}
|
1542
2119
|
|
1543
2120
|
// list
|
1544
|
-
if (cap =
|
2121
|
+
if (cap = this.rules.list.exec(src)) {
|
1545
2122
|
src = src.substring(cap[0].length);
|
1546
2123
|
|
1547
|
-
tokens.push({
|
2124
|
+
this.tokens.push({
|
1548
2125
|
type: 'list_start',
|
1549
2126
|
ordered: isFinite(cap[2])
|
1550
2127
|
});
|
1551
2128
|
|
1552
2129
|
// Get each top-level item.
|
1553
|
-
cap = cap[0].match(
|
2130
|
+
cap = cap[0].match(this.rules.item);
|
1554
2131
|
|
1555
2132
|
next = false;
|
1556
2133
|
l = cap.length;
|
@@ -1568,7 +2145,7 @@ block.token = function(src, tokens, top) {
|
|
1568
2145
|
// list item contains. Hacky.
|
1569
2146
|
if (~item.indexOf('\n ')) {
|
1570
2147
|
space -= item.length;
|
1571
|
-
item = !options.pedantic
|
2148
|
+
item = !this.options.pedantic
|
1572
2149
|
? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
|
1573
2150
|
: item.replace(/^ {1,4}/gm, '');
|
1574
2151
|
}
|
@@ -1582,21 +2159,21 @@ block.token = function(src, tokens, top) {
|
|
1582
2159
|
if (!loose) loose = next;
|
1583
2160
|
}
|
1584
2161
|
|
1585
|
-
tokens.push({
|
2162
|
+
this.tokens.push({
|
1586
2163
|
type: loose
|
1587
2164
|
? 'loose_item_start'
|
1588
2165
|
: 'list_item_start'
|
1589
2166
|
});
|
1590
2167
|
|
1591
2168
|
// Recurse.
|
1592
|
-
|
2169
|
+
this.token(item, false);
|
1593
2170
|
|
1594
|
-
tokens.push({
|
2171
|
+
this.tokens.push({
|
1595
2172
|
type: 'list_item_end'
|
1596
2173
|
});
|
1597
2174
|
}
|
1598
2175
|
|
1599
|
-
tokens.push({
|
2176
|
+
this.tokens.push({
|
1600
2177
|
type: 'list_end'
|
1601
2178
|
});
|
1602
2179
|
|
@@ -1604,10 +2181,12 @@ block.token = function(src, tokens, top) {
|
|
1604
2181
|
}
|
1605
2182
|
|
1606
2183
|
// html
|
1607
|
-
if (cap =
|
2184
|
+
if (cap = this.rules.html.exec(src)) {
|
1608
2185
|
src = src.substring(cap[0].length);
|
1609
|
-
tokens.push({
|
1610
|
-
type:
|
2186
|
+
this.tokens.push({
|
2187
|
+
type: this.options.sanitize
|
2188
|
+
? 'paragraph'
|
2189
|
+
: 'html',
|
1611
2190
|
pre: cap[1] === 'pre',
|
1612
2191
|
text: cap[0]
|
1613
2192
|
});
|
@@ -1615,19 +2194,53 @@ block.token = function(src, tokens, top) {
|
|
1615
2194
|
}
|
1616
2195
|
|
1617
2196
|
// def
|
1618
|
-
if (top && (cap =
|
2197
|
+
if (top && (cap = this.rules.def.exec(src))) {
|
1619
2198
|
src = src.substring(cap[0].length);
|
1620
|
-
tokens.links[cap[1].toLowerCase()] = {
|
2199
|
+
this.tokens.links[cap[1].toLowerCase()] = {
|
1621
2200
|
href: cap[2],
|
1622
2201
|
title: cap[3]
|
1623
2202
|
};
|
1624
2203
|
continue;
|
1625
2204
|
}
|
1626
2205
|
|
2206
|
+
// table (gfm)
|
2207
|
+
if (top && (cap = this.rules.table.exec(src))) {
|
2208
|
+
src = src.substring(cap[0].length);
|
2209
|
+
|
2210
|
+
item = {
|
2211
|
+
type: 'table',
|
2212
|
+
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
|
2213
|
+
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
|
2214
|
+
cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
|
2215
|
+
};
|
2216
|
+
|
2217
|
+
for (i = 0; i < item.align.length; i++) {
|
2218
|
+
if (/^ *-+: *$/.test(item.align[i])) {
|
2219
|
+
item.align[i] = 'right';
|
2220
|
+
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
2221
|
+
item.align[i] = 'center';
|
2222
|
+
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
2223
|
+
item.align[i] = 'left';
|
2224
|
+
} else {
|
2225
|
+
item.align[i] = null;
|
2226
|
+
}
|
2227
|
+
}
|
2228
|
+
|
2229
|
+
for (i = 0; i < item.cells.length; i++) {
|
2230
|
+
item.cells[i] = item.cells[i]
|
2231
|
+
.replace(/^ *\| *| *\| *$/g, '')
|
2232
|
+
.split(/ *\| */);
|
2233
|
+
}
|
2234
|
+
|
2235
|
+
this.tokens.push(item);
|
2236
|
+
|
2237
|
+
continue;
|
2238
|
+
}
|
2239
|
+
|
1627
2240
|
// top-level paragraph
|
1628
|
-
if (top && (cap =
|
2241
|
+
if (top && (cap = this.rules.paragraph.exec(src))) {
|
1629
2242
|
src = src.substring(cap[0].length);
|
1630
|
-
tokens.push({
|
2243
|
+
this.tokens.push({
|
1631
2244
|
type: 'paragraph',
|
1632
2245
|
text: cap[0]
|
1633
2246
|
});
|
@@ -1635,75 +2248,141 @@ block.token = function(src, tokens, top) {
|
|
1635
2248
|
}
|
1636
2249
|
|
1637
2250
|
// text
|
1638
|
-
if (cap =
|
2251
|
+
if (cap = this.rules.text.exec(src)) {
|
1639
2252
|
// Top-level should never reach here.
|
1640
2253
|
src = src.substring(cap[0].length);
|
1641
|
-
tokens.push({
|
2254
|
+
this.tokens.push({
|
1642
2255
|
type: 'text',
|
1643
2256
|
text: cap[0]
|
1644
2257
|
});
|
1645
2258
|
continue;
|
1646
2259
|
}
|
2260
|
+
|
2261
|
+
if (src) {
|
2262
|
+
throw new
|
2263
|
+
Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
2264
|
+
}
|
1647
2265
|
}
|
1648
2266
|
|
1649
|
-
return tokens;
|
2267
|
+
return this.tokens;
|
1650
2268
|
};
|
1651
2269
|
|
1652
2270
|
/**
|
1653
|
-
* Inline
|
2271
|
+
* Inline-Level Grammar
|
1654
2272
|
*/
|
1655
2273
|
|
1656
2274
|
var inline = {
|
1657
|
-
escape: /^\\([\\`*{}\[\]()#+\-.!_
|
2275
|
+
escape: /^\\([\\`*{}\[\]()#+\-.!_>|])/,
|
1658
2276
|
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
|
1659
2277
|
url: noop,
|
1660
|
-
tag: /^<!--[
|
2278
|
+
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
|
1661
2279
|
link: /^!?\[(inside)\]\(href\)/,
|
1662
2280
|
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
|
1663
2281
|
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
|
1664
|
-
strong: /^__([
|
1665
|
-
em: /^\b_((?:__|[
|
1666
|
-
code: /^(`+)([
|
2282
|
+
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
|
2283
|
+
em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
|
2284
|
+
code: /^(`+)([\s\S]*?[^`])\1(?!`)/,
|
1667
2285
|
br: /^ {2,}\n(?!\s*$)/,
|
1668
|
-
|
2286
|
+
del: noop,
|
2287
|
+
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
|
1669
2288
|
};
|
1670
2289
|
|
1671
|
-
inline.
|
1672
|
-
inline.
|
2290
|
+
inline._inside = /(?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*/;
|
2291
|
+
inline._href = /\s*<?([^\s]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
|
1673
2292
|
|
1674
2293
|
inline.link = replace(inline.link)
|
1675
|
-
('inside', inline.
|
1676
|
-
('href', inline.
|
2294
|
+
('inside', inline._inside)
|
2295
|
+
('href', inline._href)
|
1677
2296
|
();
|
1678
2297
|
|
1679
2298
|
inline.reflink = replace(inline.reflink)
|
1680
|
-
('inside', inline.
|
2299
|
+
('inside', inline._inside)
|
1681
2300
|
();
|
1682
2301
|
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
em: inline.em,
|
1687
|
-
text: inline.text
|
1688
|
-
};
|
2302
|
+
/**
|
2303
|
+
* Normal Inline Grammar
|
2304
|
+
*/
|
1689
2305
|
|
1690
|
-
inline.
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
2306
|
+
inline.normal = merge({}, inline);
|
2307
|
+
|
2308
|
+
/**
|
2309
|
+
* Pedantic Inline Grammar
|
2310
|
+
*/
|
2311
|
+
|
2312
|
+
inline.pedantic = merge({}, inline.normal, {
|
2313
|
+
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
|
2314
|
+
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
|
2315
|
+
});
|
1694
2316
|
|
1695
|
-
|
2317
|
+
/**
|
2318
|
+
* GFM Inline Grammar
|
2319
|
+
*/
|
2320
|
+
|
2321
|
+
inline.gfm = merge({}, inline.normal, {
|
2322
|
+
escape: replace(inline.escape)('])', '~])')(),
|
1696
2323
|
url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/,
|
1697
|
-
|
2324
|
+
del: /^~{2,}([\s\S]+?)~{2,}/,
|
2325
|
+
text: replace(inline.text)
|
2326
|
+
(']|', '~]|')
|
2327
|
+
('|', '|https?://|')
|
2328
|
+
()
|
2329
|
+
});
|
2330
|
+
|
2331
|
+
/**
|
2332
|
+
* GFM + Line Breaks Inline Grammar
|
2333
|
+
*/
|
2334
|
+
|
2335
|
+
inline.breaks = merge({}, inline.gfm, {
|
2336
|
+
br: replace(inline.br)('{2,}', '*')(),
|
2337
|
+
text: replace(inline.gfm.text)('{2,}', '*')()
|
2338
|
+
});
|
2339
|
+
|
2340
|
+
/**
|
2341
|
+
* Inline Lexer & Compiler
|
2342
|
+
*/
|
2343
|
+
|
2344
|
+
function InlineLexer(links, options) {
|
2345
|
+
this.options = options || marked.defaults;
|
2346
|
+
this.links = links;
|
2347
|
+
this.rules = inline.normal;
|
2348
|
+
|
2349
|
+
if (!this.links) {
|
2350
|
+
throw new
|
2351
|
+
Error('Tokens array requires a `links` property.');
|
2352
|
+
}
|
2353
|
+
|
2354
|
+
if (this.options.gfm) {
|
2355
|
+
if (this.options.breaks) {
|
2356
|
+
this.rules = inline.breaks;
|
2357
|
+
} else {
|
2358
|
+
this.rules = inline.gfm;
|
2359
|
+
}
|
2360
|
+
} else if (this.options.pedantic) {
|
2361
|
+
this.rules = inline.pedantic;
|
2362
|
+
}
|
2363
|
+
}
|
2364
|
+
|
2365
|
+
/**
|
2366
|
+
* Expose Inline Rules
|
2367
|
+
*/
|
2368
|
+
|
2369
|
+
InlineLexer.rules = inline;
|
2370
|
+
|
2371
|
+
/**
|
2372
|
+
* Static Lexing/Compiling Method
|
2373
|
+
*/
|
2374
|
+
|
2375
|
+
InlineLexer.output = function(src, links, opt) {
|
2376
|
+
var inline = new InlineLexer(links, opt);
|
2377
|
+
return inline.output(src);
|
1698
2378
|
};
|
1699
2379
|
|
1700
2380
|
/**
|
1701
|
-
*
|
2381
|
+
* Lexing/Compiling
|
1702
2382
|
*/
|
1703
2383
|
|
1704
|
-
|
2384
|
+
InlineLexer.prototype.output = function(src) {
|
1705
2385
|
var out = ''
|
1706
|
-
, links = tokens.links
|
1707
2386
|
, link
|
1708
2387
|
, text
|
1709
2388
|
, href
|
@@ -1711,20 +2390,20 @@ inline.lexer = function(src) {
|
|
1711
2390
|
|
1712
2391
|
while (src) {
|
1713
2392
|
// escape
|
1714
|
-
if (cap =
|
2393
|
+
if (cap = this.rules.escape.exec(src)) {
|
1715
2394
|
src = src.substring(cap[0].length);
|
1716
2395
|
out += cap[1];
|
1717
2396
|
continue;
|
1718
2397
|
}
|
1719
2398
|
|
1720
2399
|
// autolink
|
1721
|
-
if (cap =
|
2400
|
+
if (cap = this.rules.autolink.exec(src)) {
|
1722
2401
|
src = src.substring(cap[0].length);
|
1723
2402
|
if (cap[2] === '@') {
|
1724
2403
|
text = cap[1][6] === ':'
|
1725
|
-
? mangle(cap[1].substring(7))
|
1726
|
-
: mangle(cap[1]);
|
1727
|
-
href = mangle('mailto:') + text;
|
2404
|
+
? this.mangle(cap[1].substring(7))
|
2405
|
+
: this.mangle(cap[1]);
|
2406
|
+
href = this.mangle('mailto:') + text;
|
1728
2407
|
} else {
|
1729
2408
|
text = escape(cap[1]);
|
1730
2409
|
href = text;
|
@@ -1738,7 +2417,7 @@ inline.lexer = function(src) {
|
|
1738
2417
|
}
|
1739
2418
|
|
1740
2419
|
// url (gfm)
|
1741
|
-
if (cap =
|
2420
|
+
if (cap = this.rules.url.exec(src)) {
|
1742
2421
|
src = src.substring(cap[0].length);
|
1743
2422
|
text = escape(cap[1]);
|
1744
2423
|
href = text;
|
@@ -1751,18 +2430,18 @@ inline.lexer = function(src) {
|
|
1751
2430
|
}
|
1752
2431
|
|
1753
2432
|
// tag
|
1754
|
-
if (cap =
|
2433
|
+
if (cap = this.rules.tag.exec(src)) {
|
1755
2434
|
src = src.substring(cap[0].length);
|
1756
|
-
out += options.sanitize
|
2435
|
+
out += this.options.sanitize
|
1757
2436
|
? escape(cap[0])
|
1758
2437
|
: cap[0];
|
1759
2438
|
continue;
|
1760
2439
|
}
|
1761
2440
|
|
1762
2441
|
// link
|
1763
|
-
if (cap =
|
2442
|
+
if (cap = this.rules.link.exec(src)) {
|
1764
2443
|
src = src.substring(cap[0].length);
|
1765
|
-
out += outputLink(cap, {
|
2444
|
+
out += this.outputLink(cap, {
|
1766
2445
|
href: cap[2],
|
1767
2446
|
title: cap[3]
|
1768
2447
|
});
|
@@ -1770,40 +2449,40 @@ inline.lexer = function(src) {
|
|
1770
2449
|
}
|
1771
2450
|
|
1772
2451
|
// reflink, nolink
|
1773
|
-
if ((cap =
|
1774
|
-
|| (cap =
|
2452
|
+
if ((cap = this.rules.reflink.exec(src))
|
2453
|
+
|| (cap = this.rules.nolink.exec(src))) {
|
1775
2454
|
src = src.substring(cap[0].length);
|
1776
2455
|
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
|
1777
|
-
link = links[link.toLowerCase()];
|
2456
|
+
link = this.links[link.toLowerCase()];
|
1778
2457
|
if (!link || !link.href) {
|
1779
2458
|
out += cap[0][0];
|
1780
2459
|
src = cap[0].substring(1) + src;
|
1781
2460
|
continue;
|
1782
2461
|
}
|
1783
|
-
out += outputLink(cap, link);
|
2462
|
+
out += this.outputLink(cap, link);
|
1784
2463
|
continue;
|
1785
2464
|
}
|
1786
2465
|
|
1787
2466
|
// strong
|
1788
|
-
if (cap =
|
2467
|
+
if (cap = this.rules.strong.exec(src)) {
|
1789
2468
|
src = src.substring(cap[0].length);
|
1790
2469
|
out += '<strong>'
|
1791
|
-
+
|
2470
|
+
+ this.output(cap[2] || cap[1])
|
1792
2471
|
+ '</strong>';
|
1793
2472
|
continue;
|
1794
2473
|
}
|
1795
2474
|
|
1796
2475
|
// em
|
1797
|
-
if (cap =
|
2476
|
+
if (cap = this.rules.em.exec(src)) {
|
1798
2477
|
src = src.substring(cap[0].length);
|
1799
2478
|
out += '<em>'
|
1800
|
-
+
|
2479
|
+
+ this.output(cap[2] || cap[1])
|
1801
2480
|
+ '</em>';
|
1802
2481
|
continue;
|
1803
2482
|
}
|
1804
2483
|
|
1805
2484
|
// code
|
1806
|
-
if (cap =
|
2485
|
+
if (cap = this.rules.code.exec(src)) {
|
1807
2486
|
src = src.substring(cap[0].length);
|
1808
2487
|
out += '<code>'
|
1809
2488
|
+ escape(cap[2], true)
|
@@ -1812,24 +2491,42 @@ inline.lexer = function(src) {
|
|
1812
2491
|
}
|
1813
2492
|
|
1814
2493
|
// br
|
1815
|
-
if (cap =
|
2494
|
+
if (cap = this.rules.br.exec(src)) {
|
1816
2495
|
src = src.substring(cap[0].length);
|
1817
2496
|
out += '<br>';
|
1818
2497
|
continue;
|
1819
2498
|
}
|
1820
2499
|
|
2500
|
+
// del (gfm)
|
2501
|
+
if (cap = this.rules.del.exec(src)) {
|
2502
|
+
src = src.substring(cap[0].length);
|
2503
|
+
out += '<del>'
|
2504
|
+
+ this.output(cap[1])
|
2505
|
+
+ '</del>';
|
2506
|
+
continue;
|
2507
|
+
}
|
2508
|
+
|
1821
2509
|
// text
|
1822
|
-
if (cap =
|
2510
|
+
if (cap = this.rules.text.exec(src)) {
|
1823
2511
|
src = src.substring(cap[0].length);
|
1824
2512
|
out += escape(cap[0]);
|
1825
2513
|
continue;
|
1826
2514
|
}
|
2515
|
+
|
2516
|
+
if (src) {
|
2517
|
+
throw new
|
2518
|
+
Error('Infinite loop on byte: ' + src.charCodeAt(0));
|
2519
|
+
}
|
1827
2520
|
}
|
1828
2521
|
|
1829
2522
|
return out;
|
1830
2523
|
};
|
1831
2524
|
|
1832
|
-
|
2525
|
+
/**
|
2526
|
+
* Compile Link
|
2527
|
+
*/
|
2528
|
+
|
2529
|
+
InlineLexer.prototype.outputLink = function(cap, link) {
|
1833
2530
|
if (cap[0][0] !== '!') {
|
1834
2531
|
return '<a href="'
|
1835
2532
|
+ escape(link.href)
|
@@ -1840,7 +2537,7 @@ function outputLink(cap, link) {
|
|
1840
2537
|
+ '"'
|
1841
2538
|
: '')
|
1842
2539
|
+ '>'
|
1843
|
-
+
|
2540
|
+
+ this.output(cap[1])
|
1844
2541
|
+ '</a>';
|
1845
2542
|
} else {
|
1846
2543
|
return '<img src="'
|
@@ -1855,21 +2552,100 @@ function outputLink(cap, link) {
|
|
1855
2552
|
: '')
|
1856
2553
|
+ '>';
|
1857
2554
|
}
|
1858
|
-
}
|
2555
|
+
};
|
1859
2556
|
|
1860
2557
|
/**
|
1861
|
-
*
|
2558
|
+
* Mangle Links
|
1862
2559
|
*/
|
1863
2560
|
|
1864
|
-
|
1865
|
-
|
2561
|
+
InlineLexer.prototype.mangle = function(text) {
|
2562
|
+
var out = ''
|
2563
|
+
, l = text.length
|
2564
|
+
, i = 0
|
2565
|
+
, ch;
|
2566
|
+
|
2567
|
+
for (; i < l; i++) {
|
2568
|
+
ch = text.charCodeAt(i);
|
2569
|
+
if (Math.random() > 0.5) {
|
2570
|
+
ch = 'x' + ch.toString(16);
|
2571
|
+
}
|
2572
|
+
out += '&#' + ch + ';';
|
2573
|
+
}
|
1866
2574
|
|
1867
|
-
|
1868
|
-
|
2575
|
+
return out;
|
2576
|
+
};
|
2577
|
+
|
2578
|
+
/**
|
2579
|
+
* Parsing & Compiling
|
2580
|
+
*/
|
2581
|
+
|
2582
|
+
function Parser(options) {
|
2583
|
+
this.tokens = [];
|
2584
|
+
this.token = null;
|
2585
|
+
this.options = options || marked.defaults;
|
1869
2586
|
}
|
1870
2587
|
|
1871
|
-
|
1872
|
-
|
2588
|
+
/**
|
2589
|
+
* Static Parse Method
|
2590
|
+
*/
|
2591
|
+
|
2592
|
+
Parser.parse = function(src, options) {
|
2593
|
+
var parser = new Parser(options);
|
2594
|
+
return parser.parse(src);
|
2595
|
+
};
|
2596
|
+
|
2597
|
+
/**
|
2598
|
+
* Parse Loop
|
2599
|
+
*/
|
2600
|
+
|
2601
|
+
Parser.prototype.parse = function(src) {
|
2602
|
+
this.inline = new InlineLexer(src.links, this.options);
|
2603
|
+
this.tokens = src.reverse();
|
2604
|
+
|
2605
|
+
var out = '';
|
2606
|
+
while (this.next()) {
|
2607
|
+
out += this.tok();
|
2608
|
+
}
|
2609
|
+
|
2610
|
+
return out;
|
2611
|
+
};
|
2612
|
+
|
2613
|
+
/**
|
2614
|
+
* Next Token
|
2615
|
+
*/
|
2616
|
+
|
2617
|
+
Parser.prototype.next = function() {
|
2618
|
+
return this.token = this.tokens.pop();
|
2619
|
+
};
|
2620
|
+
|
2621
|
+
/**
|
2622
|
+
* Preview Next Token
|
2623
|
+
*/
|
2624
|
+
|
2625
|
+
Parser.prototype.peek = function() {
|
2626
|
+
return this.tokens[this.tokens.length-1] || 0;
|
2627
|
+
};
|
2628
|
+
|
2629
|
+
/**
|
2630
|
+
* Parse Text Tokens
|
2631
|
+
*/
|
2632
|
+
|
2633
|
+
Parser.prototype.parseText = function() {
|
2634
|
+
var body = this.token.text;
|
2635
|
+
|
2636
|
+
while (this.peek().type === 'text') {
|
2637
|
+
body += '\n' + this.next().text;
|
2638
|
+
}
|
2639
|
+
|
2640
|
+
return this.inline.output(body);
|
2641
|
+
};
|
2642
|
+
|
2643
|
+
/**
|
2644
|
+
* Parse Current Token
|
2645
|
+
*/
|
2646
|
+
|
2647
|
+
Parser.prototype.tok = function() {
|
2648
|
+
switch (this.token.type) {
|
1873
2649
|
case 'space': {
|
1874
2650
|
return '';
|
1875
2651
|
}
|
@@ -1878,41 +2654,78 @@ function tok() {
|
|
1878
2654
|
}
|
1879
2655
|
case 'heading': {
|
1880
2656
|
return '<h'
|
1881
|
-
+ token.depth
|
2657
|
+
+ this.token.depth
|
1882
2658
|
+ '>'
|
1883
|
-
+ inline.
|
2659
|
+
+ this.inline.output(this.token.text)
|
1884
2660
|
+ '</h'
|
1885
|
-
+ token.depth
|
2661
|
+
+ this.token.depth
|
1886
2662
|
+ '>\n';
|
1887
2663
|
}
|
1888
2664
|
case 'code': {
|
1889
|
-
if (options.highlight) {
|
1890
|
-
|
1891
|
-
if (
|
1892
|
-
token.escaped = true;
|
1893
|
-
token.text =
|
2665
|
+
if (this.options.highlight) {
|
2666
|
+
var code = this.options.highlight(this.token.text, this.token.lang);
|
2667
|
+
if (code != null && code !== this.token.text) {
|
2668
|
+
this.token.escaped = true;
|
2669
|
+
this.token.text = code;
|
1894
2670
|
}
|
1895
2671
|
}
|
1896
2672
|
|
1897
|
-
if (!token.escaped) {
|
1898
|
-
token.text = escape(token.text, true);
|
2673
|
+
if (!this.token.escaped) {
|
2674
|
+
this.token.text = escape(this.token.text, true);
|
1899
2675
|
}
|
1900
2676
|
|
1901
2677
|
return '<pre><code'
|
1902
|
-
+ (token.lang
|
2678
|
+
+ (this.token.lang
|
1903
2679
|
? ' class="lang-'
|
1904
|
-
+ token.lang
|
2680
|
+
+ this.token.lang
|
1905
2681
|
+ '"'
|
1906
2682
|
: '')
|
1907
2683
|
+ '>'
|
1908
|
-
+ token.text
|
2684
|
+
+ this.token.text
|
1909
2685
|
+ '</code></pre>\n';
|
1910
2686
|
}
|
2687
|
+
case 'table': {
|
2688
|
+
var body = ''
|
2689
|
+
, heading
|
2690
|
+
, i
|
2691
|
+
, row
|
2692
|
+
, cell
|
2693
|
+
, j;
|
2694
|
+
|
2695
|
+
// header
|
2696
|
+
body += '<thead>\n<tr>\n';
|
2697
|
+
for (i = 0; i < this.token.header.length; i++) {
|
2698
|
+
heading = this.inline.output(this.token.header[i]);
|
2699
|
+
body += this.token.align[i]
|
2700
|
+
? '<th align="' + this.token.align[i] + '">' + heading + '</th>\n'
|
2701
|
+
: '<th>' + heading + '</th>\n';
|
2702
|
+
}
|
2703
|
+
body += '</tr>\n</thead>\n';
|
2704
|
+
|
2705
|
+
// body
|
2706
|
+
body += '<tbody>\n'
|
2707
|
+
for (i = 0; i < this.token.cells.length; i++) {
|
2708
|
+
row = this.token.cells[i];
|
2709
|
+
body += '<tr>\n';
|
2710
|
+
for (j = 0; j < row.length; j++) {
|
2711
|
+
cell = this.inline.output(row[j]);
|
2712
|
+
body += this.token.align[j]
|
2713
|
+
? '<td align="' + this.token.align[j] + '">' + cell + '</td>\n'
|
2714
|
+
: '<td>' + cell + '</td>\n';
|
2715
|
+
}
|
2716
|
+
body += '</tr>\n';
|
2717
|
+
}
|
2718
|
+
body += '</tbody>\n';
|
2719
|
+
|
2720
|
+
return '<table>\n'
|
2721
|
+
+ body
|
2722
|
+
+ '</table>\n';
|
2723
|
+
}
|
1911
2724
|
case 'blockquote_start': {
|
1912
2725
|
var body = '';
|
1913
2726
|
|
1914
|
-
while (next().type !== 'blockquote_end') {
|
1915
|
-
body += tok();
|
2727
|
+
while (this.next().type !== 'blockquote_end') {
|
2728
|
+
body += this.tok();
|
1916
2729
|
}
|
1917
2730
|
|
1918
2731
|
return '<blockquote>\n'
|
@@ -1920,11 +2733,11 @@ function tok() {
|
|
1920
2733
|
+ '</blockquote>\n';
|
1921
2734
|
}
|
1922
2735
|
case 'list_start': {
|
1923
|
-
var type = token.ordered ? 'ol' : 'ul'
|
2736
|
+
var type = this.token.ordered ? 'ol' : 'ul'
|
1924
2737
|
, body = '';
|
1925
2738
|
|
1926
|
-
while (next().type !== 'list_end') {
|
1927
|
-
body += tok();
|
2739
|
+
while (this.next().type !== 'list_end') {
|
2740
|
+
body += this.tok();
|
1928
2741
|
}
|
1929
2742
|
|
1930
2743
|
return '<'
|
@@ -1938,10 +2751,10 @@ function tok() {
|
|
1938
2751
|
case 'list_item_start': {
|
1939
2752
|
var body = '';
|
1940
2753
|
|
1941
|
-
while (next().type !== 'list_item_end') {
|
1942
|
-
body += token.type === 'text'
|
1943
|
-
? parseText()
|
1944
|
-
: tok();
|
2754
|
+
while (this.next().type !== 'list_item_end') {
|
2755
|
+
body += this.token.type === 'text'
|
2756
|
+
? this.parseText()
|
2757
|
+
: this.tok();
|
1945
2758
|
}
|
1946
2759
|
|
1947
2760
|
return '<li>'
|
@@ -1951,8 +2764,8 @@ function tok() {
|
|
1951
2764
|
case 'loose_item_start': {
|
1952
2765
|
var body = '';
|
1953
2766
|
|
1954
|
-
while (next().type !== 'list_item_end') {
|
1955
|
-
body += tok();
|
2767
|
+
while (this.next().type !== 'list_item_end') {
|
2768
|
+
body += this.tok();
|
1956
2769
|
}
|
1957
2770
|
|
1958
2771
|
return '<li>'
|
@@ -1960,51 +2773,22 @@ function tok() {
|
|
1960
2773
|
+ '</li>\n';
|
1961
2774
|
}
|
1962
2775
|
case 'html': {
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
return !token.pre && !options.pedantic
|
1967
|
-
? inline.lexer(token.text)
|
1968
|
-
: token.text;
|
2776
|
+
return !this.token.pre && !this.options.pedantic
|
2777
|
+
? this.inline.output(this.token.text)
|
2778
|
+
: this.token.text;
|
1969
2779
|
}
|
1970
2780
|
case 'paragraph': {
|
1971
2781
|
return '<p>'
|
1972
|
-
+ inline.
|
2782
|
+
+ this.inline.output(this.token.text)
|
1973
2783
|
+ '</p>\n';
|
1974
2784
|
}
|
1975
2785
|
case 'text': {
|
1976
2786
|
return '<p>'
|
1977
|
-
+ parseText()
|
2787
|
+
+ this.parseText()
|
1978
2788
|
+ '</p>\n';
|
1979
2789
|
}
|
1980
2790
|
}
|
1981
|
-
}
|
1982
|
-
|
1983
|
-
function parseText() {
|
1984
|
-
var body = token.text
|
1985
|
-
, top;
|
1986
|
-
|
1987
|
-
while ((top = tokens[tokens.length-1])
|
1988
|
-
&& top.type === 'text') {
|
1989
|
-
body += '\n' + next().text;
|
1990
|
-
}
|
1991
|
-
|
1992
|
-
return inline.lexer(body);
|
1993
|
-
}
|
1994
|
-
|
1995
|
-
function parse(src) {
|
1996
|
-
tokens = src.reverse();
|
1997
|
-
|
1998
|
-
var out = '';
|
1999
|
-
while (next()) {
|
2000
|
-
out += tok();
|
2001
|
-
}
|
2002
|
-
|
2003
|
-
tokens = null;
|
2004
|
-
token = null;
|
2005
|
-
|
2006
|
-
return out;
|
2007
|
-
}
|
2791
|
+
};
|
2008
2792
|
|
2009
2793
|
/**
|
2010
2794
|
* Helpers
|
@@ -2019,38 +2803,14 @@ function escape(html, encode) {
|
|
2019
2803
|
.replace(/'/g, ''');
|
2020
2804
|
}
|
2021
2805
|
|
2022
|
-
function mangle(text) {
|
2023
|
-
var out = ''
|
2024
|
-
, l = text.length
|
2025
|
-
, i = 0
|
2026
|
-
, ch;
|
2027
|
-
|
2028
|
-
for (; i < l; i++) {
|
2029
|
-
ch = text.charCodeAt(i);
|
2030
|
-
if (Math.random() > 0.5) {
|
2031
|
-
ch = 'x' + ch.toString(16);
|
2032
|
-
}
|
2033
|
-
out += '&#' + ch + ';';
|
2034
|
-
}
|
2035
|
-
|
2036
|
-
return out;
|
2037
|
-
}
|
2038
|
-
|
2039
|
-
function tag() {
|
2040
|
-
var tag = '(?!(?:'
|
2041
|
-
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
|
2042
|
-
+ '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
|
2043
|
-
+ '|span|br|wbr|ins|del|img)\\b)\\w+';
|
2044
|
-
|
2045
|
-
return tag;
|
2046
|
-
}
|
2047
|
-
|
2048
2806
|
function replace(regex, opt) {
|
2049
2807
|
regex = regex.source;
|
2050
2808
|
opt = opt || '';
|
2051
2809
|
return function self(name, val) {
|
2052
2810
|
if (!name) return new RegExp(regex, opt);
|
2053
|
-
|
2811
|
+
val = val.source || val;
|
2812
|
+
val = val.replace(/(^|[^\[])\^/g, '$1');
|
2813
|
+
regex = regex.replace(name, val);
|
2054
2814
|
return self;
|
2055
2815
|
};
|
2056
2816
|
}
|
@@ -2058,80 +2818,78 @@ function replace(regex, opt) {
|
|
2058
2818
|
function noop() {}
|
2059
2819
|
noop.exec = noop;
|
2060
2820
|
|
2821
|
+
function merge(obj) {
|
2822
|
+
var i = 1
|
2823
|
+
, target
|
2824
|
+
, key;
|
2825
|
+
|
2826
|
+
for (; i < arguments.length; i++) {
|
2827
|
+
target = arguments[i];
|
2828
|
+
for (key in target) {
|
2829
|
+
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
2830
|
+
obj[key] = target[key];
|
2831
|
+
}
|
2832
|
+
}
|
2833
|
+
}
|
2834
|
+
|
2835
|
+
return obj;
|
2836
|
+
}
|
2837
|
+
|
2061
2838
|
/**
|
2062
2839
|
* Marked
|
2063
2840
|
*/
|
2064
2841
|
|
2065
2842
|
function marked(src, opt) {
|
2066
|
-
|
2067
|
-
|
2843
|
+
try {
|
2844
|
+
return Parser.parse(Lexer.lex(src, opt), opt);
|
2845
|
+
} catch (e) {
|
2846
|
+
e.message += '\nPlease report this to https://github.com/chjj/marked.';
|
2847
|
+
if ((opt || marked.defaults).silent) {
|
2848
|
+
return 'An error occured:\n' + e.message;
|
2849
|
+
}
|
2850
|
+
throw e;
|
2851
|
+
}
|
2068
2852
|
}
|
2069
2853
|
|
2070
2854
|
/**
|
2071
2855
|
* Options
|
2072
2856
|
*/
|
2073
2857
|
|
2074
|
-
var options
|
2075
|
-
, defaults;
|
2076
|
-
|
2077
|
-
function setOptions(opt) {
|
2078
|
-
if (!opt) opt = defaults;
|
2079
|
-
if (options === opt) return;
|
2080
|
-
options = opt;
|
2081
|
-
|
2082
|
-
if (options.gfm) {
|
2083
|
-
block.fences = block.gfm.fences;
|
2084
|
-
block.paragraph = block.gfm.paragraph;
|
2085
|
-
inline.text = inline.gfm.text;
|
2086
|
-
inline.url = inline.gfm.url;
|
2087
|
-
} else {
|
2088
|
-
block.fences = block.normal.fences;
|
2089
|
-
block.paragraph = block.normal.paragraph;
|
2090
|
-
inline.text = inline.normal.text;
|
2091
|
-
inline.url = inline.normal.url;
|
2092
|
-
}
|
2093
|
-
|
2094
|
-
if (options.pedantic) {
|
2095
|
-
inline.em = inline.pedantic.em;
|
2096
|
-
inline.strong = inline.pedantic.strong;
|
2097
|
-
} else {
|
2098
|
-
inline.em = inline.normal.em;
|
2099
|
-
inline.strong = inline.normal.strong;
|
2100
|
-
}
|
2101
|
-
}
|
2102
|
-
|
2103
2858
|
marked.options =
|
2104
2859
|
marked.setOptions = function(opt) {
|
2105
|
-
defaults = opt;
|
2106
|
-
setOptions(opt);
|
2860
|
+
marked.defaults = opt;
|
2107
2861
|
return marked;
|
2108
2862
|
};
|
2109
2863
|
|
2110
|
-
marked.
|
2864
|
+
marked.defaults = {
|
2111
2865
|
gfm: true,
|
2866
|
+
tables: true,
|
2867
|
+
breaks: false,
|
2112
2868
|
pedantic: false,
|
2113
2869
|
sanitize: false,
|
2870
|
+
silent: false,
|
2114
2871
|
highlight: null
|
2115
|
-
}
|
2872
|
+
};
|
2116
2873
|
|
2117
2874
|
/**
|
2118
2875
|
* Expose
|
2119
2876
|
*/
|
2120
2877
|
|
2121
|
-
marked.
|
2122
|
-
|
2123
|
-
return parse(src);
|
2124
|
-
};
|
2878
|
+
marked.Parser = Parser;
|
2879
|
+
marked.parser = Parser.parse;
|
2125
2880
|
|
2126
|
-
marked.
|
2127
|
-
|
2128
|
-
|
2129
|
-
|
2881
|
+
marked.Lexer = Lexer;
|
2882
|
+
marked.lexer = Lexer.lex;
|
2883
|
+
|
2884
|
+
marked.InlineLexer = InlineLexer;
|
2885
|
+
marked.inlineLexer = InlineLexer.output;
|
2130
2886
|
|
2131
2887
|
marked.parse = marked;
|
2132
2888
|
|
2133
2889
|
if (typeof module !== 'undefined') {
|
2134
2890
|
module.exports = marked;
|
2891
|
+
} else if (typeof define === 'function' && define.amd) {
|
2892
|
+
define(function() { return marked; });
|
2135
2893
|
} else {
|
2136
2894
|
this.marked = marked;
|
2137
2895
|
}
|