text:
// A number of additional default bindings that are too obscure to
// include in the core codemirror.js file.

(function() {
    //Modifier key to use: Ctrl on windows, Cmd on OS X
    var ctrl = CodeMirror.keyMap["default"] === CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-";

    function toggleWrap(cm, before, after) {
        var selectedStrings = cm.getSelections(),
            selections,
            wrap;

        //"bob" will be wrapped, "**bob**" will be unwrapped
        wrap = !(
            selectedStrings[0].indexOf(before)===0 &&
            selectedStrings[0].substring(selectedStrings[0].length-after.length)===after
        );

        //First wrap the text
        for(var i in selectedStrings){
            if(wrap){
                selectedStrings[i] = before + selectedStrings[i] + after;
            }
            else{
                selectedStrings[i] = selectedStrings[i].substring(before.length);
                selectedStrings[i] = selectedStrings[i].substring(0, selectedStrings[i].length-after.length);
            }
        }
        cm.replaceSelections(selectedStrings, "around");

        if(wrap){
            //Then move the cursor inside the parentheses
            selections = cm.listSelections();
            selections.forEach(function(selection){
                //Anchor before head
                if(
                    selection.head.line > selection.anchor.line ||
                    (selection.head.line == selection.anchor.line && selection.head.ch > selection.anchor.ch)
                ){
                    selection.anchor.ch += before.length;
                    selection.head.ch -= after.length;
                }
                //Anchor after head
                else if(
                    selection.anchor.line > selection.head.line ||
                    (selection.head.line == selection.anchor.line && selection.head.ch <= selection.anchor.ch)
                ){
                    selection.head.ch += before.length;
                    selection.anchor.ch -= after.length;
                }
                return selection;
            });
            cm.setSelections(selections);
        }
    }

    CodeMirror.commands.toggleBold = function(cm){toggleWrap(cm, '**', '**');};
    CodeMirror.commands.toggleItalics = function(cm){toggleWrap(cm, '*', '*');};

    CodeMirror.commands.toggleLink = function(cm, type){
        var prefix = (type==="image") ? '![' : '[',
            cursor = {},
            selectedStrings = cm.getSelections(),
            selections;

        //First replace text with [text]()
        for(var i in selectedStrings){
            selectedStrings[i] = prefix + selectedStrings[i] + ']()';
        }
        cm.replaceSelections(selectedStrings, "around");

        //Then move the cursor inside the parentheses
        selections = cm.listSelections();
        selections.forEach(function(selection){
            //Anchor after tail
            if(selection.head.line > selection.anchor.line){
                cursor = {
                    line: selection.head.line,
                    ch: selection.head.ch-1,
                };
            }
            //Anchor after head
            else if(selection.anchor.line > selection.head.line){
                cursor = {
                    line: selection.anchor.line,
                    ch: selection.anchor.ch-1,
                };
            }
            //Anchor and head on same line
            else{
                cursor = {
                    line: selection.head.line,
                    ch: Math.max(selection.head.ch, selection.anchor.ch)-1,
                };
            }
            selection.head = selection.anchor = cursor;
            return selection;
        });
        cm.setSelections(selections);
    };

    CodeMirror.commands.toggleImageLink = function(cm){
        CodeMirror.commands.toggleLink(cm, "image");
    };

    CodeMirror.commands.toggleFold = function(cm){
        cm.foldCode(cm.getCursor(), {scanUp: true});
    };

    CodeMirror.keyMap["default"][ctrl+"B"] = "toggleBold";
    CodeMirror.keyMap["default"][ctrl+"I"] = "toggleItalics";
    CodeMirror.keyMap["default"][ctrl+"K"] = "toggleLink";
    CodeMirror.keyMap["default"][ctrl+"M"] = "toggleFold";
    CodeMirror.keyMap["default"]["Shift-"+ctrl+"K"] = "toggleImageLink";
    CodeMirror.keyMap["default"]["Enter"] = "newlineAndIndentContinueMarkdownList";
})();


text:
//Toggles full screen mode in supported browsers
angular.module('notes.ui').directive("fullscreen", function ($document, $rootScope) {
    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            var document = $document[0];
            element.on('click', function(){
                if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
                    if (document.documentElement.requestFullscreen) {
                        document.documentElement.requestFullscreen();
                    } else if (document.documentElement.mozRequestFullScreen) {
                        document.documentElement.mozRequestFullScreen();
                    } else if (document.documentElement.webkitRequestFullscreen) {
                        document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
                    }
                    $rootScope.$broadcast('fullScreen');
                } else {
                    if (document.cancelFullScreen) {
                        document.cancelFullScreen();
                    } else if (document.mozCancelFullScreen) {
                        document.mozCancelFullScreen();
                    } else if (document.webkitCancelFullScreen) {
                        document.webkitCancelFullScreen();
                    }
                }
            });
        }
    };
});

repo_name: nicbou/markdown-notes

repo_language: JavaScript

mime_type: text/plain


text:
//Toggles full screen mode in supported browsers
angular.module('notes.ui').directive("noteMenuItem", function ($timeout, $document) {
    return {
        restrict: "A",
        templateUrl: '/static/js/views/noteMenuItem.html',
        link: function (scope, element, attrs) {
            element.attr('draggable', true);

            //Pass the note as the dragged item when drag-dropping the element
            var onDragStart = function (e) {
                e.dataTransfer.effectAllowed = "move";
                e.dataTransfer.setData("markdownnotes/note", JSON.stringify(scope.note));
            };
            element.bind("dragstart", onDragStart);
        },
    };
});


text:
angular.module('notes.ui')
    .directive('news', function () {
        return {
            restrict: 'E',
            scope: {
                title: '=newsTitle',
                dateCreated: '=',
                onClose: '&'
            },
            templateUrl: '/static/js/views/news.html',
            transclude: true
        };
    });


text:
//Requires user confirmation before processing ng-click
angular.module('notes.ui').directive("ngConfirmClick", function ($document, $rootScope) {
    return {
        priority: -1,
        restrict: 'A',
        link: function(scope, element, attrs){
            element.bind('click', function(e){
                var message = attrs.ngConfirmClick;
                if(message && !confirm(message)){
                    e.stopImmediatePropagation();
                    e.preventDefault();
                }
            });
        }
    };
});


text:
//Toggles full screen mode in supported browsers
angular.module('notes.ui').directive("notebookMenuItem", function ($timeout, $document, $notebooksService, $notesService) {
    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            scope.openSettings = function(){
                $notebooksService.notebooks.forEach(function(notebook){
                    delete notebook.settingsOpen;
                });

                scope.notebook.settingsOpen = true;
            };

            scope.toggleSettings = function(){
                if(scope.notebook.settingsOpen){
                    delete scope.notebook.settingsOpen;
                    $notebooksService.save(scope.notebook);
                }
                else{
                    scope.openSettings();
                }
            };

            scope.toggleExpanded = function(){
                scope.notebook.expanded = !scope.notebook.expanded;
                if(!scope.notebook.expanded){
                    delete scope.notebook.settingsOpen;
                }
            };

            var onDragOver = function (e) {
                e.preventDefault();
                e.dataTransfer.dropEffect = "move";
                element.addClass("drag-over");
            };

            var onDragEnd = function (e) {
                e.preventDefault();
                element.removeClass("drag-over");
            };

            //When a note is dropped
            var moveNote = function (e) {
                var noteString = e.dataTransfer.getData("markdownnotes/note");

                if(noteString){
                    var droppedNote = JSON.parse(noteString);

                    //Find the note, change its notebook, then save it
                    $notesService.notes.active.forEach(function(note){
                        if(note.id === droppedNote.id){
                            note.notebook_uri = scope.notebook.resource_uri;
                            $notesService.save(note);

                            if(!scope.notebook.expanded){
                                scope.toggleExpanded();
                            }
                        }
                    });
                }
            };

            element.bind("dragover", onDragOver);
            element.bind("dragleave", onDragEnd)
                   .bind("drop", function (e) {
                       onDragEnd(e);
                       moveNote(e);
                   });
        },
    };
});


text:
//Toggles full screen mode in supported browsers
angular.module('notes.ui').directive("preview", function ($rootScope) {
    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            scope.cachedFormulas = {};

            //MatJax options (latex equations)
            MathJax.Hub.Config({
                tex2jax: {
                    inlineMath: [['$$','$$'],],
                    displayMath: [['$$$','$$$'],]
                },
                showProcessingMessages: false,
                messageStyle: "none",
                showMathMenu: false,
                "HTML-CSS": { linebreaks: { automatic: true, width: "75% container" } },
            });
            MathJax.Hub.Configured();

            //Cache rendered formulas
            MathJax.Hub.Register.MessageHook("End Process", function(message) {
                span = angular.element(message[1]);
                formula = span.attr('data-formula');
                output = span.html();
                scope.cachedFormulas[formula] = output;
            });

            $rootScope.$on('noteChanged', function(e, note){
                if(note === undefined) return;

                var outputWindow = element.parent();

                //Get the scroll position
                var scrollTop = outputWindow.scrollTop;

                //Convert the markup to HTML and update the preview
                var content = note.content;

                element.html(marked(
                    content,
                    {
                        gfm: true,
                        breaks: true,
                        smartLists: true,
                        highlight: function(code, lang){
                            if(lang){
                                return hljs.highlight(lang, code).value;
                            }
                            return hljs.highlightAuto(code).value;
                        }, //Code highlighting
                        sanitize: true,
                        renderer: customRenderer,
                    }
                ));

                //Set the scroll position, since it might have been changed by loaded elements
                outputWindow.scrollTop = scrollTop;

                //Update the preview to show LaTex equations
                var counter = 0;
                var elements = document.getElementsByClassName('latex');
                [].forEach.call(elements, function(el){  //HTMLCollection doesn't support forEach
                    var id = 'latex-' + counter,
                        element = angular.element(el);

                    element.attr('id', id);

                    formula = element.attr('data-formula');
                    if(scope.cachedFormulas[formula] !== undefined){
                        element.html(scope.cachedFormulas[formula]);
                    }
                    else{
                        MathJax.Hub.Queue(["Typeset", MathJax.Hub, id]);
                    }
                    counter++;
                });

            });
        }
    };
});


