diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js
index eb26d0cad..9ee2d24a4 100644
--- a/app/assets/javascripts/discourse/dialects/dialect.js
+++ b/app/assets/javascripts/discourse/dialects/dialect.js
@@ -3,43 +3,6 @@
   Discourse uses the Markdown.js as its main parser. `Discourse.Dialect` is the framework
   for extending it with additional formatting.
 
-  To extend the dialect, you can register a handler, and you will receive an `event` object
-  with a handle to the markdown `Dialect` from Markdown.js that we are defining. Here's
-  a sample dialect that replaces all occurrences of "evil trout" with a link that says
-  "EVIL TROUT IS AWESOME":
-
-  ```javascript
-
-    Discourse.Dialect.on("register", function(event) {
-      var dialect = event.dialect;
-
-      // To see how this works, review one of our samples or the Markdown.js code:
-      dialect.inline["evil trout"] = function(text) {
-        return ["evil trout".length, ['a', {href: "http://eviltrout.com"}, "EVIL TROUT IS AWESOME"] ];
-      };
-
-    });
-  ```
-
-  You can also manipulate the JsonML tree that is produced by the parser before it converted to HTML.
-  This is useful if the markup you want needs a certain structure of HTML elements. Rather than
-  writing regular expressions to match HTML, consider parsing the tree instead! We use this for
-  making sure a onebox is on one line, as an example.
-
-  This example changes the content of any `<code>` tags.
-
-  The `event.path` attribute contains the current path to the node.
-
-  ```javascript
-    Discourse.Dialect.on("parseNode", function(event) {
-      var node = event.node;
-
-      if (node[0] === 'code') {
-        node[node.length-1] = "EVIL TROUT HACKED YOUR CODE";
-      }
-    });
-  ```
-
 **/
 var parser = window.BetterMarkdown,
     MD = parser.Markdown,
@@ -239,7 +202,75 @@ Discourse.Dialect = {
         return [endPos+stop.length, contents];
       }
     };
+  },
 
+  /**
+    After the parser has been executed, post process any text nodes in the HTML document.
+    This is useful if you want to apply a transformation to the text.
+
+    If you are generating HTML from the text, it is preferable to use the replacer
+    functions and do it in the parsing part of the pipeline. This function is best for
+    simple transformations or transformations that have to happen after all earlier
+    processing is done.
+
+    For example, to convert all text to upper case:
+
+    ```javascript
+
+      Discourse.Dialect.postProcessText(function (text) {
+        return text.toUpperCase();
+      });
+
+    ```
+
+    @method postProcessText
+    @param {Function} emitter The function to call with the text. It returns JsonML to modify the tree.
+  **/
+  postProcessText: function(emitter) {
+    Discourse.Dialect.on("parseNode", function(event) {
+      var node = event.node;
+      if (node.length < 2) { return; }
+
+      for (var j=1; j<node.length; j++) {
+        var textContent = node[j];
+        if (typeof textContent === "string") {
+          var result = emitter(textContent, event);
+          if (result) {
+            if (result instanceof Array) {
+              node.splice.apply(node, [j, 1].concat(result));
+            } else {
+              node[j] = result;
+            }
+
+          }
+        }
+      }
+    });
+  },
+
+  /**
+    After the parser has been executed, change the contents of a HTML tag.
+
+    Let's say you want to replace the contents of all code tags to prepend
+    "EVIL TROUT HACKED YOUR CODE!":
+
+    ```javascript
+      Discourse.Dialect.postProcessTag('code', function (contents) {
+        return "EVIL TROUT HACKED YOUR CODE!\n\n" + contents;
+      });
+    ```
+
+    @method postProcessTag
+    @param {String} tag The HTML tag you want to match on
+    @param {Function} emitter The function to call with the text. It returns JsonML to modify the tree.
+  **/
+  postProcessTag: function(tag, emitter) {
+    Discourse.Dialect.on('parseNode', function (event) {
+      var node = event.node;
+      if (node[0] === tag) {
+        node[node.length-1] = emitter(node[node.length-1]);
+      }
+    });
   }
 
 };
diff --git a/app/assets/javascripts/discourse/dialects/github_code_dialect.js b/app/assets/javascripts/discourse/dialects/github_code_dialect.js
index 2c82cd65d..c7b0b3786 100644
--- a/app/assets/javascripts/discourse/dialects/github_code_dialect.js
+++ b/app/assets/javascripts/discourse/dialects/github_code_dialect.js
@@ -78,56 +78,8 @@ Discourse.Dialect.on("register", function(event) {
 
 });
 
