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