text:
//Toggles full screen mode in supported browsers
angular.module('notes.ui').directive("newNotebookMenuItem", function ($timeout) {
    return {
        restrict: "A",
        template:
            '
' +
            '   ' +
            '       Create a notebook' +
            '   ' +
            '   ' +
            '       ' +
            '       <input type="text" ng-model="newNotebookTitle" placeholder="Give your notebook a name">' +
            '   ' +
            '
' +
            '       ' +
            '       ' +
            '
' +
            '
',
        replace: true,
        link: function (scope, element, attrs) {
            var textbox = element[0].querySelector('.editable input');

            scope.newNotebookTitle = '';

            scope.newNotebook = function(){
                scope.newNotebookTitle = '';
                scope.creatingNotebook = true;
                $timeout(function() {
                    textbox.focus();
                });
            };

            scope.saveNotebook = function(){
                scope.createNotebook(scope.newNotebookTitle);
                scope.creatingNotebook = false;
            };

            scope.cancelNewNotebook = function(){
                scope.creatingNotebook = false;
            };

            angular.element(textbox).bind('keypress', function(e){
                if(e.which == 13){ //enter
                    scope.saveNotebook();
                }
                else if(e.which == 27){ //escape
                    scope.cancelNewNotebook();
                    scope.$apply();
                }
            });
        },
    };
});


text:
(function(e){if("function"==typeof bootstrap)bootstrap("ng-time-relative",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeNgTimeRelative=e}else"undefined"!=typeof window?window.ngTimeRelative=e():global.ngTimeRelative=e()})(function(){var define,ses,bootstrap,module,exports;
return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0](function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
'use strict';

exports = module.exports = function(module) {
  module.

    constant('timeRelativeConfig', {
      calendar: {
        en: {
          lastDay : '[Yesterday], LT',
          sameDay : '[Today], LT',
          nextDay : '[Tomorrow], LT',
          lastWeek : 'dddd, LT',
          nextWeek : 'Next dddd, LT',
          sameElse : 'LL'
        }
      }
    }).

    directive('relative', ['$timeout', 'moment', directive]).

    run(function(moment, timeRelativeConfig) {
      angular.forEach(timeRelativeConfig.calendar, function(translation, lang) {
        moment.lang(lang, {calendar: translation});
      });
    });
};

exports.directive = directive;

if (angular) {
  var mod = angular.module('timeRelative', []);
  if (moment) {
    mod.constant('moment', moment);
    moment.lang('en', {});
  }
  exports(mod);
}

function directive($timeout, moment) {
  return {
    restrict: 'AC',
    scope: {
      datetime: '@'
    },
    link: function(scope, element, attrs) {
      var timeout;

      scope.$watch('datetime', function(dateString) {
        $timeout.cancel(timeout);

        var date = moment(dateString);
        if (!date) return;
        var to = function() { return moment(attrs.to); };
        var withoutSuffix = 'withoutSuffix' in attrs;

        if (!attrs.title)
          element.attr('title', date.format('LLLL'));

        function updateTime() {
          element.text(diffString(date, to()));
        }

        function diffString(a, b) {
          if (Math.abs(a.clone().startOf('day').diff(b, 'days', true)) < 1)
            return a.from(b, withoutSuffix);
          else
            return a.calendar(b);
        }

        function updateLater() {
          updateTime();
          timeout = $timeout(function() {
            updateLater();
          }, nextUpdateIn());
        }

        function nextUpdateIn() {
          var delta = Math.abs(moment().diff(date));
          if (delta < 45e3) return 45e3 - delta;
          if (delta < 90e3) return 90e3 - delta;
          if (delta < 45 * 60e3) return 60e3 - (delta + 30e3) % 60e3;
          return 3660e3 - delta % 3600e3;
        }

        element.bind('$destroy', function() {
          $timeout.cancel(timeout);
        });

        updateLater();
      });
    }
  };
}

},{}]},{},[1])
//@ sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlcyI6WyIvVXNlcnMvc3RlcGhhbi9Db2RlL25nLXRpbWUtcmVsYXRpdmUvaW5kZXguanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnO1xuXG5leHBvcnRzID0gbW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbihtb2R1bGUpIHtcbiAgbW9kdWxlLlxuXG4gICAgY29uc3RhbnQoJ3RpbWVSZWxhdGl2ZUNvbmZpZycsIHtcbiAgICAgIGNhbGVuZGFyOiB7XG4gICAgICAgIGVuOiB7XG4gICAgICAgICAgbGFzdERheSA6ICdbWWVzdGVyZGF5XSwgTFQnLFxuICAgICAgICAgIHNhbWVEYXkgOiAnW1RvZGF5XSwgTFQnLFxuICAgICAgICAgIG5leHREYXkgOiAnW1RvbW9ycm93XSwgTFQnLFxuICAgICAgICAgIGxhc3RXZWVrIDogJ2RkZGQsIExUJyxcbiAgICAgICAgICBuZXh0V2VlayA6ICdOZXh0IGRkZGQsIExUJyxcbiAgICAgICAgICBzYW1lRWxzZSA6ICdMTCdcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pLlxuXG4gICAgZGlyZWN0aXZlKCdyZWxhdGl2ZScsIFsnJHRpbWVvdXQnLCAnbW9tZW50JywgZGlyZWN0aXZlXSkuXG5cbiAgICBydW4oZnVuY3Rpb24obW9tZW50LCB0aW1lUmVsYXRpdmVDb25maWcpIHtcbiAgICAgIGFuZ3VsYXIuZm9yRWFjaCh0aW1lUmVsYXRpdmVDb25maWcuY2FsZW5kYXIsIGZ1bmN0aW9uKHRyYW5zbGF0aW9uLCBsYW5nKSB7XG4gICAgICAgIG1vbWVudC5sYW5nKGxhbmcsIHtjYWxlbmRhcjogdHJhbnNsYXRpb259KTtcbiAgICAgIH0pO1xuICAgIH0pO1xufTtcblxuZXhwb3J0cy5kaXJlY3RpdmUgPSBkaXJlY3RpdmU7XG5cbmlmIChhbmd1bGFyKSB7XG4gIHZhciBtb2QgPSBhbmd1bGFyLm1vZHVsZSgndGltZVJlbGF0aXZlJywgW10pO1xuICBpZiAobW9tZW50KSB7XG4gICAgbW9kLmNvbnN0YW50KCdtb21lbnQnLCBtb21lbnQpO1xuICAgIG1vbWVudC5sYW5nKCdlbicsIHt9KTtcbiAgfVxuICBleHBvcnRzKG1vZCk7XG59XG5cbmZ1bmN0aW9uIGRpcmVjdGl2ZSgkdGltZW91dCwgbW9tZW50KSB7XG4gIHJldHVybiB7XG4gICAgcmVzdHJpY3Q6ICdBQycsXG4gICAgc2NvcGU6IHtcbiAgICAgIGRhdGV0aW1lOiAnQCdcbiAgICB9LFxuICAgIGxpbms6IGZ1bmN0aW9uKHNjb3BlLCBlbGVtZW50LCBhdHRycykge1xuICAgICAgdmFyIHRpbWVvdXQ7XG5cbiAgICAgIHNjb3BlLiR3YXRjaCgnZGF0ZXRpbWUnLCBmdW5jdGlvbihkYXRlU3RyaW5nKSB7XG4gICAgICAgICR0aW1lb3V0LmNhbmNlbCh0aW1lb3V0KTtcblxuICAgICAgICB2YXIgZGF0ZSA9IG1vbWVudChkYXRlU3RyaW5nKTtcbiAgICAgICAgaWYgKCFkYXRlKSByZXR1cm47XG4gICAgICAgIHZhciB0byA9IGZ1bmN0aW9uKCkgeyByZXR1cm4gbW9tZW50KGF0dHJzLnRvKTsgfTtcbiAgICAgICAgdmFyIHdpdGhvdXRTdWZmaXggPSAnd2l0aG91dFN1ZmZpeCcgaW4gYXR0cnM7XG5cbiAgICAgICAgaWYgKCFhdHRycy50aXRsZSlcbiAgICAgICAgICBlbGVtZW50LmF0dHIoJ3RpdGxlJywgZGF0ZS5mb3JtYXQoJ0xMTEwnKSk7XG5cbiAgICAgICAgZnVuY3Rpb24gdXBkYXRlVGltZSgpIHtcbiAgICAgICAgICBlbGVtZW50LnRleHQoZGlmZlN0cmluZyhkYXRlLCB0bygpKSk7XG4gICAgICAgIH1cblxuICAgICAgICBmdW5jdGlvbiBkaWZmU3RyaW5nKGEsIGIpIHtcbiAgICAgICAgICBpZiAoTWF0aC5hYnMoYS5jbG9uZSgpLnN0YXJ0T2YoJ2RheScpLmRpZmYoYiwgJ2RheXMnLCB0cnVlKSkgPCAxKVxuICAgICAgICAgICAgcmV0dXJuIGEuZnJvbShiLCB3aXRob3V0U3VmZml4KTtcbiAgICAgICAgICBlbHNlXG4gICAgICAgICAgICByZXR1cm4gYS5jYWxlbmRhcihiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHVwZGF0ZUxhdGVyKCkge1xuICAgICAgICAgIHVwZGF0ZVRpbWUoKTtcbiAgICAgICAgICB0aW1lb3V0ID0gJHRpbWVvdXQoZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICB1cGRhdGVMYXRlcigpO1xuICAgICAgICAgIH0sIG5leHRVcGRhdGVJbigpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIG5leHRVcGRhdGVJbigpIHtcbiAgICAgICAgICB2YXIgZGVsdGEgPSBNYXRoLmFicyhtb21lbnQoKS5kaWZmKGRhdGUpKTtcbiAgICAgICAgICBpZiAoZGVsdGEgPCA0NWUzKSByZXR1cm4gNDVlMyAtIGRlbHRhO1xuICAgICAgICAgIGlmIChkZWx0YSA8IDkwZTMpIHJldHVybiA5MGUzIC0gZGVsdGE7XG4gICAgICAgICAgaWYgKGRlbHRhIDwgNDUgKiA2MGUzKSByZXR1cm4gNjBlMyAtIChkZWx0YSArIDMwZTMpICUgNjBlMztcbiAgICAgICAgICByZXR1cm4gMzY2MGUzIC0gZGVsdGEgJSAzNjAwZTM7XG4gICAgICAgIH1cblxuICAgICAgICBlbGVtZW50LmJpbmQoJyRkZXN0cm95JywgZnVuY3Rpb24oKSB7XG4gICAgICAgICAgJHRpbWVvdXQuY2FuY2VsKHRpbWVvdXQpO1xuICAgICAgICB9KTtcblxuICAgICAgICB1cGRhdGVMYXRlcigpO1xuICAgICAgfSk7XG4gICAgfVxuICB9O1xufVxuIl19(1)
});
;


text:
//Disables the editor+preview mode on small screens
angular.module('notes.ui').directive('setModeOnResize', function ($window, $timeout) {
    return function (scope, element) {
        var w = angular.element($window);

        w.bind('resize', function(){
            if($window.innerWidth < 768 && scope.displayMode === scope.MODE_HYBRID){
                scope.displayMode = scope.MODE_INPUT_ONLY;
                scope.$apply();
            }
        });

        //Initial size
        $timeout(function(){
            w.triggerHandler('resize');
        });
    };
});


