Permalink
Browse files

Merge remote-tracking branch 'tstone/syntax-extensions'

  • Loading branch information...
2 parents 3b9b743 + 0e4c052 commit c0d6f9fb2129852c4e1a1ce95d2897a21588f546 @coreyti coreyti committed Oct 31, 2012
View
100 README.md
@@ -104,6 +104,34 @@ Showdown has been tested successfully with:
In theory, Showdown will work in any browser that supports ECMA 262 3rd Edition (JavaScript 1.5). The converter itself might even work in things that aren't web browsers, like Acrobat. No promises.
+Extensions
+----------
+
+Showdown allows additional functionality to be loaded via extensions.
+
+### Client-side Extension Usage
+
+```js
+<script src="src/showdown.js" />
+<script src="src/extensions/twitter.js" />
+
+var converter = new Showdown().converter({ extensions: 'twitter' });
+```
+
+### Server-side Extension Usage
+
+```js
+// Using a bundled extension
+var Showdown = require('showdown');
+var converter = new Showdown().converter({ extensions: ['twitter'] });
+
+// Using a custom extension
+var mine = require('./custom-extensions/mine');
+var converter = new Showdown().converter({ extensions: ['twitter', mine] });
+```
+
+
+
Known Differences in Output
---------------------------
@@ -203,6 +231,78 @@ Once installed the tests can be run from the project root using:
New test cases can easily be added. Create a markdown file (ending in `.md`) which contains the markdown to test. Create a `.html` file of the exact same name. It will automatically be tested when the tests are executed with `mocha`.
+Creating Markdown Extensions
+----------------------------
+
+A showdown extension is simply a function which returns an array of extensions. Each single extension can be one of two types:
+
+ - Language Extension -- Language extensions are ones that that add new markdown syntax to showdown. For example, say you wanted `^^youtube http://www.youtube.com/watch?v=oHg5SJYRHA0` to automatically render as an embedded YouTube video, that would be a language extension.
+ - Output Modifiers -- After showdown has run, and generated HTML, an output modifier would change that HTML. For example, say you wanted to change `<div class="header">` to be `<header>`, that would be an output modifier.
+
+Each extension can provide two combinations of interfaces for showdown.
+
+#### Regex/Replace
+
+Regex/replace style extensions are very similar to javascripts `string.replace` function. Two properties are given, `regex` and `replace`. `regex` is a string and `replace` can be either a string or a function. If `replace` is a string, it can use the `$1` syntax for group substituation, exactly as if it were making use of `string.replace` (internally it does this actually); The value of `regex` is assumed to be a global replacement.
+
+#### Regex/Replace Example
+
+``` js
+var demo = function(converter) {
+ return [
+ // Replace escaped @ symbols
+ { type: 'lang', regex: '\\@', replace: '@' }
+ ];
+}
+```
+
+#### Filter
+
+Alternately, if you'd just like to do everything yourself, you can specify a filter which is a callback with a single input parameter, text (the current source text within the showdown engine).
+
+#### Filter Example
+
+``` js
+var demo = function(converter) {
+ return [
+ // Replace escaped @ symbols
+ { type: 'lang', function(text) {
+ return text.replace(/\\@/g, '@');
+ }}
+ ];
+}
+```
+
+#### Implementation Concerns
+
+One bit which should be taken into account is maintaining both client-side and server-side compatibility. This can be achieved with a few lines of boilerplate code. First, to prevent polluting the global scope for client-side code, the extension definition should be wrapped in a self executing function.
+
+``` js
+(function(){
+ // Your extension here
+}());
+```
+
+Second, client-side extensions should add a property onto `Showdown.extensions` which matches the name of the file. As an example, a file named `demo.js` should then add `Showdown.extensions.demo`. Server-side extensions can simply export themselves.
+
+``` js
+(function(){
+ var demo = function(converter) {
+ // ... extension code here ...
+ };
+
+ // Client-side export
+ if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.demo = demo; }
+ // Server-side export
+ if (typeof module !== 'undefined') module.exports = demo;
+}());
+```
+
+#### Testing Extensions
+
+The showdown test runner is setup to automatically test cases for extensions. To add test cases for an extension, create a new folder under `./test/extensions` which matches the name of the `.js` file in `./src/extensions`. Place any test cases into the filder using the md/html format and they will automatically be run when tests are run.
+
+
Credits
---------------------------
View
@@ -15,12 +15,16 @@
],
"repository": {
"type": "git",
- "url": "https://github.com/coreyti/showdown.git"
+ "url": "https://github.com/coreyti/showdown.git",
+ "web": "https://github.com/coreyti/showdown"
},
"devDependencies": {
"mocha": "*",
"should": "*"
},
- "licenses": [{ "type": "BSD" }],
+ "licenses": [{
+ "type": "BSD",
+ "url": "https://github.com/coreyti/showdown/raw/master/license.txt"
+ }],
"main": "./src/showdown"
}
@@ -0,0 +1,30 @@
+
+//
+// Google Prettify
+// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
+// hints to showdown's HTML output.
+//
+
+(function(){
+
+ var prettify = function(converter) {
+ return [
+ { type: 'output', filter: function(source){
+
+ return source.replace(/(<pre>)?<code>/gi, function(match, pre) {
+ if (pre) {
+ return '<pre class="prettyprint linenums" tabIndex="0"><code data-inner="1">';
+ } else {
+ return '<code class="prettyprint">';
+ }
+ });
+ }}
+ ];
+ };
+
+ // Client-side export
+ if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.googlePrettify = prettify; }
+ // Server-side export
+ if (typeof module !== 'undefined') module.exports = prettify;
+
+}());
View
@@ -0,0 +1,43 @@
+
+//
+// Twitter Extension
+// @username -> <a href="http://twitter.com/username">@username</a>
+// #hashtag -> <a href="http://twitter.com/search/%23hashtag">#hashtag</a>
+//
+
+(function(){
+
+ var twitter = function(converter) {
+ return [
+
+ // @username syntax
+ { type: 'lang', regex: '\\B(\\\\)?@([\\S]+)\\b', replace: function(match, leadingSlash, username) {
+ // Check if we matched the leading \ and return nothing changed if so
+ if (leadingSlash === '\\') {
+ return match;
+ } else {
+ return '<a href="http://twitter.com/' + username + '">@' + username + '</a>';
+ }
+ }},
+
+ // #hashtag syntax
+ { type: 'lang', regex: '\\B(\\\\)?#([\\S]+)\\b', replace: function(match, leadingSlash, tag) {
+ // Check if we matched the leading \ and return nothing changed if so
+ if (leadingSlash === '\\') {
+ return match;
+ } else {
+ return '<a href="http://twitter.com/search/%23' + tag + '">#' + tag + '</a>';
+ }
+ }},
+
+ // Escaped @'s
+ { type: 'lang', regex: '\\\\@', replace: '@' }
+ ];
+ };
+
+ // Client-side export
+ if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.twitter = twitter; }
+ // Server-side export
+ if (typeof module !== 'undefined') module.exports = twitter;
+
+}());
Oops, something went wrong.

0 comments on commit c0d6f9f

Please sign in to comment.