<commit_before>console.log('Loading DiscountField...')

Spontaneous.FieldTypes.DiscountField = (function($, S) {
	var dom = S.Dom;
	var TextCommand = new JS.Class({
		name: '',
		pre: '',
		post: '',

		extend: {
			get_state: function(input) {
				var start = input[0].selectionStart, end = input[0].selectionEnd, value = $(input).val(),
				before = value.substr(0, start), middle = value.substr(start, (end - start)), after = value.substr(end);
				return {
					start: start,
					end: end,
					before: before,
					middle: middle,
					selection: middle,
					after: after
				}
			}
		},

		initialize: function(input) {
			this.input = input;
		},
		execute: function(event) {
			this.wrap();
		},
		wrap: function() {
			var input = this.input, s = this.fix_selection(), start = s.start, end = s.end,
				before = s.before, middle = s.selection, after = s.after, wrapped;
			if ((end - start) <= 0 ) { return; }
			if (this.matches_selection(middle)) {
				wrapped  = this.remove(middle)
			} else {
				wrapped = this.surround(middle);
			}
			input.val(before + wrapped + after);
			input[0].selectionStart = start;
			input[0].selectionEnd = start + wrapped.length;
		},
		get_state: function() {
			return TextCommand.get_state(this.input);
		},
		fix_selection_whitespace: function(state) {
			var selected = state.selection, m, l;
			m = /^( +)/.exec(selected);
			if (m) {
				l = m[1].length
				state.start += l;
				state.selection = selected.substr(l);
			}
			m = /( +)$/.exec(selected);
			if (m) {
				l = m[1].length
				state.end -= l;
				state.selection = selected.substr(0, selected.length-l);
			}
			return state;
		},
		expand_selection: function(state) {
			state = this.fix_selection_whitespace(state);
			var selected = state.selection, m, start = state.start, end = state.end,
				_pre_ = this.pre.replace(/\*/g, "\\*"), _post_ = this.post.replace(/\*/g, "\\*");
			m = (new RegExp('(?:^| )('+_pre_+'[^('+_pre_+')]*)$', 'm')).exec(state.before)
			if (m) {
				start -= m[1].length;
				selected = m[1] + selected;
			}
			m = (new RegExp('^([^('+_post_+')]*?'+_post_+')[^('+_post_+')\w ]*? ')).exec(state.after)
			if (m) {
				end += m[1].length;
				selected += m[1];
			}
			// fix condition where half of the pre/post markers are selected
			if ((end - start) > 0) {
				if (selected.indexOf(this.pre) !== 0) {
					var sel;
					for (var i = 0, ii = this.pre.length; i < ii; i++) {
						sel = state.before.substr(-(i+1)) + selected;
						if (sel.indexOf(this.pre) === 0) {
							start -= (i+1);
							selected = sel;
							break;
						}
					}
				}
				if (selected.substr(-this.post.length) !== this.post) {
					var sel;
					for (var i = 0, ii = this.post.length; i < ii; i++) {
						sel = selected + state.after.substr(0, (i+1));
						if (sel.substr(-this.post.length) === this.post) {
							end += (i+1);
							selected = sel;
							break;
						}
					}
				}
			}
			return {start: start, end: end, selection:selected};
		},
		fix_selection: function() {
			var state = this.get_state(), change;
			if (!this.matches_selection(state.selection)) {
				change = this.expand_selection(state)
				$.extend(state, change);
				state = this.update_state(state);
			}
			return state;
		},
		update_state: function(state) {
			this.input[0].setSelectionRange(state.start, state.end);
			return this.get_state();
		},
		surround: function(text) {
			return this.pre + text + this.post;
		},
		remove: function(text) {
			return text.substr(this.pre.length, text.length - this.pre.length - this.post.length);
		},
		value: function() {
			return this.input.val();
		},
		button: function() {
			if (!this._button) {
				var b = $(dom.a, {'class':this.name.toLowerCase()}).click(function(event) {
					this.execute(event);
					return false;
				}.bind(this)).text(this.name);
				this._button = b;
			}
			return this._button;
		},
		respond_to_selection: function(state) {
			if (this.matches_selection(state.selection) || this.matches_selection(this.expand_selection(state).selection)) {
				this.activate();
				return true;
			} else {
				this.deactivate();
				return false;
			}
		},
		activate: function() {
			this.button().addClass('active');
		},
		deactivate: function() {
			this.button().removeClass('active');
		},
		matches_removal: function(selection) {
			return this.matches_selection(selection);
		},
		matches_selection: function(selection) {
			return (selection.indexOf(this.pre) === 0 && selection.lastIndexOf(this.post) === (selection.length - this.post.length))
		}
	});

	var Bold = new JS.Class(TextCommand, {
		name: 'Bold',
		pre: '**',
		post: '**'
		// fix_selection: function() {
		// 	var state = this.callSuper();
		// 	return this.get_state();
		// }
	});

	var Italic = new JS.Class(TextCommand, {
		name: 'Italic',
		pre: '_',
		post: '_'
	});

	var UL = new JS.Class(TextCommand, {
		name: 'UL',
		pre: '*',
		post: '',
		br: /\r?\n/,
		strip_bullet: /^ *(\d+\.|\*) */,
		is_list_entry:/(?:\r?\n)( *\*{1} +.+?)$/,
		surround: function(text) {
			var lines = text.split(this.br);
			for (var i = 0, ii = lines.length; i < ii; i++) {
				if (/^\s*$/.test(lines[i])) {
				} else {
					lines[i] = this.bullet_for(i) + lines[i].replace(this.strip_bullet, '');
				}
			}
			return lines.join("\n")
		},
		remove: function(text) {
			var lines = text.split(this.br);
			for (var i = 0, ii = lines.length; i < ii; i++) {
				lines[i] = lines[i].replace(this.strip_bullet, '');
			}
			return lines.join("\n")
		},
		expand_selection: function(state) {
			var selected = (state.selection || ''), m, start = state.start, end = state.end, br = /\r?\n/;
			m = this.strip_bullet.exec(selected);
			if (!m) {
				m = this.is_list_entry.exec(state.before);
				if (m) {
					start -= m[1].length;
					selected = m[1] + selected;
					m = /^(.*?)(?:\r?\n)/.exec(state.after);
					if (m) {
						end += m[1].length;
						selected += m[1];
					}
				}
			}
			return {selection:selected, start:start, end:end};
		},
		bullet_for: function(n) {
			return "* ";
		},
		matches_selection: function(selection) {
			return /^ *\* +/.test(selection)
		}

	});
	var OL = new JS.Class(UL, {
		name: 'OL',
		is_list_entry:/(?:\r?\n)( *\d+\..+?)$/,
		bullet_for: function(n) {
			return (n+1)+". ";
		},
		matches_selection: function(selection) {
			return /^ *\d+\./.test(selection)
		}
	});

	var H1 = new JS.Class(TextCommand, {
		name: "H1",
		pre: '',
		post: "=",
		scale: 1.0,
		surround: function(text) {
			// remove existing header (which must be different from this version)
			if (this.matches_removal(text)) { text = this.remove(text); }
			var line = '', n = Math.floor(this.input.attr('cols')*0.5), newline = /([\r\n]+)$/, newlines = newline.exec(text), undef;
			newlines = (!newlines || (newlines === undef) ? "" : newlines[1])
			for (var i = 0; i < n; i++) { line += this.post; }
			return text.replace(newline, '') + "\n" + line + newlines;
		},
		// removes either h1 or h2
		remove: function(text) {
			var r = new RegExp('[\r\n][=-]+'), s =  text.replace(r, '')
			return s.replace(/ +$/, '');
		},
		// matches either h1 or h2
		matches_removal: function(selection) {
			return (new RegExp('[\r\n][=\\-]+[\r\n ]*$')).exec(selection)
		},
		// matches only the current header class
		matches_selection: function(selection) {
			return (new RegExp('[\r\n]?'+this.post+'+[\r\n ]*$', 'm')).exec(selection)
		},
		expand_selection: function(state) {
			var selected = (state.selection || ''), m, start = state.start, end = state.end, br = /\r?\n/;
			if ((end - start) === 0) {
				// expand to select current line
				m = /(.+)$/.exec(state.before);
				if (m) {
					var s = m[1];
					start -= s.length;
					selected = m[1] + selected;
				}
				m = /^(.+)/.exec(state.after);
				if (m) {
					var s = m[1];
					end += s.length;
					selected += m[1];
				}
			}
			var lines = selected.split(br), underline = new RegExp('^[=-]+$'), found = false;
			for (var i = 0, ii = lines.length; i < ii; i++) {
				var l = lines[i];
				if (underline.test(l)) {
					found = true;
					break;
				}
			}
			if (!found) {
				// expand selection down by one line
				lines = state.after.split(br, 2);
				for (var i = 0, ii = lines.length; i < ii; i++) {
					var l = lines[i];
					if (underline.test(l)) {
						end += l.length + i;
						selected += l;
						break;
					}
				}
			} else {
				// make sure that we have the whole of the underline included in the selection
				var r = new RegExp('^([=-]+)'), m = r.exec(state.after);
				if (m) {
					var extra = m[1];
					end += extra.length;
					selected += m[1];
				}
			}
			return {selection:selected, start:start, end:end};
		}
	});

	var H2 = new JS.Class(H1, {
		name: "H2",
		post: "-",
		scale: 1.2 // hyphens are narrower than equals and narrower than the average char
	});


	var LinkView = new JS.Class(Spontaneous.PopoverView, {
		initialize: function(editor, link_text, url) {
			this.editor = editor;
			this.link_text = link_text;
			this.url = url;
			this.callSuper();
		},
		width: function() {
			return 300;
		},
		position_from_event: function(event) {
			var t = $(event.currentTarget), o = t.offset();
			o.top += t.outerHeight();
			o.left += t.outerWidth() / 2;
			return o
		},
		view: function() {
			var __view = this, w = $(dom.div), text_input, url_input;
			var input = function(value) {
				var i = $(dom.input).keypress(function(event) {
					if (event.charCode === 13) {
						__view.insert_link(text_input, url_input); // sick
						return false;
					}
				}).val(value)
				return i;
			}
			text_input = input(this.link_text);
			url_input = input(this.url);

			cancel = $(dom.a, {'class':'button cancel'}).text('Cancel').click(function() {
				this.close();
				return false;
			}.bind(this)), insert = $(dom.a, {'class':'button'}).text('Insert').click(function() {
				this.insert_link(text_input, url_input);
				return false;
			}.bind(this))
			w.append($(dom.p).append(text_input)).append($(dom.p).append(url_input));
			w.append($(dom.p).append(cancel).append(insert));
			url_input.select();
			return w;
		},
		insert_link: function(text, url) {
			this.editor.insert_link(text.val(), url.val());
			this.close();
		},
		cancel: function() {
			this.close();
		},
		after_close: function() {
			this.editor.dialogue_closed();
		}
	});

	var Link = new JS.Class(TextCommand, {
		name: 'Link',
		link_matcher: /^\[([^\]]+)\]\(([^\)]+)\)$/,
		execute: function(event) {
			var input = this.input, s = this.fix_selection(), start = s.start, end = s.end,
			before = s.before, middle = s.middle, after = s.after, wrapped,
			m = this.link_matcher.exec(middle), text = middle, url;
			if (m) {
				text = m[1];
				url = m[2];
			}
			if (!this._dialogue) {
				this._dialogue = Spontaneous.Popover.open(event, new LinkView(this, text, this.preprocess_url(text, url)));
			} else {
				this._dialogue.close();
				this._dialogue = null;
			}
			this.input.focus();
			return false;
		},
		expand_selection: function(state) {
			state = this.fix_selection_whitespace(state)
			var selected = state.selection, m, start = state.start, end = state.end;

			m = /(\[[^\)]*?)$/.exec(state.before);
			if (m) {
				start -= m[1].length;
				selected = m[1] + selected;
			}
			// TODO: this breaks if ')' in URL...
			m = /^([^\)\[]*?\))/.exec(state.after);
			if (m) {
				end += m[1].length;
				selected += m[1];
			}
			return {selection:selected, start:start, end:end};
		},
		preprocess_url: function(text, url) {
			if (!url) {
				url = this.postprocess_url(String(text)) || '';
			}
			return url;
		},
		postprocess_url: function(url) {
			if (url) {
				if (/^https?:/.test(url)) {
					url = url;
				} else if (/^[a-z-]+\.([a-z-]+\.)*[a-z]{2,}(\/[^ ]*)*$/i.exec(url)) { // look for urls without http:
					url = 'http://' + url;
				} else if (/^[^ @]+@([a-z-]+\.)+[a-z]{2,}$/i.exec(url)) { // email addresses
					url = 'mailto:' + url;
				} else {
					return false
				}
			}
			return url;
		},
		dialogue_closed: function() {
			this._dialogue = null;
			this.input.focus();
		},
		insert_link: function(text, url) {
			url = this.postprocess_url(url) || url;
			var edit = function(input_text) {
				return this.surround_with_link(text, url);
			}.bind(this);
			this.surround = edit;
			this.remove = edit;
			this.wrap();
		},
		surround_with_link: function(text, url) {
			if (url === '') {
				return text;
			} else {
				return '[' + text + '](' + url + ')';
			}
		},
		remove_link: function(text) {
			// we know that the text must match the regexp for us to arrive here
			var m = this.link_matcher.exec(text);
			return m[1];
		},
		matches_selection: function(selection) {
			return this.link_matcher.exec(selection);
		}
	});

	var DiscountField = new JS.Class(Spontaneous.FieldTypes.StringField, {
		actions: [Bold, Italic, H1, H2, UL, OL, Link],
		input: function() {
			if (!this._input) {
				this._input = $(dom.textarea, {'id':this.css_id(), 'name':this.form_name(), 'rows':10, 'cols':90}).text(this.unprocessed_value());
			}
			return this._input;
		},
		on_focus: function() {
			if (!this.expanded) {
				var input = this.input();
				input.data('original-height', input.innerHeight())
				var text_height = input[0].scrollHeight, max_height = 500, resize_height = Math.min(text_height, max_height);
				input.animate({'height':resize_height});
				this.expanded = true;
			}
			this.callSuper();
		},
		on_blur: function() {
			var input = this.input();
			input.animate({ 'height':input.data('original-height') });
			this.expanded = false;
			this.callSuper();
		},
		toolbar: function() {
			if (!this._toolbar) {
				this._wrapper = $(dom.div, {'class':'markdown-editor', 'id':'editor-'+this.css_id()});
				this._toolbar = $(dom.div, {'class':'md-toolbar'});
				this.commands = [];
				var input = this.input();
				for (var i = 0, c = this.actions, ii = c.length; i < ii; i++) {
					var cmd_class = c[i], cmd = new cmd_class(input);
					this.commands.push(cmd);
					this._toolbar.append(cmd.button());
				}
				this._wrapper.append(this._toolbar);
				input.css('height', '');
			}
			return this._wrapper;
		},
		edit: function() {
			this.expanded = false;
			var input = this.input();
			input.unbind('select.markdown').unbind('click.markdown');
			input.bind('select.markdown', this.on_select.bind(this)).bind('click.markdown', this.on_select.bind(this))
			return input;
		},
		close_edit: function() {
			this._input = null;
			this._toolbar = null;
			this.commands = [];
			this.expanded = false;
			this.callSuper();
		},
		// iterates through all the buttons and lets them highlight themselves depending on the
		// currently selected text
		on_select: function(event) {
			var state = TextCommand.get_state(this.input());
			$.each(this.commands, function() { this.deactivate(); });
			for (var i = 0, c = this.commands, ii = c.length; i < ii; i++) {
				if (c[i].respond_to_selection(state)) { /* break */;	}
			}
		}
	});

	return DiscountField;
})(jQuery, Spontaneous);