text:
angular.module('notes.ui').directive("imagedrop", function ($parse, $document) {
    var isImageDrag = function (e) {
        return e.dataTransfer.types.indexOf('Files') >= 0 || e.dataTransfer.types.indexOf('application/x-moz-file') >= 0;
    };

    //When an item is dragged over the document
    var onDragOver = function (e) {
        e.preventDefault();

        if (isImageDrag(e)) {
            angular.element($document[0].body).addClass("drag-over");
        }
    };

    //When the user leaves the window, cancels the drag or drops the item
    var onDragEnd = function (e) {
        e.preventDefault();
        angular.element($document[0].body).removeClass("drag-over");
    };

    //When a file is dropped
    var loadFile = function (file, scope, onImageDrop) {
        scope.uploadedFile = file;
        scope.$apply(onImageDrop(scope));
    };

    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            var onImageDrop = $parse(attrs.onImageDrop);

            //Dragging begins on the document
            $document.bind("dragover", onDragOver);

            //Dragging ends on the overlay, which takes the whole window
            element.bind("dragleave", onDragEnd)
                   .bind("drop", function (e) {
                       onDragEnd(e);
                       loadFile(e.dataTransfer.files[0], scope, onImageDrop);
                   });
        }
    };
});


text:
'use strict';

/**
 * Binds a CodeMirror widget to a <textarea> element.
 */
angular.module('ui.codemirror', [])
  .constant('uiCodemirrorConfig', {})
  .directive('uiCodemirror', uiCodemirrorDirective);

/**
 * @ngInject
 */
function uiCodemirrorDirective($timeout, uiCodemirrorConfig) {

  return {
    restrict: 'EA',
    require: '?ngModel',
    compile: function compile() {

      // Require CodeMirror
      if (angular.isUndefined(window.CodeMirror)) {
        throw new Error('ui-codemirror need CodeMirror to work... (o rly?)');
      }

      return postLink;
    }
  };

  function postLink(scope, iElement, iAttrs, ngModel) {

    var codemirrorOptions = angular.extend(
      { value: iElement.text() },
      uiCodemirrorConfig.codemirror || {},
      scope.$eval(iAttrs.uiCodemirror),
      scope.$eval(iAttrs.uiCodemirrorOpts)
    );

    var codemirror = newCodemirrorEditor(iElement, codemirrorOptions);

    configOptionsWatcher(
      codemirror,
      iAttrs.uiCodemirror || iAttrs.uiCodemirrorOpts,
      scope
    );

    configNgModelLink(codemirror, ngModel, scope);

    configUiRefreshAttribute(codemirror, iAttrs.uiRefresh, scope);

    // Allow access to the CodeMirror instance through a broadcasted event
    // eg: $broadcast('CodeMirror', function(cm){...});
    scope.$on('CodeMirror', function(event, callback) {
      if (angular.isFunction(callback)) {
        callback(codemirror);
      } else {
        throw new Error('the CodeMirror event requires a callback function');
      }
    });

    // onLoad callback
    if (angular.isFunction(codemirrorOptions.onLoad)) {
      codemirrorOptions.onLoad(codemirror);
    }

    // Pasting images support
    new Paster(codemirror.getWrapperElement(),
        function (data) {
          if (data instanceof Blob) {
            scope.uploadedFile = data;
            scope.uploadImage();
          } else {
            codemirror.replaceSelection('![](' + data + ')')
          }
          codemirror.focus();
        }, function (string) {
          codemirror.focus();
          codemirror.replaceSelection(string);
        });
  }

  function newCodemirrorEditor(iElement, codemirrorOptions) {
    var codemirrot;

    if (iElement[0].tagName === 'TEXTAREA') {
      // Might bug but still ...
      codemirrot = window.CodeMirror.fromTextArea(iElement[0], codemirrorOptions);
    } else {
      iElement.html('');
      codemirrot = new window.CodeMirror(function(cm_el) {
        iElement.append(cm_el);
      }, codemirrorOptions);
    }

    return codemirrot;
  }

  function configOptionsWatcher(codemirrot, uiCodemirrorAttr, scope) {
    if (!uiCodemirrorAttr) { return; }

    var codemirrorDefaultsKeys = Object.keys(window.CodeMirror.defaults);
    scope.$watch(uiCodemirrorAttr, updateOptions, true);
    function updateOptions(newValues, oldValue) {
      if (!angular.isObject(newValues)) { return; }
      codemirrorDefaultsKeys.forEach(function(key) {
        if (newValues.hasOwnProperty(key)) {

          if (oldValue && newValues[key] === oldValue[key]) {
            return;
          }

          codemirrot.setOption(key, newValues[key]);
        }
      });
    }
  }

  function configNgModelLink(codemirror, ngModel, scope) {
    if (!ngModel) { return; }
    // CodeMirror expects a string, so make sure it gets one.
    // This does not change the model.
    ngModel.$formatters.push(function(value) {
      if (angular.isUndefined(value) || value === null) {
        return '';
      } else if (angular.isObject(value) || angular.isArray(value)) {
        throw new Error('ui-codemirror cannot use an object or an array as a model');
      }
      return value;
    });

    // Override the ngModelController $render method, which is what gets called when the model is updated.
    // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
    ngModel.$render = function() {
      //Code mirror expects a string so make sure it gets one
      //Although the formatter have already done this, it can be possible that another formatter returns undefined (for example the required directive)
      var safeViewValue = ngModel.$viewValue || '';
      codemirror.setValue(safeViewValue);
    };

    // Keep the ngModel in sync with changes from CodeMirror
    codemirror.on('change', function(instance) {
      var newValue = instance.getValue();
      if (newValue !== ngModel.$viewValue) {
        scope.$evalAsync(function() {
          ngModel.$setViewValue(newValue);
        });
      }
    });
  }

  function configUiRefreshAttribute(codeMirror, uiRefreshAttr, scope) {
    if (!uiRefreshAttr) { return; }

    scope.$watch(uiRefreshAttr, function(newVal, oldVal) {
      // Skip the initial watch firing
      if (newVal !== oldVal) {
        $timeout(function() {
          codeMirror.refresh();
        });
      }
    });
  }

}

/**
 * Image pasting for Chrome, Firefox and IE 11
 *
 * @param {DOMNode} elem
 * @param {Function} imageCallback - when image is pasted, this callback is called with either Blob representing raw image to upload, or URL on the image
 * @param {Function} stringCallback - Firefox only - when text is passed, the text is caught and passed through this callback
 */
function Paster(elem, imageCallback, stringCallback) {
  var _self = this;
  var ctrlPressed = false;
  var stringPastTimeout;
  var pasteCatcher;
  var isWebkit = window.chrome !== null && window.chrome !== undefined;

  /**
   * Constructor
   */
  this.init = function () {
    if (isWebkit) {
      elem.addEventListener('paste', _self.pasteHandler, true); //official paste handler
      return;
    }

    // Content editable div, which catches all pasted data (images and text)
    pasteCatcher = document.createElement("div");
    pasteCatcher.setAttribute("id", "paste-catcher");
    pasteCatcher.setAttribute("contenteditable", "");
    pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;width:10px;margin-left:-20px;z-index:1;';
    document.body.appendChild(pasteCatcher);

    elem.addEventListener('keydown', _self.onKeyboardAction, false);
    elem.addEventListener('keyup', _self.onKeyboardUpAction, false);
    pasteCatcher.addEventListener('paste', _self.pasteFallback);

    // Observer which detects DOM changes in pasteCatcher
    var observer = new MutationObserver(function (mutations) {
      mutations.forEach(_self.mutationHandler);
    });
    var target = document.getElementById('paste-catcher');
    var config = {attributes: true, childList: true, characterData: true};
    observer.observe(target, config);
  };

  this.mutationHandler = function (mutation) {
    if (ctrlPressed == false || mutation.type != 'childList') {
      return true;
    }

    if (mutation.addedNodes.length == 1) {
      // Pasted image
      if (mutation.addedNodes[0].src != undefined) {
        clearTimeout(stringPastTimeout); // Cancel pasting string
        var imgSrc = mutation.addedNodes[0].src;

        if (_self.isUrl(imgSrc)) { // src contains link on image
          imageCallback(imgSrc);
        } else if (imgSrc.lastIndexOf('data:image', 0) === 0) { // src contains img data in base64
          var mimeType = /(image\/(png|jpg|jpeg))/g.exec(imgSrc);
          imgSrc = imgSrc.replace(/^data:image\/(png|jpg|jpeg);base64,/, "");
          imageCallback(_self.b64toBlob(imgSrc, mimeType[1]));
        } else { // unknown state
          console.log("Unknown src format: " + imgSrc);
        }
      }

      setTimeout(function () {
        pasteCatcher.innerHTML = '';
      }, 20);
    }
  };

  /**
   * Callback for 'paste' event which process image data.
   *
   * @param event
   */
  this.pasteHandler = function (event) {
    var items = (event.clipboardData || event.originalEvent.clipboardData).items || event.clipboardData.files;
    if (items) {
      for (var i = 0; i < items.length; i++) {
        if (items[i].type.indexOf("image") !== -1) {
          var blob = items[i].getAsFile();
          imageCallback(blob);
          event.preventDefault();
        }
      }
    }
  };

  /**
   * Fallback callback for Firefox for pasting a plain text.
   * After event is fired, timeout is set to see if the pasted data was image or text.
   * If it was image, the timeout is cleared by the mutation observer.
   * If it was not image, the timeout runs out and the text is pasted.
   *
   * @param event
     */
  this.pasteFallback = function (event) {
    var text;
    if(event.clipboardData){ // Firefox
      text = event.clipboardData.getData("text/plain");
    }else{ // IE
      text = window.clipboardData.getData("Text");
    }

    if(text !== ""){
      stringPastTimeout = setTimeout(function () {
        stringCallback(text);
        event.preventDefault();
      }, 50);
    }
  };

  /**
   * Callback for detecting CTRL+V keys
   * @param event
     */
  this.onKeyboardAction = function (event) {
    var key = event.keyCode;

    //ctrl
    if (key == 17 || event.metaKey || event.ctrlKey) {
        ctrlPressed = true;
    }

    //v
    if (key == 86 && ctrlPressed == true) {
      pasteCatcher.focus();
    }
  };

  //on kaybord release
  this.onKeyboardUpAction = function (event) {
    ctrlPressed = false;
  };

  /**
   * Convert base64 data into Blob file
   * @param b64Data
   * @param contentType
   * @returns {Blob}
     */
  this.b64toBlob = function (b64Data, contentType) {
    contentType = contentType || '';

    var sliceSize = 512;
    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      var slice = byteCharacters.slice(offset, offset + sliceSize);

      var byteNumbers = new Array(slice.length);
      for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }

      var byteArray = new Uint8Array(byteNumbers);

      byteArrays.push(byteArray);
    }

    return new Blob(byteArrays, {type: contentType});
  };

  /**
   * Testing string if contains valid URL
   * @param string
   * @returns {boolean}
     */
  this.isUrl = function (string) {
    var urlPattern = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
    return urlPattern.test(string);
  };

  this.init();
}


