1/*global jQuery, console, Markdown*/ 2 3/* 4 * Meltdown (Markup Extra Live Toolbox) 5 * Version: 0.1 (13-FEB-2013) 6 * Requires: jQuery v1.7.2 or later 7 */ 8 9(function (jQuery) { 10 'use strict'; 11 12 var ver, name, dbg; 13 ver = '0.1'; 14 name = 'meltdown'; 15 dbg = true; 16 17 function debug(msg) { 18 if ((typeof console !== 'undefined') && (dbg === true)) { 19 console.log(msg); 20 } 21 } 22 23 function update(previewArea, input) { 24 var mde = Markdown; 25 previewArea.height(input.outerHeight()); 26 previewArea.html(mde(input.val())); 27 } 28 29 function addEventHandler(thees, example, control) { 30 control.click(function (e) { 31 var text, selection, before, placeholder, after, lineStart, lineEnd, charBefore, charAfter; 32 before = example.before || ""; 33 placeholder = example.placeholder || ""; 34 after = example.after || ""; 35 if (typeof thees.surroundSelectedText !== 'undefined') { 36 text = thees.val(); 37 selection = thees.getSelection(); 38 if (example.lineSelect) { 39 lineStart = text.lastIndexOf('\n', selection.start) + 1; 40 lineEnd = text.indexOf('\n', selection.end); 41 if(lineEnd === -1) { 42 lineEnd = text.length; 43 } 44 thees.setSelection(lineStart, lineEnd); 45 selection = thees.getSelection(); 46 } 47 if(selection.length > 0) { 48 placeholder = selection.text; 49 } 50 if (example.isBlock) { 51 for (var i = 0; i < 2; i++) { 52 charBefore = text.charAt(selection.start - 1 - i); 53 charAfter = text.charAt(selection.end + i); 54 if (charBefore !== "\n" && charBefore !== "") { 55 before = "\n" + before; 56 } 57 if (charAfter !== "\n" && charAfter !== "") { 58 after = after + "\n"; 59 } 60 } 61 } 62 if (selection.text !== placeholder) { 63 thees.replaceSelectedText(placeholder, "select"); 64 } 65 thees.surroundSelectedText(before, after, "select"); 66 } else { 67 debug('Failed to load surroundSelectedText'); 68 thees.val(before + placeholder + after + "\n\n" + thees.val()); 69 } 70 e.preventDefault(); 71 thees.focus(); 72 thees.keyup(); 73 }); 74 } 75 76 function buildControls(opts, thees, controls) { 77 var controlList, example, control, tuple, t, groupClass, group, outer, tmpThis; 78 controlList = []; 79 80 for (example in opts.examples) { 81 if (opts.examples.hasOwnProperty(example)) { 82 example = opts.examples[example]; 83 84 control = jQuery('<li><span>' + example.label + '</span></li>'); 85 control.addClass(name + '_control'); 86 if (typeof example.styleClass !== 'undefined') { 87 control.addClass(example.styleClass); 88 } 89 90 control.children(":first").attr('title', example.altText); 91 addEventHandler(thees, example, control); 92 93 tuple = {}; 94 tuple.example = example; 95 tuple.control = control; 96 controlList.push(tuple); 97 } 98 } 99 100 function addClickHandler(outer) { 101 outer.on('click', function () { 102 var element = jQuery(this); 103 element.siblings('li').removeClass(name + '_controlgroup-open').children('ul').hide(); 104 element.toggleClass(name + '_controlgroup-open').children('ul').toggle(); 105 }); 106 } 107 108 for (t in controlList) { 109 if (controlList.hasOwnProperty(t)) { 110 t = controlList[t]; 111 if (t.example.group && t.example.groupLabel) { 112 groupClass = name + "_controlgroup-" + t.example.group; 113 group = controls.find("ul." + groupClass); 114 outer = jQuery('<li />'); 115 if (group.length === 0) { 116 group = jQuery('<ul style="display: none;" />'); 117 group.addClass(name + '_controlgroup-dropdown ' + groupClass); 118 outer.addClass(name + '_controlgroup ' + groupClass); 119 outer.append('<span>' + t.example.groupLabel + ' <i class="meltdown-icon-caret-down"></i></span><b></b>'); 120 outer.append(group); 121 controls.append(outer); 122 } 123 group.append(t.control); 124 addClickHandler(outer); 125 } else { 126 controls.append(t.control); 127 } 128 } 129 } 130 } 131 132 function getAddExampleControl(options, thees, previewArea, example) { 133 var control = jQuery('<li><span>' + example.label + '</span></li>'); 134 control.addClass(name + '_control'); 135 if (typeof example.styleClass !== 'undefined') { 136 control.addClass(example.styleClass); 137 } 138 control.children(":first").attr('title', example.altText); 139 control.on('click', function () { 140 thees.val(example.markdown + "\n\n\n" + thees.val()); 141 thees.keyup(); 142 }); 143 return control; 144 } 145 146 function getPreviewControl(options, thees, previewArea) { 147 var control = jQuery('<li class="' + name + '_control ' + name + '_control-preview"><span title="Show preview">Show preview</span></li>'); 148 control.on('click', function () { 149 150 if (control.hasClass('disabled')) { 151 return; 152 } 153 154 if (!previewArea.is(':visible')) { 155 previewArea.find('.meltdown_preview').height(thees.outerHeight()); 156 if (options.hasEffects) { 157 previewArea.slideToggle(options.previewTimeout); 158 } else { 159 previewArea.fadeIn(); 160 } 161 previewArea.addClass(name + 'visible'); 162 control.children(':eq(0)').text('Hide preview'); 163 control.addClass(name + '_preview-showing'); 164 update(previewArea.children(':eq(1)'), thees); 165 } else { 166 if (options.hasEffects) { 167 previewArea.slideToggle(options.previewTimeout); 168 } else { 169 previewArea.fadeOut(); 170 } 171 previewArea.removeClass(name + 'visible'); 172 control.removeClass(name + '_preview-showing'); 173 control.children(':eq(0)').text('Show preview'); 174 } 175 }); 176 return control; 177 } 178 179 function getExamples() { 180 var key, examples, pounds, i, j; 181 examples = { 182 bold: { 183 label: "B", 184 altText: "Bold", 185 before: "**", 186 after: "**" 187 }, 188 italics: { 189 label: "I", 190 altText: "Italics", 191 before: "*", 192 after: "*" 193 }, 194 ul: { 195 label: "UL", 196 altText: "Unordered List", 197 before: "* ", 198 placeholder: "Item\n* Item", 199 lineSelect: true, 200 isBlock: true 201 }, 202 ol: { 203 label: "OL", 204 altText: "Ordered List", 205 before: "1. ", 206 placeholder: "Item 1\n2. Item 2\n3. Item 3", 207 lineSelect: true, 208 isBlock: true 209 }, 210 table: { 211 label: "Table", 212 altText: "Table", 213 before: "First Header | Second Header\n------------- | -------------\nContent Cell | Content Cell\nContent Cell | Content Cell\n", 214 isBlock: true 215 } 216 }; 217 218 pounds = ""; 219 for (i = 1; i <= 6; i += 1) { 220 pounds = pounds + "#"; 221 examples['h' + i] = { 222 group: "h", 223 groupLabel: "Headers", 224 label: "H" + i, 225 altText: "Header " + i, 226 before: pounds + " ", 227 lineSelect: true 228 }; 229 } 230 231 examples.link = { 232 label: "Link", 233 group: "kitchenSink", 234 groupLabel: "Kitchen Sink", 235 altText: "Link", 236 before: "[", 237 placeholder: "Example link", 238 after: "](http:// \"Link title\")" 239 }; 240 241 examples.img = { 242 label: "Image", 243 group: "kitchenSink", 244 groupLabel: "Kitchen Sink", 245 altText: "Image", 246 before: "![Alt text](", 247 placeholder: "http://", 248 after: ")" 249 }; 250 251 examples.blockquote = { 252 label: "Blockquote", 253 group: "kitchenSink", 254 groupLabel: "Kitchen Sink", 255 altText: "Blockquote", 256 before: "> ", 257 placeholder: "Quoted text", 258 lineSelect: true, 259 isBlock: true 260 }; 261 262 examples.codeblock = { 263 label: "Code Block", 264 group: "kitchenSink", 265 groupLabel: "Kitchen Sink", 266 altText: "Code Block", 267 before: "~~~\n", 268 placeholder: "Code", 269 after: "\n~~~", 270 lineSelect: true, 271 isBlock: true 272 }; 273 274 examples.code = { 275 label: "Code", 276 group: "kitchenSink", 277 groupLabel: "Kitchen Sink", 278 altText: "Inline Code", 279 before: "`", 280 placeholder: "code", 281 after: "`", 282 }; 283 284 examples.footnote = { 285 label: "Footnote", 286 group: "kitchenSink", 287 groupLabel: "Kitchen Sink", 288 altText: "Footnote", 289 before: "[^1]\n\n[^1]:", 290 placeholder: "Example footnote", 291 isBlock: true 292 }; 293 294 examples.hr = { 295 label: "HR", 296 group: "kitchenSink", 297 groupLabel: "Kitchen Sink", 298 altText: "Horizontal Rule", 299 before: "----------", 300 placeholder: "", 301 isBlock: true 302 }; 303 304 for (key in examples) { 305 if (examples.hasOwnProperty(key)) { 306 examples[key].styleClass = name + "_control-" + key; 307 } 308 } 309 310 return examples; 311 } 312 313 function addToolTip(wrap) { 314 var tip, preview; 315 316 preview = wrap.find('.meltdown_control-preview'); 317 if (typeof jQuery.qtip !== 'undefined') { 318 // Disable the preview 319 preview.addClass('disabled'); 320 tip = preview.qtip({ 321 content: "Warning this feature is a tech preview feature.<br/>" 322 + "There is a <a target=\"_blank\" href=\"https://github.com/iphands/Meltdown/issues/1\">known issue</a> with one of the libraries used to generate the live preview.<br/><br/>" 323 + "Live previews <b>can</b> cause the browser tab to stop responding.<br/><br/>" 324 + "This warning will be removed when <a href=\"#\" target=\"_blank\" href=\"https://github.com/iphands/Meltdown/issues/1\">the issue</a> is resolved.<br/></br>" 325 + "<input type=\"button\" class=\"meltdown_control-preview-enabler\" value=\"Click here\"> to remove this warning and enable live previews", 326 show: { 327 delay: 0, 328 when: { 329 event: 'mouseover' 330 } 331 }, 332 hide: { 333 delay: 5000, 334 when: { 335 event: 'mouseout' 336 } 337 }, 338 position: { 339 corner: { 340 target: 'leftMiddle', 341 tooltip: 'rightMiddle' 342 } 343 }, 344 api: { 345 onRender: function () { 346 jQuery('.meltdown_control-preview-enabler').click(function () { 347 tip.qtip('destroy'); 348 jQuery('.meltdown_control-preview').removeClass('disabled'); 349 preview.click(); 350 }); 351 } 352 }, 353 style: { 354 classes: 'meltdown_techpreview-qtip', 355 name: 'dark', 356 lineHeight: '1.3em', 357 padding: '12px', 358 width: { 359 max: 300, 360 min: 0 361 }, 362 tip: true 363 } 364 }); 365 } 366 } 367 368 jQuery.fn.meltdown = function (userOptions) { 369 return this.each(function () { 370 var defaults, opts, thees, wrap, previewWrap, preview, bar, controls; 371 defaults = jQuery.fn.meltdown.defaults; 372 opts = jQuery.extend(true, {}, defaults, userOptions); 373 opts.hasEffects = typeof jQuery.ui !== 'undefined'; 374 375 thees = jQuery(this); 376 thees.wrap('<div class="' + name + '_wrap" />'); 377 thees.before('<div><div style="display: none;" class="' + name + '_preview-wrap"><span class="' + name + '_preview-header">Preview Area (<a class="meltdown_techpreview" href="https://github.com/iphands/Meltdown/issues/1">Tech Preview</a>)</span><div class="' + name + '_preview"></div></div></div><div class="meltdown_bar"><ul class="' + name + '_controls"></ul></div>'); 378 wrap = thees.parent(); 379 previewWrap = wrap.children(':eq(0)').children(':eq(0)'); /* wrapper for the preview area, but not where the updated content goes */ 380 preview = previewWrap.children(':eq(1)'); /* preview area where updates happen */ 381 bar = wrap.children(':eq(1)'); 382 controls = bar.children().first(); 383 384 buildControls(opts, thees, controls); 385 controls.append(getPreviewControl(opts, thees, previewWrap)); 386 387 wrap.width(thees.outerWidth()); 388 preview.height(thees.outerHeight()); 389 390 thees.on('keyup', function (event) { 391 if (previewWrap.is(':visible')) { 392 update(preview, thees); 393 } 394 }); 395 396 addToolTip(wrap); 397 }); 398 }; 399 400 jQuery.fn.meltdown.defaults = { 401 examples: getExamples(), 402 previewTimeout: 400 403 }; 404 405}(jQuery)); 406