Markdown and LaTeX Compatible Text Editor - Writing.js

File Size: 253 KB
Views Total: 3218
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   
Markdown and LaTeX Compatible Text Editor - Writing.js

Writing is a simple, lightweight online text editor that supports both Markdown and LaTeX syntax. Implemented in jQuery, JavaScript and CSS.

The text editor is designed to be lightweight and, unlike some other similar solutions, fast to display (no delay when writing, no flickering when writing math), as close as possible to the math.stackexchange.com editor.

Commands:

  • CTRL + D: toggle display mode (editor only, preview only or both-at-the-same-time)
  • CTRL + P: print or export as PDF
  • CTRL + S: save source code as .MD file
  • CTRL + SHIFT + L: enable / disable LaTeX (i.e. math formulas)
  • CTRL + SHIFT + R: toggle roman (LaTex-like) or sans-serif font
  • CTRL + SHIFT + H: show this help dialog
  • F11: full-screen (in most browsers)

How to use it:

1. Load JavaScript library and other required resources in the html document.

<script src="Markdown.Converter.js"></script>
<script src="Markdown.Sanitizer.js"></script>
<script src="Markdown.Editor.js"></script>
<script src="Markdown.Extra.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML-full"></script>
<script src="mathjax-editing_writing.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

2. Create the html for the text editor. Write text on the left, and the result is displayed on the right.

<div id="wmd-button-bar" class="wmd-button-bar"></div>
<textarea id="wmd-input" class="column wmd-input" spellcheck="false"></textarea>
<div id="wmd-preview" class="column wmd-preview">
<noscript>This text editor requires JavaScript.</noscript>
</div>
<div id="helpicon" class="unselectable">?</div>
<div id="help">
<div id="closeicon" class="unselectable">X</div>    
<pre>    
  ...
</pre>    
</div>

3. The CSS to style the editor.

* {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
}

.unselectable {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.fixedheight { height: 100%; }

.column { padding: 20px; }

#wmd-button-bar { display: none; }

#wmd-input {
  float: left;
  box-sizing: border-box;
  width: 50%;
  resize: horizontal;
  font-size: 14px;
  border-right: 1px solid #ddd;
  height: 100%;
  overflow: y-scroll;
}

#wmd-preview {
  overflow-y: auto;
  overflow-x: hidden;
  font-size: 15px;
  height: 100%;
  box-sizing: border-box;
}

#wmd-preview li { margin-left: 20px; }

#wmd-preview code, #wmd-input { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; }

#wmd-preview p code, #wmd-preview li code {
  background-color: #f5f5f5;
  white-space: pre-wrap;
  padding: 3px 5px;
  font-size: 13px;
}

#wmd-preview pre {
  background-color: #f5f5f5;
  padding: 5px;
  margin: 0.5em 0 1em;
  overflow-x: auto;
  word-wrap: normal;
  font-size: 13px;
}

#wmd-preview blockquote {
  background-color: #defcff;
  padding: 10px;
  margin: 0.5em 0 1em;
  overflow-x: auto;
  word-wrap: normal;
  border-left: 2px solid #0ae2f5;
}

#wmd-preview blockquote p { margin-bottom: 0; }

#wmd-preview hr {
  background-color: #ddd;
  color: #ddd;
  height: 1px;
  margin-bottom: 15px;
  margin-top: 15px;
}

#wmd-preview h1 {
  margin-bottom: 0.5em;
  font-size: 1.4em;
}

#wmd-preview h2 {
  margin-bottom: 0.5em;
  font-size: 1.2em;
}

#wmd-preview h3 {
  margin-bottom: 0.5em;
  font-size: 1em;
}

#wmd-preview p {
  margin-bottom: 1em;
  line-height: 1.25;
}

#wmd-preview ul {
  margin-bottom: 1.5em;
  line-height: 1.25;
}

#wmd-preview img {
  max-width: 100%;
  max-height: 100%;
}

#wmd-preview table {
  display: block;
  width: 100%;
  overflow: auto;
  border-collapse: collapse;
  margin-bottom: 0.5em;
}

#wmd-preview table th { font-weight: bold; }

#wmd-preview table th, #wmd-preview table td {
  padding: 6px 13px;
  border: 1px solid #ddd;
}

#wmd-preview table tr {
  background-color: #fff;
  border-top: 1px solid #ccc;
}

#wmd-preview table tr:nth-child(2n) { background-color: #f8f8f8; }

#wmd-preview a {
  text-decoration: none;
  color: #07c;
}

#wmd-preview .pagebreak {
  border-bottom: 1px dashed #eee;
  padding-top: 20px;
  margin-bottom: 30px;
}

#helpicon {
  position: absolute;
  bottom: 0;
  left: 0;
  margin: 5px;
  color: #ccc;
  cursor: pointer;
  font-family: sans-serif;
  font-size: 15px;
}

#help {
  display: none;
  position: fixed;
  top: 10%;
  height: 70%;
  left: 25%;
  max-width: 50%;
  overflow: hidden;
  background-color: white;
  border: 1px solid #ccc;
  padding: 15px 30px 20px 30px;
  overflow-y: auto;
}

#help pre {
  word-wrap: break-word !important;
  white-space: pre-wrap !important;
}

#closeicon {
  position: fixed;
  top: 10%;
  left: 25%;
  margin: 5px 8px;
  color: #ccc;
  cursor: pointer;
  font-family: sans-serif;
}

