MailChimp for WordPress - Version 1.5.3

Version Description

Fixed admin notice and improved Contact Form 7 integration.

Download this release

Release Info

Developer DvanKooten
Plugin Icon 128x128 MailChimp for WordPress
Version 1.5.3
Comparing to
See all releases

Code changes from version 1.5.1 to 1.5.3

Files changed (70) hide show
  1. assets/css/index.php +5 -5
  2. assets/img/index.php +5 -5
  3. assets/index.php +5 -5
  4. assets/js/admin.js +237 -147
  5. assets/js/beautify-html.js +839 -0
  6. assets/js/index.php +5 -5
  7. includes/class-admin.php +15 -7
  8. includes/class-api.php +27 -1
  9. includes/class-checkbox.php +40 -4
  10. includes/class-form.php +30 -8
  11. includes/class-plugin.php +6 -0
  12. includes/class-widget.php +6 -0
  13. includes/functions.php +6 -0
  14. includes/index.php +5 -5
  15. includes/template-functions.php +7 -1
  16. includes/views/api-settings.php +9 -1
  17. includes/views/checkbox-settings.php +8 -1
  18. includes/views/form-settings.php +11 -2
  19. includes/views/index.php +5 -5
  20. includes/views/parts/admin-field-wizard.php +9 -2
  21. includes/views/parts/admin-footer.php +7 -1
  22. includes/views/parts/admin-need-support.php +7 -1
  23. includes/views/parts/admin-upgrade-to-pro.php +7 -1
  24. includes/views/parts/index.php +5 -5
  25. index.php +2 -2
  26. mailchimp-for-wp.php +4 -4
  27. readme.txt +26 -5
  28. trunk/assets/css/admin.css +0 -82
  29. trunk/assets/css/checkbox.css +0 -24
  30. trunk/assets/css/css.php +0 -32
  31. trunk/assets/css/form-reset.css +0 -76
  32. trunk/assets/css/form-theme-base.css +0 -95
  33. trunk/assets/css/form-theme-blue.css +0 -23
  34. trunk/assets/css/form-theme-dark.css +0 -23
  35. trunk/assets/css/form-theme-green.css +0 -23
  36. trunk/assets/css/form-theme-light.css +0 -13
  37. trunk/assets/css/form-theme-red.css +0 -23
  38. trunk/assets/css/form.css +0 -23
  39. trunk/assets/css/index.php +0 -6
  40. trunk/assets/img/index.php +0 -6
  41. trunk/assets/img/menu-icon.png +0 -0
  42. trunk/assets/index.php +0 -6
  43. trunk/assets/js/admin.js +0 -373
  44. trunk/assets/js/forms.js +0 -136
  45. trunk/assets/js/index.php +0 -6
  46. trunk/assets/js/placeholders.min.js +0 -2
  47. trunk/includes/class-admin.php +0 -275
  48. trunk/includes/class-api.php +0 -133
  49. trunk/includes/class-checkbox.php +0 -424
  50. trunk/includes/class-form.php +0 -383
  51. trunk/includes/class-plugin.php +0 -129
  52. trunk/includes/class-widget.php +0 -73
  53. trunk/includes/functions.php +0 -71
  54. trunk/includes/index.php +0 -6
  55. trunk/includes/template-functions.php +0 -146
  56. trunk/includes/views/api-settings.php +0 -80
  57. trunk/includes/views/checkbox-settings.php +0 -98
  58. trunk/includes/views/form-settings.php +0 -234
  59. trunk/includes/views/index.php +0 -6
  60. trunk/includes/views/parts/admin-field-wizard.php +0 -62
  61. trunk/includes/views/parts/admin-footer.php +0 -5
  62. trunk/includes/views/parts/admin-need-support.php +0 -23
  63. trunk/includes/views/parts/admin-upgrade-to-pro.php +0 -10
  64. trunk/includes/views/parts/index.php +0 -6
  65. trunk/index.php +0 -6
  66. trunk/license.txt +0 -621
  67. trunk/mailchimp-for-wp.php +0 -57
  68. trunk/readme.txt +0 -457
  69. trunk/uninstall.php +0 -14
  70. uninstall.php +3 -2
assets/css/index.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
- // prevent directory listing
3
-
4
- header('HTTP/1.0 403 Forbidden');
5
- header("X-Robots-Tag: noindex");
6
- exit;
1
  <?php
2
+ if( ! defined("MC4WP_LITE_VERSION") ) {
3
+ header( 'Status: 403 Forbidden' );
4
+ header( 'HTTP/1.1 403 Forbidden' );
5
+ exit;
6
+ }
assets/img/index.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
- // prevent directory listing
3
-
4
- header('HTTP/1.0 403 Forbidden');
5
- header("X-Robots-Tag: noindex");
6
- exit;
1
  <?php
2
+ if( ! defined("MC4WP_LITE_VERSION") ) {
3
+ header( 'Status: 403 Forbidden' );
4
+ header( 'HTTP/1.1 403 Forbidden' );
5
+ exit;
6
+ }
assets/index.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
- // prevent directory listing
3
-
4
- header('HTTP/1.0 403 Forbidden');
5
- header("X-Robots-Tag: noindex");
6
- exit;
1
  <?php