<commit_msg>tidied up respond_to_selection<commit_after>console.log('Loading DiscountField...')

Spontaneous.FieldTypes.DiscountField = (function($, S) {
	var dom = S.Dom;
	var TextCommand = new JS.Class({
		name: '',
		pre: '',
		post: '',

		extend: {
			get_state: function(input) {
				var start = input[0].selectionStart, end = input[0].selectionEnd, value = $(input).val(),
				before = value.substr(0, start), middle = value.substr(start, (end - start)), after = value.substr(end);
				return {
					start: start,
					end: end,
					before: before,
					middle: middle,
					selection: middle,
					after: after
				}
			}
		},

		initialize: function(input) {
			this.input = input;
		},
		execute: function(event) {
			this.wrap();
		},
		wrap: function() {
			var input = this.input, s = this.fix_selection(), start = s.start, end = s.end,
				before = s.before, middle = s.selection, after = s.after, wrapped;
			if ((end - start) <= 0 ) { return; }
			if (this.matches_selection(middle)) {
				wrapped  = this.remove(middle)
			} else {
				wrapped = this.surround(middle);
			}
			input.val(before + wrapped + after);
			input[0].selectionStart = start;
			input[0].selectionEnd = start + wrapped.length;
		},
		get_state: function() {
			return TextCommand.get_state(this.input);
		},
		fix_selection_whitespace: function(state) {
			var selected = state.selection, m, l;
			m = /^( +)/.exec(selected);
			if (m) {
				l = m[1].length
				state.start += l;
				state.selection = selected.substr(l);
			}
			m = /( +)$/.exec(selected);
			if (m) {
				l = m[1].length
				state.end -= l;
				state.selection = selected.substr(0, selected.length-l);
			}
			return state;
		},
		expand_selection: function(state) {
			state = this.fix_selection_whitespace(state);
			var selected = state.selection, m, start = state.start, end = state.end,
				_pre_ = this.pre.replace(/\*/g, "\\*"), _post_ = this.post.replace(/\*/g, "\\*");
			m = (new RegExp('(?:^| )('+_pre_+'[^('+_pre_+')]*)$', 'm')).exec(state.before)
			if (m) {
				start -= m[1].length;
				selected = m[1] + selected;
			}
			m = (new RegExp('^([^('+_post_+')]*?'+_post_+')[^('+_post_+')\w ]*? ')).exec(state.after)
			if (m) {
				end += m[1].length;
				selected += m[1];
			}
			// fix condition where half of the pre/post markers are selected
			if ((end - start) > 0) {
				if (selected.indexOf(this.pre) !== 0) {
					var sel;
					for (var i = 0, ii = this.pre.length; i < ii; i++) {
						sel = state.before.substr(-(i+1)) + selected;
						if (sel.indexOf(this.pre) === 0) {
							start -= (i+1);
							selected = sel;
							break;
						}
					}
				}
				if (selected.substr(-this.post.length) !== this.post) {
					var sel;
					for (var i = 0, ii = this.post.length; i < ii; i++) {
						sel = selected + state.after.substr(0, (i+1));
						if (sel.substr(-this.post.length) === this.post) {
							end += (i+1);
							selected = sel;
							break;
						}
					}
				}
			}
			return {start: start, end: end, selection:selected};
		},
		fix_selection: function() {
			var state = this.get_state(), change;
			if (!this.matches_selection(state.selection)) {
				change = this.expand_selection(state)
				$.extend(state, change);
				state = this.update_state(state);
			}
			return state;
		},
		update_state: function(state) {
			this.input[0].setSelectionRange(state.start, state.end);
			return this.get_state();
		},
		surround: function(text) {
			return this.pre + text + this.post;
		},
		remove: function(text) {
			return text.substr(this.pre.length, text.length - this.pre.length - this.post.length);
		},
		value: function() {
			return this.input.val();
		},
		button: function() {
			if (!this._button) {
				var b = $(dom.a, {'class':this.name.toLowerCase()}).click(function(event) {
					this.execute(event);
					return false;
				}.bind(this)).text(this.name);
				this._button = b;
			}
			return this._button;
		},
		respond_to_selection: function(state) {
			this.deactivate();
			if (this.matches_selection(state.selection) || this.matches_selection(this.expand_selection(state).selection)) {
				this.activate();
				return true;
			} else {
				this.deactivate();
				return false;
			}
		},
		activate: function() {
			this.button().addClass('active');
		},
		deactivate: function() {
			this.button().removeClass('active');
		},
		matches_removal: function(selection) {
			return this.matches_selection(selection);
		},
		matches_selection: function(selection) {
			return (selection.indexOf(this.pre) === 0 && selection.lastIndexOf(this.post) === (selection.length - this.post.length))
		}
	});

	var Bold = new JS.Class(TextCommand, {
		name: 'Bold',
		pre: '**',
		post: '**'
		// fix_selection: function() {
		// 	var state = this.callSuper();
		// 	return this.get_state();
		// }
	});

	var Italic = new JS.Class(TextCommand, {
		name: 'Italic',
		pre: '_',
		post: '_'
	});

	var UL = new JS.Class(TextCommand, {
		name: 'UL',
		pre: '*',
		post: '',
		br: /\r?\n/,
		strip_bullet: /^ *(\d+\.|\*) */,
		is_list_entry:/(?:\r?\n)( *\*{1} +.+?)$/,
		surround: function(text) {
			var lines = text.split(this.br);
			for (var i = 0, ii = lines.length; i < ii; i++) {
				if (/^\s*$/.test(lines[i])) {
				} else {
					lines[i] = this.bullet_for(i) + lines[i].replace(this.strip_bullet, '');
				}
			}
			return lines.join("\n")
		},
		remove: function(text) {
			var lines = text.split(this.br);
			for (var i = 0, ii = lines.length; i < ii; i++) {
				lines[i] = lines[i].replace(this.strip_bullet, '');
			}
			return lines.join("\n")
		},
		expand_selection: function(state) {
			var selected = (state.selection || ''), m, start = state.start, end = state.end, br = /\r?\n/;
			m = this.strip_bullet.exec(selected);
			if (!m) {
				m = this.is_list_entry.exec(state.before);
				if (m) {
					start -= m[1].length;
					selected = m[1] + selected;
					m = /^(.*?)(?:\r?\n)/.exec(state.after);
					if (m) {
						end += m[1].length;
						selected += m[1];
					}
				}
			}
			return {selection:selected, start:start, end:end};
		},
		bullet_for: function(n) {
			return "* ";
		},
		matches_selection: function(selection) {
			return /^ *\* +/.test(selection)
		}

	});
	var OL = new JS.Class(UL, {
		name: 'OL',
		is_list_entry:/(?:\r?\n)( *\d+\..+?)$/,
		bullet_for: function(n) {
			return (n+1)+". ";
		},
		matches_selection: function(selection) {
			return /^ *\d+\./.test(selection)
		}
	});

	var H1 = new JS.Class(TextCommand, {
		name: "H1",
		pre: '',
		post: "=",
		scale: 1.0,
		surround: function(text) {
			// remove existing header (which must be different from this version)
			if (this.matches_removal(text)) { text = this.remove(text); }
			var line = '', n = Math.floor(this.input.attr('cols')*0.5), newline = /([\r\n]+)$/, newlines = newline.exec(text), undef;
			newlines = (!newlines || (newlines === undef) ? "" : newlines[1])
			for (var i = 0; i < n; i++) { line += this.post; }
			return text.replace(newline, '') + "\n" + line + newlines;
		},
		// removes either h1 or h2
		remove: function(text) {
			var r = new RegExp('[\r\n][=-]+'), s =  text.replace(r, '')
			return s.replace(/ +$/, '');
		},
		// matches either h1 or h2
		matches_removal: function(selection) {
			return (new RegExp('[\r\n][=\\-]+[\r\n ]*$')).exec(selection)
		},
		// matches only the current header class
		matches_selection: function(selection) {
			return (new RegExp('[\r\n]?'+this.post+'+[\r\n ]*$', 'm')).exec(selection)
		},
		expand_selection: function(state) {
			var selected = (state.selection || ''), m, start = state.start, end = state.end, br = /\r?\n/;
			if ((end - start) === 0) {
				// expand to select current line
				m = /(.+)$/.exec(state.before);
				if (m) {
					var s = m[1];
					start -= s.length;
					selected = m[1] + selected;
				}
				m = /^(.+)/.exec(state.after);
				if (m) {
					var s = m[1];
					end += s.length;
					selected += m[1];
				}
			}
			var lines = selected.split(br), underline = new RegExp('^[=-]+$'), found = false;
			for (var i = 0, ii = lines.length; i < ii; i++) {
				var l = lines[i];
				if (underline.test(l)) {
					found = true;
					break;
				}
			}
			if (!found) {
				// expand selection down by one line
				lines = state.after.split(br, 2);
				for (var i = 0, ii = lines.length; i < ii; i++) {
					var l = lines[i];
					if (underline.test(l)) {
						end += l.length + i;
						selected += l;
						break;
					}
				}
			} else {
				// make sure that we have the whole of the underline included in the selection
				var r = new RegExp('^([=-]+)'), m = r.exec(state.after);
				if (m) {
					var extra = m[1];
					end += extra.length;
					selected += m[1];
				}
			}
			return {selection:selected, start:start, end:end};
		}
	});

	var H2 = new JS.Class(H1, {
		name: "H2",
		post: "-",
		scale: 1.2 // hyphens are narrower than equals and narrower than the average char
	});


	var LinkView = new JS.Class(Spontaneous.PopoverView, {
		initialize: function(editor, link_text, url) {
			this.editor = editor;
			this.link_text = link_text;
			this.url = url;
			this.callSuper();
		},
		width: function() {
			return 300;
		},
		position_from_event: function(event) {
			var t = $(event.currentTarget), o = t.offset();
			o.top += t.outerHeight();
			o.left += t.outerWidth() / 2;
			return o
		},
		view: function() {
			var __view = this, w = $(dom.div), text_input, url_input;
			var input = function(value) {
				var i = $(dom.input).keypress(function(event) {
					if (event.charCode === 13) {
						__view.insert_link(text_input, url_input); // sick
						return false;
					}
				}).val(value)
				return i;
			}
			text_input = input(this.link_text);
			url_input = input(this.url);

			cancel = $(dom.a, {'class':'button cancel'}).text('Cancel').click(function() {
				this.close();
				return false;
			}.bind(this)), insert = $(dom.a, {'class':'button'}).text('Insert').click(function() {
				this.insert_link(text_input, url_input);
				return false;
			}.bind(this))
			w.append($(dom.p).append(text_input)).append($(dom.p).append(url_input));
			w.append($(dom.p).append(cancel).append(insert));
			url_input.select();
			return w;
		},
		insert_link: function(text, url) {
			this.editor.insert_link(text.val(), url.val());
			this.close();
		},
		cancel: function() {
			this.close();
		},
		after_close: function() {
			this.editor.dialogue_closed();
		}
	});

	var Link = new JS.Class(TextCommand, {
		name: 'Link',
		link_matcher: /^\[([^\]]+)\]\(([^\)]+)\)$/,
		execute: function(event) {
			var input = this.input, s = this.fix_selection(), start = s.start, end = s.end,
			before = s.before, middle = s.middle, after = s.after, wrapped,
			m = this.link_matcher.exec(middle), text = middle, url;
			if (m) {
				text = m[1];
				url = m[2];
			}
			if (!this._dialogue) {
				this._dialogue = Spontaneous.Popover.open(event, new LinkView(this, text, this.preprocess_url(text, url)));
			} else {
				this._dialogue.close();
				this._dialogue = null;
			}
			this.input.focus();
			return false;
		},
		expand_selection: function(state) {
			state = this.fix_selection_whitespace(state)
			var selected = state.selection, m, start = state.start, end = state.end;

			m = /(\[[^\)]*?)$/.exec(state.before);
			if (m) {
				start -= m[1].length;
				selected = m[1] + selected;
			}
			// TODO: this breaks if ')' in URL...
			m = /^([^\)\[]*?\))/.exec(state.after);
			if (m) {
				end += m[1].length;
				selected += m[1];
			}
			return {selection:selected, start:start, end:end};
		},
		preprocess_url: function(text, url) {
			if (!url) {
				url = this.postprocess_url(String(text)) || '';
			}
			return url;
		},
		postprocess_url: function(url) {
			if (url) {
				if (/^https?:/.test(url)) {
					url = url;
				} else if (/^[a-z-]+\.([a-z-]+\.)*[a-z]{2,}(\/[^ ]*)*$/i.exec(url)) { // look for urls without http:
					url = 'http://' + url;
				} else if (/^[^ @]+@([a-z-]+\.)+[a-z]{2,}$/i.exec(url)) { // email addresses
					url = 'mailto:' + url;
				} else {
					return false
				}
			}
			return url;
		},
		dialogue_closed: function() {
			this._dialogue = null;
			this.input.focus();
		},
		insert_link: function(text, url) {
			url = this.postprocess_url(url) || url;
			var edit = function(input_text) {
				return this.surround_with_link(text, url);
			}.bind(this);
			this.surround = edit;
			this.remove = edit;
			this.wrap();
		},
		surround_with_link: function(text, url) {
			if (url === '') {
				return text;
			} else {
				return '[' + text + '](' + url + ')';
			}
		},
		remove_link: function(text) {
			// we know that the text must match the regexp for us to arrive here
			var m = this.link_matcher.exec(text);
			return m[1];
		},
		matches_selection: function(selection) {
			return this.link_matcher.exec(selection);
		}
	});

	var DiscountField = new JS.Class(Spontaneous.FieldTypes.StringField, {
		actions: [Bold, Italic, H1, H2, UL, OL, Link],
		input: function() {
			if (!this._input) {
				this._input = $(dom.textarea, {'id':this.css_id(), 'name':this.form_name(), 'rows':10, 'cols':90}).text(this.unprocessed_value());
			}
			return this._input;
		},
		on_focus: function() {
			if (!this.expanded) {
				var input = this.input();
				input.data('original-height', input.innerHeight())
				var text_height = input[0].scrollHeight, max_height = 500, resize_height = Math.min(text_height, max_height);
				input.animate({'height':resize_height});
				this.expanded = true;
			}
			this.callSuper();
		},
		on_blur: function() {
			var input = this.input();
			input.animate({ 'height':input.data('original-height') });
			this.expanded = false;
			this.callSuper();
		},
		toolbar: function() {
			if (!this._toolbar) {
				this._wrapper = $(dom.div, {'class':'markdown-editor', 'id':'editor-'+this.css_id()});
				this._toolbar = $(dom.div, {'class':'md-toolbar'});
				this.commands = [];
				var input = this.input();
				for (var i = 0, c = this.actions, ii = c.length; i < ii; i++) {
					var cmd_class = c[i], cmd = new cmd_class(input);
					this.commands.push(cmd);
					this._toolbar.append(cmd.button());
				}
				this._wrapper.append(this._toolbar);
				input.css('height', '');
			}
			return this._wrapper;
		},
		edit: function() {
			this.expanded = false;
			var input = this.input();
			input.unbind('select.markdown').unbind('click.markdown');
			input.bind('select.markdown', this.on_select.bind(this)).bind('click.markdown', this.on_select.bind(this))
			return input;
		},
		close_edit: function() {
			this._input = null;
			this._toolbar = null;
			this.commands = [];
			this.expanded = false;
			this.callSuper();
		},
		// iterates through all the buttons and lets them highlight themselves depending on the
		// currently selected text
		on_select: function(event) {
			var state = TextCommand.get_state(this.input());
			$.each(this.commands, function() {
				this.respond_to_selection(state);
			});
		}
	});

	return DiscountField;
})(jQuery, Spontaneous);


{'fraction_non_alphanumeric': 0.19760578878922272, 'fraction_numerical': 0.005768781886691787, 'mean_word_length': 2.8468445356593124, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4882312', 'n_tokens_mistral': 13099, 'n_tokens_neox': 10772, 'n_words': 2287}