-/**
-  Ensure that content in a code block is fully escaped. This way it's not white listed
-  and we can use HTML and Javascript examples.
-
-  @event parseNode
-  @namespace Discourse.Dialect
-**/
-Discourse.Dialect.on("parseNode", function(event) {
-  var node = event.node;
-
-  if (node[0] === 'code') {
-    node[node.length-1] = Handlebars.Utils.escapeExpression(node[node.length-1]);
-  }
-});
-
-
-Discourse.Dialect.on("parseNode", function(event) {
-
-  var node = event.node,
-      opts = event.dialect.options,
-      insideCounts = event.insideCounts,
-      linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks;
-
-  if (!linebreaks) {
-    // We don't add line breaks inside a pre
-    if (insideCounts.pre > 0) { return; }
-
-    if (node.length > 1) {
-      for (var j=1; j<node.length; j++) {
-        var textContent = node[j];
-
-        if (typeof textContent === "string") {
-
-          if (textContent === "\n") {
-            node[j] = ['br'];
-          } else {
-            var split = textContent.split(/\n+/);
-            if (split.length) {
-              var spliceInstructions = [j, 1];
-              for (var i=0; i<split.length; i++) {
-                if (split[i].length > 0) {
-                  spliceInstructions.push(split[i]);
-                  if (i !== split.length-1) { spliceInstructions.push(['br']); }
-                }
-              }
-              node.splice.apply(node, spliceInstructions);
-            }
-          }
-        }
-      }
-    }
-  }
+// Ensure that content in a code block is fully escaped. This way it's not white listed
+// and we can use HTML and Javascript examples.
+Discourse.Dialect.postProcessTag('code', function (contents) {
+  return Handlebars.Utils.escapeExpression(contents);
 });
diff --git a/app/assets/javascripts/discourse/dialects/newline_dialect.js b/app/assets/javascripts/discourse/dialects/newline_dialect.js
index 3653bdcdb..1d51d4b9c 100644
--- a/app/assets/javascripts/discourse/dialects/newline_dialect.js
+++ b/app/assets/javascripts/discourse/dialects/newline_dialect.js
@@ -1,37 +1,32 @@
 /**
-  Support for the newline behavior in markdown that most expect.
-
-  @event parseNode
-  @namespace Discourse.Dialect
+  Support for the newline behavior in markdown that most expect. Look through all text nodes
+  in the tree, replace any new lines with `br`s.
 **/
-Discourse.Dialect.on("parseNode", function(event) {
-  var node = event.node,
-      opts = event.dialect.options,
+Discourse.Dialect.postProcessText(function (text, event) {
+  var opts = event.dialect.options,
       insideCounts = event.insideCounts,
       linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks;
 
-  if (linebreaks || (insideCounts.pre > 0) || (node.length < 1)) { return; }
+  if (linebreaks || (insideCounts.pre > 0)) { return; }
 
-  for (var j=1; j<node.length; j++) {
-    var textContent = node[j];
+  if (text === "\n") {
+    // If the tage is just a new line, replace it with a `<br>`
+    return [['br']];
+  } else {
 
-    if (typeof textContent === "string") {
-      if (textContent === "\n") {
-        node[j] = ['br'];
-      } else {
-        var split = textContent.split(/\n+/);
-        if (split.length) {
-          var spliceInstructions = [j, 1];
-          for (var i=0; i<split.length; i++) {
-            if (split[i].length > 0) {
-              spliceInstructions.push(split[i]);
-              if (i !== split.length-1) { spliceInstructions.push(['br']); }
-            }
-          }
-          node.splice.apply(node, spliceInstructions);
+    // If the text node contains new lines, perhaps with text between them, insert the
+    // `<br>` tags.
+    var split = text.split(/\n+/);
+    if (split.length) {
+      var replacement = [];
+      for (var i=0; i<split.length; i++) {
+        if (split[i].length > 0) {
+          replacement.push(split[i]);
+          if (i !== split.length-1) { replacement.push(['br']); }
         }
       }
+      return replacement;
     }
   }
 
-});
+});
\ No newline at end of file