2
+ if( ! defined("MC4WP_LITE_VERSION") ) {
3
+ header( 'Status: 403 Forbidden' );
4
+ header( 'HTTP/1.1 403 Forbidden' );
5
+ exit;
6
+ }
assets/js/admin.js CHANGED
@@ -1,12 +1,14 @@
1
  (function($) {
2
 
3
- $("tr.pro-feature td :radio").change(function() {
4
  this.checked = false;
5
  alert("This option is only available in the premium version of MailChimp for WordPress.");
 
6
  });
7
 
8
- $("tr.pro-feature label").click(function() {
9
  alert("This option is only available in the premium version of MailChimp for WordPress.");
 
10
  });
11
 
12
 
@@ -38,97 +40,120 @@
38
  var $placeholder = $("#mc4wp-fw-placeholder");
39
  var $required = $("#mc4wp-fw-required");
40
  var $wrapp = $("#mc4wp-fw-wrap-p");
41
- var field = {
42
- 'type': 'text',
43
- 'name': ''
44
- };
45
  var $codePreview = $("#mc4wp-fw-preview");
46
  // functions
47
 
48
  // set the fields the user can choose from
49
- function setFields()
50
  {
51
- // show notice if no lists selecteed
52
- var $selectedLists = $lists.filter(':checked');
53
- $(".no-lists-selected").toggle(($selectedLists.length == 0));
54
-
55
-
56
  // empty field select
57
  $mailchimpFields.find('option').not('.default').remove();
58
 
59
  // loop through checked lists
60
- $selectedLists.each(function() {
61
- var fields = $(this).data('fields');
62
- var groupings = $(this).data('groupings');
63
 
64
  // loop through merge fields from this list
65
- for(var i = 0; i < fields.length; i++) {
66
- var f = fields[i];
67
 
68
  // add field to select if no similar option exists yet
69
- if($mailchimpMergeFields.find("option[value='"+ f.tag +"']").length == 0) {
70
 
71
- var text = (f.name.length > 40) ? f.name.substring(0, 40) + '..' : f.name;
72
- if(f.req) { text += '*'; }
73
 
74
- // only show first 4 fields
75
- if((i <= 3)) {
76
- var $option = $("<option />").text(text).val(f.tag).data('field', f);
77
- } else {
78
- var $option = $("<option />").text("(PRO ONLY) " + text).val(f.tag).attr('disabled', 'disabled');
 
 
 
 
 
79
  }
80
-
81
  $mailchimpMergeFields.append($option);
82
  }
83
  }
84
 
85
  // loop through interest groupings
86
- for(var i = 0, groupingsCount = groupings.length; i < groupingsCount; i++) {
87
- var grouping = groupings[i];
88
-
89
  // add field to select if no similar option exists yet
90
- if($mailchimpGroupings.find("option[value='"+ grouping.id +"']").length == 0) {
91
- var text = (grouping.name.length > 40) ? grouping.name.substring(0, 40) + '..' : grouping.name;
 
 
 
 
 
 
92
 
93
  // only show 1 grouping
94
- if(i < 1) {
95
- var $option = $("<option />").text(text).val(grouping.id).data('grouping', grouping);
96
- } else {
97
- var $option = $("<option />").text("(PRO ONLY) " + text).val(grouping.id).attr('disabled', 'disabled');
98
  }
99
 
100
  $mailchimpGroupings.append($option);
101
  }
102
-
103
-
104
-
105
  }
106
 
107
 
108
  });
109
  }
110
 
 
 
 
111
  function setPresets()
112
  {
113
  resetFields();
114
 
115
  var selected = $(this).find(':selected');
116
- if(selected.val() == 'submit') {
117
- // setup values for submit field
118
- field['type'] = 'submit';
119
- $valueLabel.text("Button text");
120
- $wizardFields.find('p.row').filter('.value, .wrap-p').show();
121
- updateCodePreview();
122
- } else {
123
- var data = selected.data('field');
124
- if(data) { return setPresetsForField(data); }
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- var data = selected.data('grouping');
127
- if(data) { return setPresetsForGrouping(data); }
 
 
 
 
 
128
  }
129
- return;
 
130
  }
131
 
 
 
 
132
  function resetFields() {
133
  $wizardFields.find('.row :input').each(function() {
134
  if($(this).is(":checkbox")) { this.checked = true; } else { this.value = ''; }
@@ -138,50 +163,98 @@
138
  $multipleValues.find(':input').remove();
139
  $wizardFields.show();
140
 
141
- field['type'] = 'text';
142
- field['name'] = '';
143
  $valueLabel.html("Initial value <small>(optional)</small>");
144
  }
145
 
 
 
 
146
  function addGroupInputs(groups)
147
  {
148
  // add a text input to $multipleValues for each group
149
  for(var i = 0, groupsCount = groups.length; i < groupsCount; i++) {
150
- $("<input />").attr('type', 'text').addClass('widefat').data('value', groups[i].name).attr('placeholder', 'Label for "' + groups[i].name + '" (or leave empty)').attr('value', groups[i].name).appendTo($multipleValues);
 
 
 
 
151
  }
152
  }
153
 
 
 
 
154
  function setPresetsForGrouping(data)
155
  {
156
  $wizardFields.find('p.row').filter('.values, .label, .wrap-p').show();
157
  $label.val(data.name + ":");
158
- field['name'] = 'GROUPINGS['+ data.id + ']';
159
  addGroupInputs(data.groups);
160
 
161
- if(data.form_field == 'radio') {
162
- field['type'] = 'radio';
163
- } else if(data.form_field == 'dropdown') {
164
- field['type'] = 'select';
165
- } else if(data.form_field == 'hidden') {
166
- $wizardFields.find('p.row').filter('.values, .label, .wrap-p').hide();
167
- $wizardFields.find('p.row.value').show();
168
- field['type'] = 'hidden';
169
-
170
- for(var i = 0, groupsCount = data.groups.length; i < groupsCount; i++) {
171
- $value.val($value.val() + data.groups[i].name + ',');
172
- }
173
 
174
- } else {
175
- field['type'] = 'checkbox';
176
- field['name'] = 'GROUPINGS['+ data.id + '][]';
177
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
  // update code preview
180
  updateCodePreview();
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- // show available fields and fill it with some values
 
 
 
 
 
 
 
185
  function setPresetsForField(data)
186
  {
187
 
@@ -193,6 +266,7 @@
193
  'date': [ 'label', 'required', 'wrap-p', 'value']
194
  }
195
 
 
196
  var fieldTypesMap = {
197
  'text': 'text', 'email': 'email', 'phone': 'tel', 'address': 'text', 'number': 'number',
198
  'dropdown': 'select', 'date': 'date', 'birthday': 'date', 'radio': 'radio', 'checkbox': 'checkbox'
@@ -215,11 +289,18 @@
215
  }
216
 
217
  // populate fields with preset values
218
- field['type'] = fieldType;
219
- field['name'] = data.tag;
 
 
220
  $placeholder.val("Your " + data.name.toLowerCase());
 
 
221
  $label.val(data.name + ":");
 
 
222
  $required.attr('checked', data.req);
 
223
  if($multipleValues.is(":visible") && data.choices) {
224
  for(var i = 0, count = data.choices.length; i < count; i++) {
225
  $("<input />").attr('type', 'text').addClass('widefat').data('value', data.choices[i]).attr('placeholder', 'Label for "' + data.choices[i] + '" (or leave empty)').attr('value', data.choices[i]).appendTo($multipleValues);
@@ -230,59 +311,91 @@
230
  updateCodePreview();
231
  }
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  function updateCodePreview()
234
  {
235
  var $code = $("<div></div>");
236
  var inputs = [];
237
  var $input;
238
 
239
- // build input / select / textarea element
240
- if(field['type'] == 'select') {
241
- $input = $("<select />");
242
-
243
- // add options to select
244
- $multipleValues.find(":input").each(function() {
245
- if($(this).val().length > 0) {
246
- $el = $("<option />").val($(this).data("value")).text($(this).val());
247
- $el.appendTo($input);
248
- }
249
- });
250
-
251
- } else if(field['type'] == 'radio' || field['type'] == 'checkbox') {
252
-
253
- // build multiple input values
254
- $multipleValues.find(":input").each(function() {
255
- if($(this).val().length > 0) {
256
- $input = $("<input />").attr('type', field['type']).attr('name', field.name).val($(this).data('value'));
257
 
258
- if($required.is(':visible:checked')) {
259
- $input.attr('required', 'required');
260
- }
261
-
262
- $code.append($input);
263
-
264
- $input.wrap("<label />");
265
- $("<span />").text($(this).val() + ' ').insertAfter($input);
266
- }
267
- });
268
 
269
- } else if(field['type'] == 'textarea') {
270
- $input = $("<textarea />");
271
- } else {
272
- $input = $("<input />").attr('type', field['type']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  }
274
 
275
  // only do this piece when we're not adding radio inputs
276
- if(field['type'] != 'radio' && field['type'] != 'checkbox') {
277
 
278
  // set name attribute
279
- if(field.name.length > 0) {
280
- $input.attr('name', field.name);
281
  }
282
 
283
  // set value
284
  if($value.is(":visible") && $value.val().length > 0) {
285
- if(field['type'] == 'textarea') {
286
  $input.text($value.val());
287
  } else {
288
  $input.attr('value', $value.val());
@@ -296,76 +409,53 @@
296
 
297
  // add required attribute
298
  if($required.is(':visible:checked')) {
299
- $input.attr('required', 'required');
300
  }
301
 
302
- $code.append($input);
303
-
304
-
305
  }
306
 
307
  // build label
308
  if($label.is(":visible") && $label.val().length > 0) {
309
  $("<label />").text($label.val()).prependTo($code);
310
  }
311
-
312
- // start indenting and tabbing of code
313
- var codePreview = $code.html();
314
 
 
315
  if($wrapp.is(':visible:checked')) {
316
  $code.wrapInner($("<p />"));
317
-
318
- // indent code inside paragraphs (double tab)
319
- codePreview = $code.html()
320
- .replace(/<p>/gi, "<p>\n\t")
321
- .replace(/<label><input /gi, "\n\t<label><input ")
322
- .replace(/<\/label><input/gi, "</label> \n\t<input")
323
- .replace(/<select /gi, "\n\t<select ")
324
- .replace(/<\/select>/gi, "\n\t</select>")
325
- .replace(/<\/span><\/label>/gi, "</span>\n\t</label> \n")
326
- .replace(/<option /gi, "\n\t\t<option ")
327
- .replace(/<label><input type="radio"/g, "<label>\n\t\t<input type=\"radio\"")
328
- .replace(/<label><input type="checkbox"/g, "<label>\n\t\t<input type=\"checkbox\"")
329
- .replace(/<span>/gi, "\n\t\t<span>")
330
- } else {
331
- // indent code, single tab
332
- codePreview = codePreview
333
- .replace(/<option /gi, "\n\t<option ")
334
- .replace(/<label><input type="radio"/g, "<label>\n\t<input type=\"radio\"")
335
- .replace(/<label><input type="checkbox"/g, "<label>\n\t<input type=\"checkbox\"")
336
- .replace(/<span>/gi, "\n\t<span>");
337
  }
338
-
339
- // newline after every closed element
340
- codePreview = codePreview.replace(/></g, "> \n<");
341
-
342
- // add code to codePreview textarea
343
- $codePreview.val(codePreview);
344
  }
345
 
 
 
 
346
  function addCodeToFormMarkup() {
347
 
348
  var result = false;
349
 
350
  // try to insert in QuickTags editor at cursor position
351
- if(typeof QTags !='undefined' && QTags.insertContent) {
352
  result = QTags.insertContent($codePreview.val());
353
  }
354
 
355
- // fallback
356
  if(!result) {
357
- $("#mc4wpformmarkup").val($("#mc4wpformmarkup").val() + "\n" + $codePreview.val());
 
358
  }
359
  }
360
 
361
  // setup events
362
- $lists.change(setFields);
363
  $mailchimpFields.change(setPresets);
364
  $wizardFields.change(updateCodePreview);
365
  $("#mc4wp-fw-add-to-form").click(addCodeToFormMarkup);
366
 
367
  // init
368
- setFields();
369
 
370
  })();
371
 
1
  (function($) {
2
 
3
+ $("tr.pro-feature, tr.pro-feature td :radio").change(function() {
4
  this.checked = false;
5
  alert("This option is only available in the premium version of MailChimp for WordPress.");
6
+ event.stopPropagation();
7
  });
8
 
9
+ $("tr.pro-feature, tr.pro-feature label").click(function() {
10
  alert("This option is only available in the premium version of MailChimp for WordPress.");
11
+ event.stopPropagation();
12
  });
13
 
14
 
40
  var $placeholder = $("#mc4wp-fw-placeholder");
41
  var $required = $("#mc4wp-fw-required");
42
  var $wrapp = $("#mc4wp-fw-wrap-p");
43
+ var fieldType, fieldName;
 
 
 
44
  var $codePreview = $("#mc4wp-fw-preview");
45
  // functions
46
 
47
  // set the fields the user can choose from
48
+ function setMailChimpFields()
49
  {
 
 
 
 
 
50
  // empty field select
51
  $mailchimpFields.find('option').not('.default').remove();
52
 
53
  // loop through checked lists
54
+ $lists.filter(':checked').each(function() {
55
+ var listFields = $(this).data('list-fields');
56
+ var listGroupings = $(this).data('list-groupings');
57
 
58
  // loop through merge fields from this list
59
+ for(var i = 0, fieldCount = listFields.length; i < fieldCount; i++) {
60
+ var listField = listFields[i];
61
 
62
  // add field to select if no similar option exists yet
63
+ if($mailchimpMergeFields.find("option[value='"+ listField.tag +"']").length == 0) {
64
 
65
+ var text = (listField.name.length > 25) ? listField.name.substring(0, 25) + '..' : listField.name;
66
+ if(listField.req) { text += '*'; }
67
 
68
+ var $option = $("<option />")
69
+ .text(text)
70
+ .val(listField.tag)
71
+ .data('list-field', listField);
72
+
73
+ // only enable 3 fields
74
+ if(i > 3) {
75
+ $option.text("(PRO ONLY) " + text)
76
+ .attr('disabled', 'disabled')
77
+ .data('field', null);
78
  }
79
+
80
  $mailchimpMergeFields.append($option);
81
  }
82
  }
83
 
84
  // loop through interest groupings
85
+ for(var i = 0, groupingsCount = listGroupings.length; i < groupingsCount; i++) {
86
+ var listGrouping = listGroupings[i];
87
+
88
  // add field to select if no similar option exists yet
89
+ if($mailchimpGroupings.find("option[value='"+ listGrouping.id +"']").length == 0) {
90
+ var text = (listGrouping.name.length > 25) ? listGrouping.name.substring(0, 25) + '..' : listGrouping.name;
91
+
92
+ // build option HTML
93
+ var $option = $("<option />")
94
+ .text(text)
95
+ .val(listGrouping.id)
96
+ .data('list-grouping', listGrouping);
97
 
98
  // only show 1 grouping
99
+ if(i >= 1) {
100
+ $option.text("(PRO ONLY) " + text)
101
+ .attr('disabled', 'disabled')
102
+ .data('list-grouping', null);
103
  }
104
 
105
  $mailchimpGroupings.append($option);
106
  }
 
 
 
107
  }
108
 
109
 
110
  });
111
  }
112
 
113
+ /**
114
+ * Set Presets
115
+ */
116
  function setPresets()
117
  {
118
  resetFields();
119
 
120
  var selected = $(this).find(':selected');
121
+ switch( selected.val() ) {
122
+
123
+ case 'submit':
124
+ fieldType = 'submit';
125
+ $valueLabel.text("Button text");
126
+ $wizardFields.find('p.row').filter('.value, .wrap-p').show();
127
+ break;
128
+
129
+ case 'lists':
130
+ fieldType = 'lists';
131
+ $wizardFields.find('.wrap-p').show();
132
+ updateCodePreview();
133
+ break;
134
+
135
+ default:
136
+ // try data for MailChimp field
137
+ var data = selected.data('list-field');
138
+ if(data) {
139
+ return setPresetsForField(data);
140
+ }
141
 
142
+ // try data for interest grouping
143
+ var data = selected.data('list-grouping');
144
+ if(data) {
145
+ return setPresetsForGrouping(data);
146
+ }
147
+
148
+ break;
149
  }
150
+
151
+ updateCodePreview();
152
  }
153
 
154
+ /**
155
+ * Resets all wizard fields back to their default state
156
+ */
157
  function resetFields() {
158
  $wizardFields.find('.row :input').each(function() {
159
  if($(this).is(":checkbox")) { this.checked = true; } else { this.value = ''; }
163
  $multipleValues.find(':input').remove();
164
  $wizardFields.show();
165
 
166
+ fieldType = 'text';
167
+ fieldName = '';
168
  $valueLabel.html("Initial value <small>(optional)</small>");
169
  }
170
 
171
+ /**
172
+ * Add inputs for each group
173
+ */
174
  function addGroupInputs(groups)
175
  {
176
  // add a text input to $multipleValues for each group
177
  for(var i = 0, groupsCount = groups.length; i < groupsCount; i++) {
178
+ $("<input />").attr('type', 'text')
179
+ .addClass('widefat').data('value', groups[i].name)
180
+ .attr('placeholder', 'Label for "' + groups[i].name + '" (or leave empty)')
181
+ .attr('value', groups[i].name)
182
+ .appendTo($multipleValues);
183
  }
184
  }
185
 
186
+ /**
187
+ * Set presets for interest groupings
188
+ */
189
  function setPresetsForGrouping(data)
190
  {
191
  $wizardFields.find('p.row').filter('.values, .label, .wrap-p').show();
192
  $label.val(data.name + ":");
193
+ fieldName = 'GROUPINGS[' + data.id + ']';
194
  addGroupInputs(data.groups);
195
 
196
+ switch(data.form_field) {
197
+ case 'radio':
198
+ fieldType = 'radio';
199
+ break;
 
 
 
 
 
 
 
 
200
 
201
+ case 'hidden':
202
+ // hide all rows except value row
203
+ $wizardFields.find('p.row').filter('.values, .label, .wrap-p').hide();
204
+ $wizardFields.find('p.row.value').show();
205
+
206
+ // add group name to hidden input value
207
+ for(var i = 0, groupsCount = data.groups.length; i < groupsCount; i++) {
208
+ $value.val($value.val() + data.groups[i].name + ',');
209
+ }
210
+
211
+ fieldType = 'hidden';
212
+ break;
213
+
214
+ case 'dropdown':
215
+ fieldType = 'select';
216
+ break;
217
+
218
+ default:
219
+ fieldType = 'checkbox';
220
+
221
+ // turn field name into an array
222
+ fieldName += '[]';
223
+ break;
224
+ }
225
 
226
  // update code preview
227
  updateCodePreview();
228
  }
229
 
230
+ /**
231
+ * Build list choice HTML
232
+ */
233
+ function getListChoiceHTML()
234
+ {
235
+ var html = '';
236
+ $lists.each(function() {
237
+ var list_id = $(this).val();
238
+ var list_name = $(this).parent('label').text();
239
+ var attrs = '';
240
+
241
+ if($(this).is(':checked')) {
242
+ attrs += 'checked ';
243
+ }
244
+
245
+ html += '<label>' + "\n"
246
+ html += "\t" + '<input type="checkbox" name="_mc4wp_lists[]" value="' + list_id +'" '+ attrs + ' /> '+ list_name + "\n";
247
+ html += '</label>' + "\n";
248
+ });
249
 
250
+ return html;
251
+ }
252
+
253
+
254
+
255
+ /**
256
+ * Set presets for a fields
257
+ */
258
  function setPresetsForField(data)
259
  {
260
 
266
  'date': [ 'label', 'required', 'wrap-p', 'value']
267
  }
268
 
269
+ // map MailChimp field types to HTML5 field type
270
  var fieldTypesMap = {
271
  'text': 'text', 'email': 'email', 'phone': 'tel', 'address': 'text', 'number': 'number',
272
  'dropdown': 'select', 'date': 'date', 'birthday': 'date', 'radio': 'radio', 'checkbox': 'checkbox'
289
  }
290
 
291
  // populate fields with preset values
292
+ fieldType = fieldType;
293
+ fieldName = data.tag;
294
+
295
+ // set placeholder text
296
  $placeholder.val("Your " + data.name.toLowerCase());
297
+
298
+ // set label text
299
  $label.val(data.name + ":");
300
+
301
+ // set required attribute
302
  $required.attr('checked', data.req);
303
+
304
  if($multipleValues.is(":visible") && data.choices) {
305
  for(var i = 0, count = data.choices.length; i < count; i++) {
306
  $("<input />").attr('type', 'text').addClass('widefat').data('value', data.choices[i]).attr('placeholder', 'Label for "' + data.choices[i] + '" (or leave empty)').attr('value', data.choices[i]).appendTo($multipleValues);
311
  updateCodePreview();
312
  }
313
 
314
+ /**
315
+ * Format and indent the generated HTML
316
+ * Then add it to the code preview textarea
317
+ */
318
+ function setCodePreview(html) {
319
+ html = html_beautify(html);
320
+ $codePreview.val(html);
321
+ }
322
+
323
+
324
+ /**
325
+ * Generate HTML based on the various field values
326
+ */
327
  function updateCodePreview()
328
  {
329
  var $code = $("<div></div>");
330
  var inputs = [];
331
  var $input;
332
 
333
+ switch(fieldType) {
334
+ // MailChimp lists
335
+ case 'lists':
336
+ var html = getListChoiceHTML();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
+ if($wrapp.is(':visible:checked')) {
339
+ html = "<p>" + html + "</p>";
340
+ }
 
 
 
 
 
 
 
341
 
342
+ return setCodePreview(html);
343
+ break;
344
+
345
+ // MailChimp dropdown
346
+ case 'select':
347
+ $input = $("<select />");
348
+
349
+ // add options to select
350
+ $multipleValues.find(":input").each(function() {
351
+ if($(this).val().length > 0) {
352
+ $el = $("<option />").val($(this).data("value")).text($(this).val());
353
+ $el.appendTo($input);
354
+ }
355
+ });
356
+ break;
357
+
358
+ // MailChimo choices
359
+ case 'radio':
360
+ case 'checkbox':
361
+ // build multiple input values
362
+ $multipleValues.find(":input").each(function() {
363
+ if($(this).val().length > 0) {
364
+ $input = $("<input />").attr('type', fieldType).attr('name', fieldName).val($(this).data('value'));
365
+
366
+ if($required.is(':visible:checked')) {
367
+ $input.attr('required', true);
368
+ }
369
+
370
+ $code.append($input);
371
+
372
+ $input.wrap("<label />");
373
+ $("<span />").text($(this).val() + ' ').insertAfter($input);
374
+ }
375
+ });
376
+ break;
377
+
378
+ // MailChimp long text
379
+ case 'textarea':
380
+ $input = $("<textarea />");
381
+ break;
382
+
383
+ default:
384
+ $input = $("<input />").attr('type', fieldType);
385
+ break;
386
  }
387
 
388
  // only do this piece when we're not adding radio inputs
389
+ if(fieldType != 'radio' && fieldType != 'checkbox') {
390
 
391
  // set name attribute
392
+ if(fieldName.length > 0) {
393
+ $input.attr('name', fieldName);
394
  }
395
 
396
  // set value
397
  if($value.is(":visible") && $value.val().length > 0) {
398
+ if(fieldType == 'textarea') {
399
  $input.text($value.val());
400
  } else {
401
  $input.attr('value', $value.val());
409
 
410
  // add required attribute
411
  if($required.is(':visible:checked')) {
412
+ $input.attr('required', true);
413
  }
414
 
415
+ $code.append($input);
 
 
416
  }
417
 
418
  // build label
419
  if($label.is(":visible") && $label.val().length > 0) {
420
  $("<label />").text($label.val()).prependTo($code);
421
  }
 
 
 
422
 
423
+ // wrap in paragraphs?
424
  if($wrapp.is(':visible:checked')) {
425
  $code.wrapInner($("<p />"));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  }
427
+
428
+ var html = $code.html();
429
+ setCodePreview(html);
 
 
 
430
  }
431
 
432
+ /**
433
+ * Transfer code preview field to form mark-up
434
+ */
435
  function addCodeToFormMarkup() {
436
 
437
  var result = false;
438
 
439
  // try to insert in QuickTags editor at cursor position
440
+ if(typeof wpActiveEditor != 'undefined' && typeof QTags != 'undefined' && QTags.insertContent) {
441
  result = QTags.insertContent($codePreview.val());
442
  }
443
 
444
+ // fallback, just append
445
  if(!result) {
446
+ $formContent = $("#mc4wpformmarkup");
447
+ $formContent.val($formContent.val() + "\n" + $codePreview.val());
448
  }
449
  }
450
 
451
  // setup events
452
+ $lists.change(setMailChimpFields);
453
  $mailchimpFields.change(setPresets);
454
  $wizardFields.change(updateCodePreview);
455
  $("#mc4wp-fw-add-to-form").click(addCodeToFormMarkup);
456
 
457
  // init
458
+ setMailChimpFields();
459
 
460
  })();
461
 
assets/js/beautify-html.js ADDED
@@ -0,0 +1,839 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
2
+ /*
3
+
4
+ The MIT License (MIT)
5
+
6
+ Copyright (c) 2007-2013 Einar Lielmanis and contributors.
7
+
8
+ Permission is hereby granted, free of charge, to any person
9
+ obtaining a copy of this software and associated documentation files
10
+ (the "Software"), to deal in the Software without restriction,
11
+ including without limitation the rights to use, copy, modify, merge,
12
+ publish, distribute, sublicense, and/or sell copies of the Software,
13
+ and to permit persons to whom the Software is furnished to do so,
14
+ subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+
29
+ Style HTML
30
+ ---------------
31
+
32
+ Written by Nochum Sossonko, (nsossonko@hotmail.com)
33
+
34
+ Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv>
35
+ http://jsbeautifier.org/
36
+
37
+ Usage:
38
+ style_html(html_source);
39
+
40
+ style_html(html_source, options);
41
+
42
+ The options are:
43
+ indent_inner_html (default false) — indent <head> and <body> sections,
44
+ indent_size (default 4) — indentation size,
45
+ indent_char (default space) — character to indent with,
46
+ wrap_line_length (default 250) - maximum amount of characters per line (0 = disable)
47
+ brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
48
+ 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.
49
+ unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
50
+ indent_scripts (default normal) - "keep"|"separate"|"normal"
51
+ preserve_newlines (default true) - whether existing line breaks before elements should be preserved
52
+ Only works before elements, not inside tags or for text.
53
+ max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
54
+ indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
55
+
56
+ e.g.
57
+
58
+ style_html(html_source, {
59
+ 'indent_inner_html': false,
60
+ 'indent_size': 2,
61
+ 'indent_char': ' ',
62
+ 'wrap_line_length': 78,
63
+ 'brace_style': 'expand',
64
+ 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'],
65
+ 'preserve_newlines': true,
66
+ 'max_preserve_newlines': 5,
67
+ 'indent_handlebars': false
68
+ });
69
+ */
70
+
71
+ (function() {
72
+
73
+ function trim(s) {
74
+ return s.replace(/^\s+|\s+$/g, '');
75
+ }
76
+
77
+ function ltrim(s) {
78
+ return s.replace(/^\s+/g, '');
79
+ }
80
+
81
+ function style_html(html_source, options, js_beautify, css_beautify) {
82
+ //Wrapper function to invoke all the necessary constructors and deal with the output.
83
+
84
+ var multi_parser,
85
+ indent_inner_html,
86
+ indent_size,
87
+ indent_character,
88
+ wrap_line_length,
89
+ brace_style,
90
+ unformatted,
91
+ preserve_newlines,
92
+ max_preserve_newlines;
93
+
94
+ options = options || {};
95
+
96
+ // backwards compatibility to 1.3.4
97
+ if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
98
+ (options.max_char === undefined || parseInt(options.max_char, 10) === 0)) {
99
+ options.wrap_line_length = options.max_char;
100
+ }
101
+
102
+ indent_inner_html = options.indent_inner_html || false;
103
+ indent_size = parseInt(options.indent_size || 4, 10);
104
+ indent_character = options.indent_char || ' ';
105
+ brace_style = options.brace_style || 'collapse';
106
+ wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
107
+ unformatted = options.unformatted || ['a', 'span', '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'];
108
+ preserve_newlines = options.preserve_newlines || true;
109
+ max_preserve_newlines = preserve_newlines ? parseInt(options.max_preserve_newlines || 32786, 10) : 0;
110
+ indent_handlebars = options.indent_handlebars || false;
111
+
112
+ function Parser() {
113
+
114
+ this.pos = 0; //Parser position
115
+ this.token = '';
116
+ this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
117
+ this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
118
+ parent: 'parent1',
119
+ parentcount: 1,
120
+ parent1: ''
121
+ };
122
+ this.tag_type = '';
123
+ this.token_text = this.last_token = this.last_text = this.token_type = '';
124
+ this.newlines = 0;
125
+ this.indent_content = indent_inner_html;
126
+
127
+ this.Utils = { //Uilities made available to the various functions
128
+ whitespace: "\n\r\t ".split(''),
129
+ single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
130
+ extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
131
+ in_array: function(what, arr) {
132
+ for (var i = 0; i < arr.length; i++) {
133
+ if (what === arr[i]) {
134
+ return true;
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+ };
140
+
141
+ this.traverse_whitespace = function() {
142
+ var input_char = '';
143
+
144
+ input_char = this.input.charAt(this.pos);
145
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
146
+ this.newlines = 0;
147
+ while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
148
+ if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
149
+ this.newlines += 1;
150
+ }
151
+
152
+ this.pos++;
153
+ input_char = this.input.charAt(this.pos);
154
+ }
155
+ return true;
156
+ }
157
+ return false;
158
+ };
159
+
160
+ this.get_content = function() { //function to capture regular content between tags
161
+
162
+ var input_char = '',
163
+ content = [],
164
+ space = false; //if a space is needed
165
+
166
+ while (this.input.charAt(this.pos) !== '<') {
167
+ if (this.pos >= this.input.length) {
168
+ return content.length ? content.join('') : ['', 'TK_EOF'];
169
+ }
170
+
171
+ if (this.traverse_whitespace()) {
172
+ if (content.length) {
173
+ space = true;
174
+ }
175
+ continue; //don't want to insert unnecessary space
176
+ }
177
+
178
+ if (indent_handlebars) {
179
+ // Handlebars parsing is complicated.
180
+ // {{#foo}} and {{/foo}} are formatted tags.
181
+ // {{something}} should get treated as content, except:
182
+ // {{else}} specifically behaves like {{#if}} and {{/if}}
183
+ var peek3 = this.input.substr(this.pos, 3);
184
+ if (peek3 === '{{#' || peek3 === '{{/') {
185
+ // These are tags and not content.
186
+ break;
187
+ } else if (this.input.substr(this.pos, 2) === '{{') {
188
+ if (this.get_tag(true) === '{{else}}') {
189
+ break;
190
+ }
191
+ }
192
+ }
193
+
194
+ input_char = this.input.charAt(this.pos);
195
+ this.pos++;
196
+
197
+ if (space) {
198
+ if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
199
+ this.print_newline(false, content);
200
+ this.print_indentation(content);
201
+ } else {
202
+ this.line_char_count++;
203
+ content.push(' ');
204
+ }
205
+ space = false;
206
+ }
207
+ this.line_char_count++;
208
+ content.push(input_char); //letter at-a-time (or string) inserted to an array
209
+ }
210
+ return content.length ? content.join('') : '';
211
+ };
212
+
213
+ this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
214
+ if (this.pos === this.input.length) {
215
+ return ['', 'TK_EOF'];
216
+ }
217
+ var input_char = '';
218
+ var content = '';
219
+ var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
220
+ reg_match.lastIndex = this.pos;
221
+ var reg_array = reg_match.exec(this.input);
222
+ var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
223
+ if (this.pos < end_script) { //get everything in between the script tags
224
+ content = this.input.substring(this.pos, end_script);
225
+ this.pos = end_script;
226
+ }
227
+ return content;
228
+ };
229
+
230
+ this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
231
+ if (this.tags[tag + 'count']) { //check for the existence of this tag type
232
+ this.tags[tag + 'count']++;
233
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
234
+ } else { //otherwise initialize this tag type
235
+ this.tags[tag + 'count'] = 1;
236
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
237
+ }
238
+ 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)
239
+ this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
240
+ };
241
+
242
+ this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
243
+ if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
244
+ var temp_parent = this.tags.parent; //check to see if it's a closable tag.
245
+ while (temp_parent) { //till we reach '' (the initial value);
246
+ if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
247
+ break;
248
+ }
249
+ temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
250
+ }
251
+ if (temp_parent) { //if we caught something
252
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
253
+ this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
254
+ }
255
+ delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
256
+ delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
257
+ if (this.tags[tag + 'count'] === 1) {
258
+ delete this.tags[tag + 'count'];
259
+ } else {
260
+ this.tags[tag + 'count']--;
261
+ }
262
+ }
263
+ };
264
+
265
+ this.indent_to_tag = function(tag) {
266
+ // Match the indentation level to the last use of this tag, but don't remove it.
267
+ if (!this.tags[tag + 'count']) {
268
+ return;
269
+ }
270
+ var temp_parent = this.tags.parent;
271
+ while (temp_parent) {
272
+ if (tag + this.tags[tag + 'count'] === temp_parent) {
273
+ break;
274
+ }
275
+ temp_parent = this.tags[temp_parent + 'parent'];
276
+ }
277
+ if (temp_parent) {
278
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']];
279
+ }
280
+ };
281
+
282
+ this.get_tag = function(peek) { //function to get a full tag and parse its type
283
+ var input_char = '',
284
+ content = [],
285
+ comment = '',
286
+ space = false,
287
+ tag_start, tag_end,
288
+ tag_start_char,
289
+ orig_pos = this.pos,
290
+ orig_line_char_count = this.line_char_count;
291
+
292
+ peek = peek !== undefined ? peek : false;
293
+
294
+ do {
295
+ if (this.pos >= this.input.length) {
296
+ if (peek) {
297
+ this.pos = orig_pos;
298
+ this.line_char_count = orig_line_char_count;
299
+ }
300
+ return content.length ? content.join('') : ['', 'TK_EOF'];
301
+ }
302
+
303
+ input_char = this.input.charAt(this.pos);
304
+ this.pos++;
305
+
306
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
307
+ space = true;
308
+ continue;
309
+ }
310
+
311
+ if (input_char === "'" || input_char === '"') {
312
+ input_char += this.get_unformatted(input_char);
313
+ space = true;
314
+
315
+ }
316
+
317
+ if (input_char === '=') { //no space before =
318
+ space = false;
319
+ }
320
+
321
+ if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
322
+ //no space after = or before >
323
+ if (this.line_char_count >= this.wrap_line_length) {
324
+ this.print_newline(false, content);
325
+ this.print_indentation(content);
326
+ } else {
327
+ content.push(' ');
328
+ this.line_char_count++;
329
+ }
330
+ space = false;
331
+ }
332
+
333
+ if (indent_handlebars && tag_start_char === '<') {
334
+ // When inside an angle-bracket tag, put spaces around
335
+ // handlebars not inside of strings.
336
+ if ((input_char + this.input.charAt(this.pos)) === '{{') {
337
+ input_char += this.get_unformatted('}}');
338
+ if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
339
+ input_char = ' ' + input_char;
340
+ }
341
+ space = true;
342
+ }
343
+ }
344
+
345
+ if (input_char === '<' && !tag_start_char) {
346
+ tag_start = this.pos - 1;
347
+ tag_start_char = '<';
348
+ }
349
+
350
+ if (indent_handlebars && !tag_start_char) {
351
+ if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] == '{') {
352
+ if (input_char === '#' || input_char === '/') {
353
+ tag_start = this.pos - 3;
354
+ } else {
355
+ tag_start = this.pos - 2;
356
+ }
357
+ tag_start_char = '{';
358
+ }
359
+ }
360
+
361
+ this.line_char_count++;
362
+ content.push(input_char); //inserts character at-a-time (or string)
363
+
364
+ if (content[1] && content[1] === '!') { //if we're in a comment, do something special
365
+ // We treat all comments as literals, even more than preformatted tags
366
+ // we just look for the appropriate close tag
367
+ content = [this.get_comment(tag_start)];
368
+ break;
369
+ }
370
+
371
+ if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
372
+ break;
373
+ }
374
+ } while (input_char !== '>');
375
+
376
+ var tag_complete = content.join('');
377
+ var tag_index;
378
+ var tag_offset;
379
+
380
+ if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
381
+ tag_index = tag_complete.indexOf(' ');
382
+ } else if (tag_complete[0] === '{') {
383
+ tag_index = tag_complete.indexOf('}');
384
+ } else { //otherwise go with the tag ending
385
+ tag_index = tag_complete.indexOf('>');
386
+ }
387
+ if (tag_complete[0] === '<' || !indent_handlebars) {
388
+ tag_offset = 1;
389
+ } else {
390
+ tag_offset = tag_complete[2] === '#' ? 3 : 2;
391
+ }
392
+ var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
393
+ if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
394
+ 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 /)
395
+ if (!peek) {
396
+ this.tag_type = 'SINGLE';
397
+ }
398
+ } else if (indent_handlebars && tag_complete[0] === '{' && tag_check === 'else') {
399
+ if (!peek) {
400
+ this.indent_to_tag('if');
401
+ this.tag_type = 'HANDLEBARS_ELSE';
402
+ this.indent_content = true;
403
+ this.traverse_whitespace();
404
+ }
405
+ } else if (tag_check === 'script') { //for later script handling
406
+ if (!peek) {
407
+ this.record_tag(tag_check);
408
+ this.tag_type = 'SCRIPT';
409
+ }
410
+ } else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content)
411
+ if (!peek) {
412
+ this.record_tag(tag_check);
413
+ this.tag_type = 'STYLE';
414
+ }
415
+ } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
416
+ comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
417
+ content.push(comment);
418
+ // Preserve collapsed whitespace either before or after this tag.
419
+ if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)) {
420
+ content.splice(0, 0, this.input.charAt(tag_start - 1));
421
+ }
422
+ tag_end = this.pos - 1;
423
+ if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)) {
424
+ content.push(this.input.charAt(tag_end + 1));
425
+ }
426
+ this.tag_type = 'SINGLE';
427
+ } else if (tag_check.charAt(0) === '!') { //peek for <! comment
428
+ // for comments content is already correct.
429
+ if (!peek) {
430
+ this.tag_type = 'SINGLE';
431
+ this.traverse_whitespace();
432
+ }
433
+ } else if (!peek) {
434
+ if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
435
+ this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
436
+ this.tag_type = 'END';
437
+ this.traverse_whitespace();
438
+ } else { //otherwise it's a start-tag
439
+ this.record_tag(tag_check); //push it on the tag stack
440
+ if (tag_check.toLowerCase() !== 'html') {
441
+ this.indent_content = true;
442
+ }
443
+ this.tag_type = 'START';
444
+
445
+ // Allow preserving of newlines after a start tag
446
+ this.traverse_whitespace();
447
+ }
448
+ if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
449
+ this.print_newline(false, this.output);
450
+ if (this.output.length && this.output[this.output.length - 2] !== '\n') {
451
+ this.print_newline(true, this.output);
452
+ }
453
+ }
454
+ }
455
+
456
+ if (peek) {
457
+ this.pos = orig_pos;
458
+ this.line_char_count = orig_line_char_count;
459
+ }
460
+
461
+ return content.join(''); //returns fully formatted tag
462
+ };
463
+
464
+ this.get_comment = function(start_pos) { //function to return comment content in its entirety
465
+ // this is will have very poor perf, but will work for now.
466
+ var comment = '',
467
+ delimiter = '>',
468
+ matched = false;
469
+
470
+ this.pos = start_pos;
471
+ input_char = this.input.charAt(this.pos);
472
+ this.pos++;
473
+
474
+ while (this.pos <= this.input.length) {
475
+ comment += input_char;
476
+
477
+ // only need to check for the delimiter if the last chars match
478
+ if (comment[comment.length - 1] === delimiter[delimiter.length - 1] &&
479
+ comment.indexOf(delimiter) !== -1) {
480
+ break;
481
+ }
482
+
483
+ // only need to search for custom delimiter for the first few characters
484
+ if (!matched && comment.length < 10) {
485
+ if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
486
+ delimiter = '<![endif]>';
487
+ matched = true;
488
+ } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
489
+ delimiter = ']]>';
490
+ matched = true;
491
+ } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
492
+ delimiter = ']>';
493
+ matched = true;
494
+ } else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
495
+ delimiter = '-->';
496
+ matched = true;
497
+ }
498
+ }
499
+
500
+ input_char = this.input.charAt(this.pos);
501
+ this.pos++;
502
+ }
503
+
504
+ return comment;
505
+ };
506
+
507
+ this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
508
+
509
+ if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
510
+ return '';
511
+ }
512
+ var input_char = '';
513
+ var content = '';
514
+ var min_index = 0;
515
+ var space = true;
516
+ do {
517
+
518
+ if (this.pos >= this.input.length) {
519
+ return content;
520
+ }
521
+
522
+ input_char = this.input.charAt(this.pos);
523
+ this.pos++;
524
+
525
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
526
+ if (!space) {
527
+ this.line_char_count--;
528
+ continue;
529
+ }
530
+ if (input_char === '\n' || input_char === '\r') {
531
+ content += '\n';
532
+ /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
533
+ for (var i=0; i<this.indent_level; i++) {
534
+ content += this.indent_string;
535
+ }
536
+ space = false; //...and make sure other indentation is erased
537
+ */
538
+ this.line_char_count = 0;
539
+ continue;
540
+ }
541
+ }
542
+ content += input_char;
543
+ this.line_char_count++;
544
+ space = true;
545
+
546
+ if (indent_handlebars && input_char === '{' && content.length && content[content.length - 2] === '{') {
547
+ // Handlebars expressions in strings should also be unformatted.
548
+ content += this.get_unformatted('}}');
549
+ // These expressions are opaque. Ignore delimiters found in them.
550
+ min_index = content.length;
551
+ }
552
+ } while (content.toLowerCase().indexOf(delimiter, min_index) === -1);
553
+ return content;
554
+ };
555
+
556
+ this.get_token = function() { //initial handler for token-retrieval
557
+ var token;
558
+
559
+ if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
560
+ var type = this.last_token.substr(7);
561
+ token = this.get_contents_to(type);
562
+ if (typeof token !== 'string') {
563
+ return token;
564
+ }
565
+ return [token, 'TK_' + type];
566
+ }
567
+ if (this.current_mode === 'CONTENT') {
568
+ token = this.get_content();
569
+ if (typeof token !== 'string') {
570
+ return token;
571
+ } else {
572
+ return [token, 'TK_CONTENT'];
573
+ }
574
+ }
575
+
576
+ if (this.current_mode === 'TAG') {
577
+ token = this.get_tag();
578
+ if (typeof token !== 'string') {
579
+ return token;
580
+ } else {
581
+ var tag_name_type = 'TK_TAG_' + this.tag_type;
582
+ return [token, tag_name_type];
583
+ }
584
+ }
585
+ };
586
+
587
+ this.get_full_indent = function(level) {
588
+ level = this.indent_level + level || 0;
589
+ if (level < 1) {
590
+ return '';
591
+ }
592
+
593
+ return Array(level + 1).join(this.indent_string);
594
+ };
595
+
596
+ this.is_unformatted = function(tag_check, unformatted) {
597
+ //is this an HTML5 block-level link?
598
+ if (!this.Utils.in_array(tag_check, unformatted)) {
599
+ return false;
600
+ }
601
+
602
+ if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
603
+ return true;
604
+ }
605
+
606
+ //at this point we have an tag; is its first child something we want to remain
607
+ //unformatted?
608
+ var next_tag = this.get_tag(true /* peek. */ );
609
+
610
+ // test next_tag to see if it is just html tag (no external content)
611
+ var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
612
+
613
+ // if next_tag comes back but is not an isolated tag, then
614
+ // let's treat the 'a' tag as having content
615
+ // and respect the unformatted option
616
+ if (!tag || this.Utils.in_array(tag, unformatted)) {
617
+ return true;
618
+ } else {
619
+ return false;
620
+ }
621
+ };
622
+
623
+ this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
624
+
625
+ this.input = js_source || ''; //gets the input for the Parser
626
+ this.output = [];
627
+ this.indent_character = indent_character;
628
+ this.indent_string = '';
629
+ this.indent_size = indent_size;
630
+ this.brace_style = brace_style;
631
+ this.indent_level = 0;
632
+ this.wrap_line_length = wrap_line_length;
633
+ this.line_char_count = 0; //count to see if wrap_line_length was exceeded
634
+
635
+ for (var i = 0; i < this.indent_size; i++) {
636
+ this.indent_string += this.indent_character;
637
+ }
638
+
639
+ this.print_newline = function(force, arr) {
640
+ this.line_char_count = 0;
641
+ if (!arr || !arr.length) {
642
+ return;
643
+ }
644
+ if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
645
+ arr.push('\n');
646
+ }
647
+ };
648
+
649
+ this.print_indentation = function(arr) {
650
+ for (var i = 0; i < this.indent_level; i++) {
651
+ arr.push(this.indent_string);
652
+ this.line_char_count += this.indent_string.length;
653
+ }
654
+ };
655
+
656
+ this.print_token = function(text) {
657
+ if (text || text !== '') {
658
+ if (this.output.length && this.output[this.output.length - 1] === '\n') {
659
+ this.print_indentation(this.output);
660
+ text = ltrim(text);
661
+ }
662
+ }
663
+ this.print_token_raw(text);
664
+ };
665
+
666
+ this.print_token_raw = function(text) {
667
+ if (text && text !== '') {
668
+ if (text.length > 1 && text[text.length - 1] === '\n') {
669
+ // unformatted tags can grab newlines as their last character
670
+ this.output.push(text.slice(0, -1));
671
+ this.print_newline(false, this.output);
672
+ } else {
673
+ this.output.push(text);
674
+ }
675
+ }
676
+
677
+ for (var n = 0; n < this.newlines; n++) {
678
+ this.print_newline(n > 0, this.output);
679
+ }
680
+ this.newlines = 0;
681
+ };
682
+
683
+ this.indent = function() {
684
+ this.indent_level++;
685
+ };
686
+
687
+ this.unindent = function() {
688
+ if (this.indent_level > 0) {
689
+ this.indent_level--;
690
+ }
691
+ };
692
+ };
693
+ return this;
694
+ }
695
+
696
+ /*_____________________--------------------_____________________*/
697
+
698
+ multi_parser = new Parser(); //wrapping functions Parser
699
+ multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
700
+
701
+ while (true) {
702
+ var t = multi_parser.get_token();
703
+ multi_parser.token_text = t[0];
704
+ multi_parser.token_type = t[1];
705
+
706
+ if (multi_parser.token_type === 'TK_EOF') {
707
+ break;
708
+ }
709
+
710
+ switch (multi_parser.token_type) {
711
+ case 'TK_TAG_START':
712
+ multi_parser.print_newline(false, multi_parser.output);
713
+ multi_parser.print_token(multi_parser.token_text);
714
+ if (multi_parser.indent_content) {
715
+ multi_parser.indent();
716
+ multi_parser.indent_content = false;
717
+ }
718
+ multi_parser.current_mode = 'CONTENT';
719
+ break;
720
+ case 'TK_TAG_STYLE':
721
+ case 'TK_TAG_SCRIPT':
722
+ multi_parser.print_newline(false, multi_parser.output);
723
+ multi_parser.print_token(multi_parser.token_text);
724
+ multi_parser.current_mode = 'CONTENT';
725
+ break;
726
+ case 'TK_TAG_END':
727
+ //Print new line only if the tag has no content and has child
728
+ if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
729
+ var tag_name = multi_parser.token_text.match(/\w+/)[0];
730
+ var tag_extracted_from_last_output = null;
731
+ if (multi_parser.output.length) {
732
+ tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
733
+ }
734
+ if (tag_extracted_from_last_output === null ||
735
+ tag_extracted_from_last_output[1] !== tag_name) {
736
+ multi_parser.print_newline(false, multi_parser.output);
737
+ }
738
+ }
739
+ multi_parser.print_token(multi_parser.token_text);
740
+ multi_parser.current_mode = 'CONTENT';
741
+ break;
742
+ case 'TK_TAG_SINGLE':
743
+ // Don't add a newline before elements that should remain unformatted.
744
+ var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
745
+ if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
746
+ multi_parser.print_newline(false, multi_parser.output);
747
+ }
748
+ multi_parser.print_token(multi_parser.token_text);
749
+ multi_parser.current_mode = 'CONTENT';
750
+ break;
751
+ case 'TK_TAG_HANDLEBARS_ELSE':
752
+ multi_parser.print_token(multi_parser.token_text);
753
+ if (multi_parser.indent_content) {
754
+ multi_parser.indent();
755
+ multi_parser.indent_content = false;
756
+ }
757
+ multi_parser.current_mode = 'CONTENT';
758
+ break;
759
+ case 'TK_CONTENT':
760
+ multi_parser.print_token(multi_parser.token_text);
761
+ multi_parser.current_mode = 'TAG';
762
+ break;
763
+ case 'TK_STYLE':
764
+ case 'TK_SCRIPT':
765
+ if (multi_parser.token_text !== '') {
766
+ multi_parser.print_newline(false, multi_parser.output);
767
+ var text = multi_parser.token_text,
768
+ _beautifier,
769
+ script_indent_level = 1;
770
+ if (multi_parser.token_type === 'TK_SCRIPT') {
771
+ _beautifier = typeof js_beautify === 'function' && js_beautify;
772
+ } else if (multi_parser.token_type === 'TK_STYLE') {
773
+ _beautifier = typeof css_beautify === 'function' && css_beautify;
774
+ }
775
+
776
+ if (options.indent_scripts === "keep") {
777
+ script_indent_level = 0;
778
+ } else if (options.indent_scripts === "separate") {
779
+ script_indent_level = -multi_parser.indent_level;
780
+ }
781
+
782
+ var indentation = multi_parser.get_full_indent(script_indent_level);
783
+ if (_beautifier) {
784
+ // call the Beautifier if avaliable
785
+ text = _beautifier(text.replace(/^\s*/, indentation), options);
786
+ } else {
787
+ // simply indent the string otherwise
788
+ var white = text.match(/^\s*/)[0];
789
+ var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
790
+ var reindent = multi_parser.get_full_indent(script_indent_level - _level);
791
+ text = text.replace(/^\s*/, indentation)
792
+ .replace(/\r\n|\r|\n/g, '\n' + reindent)
793
+ .replace(/\s+$/, '');
794
+ }
795
+ if (text) {
796
+ multi_parser.print_token_raw(indentation + trim(text));
797
+ multi_parser.print_newline(false, multi_parser.output);
798
+ }
799
+ }
800
+ multi_parser.current_mode = 'TAG';
801
+ break;
802
+ }
803
+ multi_parser.last_token = multi_parser.token_type;
804
+ multi_parser.last_text = multi_parser.token_text;
805
+ }
806
+ return multi_parser.output.join('');
807
+ }
808
+
809
+ if (typeof define === "function") {
810
+ // Add support for require.js
811
+ define(["./beautify.js", "./beautify-css.js"], function(js_beautify, css_beautify) {
812
+ return {
813
+ html_beautify: function(html_source, options) {
814
+ return style_html(html_source, options, js_beautify, css_beautify);
815
+ }
816
+ };
817
+ });
818
+ } else if (typeof exports !== "undefined") {
819
+ // Add support for CommonJS. Just put this file somewhere on your require.paths
820
+ // and you will be able to `var html_beautify = require("beautify").html_beautify`.
821
+ var js_beautify = require('./beautify.js').js_beautify;
822