BlogEngine Extension: Copy Code to Clipboard
// January 7th, 2008 // Blogging, Tools
Since I first started this blog I've been using Colin Coller's CopySourceAsHtml plugin for Visual Studio (which Guy Burstein updated for Visual Studio 2008's RTM). It was a nice little tool but as I started posting more and more code snippets to the blog I found myself wanting some additional features, specifically:
- Easy to use. While it was perfectly simple to copy the source as html, it was a bit more cumbersome to paste it properly into Windows Live Writer. I wound up in HTML mode in WLW everytime I needed to paste some code.
- CSS support. The Html generated by the VS plugin inlines all of the styles and makes it difficult to change the presentation of your code without tweaking the settings of the plugin at posting time.
- Scrollable. This is mostly handled by the above feature, but I wanted to be able to limit the amount of screen real estate taken up by the code while still allowing my readers to view more of the code without having to download any files.
- Copyable. Sometimes you see a piece of code that fits your situation perfectly, but when you copy it you wind up with a bunch of extra Html guck and line numbers at the front of every line you have to manually remove. I want to allow my readers to copy the code without any of that mess.
I found syntaxhighlighter on Google Code pretty quickly and was impressed. I'd seen it around on other people's blogs and loved the "copy to clipboard" feature, but I really wanted a server-side (or pre-posting) highlighter that would take some of the strain off the browser and wanted something with richer WLW integration.
After looking at a few more options I finally found Leo Vildosola's Code Snippet plugin, which matched up with all of my requirements except for the lack of a built-in "copy to clipboard" feature. With some help from clipboard copying javascript posted at WebChicanery I set out to build an extension for BlogEngine.net that would automatically tack on the copy feature to any code I posted via the Code Snippet plugin.
A few hours later, I present the CopyCodeToClipboard extension for BlogEngine.net! Below you will find an example of the extension displaying it's own code (mmm… dogfood) and the css I've used to style the code on my site. I've tried to make the extension css friendly so you can change it's presentation a great deal without modifying the code.
CopyCodeToClipboard.zip – Download (3.82 kb)
Contains all of the below files in one convenient package.
CopyCodeToClipboard.cs – Download (7.36 kb)
This is the actual extension and should be placed into your root/App_Code/Extensions/ folder.
1: using System;
2: using System.Text.RegularExpressions;
3: using BlogEngine.Core;
4: using BlogEngine.Core.Web.Controls;
5:
6: [Extension("Adds a link to syntax highlighted code to copy it to the clipboard.", "1.0", @"<a href=""http://www.squaredroot.com"">Troy Goode</a>")]
7: public class CopyCodeToClipboard
8: {
9:
10: private ExtensionSettings settings;
11:
12: public CopyCodeToClipboard()
13: {
14:
15: Post.Serving += new EventHandler<ServingEventArgs>(Post_PostServing);
16: Post.Saving += new EventHandler<SavedEventArgs>(Post_Saving);
17:
18: ExtensionSettings initialSettings = new ExtensionSettings(GetType().Name);
19: initialSettings.Help = "This extension is written to work with the <a href="http://lvildosola.blogspot.com/2007/02/code-snippet-plugin-for-windows-live.html">Code Snippet Plugin for Windows Live Writer</a> and will not work with other syntax highlighting tools.";
20: initialSettings.AddParameter( "copyText", "Text for 'Copy to Clipboard' button?", 255, true );
21: initialSettings.AddValue( "copyText", "Copy To Clipboard" );
22: initialSettings.AddParameter( "popupText", "Text for 'View in Popup Window' button?", 255, true );
23: initialSettings.AddValue( "popupText", "View Plain" );
24: initialSettings.AddParameter("aboveBelow", "Display above or below code?", 5, true);
25: initialSettings.AddValue("aboveBelow", "Below");
26: initialSettings.AddParameter( "style", "Any additional styling?", int.MaxValue, false );
27: initialSettings.AddValue( "style", "" );
28: initialSettings.AddParameter( "flashFile", "Path to '_clipboard.swf' Flash file?", 255, true );
29: initialSettings.AddValue( "flashFile", "~/_clipboard.swf" );
30: initialSettings.IsScalar = true;
31: ExtensionManager.ImportSettings(initialSettings);
32:
33: settings = ExtensionManager.GetSettings(GetType().Name);
34:
35: }
36:
37: private void Post_Saving(object sender, SavedEventArgs e)
38: {
39: //### executing here will only run the code once per post, but will modify the post itself
40: //InsertCopyCodeLink(e);
41: }
42:
43: private void Post_PostServing(object sender, ServingEventArgs e)
44: {
45: //### executing here will execute the code on every iteration, but will not modify your post
46: InsertCopyCodeLink(e);
47: }
48:
49: private void InsertCopyCodeLink( ServingEventArgs e)
50: {
51: if( !string.IsNullOrEmpty(e.Body) )
52: {
53:
54: //### find code-div
55: string postID = Guid.NewGuid().ToString().Replace( "{", "" ).Replace( "}", "" ).Replace( "-", "" );
56: string toFind = "<div class="csharpcode-wrapper">";
57: int index = e.Body.IndexOf(toFind);
58: while( index != -1 )
59: {
60:
61: //### grab code out of code-div
62: int end = e.Body.IndexOf( "</div>", index ); //### the first </div> should be the end of "csharpcode"
63: end = e.Body.IndexOf( "</div>", end ); //### the next </div> should be the end of "csharpcode-wrapper"
64: string code = e.Body.Substring(index, end - index);
65:
66: //### parse code out of code-div
67: int codeStart = code.IndexOf("<pre ");
68: int codeEnd = code.LastIndexOf("</pre>") + 6;
69: code = code.Substring(codeStart, codeEnd - codeStart);
70: code = Regex.Replace( code, @"<(.|n)*?>", string.Empty ); //### strip html
71: code = code.Replace( " ", "" ); //### remove unnecessary  s from the blank lines
72: code = code.Replace( " ", " " ); //### convert s to spaces
73: code = Regex.Replace( code, @"^(s*)(d+): ", "" ); //### remove line numbers on first line
74: code = Regex.Replace( code, @"(n)(s*)(d+): ", "n" ); //### remove line numbers on subsequent lines
75:
76: //### create copy link
77: string insertScript = @"
78: <script type=""text/javascript"">
79: var copyToClipboard@INDEX = CopyToClipboard_Strip('@CODE');
80: </script>";
81: string insertDiv = @"<div class=""CopyToClipboard"" style=""@STYLE""><div><a href=""javascript:void(0);"" onclick=""CopyToClipboard_ViewPlain(copyToClipboard@INDEX);"">@POPUPTEXT</a> | <a href=""javascript:void(0);"" onclick=""CopyToClipboard_Copy(copyToClipboard@INDEX);"">@COPYTEXT</a></div></div>";
82:
83: //### set values for copy link and insert above/below code
84: string insert = insertDiv + OutputCommonMethods() + insertScript;
85: insert = insert.Replace( "@STYLE", settings.GetSingleValue("style") );
86: insert = insert.Replace( "@POPUPTEXT", settings.GetSingleValue("popupText") );
87: insert = insert.Replace( "@COPYTEXT", settings.GetSingleValue("copyText") );
88: insert = insert.Replace( "@INDEX", postID + "_" + index.ToString() ); //### use index of code-div as a unique ID to allow multiple code-divs on this post
89: insert = insert.Replace( "@CODE", code.Replace( "", "" ).Replace( "'", "'" ).Replace( "rn", "rn" ).Replace( "rnrnrn", "rn" ) );
90: if( settings.GetSingleValue("aboveBelow").ToLower() == "above" )
91: e.Body = e.Body.Insert( index, insert );
92: else
93: e.Body = e.Body.Insert( e.Body.IndexOf( "</div>", end + 1 ) + 6, insert );
94:
95: //### prep index to find next code-div
96: index = index + insert.Length + 1; //### ensure we don't find this same code-div again
97: if( index > e.Body.Length ) break;
98: index = e.Body.IndexOf( toFind, index ); //### find any other code divs
99:
100: }
101: }
102: }
103:
104: private string OutputCommonMethods()
105: {
106:
107: //### only output the following once per page
108: if( System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] == null )
109: {
110: //### add shared javascript
111: string flashPath = System.Web.VirtualPathUtility.ToAbsolute(settings.GetSingleValue("flashFile"));
112: string commonMethods = @"
113: <div id=""CopyToClipboard_Hidden"" style=""display:none;""></div>
114: <div id=""CopyToClipboard_FlashContainer""></div>
115: <script type=""text/javascript"">
116:
117: function CopyToClipboard_Strip( text ){
118: text = text.replace( / /g, ' ' );
119: text = text.replace( /"/g, '""' );
120: text = text.replace( /'/g, '""' );
121: text = text.replace( /&/g, '&' );
122: text = text.replace( /</g, String.fromCharCode(60) );
123: text = text.replace( />/g, String.fromCharCode(62) );
124: return text;
125: }
126:
127: function CopyToClipboard_Copy( text ){
128:
129: //### get reference to utility div
130: var ele = document.getElementById('CopyToClipboard_Hidden');
131:
132: //### the following taken from: http://webchicanery.com/2006/11/14/clipboard-copy-javascript/
133: if (false && window.clipboardData) {
134: window.clipboardData.setData( ""Text"", text );
135: } else {
136: document.getElementById('CopyToClipboard_FlashContainer').innerHTML = '';
137: var divinfo = '<embed id=""CopyToClipboard_FlashFile"" src=""" + flashPath + @""" FlashVars=""clipboard=' + encodeURIComponent(text) + '"" width=""0"" height=""0"" type=""application/x-shockwave-flash""></embed>';
138: document.getElementById('CopyToClipboard_FlashContainer').innerHTML = divinfo;
139: }
140:
141: }
142:
143: function CopyToClipboard_ViewPlain( text ){
144: var win = window.open( '', 'CopyToClipboard_Window', 'width=480, height=480, toolbar=no, menubar=no, scrollbars=auto, resizable=yes, location=no, directories=no, status=no' );
145: win.document.write( '<html><head><title>Code</title><body style=""margin:0;padding:0;""><textarea style=""width:100%;height:100%;border:0;"">' + text + '</textarea></body></html>' );
146: }
147:
148: </script>
149: ";
150: System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] = true;
151: return commonMethods;
152: }
153: else
154: return "";
155:
156: }
157:
158: }
WLWSyntaxHighlighter.css – Download (1.26 kb)
The Css generated by the Windows Live Writer Code Snippet plugin, modified to fit into my blog's colors.
1: .csharpcode-wrapper, .csharpcode-wrapper pre {
2: clear: both;
3: background-color: #f5f5f5;
4: border: solid 2px #A0410D;
5: font-family: Consolas, 'Courier New', Courier, Monospace;
6: font-size: 8pt;
7: line-height: 12pt;
8: margin: 15px 0 0 0px;
9: max-height: 227px;
10: overflow: auto;
11: padding: 0px 0px 0px 0px;
12: width: 454px;
13: }
14: .csharpcode-wrapper pre {
15: border-style: none;
16: margin: 0px 0px 0px 0px;
17: overflow: visible;
18: padding: 0px 0px 0px 0px;
19: }
20: .csharpcode, .csharpcode pre, .csharpcode .alt {
21: background-color: #f5f5f5;
22: border-style: none;
23: color: black;
24: font-family: Consolas, 'Courier New', Courier, Monospace;
25: font-size: 8pt;
26: line-height: 12pt;
27: overflow: visible;
28: padding: 0px 0px 0px 0px;
29: width: 100%;
30: }
31: .csharpcode pre {
32: margin: 0em;
33: }
34: .csharpcode .alt {
35: background-color: #f4f2df;
36: }
37: .csharpcode .asp {
38: background-color: #ffff00;
39: }
40: .csharpcode .attr {
41: color: #ff0000;
42: }
43: .csharpcode .cls {
44: color: #cc6633;
45: }
46: .csharpcode .html {
47: color: #800000;
48: }
49: .csharpcode .kwrd {
50: color: #0000ff;
51: }
52: .csharpcode .lnum {
53: color: #f1c969;
54: }
55: .csharpcode .op {
56: color: #0000c0;
57: }
58: .csharpcode .preproc {
59: color: #cc6633;
60: }
61: .csharpcode .rem {
62: color: #008000;
63: }
64: .csharpcode .str {
65: color: #006080;
66: }
CopyCodeToClipboard.css – Download (588 bytes)
The Css I use to style the little red tab below each code area. This is what you would change to match your blog.
1: .CopyToClipboard{
2: width: 458px;
3: text-align: right;
4: margin: 0 0 5px 0;
5: }
6: .CopyToClipboard div{
7: margin-left: 248px;
8: background-color: #A0410D;
9: padding: 0px 5px 2px 5px;
10: text-align: center;
11: color: #F1C969;
12: width: 200px;
13: font-size: 9px;
14: }
15: .CopyToClipboard a{
16: }
17: .CopyToClipboard a:link{
18: text-decoration: none;
19: color: #f5f5f5;
20: }
21: .CopyToClipboard a:visited{
22: text-decoration: none;
23: color: #f5f5f5;
24: }
25: .CopyToClipboard a:hover{
26: text-decoration: underline;
27: color: #f5f5f5;
28: }
29: .CopyToClipboard a:active{
30: text-decoration: underline;
31: color: #f5f5f5;
32: }
_clipboard.swf – Download (109 bytes)
A Macromedia Flash file used to circumvent Firefox & Safari's nasty habit of blocking javascript from interacting with the clipboard. Created (as best I can tell) by Mark O'Sullivan of http://lussumo.com/.
Update (July 11, 2008):
A release of the Code Snippet plug hasn't been made since the last changes to its source was checked in (in late 2007), so I went ahead and downloaded & built the source. To install the plug place the two DLLs in the zip file from below into Windows Live Writer's /Plugins/ directory.




Well done Troy, I’ll be implementing this extension right away!
Just a quick FYI to everyone:
I would [b]HIGHLY[/b] recommend downloading and installing the latest version (currently unreleased) for the Code Snippet plugin from CodePlex. The last released version available elsewhere has some fairly significant issues with its Html output. You can find the version I’m using here:
http://www.codeplex.com/wlwplugincollection/SourceControl/DownloadSourceCode.aspx?changeSetId=13425
Very cool! BTW, I use the same technique to copy contents to the clipboard. I also have the view source logic in my own blog. I’d be happy to share the javascript I wrote which allows you to keep the highlighting of the code snippet, if you’re interested. Great extension!
Absolutely, Leo, please share!
I sent you an e-mail with the details. Cheers.
I want the new code! =]
Nice extension,
Thanks
http://www.compressjavascript.com
Thanks a lot for your effort Troy.
A small correction in your code.
In method InsertCopyCodeLink method
replace
insert = insert.Replace( “@CODE”, code.Replace( “\\”, “\\\\” ).Replace( “‘”, “\\’” ).Replace( “\r\n”, “\\r\\n” ).Replace( “\\r\\n\\r\\n\\r\\n”, “\\r\\n” ) );
with
insert = insert.Replace( “@CODE”, code.Replace( “\\”, “\\\\” ).Replace( “‘”, “\\’” ).Replace(”\n”,”\\n”).Replace( “\r\\n”, “\\r\\n” ).Replace( “\\r\\n\\r\\n\\r\\n”, “\\r\\n” ) );
I encountered some more problems (Nothing wrong with your code) When I tried integrating this with Minoli.Net csharpFormatter. I will soon update the code and let you know also about the same.
Thanks for the heads up Pradeep. I’ve gone ahead and made the change to the extension I’m using on my site here. I’ll run it for a bit to make sure I don’t encounter any side effects and then roll your change into the download I’m providing.
Thanks again!
What is Blogengine. NET? It is for the first time that I open it, I try, can you gave me an idea about it. thank you.
Copy to Clipboard was useful
Sorry…
link is http://www.webtips.co.in/javascript/copy-to-clipboard-with-javascript-on-mozilla-firefox-and-ie.aspx
@Busby: BlogEngine.net is a web application used to create your own blog. You can learn more at: http://www.dotnetblogengine.net/
@Raja: Thanks Raja, I’ll take a look at that article if I need to do this sort of thing again.
Hi,
Im installing and tweaking BlogEngine.NET at the moment and have tried 2 different code formatter plugins. The bundled one is broken (code file is missing) and the other one (by Alexander Schuc at http://blog.furred.net) is having some minor issue with the TinyMCE html editor. How does your extension get along with TinyMCE or do you use something else? Note: I’m using firefox to edit posts, going to check with IE also…
Oops
Now I get it.. I really like the code presentation, maybe it will even make my code look good!
Guess I’ll check out Live Writer and see where that leads me.
btw, the copy to clipboard function above is nonfunctional for me. (Vista, Firefox 3.0.1)
Also, the Plain text view is garbled, seems like the embedded angle brackets are causing some trouble
Hi Robert,
I’ve also noticed that the copy-to-clipboard feature is no longer working. I’ll have to investigate what has caused this and publish a revised version. I promise it used to work.
Troy
What provider should I select (in LiveWriter) if I want to use Live Writer with my BlogEngine blog?
Marais,
I’m not sure what you mean. When I go to add a new account in LiveWriter the first screen gives you three options (Windows Live, SharePoint, and Other). Select the Other option (”Another weblog service”). Does that answer your question?
You have such a great potential. Keep it up! and thanks for sharing..
http://www.designersclubs.com
Designersclubs
Great extension.. Thanks…
Thanks for this great extension!
thanks