text:
angular.module('notes.service').factory('Uploader', ['$http', function($http){
    var Uploader = function(data) {
        angular.extend(this, data);
    };

    Uploader.uploadImage = function(file, note) {
        //Check file type
        if (!file || !file.type.match(/image.*/)) return;

        //Build the form data (file, note ID and CSRF token)
        var fd = new FormData();
        fd.append("image", file); // Append the file
        fd.append("note", note.id);

        //Send the request
        return $http({
            method: 'POST',
            url: '/upload/image/',
            data: fd,
            headers: {'Content-Type': undefined},
            transformRequest: angular.identity,
        }).then(function(response) {
            return response.headers('Location');
        });
    };

    return Uploader;
}]);


text:
//Syncs notes with the API
angular.module('notes.service').factory('$notesService', ['$rootScope', '$http', 'DEMO_MODE','$q', function($rootScope, $http, DEMO_MODE, $q){
    var notesUrl = DEMO_MODE ? '/api/v1/note-dummy/' : '/api/v1/note/',
        sharedNoteUrl = '/api/v1/shared-note/';

    function fakePromise(){
        var deferred = $q.defer();
        deferred.resolve();
        return deferred.promise;
    }

    var timeZone = jstz.determine().name();

    var $notesService = {
        notes: {
            'active': [],
            'deleted': [],
        },

        // Utility functions

        count: function(category){
            category = category || 'active';
            return this.notes[category].length;
        },

        // Communication with server

        save: function(note) {
            note.title = note.title || "";
            note.date_updated = moment.utc().tz(timeZone).toJSON();
            note.notebook_uri = note.notebook_uri || null;

            if(DEMO_MODE){
                note.id = note.id || Math.ceil(Math.random()*10000);
                return fakePromise();
            }

            var notesService = this;
            return $http.post(notesUrl + '?format=json', note).success(function(returnedNote) {
                if(!note.id){ //New note
                    note.date_created = moment.utc(returnedNote.date_created).tz(timeZone).toJSON();
                    note.public_id = returnedNote.public_id;

                    if(note.deleted){
                        notesService.notes.deleted.push(note);
                    }
                    else{
                        notesService.notes.active.push(note);
                    }
                }
                note.id = returnedNote.id;
            });
        },
        remove: function(note) {
            var notesService = this,
                deletedNotesIndex = this.notes.deleted.indexOf(note),
                allNotesindex = this.notes.active.indexOf(note);

            // Note already in trash. Remove permanently.
            if(deletedNotesIndex >= 0){
                notesService.notes.deleted.splice(deletedNotesIndex, 1);

                if(DEMO_MODE){
                    return fakePromise();
                }
                else{
                    return $http.delete(notesUrl + note.id + '/?format=json&permanent=true');
                }
            }
            // Note not in trash. Mark as deleted.
            else if(allNotesindex >= 0){
                notesService.notes.active.splice(allNotesindex, 1);
                notesService.notes.deleted.push(note);

                note.deleted = true;

                if(DEMO_MODE){
                    return fakePromise();
                }
                else{
                    return $http.delete(notesUrl + note.id + '/?format=json');
                }
            }
        },
        removeAll: function(category) {
            var notesService = this,
                noteURIs = this.notes[category].map(function(note){
                    return note.resource_uri;
                }),
                patchData = {
                    'objects': [],
                    'deleted_objects': noteURIs,
                };

                notesService.notes[category] = [];

                if(DEMO_MODE){
                    return fakePromise();
                }
                else{
                    return $http.patch(notesUrl + '?format=json&permanent=true', patchData);
                }
        },
        restore: function(note) {
            var deletedNotesIndex = this.notes.deleted.indexOf(note);

            // Move from trash to active
            if(deletedNotesIndex >= 0){
                this.notes.deleted.splice(deletedNotesIndex, 1);
                this.notes.active.push(note);

                note.deleted = false;
                return this.save(note);
            }
        },
        fetchFromServer: function(publicNoteId){
            var notesService = this,
                apiUrl = publicNoteId ? sharedNoteUrl + publicNoteId : notesUrl;
                apiUrl = apiUrl + '?format=json';

            return $http.get(apiUrl).then(
                function(response) {
                    if(publicNoteId){
                        notesService.notes.active = [response.data];
                    }
                    else{
                        response.data.objects.forEach(function(note){
                            note.date_created = moment.utc(note.date_created).tz(timeZone).toJSON();
                            note.date_updated = note.date_created;

                            if(note.deleted){
                                notesService.notes.deleted.push(note);
                            }
                            else{
                                notesService.notes.active.push(note);
                            }
                        });
                    }
                },
                angular.noop //If this is ommited, the following .then() error callbacks are ignored
            );
        },
        clearLocalMemory: function () {
            var notesService = this;

            notesService.notes.deleted = [];
            notesService.notes.active = [];
        }
    };

    return $notesService;
}]);


text:
//Syncs notes with the API
angular.module('notes.service').factory('$notebooksService', ['$rootScope', '$http', 'DEMO_MODE','$q', function($rootScope, $http, DEMO_MODE, $q){
    var notebooksUrl = DEMO_MODE ? '/api/v1/notebook-dummy/' : '/api/v1/notebook/';

    function fakePromise(){
        var deferred = $q.defer();
        deferred.resolve();
        return deferred.promise;
    }

    var $notebooksService = {
        notebooks: [],

        // Communication with server

        save: function(notebook) {
            notebook.title = notebook.title || "";

            if(DEMO_MODE){
                notebook.id = notebook.id || Math.ceil(Math.random()*10000);
                return fakePromise();
            }

            var notebooksService = this;
            return $http.post(notebooksUrl + '?format=json', notebook).success(function(returnedNotebook) {
                if(!notebook.id){
                    notebooksService.notebooks.push(notebook);
                }
                notebook.id = returnedNotebook.id;
                notebook.resource_uri = returnedNotebook.resource_uri;
            });
        },
        delete: function(notebook) {
            var notebooksService = this,
                notebookIndex = this.notebooks.indexOf(notebook);

            if(notebookIndex >= 0){
                notebooksService.notebooks.splice(notebookIndex, 1);

                if(DEMO_MODE){
                    return fakePromise();
                }
                else{
                    return $http.delete(notebooksUrl + notebook.id + '/?format=json');
                }
            }
        },
        fetchFromServer: function(){
            var notebooksService = this;

            return $http.get(notebooksUrl + '?format=json').then(
                function(response) {
                    notebooksService.notebooks = response.data.objects;
                },
                angular.noop //If this is ommited, the following .then() error callbacks are ignored
            );
        },
        clearLocalMemory: function () {
            var notebooksService = this;

            notebooksService.notebooks = [];
        }
    };

    return $notebooksService;
}]);


text:
angular.module('notes.service')
    .factory('$messageService', function($timeout, $rootScope, $sce){
        var $messageService = {
            classes: {
                INFO: 'info',
                WARNING: 'warning',
                ERROR: 'error',
            },
            messages: [],
            add: function(message) {
                message.message = $sce.trustAsHtml(message.message);

                this.messages.push(message);
                var messageService = this;
                if(message.timeout){
                    $timeout(
                        function(){
                            messageService.remove(message);
                        },
                        message.timeout
                    );
                }
                $rootScope.$broadcast('messageAdded', message);
            },
            replace: function(oldMessage, newMessage) {
                this.remove(oldMessage);
                this.add(newMessage);
            },
            remove: function(message) {
                var messageService = this;
                var messageIndex = messageService.messages.indexOf(message);
                if(messageIndex >= 0){
                    messageService.messages.splice(messageIndex, 1);
                    $rootScope.$broadcast('messageRemoved', message);
                }
            }
        };

        return $messageService;
    })
    .factory('$newsService', function ($http, $messageService) {
        var NEWS_URL = '/api/v1/news/';

        var $newsService = {
            loadNews: function () {
                return $http.get(NEWS_URL)
                    .then(function (response) {
                        return response.data.objects;
                    });
            },

            markNewsAsRead: function (id) {
                return $http.patch(NEWS_URL + id + '/read/')
                    .catch(function () {
                        $messageService.add({
                            'class': $messageService.classes.ERROR,
                            message: 'There was error when News was supposed to be marked as read.'
                        });
                    })
            }
        };

        return $newsService;
    });


text:
/**
 * Holds ApiKey and take care of managing Modal window with User management
 */
var servicesModule = angular.module('notes.service');