4. The JavaScript to activate the text editor.

togglemathjax = function(enabled) {
    if (enabled) {
        if (!latexenabledonce)
        {
            MathJax.Hub.Config(
{"HTML-CSS": { preferredFont: "TeX", availableFonts: ["STIX","TeX"], linebreaks: { automatic: true }, EqnChunk: (MathJax.Hub.Browser.isMobile ? 10 : 50) },
 tex2jax: { inlineMath: [ ["$", "$"], ["\\\\(","\\\\)"] ], displayMath: [ ["$$","$$"], ["\\[", "\\]"] ], processEscapes: true, ignoreClass: "tex2jax_ignore|dno" },
 TeX: {  noUndefined: { attributes: { mathcolor: "red", mathbackground: "#FFEEEE", mathsize: "90%" } }, Macros: { href: "{}" } },
 messageStyle: "none", skipStartupTypeset: true });
            mjpd1.mathjaxEditing.prepareWmdForMathJax(editor, '', [["$", "$"]]);
            latexenabledonce = true;
            if (editor.refreshPreview !== undefined)
                editor.refreshPreview();
        }
        else {
            MathJax.Hub.queue.pending = 0;
            MathJax.Hub.Queue(["Typeset", MathJax.Hub, "wmd-preview"]);
        }
    }
    else {
        MathJax.Hub.queue.pending = 1; 
        if (editor.refreshPreview !== undefined)
            editor.refreshPreview();
        else {
           MathJax.Hub.Config({ skipStartupTypeset: true });
        }
    }
}
if (localStorage.getItem("writing") !== null) {
    $('#wmd-input').val(localStorage.getItem("writing"));
}
$('#wmd-input').on('input', function() {
    localStorage.setItem("writing", $('#wmd-input').val());
});
$('#wmd-input').focus();
$('#helpicon').click(function() {
    $('#help').show();
});
$('#closeicon, #wmd-input, #wmd-preview').click(function() {
    $('#help').hide();
});
$(document).on('keydown', function(e) {
    if (e.keyCode == 80 && e.ctrlKey) {    // CTRL + P 
        if (mode != 1) {
            mode = 1;
            $('#wmd-input').hide();
            $('#wmd-preview').show();
            $('body').removeClass('fixedheight');
            $('html').removeClass('fixedheight');
            e.preventDefault();
            window.print();
            return false;
        }
        //var doc = new jsPDF();
        //var specialElementHandlers = {'#editor': function (element, renderer) { return true; } };
        //doc.fromHTML($('#wmd-preview').html(), 15, 15, { 'width': 170, 'elementHandlers': specialElementHandlers });
        //doc.save('file.pdf');
        /*var restorepage = $('body').html();
        var printcontent = $('#wmd-preview').clone();
        $('body').empty().html(printcontent);
        window.print();
        $('body').html(restorepage);
        e.preventDefault();
        return false;*/
    }
    else if (e.keyCode == 83 && e.ctrlKey) {    // CTRL + S
        var blob = new Blob([$('#wmd-input').val()], {type: 'text'});     // https://stackoverflow.com/a/33542499/1422096
        if (window.navigator.msSaveOrOpenBlob) {
            window.navigator.msSaveBlob(blob, 'newfile.md');
        }
        else {
            var elem = window.document.createElement('a');
            elem.href = window.URL.createObjectURL(blob);
            elem.download = 'newfile.md';        
            document.body.appendChild(elem);
            elem.click();        
            document.body.removeChild(elem);
        }
        e.preventDefault();
        return false;
    }
    else if (e.keyCode == 68 && e.ctrlKey) {    // CTRL + D
        mode += 1; if (mode == 3) mode = 0;
        if (mode == 1) {
            $('#wmd-input').hide();
            $('#wmd-preview').show();
            $('body').removeClass('fixedheight');
            $('html').removeClass('fixedheight');
        }
        else if (mode == 2) {
            $('#wmd-preview').hide();            
            $('#wmd-input').show().css('float', 'none').css('width', '100%').focus();
            $('body').addClass('fixedheight');
            $('html').addClass('fixedheight');
        }
        else {
            $('#wmd-input').show().css('float', 'left').css('width', '50%').focus();
            $('#wmd-preview').show();
        }
        e.preventDefault();
        return false;
    }        
    else if (e.keyCode == 72 && e.ctrlKey && e.shiftKey) {    // CTRL + H
        $('#help').show();
        e.preventDefault();
        return false;
    }    
    else if (e.keyCode == 82 && e.ctrlKey && e.shiftKey) {    // CTRL + SHIFT + R
        $('html').toggleClass('texroman');
        e.preventDefault();
        return false;
    }    
    else if (e.keyCode == 76 && e.ctrlKey && e.shiftKey) {    // CTRL + SHIFT + L 
        latexenabled = !latexenabled;
        localStorage.setItem("latex", latexenabled ? "1" : "0");
        togglemathjax(latexenabled);
        e.preventDefault();
        return false;
    }  
    else if (e.keyCode == 27)  { // ESC
        $('#help').hide();
    }
});
var mode = 0;
var latexenabledonce = false;
var latexenabled = localStorage.getItem("latex") !== "0";
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter);
var editor = new Markdown.Editor(converter, '');
var mjpd1 = new mjpd();
togglemathjax(latexenabled);
editor.run();

This awesome jQuery plugin is developed by josephernest. For more Advanced Usages, please check the demo page or visit the official website.