14

I am trying to make a directive for HTML using AngularJS so that I can render Markdown in the browser. What I want is to have a <markdown> tag with a src attribute that will load the file specified and render it correctly.

I have partially implemented this as follows -

function Main($scope) {
    $scope.theContent = '#asgakfgajgfas\n##akfaljfqpo\ndhvkajvlbndvm';
};

angular.module('myApp', [])
    .directive("markdown", function ($compile) {
    return {
        restrict: 'E',
        require: 'model',
        scope: {
            value: "=model"
        },
        template: '<div ng-bind-html-unsafe="value | markdown"></div>'
    };
}).filter('markdown', function () {
    var converter = new Showdown.converter();
    return function (value) {
        return converter.makeHtml(value || '');
    };
});

And the corresponding HTML -

<div ng-controller="Main">
    <markdown model="theContent"></markdown>
</div>

Here is the jsFiddle link(based on John Linquist's example) for the above code. This does not work with the src attribute, however it is able to load a markdown text string specified in a model.

Could you tell me how I can change this code to load the file specified in the src tag. I was thinking of using the $http provided by AngularJS but couldn't get my head around actually using it inside the directive definition.

What I would like to achieve is <markdown src="a/b/c.md" />

ssb
  • 7,422
  • 10
  • 36
  • 61
  • Why mix the Markdown rendering with downloading-stuff-over-HTTP? Do the latter in your controller, e.g. `$scope.theContent = ...` and have your Markdown directive only care about rendering it's `model`. – Philipp Reichart Feb 20 '13 at 14:39
  • 2
    i dont want to have a controller just for loading values for the directives! They dont have any more use, so there is no point making things more complicated than they are! :) – ssb Feb 20 '13 at 14:43

3 Answers3

21

I have finally come up with a solution to this.

// Render markdown in the HTML page
app.directive("markdown", function ($compile, $http) {
    var converter = new Showdown.converter();
    return {
        restrict: 'E',
        replace: true,
        link: function (scope, element, attrs) {
            if ("src" in attrs) {
                $http.get(attrs.src).then(function(data) {
                    element.html(converter.makeHtml(data.data));
                });
            } else {
                element.html(converter.makeHtml(element.text()));
            }
        }
    };
});

I define a custom link function which either takes the content of the <markdown> tags and renders them correctly, or it gets the content of the file given in the src attribute and renders that.

ssb
  • 7,422
  • 10
  • 36
  • 61
  • How would this work in a loop over an array of markdown format string arrays? I tried `

    `, but it appeared unprocessed.
    – Steve May 03 '13 at 12:18
  • 1
    this does not do the binding to a model... you can have a look at [this](http://stackoverflow.com/questions/13688852/data-binding-inside-angularjs-directive) to see how that is done... – ssb May 04 '13 at 18:05
  • Yeah, I realise now that `ng-model` is the correct attribute. I'm such a n00b. That attribute is right there on the front page in the first example. – Steve May 05 '13 at 03:07
  • Note that this bypasses the sanitizer by using low-level angular code. – Benjamin Atkin May 02 '14 at 01:06
6

Another alternative, based on the answer above. This one uses angular eval, watch and sanitize, which means that it is useful for live editing, sanitizes the html, which is necessary as markdown leaves inline html such as a script tag as-is, and uses an angular expression to access the data.

Use

Controller:

 $scope.data = {markdown:"#H1"} 

View:

<div markdown="data.markdown">
</div>

<textarea ng-model="data.markdown"></textarea>

Code:

angular.module('markdown',['ngSanitize']).directive('markdown', function ($sanitize) {
    var converter = new Showdown.converter();
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            function renderMarkdown() {
                var htmlText = converter.makeHtml(scope.$eval(attrs.markdown)  || '');
                element.html($sanitize(htmlText));
            }
            scope.$watch(attrs.markdown, function(){
                renderMarkdown();
            });
            renderMarkdown();
        }
    }
});
Gudlaugur Egilsson
  • 2,420
  • 2
  • 24
  • 23
3

Here is the simplest generic solution I could muster as using $sanatize or $sce is not required:

http://plnkr.co/edit/6OK0RcMChmD1S3JtfItp?p=preview

  <body ng-app="myApp">

    <textarea ng-model="test"></textarea>
    <div markdown="test"></div>
    
    <script src="//code.angularjs.org/1.3.0/angular.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>

    <script>
      angular.module('myApp', [])
        .run(function($rootScope) {
          $rootScope.test = '#foo\r- bar\r- baz'
        })
        .directive('markdown', function () {
              var converter = new Showdown.converter();
              return {
                  restrict: 'A',
                  link: function (scope, element, attrs) {
                      function renderMarkdown() {
                          var htmlText = converter.makeHtml(scope.$eval(attrs.markdown)  || '');
                          element.html(htmlText);
                      }
                      scope.$watch(attrs.markdown, renderMarkdown);
                      renderMarkdown();
                  }
              };
          });
    </script>
  </body>

RESULT:

foo

  • bar
  • baz
Community
  • 1
  • 1
John Culviner
  • 22,235
  • 6
  • 55
  • 51