servicesModule.factory('$authService', function (ModalService, $timeout, $q, $http, $base64, $window, DEMO_MODE) {
    var USER_ROUTE = '/api/v1/user/',
        SIGNUP_ROUTE = '/api/v1/create_user/',
        PASSWORD_RECOVERY_ROUTE = '/api/v1/password_recovery/';

    var apiKey = undefined;

    function fakePromise() {
        var deferred = $q.defer();
        deferred.resolve();
        return deferred.promise;
    }

    var $authService = {
        /**
         * Function for logging in the user and retrieving ApiKey using
         * HTTP Basic Authentication.
         *
         * @param username
         * @param password
         * @param rememberMe - if defined and true, will store ApiKey into LocalStorage
         * @returns {angular.IPromise<TResult>}
         */
        login: function (username, password, rememberMe) {
            var auth = $base64.encode(username + ":" + password),
                headers = {"Authorization": "Basic " + auth};

            return $http.get(USER_ROUTE, {headers: headers})
                .then(function (response) {
                    apiKey = username + ":" + response.data.api_key;
                    $window.sessionStorage.apiKey = apiKey;

                    if(rememberMe) {
                        $window.localStorage.apiKey = apiKey;
                    }

                    return apiKey;
                });
        },

        /**
         * Function for logout of the user. Remove the locally stored ApiKey of the user.
         */
        logout: function () {
            $window.sessionStorage.removeItem('apiKey');
            $window.localStorage.removeItem('apiKey');
            apiKey = undefined;
        },

        /**
         * Function for creating new user.
         *
         * @param username
         * @param email
         * @param password
         * @returns {*|IPromise<TResult>}
         */
        signUp: function (username, email, password) {
            var payload = {
                'username': username,
                'email': email,
                'password': password
            };

            return $http.post(SIGNUP_ROUTE, payload)
                .then(function (response) {
                    apiKey = username + ":" + response.data.api_key;
                    $window.sessionStorage.apiKey = apiKey;
                    return apiKey;
                });
        },

        /**
         * Function for recovering lost password defined by email
         *
         * @param email
         * @returns {HttpPromise}
         */
        passwordRecovery: function (email) {
            var payload = {
                'email': email
            };

            return $http.post(PASSWORD_RECOVERY_ROUTE, payload);
        },

        /**
         * Function for updating user's account settings.
         *
         * @param payload
         * @returns {HttpPromise}
         */
        updateAccountSettings: function (payload) {
            return $http.patch(USER_ROUTE, payload);
        },

        /**
         * Return ApiKey or undefined if no ApiKey is found
         *
         * @returns {*}
         */
        getApiKey: function () {
            // Check local storage for ApiKey
            if (apiKey === undefined) {
                apiKey = $window.sessionStorage.apiKey || $window.localStorage.apiKey;
            }

            return apiKey;
        },

        /**
         * True if UserService has ApiKey and therefore User is logged in
         *
         * @returns {boolean}
         */
        isLoggedIn: function () {
            return this.getApiKey() !== undefined;
        },

        /**
         * Create modal window with login/signup form and return promise
         * which will result with ApiKey of the user.
         *
         * @returns {angular.IPromise<TResult>}
         */
        modal: function (formType) {
            if(DEMO_MODE) {
                return fakePromise();
            }

            return ModalService.showModal({
                templateUrl: "/static/js/views/modal.html",
                controller: "ModalCtrl",
                inputs : {
                    'formType' : formType
                }
            }).then(function (modal) {
                return modal.close;
            });
        }
    };

    return $authService;
});

/**
 * HTTPRequestInterceptor which includes the ApiKey into all API requests
 * when the user is logged in.
 */
servicesModule.factory('httpRequestInterceptor', function ($injector) {
    return {
        request: function (config) {
            var $authService = $injector.get('$authService');

            if ($authService.isLoggedIn()) {
                config.headers['Authorization'] = 'ApiKey ' + $authService.getApiKey();
            }

            config.headers['X-Requested-With'] = 'XMLHttpRequest';

            return config;
        }
    };
});

servicesModule.config(function ($httpProvider) {
    $httpProvider.interceptors.push('httpRequestInterceptor');
});


text:
//Custom renderer for marked.js. Adds support for task lists and other
//app-specific elements

var customRenderer = new marked.Renderer();
customRenderer.listitem = function(text){
    var output = '
' + text + '
';
    if(text.indexOf('[ ]')===0){
        output = '✗' + text.substring(3);
        output = '
' + output + '
';
    }
    if(text.indexOf('[x]')===0){
        output = '✓' + text.substring(3);
        output = '
' + output + '
';
    }
    return output;
};

