+ http://jsbeautifier.org/
+
+ Usage:
+ style_html(html_source);
+
+ style_html(html_source, options);
+
+ The options are:
+ indent_inner_html (default false) — indent and sections,
+ indent_size (default 4) — indentation size,
+ indent_char (default space) — character to indent with,
+ wrap_line_length (default 250) - maximum amount of characters per line (0 = disable)
+ brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
+ put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line, or attempt to keep them where they are.
+ unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
+ indent_scripts (default normal) - "keep"|"separate"|"normal"
+ preserve_newlines (default true) - whether existing line breaks before elements should be preserved
+ Only works before elements, not inside tags or for text.
+ max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
+ indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
+ end_with_newline (false) - end with a newline
+ extra_liners (default [head,body,/html]) -List of tags that should have an extra newline before them.
+
+ e.g.
+
+ style_html(html_source, {
+ 'indent_inner_html': false,
+ 'indent_size': 2,
+ 'indent_char': ' ',
+ 'wrap_line_length': 78,
+ 'brace_style': 'expand',
+ 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'],
+ 'preserve_newlines': true,
+ 'max_preserve_newlines': 5,
+ 'indent_handlebars': false,
+ 'extra_liners': ['/html']
+ });
+*/
+
+(function() {
+
+ function trim(s) {
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+
+ function ltrim(s) {
+ return s.replace(/^\s+/g, '');
+ }
+
+ function rtrim(s) {
+ return s.replace(/\s+$/g,'');
+ }
+
+ function style_html(html_source, options, js_beautify, css_beautify) {
+ //Wrapper function to invoke all the necessary constructors and deal with the output.
+
+ var multi_parser,
+ indent_inner_html,
+ indent_size,
+ indent_character,
+ wrap_line_length,
+ brace_style,
+ unformatted,
+ preserve_newlines,
+ max_preserve_newlines,
+ indent_handlebars,
+ wrap_attributes,
+ wrap_attributes_indent_size,
+ end_with_newline,
+ extra_liners,
+ eol;
+
+ options = options || {};
+
+ // backwards compatibility to 1.3.4
+ if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
+ (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
+ options.wrap_line_length = options.max_char;
+ }
+
+ indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
+ indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
+ indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
+ brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
+ wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
+ unformatted = options.unformatted || ['a', 'span', 'img', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd',
+ 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike',
+ 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
+ max_preserve_newlines = preserve_newlines ?
+ (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10))
+ : 0;
+ indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
+ wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes;
+ wrap_attributes_indent_size = (options.wrap_attributes_indent_size === undefined) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10) || indent_size;
+ end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
+ extra_liners = (typeof options.extra_liners == 'object') && options.extra_liners ?
+ options.extra_liners.concat() : (typeof options.extra_liners === 'string') ?
+ options.extra_liners.split(',') : 'head,body,/html'.split(',');
+ eol = options.eol ? options.eol : '\n';
+
+ if(options.indent_with_tabs){
+ indent_character = '\t';
+ indent_size = 1;
+ }
+
+ eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n')
+
+ function Parser() {
+
+ this.pos = 0; //Parser position
+ this.token = '';
+ this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
+ this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
+ parent: 'parent1',
+ parentcount: 1,
+ parent1: ''
+ };
+ this.tag_type = '';
+ this.token_text = this.last_token = this.last_text = this.token_type = '';
+ this.newlines = 0;
+ this.indent_content = indent_inner_html;
+
+ this.Utils = { //Uilities made available to the various functions
+ whitespace: "\n\r\t ".split(''),
+ single_token: 'br,input,link,meta,source,!doctype,basefont,base,area,hr,wbr,param,img,isindex,embed'.split(','), //all the single tags for HTML
+ extra_liners: extra_liners, //for tags that need a line of whitespace before them
+ in_array: function(what, arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (what === arr[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ // Return true if the given text is composed entirely of whitespace.
+ this.is_whitespace = function(text) {
+ for (var n = 0; n < text.length; text++) {
+ if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ this.traverse_whitespace = function() {
+ var input_char = '';
+
+ input_char = this.input.charAt(this.pos);
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ this.newlines = 0;
+ while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
+ this.newlines += 1;
+ }
+
+ this.pos++;
+ input_char = this.input.charAt(this.pos);
+ }
+ return true;
+ }
+ return false;
+ };
+
+ // Append a space to the given content (string array) or, if we are
+ // at the wrap_line_length, append a newline/indentation.
+ this.space_or_wrap = function(content) {
+ if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
+ this.print_newline(false, content);
+ this.print_indentation(content);
+ } else {
+ this.line_char_count++;
+ content.push(' ');
+ }
+ };
+
+ this.get_content = function() { //function to capture regular content between tags
+ var input_char = '',
+ content = [],
+ space = false; //if a space is needed
+
+ while (this.input.charAt(this.pos) !== '<') {
+ if (this.pos >= this.input.length) {
+ return content.length ? content.join('') : ['', 'TK_EOF'];
+ }
+
+ if (this.traverse_whitespace()) {
+ this.space_or_wrap(content);
+ continue;
+ }
+
+ if (indent_handlebars) {
+ // Handlebars parsing is complicated.
+ // {{#foo}} and {{/foo}} are formatted tags.
+ // {{something}} should get treated as content, except:
+ // {{else}} specifically behaves like {{#if}} and {{/if}}
+ var peek3 = this.input.substr(this.pos, 3);
+ if (peek3 === '{{#' || peek3 === '{{/') {
+ // These are tags and not content.
+ break;
+ } else if (peek3 === '{{!') {
+ return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT'];
+ } else if (this.input.substr(this.pos, 2) === '{{') {
+ if (this.get_tag(true) === '{{else}}') {
+ break;
+ }
+ }
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+ this.line_char_count++;
+ content.push(input_char); //letter at-a-time (or string) inserted to an array
+ }
+ return content.length ? content.join('') : '';
+ };
+
+ this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
+ if (this.pos === this.input.length) {
+ return ['', 'TK_EOF'];
+ }
+ var input_char = '';
+ var content = '';
+ var reg_match = new RegExp('' + name + '\\s*>', 'igm');
+ reg_match.lastIndex = this.pos;
+ var reg_array = reg_match.exec(this.input);
+ var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
+ if (this.pos < end_script) { //get everything in between the script tags
+ content = this.input.substring(this.pos, end_script);
+ this.pos = end_script;
+ }
+ return content;
+ };
+
+ this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
+ if (this.tags[tag + 'count']) { //check for the existence of this tag type
+ this.tags[tag + 'count']++;
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+ } else { //otherwise initialize this tag type
+ this.tags[tag + 'count'] = 1;
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+ }
+ this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
+ this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
+ };
+
+ this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
+ if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
+ var temp_parent = this.tags.parent; //check to see if it's a closable tag.
+ while (temp_parent) { //till we reach '' (the initial value);
+ if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
+ break;
+ }
+ temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
+ }
+ if (temp_parent) { //if we caught something
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
+ this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
+ }
+ delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
+ delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
+ if (this.tags[tag + 'count'] === 1) {
+ delete this.tags[tag + 'count'];
+ } else {
+ this.tags[tag + 'count']--;
+ }
+ }
+ };
+
+ this.indent_to_tag = function(tag) {
+ // Match the indentation level to the last use of this tag, but don't remove it.
+ if (!this.tags[tag + 'count']) {
+ return;
+ }
+ var temp_parent = this.tags.parent;
+ while (temp_parent) {
+ if (tag + this.tags[tag + 'count'] === temp_parent) {
+ break;
+ }
+ temp_parent = this.tags[temp_parent + 'parent'];
+ }
+ if (temp_parent) {
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']];
+ }
+ };
+
+ this.get_tag = function(peek) { //function to get a full tag and parse its type
+ var input_char = '',
+ content = [],
+ comment = '',
+ space = false,
+ first_attr = true,
+ tag_start, tag_end,
+ tag_start_char,
+ orig_pos = this.pos,
+ orig_line_char_count = this.line_char_count;
+
+ peek = peek !== undefined ? peek : false;
+
+ do {
+ if (this.pos >= this.input.length) {
+ if (peek) {
+ this.pos = orig_pos;
+ this.line_char_count = orig_line_char_count;
+ }
+ return content.length ? content.join('') : ['', 'TK_EOF'];
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
+ space = true;
+ continue;
+ }
+
+ if (input_char === "'" || input_char === '"') {
+ input_char += this.get_unformatted(input_char);
+ space = true;
+
+ }
+
+ if (input_char === '=') { //no space before =
+ space = false;
+ }
+
+ if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
+ //no space after = or before >
+ this.space_or_wrap(content);
+ space = false;
+ if (!first_attr && wrap_attributes === 'force' && input_char !== '/') {
+ this.print_newline(true, content);
+ this.print_indentation(content);
+ for (var count = 0; count < wrap_attributes_indent_size; count++) {
+ content.push(indent_character);
+ }
+ }
+ for (var i = 0; i < content.length; i++) {
+ if (content[i] === ' ') {
+ first_attr = false;
+ break;
+ }
+ }
+ }
+
+ if (indent_handlebars && tag_start_char === '<') {
+ // When inside an angle-bracket tag, put spaces around
+ // handlebars not inside of strings.
+ if ((input_char + this.input.charAt(this.pos)) === '{{') {
+ input_char += this.get_unformatted('}}');
+ if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
+ input_char = ' ' + input_char;
+ }
+ space = true;
+ }
+ }
+
+ if (input_char === '<' && !tag_start_char) {
+ tag_start = this.pos - 1;
+ tag_start_char = '<';
+ }
+
+ if (indent_handlebars && !tag_start_char) {
+ if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] === '{') {
+ if (input_char === '#' || input_char === '/' || input_char === '!') {
+ tag_start = this.pos - 3;
+ } else {
+ tag_start = this.pos - 2;
+ }
+ tag_start_char = '{';
+ }
+ }
+
+ this.line_char_count++;
+ content.push(input_char); //inserts character at-a-time (or string)
+
+ if (content[1] && (content[1] === '!' || content[1] === '?' || content[1] === '%')) { //if we're in a comment, do something special
+ // We treat all comments as literals, even more than preformatted tags
+ // we just look for the appropriate close tag
+ content = [this.get_comment(tag_start)];
+ break;
+ }
+
+ if (indent_handlebars && content[1] && content[1] === '{' && content[2] && content[2] === '!') { //if we're in a comment, do something special
+ // We treat all comments as literals, even more than preformatted tags
+ // we just look for the appropriate close tag
+ content = [this.get_comment(tag_start)];
+ break;
+ }
+
+ if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
+ break;
+ }
+ } while (input_char !== '>');
+
+ var tag_complete = content.join('');
+ var tag_index;
+ var tag_offset;
+
+ if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+ tag_index = tag_complete.indexOf(' ');
+ } else if (tag_complete.charAt(0) === '{') {
+ tag_index = tag_complete.indexOf('}');
+ } else { //otherwise go with the tag ending
+ tag_index = tag_complete.indexOf('>');
+ }
+ if (tag_complete.charAt(0) === '<' || !indent_handlebars) {
+ tag_offset = 1;
+ } else {
+ tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2;
+ }
+ var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
+ if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
+ this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
+ if (!peek) {
+ this.tag_type = 'SINGLE';
+ }
+ } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') {
+ if (!peek) {
+ this.indent_to_tag('if');
+ this.tag_type = 'HANDLEBARS_ELSE';
+ this.indent_content = true;
+ this.traverse_whitespace();
+ }
+ } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
+ comment = this.get_unformatted('' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
+ content.push(comment);
+ tag_end = this.pos - 1;
+ this.tag_type = 'SINGLE';
+ } else if (tag_check === 'script' &&
+ (tag_complete.search('type') === -1 ||
+ (tag_complete.search('type') > -1 &&
+ tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/) > -1))) {
+ if (!peek) {
+ this.record_tag(tag_check);
+ this.tag_type = 'SCRIPT';
+ }
+ } else if (tag_check === 'style' &&
+ (tag_complete.search('type') === -1 ||
+ (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
+ if (!peek) {
+ this.record_tag(tag_check);
+ this.tag_type = 'STYLE';
+ }
+ } else if (tag_check.charAt(0) === '!') { //peek for ',
+ matched = false;
+
+ this.pos = start_pos;
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ while (this.pos <= this.input.length) {
+ comment += input_char;
+
+ // only need to check for the delimiter if the last chars match
+ if (comment.charAt(comment.length - 1) === delimiter.charAt(delimiter.length - 1) &&
+ comment.indexOf(delimiter) !== -1) {
+ break;
+ }
+
+ // only need to search for custom delimiter for the first few characters
+ if (!matched && comment.length < 10) {
+ if (comment.indexOf('';
+ matched = true;
+ } else if (comment.indexOf('';
+ matched = true;
+ } else if (comment.indexOf('';
+ matched = true;
+ } else if (comment.indexOf('';
+ matched = true;
+ } else if (comment.indexOf('{{!') === 0) { // {{! handlebars comment
+ delimiter = '}}';
+ matched = true;
+ } else if (comment.indexOf('') === 0) { // {{! handlebars comment
+ delimiter = '?>';
+ matched = true;
+ } else if (comment.indexOf('<%') === 0) { // {{! handlebars comment
+ delimiter = '%>';
+ matched = true;
+ }
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+ }
+
+ return comment;
+ };
+
+ this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
+
+ if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+ return '';
+ }
+ var input_char = '';
+ var content = '';
+ var min_index = 0;
+ var space = true;
+ do {
+
+ if (this.pos >= this.input.length) {
+ return content;
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ if (!space) {
+ this.line_char_count--;
+ continue;
+ }
+ if (input_char === '\n' || input_char === '\r') {
+ content += '\n';
+ /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect tags if they are specified in the 'unformatted array'
+ for (var i=0; i]*>\s*$/);
+
+ // if next_tag comes back but is not an isolated tag, then
+ // let's treat the 'a' tag as having content
+ // and respect the unformatted option
+ if (!tag || this.Utils.in_array(tag, unformatted)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
+
+ this.input = js_source || ''; //gets the input for the Parser
+
+ // HACK: newline parsing inconsistent. This brute force normalizes the input.
+ this.input = this.input.replace(/\r\n|[\r\u2028\u2029]/g, '\n')
+
+ this.output = [];
+ this.indent_character = indent_character;
+ this.indent_string = '';
+ this.indent_size = indent_size;
+ this.brace_style = brace_style;
+ this.indent_level = 0;
+ this.wrap_line_length = wrap_line_length;
+ this.line_char_count = 0; //count to see if wrap_line_length was exceeded
+
+ for (var i = 0; i < this.indent_size; i++) {
+ this.indent_string += this.indent_character;
+ }
+
+ this.print_newline = function(force, arr) {
+ this.line_char_count = 0;
+ if (!arr || !arr.length) {
+ return;
+ }
+ if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
+ if ((arr[arr.length - 1] !== '\n')) {
+ arr[arr.length - 1] = rtrim(arr[arr.length - 1]);
+ }
+ arr.push('\n');
+ }
+ };
+
+ this.print_indentation = function(arr) {
+ for (var i = 0; i < this.indent_level; i++) {
+ arr.push(this.indent_string);
+ this.line_char_count += this.indent_string.length;
+ }
+ };
+
+ this.print_token = function(text) {
+ // Avoid printing initial whitespace.
+ if (this.is_whitespace(text) && !this.output.length) {
+ return;
+ }
+ if (text || text !== '') {
+ if (this.output.length && this.output[this.output.length - 1] === '\n') {
+ this.print_indentation(this.output);
+ text = ltrim(text);
+ }
+ }
+ this.print_token_raw(text);
+ };
+
+ this.print_token_raw = function(text) {
+ // If we are going to print newlines, truncate trailing
+ // whitespace, as the newlines will represent the space.
+ if (this.newlines > 0) {
+ text = rtrim(text);
+ }
+
+ if (text && text !== '') {
+ if (text.length > 1 && text.charAt(text.length - 1) === '\n') {
+ // unformatted tags can grab newlines as their last character
+ this.output.push(text.slice(0, -1));
+ this.print_newline(false, this.output);
+ } else {
+ this.output.push(text);
+ }
+ }
+
+ for (var n = 0; n < this.newlines; n++) {
+ this.print_newline(n > 0, this.output);
+ }
+ this.newlines = 0;
+ };
+
+ this.indent = function() {
+ this.indent_level++;
+ };
+
+ this.unindent = function() {
+ if (this.indent_level > 0) {
+ this.indent_level--;
+ }
+ };
+ };
+ return this;
+ }
+
+ /*_____________________--------------------_____________________*/
+
+ multi_parser = new Parser(); //wrapping functions Parser
+ multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
+
+ while (true) {
+ var t = multi_parser.get_token();
+ multi_parser.token_text = t[0];
+ multi_parser.token_type = t[1];
+
+ if (multi_parser.token_type === 'TK_EOF') {
+ break;
+ }
+
+ switch (multi_parser.token_type) {
+ case 'TK_TAG_START':
+ multi_parser.print_newline(false, multi_parser.output);
+ multi_parser.print_token(multi_parser.token_text);
+ if (multi_parser.indent_content) {
+ multi_parser.indent();
+ multi_parser.indent_content = false;
+ }
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_STYLE':
+ case 'TK_TAG_SCRIPT':
+ multi_parser.print_newline(false, multi_parser.output);
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_END':
+ //Print new line only if the tag has no content and has child
+ if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+ var tag_name = multi_parser.token_text.match(/\w+/)[0];
+ var tag_extracted_from_last_output = null;
+ if (multi_parser.output.length) {
+ tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
+ }
+ if (tag_extracted_from_last_output === null ||
+ (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) {
+ multi_parser.print_newline(false, multi_parser.output);
+ }
+ }
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_SINGLE':
+ // Don't add a newline before elements that should remain unformatted.
+ var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i);
+ if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
+ multi_parser.print_newline(false, multi_parser.output);
+ }
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_HANDLEBARS_ELSE':
+ multi_parser.print_token(multi_parser.token_text);
+ if (multi_parser.indent_content) {
+ multi_parser.indent();
+ multi_parser.indent_content = false;
+ }
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_HANDLEBARS_COMMENT':
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'TAG';
+ break;
+ case 'TK_CONTENT':
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'TAG';
+ break;
+ case 'TK_STYLE':
+ case 'TK_SCRIPT':
+ if (multi_parser.token_text !== '') {
+ multi_parser.print_newline(false, multi_parser.output);
+ var text = multi_parser.token_text,
+ _beautifier,
+ script_indent_level = 1;
+ if (multi_parser.token_type === 'TK_SCRIPT') {
+ _beautifier = typeof js_beautify === 'function' && js_beautify;
+ } else if (multi_parser.token_type === 'TK_STYLE') {
+ _beautifier = typeof css_beautify === 'function' && css_beautify;
+ }
+
+ if (options.indent_scripts === "keep") {
+ script_indent_level = 0;
+ } else if (options.indent_scripts === "separate") {
+ script_indent_level = -multi_parser.indent_level;
+ }
+
+ var indentation = multi_parser.get_full_indent(script_indent_level);
+ if (_beautifier) {
+
+ // call the Beautifier if avaliable
+ var Child_options = function() {
+ this.eol = '\n';
+ };
+ Child_options.prototype = options;
+ var child_options = new Child_options();
+ text = _beautifier(text.replace(/^\s*/, indentation), child_options);
+ } else {
+ // simply indent the string otherwise
+ var white = text.match(/^\s*/)[0];
+ var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+ var reindent = multi_parser.get_full_indent(script_indent_level - _level);
+ text = text.replace(/^\s*/, indentation)
+ .replace(/\r\n|\r|\n/g, '\n' + reindent)
+ .replace(/\s+$/, '');
+ }
+ if (text) {
+ multi_parser.print_token_raw(text);
+ multi_parser.print_newline(true, multi_parser.output);
+ }
+ }
+ multi_parser.current_mode = 'TAG';
+ break;
+ default:
+ // We should not be getting here but we don't want to drop input on the floor
+ // Just output the text and move on
+ if (multi_parser.token_text !== '') {
+ multi_parser.print_token(multi_parser.token_text);
+ }
+ break;
+ }
+ multi_parser.last_token = multi_parser.token_type;
+ multi_parser.last_text = multi_parser.token_text;
+ }
+ var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, '');
+
+ // establish end_with_newline
+ if (end_with_newline) {
+ sweet_code += '\n';
+ }
+
+ if (eol != '\n') {
+ sweet_code = sweet_code.replace(/[\n]/g, eol);
+ }
+
+ return sweet_code;
+ }
+
+ if (typeof define === "function" && define.amd) {
+ // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- )
+ define(["require", "./beautify", "./beautify-css"], function(requireamd) {
+ var js_beautify = requireamd("./beautify");
+ var css_beautify = requireamd("./beautify-css");
+
+ return {
+ html_beautify: function(html_source, options) {
+ return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
+ }
+ };
+ });
+ } else if (typeof exports !== "undefined") {
+ // Add support for CommonJS. Just put this file somewhere on your require.paths
+ // and you will be able to `var html_beautify = require("beautify").html_beautify`.
+ var js_beautify = require('./beautify.js');
+ var css_beautify = require('./beautify-css.js');
+
+ exports.html_beautify = function(html_source, options) {
+ return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
+ };
+ } else if (typeof window !== "undefined") {
+ // If we're running a web page and don't have either of the above, add our one global
+ window.html_beautify = function(html_source, options) {
+ return style_html(html_source, options, window.js_beautify, window.css_beautify);
+ };
+ } else if (typeof global !== "undefined") {
+ // If we don't even have window, try global.
+ global.html_beautify = function(html_source, options) {
+ return style_html(html_source, options, global.js_beautify, global.css_beautify);
+ };
+ }
+
+}());
diff --git a/public/plugs/editor/js/simditor-html.js b/public/plugs/editor/js/simditor-html.js
new file mode 100644
index 00000000..a48d3ad6
--- /dev/null
+++ b/public/plugs/editor/js/simditor-html.js
@@ -0,0 +1,104 @@
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define('simditor-html', ["jquery","simditor","html_beautify"], function (a0,b1,c2) {
+ return (root['HTMLButton'] = factory(a0,b1,c2));
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory(require("jquery"),require("simditor"),require("js_beautify"));
+ } else {
+ root['SimditorHTML'] = factory(jQuery,Simditor,html_beautify);
+ }
+}(this, function ($, Simditor, beautify) {
+
+var HTMLButton,
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty,
+ slice = [].slice;
+
+HTMLButton = (function(superClass) {
+ extend(HTMLButton, superClass);
+
+ function HTMLButton() {
+ return HTMLButton.__super__.constructor.apply(this, arguments);
+ }
+
+ HTMLButton.prototype.name = 'html';
+
+ HTMLButton.prototype.icon = 'html5';
+
+ HTMLButton.prototype.needFocus = false;
+
+ HTMLButton.prototype._init = function() {
+ HTMLButton.__super__._init.call(this);
+ this.editor.textarea.on('focus', (function(_this) {
+ return function(e) {
+ return _this.editor.el.addClass('focus').removeClass('error');
+ };
+ })(this));
+ this.editor.textarea.on('blur', (function(_this) {
+ return function(e) {
+ _this.editor.el.removeClass('focus');
+ return _this.editor.setValue(_this.editor.textarea.val());
+ };
+ })(this));
+ return this.editor.textarea.on('input', (function(_this) {
+ return function(e) {
+ return _this._resizeTextarea();
+ };
+ })(this));
+ };
+
+ HTMLButton.prototype.status = function() {};
+
+ HTMLButton.prototype.command = function() {
+ var button, i, len, ref;
+ this.editor.blur();
+ this.editor.el.toggleClass('simditor-html');
+ this.editor.htmlMode = this.editor.el.hasClass('simditor-html');
+ if (this.editor.htmlMode) {
+ this.editor.hidePopover();
+ this.editor.textarea.val(this.beautifyHTML(this.editor.textarea.val()));
+ this._resizeTextarea();
+ } else {
+ this.editor.setValue(this.editor.textarea.val());
+ }
+ ref = this.editor.toolbar.buttons;
+ for (i = 0, len = ref.length; i < len; i++) {
+ button = ref[i];
+ if (button.name === 'html') {
+ button.setActive(this.editor.htmlMode);
+ } else {
+ button.setDisabled(this.editor.htmlMode);
+ }
+ }
+ return null;
+ };
+
+ HTMLButton.prototype.beautifyHTML = function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ if (beautify.html) {
+ return beautify.html.apply(beautify, args);
+ } else {
+ return beautify.apply(null, args);
+ }
+ };
+
+ HTMLButton.prototype._resizeTextarea = function() {
+ this._textareaPadding || (this._textareaPadding = this.editor.textarea.innerHeight() - this.editor.textarea.height());
+ return this.editor.textarea.height(this.editor.textarea[0].scrollHeight - this._textareaPadding);
+ };
+
+ return HTMLButton;
+
+})(Simditor.Button);
+
+Simditor.Toolbar.addButton(HTMLButton);
+
+return HTMLButton;
+
+}));
diff --git a/public/plugs/editor/js/simditor.js b/public/plugs/editor/js/simditor.js
index 056090fb..64354468 100644
--- a/public/plugs/editor/js/simditor.js
+++ b/public/plugs/editor/js/simditor.js
@@ -1894,7 +1894,7 @@ Toolbar = (function(superClass) {
return;
}
if (!$.isArray(this.opts.toolbar)) {
- this.opts.toolbar = ['bold', 'italic', 'underline', 'strikethrough', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent'];
+ this.opts.toolbar = ['html', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent'];
}
this._render();
this.list.on('click', function(e) {