customRenderer.latex = function(text){
    if(text.substring(0,3)==='$$$'){
        el = angular.element("

");
    }
    else{
        el = angular.element("");
    }

    el.text(text);
    el.attr('data-formula', text);
    return el.prop('outerHTML');
};

//Override the link renderer to have target="_blank"
customRenderer.link = function(href, title, text) {
    var out = '',
        isRelativeLink = +href !== NaN && +href % 1 === 0; //if href is a whole number
        target = 'target="_blank"';

    if (this.options.sanitize) {
        try {
            var prot = decodeURIComponent(unescape(href))
                .replace(/[^\w:]/g, '')
                .toLowerCase();
        } catch (e) {
            return '';
        }
        if (prot.indexOf('javascript:') === 0) {
            return '';
        }
    }

    if(isRelativeLink){
        href = '#?note=' + href;
        target = '';
    }

    var out = '' + text + '';
    return out;
};


text:
/**
 * marked - a markdown parser
 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
 * https://github.com/chjj/marked
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^( {4}[^\n]+\n*)+/,
  latex: /^(?:(\${2,3})\s*([\s\S]*?[^$])\s*(\1)(?!\1)\s?)+/,
  fences: noop,
  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  nptable: noop,
  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  table: noop,
  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
  text: /^[^\n]+/
};

block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
block.item = replace(block.item, 'gm')
  (/bull/g, block.bullet)
  ();

block.list = replace(block.list)
  (/bull/g, block.bullet)
  ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
  ('def', '\\n+(?=' + block.def.source + ')')
  ();

block.blockquote = replace(block.blockquote)
  ('def', block.def)
  ();

block._tag = '(?!(?:'
  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';

block.html = replace(block.html)
  ('comment', //)
  ('closed', /<(tag)[\s\S]+?<\/\1>/)
  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
  (/tag/g, block._tag)
  ();

block.paragraph = replace(block.paragraph)
  ('hr', block.hr)
  ('heading', block.heading)
  ('lheading', block.lheading)
  ('blockquote', block.blockquote)
  ('tag', '<' + block._tag)
  ('def', block.def)
  ();

/**
 * Normal Block Grammar
 */

block.normal = merge({}, block);

/**
 * GFM Block Grammar
 */

block.gfm = merge({}, block.normal, {
  fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
  paragraph: /^/
});

block.gfm.paragraph = replace(block.paragraph)
  ('(?!', '(?!'
    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
    + block.list.source.replace('\\1', '\\3') + '|')
  ();

/**
 * GFM + Tables Block Grammar
 */

block.tables = merge({}, block.gfm, {
  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
});

/**
 * Block Lexer
 */

function Lexer(options) {
  this.tokens = [];
  this.tokens.links = {};
  this.options = options || marked.defaults;
  this.rules = block.normal;

  if (this.options.gfm) {
    if (this.options.tables) {
      this.rules = block.tables;
    } else {
      this.rules = block.gfm;
    }
  }
}

/**
 * Expose Block Rules
 */

Lexer.rules = block;

/**
 * Static Lex Method
 */

Lexer.lex = function(src, options) {
  var lexer = new Lexer(options);
  return lexer.lex(src);
};

/**
 * Preprocessing
 */

Lexer.prototype.lex = function(src) {
  src = src
    .replace(/\r\n|\r/g, '\n')
    .replace(/\t/g, '    ')
    .replace(/\u00a0/g, ' ')
    .replace(/\u2424/g, '\n');

  return this.token(src, true);
};

/**
 * Lexing
 */

Lexer.prototype.token = function(src, top, bq) {
  var src = src.replace(/^ +$/gm, '')
    , next
    , loose
    , cap
    , bull
    , b
    , item
    , space
    , i
    , l;

  while (src) {
    // newline
    if (cap = this.rules.newline.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[0].length > 1) {
        this.tokens.push({
          type: 'space'
        });
      }
    }

    // latex
    if (cap = this.rules.latex.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'paragraph',
        text: cap[0]
      });
      continue;
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      this.tokens.push({
        type: 'code',
        text: !this.options.pedantic
          ? cap.replace(/\n+$/, '')
          : cap
      });
      continue;
    }

    // fences (gfm)
    if (cap = this.rules.fences.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'code',
        lang: cap[2],
        text: cap[3]
      });
      continue;
    }

    // heading
    if (cap = this.rules.heading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // table no leading pipe (gfm)
    if (top && (cap = this.rules.nptable.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i].split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // lheading
    if (cap = this.rules.lheading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // hr
    if (cap = this.rules.hr.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = this.rules.blockquote.exec(src)) {
      src = src.substring(cap[0].length);

      this.tokens.push({
        type: 'blockquote_start'
      });

      cap = cap[0].replace(/^ *> ?/gm, '');

      // Pass `top` to keep the current
      // "toplevel" state. This is exactly
      // how markdown.pl works.
      this.token(cap, top, true);

      this.tokens.push({
        type: 'blockquote_end'
      });

      continue;
    }

    // list
    if (cap = this.rules.list.exec(src)) {
      src = src.substring(cap[0].length);
      bull = cap[2];

      this.tokens.push({
        type: 'list_start',
        ordered: bull.length > 1
      });

      // Get each top-level item.
      cap = cap[0].match(this.rules.item);

      next = false;
      l = cap.length;
      i = 0;

      for (; i < l; i++) {
        item = cap[i];

        // Remove the list item's bullet
        // so it is seen as the next token.
        space = item.length;
        item = item.replace(/^ *([*+-]|\d+\.) +/, '');

        // Outdent whatever the
        // list item contains. Hacky.
        if (~item.indexOf('\n ')) {
          space -= item.length;
          item = !this.options.pedantic
            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
            : item.replace(/^ {1,4}/gm, '');
        }

        // Determine whether the next list item belongs here.
        // Backpedal if it does not belong in this list.
        if (this.options.smartLists && i !== l - 1) {
          b = block.bullet.exec(cap[i + 1])[0];
          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
            src = cap.slice(i + 1).join('\n') + src;
            i = l - 1;
          }
        }

        // Determine whether item is loose or not.
        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
        // for discount behavior.
        loose = next || /\n\n(?!\s*$)/.test(item);
        if (i !== l - 1) {
          next = item.charAt(item.length - 1) === '\n';
          if (!loose) loose = next;
        }

        this.tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });

        // Recurse.
        this.token(item, false, bq);

        this.tokens.push({
          type: 'list_item_end'
        });
      }

      this.tokens.push({
        type: 'list_end'
      });

      continue;
    }

    // html
    if (cap = this.rules.html.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: this.options.sanitize
          ? 'paragraph'
          : 'html',
        pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
        text: cap[0]
      });
      continue;
    }

    // def
    if ((!bq && top) && (cap = this.rules.def.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.links[cap[1].toLowerCase()] = {
        href: cap[2],
        title: cap[3]
      };
      continue;
    }

    // table (gfm)
    if (top && (cap = this.rules.table.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i]
          .replace(/^ *\| *| *\| *$/g, '')
          .split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // top-level paragraph
    if (top && (cap = this.rules.paragraph.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'paragraph',
        text: cap[1].charAt(cap[1].length - 1) === '\n'
          ? cap[1].slice(0, -1)
          : cap[1]
      });
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      // Top-level should never reach here.
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return this.tokens;
};

/**
 * Inline-Level Grammar
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
  latex: /^(\${2,3})\s*([\s\S]*?[^$])\s*(\1)(?!\1)/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  url: noop,
  tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
  link: /^!?\[(inside)\]\(href\)/,
  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
  em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
  br: /^ {2,}\n(?!\s*$)/,
  del: noop,
  text: /^[\s\S]+?(?=[\\<!\[_*`\$]| {2,}\n|$)/,
};

inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;

inline.link = replace(inline.link)
  ('inside', inline._inside)
  ('href', inline._href)
  ();

inline.reflink = replace(inline.reflink)
  ('inside', inline._inside)
  ();

/**
 * Normal Inline Grammar
 */

inline.normal = merge({}, inline);

/**
 * Pedantic Inline Grammar
 */

inline.pedantic = merge({}, inline.normal, {
  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
});

/**
 * GFM Inline Grammar
 */

inline.gfm = merge({}, inline.normal, {
  escape: replace(inline.escape)('])', '~|])')(),
  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
  del: /^~~(?=\S)([\s\S]*?\S)~~/,
  text: replace(inline.text)
    (']|', '~]|')
    ('|', '|https?://|')
    ()
});

/**
 * GFM + Line Breaks Inline Grammar
 */

inline.breaks = merge({}, inline.gfm, {
  br: replace(inline.br)('{2,}', '*')(),
  text: replace(inline.gfm.text)('{2,}', '*')()
});

/**
 * Inline Lexer & Compiler
 */

function InlineLexer(links, options) {
  this.options = options || marked.defaults;
  this.links = links;
  this.rules = inline.normal;
  this.renderer = this.options.renderer || new Renderer;
  this.renderer.options = this.options;

  if (!this.links) {
    throw new
      Error('Tokens array requires a `links` property.');
  }

  if (this.options.gfm) {
    if (this.options.breaks) {
      this.rules = inline.breaks;
    } else {
      this.rules = inline.gfm;
    }
  } else if (this.options.pedantic) {
    this.rules = inline.pedantic;
  }
}

/**
 * Expose Inline Rules
 */

InlineLexer.rules = inline;

/**
 * Static Lexing/Compiling Method
 */

InlineLexer.output = function(src, links, options) {
  var inline = new InlineLexer(links, options);
  return inline.output(src);
};

/**
 * Lexing/Compiling
 */

InlineLexer.prototype.output = function(src) {
  var out = ''
    , link
    , text
    , href
    , cap;

  while (src) {
    // escape
    if (cap = this.rules.escape.exec(src)) {
      src = src.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = this.rules.autolink.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[2] === '@') {
        text = cap[1].charAt(6) === ':'
          ? this.mangle(cap[1].substring(7))
          : this.mangle(cap[1]);
        href = this.mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += this.renderer.link(href, null, text);
      continue;
    }

    // url (gfm)
    if (!this.inLink && (cap = this.rules.url.exec(src))) {
      src = src.substring(cap[0].length);
      text = escape(cap[1]);
      href = text;
      out += this.renderer.link(href, null, text);
      continue;
    }

    // tag
    if (cap = this.rules.tag.exec(src)) {
      if (!this.inLink && /^<a /i.test(cap[0])) {
        this.inLink = true;
      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
        this.inLink = false;
      }
      src = src.substring(cap[0].length);
      out += this.options.sanitize
        ? escape(cap[0])
        : cap[0];
      continue;
    }

    // latex
    if (cap = this.rules.latex.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.latex(cap[0]);
      continue;
    }

    // link
    if (cap = this.rules.link.exec(src)) {
      src = src.substring(cap[0].length);
      this.inLink = true;
      out += this.outputLink(cap, {
        href: cap[2],
        title: cap[3]
      });
      this.inLink = false;
      continue;
    }

    // reflink, nolink
    if ((cap = this.rules.reflink.exec(src))
        || (cap = this.rules.nolink.exec(src))) {
      src = src.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = this.links[link.toLowerCase()];
      if (!link || !link.href) {
        out += cap[0].charAt(0);
        src = cap[0].substring(1) + src;
        continue;
      }
      this.inLink = true;
      out += this.outputLink(cap, link);
      this.inLink = false;
      continue;
    }

    // strong
    if (cap = this.rules.strong.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.strong(this.output(cap[2] || cap[1]));
      continue;
    }

    // em
    if (cap = this.rules.em.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.em(this.output(cap[2] || cap[1]));
      continue;
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.codespan(escape(cap[2], true));
      continue;
    }

    // br
    if (cap = this.rules.br.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.br();
      continue;
    }

    // del (gfm)
    if (cap = this.rules.del.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.del(this.output(cap[1]));
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      src = src.substring(cap[0].length);
      out += escape(this.smartypants(cap[0]));
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return out;
};

/**
 * Compile Link
 */

InlineLexer.prototype.outputLink = function(cap, link) {
  var href = escape(link.href)
    , title = link.title ? escape(link.title) : null;

  return cap[0].charAt(0) !== '!'
    ? this.renderer.link(href, title, this.output(cap[1]))
    : this.renderer.image(href, title, escape(cap[1]));
};

/**
 * Smartypants Transformations
 */

InlineLexer.prototype.smartypants = function(text) {
  if (!this.options.smartypants) return text;
  return text
    // em-dashes
    .replace(/--/g, '\u2014')
    // opening singles
    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
    // closing singles & apostrophes
    .replace(/'/g, '\u2019')
    // opening doubles
    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
    // closing doubles
    .replace(/"/g, '\u201d')
    // ellipses
    .replace(/\.{3}/g, '\u2026');
};

/**
 * Mangle Links
 */

InlineLexer.prototype.mangle = function(text) {
  var out = ''
    , l = text.length
    , i = 0
    , ch;

  for (; i < l; i++) {
    ch = text.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Renderer
 */

function Renderer(options) {
  this.options = options || {};
}

Renderer.prototype.code = function(code, lang, escaped) {
  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '
'
      + (escaped ? code : escape(code, true))
      + '\n
';
  }

  return '
'
    + (escaped ? code : escape(code, true))
    + '\n
\n';
};

Renderer.prototype.blockquote = function(quote) {
  return '
\n' + quote + '
\n';
};

Renderer.prototype.html = function(html) {
  return html;
};

Renderer.prototype.heading = function(text, level, raw) {
  return '<h'
    + level
    + ' id="'
    + this.options.headerPrefix
    + raw.toLowerCase().replace(/[^\w]+/g, '-')
    + '">'
    + text
    + '</h'
    + level
    + '>\n';
};

Renderer.prototype.hr = function() {
  return this.options.xhtml ? '
\n' : '
\n';
};

Renderer.prototype.list = function(body, ordered) {
  var type = ordered ? 'ol' : 'ul';
  return '<' + type + '>\n' + body + '</' + type + '>\n';
};

Renderer.prototype.listitem = function(text) {
  return '
' + text + '
\n';
};

Renderer.prototype.paragraph = function(text) {
  return '
' + text + '
\n';
};

Renderer.prototype.table = function(header, body) {
  return '
\n'
    + '
\n'
    + header
    + '
\n'
    + '
\n'
    + body
    + '
\n'
    + '
\n';
};

Renderer.prototype.tablerow = function(content) {
  return '
\n' + content + '
\n';
};

Renderer.prototype.tablecell = function(content, flags) {
  var type = flags.header ? 'th' : 'td';
  var tag = flags.align
    ? '<' + type + ' style="text-align:' + flags.align + '">'
    : '<' + type + '>';
  return tag + content + '</' + type + '>\n';
};

// span level renderer
Renderer.prototype.strong = function(text) {
  return '' + text + '';
};

Renderer.prototype.em = function(text) {
  return '' + text + '';
};

Renderer.prototype.codespan = function(text) {
  return '' + text + '';
};

Renderer.prototype.br = function() {
  return this.options.xhtml ? '
' : '
';
};

Renderer.prototype.del = function(text) {
  return '' + text + '';
};

Renderer.prototype.link = function(href, title, text) {
  if (this.options.sanitize) {
    try {
      var prot = decodeURIComponent(unescape(href))
        .replace(/[^\w:]/g, '')
        .toLowerCase();
    } catch (e) {
      return '';
    }
    if (prot.indexOf('javascript:') === 0) {
      return '';
    }
  }
  var out = '' + text + '';
  return out;
};

Renderer.prototype.image = function(href, title, text) {
  var out = '' : '>';
  return out;
};

/**
 * Parsing & Compiling
 */

function Parser(options) {
  this.tokens = [];
  this.token = null;
  this.options = options || marked.defaults;
  this.options.renderer = this.options.renderer || new Renderer;
  this.renderer = this.options.renderer;
  this.renderer.options = this.options;
}

/**
 * Static Parse Method
 */

Parser.parse = function(src, options, renderer) {
  var parser = new Parser(options, renderer);
  return parser.parse(src);
};

/**
 * Parse Loop
 */

Parser.prototype.parse = function(src) {
  this.inline = new InlineLexer(src.links, this.options, this.renderer);
  this.tokens = src.reverse();

  var out = '';
  while (this.next()) {
    out += this.tok();
  }

  return out;
};

/**
 * Next Token
 */

Parser.prototype.next = function() {
  return this.token = this.tokens.pop();
};

/**
 * Preview Next Token
 */

Parser.prototype.peek = function() {
  return this.tokens[this.tokens.length - 1] || 0;
};

/**
 * Parse Text Tokens
 */

Parser.prototype.parseText = function() {
  var body = this.token.text;

  while (this.peek().type === 'text') {
    body += '\n' + this.next().text;
  }

  return this.inline.output(body);
};

/**
 * Parse Current Token
 */

Parser.prototype.tok = function() {
  switch (this.token.type) {
    case 'space': {
      return '';
    }
    case 'hr': {
      return this.renderer.hr();
    }
    case 'heading': {
      return this.renderer.heading(
        this.inline.output(this.token.text),
        this.token.depth,
        this.token.text);
    }
    case 'code': {
      return this.renderer.code(this.token.text,
        this.token.lang,
        this.token.escaped);
    }
    case 'table': {
      var header = ''
        , body = ''
        , i
        , row
        , cell
        , flags
        , j;

      // header
      cell = '';
      for (i = 0; i < this.token.header.length; i++) {
        flags = { header: true, align: this.token.align[i] };
        cell += this.renderer.tablecell(
          this.inline.output(this.token.header[i]),
          { header: true, align: this.token.align[i] }
        );
      }
      header += this.renderer.tablerow(cell);

      for (i = 0; i < this.token.cells.length; i++) {
        row = this.token.cells[i];

        cell = '';
        for (j = 0; j < row.length; j++) {
          cell += this.renderer.tablecell(
            this.inline.output(row[j]),
            { header: false, align: this.token.align[j] }
          );
        }

        body += this.renderer.tablerow(cell);
      }
      return this.renderer.table(header, body);
    }
    case 'blockquote_start': {
      var body = '';

      while (this.next().type !== 'blockquote_end') {
        body += this.tok();
      }

      return this.renderer.blockquote(body);
    }
    case 'list_start': {
      var body = ''
        , ordered = this.token.ordered;

      while (this.next().type !== 'list_end') {
        body += this.tok();
      }

      return this.renderer.list(body, ordered);
    }
    case 'list_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.token.type === 'text'
          ? this.parseText()
          : this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'loose_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'html': {
      var html = !this.token.pre && !this.options.pedantic
        ? this.inline.output(this.token.text)
        : this.token.text;
      return this.renderer.html(html);
    }
    case 'paragraph': {
      return this.renderer.paragraph(this.inline.output(this.token.text));
    }
    case 'text': {
      return this.renderer.paragraph(this.parseText());
    }
  }
};

/**
 * Helpers
 */

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
}

function unescape(html) {
  return html.replace(/&([#\w]+);/g, function(_, n) {
    n = n.toLowerCase();
    if (n === 'colon') return ':';
    if (n.charAt(0) === '#') {
      return n.charAt(1) === 'x'
        ? String.fromCharCode(parseInt(n.substring(2), 16))
        : String.fromCharCode(+n.substring(1));
    }
    return '';
  });
}

function replace(regex, opt) {
  regex = regex.source;
  opt = opt || '';
  return function self(name, val) {
    if (!name) return new RegExp(regex, opt);
    val = val.source || val;
    val = val.replace(/(^|[^\[])\^/g, '$1');
    regex = regex.replace(name, val);
    return self;
  };
}

function noop() {}
noop.exec = noop;

function merge(obj) {
  var i = 1
    , target
    , key;

  for (; i < arguments.length; i++) {
    target = arguments[i];
    for (key in target) {
      if (Object.prototype.hasOwnProperty.call(target, key)) {
        obj[key] = target[key];
      }
    }
  }

  return obj;
}

/**
 * Marked
 */

function marked(src, opt, callback) {
  if (callback || typeof opt === 'function') {
    if (!callback) {
      callback = opt;
      opt = null;
    }

    opt = merge({}, marked.defaults, opt || {});

    var highlight = opt.highlight
      , tokens
      , pending
      , i = 0;

    try {
      tokens = Lexer.lex(src, opt)
    } catch (e) {
      return callback(e);
    }

    pending = tokens.length;

    var done = function(err) {
      if (err) {
        opt.highlight = highlight;
        return callback(err);
      }

      var out;

      try {
        out = Parser.parse(tokens, opt);
      } catch (e) {
        err = e;
      }

      opt.highlight = highlight;

      return err
        ? callback(err)
        : callback(null, out);
    };

    if (!highlight || highlight.length < 3) {
      return done();
    }

    delete opt.highlight;

    if (!pending) return done();

    for (; i < tokens.length; i++) {
      (function(token) {
        if (token.type !== 'code') {
          return --pending || done();
        }
        return highlight(token.text, token.lang, function(err, code) {
          if (err) return done(err);
          if (code == null || code === token.text) {
            return --pending || done();
          }
          token.text = code;
          token.escaped = true;
          --pending || done();
        });
      })(tokens[i]);
    }

    return;
  }
  try {
    if (opt) opt = merge({}, marked.defaults, opt);
    return Parser.parse(Lexer.lex(src, opt), opt);
  } catch (e) {
    e.message += '\nPlease report this to https://github.com/chjj/marked.';
    if ((opt || marked.defaults).silent) {
      return '
An error occured:

'
        + escape(e.message + '', true)
        + '
';
    }
    throw e;
  }
}

/**
 * Options
 */

marked.options =
marked.setOptions = function(opt) {
  merge(marked.defaults, opt);
  return marked;
};

marked.defaults = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: false,
  silent: false,
  highlight: null,
  langPrefix: 'lang-',
  smartypants: false,
  headerPrefix: '',
  renderer: new Renderer,
  xhtml: false
};

/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

marked.Renderer = Renderer;

marked.Lexer = Lexer;
marked.lexer = Lexer.lex;

marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;

marked.parse = marked;

if (typeof module !== 'undefined' && typeof exports === 'object') {
  module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
  define(function() { return marked; });
} else {
  this.marked = marked;
}

}).call(function() {
  return this || (typeof window !== 'undefined' ? window : global);
}());


text:
angular.module('notes').controller('NotesPreviewCtrl', function NotesCtrl($scope, $notesService, $routeParams, $rootScope, $document){
    var saveTimeout, previewTimeout; //Tracks the preview refresh and autosave delays

    $scope.notesService = $notesService;
    $scope.notesService.fetchFromServer($routeParams.noteId).then(
        function(){
            $scope.note = $scope.notesService.notes.active[0];
        },
        function(){
            $scope.note = {
                title: 'Markdown Notes',
                content: '#Note not found\n\nThe note you are looking for does not exist. Sorry!',
            };
        }
    ).finally(function(){
        $rootScope.$broadcast('noteChanged', $scope.note);
    });

    //Saves a note then exports it to .md
    $scope.export = function(note, track){
        var blob = new Blob([note.content], {type:'text/x-markdown'});
        var fileName = note.title.toSlug(100);
        fileName = fileName.length === 0 ? "untitled note" : fileName;
        saveAs(blob, fileName + '.md'); //Uses filesaver.js

        if(track){
            //Google analytics tracking
            ga('send', 'event', 'Notes', 'Export', '.md');
        }
    };

    //Ctrl + S shortcut to export files
    $document.bind('keydown', function(event) {
        if((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase()==='s') {
            $scope.exportNote($scope.note);
            return false;
        }
    });
});


text:
angular.module('notes').controller('NotesCtrl', function NotesCtrl($scope, $window, $notesService, $notebooksService, $authService, Uploader, $routeParams, $timeout, $interval, $location, $q, $document, $messageService, $newsService, $rootScope, debounce, DEMO_MODE){

    $scope.currentNoteIndex = -1;
    $scope.messages = [];
    $scope.messageService = $messageService;
    $scope.notebooksService = $notebooksService;
    $scope.notesService = $notesService;

    /**
     * Function for loading user's data
     */
    var loadData = function () {
        $scope.notebooksService.fetchFromServer().then(
            function () {
                $scope.notebooksService.notebooks.forEach(function (notebook) {
                    notebook.expanded = false;
                });
            },
            $scope.handleNetworkError
        );

        $scope.notesService.fetchFromServer().then(
            function () {
                init();
            },
            $scope.handleNetworkError
        );

        $newsService.loadNews()
            .then(function (news) {
                $scope.newsCollection = news;
            });

    };

    // Bootstrapping
    if ($authService.isLoggedIn()) {
        loadData();
    } else {
        var modalForm = 'login';

        // Show Sign Up form when signup is in URL
        if($location.search().hasOwnProperty('signup')){
            modalForm = 'signup';
        }

        $authService.modal(modalForm).then(function () {
            $location.search('signup', null);
            loadData();
        });
    }

    $scope.sideMenuOpen = false;

    $scope.MODE_INPUT_ONLY = 'input-only';
    $scope.MODE_OUTPUT_ONLY = 'output-only';
    $scope.MODE_HYBRID = 'hybrid';
    $scope.displayMode = $scope.MODE_HYBRID;

    //Controller setup
    function init(){
        var currentNote = 0;

        // Load a first note (or create one if needed)
        currentNote = parseInt($location.search().note, 10);
        if(currentNote){
            $scope.loadNote(currentNote);
        }
        else if($scope.notesService.count() === 0){
            $scope.createNote(true);
        }
        else{
            $scope.loadNote($scope.notesService.notes.active[0].id);
        }

        // Monitor note changes
        $scope.$watchGroup(
            [
                function(){
                    if($scope.currentNoteIndex >= 0 && $scope.notesService.count() > 0){
                        return $scope.notesService.notes.active[$scope.currentNoteIndex].title;
                    }
                },
                function(){
                    if($scope.currentNoteIndex >= 0 && $scope.notesService.count() > 0){
                        return $scope.notesService.notes.active[$scope.currentNoteIndex].content;
                    }
                },
            ],
            debounce(function(newValue, oldValue){
                if(newValue && newValue[0] !== undefined && newValue[1] !== undefined){
                    $scope.notesService.save($scope.notesService.notes.active[$scope.currentNoteIndex]).catch($scope.handleNetworkError);
                }
                $rootScope.$broadcast('noteChanged', $scope.notesService.notes.active[$scope.currentNoteIndex]);
            }, 200)
        );

        // Load the right note based on the note ID in the URL if it's updated elsewhere
        $scope.$on('$routeUpdate', function(){
            var currentNoteId = $scope.notesService.notes.active[$scope.currentNoteIndex].id;
            if($location.search().note){
                if(currentNoteId !== +$location.search().note){
                    $scope.loadNote(+$location.search().note);
                }
            }
            else{
                $location.search('note', $scope.notesService.notes.active[$scope.currentNoteIndex].id);
            }
        });
    }

    $scope.bindEditor = function(editor){
        //Prevents Ctrl+Z'ing back into the previous note
        $scope.$watch('currentNoteIndex', function(newId, oldId){
            editor.getDoc().clearHistory();
        });
    };

    $scope.codemirrorOptions = {
        dragDrop: false, //Disabled so the window receives the event
        foldGutter: true, //Code folding
        foldOptions: {'widget': '▾' },
        gutters: ["CodeMirror-foldgutter"],
        indentUnit: 4,
        indentWithTabs: true,
        lineWrapping: true,
        matchBrackets: true,
        mode: 'gfm-latex',
        tabindex: 2,
        viewportMargin: Infinity,
        widget: '',

        onLoad: $scope.bindEditor,
    };

    //Create a note
    $scope.createNote = function(loadCreatedNote){
        //Close the menu
        $scope.sideMenuOpen = false;

        $scope.notesService.save({title:'', content:''}).then(
            function(response){
                if(response && response.data && loadCreatedNote){
                    $scope.loadNote(response.data.id);
                }
                ga('send', 'event', 'Notes', 'Create');
            },
            $scope.handleNetworkError
        );
    };

    //Loads a note from the server
    $scope.loadNote = function(noteId, hideMenu){
        var noteFound = false;
        hideMenu = hideMenu === undefined ? true : false;

        //Load an existing note
        for(var i=0; i<$scope.notesService.count(); i++){
            var note = $scope.notesService.notes.active[i];
            if(note.id === noteId && !note.deleted){
                $scope.currentNoteIndex = i;
                noteFound = true;
            }
        }

        if(!noteFound){
            $scope.currentNoteIndex = 0;
        }
        $location.search('note', $scope.notesService.notes.active[$scope.currentNoteIndex].id);

        if(hideMenu){
            $scope.sideMenuOpen = false;
        }

        return noteFound;
    };
    //Preserves ctrl+clicking to open notes in a new tab
    $scope.loadNoteFromMenu = function($event, noteId, hideMenu){
        if($event.ctrlKey != 1 && $event.metaKey != 1){
            $event.preventDefault();
            $scope.loadNote(noteId);
        }
    };

    //Saves a note then exports it to .md
    $scope.exportNote = function(note, track){
        var blob = new Blob([note.content], {type:'text/x-markdown'});
        var fileName = note.title.toSlug(100);
        fileName = fileName.length === 0 ? "untitled note" : fileName;
        saveAs(blob, fileName + '.md'); //Uses filesaver.js

        if(track){
            //Google analytics tracking
            ga('send', 'event', 'Notes', 'Export', '.md');
        }
    };

    //Ctrl + S shortcut to export files
    $document.bind('keydown', function(event) {
        if((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase()==='s') {
            $scope.exportNote($scope.notesService.notes.active[$scope.currentNoteIndex]);
            return false;
        }
    });

    //Returns a value on which notes are sorted
    $scope.noteRanker = function(note){
        return -moment(note.date_updated).valueOf(); //Most recent first
    };
    //Sorts notes based on $scope.noteRanker values, but send the deleted ones at the end
    $scope.noteSorter = function(note1, note2){
        if(note1.deleted && !note2.deleted){
            return 1;
        }
        else if(!note1.deleted && note2.deleted){
            return -1;
        }
        else{
            return $scope.noteRanker(note1) - $scope.noteRanker(note2);
        }
    };

    //Deletes a note from the list
    $scope.removeNote = function(note){
        var isCurrentNote = note.id === $scope.notesService.notes.active[$scope.currentNoteIndex].id,
            nextNoteId = $scope.notesService.notes.active[$scope.currentNoteIndex].id;

        // Don't leave the user on a deleted note. Load the next note.
        if(isCurrentNote){
            //The notes in notesService are in arbitrary order, while those in the menu are sorted
            //based on $scope.noteRanker.
            var sortedNotes = $scope.notesService.notes.active.slice().sort($scope.noteSorter),
                indexInSortedNotes = sortedNotes.indexOf(note),
                isLastInList = indexInSortedNotes === sortedNotes.length-1, //If deleting the last note, load the next-to-last note
                nextNoteIndex = isLastInList ? indexInSortedNotes-1 : indexInSortedNotes+1;

            nextNoteId = sortedNotes[0].id;
        }

        $scope.notesService.remove(note).then(
            function(){
                //If we deleted the last note, create a new one
                if($scope.notesService.count() === 0){
                    $scope.createNote(true);
                }
                //Update the currentNoteIndex, since the array changed
                else{
                    $scope.loadNote(nextNoteId, false);
                }
            },
            $scope.handleNetworkError
        );
    };

    $scope.restoreNote = function(note){
        $scope.notesService.restore(note);
        ga('send', 'event', 'Notes', 'Trash', 'Restore note');
    };

    $scope.emptyTrash = function(){
        $scope.notesService.removeAll('deleted');
        ga('send', 'event', 'Notes', 'Trash', 'Empty trash');
    };

    $scope.uploadImage = function(){
        var error,
            message = {
                message: 'Uploading image...',
                class: $scope.messageService.classes.INFO,
                timeout: 8000,
            };
        $scope.messageService.add(message);

        //Upload the image
        Uploader.uploadImage($scope.uploadedFile, $scope.notesService.notes.active[$scope.currentNoteIndex]).then(
            function(imageUrl){
                var markdownImage = '![](' + imageUrl + ')';

                //Insert at cursor in the editor
                document.getElementsByClassName('CodeMirror')[0].CodeMirror.replaceSelection(markdownImage);

                //Delete the "uploading" message
                $scope.messageService.remove(message);

                ga('send', 'event', 'Notes', 'Upload', 'Image upload');
            },
            function(){
                error = {
                    message: 'An error occured while uploading the images.',
                    class: $scope.messageService.classes.ERROR,
                    timeout: 5000,
                };
                $scope.messageService.replace(message, error);
            }
        );

        //Clear the uploaded file
        $scope.uploadedFile = null;
    };

    $scope.createNotebook = function(title){
        $scope.notebooksService.save({
            title: title || '',
        });
    };

    $scope.deleteNotebook = function(notebook){
        var clearNotebookUri = function(note){
            if(note.notebook_uri === notebook.resource_uri){
                note.notebook_uri = null;
            }
        };

        //Unassign the notebook from any note that has it.
        //This is already done server-side when deleting the notebook.
        //We just avoid a needless sync.
        $scope.notesService.notes.active.forEach(clearNotebookUri);
        $scope.notesService.notes.deleted.forEach(clearNotebookUri);

        $scope.notebooksService.delete(notebook);
    };

    //Sets the focus on the editor
    $scope.focusEditor = function(){
        document.getElementsByClassName('CodeMirror')[0].CodeMirror.focus();
    };

    $rootScope.$on('fullScreen', function(){
        ga('send', 'event', 'Notes', 'Full screen');
    });

    //Hide the preview, show only the editor
    $scope.toggleMode = function(mode){
        $scope.displayMode = mode;
        if(mode === $scope.MODE_INPUT_ONLY){
            ga('send', 'event', 'Notes', 'Toggle view', 'Input only');
        }
        else if(mode === $scope.MODE_OUTPUT_ONLY){
            ga('send', 'event', 'Notes', 'Toggle view', 'Output only');
        }
        else if(mode === $scope.MODE_HYBRID){
            ga('send', 'event', 'Notes', 'Toggle view', 'Hybrid');
        }
    };

    /**
     * Log out user, remove all locally stored data (notes, notebooks),
     * restore the editor into initial setup and open modal window for
     * new login.
     */
    $scope.logOut = function () {
        $authService.logout();

        $scope.sideMenuOpen = false;
        $scope.currentNoteIndex = -1;
        $scope.notesService.clearLocalMemory();
        $scope.notebooksService.clearLocalMemory();
        $rootScope.$broadcast('noteChanged', {content: ''});

        $authService.modal()
            .then(function () {
                loadData();
            });

    };

    /**
     * Open modal window for modifiying the account settings.
     */
    $scope.accountSettings = function () {
        $authService.modal('accountSettings');
        $scope.sideMenuOpen = false;
    };

    $scope.markNewsAsRead = function (index) {
        var newsId = $scope.newsCollection[index].id;
        $newsService.markNewsAsRead(newsId);

        $scope.newsCollection.splice(index, 1);
    };

    $scope.handleNetworkError = function(err){
        if(err.status === 401 || err.status === 403){
            $authService.modal();
        }
        else{
            $scope.messageService.add({
                message: 'An error occured while saving the note.',
                class: $scope.messageService.classes.ERROR,
                timeout: 5000
            });
        }
    };
});


text:
angular.module('notes').controller('ModalCtrl', function ($scope, close, $authService, formType) {

    // Form types: login, signup, passwordRecovery, accountSettings
    $scope.formType = (formType != undefined ? formType : 'login');
    $scope.formData = {};

    /**
     * Form function for login and retrieving ApiKey of the user.
     */
    $scope.login = function () {
        $scope.msg = undefined;
        $scope.msgType = undefined;
        $scope.errorFields = undefined;

        $authService.login($scope.formData.username, $scope.formData.password, $scope.formData.rememberMe)
            .then(function (apiKey) {
                close(apiKey);
            })
            .catch(function (response) {
                $scope.msgType = 'error';
                if (response.status == 403) {
                    $scope.msg = 'Username or password doesn\'t match!';
                }else{
                    $scope.msg = 'There was unknown error!';
                }
            });
    };

    /**
     * Form function for creating new user.
     */
    $scope.signup = function () {
        $scope.msg = undefined;
        $scope.msgType = undefined;
        $scope.errorFields = undefined;

        if($scope.formData.password != $scope.formData.passwordAgain) {
            $scope.msg = 'Passwords doesn\'t match!';
            $scope.msgType = 'error';
            $scope.errorFields = ['password', 'passwordAgain'];
            return;
        }

        $authService.signUp($scope.formData.username, $scope.formData.email, $scope.formData.password)
            .then(function (apiKey) {
                close(apiKey);
            })
            .catch(function (response) {
                var error = response.data.error;
                $scope.msgType = 'error';
                $scope.msg = error.message;
                $scope.errorFields = [error.field];
            });
    };

    /**
     * Form function for recovering lost password.
     */
    $scope.passwordRecovery = function () {
        $scope.msg = undefined;
        $scope.msgType = undefined;
        $scope.errorFields = undefined;

        $authService.passwordRecovery($scope.formData.email)
            .then(function () {
                $scope.msg = 'Email was send to your email address with recovery link.';
                $scope.msgType = 'success';
            })
            .catch(function () {
                $scope.msg = 'There was unknown error!';
                $scope.msgType = 'error';
            });
    };

    /**
     * Form function for editing user's account settings.
     */
    $scope.accountSettings = function () {
        $scope.msg = undefined;
        $scope.msgType = undefined;
        $scope.errorFields = undefined;

        if(!$authService.isLoggedIn()){
            $scope.msg = 'You are not logged in!';
            $scope.msgType = 'error';
            return;
        }

        var email = $scope.formData.email;
        var password = $scope.formData.password;
        var passwordAgain = $scope.formData.passwordAgain;
        var oldPassword = $scope.formData.oldPassword;
        var payload = {};

        if(password || passwordAgain || oldPassword) {
            if(!password || !passwordAgain || !oldPassword) {
                $scope.msg = 'If you want to change password, you have to fill in all password fields!';
                $scope.msgType = 'error';
                $scope.errorFields = ['password', 'passwordAgain', 'oldPassword'];
                return;
            }

            if (password != passwordAgain) {
                $scope.msg = 'Password and repeated password has to match!';
                $scope.msgType = 'error';
                $scope.errorFields = ['password', 'passwordAgain'];
                return;
            }

            payload['password'] = password;
            payload['old_password'] = oldPassword;
        }

        if(email) {
            payload['email'] = email;
        }

        $authService.updateAccountSettings(payload)
            .then(function () {
                $scope.msg = 'Settings were successfully changed!';
                $scope.msgType = 'success';
                $scope.formData = {};
            })
            .catch(function (response) {
                var error = response.data.error;
                $scope.msgType = 'error';
                $scope.msg = error.message;
                $scope.errorFields = [error.field];
            });

    };

    /**
     * Function for closing modal window.
     */
    $scope.close = function () {
        close();
    };

    // Resetting data when changing form type
    $scope.$watch('formType', function () {
        $scope.formData = {};
        $scope.msg = undefined;
        $scope.msgType = undefined;
        $scope.errorFields = undefined;
    });
});


text:
    {{note.title}}Untitled note
    <time class='subtitle' relative datetime="{{ note.date_updated }}"></time>