Browse Source

upd to v6.6.1 by @5HT
(+ code formatting)

221V 1 year ago
parent
commit
bf7c537794
146 changed files with 3565 additions and 13979 deletions
  1. 14 0
      include/calendar.hrl
  2. 23 0
      include/comboLookup.hrl
  3. 12 0
      include/comboLookupEdit.hrl
  4. 12 0
      include/comboLookupText.hrl
  5. 12 0
      include/comboLookupVec.hrl
  6. 11 0
      include/cx.hrl
  7. 12 0
      include/event.hrl
  8. 12 0
      include/koatuuControl.hrl
  9. 61 0
      include/n2o.hrl
  10. 207 197
      include/nitro.hrl
  11. 13 0
      include/proto.hrl
  12. 11 0
      include/sortable_item.hrl
  13. 11 0
      include/sortable_list.hrl
  14. 34 0
      lib/NITRO.ex
  15. 199 0
      lib/combo.ex
  16. 0 948
      priv/css/redactor.css
  17. 0 1006
      priv/css/redactor.less
  18. 106 0
      priv/css/sortable.css
  19. BIN
      priv/fonts/redactor-font.eot
  20. 36 9
      priv/js/calendar.js
  21. 110 0
      priv/js/comboLookup.js
  22. 0 75
      priv/js/lang/ru.js
  23. 0 76
      priv/js/lang/ua.js
  24. 80 0
      priv/js/nitro.js
  25. 0 6
      priv/js/plugins/clips/clips.css
  26. 0 62
      priv/js/plugins/clips/clips.js
  27. 0 42
      priv/js/plugins/counter/counter.js
  28. 0 51
      priv/js/plugins/definedlinks/definedlinks.js
  29. 0 62
      priv/js/plugins/filemanager/filemanager.js
  30. 0 74
      priv/js/plugins/fontcolor/fontcolor.js
  31. 0 33
      priv/js/plugins/fontfamily/fontfamily.js
  32. 0 32
      priv/js/plugins/fontsize/fontsize.js
  33. 0 121
      priv/js/plugins/fullscreen/fullscreen.js
  34. 0 55
      priv/js/plugins/imagemanager/imagemanager.js
  35. 0 39
      priv/js/plugins/limiter/limiter.js
  36. 0 470
      priv/js/plugins/table/table.js
  37. 0 29
      priv/js/plugins/textdirection/textdirection.js
  38. 0 73
      priv/js/plugins/textexpander/textexpander.js
  39. 0 75
      priv/js/plugins/video/video.js
  40. 0 9542
      priv/js/redactor.js
  41. 232 0
      priv/js/sortable.js
  42. 20 0
      priv/js/validation.js
  43. 1 0
      src/actions/action_alert.erl
  44. 4 3
      src/actions/action_api.erl
  45. 15 2
      src/actions/action_bind.erl
  46. 1 0
      src/actions/action_confirm.erl
  47. 7 5
      src/actions/action_event.erl
  48. 7 2
      src/actions/action_jq.erl
  49. 3 3
      src/actions/action_transfer.erl
  50. 1 1
      src/actions/action_ui.erl
  51. 4 3
      src/actions/action_wire.erl
  52. 32 30
      src/elements/combo/element_calendar.erl
  53. 50 0
      src/elements/combo/element_comboLookup.erl
  54. 51 0
      src/elements/combo/element_comboLookupEdit.erl
  55. 27 0
      src/elements/combo/element_comboLookupText.erl
  56. 40 0
      src/elements/combo/element_comboLookupVec.erl
  57. 25 0
      src/elements/combo/element_koatuu.erl
  58. 52 0
      src/elements/combo/element_sortable_item.erl
  59. 30 0
      src/elements/combo/element_sortable_list.erl
  60. 9 12
      src/elements/edit/element_del.erl
  61. 9 12
      src/elements/edit/element_ins.erl
  62. 9 7
      src/elements/embed/element_area.erl
  63. 14 17
      src/elements/embed/element_audio.erl
  64. 9 7
      src/elements/embed/element_canvas.erl
  65. 8 6
      src/elements/embed/element_embed.erl
  66. 9 7
      src/elements/embed/element_iframe.erl
  67. 2 0
      src/elements/embed/element_image.erl
  68. 10 13
      src/elements/embed/element_map.erl
  69. 9 12
      src/elements/embed/element_object.erl
  70. 8 6
      src/elements/embed/element_param.erl
  71. 8 6
      src/elements/embed/element_source.erl
  72. 10 8
      src/elements/embed/element_track.erl
  73. 14 17
      src/elements/embed/element_video.erl
  74. 8 6
      src/elements/form/element_button.erl
  75. 15 17
      src/elements/form/element_fieldset.erl
  76. 26 10
      src/elements/form/element_form.erl
  77. 15 11
      src/elements/form/element_keygen.erl
  78. 2 0
      src/elements/form/element_label.erl
  79. 2 0
      src/elements/form/element_legend.erl
  80. 7 12
      src/elements/form/element_meter.erl
  81. 12 15
      src/elements/form/element_output.erl
  82. 9 12
      src/elements/form/element_progress.erl
  83. 26 27
      src/elements/form/element_select.erl
  84. 14 17
      src/elements/form/element_textarea.erl
  85. 2 2
      src/elements/group/element_blockquote.erl
  86. 4 2
      src/elements/group/element_dtl.erl
  87. 9 12
      src/elements/group/element_html.erl
  88. 2 0
      src/elements/group/element_li.erl
  89. 14 13
      src/elements/group/element_script.erl
  90. 22 18
      src/elements/input/element_checkbox.erl
  91. 16 12
      src/elements/input/element_color.erl
  92. 18 14
      src/elements/input/element_date.erl
  93. 17 14
      src/elements/input/element_datetime.erl
  94. 18 14
      src/elements/input/element_datetime_local.erl
  95. 11 8
      src/elements/input/element_dropdown.erl
  96. 19 15
      src/elements/input/element_email.erl
  97. 17 13
      src/elements/input/element_file.erl
  98. 9 7
      src/elements/input/element_hidden.erl
  99. 33 10
      src/elements/input/element_input.erl
  100. 14 10
      src/elements/input/element_input_button.erl
  101. 15 11
      src/elements/input/element_input_image.erl
  102. 18 14
      src/elements/input/element_input_time.erl
  103. 13 9
      src/elements/input/element_link.erl
  104. 2 0
      src/elements/input/element_list.erl
  105. 2 0
      src/elements/input/element_literal.erl
  106. 17 13
      src/elements/input/element_month.erl
  107. 18 14
      src/elements/input/element_number.erl
  108. 18 14
      src/elements/input/element_password.erl
  109. 9 7
      src/elements/input/element_radio.erl
  110. 11 8
      src/elements/input/element_radiogroup.erl
  111. 16 12
      src/elements/input/element_range.erl
  112. 15 11
      src/elements/input/element_reset.erl
  113. 18 14
      src/elements/input/element_search.erl
  114. 7 5
      src/elements/input/element_submit.erl
  115. 16 13
      src/elements/input/element_tel.erl
  116. 5 3
      src/elements/input/element_textbox.erl
  117. 8 6
      src/elements/input/element_time.erl
  118. 18 14
      src/elements/input/element_url.erl
  119. 18 14
      src/elements/input/element_week.erl
  120. 10 8
      src/elements/interactive/element_command.erl
  121. 10 8
      src/elements/interactive/element_details.erl
  122. 10 13
      src/elements/interactive/element_menu.erl
  123. 9 12
      src/elements/interactive/element_summary.erl
  124. 5 2
      src/elements/interactive/element_upload.erl
  125. 8 6
      src/elements/meta/element_meta.erl
  126. 6 6
      src/elements/meta/element_meta_base.erl
  127. 8 6
      src/elements/meta/element_meta_link.erl
  128. 10 13
      src/elements/meta/element_style.erl
  129. 9 7
      src/elements/table/element_col.erl
  130. 9 7
      src/elements/table/element_colgroup.erl
  131. 8 7
      src/elements/table/element_table.erl
  132. 3 1
      src/elements/table/element_td.erl
  133. 3 1
      src/elements/table/element_th.erl
  134. 11 5
      src/elements/table/element_tr.erl
  135. 7 12
      src/elements/text/element_q.erl
  136. 2 2
      src/nitro.app.src
  137. 428 62
      src/nitro.erl
  138. 279 0
      src/nitro_conv.erl
  139. 171 0
      src/nitro_n2o.erl
  140. 2 2
      src/nitro_pickle.erl
  141. 105 0
      src/nitro_static.erl
  142. 29 10
      src/render/wf_event.erl
  143. 7 4
      src/render/wf_render.erl
  144. 6 5
      src/render/wf_render_actions.erl
  145. 74 38
      src/render/wf_render_elements.erl
  146. 25 13
      src/render/wf_tags.erl

+ 14 - 0
include/calendar.hrl

@@ -0,0 +1,14 @@
+-ifndef(CAL_HRL).
+-define(CAL_HRL, true).
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(calendar, {?ELEMENT_BASE(element_calendar),
+  autocomplete = true, autofocus = false, disabled = false, form = [], list = [], maxDate = {2134,2,2},
+  minDate, format = "DD.MM.YYYY", pattern = [], name = [], step = [], readonly = [], required = [],
+  value = {2020,2,2}, placeholder = [], onSelect = [], disableDayFn = [], firstDay = 0, position = [],
+  reposition = [], yearRange = 100}).
+
+-endif.
+

+ 23 - 0
include/comboLookup.hrl

@@ -0,0 +1,23 @@
+-ifndef(COMBO_LOOKUP_HRL).
+-define(COMBO_LOOKUP_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(comboKey,  { value = [], dom = [], feed = [], delegate = []} ).
+-record(comboKeyup,  { value = [], dom = [], feed = [], delegate = []} ).
+-record(comboSelect,  { value = [], dom = [], feed = [], delegate = []} ).
+-record(comboNext,   { pos = [],  count = [], feed = []} ).
+-record(comboLookup, { ?ELEMENT_BASE(element_comboLookup),
+    value = [],
+    disabled = false,
+    feed = [],
+    reader = [],
+    chunk = 20} ).
+
+-record(process, {name = []} ).
+
+
+-endif.
+

+ 12 - 0
include/comboLookupEdit.hrl

@@ -0,0 +1,12 @@
+-ifndef(COMBO_LOOKUP_EDIT_HRL).
+-define(COMBO_LOOKUP_EDIT_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(comboLookupEdit, {?ELEMENT_BASE(element_comboLookupEdit), input, disabled, form, values, multiple} ).
+
+
+-endif.
+

+ 12 - 0
include/comboLookupText.hrl

@@ -0,0 +1,12 @@
+-ifndef(COMBO_LOOKUP_TEXT_HRL).
+-define(COMBO_LOOKUP_TEXT_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(comboLookupText, {?ELEMENT_BASE(element_comboLookupText), input, disabled, textarea, values} ).
+
+
+-endif.
+

+ 12 - 0
include/comboLookupVec.hrl

@@ -0,0 +1,12 @@
+-ifndef(COMBO_LOOKUP_VEC_HRL).
+-define(COMBO_LOOKUP_VEC_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(comboLookupVec, {?ELEMENT_BASE(element_comboLookupVec), input, disabled, values} ).
+
+
+-endif.
+

+ 11 - 0
include/cx.hrl

@@ -0,0 +1,11 @@
+-ifndef(NITRO_CX).
+-define(NITRO_CX, true).
+
+
+-record(cx, { handlers = [], actions = [], req = [], module = [], lang = [], path = [],
+              session = [], token = [], formatter=bert, params = [], node = [],
+              client_pid = [], state = [], from = [], vsn = []} ).
+
+
+-endif.
+

+ 12 - 0
include/event.hrl

@@ -0,0 +1,12 @@
+-ifndef(NITRO_EVENT).
+-define(NITRO_EVENT, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(event, {?ACTION_BASE(action_event), type = default, postback, delegate, validation = []} ).
+
+
+-endif.
+

+ 12 - 0
include/koatuuControl.hrl

@@ -0,0 +1,12 @@
+-ifndef(KOATUU_NX_HRL).
+-define(KOATUU_NX_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+
+-record(koatuu, {?ELEMENT_BASE(element_koatuu)} ).
+
+
+-endif.
+

+ 61 - 0
include/n2o.hrl

@@ -0,0 +1,61 @@
+-ifndef(N2O_HRL).
+-define(N2O_HRL, true).
+
+
+-define(FORMAT(F), case F of F when erlang:is_binary(F) -> erlang:binary_to_list(F);
+                             F when erlang:is_atom(F) -> erlang:atom_to_list(F);
+                             F when erlang:is_list(F) -> F
+                   end).
+
+
+-ifdef(OTP_RELEASE).
+
+-include_lib("kernel/include/logger.hrl").
+
+-else.
+
+-define(LOG_INFO(F),     io:format(?FORMAT(F)) end).
+-define(LOG_INFO(F, X),  io:format(?FORMAT(F), X) ).
+-define(LOG_ERROR(F),    io:format("{~p,~p}: ~p~n", [?MODULE, ?LINE, F]) ).
+-define(LOG_ERROR(F, X), io:format(?FORMAT(F), X) ).
+-endif.
+
+
+-define(LOG_EXCEPTION(E, R, S), ?LOG_ERROR( #{exception => E, reason => R, stack => S} ) ).
+
+
+-record(pi, {name   :: term(),
+             table  :: atom(),
+             sup    :: atom(),
+             module :: atom(),
+             state  :: term()} ).
+
+
+-record(cx, {handlers  = [] :: list({atom(), atom()}),
+             actions   = [] :: list(tuple()),
+             req       = [] :: [] | term(),
+             module    = [] :: [] | atom() | list(),
+             lang      = [] :: [] | atom(),
+             path      = [] :: [] | binary(),
+             session   = [] :: [] | binary(),
+             token     = [] :: [] | binary(),
+             formatter = bert :: bert | json | atom(),
+             params    = [] :: [] | list(tuple()) | binary() | list(),
+             node      = [] :: [] | atom() | list(),
+             client_pid= [] :: [] | term(),
+             state     = [] :: [] | term(),
+             from      = [] :: [] | binary(),
+             vsn       = [] :: [] | binary()} ).
+
+
+-define(CTX(ClientId), n2o:cache(ClientId) ).
+-define(REQ(ClientId), (n2o:cache(ClientId))#cx.req ).
+
+
+%% Nitrogen Protocol
+
+-include_lib("nitro/include/proto.hrl").
+
+
+-endif.
+

+ 207 - 197
include/nitro.hrl

@@ -1,202 +1,212 @@
 -ifndef(NITRO_HRL).
 -define(NITRO_HRL, true).
 
--define(DEFAULT_BASE, {?ELEMENT_BASE(undefined)}).
--define(DEFAULT_BASE_TAG(Tag), {?ELEMENT_BASE(undefined,Tag,undefined)}).
--define(ELEMENT_BASE(Module), ?ELEMENT_BASE(Module,undefined,undefined)).
--define(ELEMENT_BASE(Module,Tag,Delegate),
-        ancestor=element, id, module=Module, delegate=Delegate, validation=[], validate=[], actions, class=[], style=[], source=[], onmouseover, onkeypress, onchange, onkeyup, onkeydown, onclick,
-        data_fields=[], aria_states=[], body, role, tabindex, show_if=true, html_tag=Tag, title, postback, accesskey, contenteditable, contextmenu, dir, draggable, dropzone,
-        hidden, lang, spellcheck, translate, onblur, onerror, onfocus, onmessage, onresize).
--define(ACTION_BASE(Module), ancestor=action, trigger, target, module=Module, actions, source=[]).
--define(CTRL_BASE(Module), ?ELEMENT_BASE(Module,undefined,Module)).
-
--record(element, {?ELEMENT_BASE(undefined)}).
--record(literal, {?ELEMENT_BASE(element_literal), html_encode=true }).
--record(dtl, {?ELEMENT_BASE(element_dtl), file="index", bindings=[], app=web, folder="priv/templates", ext="html", bind_script=true, js_escape=false }).
--record(list, {?ELEMENT_BASE(element_list), numbered=false }).
--record(dropdown, {?ELEMENT_BASE(element_dropdown), options, value, multiple=false, disabled=false, name}).
--record(radiogroup, {?ELEMENT_BASE(element_radiogroup)}).
--record(spinner, {?ELEMENT_BASE(element_spinner), image="/priv/static/spinner.gif"}).
-
-% HTML Document meta
--record(base,       {?ELEMENT_BASE(element_meta_base), href, target}).
--record(head,       ?DEFAULT_BASE).
--record(meta_link,       {?ELEMENT_BASE(element_meta_link), href, hreflang, media, rel, sizes, type}).
--record(meta,       {?ELEMENT_BASE(element_meta), charset, content, http_equiv, name, type}).
--record(style,       {?ELEMENT_BASE(element_style), media, scoped, type}).
--record(title,       ?DEFAULT_BASE).
-
-% HTML Edits
--record('del',       {?ELEMENT_BASE(element_del), cite, datetime}).
--record(ins,       {?ELEMENT_BASE(element_ins), cite, datetime}).
-
-% HTML Embedded
--record(area,       {?ELEMENT_BASE(element_area), alt, coords, href, hreflang, media, target, rel, shape, type}).
--record(audio,       {?ELEMENT_BASE(element_audio), autoplay, controls, loop, mediagroup, muted, preload, src, width}).
--record(canvas,       {?ELEMENT_BASE(element_canvas), height, width}).
--record(embed,       {?ELEMENT_BASE(element_embed), height, src, type, width}).
--record(iframe,       {?ELEMENT_BASE(element_iframe), height, name, sandbox, seamless, src, srcdoc, width}).
--record(image,       {?ELEMENT_BASE(element_image), alt, height, ismap, src, usemap, width, image}).
--record(map,       {?ELEMENT_BASE(element_map), name}).
--record(object,       {?ELEMENT_BASE(element_object), data, form, height, name, type, usemap, width}).
--record(param,       {?ELEMENT_BASE(element_param), name, value}).
--record(source,       {?ELEMENT_BASE(element_source), media, src, type}).
--record(track,       {?ELEMENT_BASE(element_track), default, kind, label, src, srclang}).
--record(video,       {?ELEMENT_BASE(element_video), autoplay, controls, height, loop, mediagroup, muted, poster, preload, src, width}).
-
-% HTML Form
--record(button,       {?ELEMENT_BASE(element_button), autofocus, disabled, form, formaction, formenctype, formmethod, formtarget, formnovalidate, name, type= <<"button">>, value}).
--record(datalist,       ?DEFAULT_BASE).
--record(fieldset,       {?ELEMENT_BASE(element_fieldset), disabled, form, name, legend}).
--record(form,       {?ELEMENT_BASE(element_form), accept_charset, action, autocomplete, enctype, method, name, novalidate, target}).
--record(keygen,       {?ELEMENT_BASE(element_keygen), autofocus, challenge, disabled, form, keytype, name}).
--record(legend,       ?DEFAULT_BASE).
--record(label,       {?ELEMENT_BASE(element_label), for, form}).
--record(meter,       {?ELEMENT_BASE(element_meter), high, low, max, min, optimum, value}).
--record(optgroup,       {?ELEMENT_BASE(element_select), disabled, label}).
--record(option,       {?ELEMENT_BASE(element_select), disabled, label, selected=false, value}).
--record(output,       {?ELEMENT_BASE(element_output), for, form, name}).
--record(progress,       {?ELEMENT_BASE(element_progress), max, value}).
--record(select,       {?ELEMENT_BASE(element_select), autofocus, disabled, form, multiple, name, required, size}).
--record(textarea,       {?ELEMENT_BASE(element_textarea), autofocus, cols, dirname, disabled, form, maxlength, name, placeholder, readonly, required, rows, wrap, value}).
-
-% HTML Form inputs
--record(input,       {?ELEMENT_BASE(element_input),  autofocus, disabled, form, name, value, type=[], placeholder, multiple, min, max, pattern, accept}).
--record(input_button,       {?ELEMENT_BASE(element_input_button),  autofocus, disabled, form, name, value}).
--record(checkbox,           {?ELEMENT_BASE(element_checkbox),  autofocus, checked=false, disabled, form, name, required, value}).
--record(color,           {?ELEMENT_BASE(element_color),  autocomplete, autofocus, disabled, form, list, name, value}).
--record(date,           {?ELEMENT_BASE(element_date),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
--record(calendar,       {?ELEMENT_BASE(element_calendar),  autocomplete, autofocus, disabled, form, list, maxDate, minDate, format, pattern, name, step, readonly, required, value, placeholder, onSelect, disableDayFn, position,reposition,yearRange=100}).
--record(datetime,           {?ELEMENT_BASE(element_datetime),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
--record(datetime_local,           {?ELEMENT_BASE(element_datetime_local),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
--record(email,           {?ELEMENT_BASE(element_email),  autocomplete, autofocus, disabled, form, list, maxlength, multiple, name, pattern, placeholder, readonly, required, size, value}).
--record(file,           {?ELEMENT_BASE(element_file),  accept, autofocus, disabled, form, multiple, name, required}).
--record(hidden,           {?ELEMENT_BASE(element_hidden),  disabled, form, name, value, html_name}).
--record(input_image,           {?ELEMENT_BASE(element_input_image),  alt, autofocus, disabled, form, formaction, formenctype, formmethod, formnovalue, formtarget, height, name, src, width}).
--record(month,              {?ELEMENT_BASE(element_month),  alt, autocomplete, autofocus, disabled, form, list, min, max, name, readonly, required, step, value}).
--record(number,              {?ELEMENT_BASE(element_number),  autocomplete, autofocus, disabled, form, list, max, min, name, placeholder, readonly, required, step, value}).
--record(password,              {?ELEMENT_BASE(element_password),  autocomplete, autofocus, disabled, form, maxlength, name, pattern, placeholder, readonly, required, size, value}).
--record(radio,              {?ELEMENT_BASE(element_radio),  autofocus, checked, disabled, form, name, required, value, html_name}).
--record(range,              {?ELEMENT_BASE(element_range),  autocomplete, autofocus, disabled, form, list, max=100, min=0, name, step=1, value}).
--record(reset,              {?ELEMENT_BASE(element_reset),  autofocus, disabled, form, name, value}).
--record(search,              {?ELEMENT_BASE(element_search),  autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
--record(submit,              {?ELEMENT_BASE(element_submit),  autofocus, disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, value, click}).
--record(tel,              {?ELEMENT_BASE(element_tel),  autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
--record(textbox,              {?ELEMENT_BASE(element_textbox),  autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
--record(input_time,              {?ELEMENT_BASE(element_input_time),  autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value}).
--record(url,              {?ELEMENT_BASE(element_url),  autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value}).
--record(week,              {?ELEMENT_BASE(element_week),  autocomplete, autofocus, disabled, form, list, max, min, name, readonly, required, step, value}).
-
-% HTML Interactive
--record(command,       {?ELEMENT_BASE(element_command),  checked, disabled, icon, label, radiogroup, type= <<"command">>}).
--record(details,       {?ELEMENT_BASE(element_details),  open}).
--record(menu,       {?ELEMENT_BASE(element_menu),  label, type}).
--record(summary,       ?DEFAULT_BASE).
-
-% HTML Grouping content
--record(blockquote,		{?ELEMENT_BASE(element_blockquote),  cite}).
--record(br,       		?DEFAULT_BASE).
--record(dd,       		?DEFAULT_BASE).
--record('div',      	?DEFAULT_BASE_TAG(<<"div">>)).
--record(dl,       		?DEFAULT_BASE).
--record(dt,       		?DEFAULT_BASE).
--record(figcaption,		?DEFAULT_BASE).
--record(figure,       	?DEFAULT_BASE).
--record(hr,       		?DEFAULT_BASE).
--record(li,             {?ELEMENT_BASE(element_li),  value}).
--record(ol,             ?DEFAULT_BASE).
--record(p,       		?DEFAULT_BASE).
--record(panel,          ?DEFAULT_BASE_TAG(<<"div">>)).
--record(pre,       		?DEFAULT_BASE).
--record(ul,       		?DEFAULT_BASE).
-
-% HTML Root
--record(html,			{?ELEMENT_BASE(element_html), manifest}).
-
-% HTML Scripting
--record(script,			{?ELEMENT_BASE(element_script),  async, charset, defer, src, type}).
--record(noscript,      	?DEFAULT_BASE).
-
-% HTML Sections
--record(body,       	?DEFAULT_BASE).
--record(section,    	?DEFAULT_BASE).
--record(nav,        	?DEFAULT_BASE).
--record(article,    	?DEFAULT_BASE).
--record(aside,      	?DEFAULT_BASE).
--record(h1,         	?DEFAULT_BASE).
--record(h2,         	?DEFAULT_BASE).
--record(h3,         	?DEFAULT_BASE).
--record(h4,         	?DEFAULT_BASE).
--record(h5,         	?DEFAULT_BASE).
--record(h6,         	?DEFAULT_BASE).
--record(header,     	?DEFAULT_BASE).
--record(hgroup,     	?DEFAULT_BASE).
--record(footer,     	?DEFAULT_BASE).
--record(address,    	?DEFAULT_BASE).
--record(main,       	?DEFAULT_BASE).
-
-% HTML Table
--record(caption,       	?DEFAULT_BASE).
--record(col,            {?ELEMENT_BASE(element_col),  span}).
--record(colgroup,       {?ELEMENT_BASE(element_colgroup), col, span}).
--record(table,          {?ELEMENT_BASE(element_table),  caption, colgroup, border, footer, header}).
--record(tbody,          ?DEFAULT_BASE).
--record(td, 			{?ELEMENT_BASE(element_td), colspan=1, headers, rowspan=1, scope, bgcolor}).
--record(tfoot,       	?DEFAULT_BASE).
--record(th, 			{?ELEMENT_BASE(element_th), colspan=1, headers, rowspan=1, scope}).
--record(thead,       	?DEFAULT_BASE).
--record(tr, 			{?ELEMENT_BASE(element_tr), cells}).
-
-% HTML Text-level semantics
--record(link,           {?ELEMENT_BASE(element_link),  href, hreflang, media, rel, target, type, url="javascript:void(0);", download, name}).
--record(abbr,       	?DEFAULT_BASE).
--record(b,       		?DEFAULT_BASE).
--record(bdi,       		?DEFAULT_BASE).
--record(bdo,       		?DEFAULT_BASE).
--record(cite,       	?DEFAULT_BASE).
--record(code,       	?DEFAULT_BASE).
--record(dfn,       		?DEFAULT_BASE).
--record(em,       		?DEFAULT_BASE).
--record(i,       		?DEFAULT_BASE).
--record(kbd,       		?DEFAULT_BASE).
--record(mark,       	?DEFAULT_BASE).
--record(q,              {?ELEMENT_BASE(element_q),  cite}).
--record(rt,       		?DEFAULT_BASE).
--record(rp,       		?DEFAULT_BASE).
--record(ruby,       	?DEFAULT_BASE).
--record(s,       		?DEFAULT_BASE).
--record(samp,       	?DEFAULT_BASE).
--record(small,       	?DEFAULT_BASE).
--record(span,       	?DEFAULT_BASE).
--record(strong,       	?DEFAULT_BASE).
--record(sub,       		?DEFAULT_BASE).
--record(sup,       		?DEFAULT_BASE).
--record(time,           {?ELEMENT_BASE(element_time),  datetime}).
--record(u,       		?DEFAULT_BASE).
--record(var,       		?DEFAULT_BASE).
-
-% Extras
--record(upload,         {?CTRL_BASE(element_upload), name, value}).
-
-% HTML5 template
--record(template,		?DEFAULT_BASE).
-
-% Actions
--record(action,  {?ACTION_BASE(undefined)}).
--record(wire,    {?ACTION_BASE(action_wire)}).
-
--record(replace, {?ACTION_BASE(action_manage), elements}).
--record(insert,  {?ACTION_BASE(action_manage), elements, position = beforeend}).
--record(multi,   {?ACTION_BASE(action_manage)}).
--record(focus,   {?ACTION_BASE(action_ui)}).
-
--record(api,     {?ACTION_BASE(action_api), name, tag, delegate }).
--record(event,   {?ACTION_BASE(action_event), type=default, postback, delegate, validation=[]}).
--record(bind,    {?ACTION_BASE(action_bind), type=click, postback}).
--record(alert,   {?ACTION_BASE(action_alert), text}).
--record(confirm, {?ACTION_BASE(action_confirm), text, postback, delegate}).
--record(jq,      {?ACTION_BASE(action_jq), property, method, args=[], right, format="~s"}).
--record(transfer,{?ACTION_BASE(action_transfer), state, events=[] }).
+-ifndef(CTX).
+-define(CTX, (get(context))).
+-endif.
+
+-define(DEFAULT_BASE, {?ELEMENT_BASE([])} ).
+-define(DEFAULT_BASE_TAG(Tag), {?ELEMENT_BASE([], Tag, [])} ).
+-define(ELEMENT_BASE(Module), ?ELEMENT_BASE(Module, [], []) ).
+-define(ELEMENT_BASE(Module, Tag, Delegate),
+        ancestor = element, id = [], module = Module, delegate = Delegate, validation = [],
+        validate = [], actions = [], class = [], style = [], source = [], onmouseover = [], onmouseout = [], onmousemove = [],
+        onkeypress = [], onchange = [], onkeyup = [], onkeydown = [], onclick = [],
+        data_fields = [], aria_states = [], body = [], role = [], tabindex = [], show_if = true,
+        html_tag = Tag, title = [], postback = [], accesskey = [], contenteditable = [],
+        contextmenu = [], dir = [], draggable = [], dropzone = [], hidden = [], lang = [],
+        spellcheck = [], translate = [], onblur = [], onerror = [], onfocus = [],
+        onmessage = [], onresize = [], bind = []).
+
+-define(ACTION_BASE(Module), ancestor = action, trigger = [], target = [], module = Module, actions = [], source = [] ).
+-define(CTRL_BASE(Module), ?ELEMENT_BASE(Module, [], Module)).
+
+-record(element,    {?ELEMENT_BASE([])} ).
+-record(literal,    {?ELEMENT_BASE(element_literal), html_encode = true} ).
+-record(dtl,        {?ELEMENT_BASE(element_dtl), file = "index", bindings = [], app = web, folder="priv/templates", ext = "html", bind_script = true, js_escape = false} ).
+-record(list,       {?ELEMENT_BASE(element_list), numbered = false} ).
+-record(dropdown,   {?ELEMENT_BASE(element_dropdown), options, value, multiple = false, disabled = false, name} ).
+-record(radiogroup, {?ELEMENT_BASE(element_radiogroup)} ).
+-record(spinner,    {?ELEMENT_BASE(element_spinner), image = "/priv/static/spinner.gif"} ).
+
+%% HTML Document meta
+-record(base,      {?ELEMENT_BASE(element_meta_base), href = [], target = []} ).
+-record(head,      ?DEFAULT_BASE).
+-record(meta_link, {?ELEMENT_BASE(element_meta_link), href = [], hreflang = [], media = [], rel = [], sizes = [], type = []} ).
+-record(meta,      {?ELEMENT_BASE(element_meta), charset = [], content = [], http_equiv = [], name = [], type = []} ).
+-record(style,     {?ELEMENT_BASE(element_style), media = [], scoped = [], type = []} ).
+-record(title,     ?DEFAULT_BASE).
+
+%% HTML Edits
+-record('del',  {?ELEMENT_BASE(element_del), cite = [], datetime} ).
+-record(ins,    {?ELEMENT_BASE(element_ins), cite = [], datetime} ).
+
+%% HTML Embedded
+-record(area,   {?ELEMENT_BASE(element_area), alt, coords, href, hreflang, media, target, rel, shape, type} ).
+-record(audio,  {?ELEMENT_BASE(element_audio), autoplay, controls, loop, mediagroup, muted, preload, src, width} ).
+-record(canvas, {?ELEMENT_BASE(element_canvas), height, width} ).
+-record(embed,  {?ELEMENT_BASE(element_embed), height, src, type, width} ).
+-record(iframe, {?ELEMENT_BASE(element_iframe), height, name, sandbox, seamless, src, srcdoc, width} ).
+-record(image,  {?ELEMENT_BASE(element_image), alt, height, ismap, src, usemap, width, image} ).
+-record(map,    {?ELEMENT_BASE(element_map), name} ).
+-record(object, {?ELEMENT_BASE(element_object), data, form, height, name, type, usemap, width} ).
+-record(param,  {?ELEMENT_BASE(element_param), name, value} ).
+-record(source, {?ELEMENT_BASE(element_source), media, src, type} ).
+-record(track,  {?ELEMENT_BASE(element_track), default, kind, label, src, srclang} ).
+-record(video,  {?ELEMENT_BASE(element_video), autoplay, controls, height, loop, mediagroup, muted, poster, preload, src, width} ).
+
+%% HTML Form
+-record(button,   {?ELEMENT_BASE(element_button), autofocus = [], disabled = [], form = [], formaction = [], formenctype = [], formmethod = [], formtarget = [], formnovalidate = [], name = [], type = <<"button">>, value = []} ).
+-record(datalist, ?DEFAULT_BASE).
+-record(fieldset, {?ELEMENT_BASE(element_fieldset), disabled = [], form = [], name = [], legend = []} ).
+-record(form,     {?ELEMENT_BASE(element_form), accept_charset = [], action = [], autocomplete = [], enctype = [], method = [], name = [], novalidate = [], target = []} ).
+-record(keygen,   {?ELEMENT_BASE(element_keygen), autofocus = [], challenge = [], disabled = [], form = [], keytype = [], name = []} ).
+-record(legend,   ?DEFAULT_BASE).
+-record(label,    {?ELEMENT_BASE(element_label), for = [], form = []} ).
+-record(meter,    {?ELEMENT_BASE(element_meter), high = [], low = [], max = [], min = [], optimum = [], value = []} ).
+-record(optgroup, {?ELEMENT_BASE(element_select), disabled = [], label = []} ).
+-record(option,   {?ELEMENT_BASE(element_select), disabled = [], label = [], selected = false, value = []} ).
+-record(output,   {?ELEMENT_BASE(element_output), for, form, name} ).
+-record(progress, {?ELEMENT_BASE(element_progress), max = [], value = []} ).
+-record(select,   {?ELEMENT_BASE(element_select), autofocus = [], disabled = [], form = [], multiple = [], name = [], required = [], size = []} ).
+-record(textarea, {?ELEMENT_BASE(element_textarea), autofocus = [], cols = [], dirname = [], disabled = [], form = [], maxlength, name, placeholder, readonly = [], required = [], rows = [], wrap = [], value = []} ).
+
+%% HTML Form inputs
+-record(input,         {?ELEMENT_BASE(element_input), required, autocomplete, autofocus, disabled, form, name, value, type = [], checked = false, placeholder, multiple, min, max, pattern, accept} ).
+-record(input_button,  {?ELEMENT_BASE(element_input_button), autofocus, disabled, form, name, value} ).
+-record(checkbox,      {?ELEMENT_BASE(element_checkbox), autofocus, checked = false, disabled, form, name, required, value} ).
+-record(color,         {?ELEMENT_BASE(element_color), autocomplete, autofocus, disabled, form, list, name, value} ).
+-record(date,          {?ELEMENT_BASE(element_date), autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value} ).
+-record(datetime,      {?ELEMENT_BASE(element_datetime), autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value} ).
+-record(datetime_local,{?ELEMENT_BASE(element_datetime_local), autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value} ).
+-record(email,         {?ELEMENT_BASE(element_email), autocomplete, autofocus, disabled, form, list, maxlength, multiple, name, pattern, placeholder, readonly, required, size, value} ).
+-record(file,          {?ELEMENT_BASE(element_file), accept, autofocus, disabled, form, multiple, name, required} ).
+-record(hidden,        {?ELEMENT_BASE(element_hidden), disabled, form, name, value, html_name} ).
+-record(input_image,   {?ELEMENT_BASE(element_input_image), alt, autofocus, disabled, form, formaction, formenctype, formmethod, formnovalue, formtarget, height, name, src, width} ).
+-record(month,         {?ELEMENT_BASE(element_month), alt, autocomplete, autofocus, disabled, form, list, min, max, name, readonly, required, step, value} ).
+-record(number,        {?ELEMENT_BASE(element_number), autocomplete, autofocus, disabled, form, list, max, min, name, placeholder, readonly, required, step, value} ).
+-record(password,      {?ELEMENT_BASE(element_password), autocomplete, autofocus, disabled, form, maxlength, name, pattern, placeholder, readonly, required, size, value} ).
+-record(radio,         {?ELEMENT_BASE(element_radio), autofocus, checked, disabled, form, name, required, value, html_name} ).
+-record(range,         {?ELEMENT_BASE(element_range), autocomplete, autofocus, disabled, form, list, max = 100, min = 0, name, step = 1, value} ).
+-record(reset,         {?ELEMENT_BASE(element_reset), autofocus, disabled, form, name, value} ).
+-record(search,        {?ELEMENT_BASE(element_search), autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value} ).
+-record(submit,        {?ELEMENT_BASE(element_submit), autofocus, disabled, form, formaction, formenctype, formmethod, formnovalidate, formtarget, name, value, click} ).
+-record(tel,           {?ELEMENT_BASE(element_tel), autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value} ).
+-record(textbox,       {?ELEMENT_BASE(element_textbox), autocomplete, autofocus, dirname, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value} ).
+-record(input_time,    {?ELEMENT_BASE(element_input_time), autocomplete, autofocus, disabled, form, list, max, min, name, step, readonly, required, value} ).
+-record(url,           {?ELEMENT_BASE(element_url), autocomplete, autofocus, disabled, form, list, maxlength, name, pattern, placeholder, readonly, required, size, value} ).
+-record(week,          {?ELEMENT_BASE(element_week), autocomplete, autofocus, disabled, form, list, max, min, name, readonly, required, step, value} ).
+
+%% HTML Interactive
+-record(command, {?ELEMENT_BASE(element_command), checked, disabled, icon, label, radiogroup, type = <<"command">>} ).
+-record(details, {?ELEMENT_BASE(element_details), open} ).
+-record(menu,    {?ELEMENT_BASE(element_menu), label, type} ).
+-record(summary, ?DEFAULT_BASE).
+
+%% HTML Grouping content
+-record(blockquote, {?ELEMENT_BASE(element_blockquote), cite} ).
+-record(br,         ?DEFAULT_BASE).
+-record(dd,         ?DEFAULT_BASE).
+-record('div',      ?DEFAULT_BASE_TAG(<<"div">>)).
+-record(dl,         ?DEFAULT_BASE).
+-record(dt,         ?DEFAULT_BASE).
+-record(figcaption, ?DEFAULT_BASE).
+-record(figure,     ?DEFAULT_BASE).
+-record(hr,         ?DEFAULT_BASE).
+-record(li,         {?ELEMENT_BASE(element_li), value} ).
+-record(ol,         ?DEFAULT_BASE).
+-record(p,          ?DEFAULT_BASE).
+-record(panel,      ?DEFAULT_BASE_TAG(<<"div">>)).
+-record(pre,        ?DEFAULT_BASE).
+-record(ul,         ?DEFAULT_BASE).
+
+%% HTML Root
+-record(html,     {?ELEMENT_BASE(element_html), manifest} ).
+
+%% HTML Scripting
+-record(script,   {?ELEMENT_BASE(element_script), async = [], charset = [], defer = [], src = [], type = []} ).
+-record(noscript, ?DEFAULT_BASE).
+
+%% HTML Sections
+-record(body,    ?DEFAULT_BASE).
+-record(section, ?DEFAULT_BASE).
+-record(nav,     ?DEFAULT_BASE).
+-record(article, ?DEFAULT_BASE).
+-record(aside,   ?DEFAULT_BASE).
+-record(h1,      ?DEFAULT_BASE).
+-record(h2,      ?DEFAULT_BASE).
+-record(h3,      ?DEFAULT_BASE).
+-record(h4,      ?DEFAULT_BASE).
+-record(h5,      ?DEFAULT_BASE).
+-record(h6,      ?DEFAULT_BASE).
+-record(header,  ?DEFAULT_BASE).
+-record(hgroup,  ?DEFAULT_BASE).
+-record(footer,  ?DEFAULT_BASE).
+-record(address, ?DEFAULT_BASE).
+-record(main,    ?DEFAULT_BASE).
+
+%% HTML Table
+-record(caption,  ?DEFAULT_BASE).
+-record(col,      {?ELEMENT_BASE(element_col), span} ).
+-record(colgroup, {?ELEMENT_BASE(element_colgroup), col, span} ).
+-record(table,    {?ELEMENT_BASE(element_table), caption, colgroup, border, footer, header} ).
+-record(tbody,    ?DEFAULT_BASE).
+-record(td,       {?ELEMENT_BASE(element_td), colspan = 1, headers, rowspan = 1, scope, bgcolor} ).
+-record(tfoot,    ?DEFAULT_BASE).
+-record(th,       {?ELEMENT_BASE(element_th), colspan = 1, headers, rowspan = 1, scope} ).
+-record(thead,    ?DEFAULT_BASE).
+-record(tr,       {?ELEMENT_BASE(element_tr), cells} ).
+
+%% HTML Text-level semantics
+-record(link,   {?ELEMENT_BASE(element_link), href, hreflang, media, rel, target, type, url = "javascript:void(0);", download, name} ).
+-record(abbr,   ?DEFAULT_BASE).
+-record(b,      ?DEFAULT_BASE).
+-record(bdi,    ?DEFAULT_BASE).
+-record(bdo,    ?DEFAULT_BASE).
+-record(cite,   ?DEFAULT_BASE).
+-record(code,   ?DEFAULT_BASE).
+-record(dfn,    ?DEFAULT_BASE).
+-record(em,     ?DEFAULT_BASE).
+-record(i,      ?DEFAULT_BASE).
+-record(kbd,    ?DEFAULT_BASE).
+-record(mark,   ?DEFAULT_BASE).
+-record(q,      {?ELEMENT_BASE(element_q), cite}).
+-record(rt,     ?DEFAULT_BASE).
+-record(rp,     ?DEFAULT_BASE).
+-record(ruby,   ?DEFAULT_BASE).
+-record(s,      ?DEFAULT_BASE).
+-record(samp,   ?DEFAULT_BASE).
+-record(small,  ?DEFAULT_BASE).
+-record(span,   ?DEFAULT_BASE).
+-record(strong, ?DEFAULT_BASE).
+-record(sub,    ?DEFAULT_BASE).
+-record(sup,    ?DEFAULT_BASE).
+-record('time', {?ELEMENT_BASE(element_time), datetime}).
+-record(u,      ?DEFAULT_BASE).
+-record(var,    ?DEFAULT_BASE).
+
+%% Extras
+-record(upload, {?CTRL_BASE(element_upload), name, value} ).
+
+%% HTML5 template
+-record(template, ?DEFAULT_BASE).
+-record(message,  ?DEFAULT_BASE).
+-record(author,   ?DEFAULT_BASE).
+
+-record(action, {?ACTION_BASE(undefined)} ).
+-record(wire,   {?ACTION_BASE(action_wire)} ).
+
+-record(replace, {?ACTION_BASE(action_manage), elements} ).
+-record(insert,  {?ACTION_BASE(action_manage), elements, position = beforeend} ).
+-record(multi,   {?ACTION_BASE(action_manage)} ).
+-record(focus,   {?ACTION_BASE(action_ui)} ).
+
+-record(api,     {?ACTION_BASE(action_api), name, tag, delegate} ).
+-record(bind,    {?ACTION_BASE(action_bind), type = click, postback} ).
+-record(alert,   {?ACTION_BASE(action_alert), text} ).
+-record(confirm, {?ACTION_BASE(action_confirm), text, postback, delegate} ).
+-record(jq,      {?ACTION_BASE(action_jq), property, method, args = [], right, format = "~s"} ).
+-record(transfer,{?ACTION_BASE(action_transfer), state, events = []} ).
 
 -endif.
+

+ 13 - 0
include/proto.hrl

@@ -0,0 +1,13 @@
+-ifndef(NITRO_PROTO_HRL).
+-define(NITRO_PROTO_HRL, true).
+
+-record(client,  { data = []} ).
+-record(server,  { data = []} ).
+-record(init,    { token = []} ).
+-record(pickle,  { source = [], pickled = [], args = []} ).
+-record(flush,   { data = []} ).
+-record(direct,  { data = []} ).
+-record(ev,      { module = [], msg = [], trigger = [], name = []} ).
+
+-endif.
+

+ 11 - 0
include/sortable_item.hrl

@@ -0,0 +1,11 @@
+-ifndef(SORTABLE_ITEM_NX_HRL).
+-define(SORTABLE_ITEM_NX_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+-record(sortable_item, {?ELEMENT_BASE(element_sortable_item), list_id, value, closeable, disabled} ).
+
+
+-endif.
+

+ 11 - 0
include/sortable_list.hrl

@@ -0,0 +1,11 @@
+-ifndef(SORTABLE_LIST_NX_HRL).
+-define(SORTABLE_LIST_NX_HRL, true).
+
+
+-include_lib("nitro/include/nitro.hrl").
+
+-record(sortable_list, {?ELEMENT_BASE(element_sortable_list), values, closeable, disabled} ).
+
+
+-endif.
+

+ 34 - 0
lib/NITRO.ex

@@ -0,0 +1,34 @@
+defmodule NITRO do
+  require Record
+
+  files = ["calendar.hrl", "nitro.hrl", "comboLookup.hrl",
+           "comboLookupVec.hrl", "comboLookupEdit.hrl", "koatuuControl.hrl"]
+
+  hrl_files =
+    Enum.filter(files, fn f ->
+      !String.contains?(f, "/_") and Path.extname(f) == ".hrl"
+    end)
+
+  Enum.each(
+    hrl_files,
+    fn t ->
+      Enum.each(
+        Record.extract_all(from_lib: "nitro/include/" <> t),
+        fn {name, definition} ->
+#          IO.inspect({name, definition, t})
+          prev = :application.get_env(:kernel, :nitro_tables, [])
+
+          case :lists.member(name, prev) do
+            true ->
+              :skip
+
+            false ->
+              Record.defrecord(name, definition)
+              :application.set_env(:kernel, :nitro_tables, [name | prev])
+          end
+        end
+      )
+    end
+  )
+
+end

+ 199 - 0
lib/combo.ex

@@ -0,0 +1,199 @@
+defmodule NITRO.Combo do
+  require NITRO
+  require Record
+
+  def to_list(fld) when is_atom(fld), do: fld
+  def to_list(fld), do: :unicode.characters_to_list(fld, :unicode)
+
+  def new(_name, obj, module, options) do
+    dom = :proplists.get_value(:dom, options)
+    feed = :proplists.get_value(:feed, options)
+    dropDown(obj, dom, module, feed)
+  end
+
+  def proto(NITRO.comboKey(value: :all) = msg), do: keyUp(msg)
+  def proto(NITRO.comboKey(delegate: []) = msg), do: keyUp(msg)
+
+  def proto(NITRO.comboKey(delegate: module) = msg) do
+    case has_function(module, :keyUp) do
+      true -> module.keyUp(msg)
+      false -> keyUp(msg)
+    end
+  end
+
+  def proto(NITRO.comboSelect(delegate: module) = msg) do
+    case has_function(module, :select) do
+      true -> module.select(msg)
+      false -> :skip
+    end
+  end
+
+  def keyUp(NITRO.comboKey(value: :all, dom: field0, feed: feed, delegate: module)) do
+    field = :nitro.to_list(field0)
+    all = apply(:kvs,:all,[feed])
+    :nitro.clear(:nitro.atom([:comboContainer, field]))
+    dropDownList0(all, field, module, feed)
+  end
+
+  def keyUp(NITRO.comboKey(value: value0, dom: field0, feed: feed, delegate: module)) do
+    value = :string.lowercase(:unicode.characters_to_list(value0, :unicode))
+    field = :nitro.to_list(field0)
+    all = apply(:kvs,:all,[feed])
+    index = index(module)
+    :nitro.clear(:nitro.atom([:comboContainer, field]))
+    :nitro.wire("comboLookupChange('#{field0}');")
+
+    filtered =
+      Enum.filter(all, fn x ->
+        Enum.any?(index, fn i ->
+          fld0 =
+            if is_function(i) do
+              i.(x)
+            else
+              f0 = apply(:kvs,:field, [x, i])
+              tab = elem(x, 0)
+
+              if f0 == tab do
+                :nitro.to_list(f0)
+              else
+                f0
+              end
+            end
+
+          fld = to_list(fld0)
+          fld != elem(x, 0) and :string.rstr(:string.lowercase(:nitro.to_list(fld)), value) > 0
+        end)
+      end)
+
+    dropDownList0(filtered, field, module, feed)
+  end
+
+  def dropDownList0(filtered, field, module, feed) do
+    if has_function(module, :dropDownList) do
+      module.dropDownList(filtered, field, module, feed)
+    else
+      dropDownList(filtered, field, module, feed)
+    end
+  end
+
+  def dropDownList(filtered, field, module, feed) do
+    formId = :nitro.atom([field, "form"])
+
+    if filtered == [] do
+      :nitro.wire("activeCombo = undefined; currentItem = undefined;")
+      :nitro.hide(:nitro.atom([:comboContainer, field]))
+      :nitro.wire("comboOpenFormById('#{formId}');")
+    else
+      :nitro.display(:nitro.atom([:comboContainer, field]), :block)
+      :nitro.wire("comboCloseFormById('#{formId}');")
+    end
+
+    Enum.each(filtered, fn obj ->
+      form = dropDown0(obj, field, module, feed)
+
+      :nitro.insert_top(
+        :nitro.atom([:comboContainer, field]),
+        :nitro.render(form)
+      )
+    end)
+  end
+
+  def dropDown0(obj, dom0, module, feed) do
+    case has_function(module, :dropDown) do
+      true ->
+        module.dropDown(obj, dom0, feed)
+
+      false ->
+        dropDown(obj, dom0, module, feed)
+    end
+  end
+
+  def view_value(obj, _, _) when obj in ["", [], :undefined], do: []
+
+  def view_value(obj, module, feed) do
+    case :erlang.function_exported(module, :view_value, 2) do
+      true ->
+        module.view_value(obj, feed)
+
+      false ->
+        if has_function(module, :view_value) do
+          module.view_value(obj)
+        else
+          view_value(obj)
+        end
+    end
+  end
+
+  def view_value(obj) when obj in ["", [], :undefined], do: []
+  def view_value(obj), do: :nitro.jse(:erlang.iolist_to_binary(apply(:kvs,:field,[obj, hd(index([]))])))
+
+  def dropDown(obj, dom0, module, feed) do
+    view_value = view_value(obj, module, feed)
+    dom = :nitro.to_list(dom0)
+    id = :nitro.jse(:erlang.iolist_to_binary(:nitro.atom([dom, :erlang.element(2, obj)])))
+    item = :nitro.to_list(item(obj, module))
+    source = :erlang.iolist_to_binary(feed)
+    click = :nitro.jse("comboSelect('#{dom}', '#{view_value}', '#{source}', '#{module}', '#{id}')")
+    move = :nitro.jse("comboLookupMouseMove('#{dom}')")
+
+    NITRO.panel(
+      id: id,
+      class: ['dropdown-item'],
+      bind: :base64.encode(:erlang.term_to_binary(obj)),
+      onclick: click,
+      onmousemove: move,
+      body: [
+        NITRO.p(body: item)
+      ]
+    )
+  end
+
+  def index([]), do: [:name, :id]
+
+  def index(module) do
+    case has_function(module, :index) do
+      true -> module.index()
+      false -> index([])
+    end
+  end
+
+  def item(obj, []), do: view_value(obj)
+
+  def item(obj, module) do
+    case has_function(module, :item) do
+      true -> module.item(obj)
+      false -> item(obj, [])
+    end
+  end
+
+  def update_comboVec(_parent, dom, feed, module, default, elem) do
+    vector  = view_value(default, module, feed)
+    clear   = "createSortable('##{dom}_list');"
+    append  = :lists.map(fn emp -> bind = emp |> :erlang.term_to_binary |> :base64.encode
+                 "appendItemFromBind('#{dom}_list','#{module.cn(emp)}','#{bind}');" end, vector)
+              |> :erlang.iolist_to_binary
+    render  = :nitro.render elem
+    command = "elem = qi('#{dom}'); elem.outerHTML = '#{render}';"
+    :nitro.wire(command <> append <> clear) # reverse
+  end
+
+  def update_combo(id, feed, module), do: :nitro.update(:nitro.atom([:lookup, id]), NITRO.comboLookup(id: id, feed: feed, delegate: module))
+
+  def update_combo_value(dom, value, feed, module) do
+    view_value = view_value(value, module, feed)
+    update_combo_value(dom, value, feed, module, view_value)
+  end
+
+  def update_combo_value(dom, value, feed, module, view_value) do
+    bind = :base64.encode(:erlang.term_to_binary(value))
+    update_combo(dom, feed, module)
+    command = "elem = qi('#{dom}'); elem.setAttribute('data-bind', '#{bind}'); elem.value = '#{view_value}';"
+    :nitro.wire(command)
+  end
+
+  def has_function(m, f) do
+    functions = apply(m, :module_info, [:exports])
+    isF = Keyword.get(functions, f, -1)
+    isF != -1
+  end
+end

+ 0 - 948
priv/css/redactor.css

@@ -1,948 +0,0 @@
-/*
-	Icon font
-*/
-@font-face {
-  font-family: 'RedactorFont';
-  src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
-  font-weight: normal;
-  font-style: normal;
-}
-/*
-	Box
-*/
-.redactor-box {
-  position: relative;
-  overflow: visible;
-  margin-bottom: 24px;
-}
-.redactor-box textarea {
-  display: block;
-  position: relative;
-  margin: 0;
-  padding: 0;
-  width: 100%;
-  overflow: auto;
-  outline: none;
-  border: none;
-  background-color: #111;
-  box-shadow: none;
-  color: #ccc;
-  font-size: 13px;
-  font-family: Menlo, Monaco, monospace, sans-serif !important;
-  resize: none;
-}
-.redactor-box textarea:focus {
-  outline: none;
-}
-.redactor-editor,
-.redactor-box {
-  background: #fff;
-}
-/*
-	Z-index setup
-*/
-.redactor-editor,
-.redactor-box,
-.redactor-box textarea {
-  z-index: auto;
-}
-.redactor-box-fullscreen {
-  z-index: 1051;
-}
-.redactor-toolbar {
-  z-index: 100;
-}
-.redactor-dropdown {
-  z-index: 1052;
-}
-#redactor-modal-overlay,
-#redactor-modal-box,
-#redactor-modal {
-  z-index: 1053;
-}
-/*
-	Fullscreen
-*/
-body .redactor-box-fullscreen {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-}
-/*
-	Utils
-*/
-.redactor-scrollbar-measure {
-  position: absolute;
-  top: -9999px;
-  width: 50px;
-  height: 50px;
-  overflow: scroll;
-}
-/*
-	Editor
-*/
-.redactor-editor {
-  position: relative;
-  overflow: auto;
-  margin: 0 !important;
-  padding: 20px;
-  min-height: 80px;
-  outline: none;
-  white-space: normal;
-  border: 1px solid #eee;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  font-size: 14px;
-  line-height: 1.6em;
-}
-.redactor-editor:focus {
-  outline: none;
-}
-.toolbar-fixed-box + .redactor-editor {
-  padding-top: 32px !important;
-}
-/*
-	Placeholder
-*/
-.redactor-placeholder:after {
-  position: absolute;
-  top: 20px;
-  left: 20px;
-  content: attr(placeholder);
-  display: block;
-  /* For Firefox */
-  color: #999 !important;
-  font-weight: normal !important;
-}
-/*
-	Toolbar
-*/
-.redactor-toolbar {
-  position: relative;
-  top: 0;
-  left: 0;
-  margin: 0 !important;
-  padding: 0 !important;
-  list-style: none !important;
-  font-size: 14px !important;
-  line-height: 1 !important;
-  background: #fff;
-  border: none;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
-}
-.redactor-toolbar:after {
-  content: "";
-  display: table;
-  clear: both;
-}
-.redactor-toolbar.redactor-toolbar-overflow {
-  overflow-y: auto;
-  height: 29px;
-  white-space: nowrap;
-}
-.redactor-toolbar.redactor-toolbar-external {
-  z-index: 999;
-  box-shadow: none;
-  border: 1px solid rgba(0, 0, 0, 0.1);
-}
-.redactor-toolbar li {
-  vertical-align: top;
-  display: inline-block;
-  margin: 0 !important;
-  padding: 0 !important;
-  outline: none;
-  list-style: none !important;
-  -webkit-box-sizing: content-box;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-.redactor-toolbar li a {
-  display: block;
-  color: #333;
-  text-align: center;
-  padding: 9px 10px;
-  outline: none;
-  border: none;
-  text-decoration: none;
-  cursor: pointer;
-  zoom: 1;
-  -webkit-box-sizing: content-box;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-.redactor-toolbar li a:hover {
-  outline: none;
-  background-color: #1f78d8;
-  color: #fff;
-}
-.redactor-toolbar li a:hover i:before {
-  color: #fff;
-}
-.redactor-toolbar li a:active,
-.redactor-toolbar li a.redactor-act {
-  outline: none;
-  background-color: #ccc;
-  color: #444;
-}
-.redactor-toolbar li a.redactor-btn-image {
-  width: 14px;
-  height: 14px;
-  background-position: center center;
-  background-repeat: no-repeat;
-}
-.redactor-toolbar li a.fa-redactor-btn {
-  display: inline-block;
-  padding: 9px 10px 8px 10px;
-  line-height: 1;
-}
-.redactor-toolbar li a.redactor-button-disabled {
-  filter: alpha(opacity=30);
-  -moz-opacity: 0.3;
-  opacity: 0.3;
-}
-.redactor-toolbar li a.redactor-button-disabled:hover {
-  color: #333;
-  outline: none;
-  background-color: transparent !important;
-  cursor: default;
-}
-.redactor-toolbar li a.redactor-button-focus {
-  color: #fff;
-  background: #000;
-}
-/*
-	CodeMirror
-*/
-.redactor-box .CodeMirror {
-  display: none;
-}
-/*
-	Icons
-*/
-.re-icon {
-  font-family: 'RedactorFont';
-  speak: none;
-  font-style: normal;
-  font-weight: normal;
-  font-variant: normal;
-  text-transform: none;
-  line-height: 1;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-.re-icon i:before {
-  position: relative;
-  font-size: 14px;
-}
-.re-video:before {
-  content: "\e600";
-}
-.re-unorderedlist:before {
-  content: "\e601";
-}
-.re-undo:before {
-  content: "\e602";
-}
-.re-underline:before {
-  content: "\e603";
-}
-.re-textdirection:before {
-  content: "\e604";
-}
-.re-fontcolor:before {
-  content: "\e605";
-}
-.re-table:before {
-  content: "\e606";
-}
-.re-redo:before {
-  content: "\e607";
-}
-.re-quote:before {
-  content: "\e608";
-}
-.re-outdent:before {
-  content: "\e609";
-}
-.re-orderedlist:before {
-  content: "\e60a";
-}
-.re-link:before {
-  content: "\e60b";
-}
-.re-horizontalrule:before {
-  content: "\e60c";
-}
-.re-italic:before {
-  content: "\e60d";
-}
-.re-indent:before {
-  content: "\e60e";
-}
-.re-image:before {
-  content: "\e60f";
-}
-.re-fullscreen:before {
-  content: "\e610";
-}
-.re-normalscreen:before {
-  content: "\e611";
-}
-.re-formatting:before {
-  content: "\e612";
-}
-.re-fontsize:before {
-  content: "\e613";
-}
-.re-fontfamily:before {
-  content: "\e614";
-}
-.re-deleted:before {
-  content: "\e615";
-}
-.re-html:before {
-  content: "\e616";
-}
-.re-clips:before {
-  content: "\e617";
-}
-.re-bold:before {
-  content: "\e618";
-}
-.re-backcolor:before {
-  content: "\e619";
-}
-.re-file:before {
-  content: "\e61a";
-}
-.re-alignright:before {
-  content: "\e61b";
-}
-.re-alignment:before,
-.re-alignleft:before {
-  content: "\e61c";
-}
-.re-alignjustify:before {
-  content: "\e61d";
-}
-.re-aligncenter:before {
-  content: "\e61e";
-}
-.re-gallery:before {
-  content: "\e61f";
-}
-/*
-	Toolbar tooltip
-*/
-.redactor-toolbar-tooltip {
-  position: absolute;
-  z-index: 1054;
-  text-align: center;
-  top: 0;
-  left: 0;
-  background: #000;
-  color: #fff;
-  padding: 5px 8px;
-  line-height: 1;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  font-size: 12px;
-  border-radius: 2px;
-}
-/*
-	Dropdown
-*/
-.redactor-dropdown {
-  position: absolute;
-  top: 28px;
-  left: 0;
-  padding: 0;
-  min-width: 220px;
-  max-height: 254px;
-  overflow: auto;
-  background-color: #fff;
-  box-shadow: 0 1px 7px rgba(0, 0, 0, 0.25);
-  font-size: 14px;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  line-height: 1.6em;
-}
-.redactor-dropdown a {
-  display: block;
-  padding: 10px 15px;
-  color: #000;
-  text-decoration: none;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.07);
-}
-.redactor-dropdown a:last-child {
-  border-bottom: none;
-}
-.redactor-dropdown a:hover {
-  background-color: #1f78d8;
-  color: #fff !important;
-  text-decoration: none;
-}
-.redactor-dropdown a.selected {
-  background-color: #000;
-  color: #fff;
-}
-.redactor-dropdown a.redactor-dropdown-link-inactive,
-.redactor-dropdown a.redactor-dropdown-link-inactive:hover {
-  background: none;
-  cursor: default;
-  color: #000 !important;
-  filter: alpha(opacity=40);
-  -moz-opacity: 0.4;
-  opacity: 0.4;
-}
-.redactor-dropdown a.redactor-dropdown-link-selected {
-  color: #fff;
-  background: #000;
-}
-/*
-	IMAGE BOX
-*/
-#redactor-image-box {
-  position: relative;
-  max-width: 100%;
-  display: inline-block;
-  line-height: 0;
-  outline: 1px dashed rgba(0, 0, 0, 0.6);
-}
-#redactor-image-editter {
-  position: absolute;
-  z-index: 5;
-  top: 50%;
-  left: 50%;
-  margin-top: -11px;
-  margin-left: -18px;
-  line-height: 1;
-  background-color: #000;
-  color: #fff;
-  font-size: 11px;
-  padding: 7px 10px;
-  cursor: pointer;
-}
-#redactor-image-resizer {
-  position: absolute;
-  z-index: 2;
-  line-height: 1;
-  cursor: nw-resize;
-  bottom: -4px;
-  right: -5px;
-  border: 1px solid #fff;
-  background-color: #000;
-  width: 8px;
-  height: 8px;
-}
-/*
-	LINK TOOLTIP
-*/
-.redactor-link-tooltip {
-  position: absolute;
-  z-index: 99;
-  padding: 10px;
-  line-height: 1;
-  display: inline-block;
-  background-color: #000;
-  color: #555 !important;
-}
-.redactor-link-tooltip,
-.redactor-link-tooltip a {
-  font-size: 12px;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-}
-.redactor-link-tooltip a {
-  color: #ccc;
-  margin: 0 5px;
-  text-decoration: none;
-}
-.redactor-link-tooltip a:hover {
-  color: #fff;
-}
-/*
-	DROPAREA
-*/
-#redactor-droparea {
-  position: relative;
-  overflow: hidden;
-  padding: 140px 20px;
-  border: 3px dashed rgba(0, 0, 0, 0.1);
-}
-#redactor-droparea.drag-hover {
-  background: rgba(200, 222, 250, 0.75);
-}
-#redactor-droparea.drag-drop {
-  background: rgba(250, 248, 200, 0.5);
-}
-#redactor-droparea-placeholder {
-  text-align: center;
-  font-size: 12px;
-  color: rgba(0, 0, 0, 0.7);
-}
-/*
-	PROGRESS
-*/
-#redactor-progress {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  z-index: 1000000;
-  height: 10px;
-}
-#redactor-progress span {
-  display: block;
-  width: 100%;
-  height: 100%;
-  background-color: #3d58a8;
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
-  -webkit-animation: progress-bar-stripes 2s linear infinite;
-  -o-animation: progress-bar-stripes 2s linear infinite;
-  animation: progress-bar-stripes 2s linear infinite;
-  background-size: 40px 40px;
-}
-@-webkit-keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-@-o-keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-@keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-/*
-	MODAL
-*/
-#redactor-modal-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  margin: auto;
-  overflow: auto;
-  width: 100%;
-  height: 100%;
-  background-color: #000 !important;
-  filter: alpha(opacity=30);
-  -moz-opacity: 0.3;
-  opacity: 0.3;
-}
-#redactor-modal-box {
-  position: fixed;
-  top: 0;
-  left: 0;
-  bottom: 0;
-  right: 0;
-  overflow-x: hidden;
-  overflow-y: auto;
-}
-#redactor-modal {
-  outline: 0;
-  position: relative;
-  margin: auto;
-  margin-bottom: 20px;
-  padding: 0;
-  background: #fff;
-  color: #000;
-  font-size: 14px !important;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  box-shadow: 0 1px 70px rgba(0, 0, 0, 0.5);
-}
-#redactor-modal header {
-  padding: 30px 40px 5px 40px;
-  font-size: 18px;
-  font-weight: bold;
-}
-#redactor-modal section {
-  padding: 30px 40px 50px 40px;
-}
-#redactor-modal label {
-  display: block;
-  float: none !important;
-  margin: 15px 0 3px 0 !important;
-  padding: 0;
-}
-#redactor-modal input[type="radio"],
-#redactor-modal input[type="checkbox"] {
-  position: relative;
-  top: -1px;
-}
-#redactor-modal select {
-  width: 100%;
-}
-#redactor-modal input[type="text"],
-#redactor-modal input[type="password"],
-#redactor-modal input[type="email"],
-#redactor-modal input[type="url"],
-#redactor-modal textarea {
-  position: relative;
-  z-index: 2;
-  margin: 0;
-  padding: 5px 4px;
-  height: 28px;
-  border: 1px solid #ccc;
-  border-radius: 1px;
-  background-color: white;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset;
-  color: #333;
-  width: 100%;
-  font-size: 14px;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  -moz-transition: border 0.3s ease-in;
-  transition: border 0.3s ease-in;
-}
-#redactor-modal input[type="text"]:focus,
-#redactor-modal input[type="password"]:focus,
-#redactor-modal input[type="email"]:focus,
-#redactor-modal input[type="url"]:focus,
-#redactor-modal textarea:focus {
-  outline: none;
-  border-color: #5ca9e4;
-  box-shadow: 0 0 0 2px rgba(70, 161, 231, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2) inset;
-}
-#redactor-modal input[type="text"].redactor-input-error,
-#redactor-modal input[type="password"].redactor-input-error,
-#redactor-modal input[type="email"].redactor-input-error,
-#redactor-modal input[type="url"].redactor-input-error,
-#redactor-modal textarea.redactor-input-error {
-  border-color: #e82f2f;
-  box-shadow: 0 0 0 2px rgba(232, 47, 47, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2) inset;
-}
-#redactor-modal textarea {
-  display: block;
-  margin-top: 4px;
-  line-height: 1.4em;
-}
-/*
-	Tabs in Modal
-*/
-#redactor-modal-tabber {
-  margin-bottom: 15px;
-  font-size: 12px;
-}
-#redactor-modal-tabber a {
-  border: 1px solid #ddd;
-  line-height: 1;
-  padding: 8px 15px;
-  margin-right: -1px;
-  text-decoration: none;
-  color: #000;
-}
-#redactor-modal-tabber a:hover {
-  background-color: #1f78d8;
-  border-color: #1f78d8;
-  color: #fff;
-}
-#redactor-modal-tabber a.active {
-  cursor: default;
-  background-color: #ddd;
-  border-color: #ddd;
-  color: rgba(0, 0, 0, 0.6);
-}
-/*
-	List in Modal
-*/
-#redactor-modal #redactor-modal-list {
-  margin-left: 0;
-  padding-left: 0;
-  list-style: none;
-  max-height: 250px;
-  overflow-x: auto;
-}
-#redactor-modal #redactor-modal-list li {
-  border-bottom: 1px solid #ddd;
-}
-#redactor-modal #redactor-modal-list li:last-child {
-  border-bottom: none;
-}
-#redactor-modal #redactor-modal-list a {
-  padding: 10px 5px;
-  color: #000;
-  text-decoration: none;
-  font-size: 13px;
-  display: block;
-  position: relative;
-}
-#redactor-modal #redactor-modal-list a:hover {
-  background-color: #eee;
-}
-#redactor-modal-close {
-  position: absolute;
-  top: 10px;
-  right: 10px;
-  width: 30px;
-  height: 30px;
-  text-align: right;
-  color: #bbb;
-  font-size: 30px;
-  font-weight: 300;
-  cursor: pointer;
-  -webkit-appearance: none;
-  padding: 0;
-  border: 0;
-  background: 0;
-  outline: none;
-}
-#redactor-modal-close:hover {
-  color: #000;
-}
-#redactor-modal footer button {
-  position: relative;
-  width: 100%;
-  padding: 14px 16px;
-  margin: 0;
-  outline: none;
-  border: none;
-  background-color: #ddd;
-  color: #000;
-  text-align: center;
-  text-decoration: none;
-  font-weight: normal;
-  font-size: 12px;
-  font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-  line-height: 1;
-  cursor: pointer;
-}
-#redactor-modal footer button:hover {
-  color: #777;
-  background: none;
-  background: #bbb;
-  text-decoration: none;
-}
-#redactor-modal footer button.redactor-modal-delete-btn {
-  background: none;
-  color: #fff;
-  background-color: #b52525;
-}
-#redactor-modal footer button.redactor-modal-delete-btn:hover {
-  color: rgba(255, 255, 255, 0.6);
-  background-color: #881b1b;
-}
-#redactor-modal footer button.redactor-modal-action-btn {
-  background: none;
-  color: #fff;
-  background-color: #2461b5;
-}
-#redactor-modal footer button.redactor-modal-action-btn:hover {
-  color: rgba(255, 255, 255, 0.6);
-  background-color: #1a4580;
-}
-/*
-	##############################################
-
-	DROPDOWN FORMATTING
-
-	##############################################
-*/
-.redactor-dropdown .redactor-formatting-blockquote {
-  color: rgba(0, 0, 0, 0.4);
-  font-style: italic;
-}
-.redactor-dropdown .redactor-formatting-pre {
-  font-family: monospace, sans-serif;
-}
-.redactor-dropdown .redactor-formatting-h1 {
-  font-size: 36px;
-  line-height: 36px;
-  font-weight: bold;
-}
-.redactor-dropdown .redactor-formatting-h2 {
-  font-size: 24px;
-  line-height: 36px;
-  font-weight: bold;
-}
-.redactor-dropdown .redactor-formatting-h3 {
-  font-size: 21px;
-  line-height: 30px;
-  font-weight: bold;
-}
-.redactor-dropdown .redactor-formatting-h4 {
-  font-size: 18px;
-  line-height: 26px;
-  font-weight: bold;
-}
-.redactor-dropdown .redactor-formatting-h5 {
-  font-size: 16px;
-  line-height: 23px;
-  font-weight: bold;
-}
-/*
-	##############################################
-
-	 CONTENT STYLES
-
-	##############################################
-*/
-.redactor-editor code,
-.redactor-editor pre {
-  font-family: Menlo, Monaco, monospace, sans-serif !important;
-  cursor: text;
-}
-.redactor-editor div,
-.redactor-editor p,
-.redactor-editor ul,
-.redactor-editor ol,
-.redactor-editor table,
-.redactor-editor dl,
-.redactor-editor blockquote,
-.redactor-editor pre {
-  font-size: 14px;
-  line-height: 1.6em;
-}
-.redactor-editor a {
-  color: #15c;
-  text-decoration: underline;
-}
-.redactor-editor object,
-.redactor-editor embed,
-.redactor-editor video,
-.redactor-editor img {
-  max-width: 100%;
-  width: auto;
-}
-.redactor-editor video,
-.redactor-editor img {
-  height: auto;
-}
-.redactor-editor div,
-.redactor-editor p,
-.redactor-editor ul,
-.redactor-editor ol,
-.redactor-editor table,
-.redactor-editor dl,
-.redactor-editor figure,
-.redactor-editor blockquote,
-.redactor-editor pre {
-  margin: 0;
-  margin-bottom: 15px;
-  border: none;
-  background: none;
-  box-shadow: none;
-}
-.redactor-editor iframe,
-.redactor-editor object,
-.redactor-editor hr {
-  margin-bottom: 15px;
-}
-.redactor-editor blockquote {
-  margin-left: 1.6em !important;
-  padding: 0;
-  text-align: left;
-  color: #777;
-  font-style: italic;
-}
-.redactor-editor blockquote:before,
-.redactor-editor blockquote:after {
-  content: '';
-}
-.redactor-editor ul,
-.redactor-editor ol {
-  padding-left: 2em;
-}
-.redactor-editor ul ul,
-.redactor-editor ol ol,
-.redactor-editor ul ol,
-.redactor-editor ol ul {
-  margin: 2px;
-  padding: 0;
-  padding-left: 2em;
-  border: none;
-}
-.redactor-editor ol ol li {
-  list-style-type: lower-alpha;
-}
-.redactor-editor ol ol ol li {
-  list-style-type: lower-roman;
-}
-.redactor-editor dl dt {
-  font-weight: bold;
-}
-.redactor-editor dd {
-  margin-left: 1em;
-}
-.redactor-editor table {
-  border-collapse: collapse;
-  font-size: 1em;
-  width: 100%;
-}
-.redactor-editor table td,
-.redactor-editor table th {
-  padding: 5px;
-  border: 1px solid #ddd;
-  vertical-align: top;
-}
-.redactor-editor table thead td,
-.redactor-editor table th {
-  font-weight: bold;
-  border-bottom-color: #888;
-}
-.redactor-editor code {
-  background-color: #d8d7d7;
-}
-.redactor-editor pre {
-  padding: 1em;
-  border: 1px solid #ddd;
-  border-radius: 3px;
-  background: #f8f8f8;
-  font-size: 90%;
-}
-.redactor-editor hr {
-  display: block;
-  height: 1px;
-  border: 0;
-  border-top: 1px solid #ccc;
-}
-.redactor-editor h1,
-.redactor-editor h2,
-.redactor-editor h3,
-.redactor-editor h4,
-.redactor-editor h5,
-.redactor-editor h6 {
-  font-weight: bold;
-  color: #000;
-  padding: 0;
-  background: none;
-  text-rendering: optimizeLegibility;
-  margin: 0 0 .5em 0;
-}
-.redactor-editor h1,
-.redactor-editor h2,
-.redactor-editor h3,
-.redactor-editor h4 {
-  line-height: 1.3;
-}
-.redactor-editor h1 {
-  font-size: 36px;
-}
-.redactor-editor h2 {
-  font-size: 24px;
-  margin-bottom: .7em;
-}
-.redactor-editor h3 {
-  font-size: 21px;
-}
-.redactor-editor h4 {
-  font-size: 18px;
-}
-.redactor-editor h5 {
-  font-size: 16px;
-}
-.redactor-editor h6 {
-  font-size: 12px;
-  text-transform: uppercase;
-}

+ 0 - 1006
priv/css/redactor.less

@@ -1,1006 +0,0 @@
-.clearfix() {
-	&:after {
-		content: "";
-		display: table;
-		clear: both;
-	}
-}
-.transition-redactor(@transition: all linear .2s) {
-	-moz-transition: @transition;
-	transition: @transition;
-}
-
-.opacity-redactor(@opacity: 100) {
-	filter: e(%("alpha(opacity=%d)", @opacity));
-	-moz-opacity: @opacity / 100;
-	opacity: @opacity / 100;
-}
-.box-sizing-redactor(@box-model) {
-	-webkit-box-sizing: @box-model;
-	-moz-box-sizing: @box-model;
-	box-sizing: @box-model;
-}
-.striped-redactor(@color: rgba(255, 255, 255, .2); @angle: 45deg) {
-	background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
-	background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
-	background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
-}
-
-.animation-redactor(@animation) {
-	-webkit-animation: @animation;
-	-o-animation: @animation;
-	animation: @animation;
-}
-
-// VARIABLES
-@redactorFontFamily: Arial, Helvetica, Verdana, Tahoma, sans-serif !important;
-@redactorCodeFontFamily: Menlo, Monaco, monospace, sans-serif !important;
-
-@redactorFontSize: 14px;
-@redactorLineHeight: 1.6em;
-
-/*
-	Icon font
-*/
-@font-face {
-	font-family: 'RedactorFont';
-	src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'),
-		 url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
-	font-weight: normal;
-	font-style: normal;
-}
-
-
-/*
-	Box
-*/
-.redactor-box {
-	position: relative;
-	overflow: visible;
-	margin-bottom: 24px;
-
-	& textarea {
-		display: block;
-		position: relative;
-		margin: 0;
-		padding: 0;
-		width: 100%;
-		overflow: auto;
-		outline: none;
-		border: none;
-		background-color: #111;
-		box-shadow: none;
-		color: #ccc;
-		font-size: 13px;
-		font-family: @redactorCodeFontFamily;
-		resize: none;
-		&:focus {
-			outline: none;
-		}
-	}
-}
-.redactor-editor,
-.redactor-box {
-	background: #fff;
-}
-
-/*
-	Z-index setup
-*/
-.redactor-editor,
-.redactor-box,
-.redactor-box textarea {
-	z-index: auto;
-}
-.redactor-box-fullscreen {
-	z-index: 1051;
-}
-.redactor-toolbar {
-	z-index: 100;
-}
-.redactor-dropdown {
-	z-index: 1052;
-}
-#redactor-modal-overlay,
-#redactor-modal-box,
-#redactor-modal {
-	z-index: 1053;
-}
-
-/*
-	Fullscreen
-*/
-body .redactor-box-fullscreen {
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-}
-
-/*
-	Utils
-*/
-.redactor-scrollbar-measure {
-	position: absolute;
-	top: -9999px;
-	width: 50px;
-	height: 50px;
-	overflow: scroll;
-}
-
-/*
-	Editor
-*/
-.redactor-editor {
-	position: relative;
-	overflow: auto;
-	margin: 0 !important;
-	padding: 20px;
-	min-height: 80px;
-	outline: none;
-	white-space: normal;
-	border: 1px solid #eee;
-	font-family: @redactorFontFamily;
-	font-size: @redactorFontSize;
-	line-height: @redactorLineHeight;
-	&:focus {
-		outline: none;
-	}
-}
-.toolbar-fixed-box + .redactor-editor {
-	padding-top: 32px !important;
-}
-
-/*
-	Placeholder
-*/
-.redactor-placeholder:after {
-	position: absolute;
-	top: 20px;
-	left: 20px;
-	content: attr(placeholder);
-	display: block; /* For Firefox */
-	color: #999 !important;
-	font-weight: normal !important;
-}
-
-
-/*
-	Toolbar
-*/
-.redactor-toolbar {
-	position: relative;
-	top: 0;
-	left: 0;
-	margin: 0 !important;
-	padding: 0 !important;
-	list-style: none !important;
-	font-size: 14px !important;
-	line-height: 1 !important;
-
-	background: #fff;
-	border: none;
-	box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
-
-	.clearfix;
-
-	&.redactor-toolbar-overflow {
-		overflow-y: auto;
-		height: 29px;
-		white-space: nowrap;
-	}
-	&.redactor-toolbar-external {
-		z-index: 999;
-		box-shadow: none;
-		border: 1px solid rgba(0, 0, 0, .1);
-	}
-	& li {
-		vertical-align: top;
-		display: inline-block;
-		margin: 0 !important;
-		padding: 0 !important;
-		outline: none;
-		list-style: none !important;
-		.box-sizing-redactor(content-box);
-	}
-	& li a {
-		display: block;
-		color: #333;
-		text-align: center;
-		padding: 9px 10px;
-		cursor: pointer;
-		outline: none;
-		border: none;
-		text-decoration: none;
-		cursor: pointer;
-		zoom: 1;
-		.box-sizing-redactor(content-box);
-	}
-	& li a {
-		&:hover {
-			outline: none;
-			background-color: rgba(31,120,216,1);
-			color: #fff;
-		}
-		&:hover i:before {
-			color: #fff;
-		}
-		&:active,
-		&.redactor-act {
-			outline: none;
-			background-color: #ccc;
-			color: #444;
-		}
-	}
-	& li a.redactor-btn-image {
-		width: 14px;
-	    height: 14px;
-	    background-position: center center;
-	    background-repeat: no-repeat;
-	}
-	& li a.fa-redactor-btn {
-		display: inline-block;
-		padding: 9px 10px 8px 10px;
-		line-height: 1;
-	}
-	& li a.redactor-button-disabled {
-		.opacity-redactor(30);
-		&:hover {
-			color: #333;
-			outline: none;
-			background-color: transparent !important;
-			cursor: default;
-		}
-	}
-	& li a.redactor-button-focus {
-		color:#fff;
-		background:#000;
-	}
-}
-
-/*
-	CodeMirror
-*/
-.redactor-box .CodeMirror {
-	display: none;
-}
-
-/*
-	Icons
-*/
-.re-icon {
-	font-family: 'RedactorFont';
-	speak: none;
-	font-style: normal;
-	font-weight: normal;
-	font-variant: normal;
-	text-transform: none;
-	line-height: 1;
-	-webkit-font-smoothing: antialiased;
-	-moz-osx-font-smoothing: grayscale;
-}
-.re-icon i:before {
-	position: relative;
-	font-size: 14px;
-}
-
-
-.re-video:before {
-	content: "\e600";
-}
-.re-unorderedlist:before {
-	content: "\e601";
-}
-.re-undo:before {
-	content: "\e602";
-}
-.re-underline:before {
-	content: "\e603";
-}
-.re-textdirection:before {
-	content: "\e604";
-}
-.re-fontcolor:before {
-	content: "\e605";
-}
-.re-table:before {
-	content: "\e606";
-}
-.re-redo:before {
-	content: "\e607";
-}
-.re-quote:before {
-	content: "\e608";
-}
-.re-outdent:before {
-	content: "\e609";
-}
-.re-orderedlist:before {
-	content: "\e60a";
-}
-.re-link:before {
-	content: "\e60b";
-}
-.re-horizontalrule:before {
-	content: "\e60c";
-}
-.re-italic:before {
-	content: "\e60d";
-}
-.re-indent:before {
-	content: "\e60e";
-}
-.re-image:before {
-	content: "\e60f";
-}
-.re-fullscreen:before {
-	content: "\e610";
-}
-.re-normalscreen:before {
-	content: "\e611";
-}
-.re-formatting:before {
-	content: "\e612";
-}
-.re-fontsize:before {
-	content: "\e613";
-}
-.re-fontfamily:before {
-	content: "\e614";
-}
-.re-deleted:before {
-	content: "\e615";
-}
-.re-html:before {
-	content: "\e616";
-}
-.re-clips:before {
-	content: "\e617";
-}
-.re-bold:before {
-	content: "\e618";
-}
-.re-backcolor:before {
-	content: "\e619";
-}
-.re-file:before {
-	content: "\e61a";
-}
-.re-alignright:before {
-	content: "\e61b";
-}
-.re-alignment:before,
-.re-alignleft:before {
-	content: "\e61c";
-}
-.re-alignjustify:before {
-	content: "\e61d";
-}
-.re-aligncenter:before {
-	content: "\e61e";
-}
-.re-gallery:before {
-	content: "\e61f";
-}
-
-
-/*
-	Toolbar tooltip
-*/
-.redactor-toolbar-tooltip {
-	position: absolute;
-	z-index: 1054;
-	text-align: center;
-	top: 0;
-	left: 0;
-	background: #000;
-	color: #fff;
-	padding: 5px 8px;
-	line-height: 1;
-	font-family: @redactorFontFamily;
-	font-size: 12px;
-	border-radius: 2px;
-}
-
-
-/*
-	Dropdown
-*/
-.redactor-dropdown {
-	position: absolute;
-	top: 28px;
-	left: 0;
-	padding: 0;
-	min-width: 220px;
-	max-height: 254px;
-	overflow: auto;
-	background-color: #fff;
-	box-shadow: 0 1px 7px rgba(0, 0, 0, .25);
-	font-size: @redactorFontSize;
-	font-family: @redactorFontFamily;
-	line-height: @redactorLineHeight;
-	& a {
-		display: block;
-		padding: 10px 15px;
-		color: #000;
-		text-decoration: none;
-		border-bottom: 1px solid rgba(0, 0, 0, .07);
-		&:last-child {
-			border-bottom: none;
-		}
-		&:hover {
-			background-color: rgba(31, 120, 216, 1);
-			color: #fff !important;
-			text-decoration: none;
-		}
-		&.selected {
-			background-color: #000;
-			color: #fff;
-		}
-        &.redactor-dropdown-link-inactive,
-        &.redactor-dropdown-link-inactive:hover {
-            background: none;
-            cursor: default;
-            color: #000 !important;
-            .opacity-redactor(40);
-        }
-		&.redactor-dropdown-link-selected {
-			color:#fff;
-			background:#000;
-		}
-	}
-}
-
-
-/*
-	IMAGE BOX
-*/
-#redactor-image-box {
-	position: relative;
-	max-width: 100%;
-	display: inline-block;
-	line-height: 0;
-	outline: 1px dashed rgba(0, 0, 0, .6),
-}
-#redactor-image-editter {
-	position: absolute;
-	z-index: 5;
-	top: 50%;
-	left: 50%;
-	margin-top: -11px;
-	margin-left: -18px;
-	line-height: 1;
-	background-color: #000;
-	color: #fff;
-	font-size: 11px;
-	padding: 7px 10px;
-	cursor: pointer;
-}
-#redactor-image-resizer {
-	position: absolute;
-	z-index: 2;
-	line-height: 1;
-	cursor: nw-resize;
-	bottom: -4px;
-	right: -5px;
-	border: 1px solid #fff;
-	background-color: #000;
-	width: 8px;
-	height: 8px;
-}
-
-
-
-/*
-	LINK TOOLTIP
-*/
-.redactor-link-tooltip {
-	position: absolute;
-	z-index: 99;
-	padding: 10px;
-	line-height: 1;
-	display: inline-block;
-	background-color: #000;
-	color: #555 !important;
-}
-.redactor-link-tooltip,
-.redactor-link-tooltip a {
-	font-size: 12px;
-	font-family: @redactorFontFamily;
-}
-.redactor-link-tooltip a {
-	color: #ccc;
-	margin: 0 5px;
-	text-decoration: none;
-	&:hover {
-		color: #fff;
-	}
-}
-
-/*
-	DROPAREA
-*/
-#redactor-droparea {
-	position: relative;
-	overflow: hidden;
-	padding: 140px 20px;
-	border: 3px dashed rgba(0, 0, 0, .1);
-	&.drag-hover {
-		background: rgba(200, 222, 250, 0.75);
-	}
-	&.drag-drop {
-		background: rgba(250, 248, 200, 0.5);
-	}
-}
-#redactor-droparea-placeholder {
-	text-align: center;
-	font-size: 12px;
-	color: rgba(0, 0, 0, .7);
-}
-
-
-/*
-	PROGRESS
-*/
-#redactor-progress {
-	position: fixed;
-	top: 0;
-	left: 0;
-	width: 100%;
-	z-index: 1000000;
-	height: 10px;
-}
-#redactor-progress span {
-	display: block;
-	width: 100%;
-	height: 100%;
-	background-color: #3d58a8;
-	.striped-redactor();
-	.animation-redactor(progress-bar-stripes 2s linear infinite);
-	background-size: 40px 40px;
-}
-
-@-webkit-keyframes progress-bar-stripes {
-	from { background-position: 40px 0; }
-	to { background-position: 0 0; }
-}
-@-o-keyframes progress-bar-stripes {
-	from { background-position: 40px 0; }
-	to { background-position: 0 0; }
-}
-@keyframes progress-bar-stripes {
-	from { background-position: 40px 0; }
-	to { background-position: 0 0; }
-}
-
-
-/*
-	MODAL
-*/
-#redactor-modal-overlay {
-	position: fixed;
-	top: 0;
-	left: 0;
-	margin: auto;
-	overflow: auto;
-	width: 100%;
-	height: 100%;
-	background-color: #000 !important;
-	.opacity-redactor(30);
-}
-#redactor-modal-box {
-	position: fixed;
-	top: 0;
-	left: 0;
-	bottom: 0;
-	right: 0;
-	overflow-x: hidden;
-	overflow-y: auto;
-}
-#redactor-modal {
-	outline:0;
-	position: relative;
-	margin: auto;
-	margin-bottom: 20px;
-	padding: 0;
-	background: #fff;
-	color: #000;
-	font-size: 14px !important;
-	font-family: @redactorFontFamily;
-	box-shadow: 0 1px 70px rgba(0, 0, 0, .5);
-
-	& header {
-		padding: 30px 40px 5px 40px;
-		font-size: 18px;
-		font-weight: bold;
-	}
-	& section {
-		padding: 30px 40px 50px 40px;
-	}
-	& label {
-		display: block;
-		float: none !important;
-		margin: 15px 0 3px 0 !important;
-		padding: 0;
-	}
-	& input[type="radio"],
-	& input[type="checkbox"] {
-		position: relative;
-		top: -1px;
-	}
-	& select {
-		width: 100%;
-	}
-	& input[type="text"],
-	& input[type="password"],
-	& input[type="email"],
-	& input[type="url"],
-	& textarea {
-		position: relative;
-		z-index: 2;
-		margin: 0;
-		padding: 5px 4px;
-		height: 28px;
-		border: 1px solid #ccc;
-		border-radius: 1px;
-		background-color: white;
-		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset;
-		color: #333;
-		width: 100%;
-		font-size: 14px;
-		font-family: @redactorFontFamily;
-		.transition-redactor(border 0.3s ease-in);
-		&:focus {
-			outline: none;
-			border-color: #5ca9e4;
-			box-shadow: 0 0 0 2px rgba(70, 161, 231, .3), 0 1px 2px rgba(0, 0, 0, .2) inset;
-		}
-		&.redactor-input-error {
-			border-color: #e82f2f;
-			box-shadow: 0 0 0 2px rgba(232, 47, 47, .3), 0 1px 2px rgba(0, 0, 0, .2) inset;
-		}
-	}
-	& textarea {
-		display: block;
-		margin-top: 4px;
-		line-height: 1.4em;
-	}
-
-}
-
-/*
-	Tabs in Modal
-*/
-#redactor-modal-tabber {
-	margin-bottom: 15px;
-	font-size: 12px;
-	& a {
-		border: 1px solid #ddd;
-		line-height: 1;
-		padding: 8px 15px;
-		margin-right: -1px;
-		text-decoration: none;
-		color: #000;
-		&:hover {
-			background-color: rgba(31,120,216,1);
-			border-color: rgba(31,120,216,1);
-			color: #fff;
-		}
-		&.active {
-			cursor: default;
-			background-color: #ddd;
-			border-color: #ddd;
-			color: rgba(0, 0, 0, .6);
-		}
-	}
-}
-
-
-/*
-	List in Modal
-*/
-#redactor-modal {
-	#redactor-modal-list {
-		margin-left: 0;
-		padding-left: 0;
-		list-style: none;
-		max-height: 250px;
-		overflow-x: auto;
-		& li {
-			border-bottom: 1px solid #ddd;
-			&:last-child {
-				border-bottom: none;
-			}
-		}
-
-		& a {
-			padding: 10px 5px;
-			color: #000;
-			text-decoration: none;
-			font-size: 13px;
-			display: block;
-			position: relative;
-			&:hover {
-				background-color: #eee;
-			}
-		}
-	}
-}
-
-#redactor-modal-close {
-	position: absolute;
-	top: 10px;
-	right: 10px;
-	width: 30px;
-	height: 30px;
-	text-align: right;
-	color: #bbb;
-	font-size: 30px;
-	font-weight: 300;
-	cursor: pointer;
-	-webkit-appearance: none;
-	padding:0;
-	border:0;
-	background:0;
-	outline:none;
-	&:hover {
-		color: #000;
-	}
-
-}
-#redactor-modal footer button {
-	position: relative;
-	width: 100%;
-	padding: 14px 16px;
-	margin: 0;
-	outline: none;
-	border: none;
-	background-color: #ddd;
-	color: #000;
-	text-align: center;
-	text-decoration: none;
-	font-weight: normal;
-	font-size: 12px;
-	font-family: @redactorFontFamily;
-	line-height: 1;
-	cursor: pointer;
-
-	&:hover {
-		color: #777;
-		background: none;
-		background: #bbb;
-		text-decoration: none;
-	}
-
-	&.redactor-modal-delete-btn {
-		background: none;
-		color: #fff;
-		background-color: #b52525;
-		&:hover {
-			color: rgba(255, 255, 255, .6);
-			background-color: #881b1b;
-		}
-	}
-	&.redactor-modal-action-btn {
-		background: none;
-		color: #fff;
-		background-color: #2461b5;
-		&:hover {
-			color: rgba(255, 255, 255, .6);
-			background-color: #1a4580;
-		}
-	}
-}
-
-
-/*
-	##############################################
-
-	DROPDOWN FORMATTING
-
-	##############################################
-*/
-.redactor-dropdown {
-	& .redactor-formatting-blockquote {
-		color: rgba(0, 0, 0, .4);
-		font-style: italic;
-	}
-	& .redactor-formatting-pre {
-		font-family: monospace, sans-serif;
-	}
-	& .redactor-formatting-h1 {
-		font-size: 36px;
-		line-height: 36px;
-		font-weight: bold;
-	}
-	& .redactor-formatting-h2 {
-		font-size: 24px;
-		line-height: 36px;
-		font-weight: bold;
-	}
-	& .redactor-formatting-h3 {
-		font-size: 21px;
-		line-height: 30px;
-		font-weight: bold;
-	}
-	& .redactor-formatting-h4 {
-		font-size: 18px;
-		line-height: 26px;
-		font-weight: bold;
-	}
-	& .redactor-formatting-h5 {
-		font-size: 16px;
-		line-height: 23px;
-		font-weight: bold;
-	}
-}
-
-
-/*
-	##############################################
-
-	 CONTENT STYLES
-
-	##############################################
-*/
-.redactor-editor {
-	code,
-	pre {
-		font-family: @redactorCodeFontFamily;
-		cursor: text;
-	}
-	div,
-	p,
-	ul,
-	ol,
-	table,
-	dl,
-	blockquote,
-	pre {
-		font-size: @redactorFontSize;
-		line-height: @redactorLineHeight;
-	}
-	a {
-		color: #15c;
-		text-decoration: underline;
-	}
-
-	object,
-	embed,
-	video,
-	img {
-		max-width: 100%;
-		width: auto;
-	}
-	video,
-	img {
-		height: auto;
-	}
-	div,
-	p,
-	ul,
-	ol,
-	table,
-	dl,
-	figure,
-	blockquote,
-	pre {
-	  margin: 0;
-	  margin-bottom: 15px;
-	  border: none;
-	  background: none;
-	  box-shadow: none;
-	}
-	iframe,
-	object,
-	hr {
-	  margin-bottom: 15px;
-	}
-	blockquote {
-	  margin-left: 1.6em !important;
-	  padding: 0;
-	  text-align: left;
-	  color: #777;
-	  font-style: italic;
-	  &:before,
-	  &:after {
-		  content: '';
-	  }
-	}
-	ul,
-	ol {
-	  padding-left: 2em;
-	}
-	ul ul,
-	ol ol,
-	ul ol,
-	ol ul {
-	  margin: 2px;
-	  padding: 0;
-	  padding-left: 2em;
-	  border: none;
-	}
-	ol ol li {
-		list-style-type: lower-alpha;
-	}
-	ol ol ol li {
-		list-style-type: lower-roman;
-	}
-	dl dt {
-	  font-weight: bold;
-	}
-	dd {
-	  margin-left: 1em;
-	}
-	table {
-	  border-collapse: collapse;
-	  font-size: 1em;
-	  width: 100%;
-	  & td,
-	  & th {
-		  padding: 5px;
-		  border: 1px solid #ddd;
-		  vertical-align: top;
-	  }
-	}
-	table thead td,
-	table th {
-	  font-weight: bold;
-	  border-bottom-color: #888;
-	}
-	code {
-	  background-color: #d8d7d7;
-	}
-	pre {
-	  padding: 1em;
-	  border: 1px solid #ddd;
-	  border-radius: 3px;
-	  background: #f8f8f8;
-	  font-size: 90%;
-	}
-	hr {
-	  display: block;
-	  height: 1px;
-	  border: 0;
-	  border-top: 1px solid #ccc;
-	}
-	h1,
-	h2,
-	h3,
-	h4,
-	h5,
-	h6 {
-		font-weight: bold;
-		color: #000;
-		padding: 0;
-		background: none;
-		text-rendering: optimizeLegibility;
-		margin: 0 0 .5em 0;
-	}
-	h1,
-	h2,
-	h3,
-	h4 {
-	  line-height: 1.3;
-	}
-	h1 {
-	  font-size: 36px;
-	}
-	h2 {
-	  font-size: 24px;
-	  margin-bottom: .7em;
-	}
-	h3 {
-	  font-size: 21px;
-	}
-	h4 {
-	  font-size: 18px;
-	}
-	h5 {
-	  font-size: 16px;
-	}
-	h6 {
-	  font-size: 12px;
-	  text-transform: uppercase;
-	}
-}

+ 106 - 0
priv/css/sortable.css

@@ -0,0 +1,106 @@
+
+body {
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-tap-highlight-color: transparent;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+.title {
+  font-weight: 500;
+  text-align: center;
+  margin-bottom: 20px;
+  font-size: 20px;
+  color: #0E254E;
+}
+
+.list {
+  margin: 0 auto;
+  width: 100%;
+  max-width: 380px;
+  user-select: none;
+}
+.list__item {
+  transition: box-shadow 200ms ease-out, opacity 200ms ease-out;
+  border-radius: 6px;
+  background: #fff;
+  box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
+  display: flex;
+}
+.list__item:not(:last-child) {
+  margin-bottom: 7px;
+}
+.list__item.is-dragging {
+  box-shadow: 0 0 24px rgba(0, 0, 0, 0.1);
+  opacity: 0.8;
+}
+.list__item-content {
+  width: calc(100% - 40px - 40px);
+  padding: 10px 15px;
+}
+.list__item-title, .list__item-description {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
+}
+.list__item-title {
+  font-size: 15px;
+  color: #0E254E;
+}
+.list__item-description {
+  font-size: 13px;
+  color: #66748E;
+}
+.list__item-handle {
+  position: relative;
+  width: 40px;
+  cursor: pointer;
+}
+.list__item-handle:before, .list__item-handle:after {
+  content: "";
+  position: absolute;
+  left: 15px;
+  right: 15px;
+  top: 50%;
+  height: 1px;
+  background: #c2cada;
+}
+.list__item-handle:before {
+  transform: translateY(-4px);
+}
+.list__item-handle:after {
+  transform: translateY(4px);
+}
+
+.list__item-close {
+  position: relative;
+  width: 40px;
+  cursor: pointer;
+}
+.list__item-close:after {
+  content: "X";
+  position: absolute;
+  left: 15px;
+  right: 15px;
+  top: 30%;
+  height: 0px;
+  background: #c2cada;
+}
+.list__item-close:after {
+  transform: translateY(0px);
+}
+
+.list__item-close:hover {
+  background-color: #f44336;
+  color: white;
+}
+
+.triangle{
+  position: absolute;
+  top: 1px;
+  right: 5px;
+  cursor: default;
+}

BIN
priv/fonts/redactor-font.eot


+ 36 - 9
priv/js/calendar.js

@@ -52,7 +52,7 @@ function getDateParamBySign(date,sign) {
 
 function formatter(date, format) {
     date = date || new Date();
-    format = format || "YYYY-MM-DD";
+    format = format || "DD.MM.YYYY";
     var signs = format.match(/(Y{2,4})|(M{2})|(D{2})/g);
     var params = [];
     var reStr = '';
@@ -70,14 +70,39 @@ function formatter(date, format) {
     return value;
 }
 
-function parseDateFromInput(value) {
-    if(isNaN(Date.parse(value))) {
-        var res = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/.exec(value);
-        if(res.length == 4) { return new Date(res[3],(res[2]-1),res[1]); }
-        else { return null; }
-    }else{ return new Date(Date.parse(value)); }
+function parser(str, format) {
+    format = format || "DD.MM.YYYY";
+    var signs = format.match(/(Y{2,4})|(M{2})|(D{2})/g);
+    var reStr = "(";
+    for(var i=0; i<signs.length; ++i) {
+        reStr += ".".repeat(signs[i].length) + (((i+1) != signs.length) ? ").(" : ")");
+    }
+    var re = new RegExp(reStr,'g');
+    var values = re.exec(str);
+    var year, month, day;
+    if (values && signs.length+1 == values.length) {
+        values = values.slice(1);
+        for(var i=0; i<signs.length; ++i) {
+            switch(signs[i].slice(0,1)){
+                case "Y": year = values[i]; break;
+                case "M": month = values[i]; break;
+                case "D": day = values[i]; break;
+            }
+        }
+        const res = new Date(year, month-1, day);
+        return res;
+    }
+    return null;
 }
 
+// function parseDateFromInput(value) {
+//     if(isNaN(Date.parse(value))) {
+//         var res = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/.exec(value);
+//         if(res && res.length == 4) { return new Date(res[3],(res[2]-1),res[1]); }
+//         else { return null; }
+//     }else{ return new Date(Date.parse(value)); }
+// }
+
 
 (function (root, factory)
 {
@@ -266,7 +291,7 @@ function parseDateFromInput(value) {
         reposition: true,
 
         // the default output format for `.toString()` and `field` value
-        format: 'YYYY-MM-DD',
+        format: 'DD.MM.YYYY',
 
         // the initial date to view when first opened
         defaultDate: null,
@@ -482,6 +507,7 @@ function parseDateFromInput(value) {
             }
             e = e || window.event;
             var target = e.target || e.srcElement;
+            console.log(target);
             if (!target) {
                 return;
             }
@@ -545,7 +571,8 @@ function parseDateFromInput(value) {
                 date = (date && date.isValid()) ? date.toDate() : null;
             }
             else {
-                date = parseDateFromInput(opts.field.value);
+                // date = parseDateFromInput(opts.field.value);
+                date = parser(opts.field.value, opts.format);
             }
             if (isDate(date)) {
                 self.setDate(date);

+ 110 - 0
priv/js/comboLookup.js

@@ -0,0 +1,110 @@
+function comboClear(dom) {
+    let elem = qi('comboContainer_' + dom)
+    if (elem) { elem.style.display = 'none' }
+    activeCombo = undefined; currentItem = undefined;
+}
+
+function comboSelect(dom, row, feed, mod, id) {
+    let elem = qi(dom); comboClear(dom);
+    if (qi(id)) elem.setAttribute("data-bind", qi(id).getAttribute('data-bind'));
+    elem.value = row;
+    elem.style.backgroundColor = 'white';
+    direct(tuple(atom('comboSelect'),
+                 string(dom),
+                 string(row),
+                 string(feed),
+                 atom(mod)));
+}
+
+function comboLookupChange(dom) {
+  let elem = qi(dom);
+  if (elem && elem.value == "" && elem.getAttribute("data-bind")) {
+    elem.removeAttribute("data-bind");
+  }
+}
+
+function comboLookupClick(dom, feed, mod) {
+  var char = event.which || event.keyCode;
+  if (char ==  1 && !activeCombo && qi(dom).value == '') {
+    activeCombo = dom;
+    currentItem = undefined;
+    direct(tuple(atom('comboKey'),
+                atom('all'),
+                string(dom),
+                string(feed),
+                atom(mod)));
+    return
+  } else if(char ==  1 && activeCombo == dom){comboClear(dom);}
+}
+
+function comboLookupKeydown(dom, feed, mod) {
+    var char = event.which || event.keyCode;
+    if (char == 40 && !activeCombo && qi(dom).value == '') {
+        activeCombo = dom;
+        currentItem = undefined;
+        direct(tuple(atom('comboKey'),
+                     atom('all'),
+                     string(dom),
+                     string(feed),
+                     atom(mod)));
+        return
+    }
+    if (activeCombo && [38, 40].includes(char)) {
+        event.preventDefault();
+        console.log('Keycode: ' + char + ", DOM: " + dom);
+        if (char == 40) { set_focus( currentItem && ( !cycleEnabled || currentItem.nextSibling)
+                        ? currentItem.nextSibling : qi('comboContainer_' + dom).firstChild, true) }
+        if (char == 38) { set_focus( currentItem && ( !cycleEnabled || currentItem.previousSibling)
+                        ? currentItem.previousSibling : qi('comboContainer_' + dom).lastChild, true) }
+    }
+}
+
+function comboLookupKeyup(dom, feed, mod) {
+    var char = event.which || event.keyCode;
+    if (char == 27 || (char == 8 || char == 46) && qi(dom).value == '') {
+        qi(dom).value = '';
+        comboClear(dom);
+        return
+    }
+    if (char == 13 && currentItem) { currentItem.click(); return }
+    if ([33, 34, 35, 36, 37, 39].includes(char)) { return }
+    if (activeCombo && [38, 40].includes(char)) { return }
+    else {
+        activeCombo = dom;
+        currentItem = undefined;
+        direct(tuple(atom('comboKey'),
+                     bin(qi(dom).value),
+                     string(dom),
+                     string(feed),
+                     atom(mod)));
+    }
+}
+
+function comboLookupMouseMove(dom) {
+  set_focus(event.target.closest('.dropdown-item'), false)
+}
+
+function set_focus(elem, scroll) {
+  if (elem) {
+    if(currentItem) {currentItem.className = "dropdown-item"}
+    elem.className = "dropdown-item focus"
+    if (scroll==true) {elem.scrollIntoView({block: "nearest", inline: "nearest"})}
+    currentItem = elem
+  }
+}
+
+document.addEventListener("click", () => {
+  if (activeCombo && event.target.className != 'triangle' &&
+    !event.target.closest('#comboContainer_' + activeCombo)) {
+    qi(activeCombo).value = '';
+    comboClear(activeCombo);
+  }
+})
+
+function comboLostFocus(e) { }
+function comboOnFocus(e) { }
+function comboLookupMouseOut(dom) { }
+
+var activeCombo = undefined
+var currentItem = undefined
+var cycleEnabled = false

+ 0 - 75
priv/js/lang/ru.js

@@ -1,75 +0,0 @@
-(function ($) {
-$.Redactor.opts.langs['ru'] = {
-	html: 'Код',
-	video: 'Видео',
-	image: 'Изображение',
-	table: 'Таблица',
-	link: 'Ссылка',
-	link_insert: 'Вставить ссылку ...',
-	link_edit: 'Изменить ссылку',
-	unlink: 'Удалить ссылку',
-	formatting: 'Форматирование',
-	paragraph: 'Обычный текст',
-	quote: 'Цитата',
-	code: 'Код',
-	header1: 'Заголовок 1',
-	header2: 'Заголовок 2',
-	header3: 'Заголовок 3',
-	header4: 'Заголовок 4',
-	header5: 'Заголовок 5',
-	bold:  'Полужирный',
-	italic: 'Наклонный',
-	fontcolor: 'Цвет текста',
-	backcolor: 'Заливка текста',
-	unorderedlist: 'Обычный список',
-	orderedlist: 'Нумерованный список',
-	outdent: 'Уменьшить отступ',
-	indent: 'Увеличить отступ',
-	cancel: 'Отменить',
-	insert: 'Вставить',
-	save: 'Сохранить',
-	_delete: 'Удалить',
-	insert_table: 'Вставить таблицу',
-	insert_row_above: 'Добавить строку сверху',
-	insert_row_below: 'Добавить строку снизу',
-	insert_column_left: 'Добавить столбец слева',
-	insert_column_right: 'Добавить столбец справа',
-	delete_column: 'Удалить столбец',
-	delete_row: 'Удалить строку',
-	delete_table: 'Удалить таблицу',
-	rows: 'Строки',
-	columns: 'Столбцы',
-	add_head: 'Добавить заголовок',
-	delete_head: 'Удалить заголовок',
-	title: 'Подсказка',
-	image_position: 'Обтекание текстом',
-	none: 'Нет',
-	left: 'Cлева',
-	right: 'Cправа',
-	image_web_link: 'Cсылка на изображение',
-	text: 'Текст',
-	mailto: 'Эл. почта',
-	web: 'URL',
-	video_html_code: 'Код видео ролика',
-	file: 'Файл',
-	upload: 'Загрузить',
-	download: 'Скачать',
-	choose: 'Выбрать',
-	or_choose: 'Или выберите',
-	drop_file_here: 'Перетащите файл сюда',
-	align_left:	'По левому краю',
-	align_center: 'По центру',
-	align_right: 'По правому краю',
-	align_justify: 'Выровнять текст по ширине',
-	horizontalrule: 'Горизонтальная линейка',
-	fullscreen: 'Во весь экран',
-	deleted: 'Зачеркнутый',
-	anchor: 'Якорь',
-	link_new_tab: 'Открывать в новой вкладке',
-	underline: 'Подчеркнутый',
-	alignment: 'Выравнивание',
-	filename: 'Название (необязательно)',
-	edit: 'Ред.',
-	center: 'По центру'
-};
-})( jQuery );

+ 0 - 76
priv/js/lang/ua.js

@@ -1,76 +0,0 @@
-(function ($) {
-$.Redactor.opts.langs['ua'] = {
-	html: 'Код',
-	video: 'Відео',
-	image: 'Зображення',
-	table: 'Таблиця',
-	link: 'Посилання',
-	link_insert: 'Вставити посилання ...',
-	link_edit: 'Edit link',
-	unlink: 'Видалити посилання',
-	formatting: 'Стилі',
-	paragraph: 'Звичайний текст',
-	quote: 'Цитата',
-	code: 'Код',
-	header1: 'Заголовок 1',
-	header2: 'Заголовок 2',
-	header3: 'Заголовок 3',
-	header4: 'Заголовок 4',
-	header5: 'Заголовок 5',
-	bold:  'Жирний',
-	italic: 'Похилий',
-	fontcolor: 'Колір тексту',
-	backcolor: 'Заливка тексту',
-	unorderedlist: 'Звичайний список',
-	orderedlist: 'Нумерований список',
-	outdent: 'Зменшити відступ',
-	indent: 'Збільшити відступ',
-	cancel: 'Скасувати',
-	insert: 'Вставити',
-	save: 'Зберегти',
-	_delete: 'Видалити',
-	insert_table: 'Вставити таблицю',
-	insert_row_above: 'Додати рядок зверху',
-	insert_row_below: 'Додати рядок знизу',
-	insert_column_left: 'Додати стовпець ліворуч',
-	insert_column_right: 'Додати стовпець праворуч',
-	delete_column: 'Видалити стовпець',
-	delete_row: 'Видалити рядок',
-	delete_table: 'Видалити таблицю',
-	rows: 'Рядки',
-	columns: 'Стовпці',
-	add_head: 'Додати заголовок',
-	delete_head: 'Видалити заголовок',
-	title: 'Підказка',
-	image_view: 'Завантажити зображення',
-	image_position: 'Обтікання текстом',
-	none: 'ні',
-	left: 'ліворуч',
-	right: 'праворуч',
-	image_web_link: 'Посилання на зображення',
-	text: 'Текст',
-	mailto: 'Ел. пошта',
-	web: 'URL',
-	video_html_code: 'Код відео ролика',
-	file: 'Файл',
-	upload: 'Завантажити',
-	download: 'Завантажити',
-	choose: 'Вибрати',
-	or_choose: 'Або виберіть',
-	drop_file_here: 'Перетягніть файл сюди',
-	align_left:	'По лівому краю',
-	align_center: 'По центру',
-	align_right: 'По правому краю',
-	align_justify: 'Вирівняти текст по ширині',
-	horizontalrule: 'Горизонтальная лінійка',
-	fullscreen: 'На весь екран',
-	deleted: 'Закреслений',
-    anchor: 'Anchor',
-	link_new_tab: 'Open link in new tab',
-	underline: 'Underline',
-	alignment: 'Alignment',
-	filename: 'Name (optional)',
-	edit: 'Edit',
-	center: 'Center'
-};
-})( jQuery );

+ 80 - 0
priv/js/nitro.js

@@ -0,0 +1,80 @@
+// Nitrogen Compatibility Layer
+
+function unbase64(base64) {
+    var binary_string = window.atob(base64);
+    var len = binary_string.length;
+    var bytes = new Uint8Array(len);
+    for (var i = 0; i < len; i++) bytes[i] = binary_string.charCodeAt(i);
+    return bytes.buffer;
+}
+
+// Nitrogen Compatibility Layer
+
+function direct(term) { ws.send(enc(tuple(atom('direct'),term))); }
+function validateSources() { return true; }
+function querySourceRaw(Id) {
+    var val, el = document.getElementById(Id);
+    if (!el) {
+       val = qs('input[name='+Id+']:checked'); val = val ? val.value : "";
+    } else switch (el.tagName) {
+        case 'FIELDSET':
+            val = qs('[id="'+Id+'"]:checked'); val = val ? val.value : ""; break;
+        case 'INPUT':
+            switch (el.getAttribute("type")) {
+                case 'radio': val = qs('input[name='+Id+']:checked'); val = val ? val.value : ""; break;
+                case 'checkbox': val = qs('input[id='+Id+']:checked'); val = val ? val.value : ""; break;
+                case 'date': val = Date.parse(el.value);  val = val && new Date(val) || ""; break;
+                case 'calendar': val = pickers[el.id]._d || ""; break;
+                case 'comboLookup': case 'hidden':
+                    if (el.hasAttribute('data-bind')) {
+                        val = { 'text' : el.value, 'bind' : el.getAttribute('data-bind')};
+                    } else {
+                        val = el.value;
+                    }
+                    break;
+                default: var edit = el.contentEditable;
+                    if (edit && edit === 'true') val = el.innerHTML;
+                    else val = el.value;
+            }
+            break;
+        default:
+            if(el.getAttribute('data-text-input')) {
+              val = querySourceRaw(el.children[1].children[0].id);
+            }
+            else if (el.getAttribute('data-vector-input')) {
+                val = querySourceRaw(el.children[1].id);
+            } else if (el.getAttribute('data-edit-input')) {
+                val = querySourceRaw(el.children[0].children[0].children[0].id);
+            } else if (el.getAttribute('data-sortable-list')) {
+                val = getSortableValues('#' + el.id);
+            } else if (el.contentEditable === 'true') {
+                val = el.innerHTML;
+            } else {
+                val = el.value;
+                switch (val) {
+                    case "true": val = new Boolean(true); break;
+                    case "false": val = new Boolean(false); break;
+                }
+            }
+    }
+    return val;
+}
+
+function querySourceConvert(qs) {
+    if (qs && qs.hasOwnProperty('text') && qs.hasOwnProperty('bind')) {
+        return dec(unbase64(qs.bind)); }
+    else if (qs instanceof Date) {
+        return tuple(number(qs.getFullYear()),
+                     number(qs.getMonth() + 1),
+                     number(qs.getDate())); }
+    else if (qs instanceof Boolean) {
+        return atom(qs.valueOf()); }
+    else if (qs instanceof Array) {
+        return list.apply(null, qs.map(querySourceConvert)); }
+    else { return bin(qs); }
+}
+
+function querySource(Id) {
+    var qs = querySourceRaw(Id);
+    return querySourceConvert(qs);
+}

+ 0 - 6
priv/js/plugins/clips/clips.css

@@ -1,6 +0,0 @@
-.label-red {
-	color: #fff;
-	background: #c92020;
-	padding: 0 7px;
-	border-radius: 4px;
-}

+ 0 - 62
priv/js/plugins/clips/clips.js

@@ -1,62 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.clips = function()
-	{
-		return {
-			init: function()
-			{
-				var items = [
-					['Lorem ipsum...', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'],
-					['Red label', '<span class="label-red">Label</span>']
-				];
-
-				this.clips.template = $('<ul id="redactor-modal-list">');
-
-				for (var i = 0; i < items.length; i++)
-				{
-					var li = $('<li>');
-					var a = $('<a href="#" class="redactor-clip-link">').text(items[i][0]);
-					var div = $('<div class="redactor-clip">').hide().html(items[i][1]);
-
-					li.append(a);
-					li.append(div);
-					this.clips.template.append(li);
-				}
-
-				this.modal.addTemplate('clips', '<section>' + this.utils.getOuterHtml(this.clips.template) + '</section>');
-
-				var button = this.button.add('clips', 'Clips');
-				this.button.addCallback(button, this.clips.show);
-
-			},
-			show: function()
-			{
-				this.modal.load('clips', 'Insert Clips', 400);
-
-				this.modal.createCancelButton();
-
-				$('#redactor-modal-list').find('.redactor-clip-link').each($.proxy(this.clips.load, this));
-
-				this.selection.save();
-				this.modal.show();
-			},
-			load: function(i,s)
-			{
-				$(s).on('click', $.proxy(function(e)
-				{
-					e.preventDefault();
-					this.clips.insert($(s).next().html());
-
-				}, this));
-			},
-			insert: function(html)
-			{
-				this.selection.restore();
-				this.insert.html(html);
-				this.modal.close();
-				this.observe.load();
-			}
-		};
-	};
-})(jQuery);
-

+ 0 - 42
priv/js/plugins/counter/counter.js

@@ -1,42 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.counter = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.counterCallback) return;
-
-				this.$editor.on('keyup.redactor-limiter', $.proxy(function(e)
-				{
-					var words = 0, characters = 0, spaces = 0;
-
-					var html = this.code.get();
-
-					var text = html.replace(/<\/(.*?)>/gi, ' ');
-					text = text.replace(/<(.*?)>/gi, '');
-					text = text.replace(/\t/gi, '');
-					text = text.replace(/\n/gi, ' ');
-					text = text.replace(/\r/gi, ' ');
-					text = $.trim(text);
-
-					if (text !== '')
-					{
-						var arrWords = text.split(/\s+/);
-						var arrSpaces = text.match(/\s/g);
-
-						if (arrWords) words = arrWords.length;
-						if (arrSpaces) spaces = arrSpaces.length;
-
-						characters = text.length;
-
-					}
-
-					this.core.setCallback('counter', { words: words, characters: characters, spaces: spaces });
-
-
-				}, this));
-			}
-		};
-	};
-})(jQuery);

+ 0 - 51
priv/js/plugins/definedlinks/definedlinks.js

@@ -1,51 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.definedlinks = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.definedLinks) return;
-
-				this.modal.addCallback('link', $.proxy(this.definedlinks.load, this));
-
-			},
-			load: function()
-			{
-				var $select = $('<select id="redactor-defined-links" />');
-				$('#redactor-modal-link-insert').prepend($select);
-
-				this.definedlinks.storage = {};
-
-				$.getJSON(this.opts.definedLinks, $.proxy(function(data)
-				{
-					$.each(data, $.proxy(function(key, val)
-					{
-						this.definedlinks.storage[key] = val;
-						$select.append($('<option>').val(key).html(val.name));
-
-					}, this));
-
-					$select.on('change', $.proxy(this.definedlinks.select, this));
-
-				}, this));
-
-			},
-			select: function(e)
-			{
-				var key = $(e.target).val();
-				var name = '', url = '';
-				if (key !== 0)
-				{
-					name = this.definedlinks.storage[key].name;
-					url = this.definedlinks.storage[key].url;
-				}
-
-				$('#redactor-link-url').val(url);
-
-				var $el = $('#redactor-link-url-text');
-				if ($el.val() === '') $el.val(name);
-			}
-		};
-	};
-})(jQuery);

+ 0 - 62
priv/js/plugins/filemanager/filemanager.js

@@ -1,62 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.filemanager = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.fileManagerJson) return;
-
-				this.modal.addCallback('file', this.filemanager.load);
-			},
-			load: function()
-			{
-				var $modal = this.modal.getModal();
-
-				this.modal.createTabber($modal);
-				this.modal.addTab(1, 'Upload', 'active');
-				this.modal.addTab(2, 'Choose');
-
-				$('#redactor-modal-file-upload-box').addClass('redactor-tab redactor-tab1');
-
-				var $box = $('<div id="redactor-file-manager-box" style="overflow: auto; height: 300px;" class="redactor-tab redactor-tab2">').hide();
-				$modal.append($box);
-
-
-				$.ajax({
-				  dataType: "json",
-				  cache: false,
-				  url: this.opts.fileManagerJson,
-				  success: $.proxy(function(data)
-					{
-						var ul = $('<ul id="redactor-modal-list">');
-						$.each(data, $.proxy(function(key, val)
-						{
-							var a = $('<a href="#" title="' + val.title + '" rel="' + val.link + '" class="redactor-file-manager-link">' + val.title + ' <span style="font-size: 11px; color: #888;">' + val.name + '</span> <span style="position: absolute; right: 10px; font-size: 11px; color: #888;">(' + val.size + ')</span></a>');
-							var li = $('<li />');
-
-							a.on('click', $.proxy(this.filemanager.insert, this));
-
-							li.append(a);
-							ul.append(li);
-
-						}, this));
-
-						$('#redactor-file-manager-box').append(ul);
-
-
-					}, this)
-				});
-
-			},
-			insert: function(e)
-			{
-				e.preventDefault();
-
-				var $target = $(e.target).closest('.redactor-file-manager-link');
-
-				this.file.insert('<a href="' + $target.attr('rel') + '">' + $target.attr('title') + '</a>');
-			}
-		};
-	};
-})(jQuery);

+ 0 - 74
priv/js/plugins/fontcolor/fontcolor.js

@@ -1,74 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.fontcolor = function()
-	{
-		return {
-			init: function()
-			{
-				var colors = [
-					'#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00',
-					'#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca',
-					'#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694',
-					'#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314',
-					'#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100',
-					'#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b',  '#974806', '#7f6000'
-				];
-
-				var buttons = ['fontcolor', 'backcolor'];
-
-				for (var i = 0; i < 2; i++)
-				{
-					var name = buttons[i];
-
-					var button = this.button.add(name, this.lang.get(name));
-					var $dropdown = this.button.addDropdown(button);
-
-					$dropdown.width(242);
-					this.fontcolor.buildPicker($dropdown, name, colors);
-
-				}
-			},
-			buildPicker: function($dropdown, name, colors)
-			{
-				var rule = (name == 'backcolor') ? 'background-color' : 'color';
-
-				var len = colors.length;
-				var self = this;
-				var func = function(e)
-				{
-					e.preventDefault();
-					self.fontcolor.set($(this).data('rule'), $(this).attr('rel'));
-				};
-
-				for (var z = 0; z < len; z++)
-				{
-					var color = colors[z];
-
-					var $swatch = $('<a rel="' + color + '" data-rule="' + rule +'" href="#" style="float: left; font-size: 0; border: 2px solid #fff; padding: 0; margin: 0; width: 22px; height: 22px;"></a>');
-					$swatch.css('background-color', color);
-					$swatch.on('click', func);
-
-					$dropdown.append($swatch);
-				}
-
-				var $elNone = $('<a href="#" style="display: block; clear: both; padding: 5px; font-size: 12px; line-height: 1;"></a>').html(this.lang.get('none'));
-				$elNone.on('click', $.proxy(function(e)
-				{
-					e.preventDefault();
-					this.fontcolor.remove(rule);
-
-				}, this));
-
-				$dropdown.append($elNone);
-			},
-			set: function(rule, type)
-			{
-				this.inline.format('span', 'style', rule + ': ' + type + ';');
-			},
-			remove: function(rule)
-			{
-				this.inline.removeStyleRule(rule);
-			}
-		};
-	};
-})(jQuery);

+ 0 - 33
priv/js/plugins/fontfamily/fontfamily.js

@@ -1,33 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.fontfamily = function()
-	{
-		return {
-			init: function ()
-			{
-				var fonts = [ 'Arial', 'Helvetica', 'Georgia', 'Times New Roman', 'Monospace' ];
-				var that = this;
-				var dropdown = {};
-
-				$.each(fonts, function(i, s)
-				{
-					dropdown['s' + i] = { title: s, func: function() { that.fontfamily.set(s); }};
-				});
-
-				dropdown.remove = { title: 'Remove Font Family', func: that.fontfamily.reset };
-
-				var button = this.button.add('fontfamily', 'Change Font Family');
-				this.button.addDropdown(button, dropdown);
-
-			},
-			set: function (value)
-			{
-				this.inline.format('span', 'style', 'font-family:' + value + ';');
-			},
-			reset: function()
-			{
-				this.inline.removeStyleRule('font-family');
-			}
-		};
-	};
-})(jQuery);

+ 0 - 32
priv/js/plugins/fontsize/fontsize.js

@@ -1,32 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.fontsize = function()
-	{
-		return {
-			init: function()
-			{
-				var fonts = [10, 11, 12, 14, 16, 18, 20, 24, 28, 30];
-				var that = this;
-				var dropdown = {};
-
-				$.each(fonts, function(i, s)
-				{
-					dropdown['s' + i] = { title: s + 'px', func: function() { that.fontsize.set(s); } };
-				});
-
-				dropdown.remove = { title: 'Remove Font Size', func: that.fontsize.reset };
-
-				var button = this.button.add('fontsize', 'Change Font Size');
-				this.button.addDropdown(button, dropdown);
-			},
-			set: function(size)
-			{
-				this.inline.format('span', 'style', 'font-size: ' + size + 'px;');
-			},
-			reset: function()
-			{
-				this.inline.removeStyleRule('font-size');
-			}
-		};
-	};
-})(jQuery);

+ 0 - 121
priv/js/plugins/fullscreen/fullscreen.js

@@ -1,121 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.fullscreen = function()
-	{
-		return {
-			init: function()
-			{
-				this.fullscreen.isOpen = false;
-
-				var button = this.button.add('fullscreen', 'Fullscreen');
-				this.button.addCallback(button, this.fullscreen.toggle);
-
-				if (this.opts.fullscreen) this.fullscreen.toggle();
-			},
-			enable: function()
-			{
-				this.button.changeIcon('fullscreen', 'normalscreen');
-				this.button.setActive('fullscreen');
-				this.fullscreen.isOpen = true;
-
-				if (this.opts.toolbarExternal)
-				{
-					this.fullscreen.toolcss = {};
-					this.fullscreen.boxcss = {};
-					this.fullscreen.toolcss.width = this.$toolbar.css('width');
-					this.fullscreen.toolcss.top = this.$toolbar.css('top');
-					this.fullscreen.toolcss.position = this.$toolbar.css('position');
-					this.fullscreen.boxcss.top = this.$box.css('top');
-				}
-
-				this.fullscreen.height = this.$editor.height();
-
-				if (this.opts.maxHeight) this.$editor.css('max-height', '');
-				if (this.opts.minHeight) this.$editor.css('min-height', '');
-
-				if (!this.$fullscreenPlaceholder) this.$fullscreenPlaceholder = $('<div/>');
-				this.$fullscreenPlaceholder.insertAfter(this.$box);
-
-				this.$box.appendTo(document.body);
-
-				this.$box.addClass('redactor-box-fullscreen');
-				$('body, html').css('overflow', 'hidden');
-
-				this.fullscreen.resize();
-				$(window).on('resize.redactor.fullscreen', $.proxy(this.fullscreen.resize, this));
-				$(document).scrollTop(0, 0);
-
-				$('.redactor-toolbar-tooltip').hide();
-				this.$editor.focus();
-				this.observe.load();
-			},
-			disable: function()
-			{
-				this.button.removeIcon('fullscreen', 'normalscreen');
-				this.button.setInactive('fullscreen');
-				this.fullscreen.isOpen = false;
-
-				$(window).off('resize.redactor.fullscreen');
-				$('body, html').css('overflow', '');
-
-				this.$box.insertBefore(this.$fullscreenPlaceholder);
-				this.$fullscreenPlaceholder.remove();
-
-				this.$box.removeClass('redactor-box-fullscreen').css({ width: 'auto', height: 'auto' });
-
-				this.code.sync();
-
-				if (this.opts.toolbarExternal)
-				{
-					this.$box.css('top', this.fullscreen.boxcss.top);
-					this.$toolbar.css({
-						'width': this.fullscreen.toolcss.width,
-						'top': this.fullscreen.toolcss.top,
-						'position': this.fullscreen.toolcss.position
-					});
-				}
-
-				if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
-				if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);
-
-				$('.redactor-toolbar-tooltip').hide();
-				this.$editor.css('height', 'auto');
-				this.$editor.focus();
-				this.observe.load();
-			},
-			toggle: function()
-			{
-				if (this.fullscreen.isOpen)
-				{
-					this.fullscreen.disable();
-				}
-				else
-				{
-					this.fullscreen.enable();
-				}
-			},
-			resize: function()
-			{
-				if (!this.fullscreen.isOpen) return;
-
-				var toolbarHeight = this.$toolbar.height();
-
-				var height = $(window).height() - toolbarHeight - this.utils.normalize(this.$editor.css('padding-top')) - this.utils.normalize(this.$editor.css('padding-bottom'));
-				this.$box.width($(window).width()).height(height);
-
-				if (this.opts.toolbarExternal)
-				{
-					this.$toolbar.css({
-						'top': '0px',
-						'position': 'absolute',
-						'width': '100%'
-					});
-
-					this.$box.css('top', toolbarHeight + 'px');
-				}
-
-				this.$editor.height(height);
-			}
-		};
-	};
-})(jQuery);

+ 0 - 55
priv/js/plugins/imagemanager/imagemanager.js

@@ -1,55 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.imagemanager = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.imageManagerJson) return;
-
-				this.modal.addCallback('image', this.imagemanager.load);
-			},
-			load: function()
-			{
-				var $modal = this.modal.getModal();
-
-				this.modal.createTabber($modal);
-				this.modal.addTab(1, 'Upload', 'active');
-				this.modal.addTab(2, 'Choose');
-
-				$('#redactor-modal-image-droparea').addClass('redactor-tab redactor-tab1');
-
-				var $box = $('<div id="redactor-image-manager-box" style="overflow: auto; height: 300px;" class="redactor-tab redactor-tab2">').hide();
-				$modal.append($box);
-
-				$.ajax({
-				  dataType: "json",
-				  cache: false,
-				  url: this.opts.imageManagerJson,
-				  success: $.proxy(function(data)
-					{
-						$.each(data, $.proxy(function(key, val)
-						{
-							// title
-							var thumbtitle = '';
-							if (typeof val.title !== 'undefined') thumbtitle = val.title;
-
-							var img = $('<img src="' + val.thumb + '" rel="' + val.image + '" title="' + thumbtitle + '" style="width: 100px; height: 75px; cursor: pointer;" />');
-							$('#redactor-image-manager-box').append(img);
-							$(img).click($.proxy(this.imagemanager.insert, this));
-
-						}, this));
-
-
-					}, this)
-				});
-
-
-			},
-			insert: function(e)
-			{
-				this.image.insert('<img src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '">');
-			}
-		};
-	};
-})(jQuery);

+ 0 - 39
priv/js/plugins/limiter/limiter.js

@@ -1,39 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.limiter = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.limiter) return;
-
-				this.$editor.on('keydown.redactor-limiter', $.proxy(function(e)
-				{
-					var key = e.which;
-					var ctrl = e.ctrlKey || e.metaKey;
-
-					if (key == this.keyCode.BACKSPACE
-					   	|| key == this.keyCode.DELETE
-					    || key == this.keyCode.ESC
-					    || key == this.keyCode.SHIFT
-					    || (ctrl && key == 65)
-					    || (ctrl && key == 82)
-					    || (ctrl && key == 116)
-					)
-					{
-						return;
-					}
-
-					var count = this.$editor.text().length;
-					if (count >= this.opts.limiter)
-					{
-						return false;
-					}
-
-
-				}, this));
-
-			}
-		};
-	};
-})(jQuery);

+ 0 - 470
priv/js/plugins/table/table.js

@@ -1,470 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.table = function()
-	{
-		return {
-			getTemplate: function()
-			{
-				return String()
-				+ '<section id="redactor-modal-table-insert">'
-					+ '<label>' + this.lang.get('rows') + '</label>'
-					+ '<input type="text" size="5" value="2" id="redactor-table-rows" />'
-					+ '<label>' + this.lang.get('columns') + '</label>'
-					+ '<input type="text" size="5" value="3" id="redactor-table-columns" />'
-				+ '</section>';
-			},
-			init: function()
-			{
-				var dropdown = {};
-
-				dropdown.insert_table = {
-									title: this.lang.get('insert_table'),
-									func: this.table.show,
-									observe: {
-										element: 'table',
-										in: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.insert_row_above = {
-									title: this.lang.get('insert_row_above'),
-									func: this.table.addRowAbove,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.insert_row_below = {
-									title: this.lang.get('insert_row_below'),
-									func: this.table.addRowBelow,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.insert_column_left = {
-									title: this.lang.get('insert_column_left'),
-									func: this.table.addColumnLeft,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.insert_column_right = {
-									title: this.lang.get('insert_column_right'),
-									func: this.table.addColumnRight,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.add_head = {
-									title: this.lang.get('add_head'),
-									func: this.table.addHead,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.delete_head = {
-									title: this.lang.get('delete_head'),
-									func: this.table.deleteHead,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.delete_column = {
-									title: this.lang.get('delete_column'),
-									func: this.table.deleteColumn,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.delete_row = {
-									title: this.lang.get('delete_row'),
-									func: this.table.deleteRow,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				dropdown.delete_table = {
-									title: this.lang.get('delete_table'),
-									func: this.table.deleteTable,
-									observe: {
-										element: 'table',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true,
-											}
-										}
-									}
-								};
-
-				this.observe.addButton('td', 'table');
-				this.observe.addButton('th', 'table');
-
-				var button = this.button.addBefore('link', 'table', this.lang.get('table'));
-				this.button.addDropdown(button, dropdown);
-			},
-			show: function()
-			{
-				this.modal.addTemplate('table', this.table.getTemplate());
-
-				this.modal.load('table', this.lang.get('insert_table'), 300);
-				this.modal.createCancelButton();
-
-				var button = this.modal.createActionButton(this.lang.get('insert'));
-				button.on('click', this.table.insert);
-
-				this.selection.save();
-				this.modal.show();
-
-				$('#redactor-table-rows').focus();
-
-			},
-			insert: function()
-			{
-				this.placeholder.remove();
-
-				var rows = $('#redactor-table-rows').val(),
-					columns = $('#redactor-table-columns').val(),
-					$tableBox = $('<div>'),
-					tableId = Math.floor(Math.random() * 99999),
-					$table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
-					i, $row, z, $column;
-
-				for (i = 0; i < rows; i++)
-				{
-					$row = $('<tr>');
-
-					for (z = 0; z < columns; z++)
-					{
-						$column = $('<td>' + this.opts.invisibleSpace + '</td>');
-
-						// set the focus to the first td
-						if (i === 0 && z === 0)
-						{
-							$column.append(this.selection.getMarker());
-						}
-
-						$($row).append($column);
-					}
-
-					$table.append($row);
-				}
-
-				$tableBox.append($table);
-				var html = $tableBox.html();
-
-				this.modal.close();
-				this.selection.restore();
-
-				if (this.table.getTable()) return;
-
-				this.buffer.set();
-
-				var current = this.selection.getBlock() || this.selection.getCurrent();
-				if (current && current.tagName != 'BODY')
-				{
-					if (current.tagName == 'LI') current = $(current).closest('ul, ol');
-					$(current).after(html);
-				}
-				else
-				{
-					this.insert.html(html, false);
-				}
-
-				this.selection.restore();
-
-				var table = this.$editor.find('#table' + tableId);
-
-				var p = table.prev("p");
-
-				if (p.length > 0 && this.utils.isEmpty(p.html()))
-				{
-					p.remove();
-				}
-
-				if (!this.opts.linebreaks && (this.utils.browser('mozilla') || this.utils.browser('msie')))
-				{
-					var $next = table.next();
-					if ($next.length === 0)
-					{
-						 table.after(this.opts.emptyHtml);
-					}
-				}
-
-				this.observe.buttons();
-
-				table.find('span.redactor-selection-marker').remove();
-				table.removeAttr('id');
-
-				this.code.sync();
-				this.core.setCallback('insertedTable', table);
-			},
-			getTable: function()
-			{
-				var $table = $(this.selection.getParent()).closest('table');
-
-				if (!this.utils.isRedactorParent($table)) return false;
-				if ($table.size() === 0) return false;
-
-				return $table;
-			},
-			restoreAfterDelete: function($table)
-			{
-				this.selection.restore();
-				$table.find('span.redactor-selection-marker').remove();
-				this.code.sync();
-			},
-			deleteTable: function()
-			{
-				var $table = this.table.getTable();
-				if (!$table) return;
-
-				this.buffer.set();
-
-
-				var $next = $table.next();
-				if (!this.opts.linebreaks && $next.length !== 0)
-				{
-					this.caret.setStart($next);
-				}
-				else
-				{
-					this.caret.setAfter($table);
-				}
-
-
-				$table.remove();
-
-				this.code.sync();
-			},
-			deleteRow: function()
-			{
-			var $table = this.table.getTable();
-			if (!$table) return;
-
-			var $current = $(this.selection.getCurrent());
-
-			this.buffer.set();
-
-			var $current_tr = $current.closest('tr');
-			var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
-			if ($focus_tr.length)
-			{
-				var $focus_td = $focus_tr.children('td, th').first();
-				if ($focus_td.length) $focus_td.prepend(this.selection.getMarker());
-			}
-
-			$current_tr.remove();
-			this.table.restoreAfterDelete($table);
-		},
-			deleteColumn: function()
-			{
-			var $table = this.table.getTable();
-			if (!$table) return;
-
-			this.buffer.set();
-
-			var $current = $(this.selection.getCurrent());
-			var $current_td = $current.closest('td, th');
-			var index = $current_td[0].cellIndex;
-
-			$table.find('tr').each($.proxy(function(i, elem)
-			{
-				var $elem = $(elem);
-				var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
-				if (i === 0) $elem.find('td, th').eq(focusIndex).prepend(this.selection.getMarker());
-
-				$elem.find('td, th').eq(index).remove();
-
-			}, this));
-
-			this.table.restoreAfterDelete($table);
-		},
-			addHead: function()
-			{
-				var $table = this.table.getTable();
-				if (!$table) return;
-
-				this.buffer.set();
-
-				if ($table.find('thead').size() !== 0)
-				{
-					this.table.deleteHead();
-					return;
-				}
-
-				var tr = $table.find('tr').first().clone();
-				tr.find('td').replaceWith($.proxy(function()
-				{
-					return $('<th>').html(this.opts.invisibleSpace);
-				}, this));
-
-				$thead = $('<thead></thead>').append(tr);
-				$table.prepend($thead);
-
-				this.code.sync();
-
-			},
-			deleteHead: function()
-			{
-				var $table = this.table.getTable();
-				if (!$table) return;
-
-				var $thead = $table.find('thead');
-				if ($thead.size() === 0) return;
-
-				this.buffer.set();
-
-				$thead.remove();
-				this.code.sync();
-			},
-			addRowAbove: function()
-			{
-				this.table.addRow('before');
-			},
-			addRowBelow: function()
-			{
-				this.table.addRow('after');
-			},
-			addColumnLeft: function()
-			{
-				this.table.addColumn('before');
-			},
-			addColumnRight: function()
-			{
-				this.table.addColumn('after');
-			},
-			addRow: function(type)
-			{
-				var $table = this.table.getTable();
-				if (!$table) return;
-
-				this.buffer.set();
-
-				var $current = $(this.selection.getCurrent());
-				var $current_tr = $current.closest('tr');
-				var new_tr = $current_tr.clone();
-
-				new_tr.find('th').replaceWith(function()
-				{
-					var $td = $('<td>');
-					$td[0].attributes = this.attributes;
-
-					return $td.append($(this).contents());
-				});
-
-				new_tr.find('td').html(this.opts.invisibleSpace);
-
-				if (type == 'after')
-				{
-					$current_tr.after(new_tr);
-				}
-				else
-				{
-					$current_tr.before(new_tr);
-				}
-
-				this.code.sync();
-			},
-			addColumn: function (type)
-			{
-				var $table = this.table.getTable();
-				if (!$table) return;
-
-				var index = 0;
-				var current = $(this.selection.getCurrent());
-
-				this.buffer.set();
-
-				var $current_tr = current.closest('tr');
-				var $current_td = current.closest('td, th');
-
-				$current_tr.find('td, th').each($.proxy(function(i, elem)
-				{
-					if ($(elem)[0] === $current_td[0]) index = i;
-
-				}, this));
-
-				$table.find('tr').each($.proxy(function(i, elem)
-				{
-					var $current = $(elem).find('td, th').eq(index);
-
-					var td = $current.clone();
-					td.html(this.opts.invisibleSpace);
-
-					if (type == 'after')
-					{
-						$current.after(td);
-					}
-					else
-					{
-						$current.before(td);
-					}
-
-				}, this));
-
-				this.code.sync();
-			}
-		};
-	};
-})(jQuery);

+ 0 - 29
priv/js/plugins/textdirection/textdirection.js

@@ -1,29 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.textdirection = function()
-	{
-		return {
-			init: function()
-			{
-				var that = this;
-				var dropdown = {};
-
-				dropdown.ltr = { title: 'Left to Right', func: that.textdirection.setLtr };
-				dropdown.rtl = { title: 'Right to Left', func: that.textdirection.setRtl};
-
-				var button = this.button.add('textdirection', 'Change Text Direction');
-				this.button.addDropdown(button, dropdown);
-			},
-			setRtl: function()
-			{
-				this.buffer.set();
-				this.block.setAttr('dir', 'rtl');
-			},
-			setLtr: function()
-			{
-				this.buffer.set();
-				this.block.removeAttr('dir');
-			}
-		};
-	};
-})(jQuery);

+ 0 - 73
priv/js/plugins/textexpander/textexpander.js

@@ -1,73 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.textexpander = function()
-	{
-		return {
-			init: function()
-			{
-				if (!this.opts.textexpander) return;
-
-				this.$editor.on('keyup.redactor-limiter', $.proxy(function(e)
-				{
-					var key = e.which;
-					if (key == this.keyCode.SPACE)
-					{
-						var current = this.textexpander.getCurrent();
-						var cloned = $(current).clone();
-
-						var $div = $('<div>');
-						$div.html(cloned);
-
-						var text = $div.html();
-						$div.remove();
-
-						var len = this.opts.textexpander.length;
-						var replaced = 0;
-
-						for (var i = 0; i < len; i++)
-						{
-							var re = new RegExp(this.opts.textexpander[i][0]);
-							if (text.search(re) != -1)
-							{
-								replaced++;
-								text = text.replace(re, this.opts.textexpander[i][1]);
-
-								$div = $('<div>');
-								$div.html(text);
-								$div.append(this.selection.getMarker());
-
-								var html = $div.html().replace(/&nbsp;/, '');
-
-								$(current).replaceWith(html);
-								$div.remove();
-							}
-						}
-
-						if (replaced !== 0)
-						{
-							this.selection.restore();
-						}
-					}
-
-
-				}, this));
-
-			},
-			getCurrent: function()
-			{
-				var selection
-				if (window.getSelection) selection = window.getSelection();
-				else if (document.selection && document.selection.type != "Control") selection = document.selection;
-
-				if (this.utils.browser('mozilla'))
-				{
-					return selection.anchorNode.previousSibling;
-				}
-				else
-				{
-					return selection.anchorNode;
-				}
-			}
-		};
-	};
-})(jQuery);

+ 0 - 75
priv/js/plugins/video/video.js

@@ -1,75 +0,0 @@
-(function($)
-{
-	$.Redactor.prototype.video = function()
-	{
-		return {
-			reUrlYoutube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
-			reUrlVimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
-			getTemplate: function()
-			{
-				return String()
-				+ '<section id="redactor-modal-video-insert">'
-					+ '<label>' + this.lang.get('video_html_code') + '</label>'
-					+ '<textarea id="redactor-insert-video-area" style="height: 160px;"></textarea>'
-				+ '</section>';
-			},
-			init: function()
-			{
-				var button = this.button.addAfter('image', 'video', this.lang.get('video'));
-				this.button.addCallback(button, this.video.show);
-			},
-			show: function()
-			{
-				this.modal.addTemplate('video', this.video.getTemplate());
-
-				this.modal.load('video', this.lang.get('video'), 700);
-				this.modal.createCancelButton();
-
-				var button = this.modal.createActionButton(this.lang.get('insert'));
-				button.on('click', this.video.insert);
-
-				this.selection.save();
-				this.modal.show();
-
-				$('#redactor-insert-video-area').focus();
-
-			},
-			insert: function()
-			{
-				var data = $('#redactor-insert-video-area').val();
-
-				if (!data.match(/<iframe|<video/gi))
-				{
-					data = this.clean.stripTags(data);
-
-					// parse if it is link on youtube & vimeo
-					var iframeStart = '<iframe style="width: 500px; height: 281px;" src="',
-						iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
-
-					if (data.match(this.video.reUrlYoutube))
-					{
-						data = data.replace(this.video.reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
-					}
-					else if (data.match(this.video.reUrlVimeo))
-					{
-						data = data.replace(this.video.reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
-					}
-				}
-
-				this.selection.restore();
-				this.modal.close();
-
-				var current = this.selection.getBlock() || this.selection.getCurrent();
-
-				if (current) $(current).after(data);
-				else
-				{
-					this.insert.html(data);
-				}
-
-				this.code.sync();
-			}
-
-		};
-	};
-})(jQuery);

+ 0 - 9542
priv/js/redactor.js

@@ -1,9542 +0,0 @@
-/*
-	Redactor 10.2.5
-	Updated: October 1, 2015
-
-	http://imperavi.com/redactor/
-
-	Copyright (c) 2009-2015, Imperavi LLC.
-	License: http://imperavi.com/redactor/license/
-
-	Usage: $('#content').redactor();
-*/
-
-(function($)
-{
-
-	'use strict';
-
-	if (!Function.prototype.bind)
-	{
-		Function.prototype.bind = function(scope)
-		{
-			var fn = this;
-			return function()
-			{
-				return fn.apply(scope);
-			};
-		};
-	}
-
-	var uuid = 0;
-
-	// Plugin
-	$.fn.redactor = function(options)
-	{
-		var val = [];
-		var args = Array.prototype.slice.call(arguments, 1);
-
-		if (typeof options === 'string')
-		{
-			this.each(function()
-			{
-				var instance = $.data(this, 'redactor');
-				var func;
-
-				if (options.search(/\./) != '-1')
-				{
-					func = options.split('.');
-					if (typeof instance[func[0]] != 'undefined')
-					{
-						func = instance[func[0]][func[1]];
-					}
-				}
-				else
-				{
-					func = instance[options];
-				}
-
-				if (typeof instance !== 'undefined' && $.isFunction(func))
-				{
-					var methodVal = func.apply(instance, args);
-					if (methodVal !== undefined && methodVal !== instance)
-					{
-						val.push(methodVal);
-					}
-				}
-				else
-				{
-					$.error('No such method "' + options + '" for Redactor');
-				}
-			});
-		}
-		else
-		{
-			this.each(function()
-			{
-				$.data(this, 'redactor', {});
-				$.data(this, 'redactor', Redactor(this, options));
-			});
-		}
-
-		if (val.length === 0) return this;
-		else if (val.length === 1) return val[0];
-		else return val;
-
-	};
-
-	// Initialization
-	function Redactor(el, options)
-	{
-		return new Redactor.prototype.init(el, options);
-	}
-
-	// Functionality
-	$.Redactor = Redactor;
-	$.Redactor.VERSION = '10.2.5';
-	$.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button',
-						  'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus',
-						  'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
-						  'lang', 'line', 'link', 'linkify', 'list', 'modal', 'observe', 'paragraphize',
-						  'paste', 'placeholder', 'progress', 'selection', 'shortcuts',
-						  'tabifier', 'tidy',  'toolbar', 'upload', 'utils'];
-
-	$.Redactor.opts = {
-
-		// settings
-		lang: 'en',
-		direction: 'ltr', // ltr or rtl
-
-		plugins: false, // array
-
-		focus: false,
-		focusEnd: false,
-
-		placeholder: false,
-
-		visual: true,
-		tabindex: false,
-
-		minHeight: false,
-		maxHeight: false,
-
-		linebreaks: false,
-		replaceDivs: true,
-		paragraphize: true,
-		cleanStyleOnEnter: false,
-		enterKey: true,
-
-		cleanOnPaste: true,
-		cleanSpaces: true,
-		pastePlainText: false,
-
-		autosave: false, // false or url
-		autosaveName: false,
-		autosaveInterval: 60, // seconds
-		autosaveOnChange: false,
-		autosaveFields: false,
-
-		linkTooltip: true,
-		linkProtocol: 'http',
-		linkNofollow: false,
-		linkSize: 50,
-
-		imageEditable: true,
-		imageLink: true,
-		imagePosition: true,
-		imageFloatMargin: '10px',
-		imageResizable: true,
-
-		imageUpload: null,
-		imageUploadParam: 'file',
-
-		uploadImageField: false,
-
-		dragImageUpload: true,
-
-		fileUpload: null,
-		fileUploadParam: 'file',
-
-		dragFileUpload: true,
-
-		s3: false,
-
-		convertLinks: true,
-		convertUrlLinks: true,
-		convertImageLinks: true,
-		convertVideoLinks: true,
-
-		preSpaces: 4, // or false
-		tabAsSpaces: false, // true or number of spaces
-		tabKey: true,
-
-		scrollTarget: false,
-
-		toolbar: true,
-		toolbarFixed: true,
-		toolbarFixedTarget: document,
-		toolbarFixedTopOffset: 0, // pixels
-		toolbarExternal: false, // ID selector
-		toolbarOverflow: false,
-
-		source: true,
-		buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
-				  'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline'
-
-		buttonsHide: [],
-		buttonsHideOnMobile: [],
-
-		formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
-		formattingAdd: false,
-
-		tabifier: true,
-
-		deniedTags: ['script', 'style'],
-		allowedTags: false, // or array
-
-		paragraphizeBlocks: ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption',
-							'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea',
-							'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'],
-
-		removeComments: false,
-		replaceTags: [
-			['strike', 'del'],
-			['b', 'strong']
-		],
-		replaceStyles: [
-            ['font-weight:\\s?bold', "strong"],
-            ['font-style:\\s?italic', "em"],
-            ['text-decoration:\\s?underline', "u"],
-            ['text-decoration:\\s?line-through', 'del']
-        ],
-        removeDataAttr: false,
-
-		removeAttr: false, // or multi array
-		allowedAttr: false, // or multi array
-
-		removeWithoutAttr: ['span'], // or false
-		removeEmpty: ['p'], // or false;
-
-		activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
-						'alignleft', 'aligncenter', 'alignright', 'justify'],
-		activeButtonsStates: {
-			b: 'bold',
-			strong: 'bold',
-			i: 'italic',
-			em: 'italic',
-			del: 'deleted',
-			strike: 'deleted',
-			ul: 'unorderedlist',
-			ol: 'orderedlist',
-			u: 'underline'
-		},
-
-		shortcuts: {
-			'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' },
-			'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] },
-			'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] },
-			'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] },
-			'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] },
-			'ctrl+k, meta+k': { func: 'link.show' },
-			'ctrl+shift+7':   { func: 'list.toggle', params: ['orderedlist'] },
-			'ctrl+shift+8':   { func: 'list.toggle', params: ['unorderedlist'] }
-		},
-		shortcutsAdd: false,
-
-		// private
-		buffer: [],
-		rebuffer: [],
-		emptyHtml: '<p>&#x200b;</p>',
-		invisibleSpace: '&#x200b;',
-		imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
-		indentValue: 20,
-		verifiedTags: 		['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule
-		inlineTags: 		['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
-		alignmentTags: 		['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',  'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
-		blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
-		highContrast: false,
-		observe: {
-			dropdowns: []
-		},
-
-		// lang
-		langs: {
-			en: {
-				html: 'HTML',
-				video: 'Insert Video',
-				image: 'Insert Image',
-				table: 'Table',
-				link: 'Link',
-				link_insert: 'Insert link',
-				link_edit: 'Edit link',
-				unlink: 'Unlink',
-				formatting: 'Formatting',
-				paragraph: 'Normal text',
-				quote: 'Quote',
-				code: 'Code',
-				header1: 'Header 1',
-				header2: 'Header 2',
-				header3: 'Header 3',
-				header4: 'Header 4',
-				header5: 'Header 5',
-				bold: 'Bold',
-				italic: 'Italic',
-				fontcolor: 'Font Color',
-				backcolor: 'Back Color',
-				unorderedlist: 'Unordered List',
-				orderedlist: 'Ordered List',
-				outdent: 'Outdent',
-				indent: 'Indent',
-				cancel: 'Cancel',
-				insert: 'Insert',
-				save: 'Save',
-				_delete: 'Delete',
-				insert_table: 'Insert Table',
-				insert_row_above: 'Add Row Above',
-				insert_row_below: 'Add Row Below',
-				insert_column_left: 'Add Column Left',
-				insert_column_right: 'Add Column Right',
-				delete_column: 'Delete Column',
-				delete_row: 'Delete Row',
-				delete_table: 'Delete Table',
-				rows: 'Rows',
-				columns: 'Columns',
-				add_head: 'Add Head',
-				delete_head: 'Delete Head',
-				title: 'Title',
-				image_position: 'Position',
-				none: 'None',
-				left: 'Left',
-				right: 'Right',
-				center: 'Center',
-				image_web_link: 'Image Web Link',
-				text: 'Text',
-				mailto: 'Email',
-				web: 'URL',
-				video_html_code: 'Video Embed Code or Youtube/Vimeo Link',
-				file: 'Insert File',
-				upload: 'Upload',
-				download: 'Download',
-				choose: 'Choose',
-				or_choose: 'Or choose',
-				drop_file_here: 'Drop file here',
-				align_left: 'Align text to the left',
-				align_center: 'Center text',
-				align_right: 'Align text to the right',
-				align_justify: 'Justify text',
-				horizontalrule: 'Insert Horizontal Rule',
-				deleted: 'Deleted',
-				anchor: 'Anchor',
-				link_new_tab: 'Open link in new tab',
-				underline: 'Underline',
-				alignment: 'Alignment',
-				filename: 'Name (optional)',
-				edit: 'Edit',
-				upload_label: 'Drop file here or '
-			}
-		},
-
-		linkify: {
-			regexps: {
-				youtube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
-				vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
-				image: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig,
-				url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig,
-			}
-		},
-
-		codemirror: false
-	};
-
-	// Functionality
-	Redactor.fn = $.Redactor.prototype = {
-
-		keyCode: {
-			BACKSPACE: 8,
-			DELETE: 46,
-			UP: 38,
-			DOWN: 40,
-			ENTER: 13,
-			SPACE: 32,
-			ESC: 27,
-			TAB: 9,
-			CTRL: 17,
-			META: 91,
-			SHIFT: 16,
-			ALT: 18,
-			RIGHT: 39,
-			LEFT: 37,
-			LEFT_WIN: 91
-		},
-
-		// Initialization
-		init: function(el, options)
-		{
-			this.$element = $(el);
-			this.uuid = uuid++;
-
-			// if paste event detected = true
-			this.rtePaste = false;
-			this.$pasteBox = false;
-
-			this.loadOptions(options);
-			this.loadModules();
-
-			// formatting storage
-			this.formatting = {};
-
-			// block level tags
-			$.merge(this.opts.blockLevelElements, this.opts.alignmentTags);
-			this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
-
-			// setup allowed and denied tags
-			this.tidy.setupAllowed();
-
-			// setup denied tags
-			if (this.opts.deniedTags !== false)
-			{
-				var tags = ['html', 'head', 'link', 'body', 'meta', 'applet'];
-				for (var i = 0; i < tags.length; i++)
-				{
-					this.opts.deniedTags.push(tags[i]);
-				}
-			}
-
-			// load lang
-			this.lang.load();
-
-			// extend shortcuts
-			$.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
-
-			// start callback
-			this.core.setCallback('start');
-
-			// build
-			this.start = true;
-			this.build.run();
-		},
-
-		loadOptions: function(options)
-		{
-			this.opts = $.extend(
-				{},
-				$.extend(true, {}, $.Redactor.opts),
-				this.$element.data(),
-				options
-			);
-		},
-		getModuleMethods: function(object)
-		{
-			return Object.getOwnPropertyNames(object).filter(function(property)
-			{
-				return typeof object[property] == 'function';
-			});
-		},
-		loadModules: function()
-		{
-			var len = $.Redactor.modules.length;
-			for (var i = 0; i < len; i++)
-			{
-				this.bindModuleMethods($.Redactor.modules[i]);
-			}
-		},
-		bindModuleMethods: function(module)
-		{
-			if (typeof this[module] == 'undefined') return;
-
-			// init module
-			this[module] = this[module]();
-
-			var methods = this.getModuleMethods(this[module]);
-			var len = methods.length;
-
-			// bind methods
-			for (var z = 0; z < len; z++)
-			{
-				this[module][methods[z]] = this[module][methods[z]].bind(this);
-			}
-		},
-		alignment: function()
-		{
-			return {
-				left: function()
-				{
-					this.alignment.set('');
-				},
-				right: function()
-				{
-					this.alignment.set('right');
-				},
-				center: function()
-				{
-					this.alignment.set('center');
-				},
-				justify: function()
-				{
-					this.alignment.set('justify');
-				},
-				set: function(type)
-				{
-					// focus
-					if (!this.utils.browser('msie') && !this.opts.linebreaks)
-					{
-						this.$editor.focus();
-					}
-
-					// get blocks
-					this.alignment.blocks = this.selection.getBlocks();
-					this.alignment.type = type;
-
-					this.buffer.set();
-					this.selection.save();
-
-					// set alignment
-					if (this.alignment.isLinebreaksOrNoBlocks())
-					{
-						this.alignment.setText();
-					}
-					else
-					{
-						this.alignment.setBlocks();
-					}
-
-					// sync
-					this.selection.restore();
-					this.code.sync();
-				},
-				setText: function()
-				{
-					var wrapper = this.selection.wrap('div');
-					$(wrapper).attr('data-tagblock', 'redactor').css('text-align', this.alignment.type);
-				},
-				setBlocks: function()
-				{
-					$.each(this.alignment.blocks, $.proxy(function(i, el)
-					{
-						var $el = this.utils.getAlignmentElement(el);
-						if (!$el) return;
-
-						if (this.alignment.isNeedReplaceElement($el))
-						{
-							this.alignment.replaceElement($el);
-						}
-						else
-						{
-							this.alignment.alignElement($el);
-						}
-
-					}, this));
-				},
-				isLinebreaksOrNoBlocks: function()
-				{
-					return (this.opts.linebreaks && this.alignment.blocks[0] === false);
-				},
-				isNeedReplaceElement: function($el)
-				{
-					return (this.alignment.type === '' && typeof($el.data('tagblock')) !== 'undefined');
-				},
-				replaceElement: function($el)
-				{
-					$el.replaceWith($el.html());
-				},
-				alignElement: function($el)
-				{
-					$el.css('text-align', this.alignment.type);
-					this.utils.removeEmptyAttr($el, 'style');
-				}
-			};
-		},
-		autosave: function()
-		{
-			return {
-				html: false,
-				enable: function()
-				{
-					if (!this.opts.autosave) return;
-
-					this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
-
-					if (this.opts.autosaveOnChange) return;
-					this.autosaveInterval = setInterval(this.autosave.load, this.opts.autosaveInterval * 1000);
-				},
-				onChange: function()
-				{
-					if (!this.opts.autosaveOnChange) return;
-					this.autosave.load();
-				},
-				load: function()
-				{
-					if (!this.opts.autosave) return;
-
-					this.autosave.source = this.code.get();
-
-					if (this.autosave.html === this.autosave.source) return;
-
-					// data
-					var data = {};
-					data['name'] = this.autosave.name;
-					data[this.autosave.name] = this.autosave.source;
-					data = this.autosave.getHiddenFields(data);
-
-					// ajax
-					var jsxhr = $.ajax({
-						url: this.opts.autosave,
-						type: 'post',
-						data: data
-					});
-
-					jsxhr.done(this.autosave.success);
-				},
-				getHiddenFields: function(data)
-				{
-					if (this.opts.autosaveFields === false || typeof this.opts.autosaveFields !== 'object')
-					{
-						return data;
-					}
-
-					$.each(this.opts.autosaveFields, $.proxy(function(k, v)
-					{
-						if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
-						data[k] = v;
-
-					}, this));
-
-					return data;
-
-				},
-				success: function(data)
-				{
-					var json;
-					try
-					{
-						json = $.parseJSON(data);
-					}
-					catch(e)
-					{
-						//data has already been parsed
-						json = data;
-					}
-
-					var callbackName = (typeof json.error == 'undefined') ? 'autosave' :  'autosaveError';
-
-					this.core.setCallback(callbackName, this.autosave.name, json);
-					this.autosave.html = this.autosave.source;
-				},
-				disable: function()
-				{
-					clearInterval(this.autosaveInterval);
-				}
-			};
-		},
-		block: function()
-		{
-			return {
-				formatting: function(name)
-				{
-					this.block.clearStyle = false;
-					var type, value;
-
-					if (typeof this.formatting[name].data != 'undefined') type = 'data';
-					else if (typeof this.formatting[name].attr != 'undefined') type = 'attr';
-					else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class';
-
-					if (typeof this.formatting[name].clear != 'undefined')
-					{
-						this.block.clearStyle = true;
-					}
-
-					if (type) value = this.formatting[name][type];
-
-					this.block.format(this.formatting[name].tag, type, value);
-
-				},
-				format: function(tag, type, value)
-				{
-					if (tag == 'quote') tag = 'blockquote';
-
-					var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-					if ($.inArray(tag, formatTags) == -1) return;
-
-					this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1);
-
-					// focus
-					if (!this.utils.browser('msie')) this.$editor.focus();
-
-					var html = $.trim(this.$editor.html());
-					this.block.isEmpty = this.utils.isEmpty(html);
-
-					// FF focus
-					if (this.utils.browser('mozilla') && !this.focus.isFocused())
-					{
-						if (this.block.isEmpty)
-						{
-							var $first;
-							if (!this.opts.linebreaks)
-							{
-								$first = this.$editor.children().first();
-								this.caret.setEnd($first);
-							}
-						}
-					}
-
-					this.block.blocks = this.selection.getBlocks();
-
-					this.block.blocksSize = this.block.blocks.length;
-					this.block.type = type;
-					this.block.value = value;
-
-					this.buffer.set();
-					this.selection.save();
-
-					this.block.set(tag);
-
-					this.selection.restore();
-					this.code.sync();
-					this.observe.load();
-
-				},
-				set: function(tag)
-				{
-
-					this.selection.get();
-					this.block.containerTag = this.range.commonAncestorContainer.tagName;
-
-					if (this.range.collapsed)
-					{
-						this.block.setCollapsed(tag);
-					}
-					else
-					{
-						this.block.setMultiple(tag);
-					}
-				},
-				setCollapsed: function(tag)
-				{
-					if (this.opts.linebreaks && this.block.isEmpty && tag != 'p')
-					{
-						var node = document.createElement(tag);
-						this.$editor.html(node);
-						this.caret.setEnd(node);
-
-						return;
-					}
-
-					var block = this.block.blocks[0];
-					if (block === false) return;
-
-					if (block.tagName == 'LI')
-					{
-						if (tag != 'blockquote') return;
-
-						this.block.formatListToBlockquote();
-						return;
-					}
-
-					var isContainerTable = (this.block.containerTag  == 'TD' || this.block.containerTag  == 'TH');
-					if (isContainerTable && !this.opts.linebreaks)
-					{
-						document.execCommand('formatblock', false, '<' + tag + '>');
-
-						block = this.selection.getBlock();
-						this.block.toggle($(block));
-
-					}
-					else if (block.tagName.toLowerCase() != tag)
-					{
-						if (this.opts.linebreaks && tag == 'p')
-						{
-							$(block).append('<br>');
-							this.utils.replaceWithContents(block);
-						}
-						else
-						{
-							var $formatted = this.utils.replaceToTag(block, tag);
-
-							this.block.toggle($formatted);
-
-							if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
-							if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
-							if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
-
-							this.block.formatTableWrapping($formatted);
-						}
-					}
-					else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag)
-					{
-						// blockquote off
-						if (this.opts.linebreaks)
-						{
-							$(block).append('<br>');
-							this.utils.replaceWithContents(block);
-						}
-						else
-						{
-							var $el = this.utils.replaceToTag(block, 'p');
-							this.block.toggle($el);
-						}
-					}
-					else if (block.tagName.toLowerCase() == tag)
-					{
-						this.block.toggle($(block));
-					}
-
-
-					if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
-					{
-						$(block).removeAttr('class').removeAttr('style');
-					}
-				},
-				setMultiple: function(tag)
-				{
-					var block = this.block.blocks[0];
-
-					var isContainerTable = (this.block.containerTag  == 'TD' || this.block.containerTag  == 'TH');
-
-					if (block !== false && this.block.blocksSize === 1)
-					{
-						if (block.tagName.toLowerCase() == tag &&  tag == 'blockquote')
-						{
-							// blockquote off
-							if (this.opts.linebreaks)
-							{
-								$(block).append('<br>');
-								this.utils.replaceWithContents(block);
-							}
-							else
-							{
-								var $el = this.utils.replaceToTag(block, 'p');
-								this.block.toggle($el);
-							}
-						}
-						else if (block.tagName == 'LI')
-						{
-							if (tag != 'blockquote') return;
-
-							this.block.formatListToBlockquote();
-						}
-						else if (this.block.containerTag == 'BLOCKQUOTE')
-						{
-							this.block.formatBlockquote(tag);
-						}
-						else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block)))
-						{
-							this.block.formatWrap(tag);
-						}
-						else
-						{
-							if (this.opts.linebreaks && tag == 'p')
-							{
-								$(block).prepend('<br>').append('<br>');
-								this.utils.replaceWithContents(block);
-							}
-							else if (block.tagName === 'TD')
-							{
-								this.block.formatWrap(tag);
-							}
-							else
-							{
-								var $formatted = this.utils.replaceToTag(block, tag);
-
-								this.block.toggle($formatted);
-
-								if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
-								if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
-							}
-						}
-					}
-					else
-					{
-						if (this.opts.linebreaks || tag != 'p')
-						{
-							if (tag == 'blockquote')
-							{
-								var count = 0;
-								for (var i = 0; i < this.block.blocksSize; i++)
-								{
-									if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++;
-								}
-
-								// only blockquote selected
-								if (count == this.block.blocksSize)
-								{
-									$.each(this.block.blocks, $.proxy(function(i,s)
-									{
-										var $formatted = false;
-										if (this.opts.linebreaks)
-										{
-											$(s).prepend('<br>').append('<br>');
-											$formatted = this.utils.replaceWithContents(s);
-										}
-										else
-										{
-											$formatted = this.utils.replaceToTag(s, 'p');
-										}
-
-										if ($formatted && typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
-										{
-											$formatted.removeAttr('class').removeAttr('style');
-										}
-
-									}, this));
-
-									return;
-								}
-
-							}
-
-							this.block.formatWrap(tag);
-						}
-						else
-						{
-							var classSize = 0;
-							var toggleType = false;
-							if (this.block.type == 'class')
-							{
-								toggleType = 'toggle';
-								classSize = $(this.block.blocks).filter('.' + this.block.value).length;
-
-								if (this.block.blocksSize == classSize) toggleType = 'toggle';
-								else if (this.block.blocksSize > classSize) toggleType = 'set';
-								else if (classSize === 0) toggleType = 'set';
-
-							}
-
-							var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd'];
-							$.each(this.block.blocks, $.proxy(function(i,s)
-							{
-								if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return;
-
-								var $formatted = this.utils.replaceToTag(s, tag);
-
-								if (toggleType)
-								{
-									if (toggleType == 'toggle') this.block.toggle($formatted);
-									else if (toggleType == 'remove') this.block.remove($formatted);
-									else if (toggleType == 'set') this.block.setForce($formatted);
-								}
-								else this.block.toggle($formatted);
-
-								if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
-								if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
-								if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
-
-								if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
-								{
-									$formatted.removeAttr('class').removeAttr('style');
-								}
-
-
-							}, this));
-						}
-					}
-				},
-				setForce: function($el)
-				{
-					// remove style and class if the specified setting
-					if (this.block.clearStyle)
-					{
-						$el.removeAttr('class').removeAttr('style');
-					}
-
-					if (this.block.type == 'class')
-					{
-						$el.addClass(this.block.value);
-						return;
-					}
-					else if (this.block.type == 'attr' || this.block.type == 'data')
-					{
-						$el.attr(this.block.value.name, this.block.value.value);
-						return;
-					}
-				},
-				toggle: function($el)
-				{
-					// remove style and class if the specified setting
-					if (this.block.clearStyle)
-					{
-						$el.removeAttr('class').removeAttr('style');
-					}
-
-					if (this.block.type == 'class')
-					{
-						$el.toggleClass(this.block.value);
-						return;
-					}
-					else if (this.block.type == 'attr' || this.block.type == 'data')
-					{
-						if ($el.attr(this.block.value.name) == this.block.value.value)
-						{
-							$el.removeAttr(this.block.value.name);
-						}
-						else
-						{
-							$el.attr(this.block.value.name, this.block.value.value);
-						}
-
-						return;
-					}
-					else
-					{
-						$el.removeAttr('style class');
-						return;
-					}
-				},
-				remove: function($el)
-				{
-					$el.removeClass(this.block.value);
-				},
-				formatListToBlockquote: function()
-				{
-					var block = $(this.block.blocks[0]).closest('ul, ol', this.$editor[0]);
-
-					$(block).find('ul, ol').contents().unwrap();
-					$(block).find('li').append($('<br>')).contents().unwrap();
-
-					var $el = this.utils.replaceToTag(block, 'blockquote');
-					this.block.toggle($el);
-				},
-				formatBlockquote: function(tag)
-				{
-					document.execCommand('outdent');
-					document.execCommand('formatblock', false, tag);
-
-					this.clean.clearUnverified();
-					this.$editor.find('p:empty').remove();
-
-					var formatted = this.selection.getBlock();
-
-					if (tag != 'p')
-					{
-						$(formatted).find('img').remove();
-					}
-
-					if (!this.opts.linebreaks)
-					{
-						this.block.toggle($(formatted));
-					}
-
-					this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
-
-					if (this.opts.linebreaks && tag == 'p')
-					{
-						this.utils.replaceWithContents(formatted);
-					}
-
-				},
-				formatWrap: function(tag)
-				{
-					if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
-					{
-						if (tag == 'blockquote')
-						{
-							this.block.formatListToBlockquote();
-						}
-						else
-						{
-							return;
-						}
-					}
-
-					var formatted = this.selection.wrap(tag);
-					if (formatted === false) return;
-
-					var $formatted = $(formatted);
-
-					this.block.formatTableWrapping($formatted);
-
-					var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
-
-					$elements.contents().unwrap();
-
-					if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
-
-					$.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
-
-					$formatted.append(this.selection.getMarker(2));
-
-					if (!this.opts.linebreaks)
-					{
-						this.block.toggle($formatted);
-					}
-
-					this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
-					$formatted.find('blockquote:empty').remove();
-
-					if (this.block.isRemoveInline)
-					{
-						this.utils.removeInlineTags($formatted);
-					}
-
-					if (this.opts.linebreaks && tag == 'p')
-					{
-						this.utils.replaceWithContents($formatted);
-					}
-
-					if (this.opts.linebreaks)
-					{
-						var $next = $formatted.next().next();
-						if ($next.size() != 0 && $next[0].tagName === 'BR')
-						{
-							$next.remove();
-						}
-					}
-
-
-
-				},
-				formatTableWrapping: function($formatted)
-				{
-					if ($formatted.closest('table', this.$editor[0]).length === 0) return;
-
-					if ($formatted.closest('tr', this.$editor[0]).length === 0) $formatted.wrap('<tr>');
-					if ($formatted.closest('td', this.$editor[0]).length === 0 && $formatted.closest('th').length === 0)
-					{
-						$formatted.wrap('<td>');
-					}
-				},
-				removeData: function(name, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).removeAttr('data-' + name);
-
-					this.code.sync();
-				},
-				setData: function(name, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).attr('data-' + name, value);
-
-					this.code.sync();
-				},
-				toggleData: function(name, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$.each(blocks, function()
-					{
-						if ($(this).attr('data-' + name))
-						{
-							$(this).removeAttr('data-' + name);
-						}
-						else
-						{
-							$(this).attr('data-' + name, value);
-						}
-					});
-				},
-				removeAttr: function(attr, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).removeAttr(attr);
-
-					this.code.sync();
-				},
-				setAttr: function(attr, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).attr(attr, value);
-
-					this.code.sync();
-				},
-				toggleAttr: function(attr, value)
-				{
-					var blocks = this.selection.getBlocks();
-					$.each(blocks, function()
-					{
-						if ($(this).attr(name))
-						{
-							$(this).removeAttr(name);
-						}
-						else
-						{
-							$(this).attr(name, value);
-						}
-					});
-				},
-				removeClass: function(className)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).removeClass(className);
-
-					this.utils.removeEmptyAttr(blocks, 'class');
-
-					this.code.sync();
-				},
-				setClass: function(className)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).addClass(className);
-
-					this.code.sync();
-				},
-				toggleClass: function(className)
-				{
-					var blocks = this.selection.getBlocks();
-					$(blocks).toggleClass(className);
-
-					this.code.sync();
-				}
-			};
-		},
-		buffer: function()
-		{
-			return {
-				set: function(type)
-				{
-					if (typeof type == 'undefined' || type == 'undo')
-					{
-						this.buffer.setUndo();
-					}
-					else
-					{
-						this.buffer.setRedo();
-					}
-				},
-				setUndo: function()
-				{
-					this.selection.save();
-					this.opts.buffer.push(this.$editor.html());
-					this.selection.restore();
-				},
-				setRedo: function()
-				{
-					this.selection.save();
-					this.opts.rebuffer.push(this.$editor.html());
-					this.selection.restore();
-				},
-				getUndo: function()
-				{
-					this.$editor.html(this.opts.buffer.pop());
-				},
-				getRedo: function()
-				{
-					this.$editor.html(this.opts.rebuffer.pop());
-				},
-				add: function()
-				{
-					this.opts.buffer.push(this.$editor.html());
-				},
-				undo: function()
-				{
-					if (this.opts.buffer.length === 0) return;
-
-					this.buffer.set('redo');
-					this.buffer.getUndo();
-
-					this.selection.restore();
-
-					setTimeout($.proxy(this.observe.load, this), 50);
-				},
-				redo: function()
-				{
-					if (this.opts.rebuffer.length === 0) return;
-
-					this.buffer.set('undo');
-					this.buffer.getRedo();
-
-					this.selection.restore();
-
-					setTimeout($.proxy(this.observe.load, this), 50);
-				}
-			};
-		},
-		build: function()
-		{
-			return {
-				focused: false,
-				blured: true,
-				run: function()
-				{
-					this.build.createContainerBox();
-					this.build.loadContent();
-					this.build.loadEditor();
-					this.build.enableEditor();
-					this.build.setCodeAndCall();
-				},
-				isTextarea: function()
-				{
-					return (this.$element[0].tagName === 'TEXTAREA');
-				},
-				createContainerBox: function()
-				{
-					this.$box = $('<div class="redactor-box" role="application" />');
-				},
-				createTextarea: function()
-				{
-					this.$textarea = $('<textarea />').attr('name', this.build.getTextareaName());
-				},
-				getTextareaName: function()
-				{
-					return ((typeof(name) == 'undefined')) ? 'content-' + this.uuid : this.$element.attr('id');
-				},
-				loadContent: function()
-				{
-					var func = (this.build.isTextarea()) ? 'val' : 'html';
-					this.content = $.trim(this.$element[func]());
-				},
-				enableEditor: function()
-				{
-					this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
-				},
-				loadEditor: function()
-				{
-					var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement';
-					this.build[func]();
-				},
-				fromTextarea: function()
-				{
-					this.$editor = $('<div />');
-					this.$textarea = this.$element;
-					this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element);
-					this.$editor.addClass('redactor-editor');
-
-					this.$element.hide();
-				},
-				fromElement: function()
-				{
-					this.$editor = this.$element;
-					this.build.createTextarea();
-					this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea);
-					this.$editor.addClass('redactor-editor');
-
-					this.$textarea.hide();
-				},
-				setCodeAndCall: function()
-				{
-					// set code
-					this.code.set(this.content);
-
-					this.build.setOptions();
-					this.build.callEditor();
-
-					// code mode
-					if (this.opts.visual) return;
-					setTimeout($.proxy(this.code.showCode, this), 200);
-				},
-				callEditor: function()
-				{
-					this.build.disableMozillaEditing();
-					this.build.disableIeLinks();
-					this.build.setEvents();
-					this.build.setHelpers();
-
-					// load toolbar
-					if (this.opts.toolbar)
-					{
-						this.opts.toolbar = this.toolbar.init();
-						this.toolbar.build();
-					}
-
-					// modal templates init
-					this.modal.loadTemplates();
-
-					// plugins
-					this.build.plugins();
-
-					// observers
-					setTimeout($.proxy(this.observe.load, this), 4);
-
-					// init callback
-					this.core.setCallback('init');
-				},
-				setOptions: function()
-				{
-					// textarea direction
-					$(this.$textarea).attr('dir', this.opts.direction);
-
-					if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks');
-
-					if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex);
-
-					if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
-					if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);
-
-				},
-				setEventDropUpload: function(e)
-				{
-					e.preventDefault();
-
-					if (!this.opts.dragImageUpload || !this.opts.dragFileUpload) return;
-
-					var files = e.dataTransfer.files;
-					this.upload.directUpload(files[0], e);
-				},
-				setEventDrop: function(e)
-				{
-					this.code.sync();
-					setTimeout(this.clean.clearUnverified, 1);
-					this.core.setCallback('drop', e);
-				},
-				setEvents: function()
-				{
-					// drop
-					this.$editor.on('dragover.redactor dragenter.redactor', function(e)
-					{
-						e.preventDefault();
-						e.stopPropagation();
-				    });
-
-					this.$editor.on('drop.redactor', $.proxy(function(e)
-					{
-						e = e.originalEvent || e;
-
-						if (window.FormData === undefined || !e.dataTransfer) return true;
-
-						if (e.dataTransfer.files.length === 0)
-						{
-							return this.build.setEventDrop(e);
-						}
-						else
-						{
-							this.build.setEventDropUpload(e);
-						}
-
-						setTimeout(this.clean.clearUnverified, 1);
-						this.core.setCallback('drop', e);
-
-					}, this));
-
-
-					// click
-					this.$editor.on('click.redactor', $.proxy(function(e)
-					{
-						var event = this.core.getEvent();
-						var type = (event == 'click' || event == 'arrow') ? false : 'click';
-
-						this.core.addEvent(type);
-						this.utils.disableSelectAll();
-						this.core.setCallback('click', e);
-
-					}, this));
-
-					// paste
-					this.$editor.on('paste.redactor', $.proxy(this.paste.init, this));
-
-					// cut
-					this.$editor.on('cut.redactor', $.proxy(this.code.sync, this));
-
-					// keydown
-					this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this));
-
-					// keyup
-					this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this));
-
-					// textarea keydown
-					if ($.isFunction(this.opts.codeKeydownCallback))
-					{
-						this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this));
-					}
-
-					// textarea keyup
-					if ($.isFunction(this.opts.codeKeyupCallback))
-					{
-						this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this));
-					}
-
-					// focus
-					this.$editor.on('focus.redactor', $.proxy(function(e)
-					{
-						if ($.isFunction(this.opts.focusCallback))
-						{
-							this.core.setCallback('focus', e);
-						}
-
-						this.build.focused = true;
-						this.build.blured = false;
-
-						if (this.selection.getCurrent() === false)
-						{
-							this.selection.get();
-							this.range.setStart(this.$editor[0], 0);
-							this.range.setEnd(this.$editor[0], 0);
-							this.selection.addRange();
-						}
-
-
-					}, this));
-
-
-					// blur
-					$(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(function(e)
-					{
-						if (this.start) return;
-						if (this.rtePaste) return;
-
-						if ($(e.target).closest('.redactor-editor, .redactor-toolbar, .redactor-dropdown').size() !== 0)
-						{
-							return;
-						}
-
-						this.utils.disableSelectAll();
-						if (!this.build.blured && $.isFunction(this.opts.blurCallback))
-						{
-							this.core.setCallback('blur', e);
-						}
-
-						this.build.focused = false;
-						this.build.blured = true;
-
-					}, this));
-
-				},
-				setHelpers: function()
-				{
-					// linkify
-					if (this.linkify.isEnabled())
-					{
-						this.linkify.format();
-					}
-
-					// placeholder
-					this.placeholder.enable();
-
-					// focus
-					if (this.opts.focus) setTimeout(this.focus.setStart, 100);
-					if (this.opts.focusEnd) setTimeout(this.focus.setEnd, 100);
-
-				},
-				plugins: function()
-				{
-					if (!this.opts.plugins) return;
-
-					$.each(this.opts.plugins, $.proxy(function(i, s)
-					{
-						var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn;
-
-						if (!$.isFunction(func[s]))
-						{
-							return;
-						}
-
-						this[s] = func[s]();
-
-						// get methods
-						var methods = this.getModuleMethods(this[s]);
-						var len = methods.length;
-
-						// bind methods
-						for (var z = 0; z < len; z++)
-						{
-							this[s][methods[z]] = this[s][methods[z]].bind(this);
-						}
-
-						if ($.isFunction(this[s].init))
-						{
-							this[s].init();
-						}
-
-
-					}, this));
-
-				},
-				disableMozillaEditing: function()
-				{
-					if (!this.utils.browser('mozilla')) return;
-
-					// FF fix
-					try {
-						document.execCommand('enableObjectResizing', false, false);
-						document.execCommand('enableInlineTableEditing', false, false);
-					} catch (e) {}
-				},
-				disableIeLinks: function()
-				{
-					if (!this.utils.browser('msie')) return;
-
-					// IE prevent converting links
-					document.execCommand("AutoUrlDetect", false, false);
-				}
-			};
-		},
-		button: function()
-		{
-			return {
-				build: function(btnName, btnObject)
-				{
-					var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr({'role': 'button', 'aria-label': btnObject.title, 'tabindex': '-1'});
-
-					// click
-					if (btnObject.func || btnObject.command || btnObject.dropdown)
-					{
-						this.button.setEvent($button, btnName, btnObject);
-					}
-
-					// dropdown
-					if (btnObject.dropdown)
-					{
-						$button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
-
-						var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
-						$button.data('dropdown', $dropdown);
-						this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
-					}
-
-					// tooltip
-					if (this.utils.isDesktop())
-					{
-						this.button.createTooltip($button, btnName, btnObject.title);
-					}
-
-					return $button;
-				},
-				setEvent: function($button, btnName, btnObject)
-				{
-					$button.on('touchstart click', $.proxy(function(e)
-					{
-						if ($button.hasClass('redactor-button-disabled')) return false;
-
-						var type = 'func';
-						var callback = btnObject.func;
-
-						if (btnObject.command)
-						{
-							type = 'command';
-							callback = btnObject.command;
-						}
-						else if (btnObject.dropdown)
-						{
-							type = 'dropdown';
-							callback = false;
-						}
-
-						this.button.onClick(e, btnName, type, callback);
-
-					}, this));
-				},
-				createTooltip: function($button, name, title)
-				{
-					var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + this.uuid + ' redactor-toolbar-tooltip-' + name).hide().html(title);
-					$tooltip.appendTo('body');
-
-					$button.on('mouseover', function()
-					{
-						if ($(this).hasClass('redactor-button-disabled'))
-						{
-							return;
-						}
-
-						var pos = $button.offset();
-
-						$tooltip.css({
-							top: (pos.top + $button.innerHeight()) + 'px',
-							left: (pos.left + $button.innerWidth()/2 - $tooltip.innerWidth()/2) + 'px'
-						});
-						$tooltip.show();
-
-					});
-
-					$button.on('mouseout', function()
-					{
-						$tooltip.hide();
-					});
-
-				},
-				onClick: function(e, btnName, type, callback)
-				{
-					this.button.caretOffset = this.caret.getOffset();
-
-					e.preventDefault();
-
-					$(document).find('.redactor-toolbar-tooltip').hide();
-
-					if (this.utils.browser('msie')) e.returnValue = false;
-
-					if (type == 'command') this.inline.format(callback);
-					else if (type == 'dropdown') this.dropdown.show(e, btnName);
-					else this.button.onClickCallback(e, callback, btnName);
-				},
-				onClickCallback: function(e, callback, btnName)
-				{
-					var func;
-
-					if ($.isFunction(callback)) callback.call(this, btnName);
-					else if (callback.search(/\./) != '-1')
-					{
-						func = callback.split('.');
-						if (typeof this[func[0]] == 'undefined') return;
-
-						this[func[0]][func[1]](btnName);
-					}
-					else this[callback](btnName);
-
-					this.observe.buttons(e, btnName);
-				},
-				get: function(key)
-				{
-					return this.$toolbar.find('a.re-' + key);
-				},
-				setActive: function(key)
-				{
-					this.button.get(key).addClass('redactor-act');
-				},
-				setInactive: function(key)
-				{
-					this.button.get(key).removeClass('redactor-act');
-				},
-				setInactiveAll: function(key)
-				{
-					if (typeof key === 'undefined')
-					{
-						this.$toolbar.find('a.re-icon').removeClass('redactor-act');
-					}
-					else
-					{
-						this.$toolbar.find('a.re-icon').not('.re-' + key).removeClass('redactor-act');
-					}
-				},
-				setActiveInVisual: function()
-				{
-					this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').removeClass('redactor-button-disabled');
-				},
-				setInactiveInCode: function()
-				{
-					this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').addClass('redactor-button-disabled');
-				},
-				changeIcon: function(key, classname)
-				{
-					this.button.get(key).addClass('re-' + classname);
-				},
-				removeIcon: function(key, classname)
-				{
-					this.button.get(key).removeClass('re-' + classname);
-				},
-				setAwesome: function(key, name)
-				{
-					var $button = this.button.get(key);
-					$button.removeClass('redactor-btn-image').addClass('fa-redactor-btn');
-					$button.html('<i class="fa ' + name + '"></i>');
-				},
-				addCallback: function($btn, callback)
-				{
-					if ($btn == "buffer") return;
-
-					var type = (callback == 'dropdown') ? 'dropdown' : 'func';
-					var key = $btn.attr('rel');
-					$btn.on('touchstart click', $.proxy(function(e)
-					{
-						if ($btn.hasClass('redactor-button-disabled')) return false;
-						this.button.onClick(e, key, type, callback);
-
-					}, this));
-				},
-				addDropdown: function($btn, dropdown)
-				{
-					$btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
-
-					var key = $btn.attr('rel');
-					this.button.addCallback($btn, 'dropdown');
-
-					var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">');
-					$btn.data('dropdown', $dropdown);
-
-					// build dropdown
-					if (dropdown) this.dropdown.build(key, $dropdown, dropdown);
-
-					return $dropdown;
-				},
-				add: function(key, title)
-				{
-					if (!this.opts.toolbar) return;
-
-					if (this.button.isMobileUndoRedo(key)) return "buffer";
-
-					var btn = this.button.build(key, { title: title });
-					btn.addClass('redactor-btn-image');
-
-					this.$toolbar.append($('<li>').append(btn));
-
-					return btn;
-				},
-				addFirst: function(key, title)
-				{
-					if (!this.opts.toolbar) return;
-
-					if (this.button.isMobileUndoRedo(key)) return "buffer";
-
-					var btn = this.button.build(key, { title: title });
-					btn.addClass('redactor-btn-image');
-					this.$toolbar.prepend($('<li>').append(btn));
-
-					return btn;
-				},
-				addAfter: function(afterkey, key, title)
-				{
-					if (!this.opts.toolbar) return;
-
-					if (this.button.isMobileUndoRedo(key)) return "buffer";
-
-					var btn = this.button.build(key, { title: title });
-					btn.addClass('redactor-btn-image');
-					var $btn = this.button.get(afterkey);
-
-					if ($btn.length !== 0) $btn.parent().after($('<li>').append(btn));
-					else this.$toolbar.append($('<li>').append(btn));
-
-					return btn;
-				},
-				addBefore: function(beforekey, key, title)
-				{
-					if (!this.opts.toolbar) return;
-
-					if (this.button.isMobileUndoRedo(key)) return "buffer";
-
-					var btn = this.button.build(key, { title: title });
-					btn.addClass('redactor-btn-image');
-					var $btn = this.button.get(beforekey);
-
-					if ($btn.length !== 0) $btn.parent().before($('<li>').append(btn));
-					else this.$toolbar.append($('<li>').append(btn));
-
-					return btn;
-				},
-				remove: function(key)
-				{
-					this.button.get(key).remove();
-				},
-				isMobileUndoRedo: function(key)
-				{
-					return (key == "undo" || key == "redo") && !this.utils.isDesktop();
-				}
-			};
-		},
-		caret: function()
-		{
-			return {
-				setStart: function(node)
-				{
-					// inline tag
-					if (!this.utils.isBlock(node))
-					{
-						var space = this.utils.createSpaceElement();
-
-						$(node).prepend(space);
-						this.caret.setEnd(space);
-					}
-					else
-					{
-						this.caret.set(node, 0, node, 0);
-					}
-				},
-				setEnd: function(node)
-				{
-					node = node[0] || node;
-					if (node.lastChild.nodeType == 1)
-					{
-						return this.caret.setAfter(node.lastChild);
-					}
-
-					this.caret.set(node, 1, node, 1);
-
-				},
-				set: function(orgn, orgo, focn, foco)
-				{
-					// focus
-					// disabled in 10.0.7
-					// if (!this.utils.browser('msie')) this.$editor.focus();
-
-					orgn = orgn[0] || orgn;
-					focn = focn[0] || focn;
-
-					if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
-					{
-						orgn.innerHTML = this.opts.invisibleSpace;
-					}
-
-					if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
-					{
-						var parent = $(this.opts.emptyHtml)[0];
-						$(orgn).replaceWith(parent);
-						orgn = parent;
-						focn = orgn;
-					}
-
-					this.selection.get();
-
-					try
-					{
-						this.range.setStart(orgn, orgo);
-						this.range.setEnd(focn, foco);
-					}
-					catch (e) {}
-
-					this.selection.addRange();
-				},
-				setAfter: function(node)
-				{
-					try
-					{
-						var tag = $(node)[0].tagName;
-
-						// inline tag
-						if (tag != 'BR' && !this.utils.isBlock(node))
-						{
-							var space = this.utils.createSpaceElement();
-
-							$(node).after(space);
-							this.caret.setEnd(space);
-						}
-						else
-						{
-							if (tag != 'BR' && this.utils.browser('msie'))
-							{
-								this.caret.setStart($(node).next());
-							}
-							else
-							{
-								this.caret.setAfterOrBefore(node, 'after');
-							}
-						}
-					}
-					catch (e)
-					{
-						var space = this.utils.createSpaceElement();
-						$(node).after(space);
-						this.caret.setEnd(space);
-					}
-				},
-				setBefore: function(node)
-				{
-					// block tag
-					if (this.utils.isBlock(node))
-					{
-						this.caret.setEnd($(node).prev());
-					}
-					else
-					{
-						this.caret.setAfterOrBefore(node, 'before');
-					}
-				},
-				setAfterOrBefore: function(node, type)
-				{
-					// focus
-					if (!this.utils.browser('msie')) this.$editor.focus();
-
-					node = node[0] || node;
-
-					this.selection.get();
-
-					if (type == 'after')
-					{
-						try {
-
-							this.range.setStartAfter(node);
-							this.range.setEndAfter(node);
-						}
-						catch (e) {}
-					}
-					else
-					{
-						try {
-							this.range.setStartBefore(node);
-							this.range.setEndBefore(node);
-						}
-						catch (e) {}
-					}
-
-
-					this.range.collapse(false);
-					this.selection.addRange();
-				},
-				getOffsetOfElement: function(node)
-				{
-					node = node[0] || node;
-
-					this.selection.get();
-
-					var cloned = this.range.cloneRange();
-					cloned.selectNodeContents(node);
-					cloned.setEnd(this.range.endContainer, this.range.endOffset);
-
-					return $.trim(cloned.toString()).length;
-				},
-				getOffset: function()
-				{
-					var offset = 0;
-				    var sel = window.getSelection();
-
-				    if (sel.rangeCount > 0)
-				    {
-				        var range = window.getSelection().getRangeAt(0);
-				        var caretRange = range.cloneRange();
-				        caretRange.selectNodeContents(this.$editor[0]);
-				        caretRange.setEnd(range.endContainer, range.endOffset);
-				        offset = caretRange.toString().length;
-				    }
-
-					return offset;
-				},
-				setOffset: function(start, end)
-				{
-					if (typeof end == 'undefined') end = start;
-					if (!this.focus.isFocused()) this.focus.setStart();
-
-					var sel = this.selection.get();
-					var node, offset = 0;
-					var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
-
-					while (node = walker.nextNode())
-					{
-						offset += node.nodeValue.length;
-						if (offset > start)
-						{
-							this.range.setStart(node, node.nodeValue.length + start - offset);
-							start = Infinity;
-						}
-
-						if (offset >= end)
-						{
-							this.range.setEnd(node, node.nodeValue.length + end - offset);
-							break;
-						}
-					}
-
-					this.range.collapse(false);
-					this.selection.addRange();
-				},
-				// deprecated
-				setToPoint: function(start, end)
-				{
-					this.caret.setOffset(start, end);
-				},
-				getCoords: function()
-				{
-					return this.caret.getOffset();
-				}
-			};
-		},
-		clean: function()
-		{
-			return {
-				onSet: function(html)
-				{
-					html = this.clean.savePreCode(html);
-
-					// convert script tag
-					html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" style="display: none;" $1>$2</pre>');
-
-					// replace dollar sign to entity
-					html = html.replace(/\$/g, '&#36;');
-
-					// replace special characters in links
-					html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1&reg$2">');
-
-					if (this.opts.replaceDivs && !this.opts.linebreaks) html = this.clean.replaceDivs(html);
-					if (this.opts.linebreaks)  html = this.clean.replaceParagraphsToBr(html);
-
-					// save form tag
-					html = this.clean.saveFormTags(html);
-
-					// convert font tag to span
-					var $div = $('<div>');
-					$div.html(html);
-					var fonts = $div.find('font[style]');
-					if (fonts.length !== 0)
-					{
-						fonts.replaceWith(function()
-						{
-							var $el = $(this);
-							var $span = $('<span>').attr('style', $el.attr('style'));
-							return $span.append($el.contents());
-						});
-
-						html = $div.html();
-					}
-
-					$div.remove();
-
-					// remove font tag
-					html = html.replace(/<font(.*?)>/gi, '');
-					html = html.replace(/<\/font>/gi, '');
-
-					// tidy html
-					html = this.tidy.load(html);
-
-					// paragraphize
-					if (this.opts.paragraphize) html = this.paragraphize.load(html);
-
-					// verified
-					html = this.clean.setVerified(html);
-
-					// convert inline tags
-					html = this.clean.convertInline(html);
-
-					html = html.replace(/&amp;/g, '&');
-
-					return html;
-				},
-				onSync: function(html)
-				{
-					// remove spaces
-					html = html.replace(/\u200B/g, '');
-					html = html.replace(/&#x200b;/gi, '');
-
-					if (this.opts.cleanSpaces)
-					{
-						html = html.replace(/&nbsp;/gi, ' ');
-					}
-
-					if (html.search(/^<p>(||\s||<br\s?\/?>||&nbsp;)<\/p>$/i) != -1)
-					{
-						return '';
-					}
-
-					// reconvert script tag
-					html = html.replace(/<pre class="redactor-script-tag" style="display: none;"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>');
-
-					// restore form tag
-					html = this.clean.restoreFormTags(html);
-
-					var chars = {
-						'\u2122': '&trade;',
-						'\u00a9': '&copy;',
-						'\u2026': '&hellip;',
-						'\u2014': '&mdash;',
-						'\u2010': '&dash;'
-					};
-					// replace special characters
-					$.each(chars, function(i,s)
-					{
-						html = html.replace(new RegExp(i, 'g'), s);
-					});
-
-					// remove last br in FF
-					if (this.utils.browser('mozilla'))
-					{
-						html = html.replace(/<br\s?\/?>$/gi, '');
-					}
-
-					// remove br in|of li tags
-					html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
-					html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
-
-					// remove empty attributes
-					html = html.replace(/<(.*?)rel="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
-					html = html.replace(/<(.*?)style="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
-					html = html.replace(/="">/gi, '>');
-					html = html.replace(/""">/gi, '">');
-					html = html.replace(/"">/gi, '">');
-
-					// remove verified
-					html = html.replace(/<div(.*?)data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
-					html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>');
-
-					var $div = $("<div/>").html($.parseHTML(html, document, true));
-					$div.find("span").removeAttr("rel");
-
-					$div.find('pre .redactor-invisible-space').each(function()
-					{
-						$(this).contents().unwrap();
-					});
-
-					html = $div.html();
-
-					// remove rel attribute from img
-					html = html.replace(/<img(.*?[^>])rel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
-					html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1');
-
-					html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
-
-					// remove image resize
-					html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
-					html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, '');
-					html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
-
-					// remove font tag
-					html = html.replace(/<font(.*?)>/gi, '');
-					html = html.replace(/<\/font>/gi, '');
-
-					// tidy html
-					html = this.tidy.load(html);
-
-					// link nofollow
-					if (this.opts.linkNofollow)
-					{
-						html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
-						html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
-					}
-
-					// reconvert inline
-					html = html.replace(/\sdata-redactor-(tag|class|style)="(.*?[^>])"/gi, '');
-					html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
-					html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
-
-					html = html.replace(/&amp;/g, '&');
-
-					return html;
-				},
-				onPaste: function(html, setMode)
-				{
-					html = $.trim(html);
-					html = html.replace(/\$/g, '&#36;');
-
-					// convert dirty spaces
-					html = html.replace(/<span class="s[0-9]">/gi, '<span>');
-					html = html.replace(/<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ');
-					html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
-					html = html.replace(/<span[^>]*>(\s|&nbsp;)<\/span>/gi, ' ');
-
-					if (this.opts.pastePlainText)
-					{
-						return this.clean.getPlainText(html);
-					}
-
-					if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
-					{
-						if (this.utils.isCurrentOrParent(['FIGCAPTION', 'A']))
-						{
-							return this.clean.getPlainText(html, false);
-						}
-
-						if (this.utils.isCurrentOrParent('PRE'))
-						{
-							html = html.replace(/”/g, '"');
-							html = html.replace(/“/g, '"');
-							html = html.replace(/‘/g, '\'');
-							html = html.replace(/’/g, '\'');
-
-							return this.clean.getPreCode(html);
-						}
-
-						if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']))
-						{
-							html = this.clean.getOnlyImages(html);
-
-							if (!this.utils.browser('msie'))
-							{
-								var block = this.selection.getBlock();
-								if (block && block.tagName == 'P')
-								{
-									html = html.replace(/<img(.*?)>/gi, '<p><img$1></p>');
-								}
-							}
-
-							return html;
-						}
-
-						if (this.utils.isCurrentOrParent(['TD']))
-						{
-							html = this.clean.onPasteTidy(html, 'td');
-
-							if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
-
-							html = this.clean.replaceDivsToBr(html);
-
-							return html;
-						}
-
-
-						if (this.utils.isCurrentOrParent(['LI']))
-						{
-							return this.clean.onPasteTidy(html, 'li');
-						}
-					}
-
-
-					html = this.clean.isSingleLine(html, setMode);
-
-					if (!this.clean.singleLine)
-					{
-						if (this.opts.linebreaks)  html = this.clean.replaceParagraphsToBr(html);
-						if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
-
-						html = this.clean.saveFormTags(html);
-					}
-
-
-					html = this.clean.onPasteWord(html);
-					html = this.clean.onPasteExtra(html);
-
-					html = this.clean.onPasteTidy(html, 'all');
-
-
-					// paragraphize
-					if (!this.clean.singleLine && this.opts.paragraphize)
-					{
-						html = this.paragraphize.load(html);
-					}
-
-					html = this.clean.removeDirtyStyles(html);
-					html = this.clean.onPasteRemoveSpans(html);
-					html = this.clean.onPasteRemoveEmpty(html);
-
-
-					html = this.clean.convertInline(html);
-
-					return html;
-				},
-				onPasteWord: function(html)
-				{
-					// comments
-					html = html.replace(/<!--[\s\S]*?-->/gi, '');
-
-					// style
-					html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
-
-					// op
-					html = html.replace(/<o\:p[^>]*>[\s\S]*?<\/o\:p>/gi, '');
-
-					if (html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i))
-					{
-						// comments
-						html = html.replace(/<!--[\s\S]+?-->/gi, '');
-
-						// scripts
-						html = html.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '');
-
-						// Convert <s> into <strike>
-						html = html.replace(/<(\/?)s>/gi, "<$1strike>");
-
-						// Replace nbsp entites to char since it's easier to handle
-						html = html.replace(/ /gi, ' ');
-
-						// Convert <span style="mso-spacerun:yes">___</span> to string of alternating
-						// breaking/non-breaking spaces of same length
-						html = html.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, function(str, spaces) {
-							return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : '';
-						});
-
-						html = this.clean.onPasteIeFixLinks(html);
-
-						// shapes
-						html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
-						html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
-
-						// lists
-						var $div = $("<div/>").html(html);
-
-						var lastList = false;
-						var lastLevel = 1;
-						var listsIds = [];
-
-						$div.find("p[style]").each(function()
-						{
-							var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);
-
-							if (matches)
-							{
-								var currentList = parseInt(matches[1]);
-								var currentLevel = parseInt(matches[2]);
-								var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul";
-
-								var $li = $("<li/>").html($(this).html());
-
-								$li.html($li.html().replace(/^([\w\.]+)</, '<'));
-								$li.find("span:first").remove();
-
-								if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1)
-								{
-									var $list = $("<" + listType + "/>").attr({"data-level": currentLevel,
-																			   "data-list": currentList})
-																	  .html($li);
-
-									$(this).replaceWith($list);
-
-									lastList = currentList;
-									listsIds.push(currentList);
-								}
-								else
-								{
-									if (currentLevel > lastLevel)
-									{
-										var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]');
-
-										var $lastList = $prevList;
-
-										for(var i = lastLevel; i < currentLevel; i++)
-										{
-											$list = $("<" + listType + "/>");
-
-											$list.appendTo($lastList.find("li").last());
-
-											$lastList = $list;
-										}
-
-										$lastList.attr({"data-level": currentLevel,
-														"data-list": currentList})
-												 .html($li);
-
-									}
-									else
-									{
-										var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last();
-
-										$prevList.append($li);
-									}
-
-									lastLevel = currentLevel;
-									lastList = currentList;
-
-									$(this).remove();
-								}
-							}
-						});
-
-						$div.find('[data-level][data-list]').removeAttr('data-level data-list');
-						html = $div.html();
-
-						// remove ms word's bullet
-						html = html.replace(/·/g, '');
-						html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
-
-						// classes
-						html = html.replace(/ class=\"(mso[^\"]*)\"/gi,	"");
-						html = html.replace(/ class=(mso\w+)/gi, "");
-
-						// remove ms word tags
-						html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
-
-						// ms word break lines
-						html = html.replace(/\n/g, ' ');
-
-						// ms word lists break lines
-						html = html.replace(/<p>\n?<li>/gi, '<li>');
-					}
-
-					return html;
-				},
-				onPasteExtra: function(html)
-				{
-					// remove google docs markers
-					html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
-					html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
-
-					// google docs styles
-			 		html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
-			 		html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
-					html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
-					html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
-
-					html = html.replace(/<img>/gi, '');
-					html = html.replace(/\n{3,}/gi, '\n');
-					html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
-
-					// remove dirty p
-					html = html.replace(/<p><p>/gi, '<p>');
-					html = html.replace(/<\/p><\/p>/gi, '</p>');
-					html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
-					html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
-
-					// remove space between paragraphs
-					html = html.replace(/<\/p>\s<p/gi, '<\/p><p');
-
-					// remove safari local images
-					html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
-
-					// bullets
-					html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
-
-					// FF fix
-					if (this.utils.browser('mozilla'))
-					{
-						html = html.replace(/<br\s?\/?>$/gi, '');
-					}
-
-					return html;
-				},
-				onPasteTidy: function(html, type)
-				{
-					// remove all tags except these
-					var tags = ['span', 'a', 'pre', 'blockquote', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'address', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
-								'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'iframe', 'embed', 'param', 'object', 'img', 'table',
-								'td', 'th', 'tr', 'tbody', 'tfoot', 'thead', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-					var tagsEmpty = false;
-					var attrAllowed =  [
-							['a', '*'],
-							['img', ['src', 'alt']],
-							['span', ['class', 'rel', 'data-verified']],
-							['iframe', '*'],
-							['video', '*'],
-							['audio', '*'],
-							['embed', '*'],
-							['object', '*'],
-							['param', '*'],
-							['source', '*']
-						];
-
-					if (type == 'all')
-					{
-						tagsEmpty = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-						attrAllowed =  [
-							['table', 'class'],
-							['td', ['colspan', 'rowspan']],
-							['a', '*'],
-							['img', ['src', 'alt', 'data-redactor-inserted-image']],
-							['span', ['class', 'rel', 'data-verified']],
-							['iframe', '*'],
-							['video', '*'],
-							['audio', '*'],
-							['embed', '*'],
-							['object', '*'],
-							['param', '*'],
-							['source', '*']
-						];
-					}
-					else if (type == 'td')
-					{
-						// remove all tags except these and remove all table tags: tr, td etc
-						tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
-								'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
-
-					}
-					else if (type == 'li')
-					{
-						// only inline tags and ul, ol, li
-						tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br',
-								'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img'];
-					}
-
-					var options = {
-						deniedTags: (this.opts.deniedTags) ? this.opts.deniedTags : false,
-						allowedTags: (this.opts.allowedTags) ? this.opts.allowedTags : tags,
-						removeComments: true,
-						removePhp: true,
-						removeAttr: (this.opts.removeAttr) ? this.opts.removeAttr : false,
-						allowedAttr: (this.opts.allowedAttr) ? this.opts.allowedAttr : attrAllowed,
-						removeEmpty: tagsEmpty
-					};
-
-					return this.tidy.load(html, options);
-				},
-				onPasteRemoveEmpty: function(html)
-				{
-					html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, '');
-
-					// remove br in the end
-					if (!this.opts.linebreaks) html = html.replace(/<br>$/i, '');
-
-					return html;
-				},
-				onPasteRemoveSpans: function(html)
-				{
-					html = html.replace(/<span>(.*?)<\/span>/gi, '$1');
-					html = html.replace(/<span[^>]*>\s|&nbsp;<\/span>/gi, ' ');
-
-					return html;
-				},
-				onPasteIeFixLinks: function(html)
-				{
-					if (!this.utils.browser('msie')) return html;
-
-					var tmp = $.trim(html);
-					if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) === 0)
-					{
-						html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
-					}
-
-					return html;
-				},
-				isSingleLine: function(html, setMode)
-				{
-					this.clean.singleLine = false;
-
-					if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
-					{
-						var blocks = this.opts.blockLevelElements.join('|').replace('P|', '').replace('DIV|', '');
-
-						var matchBlocks = html.match(new RegExp('</(' + blocks + ')>', 'gi'));
-						var matchContainers = html.match(/<\/(p|div)>/gi);
-
-						if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
-						{
-							var matchBR = html.match(/<br\s?\/?>/gi);
-							//var matchIMG = html.match(/<img(.*?[^>])>/gi);
-							if (!matchBR)
-							{
-								this.clean.singleLine = true;
-								html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
-							}
-						}
-					}
-
-					return html;
-				},
-				stripTags: function(input, allowed)
-				{
-				    allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
-				    var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
-
-				    return input.replace(tags, function ($0, $1) {
-				        return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
-				    });
-				},
-				savePreCode: function(html)
-				{
-					html = this.clean.savePreFormatting(html);
-					html = this.clean.saveCodeFormatting(html);
-
-					html = this.clean.restoreSelectionMarker(html);
-
-					return html;
-				},
-				savePreFormatting: function(html)
-				{
-					var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);
-
-					if (pre !== null)
-					{
-						$.each(pre, $.proxy(function(i,s)
-						{
-							var arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i);
-
-							arr[2] = arr[2].replace(/<br\s?\/?>/g, '\n');
-							arr[2] = arr[2].replace(/&nbsp;/g, ' ');
-
-							if (this.opts.preSpaces)
-							{
-								arr[2] = arr[2].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
-							}
-
-							arr[2] = this.clean.encodeEntities(arr[2]);
-
-							// $ fix
-							arr[2] = arr[2].replace(/\$/g, '&#36;');
-
-							html = html.replace(s, '<pre' + arr[1] + '>' + arr[2] + '</pre>');
-
-						}, this));
-					}
-
-					return html;
-				},
-				saveCodeFormatting: function(html)
-				{
-					var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);
-
-					if (code !== null)
-					{
-						$.each(code, $.proxy(function(i,s)
-						{
-							var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i);
-
-							arr[2] = arr[2].replace(/&nbsp;/g, ' ');
-							arr[2] = this.clean.encodeEntities(arr[2]);
-							arr[2] = arr[2].replace(/\$/g, '&#36;');
-
-							html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>');
-						}, this));
-					}
-
-					return html;
-				},
-				restoreSelectionMarker: function(html)
-				{
-					html = html.replace(/&lt;span id=&quot;selection-marker-([0-9])&quot; class=&quot;redactor-selection-marker&quot; data-verified=&quot;redactor&quot;&gt;​&lt;\/span&gt;/g, '<span id="selection-marker-$1" class="redactor-selection-marker" data-verified="redactor">​</span>');
-
-					return html;
-				},
-				getTextFromHtml: function(html)
-				{
-					html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
-
-					var tmp = document.createElement('div');
-					tmp.innerHTML = html;
-					html = tmp.textContent || tmp.innerText;
-
-					return $.trim(html);
-				},
-				getPlainText: function(html, paragraphize)
-				{
-					html = this.clean.getTextFromHtml(html);
-					html = html.replace(/\n\s*\n/g, "\n");
-					html = html.replace(/\n\n/g, "\n");
-					html = html.replace(/\n/g, '<br />');
-
-					if (this.opts.paragraphize && typeof paragraphize == 'undefined' && !this.utils.browser('mozilla'))
-					{
-						html = this.paragraphize.load(html);
-					}
-
-					return html;
-				},
-				getPreCode: function(html)
-				{
-					html = html.replace(/<img(.*?) style="(.*?)"(.*?[^>])>/gi, '<img$1$3>');
-					html = html.replace(/<img(.*?)>/gi, '&lt;img$1&gt;');
-					html = this.clean.getTextFromHtml(html);
-
-					if (this.opts.preSpaces)
-					{
-						html = html.replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
-					}
-
-					html = this.clean.encodeEntities(html);
-
-					return html;
-				},
-				getOnlyImages: function(html)
-				{
-					html = html.replace(/<img(.*?)>/gi, '[img$1]');
-
-					// remove all tags
-					html = html.replace(/<([Ss]*?)>/gi, '');
-
-					html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
-
-					return html;
-				},
-				getOnlyLinksAndImages: function(html)
-				{
-					html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
-					html = html.replace(/<img(.*?)>/gi, '[img$1]');
-
-					// remove all tags
-					html = html.replace(/<(.*?)>/gi, '');
-
-					html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
-					html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
-
-					return html;
-				},
-				encodeEntities: function(str)
-				{
-					str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
-					return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
-				},
-				removeDirtyStyles: function(html)
-				{
-					if (this.utils.browser('msie')) return html;
-
-					var div = document.createElement('div');
-					div.innerHTML = html;
-
-					this.clean.clearUnverifiedRemove($(div));
-
-					html = div.innerHTML;
-					$(div).remove();
-
-					return html;
-				},
-				clearUnverified: function()
-				{
-					if (this.utils.browser('msie')) return;
-
-					this.clean.clearUnverifiedRemove(this.$editor);
-
-					var headers = this.$editor.find('h1, h2, h3, h4, h5, h6');
-					headers.find('span').removeAttr('style');
-					headers.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
-
-					this.code.sync();
-				},
-				clearUnverifiedRemove: function($editor)
-				{
-					$editor.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
-					$editor.find('span').not('[data-verified="redactor"]').removeAttr('style');
-
-					$editor.find('span[data-verified="redactor"], img[data-verified="redactor"]').each(function(i, s)
-					{
-						var $s = $(s);
-						$s.attr('style', $s.attr('rel'));
-					});
-
-				},
-				cleanEmptyParagraph: function()
-				{
-
-				},
-				setVerified: function(html)
-				{
-					if (this.utils.browser('msie')) return html;
-
-					html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">');
-					html = html.replace(new RegExp('<span(.*?[^>])>', 'gi'), '<span$1 data-verified="redactor">');
-
-					var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi'));
-
-					if (matches)
-					{
-						var len = matches.length;
-						for (var i = 0; i < len; i++)
-						{
-							try {
-
-								var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
-								html = html.replace(matches[i], newTag);
-
-							}
-							catch (e) {}
-						}
-					}
-
-					return html;
-				},
-				convertInline: function(html)
-				{
-					var $div = $('<div />').html(html);
-
-					var tags = this.opts.inlineTags;
-					tags.push('span');
-
-					$div.find(tags.join(',')).each(function()
-					{
-						var $el = $(this);
-						var tag = this.tagName.toLowerCase();
-						$el.attr('data-redactor-tag', tag);
-
-						if (tag == 'span')
-						{
-							if ($el.attr('style')) $el.attr('data-redactor-style', $el.attr('style'));
-							else if ($el.attr('class')) $el.attr('data-redactor-class', $el.attr('class'));
-						}
-
-					});
-
-					html = $div.html();
-					$div.remove();
-
-					return html;
-				},
-				normalizeLists: function()
-				{
-					this.$editor.find('li').each(function(i,s)
-					{
-						var $next = $(s).next();
-						if ($next.length !== 0 && ($next[0].tagName == 'UL' || $next[0].tagName == 'OL'))
-						{
-							$(s).append($next);
-						}
-
-					});
-				},
-				removeSpaces: function(html)
-				{
-					html = html.replace(/\n/g, '');
-					html = html.replace(/[\t]*/g, '');
-					html = html.replace(/\n\s*\n/g, "\n");
-					html = html.replace(/^[\s\n]*/g, ' ');
-					html = html.replace(/[\s\n]*$/g, ' ');
-					html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
-					html = html.replace(/\n\n/g, "\n");
-					html = html.replace(/\u200B/g, '');
-
-					return html;
-				},
-				replaceDivs: function(html)
-				{
-					if (this.opts.linebreaks)
-					{
-						html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br />');
-						html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2<br />');
-					}
-					else
-					{
-						html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
-					}
-
-					html = html.replace(/<div(.*?[^>])>/gi, '');
-					html = html.replace(/<\/div>/gi, '');
-
-					return html;
-				},
-				replaceDivsToBr: function(html)
-				{
-					html = html.replace(/<div\s(.*?)>/gi, '<p>');
-					html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br /><br />');
-					html = html.replace(/<div>([\w\W]*?)<\/div>/gi, '$1<br /><br />');
-
-					return html;
-				},
-				replaceParagraphsToBr: function(html)
-				{
-					html = html.replace(/<p\s(.*?)>/gi, '<p>');
-					html = html.replace(/<p><br\s?\/?><\/p>/gi, '<br />');
-					html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br /><br />');
-					html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
-
-					return html;
-				},
-				saveFormTags: function(html)
-				{
-					return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
-				},
-				restoreFormTags: function(html)
-				{
-					return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
-				}
-			};
-		},
-		code: function()
-		{
-			return {
-				set: function(html)
-				{
-					html = $.trim(html.toString());
-
-					// clean
-					html = this.clean.onSet(html);
-
-
-					if (this.utils.browser('msie'))
-					{
-						html = html.replace(/<span(.*?)id="selection-marker-(1|2)"(.*?)><\/span>/gi, '');
-					}
-
-					this.$editor.html(html);
-					this.code.sync();
-
-					if (html !== '') this.placeholder.remove();
-
-					setTimeout($.proxy(this.buffer.add, this), 15);
-					if (this.start === false) this.observe.load();
-
-				},
-				get: function()
-				{
-					var code = this.$textarea.val();
-
-					if (this.opts.replaceDivs) code = this.clean.replaceDivs(code);
-					if (this.opts.linebreaks) code = this.clean.replaceParagraphsToBr(code);
-
-					// indent code
-					code = this.tabifier.get(code);
-
-					return code;
-				},
-				sync: function()
-				{
-					setTimeout($.proxy(this.code.startSync, this), 10);
-				},
-				startSync: function()
-				{
-					var html = this.$editor.html();
-
-					// is there a need to synchronize
-					if (this.code.syncCode && this.code.syncCode == html || (this.start && html == '' ))
-					{
-						// do not sync
-						return;
-					}
-
-					// save code
-					this.code.syncCode = html;
-
-					// before clean callback
-					html = this.core.setCallback('syncBefore', html);
-
-					// clean
-					html = this.clean.onSync(html);
-
-					// set code
-					this.$textarea.val(html);
-
-					// after sync callback
-					this.core.setCallback('sync', html);
-
-					if (this.start === false)
-					{
-						this.core.setCallback('change', html);
-					}
-
-					this.start = false;
-
-					if (this.autosave.html == false)
-					{
-						this.autosave.html = this.code.get();
-					}
-
-					if (this.opts.codemirror)
-					{
-						this.$textarea.next('.CodeMirror').each(function(i, el)
-						{
-							el.CodeMirror.setValue(html);
-						});
-					}
-
-					//autosave
-					this.autosave.onChange();
-					this.autosave.enable();
-				},
-				toggle: function()
-				{
-					if (this.opts.visual)
-					{
-						this.code.showCode();
-					}
-					else
-					{
-						this.code.showVisual();
-					}
-				},
-				showCode: function()
-				{
-					this.selection.save();
-
-					this.code.offset = this.caret.getOffset();
-					var scroll = $(window).scrollTop();
-
-					var	width = this.$editor.innerWidth(),
-						height = this.$editor.innerHeight();
-
-					this.$editor.hide();
-
-					var html = this.$textarea.val();
-
-					this.modified = this.clean.removeSpaces(html);
-
-					// indent code
-					html = this.tabifier.get(html);
-
-					// caret position sync
-					var start = 0, end = 0;
-					var $editorDiv = $("<div/>").append($.parseHTML(this.clean.onSync(this.$editor.html()), document, true));
-					var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
-
-					if ($selectionMarkers.length > 0)
-					{
-						var editorHtml = this.tabifier.get($editorDiv.html()).replace(/&amp;/g, '&');
-
-						if ($selectionMarkers.length == 1)
-						{
-							start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
-							end   = start;
-						}
-						else if ($selectionMarkers.length == 2)
-						{
-							start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
-							end	 = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
-						}
-					}
-
-					this.selection.removeMarkers();
-					this.$textarea.val(html);
-
-					if (this.opts.codemirror)
-					{
-						this.$textarea.next('.CodeMirror').each(function(i, el)
-						{
-							$(el).show();
-							el.CodeMirror.setValue(html);
-							el.CodeMirror.setSize('100%', height);
-							el.CodeMirror.refresh();
-
-							if (start == end)
-							{
-								el.CodeMirror.setCursor(el.CodeMirror.posFromIndex(start).line, el.CodeMirror.posFromIndex(end).ch);
-							}
-							else
-							{
-								el.CodeMirror.setSelection({line: el.CodeMirror.posFromIndex(start).line,
-															ch: el.CodeMirror.posFromIndex(start).ch},
-														  {line: el.CodeMirror.posFromIndex(end).line,
-														   ch:  el.CodeMirror.posFromIndex(end).ch});
-							}
-
-							el.CodeMirror.focus();
-						});
-					}
-					else
-					{
-						this.$textarea.height(height).show().focus();
-						this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
-
-						$(window).scrollTop(scroll);
-
-						if (this.$textarea[0].setSelectionRange)
-						{
-							this.$textarea[0].setSelectionRange(start, end);
-						}
-
-						this.$textarea[0].scrollTop = 0;
-					}
-
-					this.opts.visual = false;
-
-					this.button.setInactiveInCode();
-					this.button.setActive('html');
-					this.core.setCallback('source', html);
-				},
-				showVisual: function()
-				{
-					var html;
-
-					if (this.opts.visual) return;
-
-					var start = 0, end = 0;
-
-					if (this.opts.codemirror)
-					{
-						var selection;
-
-						this.$textarea.next('.CodeMirror').each(function(i, el)
-						{
-							selection = el.CodeMirror.listSelections();
-
-							start = el.CodeMirror.indexFromPos(selection[0].anchor);
-							end = el.CodeMirror.indexFromPos(selection[0].head);
-
-							html = el.CodeMirror.getValue();
-						});
-					}
-					else
-					{
-						start = this.$textarea.get(0).selectionStart;
-						end = this.$textarea.get(0).selectionEnd;
-
-						html = this.$textarea.hide().val();
-					}
-
-					// if selection starts from end
-					if (start > end && end > 0)
-					{
-						var tempStart = end;
-						var tempEnd = start;
-
-						start = tempStart;
-						end = tempEnd;
-					}
-
-					start = this.code.enlargeOffset(html, start);
-					end = this.code.enlargeOffset(html, end);
-
-					html = html.substr(0, start) + this.selection.getMarkerAsHtml(1) + html.substr(start);
-
-					if (end > start)
-					{
-						var markerLength = this.selection.getMarkerAsHtml(1).toString().length;
-
-						html = html.substr(0, end + markerLength) + this.selection.getMarkerAsHtml(2) + html.substr(end + markerLength);
-					}
-
-
-
-					if (this.modified !== this.clean.removeSpaces(html))
-					{
-						this.code.set(html);
-
-					}
-
-					if (this.opts.codemirror)
-					{
-						this.$textarea.next('.CodeMirror').hide();
-					}
-
-					this.$editor.show();
-
-					if (!this.utils.isEmpty(html))
-					{
-						this.placeholder.remove();
-					}
-
-					this.selection.restore();
-
-					this.$textarea.off('keydown.redactor-textarea-indenting');
-
-					this.button.setActiveInVisual();
-					this.button.setInactive('html');
-					this.observe.load();
-					this.opts.visual = true;
-					this.core.setCallback('visual', html);
-				},
-				textareaIndenting: function(e)
-				{
-					if (e.keyCode !== 9) return true;
-
-					var $el = this.$textarea;
-					var start = $el.get(0).selectionStart;
-					$el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
-					$el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
-
-					return false;
-				},
-				enlargeOffset: function(html, offset)
-				{
-					var htmlLength = html.length;
-					var c = 0;
-
-					if (html[offset] == '>')
-					{
-						c++;
-					}
-					else
-					{
-						for(var i = offset; i <= htmlLength; i++)
-						{
-							c++;
-
-							if (html[i] == '>')
-							{
-								break;
-							}
-							else if (html[i] == '<' || i == htmlLength)
-							{
-								c = 0;
-								break;
-							}
-						}
-					}
-
-					return offset + c;
-				}
-			};
-		},
-		core: function()
-		{
-			return {
-				getObject: function()
-				{
-					return $.extend({}, this);
-				},
-				getEditor: function()
-				{
-					return this.$editor;
-				},
-				getBox: function()
-				{
-					return this.$box;
-				},
-				getElement: function()
-				{
-					return this.$element;
-				},
-				getTextarea: function()
-				{
-					return this.$textarea;
-				},
-				getToolbar: function()
-				{
-					return (this.$toolbar) ? this.$toolbar : false;
-				},
-				addEvent: function(name)
-				{
-					this.core.event = name;
-				},
-				getEvent: function()
-				{
-					return this.core.event;
-				},
-				setCallback: function(type, e, data)
-				{
-					var eventName = type + 'Callback';
-					var eventNamespace = 'redactor';
-					var callback = this.opts[eventName];
-
-					if (this.$textarea)
-					{
-						var returnValue = false;
-						var events = $._data(this.$textarea[0], 'events');
-
-						if (typeof events != 'undefined' && typeof events[eventName] != 'undefined')
-						{
-							$.each(events[eventName], $.proxy(function(key, value)
-							{
-								if (value['namespace'] == eventNamespace)
-								{
-									var data = (typeof data == 'undefined') ? [e] : [e, data];
-
-									returnValue = (typeof data == 'undefined') ? value.handler.call(this, e) : value.handler.call(this, e, data);
-								}
-							}, this));
-						}
-
-						if (returnValue) return returnValue;
-					}
-
-					if ($.isFunction(callback))
-					{
-						return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
-					}
-					else
-					{
-						return (typeof data == 'undefined') ? e : data;
-					}
-				},
-				destroy: function()
-				{
-					this.opts.destroyed = true;
-
-					this.core.setCallback('destroy');
-
-					// off events and remove data
-					this.$element.off('.redactor').removeData('redactor');
-					this.$editor.off('.redactor');
-
-					$(document).off('mousedown.redactor-blur.' + this.uuid);
-					$(document).off('mousedown.redactor.' + this.uuid);
-					$(document).off('click.redactor-image-delete.' + this.uuid);
-					$(document).off('click.redactor-image-resize-hide.' + this.uuid);
-					$(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid);
-					$("body").off('scroll.redactor.' + this.uuid);
-					$(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid);
-
-					// common
-					this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
-					this.$editor.removeAttr('contenteditable');
-
-					var html = this.code.get();
-
-					if (this.opts.toolbar)
-					{
-						// dropdowns off
-						this.$toolbar.find('a').each(function()
-						{
-							var $el = $(this);
-							if ($el.data('dropdown'))
-							{
-								$el.data('dropdown').remove();
-								$el.data('dropdown', {});
-							}
-						});
-					}
-
-					if (this.build.isTextarea())
-					{
-						this.$box.after(this.$element);
-						this.$box.remove();
-						this.$element.val(html).show();
-					}
-					else
-					{
-						this.$box.after(this.$editor);
-						this.$box.remove();
-						this.$element.html(html).show();
-					}
-
-					// paste box
-					if (this.$pasteBox) this.$pasteBox.remove();
-
-					// modal
-					if (this.$modalBox) this.$modalBox.remove();
-					if (this.$modalOverlay) this.$modalOverlay.remove();
-
-					// buttons tooltip
-					$('.redactor-toolbar-tooltip-' + this.uuid).remove();
-
-					// autosave
-					clearInterval(this.autosaveInterval);
-				}
-			};
-		},
-		dropdown: function()
-		{
-			return {
-				build: function(name, $dropdown, dropdownObject)
-				{
-					if (name == 'formatting' && this.opts.formattingAdd)
-					{
-						$.each(this.opts.formattingAdd, $.proxy(function(i,s)
-						{
-							var name = s.tag,
-								func;
-
-							if (typeof s['class'] != 'undefined')
-							{
-								name = name + '-' + s['class'];
-							}
-
-							s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
-
-							if (typeof s.func !== "undefined")
-							{
-								func = s.func;
-							}
-							else
-							{
-								func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
-							}
-
-							if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
-
-							this.formatting[name] = {
-								tag: s.tag,
-								style: s.style,
-								'class': s['class'],
-								attr: s.attr,
-								data: s.data,
-								clear: s.clear
-							};
-
-							dropdownObject[name] = {
-								func: func,
-								title: s.title
-							};
-
-						}, this));
-					}
-
-					$.each(dropdownObject, $.proxy(function(btnName, btnObject)
-					{
-						var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button">' + btnObject.title + '</a>');
-						if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
-
-						$item.on('click', $.proxy(function(e)
-						{
-							e.preventDefault();
-
-							var type = 'func';
-							var callback = btnObject.func;
-							if (btnObject.command)
-							{
-								type = 'command';
-								callback = btnObject.command;
-							}
-							else if (btnObject.dropdown)
-							{
-								type = 'dropdown';
-								callback = btnObject.dropdown;
-							}
-
-							if ($(e.target).hasClass('redactor-dropdown-link-inactive')) return;
-
-							this.button.onClick(e, btnName, type, callback);
-							this.dropdown.hideAll();
-
-						}, this));
-
-						this.observe.addDropdown($item, btnName, btnObject);
-
-						$dropdown.append($item);
-
-					}, this));
-				},
-				show: function(e, key)
-				{
-					if (!this.opts.visual)
-					{
-						e.preventDefault();
-						return false;
-					}
-
-					var $button = this.button.get(key);
-
-					// Always re-append it to the end of <body> so it always has the highest sub-z-index.
-					var $dropdown = $button.data('dropdown').appendTo(document.body);
-
-					if (this.opts.highContrast)
-					{
-						$dropdown.addClass("redactor-dropdown-contrast");
-					}
-
-					if ($button.hasClass('dropact'))
-					{
-						this.dropdown.hideAll();
-					}
-					else
-					{
-						this.dropdown.hideAll();
-						this.observe.dropdowns();
-
-						this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
-
-						this.button.setActive(key);
-
-						$button.addClass('dropact');
-
-						var keyPosition = $button.offset();
-
-						// fix right placement
-						var dropdownWidth = $dropdown.width();
-						if ((keyPosition.left + dropdownWidth) > $(document).width())
-						{
-							keyPosition.left = Math.max(0, keyPosition.left - dropdownWidth);
-						}
-
-						var left = keyPosition.left + 'px';
-						if (this.$toolbar.hasClass('toolbar-fixed-box'))
-						{
-							var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
-							var position = 'fixed';
-							if (this.opts.toolbarFixedTarget !== document)
-							{
-								top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
-								position = 'absolute';
-							}
-
-							$dropdown.css({ position: position, left: left, top: top + 'px' }).show();
-						}
-						else
-						{
-							var top = ($button.innerHeight() + keyPosition.top) + 'px';
-
-							$dropdown.css({ position: 'absolute', left: left, top: top }).show();
-						}
-
-						this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
-
-						this.$dropdown = $dropdown;
-					}
-
-
-					$(document).one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
-					this.$editor.one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
-					$(document).one('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this));
-
-					// disable scroll whan dropdown scroll
-					$dropdown.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this)).on('mouseout.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this));
-
-					e.stopPropagation();
-				},
-				closeHandler: function(e)
-				{
-					if (e.which != this.keyCode.ESC) return;
-
-					this.dropdown.hideAll();
-					this.$editor.focus();
-				},
-				hideAll: function()
-				{
-					this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
-
-					this.utils.enableBodyScroll();
-
-					$('.redactor-dropdown-' + this.uuid).hide();
-					$('.redactor-dropdown-link-selected').removeClass('redactor-dropdown-link-selected');
-
-
-					if (this.$dropdown)
-					{
-						this.$dropdown.off('.redactor-dropdown');
-						this.core.setCallback('dropdownHide', this.$dropdown);
-
-						this.$dropdown = false;
-					}
-				},
-				hide: function (e)
-				{
-					var $dropdown = $(e.target);
-
-					if (!$dropdown.hasClass('dropact') && !$dropdown.hasClass('redactor-dropdown-link-inactive'))
-					{
-						if ($dropdown.hasClass('redactor-dropdown'))
-						{
-							$dropdown.removeClass('dropact');
-							$dropdown.off('mouseover mouseout');
-						}
-
-						this.dropdown.hideAll();
-					}
-				}
-			};
-		},
-		file: function()
-		{
-			return {
-				show: function()
-				{
-					this.modal.load('file', this.lang.get('file'), 700);
-					this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
-
-					this.selection.save();
-
-					this.selection.get();
-					var text = this.sel.toString();
-
-					$('#redactor-filename').val(text);
-
-					this.modal.show();
-				},
-				insert: function(json, direct, e)
-				{
-					// error callback
-					if (typeof json.error != 'undefined')
-					{
-						this.modal.close();
-						this.selection.restore();
-						this.core.setCallback('fileUploadError', json);
-						return;
-					}
-
-					var link;
-					if (typeof json == 'string')
-					{
-						link = json;
-					}
-					else
-					{
-						var text = $('#redactor-filename').val();
-						if (typeof text == 'undefined' || text === '') text = json.filename;
-
-						link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
-					}
-
-					if (direct)
-					{
-						this.selection.removeMarkers();
-						var marker = this.selection.getMarker();
-						this.insert.nodeToCaretPositionFromPoint(e, marker);
-					}
-					else
-					{
-						this.modal.close();
-					}
-
-					this.selection.restore();
-					this.buffer.set();
-
-					this.insert.htmlWithoutClean(link);
-
-					if (typeof json == 'string') return;
-
-					var linkmarker = $(this.$editor.find('a#filelink-marker'));
-					if (linkmarker.length !== 0)
-					{
-						linkmarker.removeAttr('id').removeAttr('style');
-					}
-					else linkmarker = false;
-
-					this.core.setCallback('fileUpload', linkmarker, json);
-
-				}
-			};
-		},
-		focus: function()
-		{
-			return {
-				setStart: function()
-				{
-					this.$editor.focus();
-
-					var first = this.$editor.children().first();
-
-					if (first.length === 0) return;
-					if (first[0].length === 0 || first[0].tagName == 'BR' || first[0].nodeType == 3)
-					{
-						return;
-					}
-
-					if (first[0].tagName == 'UL' || first[0].tagName == 'OL')
-					{
-						var child = first.find('li').first();
-						if (!this.utils.isBlock(child) && child.text() === '')
-						{
-							// empty inline tag in li
-							this.caret.setStart(child);
-							return;
-						}
-					}
-
-					if (this.opts.linebreaks && !this.utils.isBlockTag(first[0].tagName))
-					{
-						this.selection.get();
-						this.range.setStart(this.$editor[0], 0);
-						this.range.setEnd(this.$editor[0], 0);
-						this.selection.addRange();
-
-						return;
-					}
-
-					// if node is tag
-					this.caret.setStart(first);
-				},
-				setEnd: function()
-				{
-					var last = this.$editor.children().last();
-					this.$editor.focus();
-
-					if (last.size() === 0) return;
-					if (this.utils.isEmpty(this.$editor.html()))
-					{
-
-						this.selection.get();
-						this.range.collapse(true);
-						this.range.setStartAfter(last[0]);
-						this.range.setEnd(last[0], 0);
-						this.selection.addRange();
-					}
-					else
-					{
-						this.selection.get();
-						this.range.selectNodeContents(last[0]);
-						this.range.collapse(false);
-						this.selection.addRange();
-
-					}
-				},
-				isFocused: function()
-				{
-					return this.$editor[0] === document.activeElement;
-				}
-			};
-		},
-		image: function()
-		{
-			return {
-				show: function()
-				{
-					this.modal.load('image', this.lang.get('image'), 700);
-					this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
-
-					this.selection.save();
-					this.modal.show();
-
-				},
-				showEdit: function($image)
-				{
-					var $link = $image.closest('a', this.$editor[0]);
-
-					this.modal.load('imageEdit', this.lang.get('edit'), 705);
-
-					this.modal.createCancelButton();
-					this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
-					this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
-
-					this.image.buttonDelete.on('click', $.proxy(function()
-					{
-						this.image.remove($image);
-
-					}, this));
-
-					this.image.buttonSave.on('click', $.proxy(function()
-					{
-						this.image.update($image);
-
-					}, this));
-
-					// hide link's tooltip
-					$('.redactor-link-tooltip').remove();
-
-					$('#redactor-image-title').val($image.attr('alt'));
-
-					if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
-					else
-					{
-						var $redactorImageLink = $('#redactor-image-link');
-
-						$redactorImageLink.attr('href', $image.attr('src'));
-						if ($link.length !== 0)
-						{
-							$redactorImageLink.val($link.attr('href'));
-							if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
-						}
-					}
-
-					if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
-					else
-					{
-						var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
-						$('#redactor-image-align').val(floatValue);
-					}
-
-					this.modal.show();
-					$('#redactor-image-title').focus();
-
-				},
-				setFloating: function($image)
-				{
-					var floating = $('#redactor-image-align').val();
-
-					var imageFloat = '';
-					var imageDisplay = '';
-					var imageMargin = '';
-
-					switch (floating)
-					{
-						case 'left':
-							imageFloat = 'left';
-							imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
-						break;
-						case 'right':
-							imageFloat = 'right';
-							imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
-						break;
-						case 'center':
-							imageDisplay = 'block';
-							imageMargin = 'auto';
-						break;
-					}
-
-					$image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
-					$image.attr('rel', $image.attr('style'));
-				},
-				update: function($image)
-				{
-					this.image.hideResize();
-					this.buffer.set();
-
-					var $link = $image.closest('a', this.$editor[0]);
-
-					var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,"");
-					$image.attr('alt', title);
-
-					this.image.setFloating($image);
-
-					// as link
-					var link = $.trim($('#redactor-image-link').val());
-					var link = link.replace(/(<([^>]+)>)/ig,"");
-					if (link !== '')
-					{
-						// test url (add protocol)
-						var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
-						var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
-						var re2 = new RegExp('^' + pattern, 'i');
-
-						if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
-						{
-							link = this.opts.linkProtocol + '://' + link;
-						}
-
-						var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
-
-						if ($link.length === 0)
-						{
-							var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
-							if (target) a.attr('target', '_blank');
-
-							$image.replaceWith(a);
-						}
-						else
-						{
-							$link.attr('href', link);
-							if (target)
-							{
-								$link.attr('target', '_blank');
-							}
-							else
-							{
-								$link.removeAttr('target');
-							}
-						}
-					}
-					else if ($link.length !== 0)
-					{
-						$link.replaceWith(this.utils.getOuterHtml($image));
-
-					}
-
-					this.modal.close();
-					this.observe.images();
-					this.code.sync();
-
-
-				},
-				setEditable: function($image)
-				{
-					if (this.opts.imageEditable)
-					{
-						$image.on('dragstart', $.proxy(this.image.onDrag, this));
-					}
-
-					var handler = $.proxy(function(e)
-					{
-
-						this.observe.image = $image;
-
-						this.image.resizer = this.image.loadEditableControls($image);
-
-						$(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
-
-						// resize
-						if (!this.opts.imageResizable) return;
-
-						this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
-						{
-							this.image.setResizable(e, $image);
-						}, this));
-
-					}, this);
-
-
-					$image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this));
-					$image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler);
-				},
-				setResizable: function(e, $image)
-				{
-					e.preventDefault();
-
-				    this.image.resizeHandle = {
-				        x : e.pageX,
-				        y : e.pageY,
-				        el : $image,
-				        ratio: $image.width() / $image.height(),
-				        h: $image.height()
-				    };
-
-				    e = e.originalEvent || e;
-
-				    if (e.targetTouches)
-				    {
-				         this.image.resizeHandle.x = e.targetTouches[0].pageX;
-				         this.image.resizeHandle.y = e.targetTouches[0].pageY;
-				    }
-
-					this.image.startResize();
-
-
-				},
-				startResize: function()
-				{
-					$(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
-					$(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
-				},
-				moveResize: function(e)
-				{
-					e.preventDefault();
-
-					e = e.originalEvent || e;
-
-					var height = this.image.resizeHandle.h;
-
-		            if (e.targetTouches) height += (e.targetTouches[0].pageY -  this.image.resizeHandle.y);
-		            else height += (e.pageY -  this.image.resizeHandle.y);
-
-					var width = Math.round(height * this.image.resizeHandle.ratio);
-
-					if (height < 50 || width < 100) return;
-
-					var height = Math.round(this.image.resizeHandle.el.width() / this.image.resizeHandle.ratio);
-
-					this.image.resizeHandle.el.attr({width: width, height: height});
-		            this.image.resizeHandle.el.width(width);
-		            this.image.resizeHandle.el.height(height);
-
-		            this.code.sync();
-				},
-				stopResize: function()
-				{
-					this.handle = false;
-					$(document).off('.redactor-image-resize');
-
-					this.image.hideResize();
-				},
-				onDrag: function(e)
-				{
-					if (this.$editor.find('#redactor-image-box').length !== 0)
-					{
-						e.preventDefault();
-						return false;
-					}
-
-					this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
-					{
-						setTimeout($.proxy(this.image.onDrop, this), 1);
-
-					}, this));
-				},
-				onDrop: function()
-				{
-					this.image.fixImageSourceAfterDrop();
-					this.observe.images();
-					this.$editor.off('drop.redactor-image-inside-drop');
-					this.clean.clearUnverified();
-					this.code.sync();
-				},
-				fixImageSourceAfterDrop: function()
-				{
-					this.$editor.find('img[data-save-url]').each(function()
-					{
-						var $el = $(this);
-						$el.attr('src', $el.attr('data-save-url'));
-						$el.removeAttr('data-save-url');
-					});
-				},
-				hideResize: function(e)
-				{
-					if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return;
-					if (e && e.target.tagName == 'IMG')
-					{
-						var $image = $(e.target);
-						$image.attr('data-save-url', $image.attr('src'));
-					}
-
-					var imageBox = this.$editor.find('#redactor-image-box');
-					if (imageBox.length === 0) return;
-
-					$('#redactor-image-editter').remove();
-					$('#redactor-image-resizer').remove();
-
-					imageBox.find('img').css({
-						marginTop: imageBox[0].style.marginTop,
-						marginBottom: imageBox[0].style.marginBottom,
-						marginLeft: imageBox[0].style.marginLeft,
-						marginRight: imageBox[0].style.marginRight
-					});
-
-					imageBox.css('margin', '');
-					imageBox.find('img').css('opacity', '');
-					imageBox.replaceWith(function()
-					{
-						return $(this).contents();
-					});
-
-					$(document).off('mousedown.redactor-image-resize-hide.' + this.uuid);
-
-
-					if (typeof this.image.resizeHandle !== 'undefined')
-					{
-						this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style'));
-					}
-
-					this.code.sync();
-
-				},
-				loadResizableControls: function($image, imageBox)
-				{
-					if (this.opts.imageResizable && !this.utils.isMobile())
-					{
-						var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
-
-						if (!this.utils.isDesktop())
-						{
-							imageResizer.css({ width: '15px', height: '15px' });
-						}
-
-						imageResizer.attr('contenteditable', false);
-						imageBox.append(imageResizer);
-						imageBox.append($image);
-
-						return imageResizer;
-					}
-					else
-					{
-						imageBox.append($image);
-						return false;
-					}
-				},
-				loadEditableControls: function($image)
-				{
-					var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
-					imageBox.css('float', $image.css('float')).attr('contenteditable', false);
-
-					if ($image[0].style.margin != 'auto')
-					{
-						imageBox.css({
-							marginTop: $image[0].style.marginTop,
-							marginBottom: $image[0].style.marginBottom,
-							marginLeft: $image[0].style.marginLeft,
-							marginRight: $image[0].style.marginRight
-						});
-
-						$image.css('margin', '');
-					}
-					else
-					{
-						imageBox.css({ 'display': 'block', 'margin': 'auto' });
-					}
-
-					$image.css('opacity', '.5').after(imageBox);
-
-
-					if (this.opts.imageEditable)
-					{
-						// editter
-						this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
-						this.image.editter.attr('contenteditable', false);
-						this.image.editter.on('click', $.proxy(function()
-						{
-							this.image.showEdit($image);
-						}, this));
-
-						imageBox.append(this.image.editter);
-
-						// position correction
-						var editerWidth = this.image.editter.innerWidth();
-						this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
-					}
-
-					return this.image.loadResizableControls($image, imageBox);
-
-				},
-				remove: function(image)
-				{
-					var $image = $(image);
-					var $link = $image.closest('a', this.$editor[0]);
-					var $figure = $image.closest('figure', this.$editor[0]);
-					var $parent = $image.parent();
-					if ($('#redactor-image-box').length !== 0)
-					{
-						$parent = $('#redactor-image-box').parent();
-					}
-
-					var $next;
-					if ($figure.length !== 0)
-					{
-						$next = $figure.next();
-						$figure.remove();
-					}
-					else if ($link.length !== 0)
-					{
-						$parent = $link.parent();
-						$link.remove();
-					}
-					else
-					{
-						$image.remove();
-					}
-
-					$('#redactor-image-box').remove();
-
-					if ($figure.length !== 0)
-					{
-						this.caret.setStart($next);
-					}
-					else
-					{
-						this.caret.setStart($parent);
-					}
-
-					// delete callback
-					this.core.setCallback('imageDelete', $image[0].src, $image);
-
-					this.modal.close();
-					this.code.sync();
-				},
-				insert: function(json, direct, e)
-				{
-					// error callback
-					if (typeof json.error != 'undefined')
-					{
-						this.modal.close();
-						this.selection.restore();
-						this.core.setCallback('imageUploadError', json);
-						return;
-					}
-
-					var $img;
-					if (typeof json == 'string')
-					{
-						$img = $(json).attr('data-redactor-inserted-image', 'true');
-					}
-					else
-					{
-						$img = $('<img>');
-						$img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
-					}
-
-
-					var node = $img;
-					var isP = this.utils.isCurrentOrParent('P');
-					if (isP)
-					{
-						// will replace
-						node = $('<blockquote />').append($img);
-					}
-
-					if (direct)
-					{
-						this.selection.removeMarkers();
-						var marker = this.selection.getMarker();
-						this.insert.nodeToCaretPositionFromPoint(e, marker);
-					}
-					else
-					{
-						this.modal.close();
-					}
-
-					this.selection.restore();
-					this.buffer.set();
-
-					this.insert.html(this.utils.getOuterHtml(node), false);
-
-					var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
-
-					if (isP)
-					{
-						$image.parent().contents().unwrap().wrap('<p />');
-					}
-					else if (this.opts.linebreaks)
-					{
-						if (!this.utils.isEmpty(this.code.get()))
-						{
-							$image.before('<br>');
-						}
-
-						$image.after('<br>');
-					}
-
-					if (typeof json == 'string') return;
-
-					this.core.setCallback('imageUpload', $image, json);
-
-				}
-			};
-		},
-		indent: function()
-		{
-			return {
-				increase: function()
-				{
-					// focus
-					if (!this.utils.browser('msie')) this.$editor.focus();
-
-					this.buffer.set();
-					this.selection.save();
-
-					var block = this.selection.getBlock();
-
-					if (block && block.tagName == 'LI')
-					{
-						this.indent.increaseLists();
-					}
-					else if (block === false && this.opts.linebreaks)
-					{
-						this.indent.increaseText();
-					}
-					else
-					{
-						this.indent.increaseBlocks();
-					}
-
-					this.selection.restore();
-					this.code.sync();
-				},
-				increaseLists: function()
-				{
-					document.execCommand('indent');
-
-					this.indent.fixEmptyIndent();
-					this.clean.normalizeLists();
-					this.clean.clearUnverified();
-				},
-				increaseBlocks: function()
-				{
-					$.each(this.selection.getBlocks(), $.proxy(function(i, elem)
-					{
-						if (elem.tagName === 'TD' || elem.tagName === 'TH') return;
-
-						var $el = this.utils.getAlignmentElement(elem);
-
-						var left = this.utils.normalize($el.css('margin-left')) + this.opts.indentValue;
-						$el.css('margin-left', left + 'px');
-
-					}, this));
-				},
-				increaseText: function()
-				{
-					var wrapper = this.selection.wrap('div');
-					$(wrapper).attr('data-tagblock', 'redactor');
-					$(wrapper).css('margin-left', this.opts.indentValue + 'px');
-				},
-				decrease: function()
-				{
-					this.buffer.set();
-					this.selection.save();
-
-					var block = this.selection.getBlock();
-					if (block && block.tagName == 'LI')
-					{
-						this.indent.decreaseLists();
-					}
-					else
-					{
-						this.indent.decreaseBlocks();
-					}
-
-					this.selection.restore();
-					this.code.sync();
-				},
-				decreaseLists: function()
-				{
-					document.execCommand('outdent');
-
-					var current = this.selection.getCurrent();
-					var $item = $(current).closest('li', this.$editor[0]);
-
-					this.indent.fixEmptyIndent();
-
-					if (!this.opts.linebreaks && $item.length === 0)
-					{
-						document.execCommand('formatblock', false, 'p');
-						this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
-					}
-
-					this.clean.clearUnverified();
-				},
-				decreaseBlocks: function()
-				{
-					$.each(this.selection.getBlocks(), $.proxy(function(i, elem)
-					{
-						var $el = this.utils.getAlignmentElement(elem);
-						var left = this.utils.normalize($el.css('margin-left')) - this.opts.indentValue;
-
-						if (left <= 0)
-						{
-							if (this.opts.linebreaks && typeof($el.data('tagblock')) !== 'undefined')
-							{
-								$el.replaceWith($el.html() + '<br />');
-							}
-							else
-							{
-								$el.css('margin-left', '');
-								this.utils.removeEmptyAttr($el, 'style');
-							}
-						}
-						else
-						{
-							$el.css('margin-left', left + 'px');
-						}
-
-					}, this));
-				},
-				fixEmptyIndent: function()
-				{
-					var block = this.selection.getBlock();
-
-					if (this.range.collapsed && block && block.tagName == 'LI' && this.utils.isEmpty($(block).text()))
-					{
-						var $block = $(block);
-						$block.find('span').not('.redactor-selection-marker').contents().unwrap();
-						$block.append('<br>');
-					}
-				}
-			};
-		},
-		inline: function()
-		{
-			return {
-				formatting: function(name)
-				{
-					var type, value;
-
-					if (typeof this.formatting[name].style != 'undefined') type = 'style';
-					else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class';
-
-					if (type) value = this.formatting[name][type];
-
-					this.inline.format(this.formatting[name].tag, type, value);
-
-				},
-				format: function(tag, type, value)
-				{
-					var current = this.selection.getCurrent();
-					if (current && current.tagName === 'TR') return;
-
-					// Stop formatting pre and headers
-					if (this.utils.isCurrentOrParent('PRE') || this.utils.isCurrentOrParentHeader()) return;
-
-					var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
-					var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
-
-					for (var i = 0; i < tags.length; i++)
-					{
-						if (tag == tags[i]) tag = replaced[i];
-					}
-
-
-					if (this.opts.allowedTags)
-					{
-						if ($.inArray(tag, this.opts.allowedTags) == -1) return;
-					}
-					else
-					{
-						if ($.inArray(tag, this.opts.deniedTags) !== -1) return;
-					}
-
-					this.inline.type = type || false;
-					this.inline.value = value || false;
-
-					this.buffer.set();
-
-					if (!this.utils.browser('msie') && !this.opts.linebreaks)
-					{
-						this.$editor.focus();
-					}
-
-					this.selection.get();
-
-					if (this.range.collapsed)
-					{
-						this.inline.formatCollapsed(tag);
-					}
-					else
-					{
-						this.inline.formatMultiple(tag);
-					}
-				},
-				formatCollapsed: function(tag)
-				{
-					var current = this.selection.getCurrent();
-					var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']', this.$editor[0]);
-
-					// inline there is
-					if ($parent.length !== 0 && (this.inline.type != 'style' && $parent[0].tagName != 'SPAN'))
-					{
-						// remove empty
-						if (this.utils.isEmpty($parent.text()))
-						{
-							this.caret.setAfter($parent[0]);
-
-							$parent.remove();
-							this.code.sync();
-						}
-						else if (this.utils.isEndOfElement($parent))
-						{
-							this.caret.setAfter($parent[0]);
-						}
-
-						return;
-					}
-
-					// create empty inline
-					var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
-					node.html(this.opts.invisibleSpace);
-
-					node = this.inline.setFormat(node);
-
-					var node = this.insert.node(node);
-					this.caret.setEnd(node);
-
-					this.code.sync();
-				},
-				formatMultiple: function(tag)
-				{
-					this.inline.formatConvert(tag);
-
-					this.selection.save();
-					document.execCommand('strikethrough');
-
-					this.$editor.find('strike').each($.proxy(function(i,s)
-					{
-						var $el = $(s);
-
-						this.inline.formatRemoveSameChildren($el, tag);
-
-						var $span;
-						if (this.inline.type)
-						{
-							$span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
-							$span = this.inline.setFormat($span);
-						}
-						else
-						{
-							$span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
-						}
-
-						$el.replaceWith($span.html($el.contents()));
-						var $parent = $span.parent();
-
-						// remove U tag if selected link + node
-						if ($span[0].tagName === 'A' && $parent && $parent[0].tagName === 'U')
-						{
-							$span.parent().replaceWith($span);
-						}
-
-						if (tag == 'span')
-						{
-							if ($parent && $parent[0].tagName === 'SPAN' && this.inline.type === 'style')
-							{
-								var arr = this.inline.value.split(';');
-
-								for (var z = 0; z < arr.length; z++)
-								{
-									if (arr[z] === '') return;
-									var style = arr[z].split(':');
-									$parent.css(style[0], '');
-
-									if (this.utils.removeEmptyAttr($parent, 'style'))
-									{
-										$parent.replaceWith($parent.contents());
-									}
-
-								}
-
-							}
-						}
-
-					}, this));
-
-					// clear text decoration
-					if (tag != 'span')
-					{
-						this.$editor.find(this.opts.inlineTags.join(', ')).each($.proxy(function(i,s)
-						{
-							var $el = $(s);
-
-
-							if (s.tagName === 'U' && s.attributes.length === 0)
-							{
-								$el.replaceWith($el.contents());
-								return;
-							}
-
-							var property = $el.css('text-decoration');
-							if (property === 'line-through')
-							{
-								$el.css('text-decoration', '');
-								this.utils.removeEmptyAttr($el, 'style');
-							}
-						}, this));
-					}
-
-					if (tag != 'del')
-					{
-						var _this = this;
-						this.$editor.find('inline').each(function(i,s)
-						{
-							_this.utils.replaceToTag(s, 'del');
-						});
-					}
-
-					if (tag != 'u')
-					{
-						var _this = this;
-						this.$editor.find('unline').each(function(i,s)
-						{
-							_this.utils.replaceToTag(s, 'u');
-						});
-					}
-
-					this.selection.restore();
-					this.code.sync();
-
-				},
-				formatRemoveSameChildren: function($el, tag)
-				{
-					var self = this;
-					$el.children(tag).each(function()
-					{
-						var $child = $(this);
-
-						if (!$child.hasClass('redactor-selection-marker'))
-						{
-							if (self.inline.type == 'style')
-							{
-								var arr = self.inline.value.split(';');
-
-								for (var z = 0; z < arr.length; z++)
-								{
-									if (arr[z] === '') return;
-
-									var style = arr[z].split(':');
-									$child.css(style[0], '');
-
-									if (self.utils.removeEmptyAttr($child , 'style'))
-									{
-										$child.replaceWith($child.contents());
-									}
-
-								}
-							}
-							else
-							{
-								$child.contents().unwrap();
-							}
-						}
-
-					});
-				},
-				formatConvert: function(tag)
-				{
-					this.selection.save();
-
-					var find = '';
-					if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
-					else if (this.inline.type == 'style')
-					{
-						find = '[data-redactor-style="' + this.inline.value + '"]';
-					}
-
-					var self = this;
-					if (tag != 'del')
-					{
-						this.$editor.find('del').each(function(i,s)
-						{
-							self.utils.replaceToTag(s, 'inline');
-						});
-					}
-
-					if (tag != 'u')
-					{
-						this.$editor.find('u').each(function(i,s)
-						{
-							self.utils.replaceToTag(s, 'unline');
-						});
-					}
-
-					if (tag != 'span')
-					{
-						this.$editor.find(tag).each(function()
-						{
-							var $el = $(this);
-							$el.replaceWith($('<strike />').html($el.contents()));
-
-						});
-					}
-
-					this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
-					{
-						if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
-
-						var $el = $(this);
-						$el.replaceWith($('<strike />').html($el.contents()));
-
-					});
-
-					this.selection.restore();
-				},
-				setFormat: function(node)
-				{
-					switch (this.inline.type)
-					{
-						case 'class':
-
-							if (node.hasClass(this.inline.value))
-							{
-								node.removeClass(this.inline.value);
-								node.removeAttr('data-redactor-class');
-							}
-							else
-							{
-								node.addClass(this.inline.value);
-								node.attr('data-redactor-class', this.inline.value);
-							}
-
-
-						break;
-						case 'style':
-
-							node[0].style.cssText = this.inline.value;
-							node.attr('data-redactor-style', this.inline.value);
-
-						break;
-					}
-
-					return node;
-				},
-				removeStyle: function()
-				{
-					this.buffer.set();
-					var current = this.selection.getCurrent();
-					var nodes = this.selection.getInlines();
-
-					this.selection.save();
-
-					if (current && current.tagName === 'SPAN')
-					{
-						var $s = $(current);
-
-						$s.removeAttr('style');
-						if ($s[0].attributes.length === 0)
-						{
-							$s.replaceWith($s.contents());
-						}
-					}
-
-					$.each(nodes, $.proxy(function(i,s)
-					{
-						var $s = $(s);
-						if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
-						{
-							$s.removeAttr('style');
-							if ($s[0].attributes.length === 0)
-							{
-								$s.replaceWith($s.contents());
-							}
-						}
-					}, this));
-
-					this.selection.restore();
-					this.code.sync();
-
-				},
-				removeStyleRule: function(name)
-				{
-					this.buffer.set();
-					var parent = this.selection.getParent();
-					var nodes = this.selection.getInlines();
-
-					this.selection.save();
-
-					if (parent && parent.tagName === 'SPAN')
-					{
-						var $s = $(parent);
-
-						$s.css(name, '');
-						this.utils.removeEmptyAttr($s, 'style');
-						if ($s[0].attributes.length === 0)
-						{
-							$s.replaceWith($s.contents());
-						}
-					}
-
-					$.each(nodes, $.proxy(function(i,s)
-					{
-						var $s = $(s);
-						if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
-						{
-							$s.css(name, '');
-							this.utils.removeEmptyAttr($s, 'style');
-							if ($s[0].attributes.length === 0)
-							{
-								$s.replaceWith($s.contents());
-							}
-						}
-					}, this));
-
-					this.selection.restore();
-					this.code.sync();
-				},
-				removeFormat: function()
-				{
-					this.buffer.set();
-					var current = this.selection.getCurrent();
-
-					this.selection.save();
-
-					document.execCommand('removeFormat');
-
-					if (current && current.tagName === 'SPAN')
-					{
-						$(current).replaceWith($(current).contents());
-					}
-
-
-					$.each(this.selection.getNodes(), $.proxy(function(i,s)
-					{
-						var $s = $(s);
-						if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
-						{
-							$s.replaceWith($s.contents());
-						}
-					}, this));
-
-					this.selection.restore();
-					this.code.sync();
-
-				},
-				toggleClass: function(className)
-				{
-					this.inline.format('span', 'class', className);
-				},
-				toggleStyle: function(value)
-				{
-					this.inline.format('span', 'style', value);
-				}
-			};
-		},
-		insert: function()
-		{
-			return {
-				set: function(html, clean)
-				{
-					this.placeholder.remove();
-
-					html = this.clean.setVerified(html);
-
-					if (typeof clean == 'undefined')
-					{
-						html = this.clean.onPaste(html, false);
-					}
-
-					this.$editor.html(html);
-					this.selection.remove();
-					this.focus.setEnd();
-					this.clean.normalizeLists();
-					this.code.sync();
-					this.observe.load();
-
-					if (typeof clean == 'undefined')
-					{
-						setTimeout($.proxy(this.clean.clearUnverified, this), 10);
-					}
-				},
-				text: function(text)
-				{
-					this.placeholder.remove();
-
-					text = text.toString();
-					text = $.trim(text);
-					text = this.clean.getPlainText(text, false);
-
-					this.$editor.focus();
-
-					if (this.utils.browser('msie'))
-					{
-						this.insert.htmlIe(text);
-					}
-					else
-					{
-						this.selection.get();
-
-						this.range.deleteContents();
-						var el = document.createElement("div");
-						el.innerHTML = text;
-						var frag = document.createDocumentFragment(), node, lastNode;
-						while ((node = el.firstChild))
-						{
-							lastNode = frag.appendChild(node);
-						}
-
-						this.range.insertNode(frag);
-
-						if (lastNode)
-						{
-							var range = this.range.cloneRange();
-							range.setStartAfter(lastNode);
-							range.collapse(true);
-							this.sel.removeAllRanges();
-							this.sel.addRange(range);
-						}
-					}
-
-					this.code.sync();
-					this.clean.clearUnverified();
-				},
-				htmlWithoutClean: function(html)
-				{
-					this.insert.html(html, false);
-				},
-				html: function(html, clean)
-				{
-					this.placeholder.remove();
-
-					if (typeof clean == 'undefined') clean = true;
-
-					if (!this.opts.linebreaks)
-					{
-						this.$editor.focus();
-					}
-
-					html = this.clean.setVerified(html);
-
-					if (clean)
-					{
-						html = this.clean.onPaste(html);
-					}
-
-					if (this.utils.browser('msie'))
-					{
-						this.insert.htmlIe(html);
-					}
-					else
-					{
-						if (this.clean.singleLine) this.insert.execHtml(html);
-						else document.execCommand('insertHTML', false, html);
-
-						this.insert.htmlFixMozilla();
-
-					}
-
-					this.clean.normalizeLists();
-
-					// remove empty paragraphs finaly
-					if (!this.opts.linebreaks)
-					{
-						this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
-					}
-
-					this.code.sync();
-					this.observe.load();
-
-					if (clean)
-					{
-						this.clean.clearUnverified();
-					}
-
-				},
-				htmlFixMozilla: function()
-				{
-					// FF inserts empty p when content was selected dblclick
-					if (!this.utils.browser('mozilla')) return;
-
-					var $next = $(this.selection.getBlock()).next();
-					if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
-					{
-						$next.remove();
-					}
-
-				},
-				htmlIe: function(html)
-				{
-					if (this.utils.isIe11())
-					{
-						var parent = this.utils.isCurrentOrParent('P');
-						var $html = $('<div>').append(html);
-						var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
-
-						if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
-						else this.insert.ie11PasteFrag(html);
-
-						return;
-					}
-
-					document.selection.createRange().pasteHTML(html);
-
-				},
-				execHtml: function(html)
-				{
-					html = this.clean.setVerified(html);
-
-					this.selection.get();
-
-					this.range.deleteContents();
-
-					var el = document.createElement('div');
-					el.innerHTML = html;
-
-					var frag = document.createDocumentFragment(), node, lastNode;
-					while ((node = el.firstChild))
-					{
-						lastNode = frag.appendChild(node);
-					}
-
-					this.range.insertNode(frag);
-
-					this.range.collapse(true);
-					this.caret.setAfter(lastNode);
-
-				},
-				node: function(node, deleteContents)
-				{
-					node = node[0] || node;
-
-					var offset = this.caret.getOffset();
-					var html = this.utils.getOuterHtml(node);
-					html = this.clean.setVerified(html);
-
-					if (html.match(/</g) !== null)
-					{
-						node = $(html)[0];
-					}
-
-					this.selection.get();
-
-					if (deleteContents !== false)
-					{
-						this.range.deleteContents();
-					}
-
-					this.range.insertNode(node);
-					this.range.collapse(false);
-					this.selection.addRange();
-
-					this.caret.setOffset(offset);
-
-					return node;
-				},
-				nodeToPoint: function(node, x, y)
-				{
-					node = node[0] || node;
-
-					this.selection.get();
-
-					var range;
-					if (document.caretPositionFromPoint)
-					{
-					    var pos = document.caretPositionFromPoint(x, y);
-
-					    this.range.setStart(pos.offsetNode, pos.offset);
-					    this.range.collapse(true);
-					    this.range.insertNode(node);
-					}
-					else if (document.caretRangeFromPoint)
-					{
-					    range = document.caretRangeFromPoint(x, y);
-					    range.insertNode(node);
-					}
-					else if (typeof document.body.createTextRange != "undefined")
-					{
-				        range = document.body.createTextRange();
-				        range.moveToPoint(x, y);
-				        var endRange = range.duplicate();
-				        endRange.moveToPoint(x, y);
-				        range.setEndPoint("EndToEnd", endRange);
-				        range.select();
-					}
-				},
-				nodeToCaretPositionFromPoint: function(e, node)
-				{
-					node = node[0] || node;
-
-					var range;
-					var x = e.clientX, y = e.clientY;
-					if (document.caretPositionFromPoint)
-					{
-					    var pos = document.caretPositionFromPoint(x, y);
-					    var sel = document.getSelection();
-					    range = sel.getRangeAt(0);
-					    range.setStart(pos.offsetNode, pos.offset);
-					    range.collapse(true);
-					    range.insertNode(node);
-					}
-					else if (document.caretRangeFromPoint)
-					{
-					    range = document.caretRangeFromPoint(x, y);
-					    range.insertNode(node);
-					}
-					else if (typeof document.body.createTextRange != "undefined")
-					{
-				        range = document.body.createTextRange();
-				        range.moveToPoint(x, y);
-				        var endRange = range.duplicate();
-				        endRange.moveToPoint(x, y);
-				        range.setEndPoint("EndToEnd", endRange);
-				        range.select();
-					}
-
-				},
-				ie11FixInserting: function(parent, html)
-				{
-					var node = document.createElement('span');
-					node.className = 'redactor-ie-paste';
-					this.insert.node(node);
-
-					var parHtml = $(parent).html();
-
-					parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
-					parHtml = parHtml.replace(/<p><\/p>/gi, '');
-					$(parent).replaceWith(parHtml);
-				},
-				ie11PasteFrag: function(html)
-				{
-					this.selection.get();
-					this.range.deleteContents();
-
-					var el = document.createElement("div");
-					el.innerHTML = html;
-
-					var frag = document.createDocumentFragment(), node, lastNode;
-					while ((node = el.firstChild))
-					{
-						lastNode = frag.appendChild(node);
-					}
-
-					this.range.insertNode(frag);
-					this.range.collapse(false);
-					this.selection.addRange();
-				}
-			};
-		},
-		keydown: function()
-		{
-			return {
-				init: function(e)
-				{
-					if (this.rtePaste) return;
-
-					var key = e.which;
-					var arrow = (key >= 37 && key <= 40);
-
-					this.keydown.ctrl = e.ctrlKey || e.metaKey;
-					this.keydown.current = this.selection.getCurrent();
-					this.keydown.parent = this.selection.getParent();
-					this.keydown.block = this.selection.getBlock();
-
-			        // detect tags
-					this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
-					this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
-					this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
-
-					// shortcuts setup
-					this.shortcuts.init(e, key);
-
-					if (this.utils.isDesktop())
-					{
-						this.keydown.checkEvents(arrow, key);
-						this.keydown.setupBuffer(e, key);
-					}
-
-					this.keydown.addArrowsEvent(arrow);
-					this.keydown.setupSelectAll(e, key);
-
-					// callback
-					var keydownStop = this.core.setCallback('keydown', e);
-					if (keydownStop === false)
-					{
-						e.preventDefault();
-						return false;
-					}
-
-					// ie and ff exit from table
-					if (this.opts.enterKey && (this.utils.browser('msie') || this.utils.browser('mozilla')) && (key === this.keyCode.DOWN || key === this.keyCode.RIGHT))
-					{
-						var isEndOfTable = false;
-						var $table = false;
-						if (this.keydown.block && this.keydown.block.tagName === 'TD')
-						{
-							$table = $(this.keydown.block).closest('table', this.$editor[0]);
-						}
-
-						if ($table && $table.find('td').last()[0] === this.keydown.block)
-						{
-							isEndOfTable = true;
-						}
-
-						if (this.utils.isEndOfElement() && isEndOfTable)
-						{
-							var node = $(this.opts.emptyHtml);
-							$table.after(node);
-							this.caret.setStart(node);
-						}
-					}
-
-					// down
-					if (this.opts.enterKey && key === this.keyCode.DOWN)
-					{
-						this.keydown.onArrowDown();
-					}
-
-					// turn off enter key
-					if (!this.opts.enterKey && key === this.keyCode.ENTER)
-					{
-						e.preventDefault();
-						// remove selected
-						if (!this.range.collapsed) this.range.deleteContents();
-						return;
-					}
-
-					// on enter
-					if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
-					{
-						var stop = this.core.setCallback('enter', e);
-						if (stop === false)
-						{
-							e.preventDefault();
-							return false;
-						}
-
-						if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
-						{
-							return false;
-						}
-
-						var current, $next;
-						if (this.keydown.pre)
-						{
-							return this.keydown.insertNewLine(e);
-						}
-						else if (this.keydown.blockquote || this.keydown.figcaption)
-						{
-							current = this.selection.getCurrent();
-							$next = $(current).next();
-
-							if ($next.length !== 0 && $next[0].tagName == 'BR')
-							{
-								return this.keydown.insertBreakLine(e);
-							}
-							else if (this.utils.isEndOfElement() && (current && current != 'SPAN'))
-							{
-								return this.keydown.insertDblBreakLine(e);
-							}
-							else
-							{
-								return this.keydown.insertBreakLine(e);
-							}
-						}
-						else if (this.opts.linebreaks && !this.keydown.block)
-						{
-							current = this.selection.getCurrent();
-							$next = $(this.keydown.current).next();
-
-							if ($next.length !== 0 && $next[0].tagName == 'BR')
-							{
-								return this.keydown.insertBreakLine(e);
-							}
-							else if (current !== false && $(current).hasClass('redactor-invisible-space'))
-							{
-								this.caret.setAfter(current);
-								$(current).contents().unwrap();
-
-								return this.keydown.insertDblBreakLine(e);
-							}
-							else
-							{
-								if (this.utils.isEndOfEditor())
-								{
-									return this.keydown.insertDblBreakLine(e);
-								}
-								else if ($next.length === 0 && current === false && typeof $next.context != 'undefined')
-								{
-									return this.keydown.insertBreakLine(e);
-								}
-
-								return this.keydown.insertBreakLine(e);
-							}
-
-						}
-						else if (this.opts.linebreaks && this.keydown.block)
-						{
-							setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1);
-						}
-						// paragraphs
-						else if (!this.opts.linebreaks && this.keydown.block)
-						{
-							setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
-
-							if (this.keydown.block.tagName === 'LI')
-							{
-								current = this.selection.getCurrent();
-								var $parent = $(current).closest('li', this.$editor[0]);
-								var $list = $parent.closest('ul,ol', this.$editor[0]);
-
-								if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html()))
-								{
-									$list.find("li").last().remove();
-
-									var node = $(this.opts.emptyHtml);
-									$list.after(node);
-									this.caret.setStart(node);
-
-									return false;
-								}
-							}
-						}
-						else if (!this.opts.linebreaks && !this.keydown.block)
-						{
-							return this.keydown.insertParagraph(e);
-						}
-					}
-
-					// Shift+Enter or Ctrl+Enter
-					if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
-					{
-						return this.keydown.onShiftEnter(e);
-					}
-
-
-					// tab or cmd + [
-					if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
-					{
-						return this.keydown.onTab(e, key);
-					}
-
-					// image delete and backspace
-					if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
-					{
-						var nodes = this.selection.getNodes();
-
-						if (nodes)
-						{
-							var len = nodes.length;
-							var last;
-							for (var i = 0; i < len; i++)
-							{
-								var children = $(nodes[i]).children('img');
-								if (children.length !== 0)
-								{
-									var self = this;
-									$.each(children, function(z,s)
-									{
-										var $s = $(s);
-										if ($s.css('float') != 'none') return;
-
-										// image delete callback
-										self.core.setCallback('imageDelete', s.src, $s);
-										last = s;
-									});
-								}
-								else if (nodes[i].tagName == 'IMG')
-								{
-									if (last != nodes[i])
-									{
-										// image delete callback
-										this.core.setCallback('imageDelete', nodes[i].src, $(nodes[i]));
-										last = nodes[i];
-									}
-								}
-							}
-						}
-					}
-
-					// backspace
-					if (key === this.keyCode.BACKSPACE)
-					{
-						// backspace as outdent
-						var block = this.selection.getBlock();
-						var indented = ($(block).css('margin-left') !== '0px');
-						if (block && indented && this.range.collapsed && this.utils.isStartOfElement())
-						{
-							this.indent.decrease();
-							e.preventDefault();
-							return;
-						}
-
-						// remove hr in FF
-						if (this.utils.browser('mozilla'))
-						{
-							var prev = this.selection.getPrev();
-							var prev2 = $(prev).prev()[0];
-							if (prev && prev.tagName === 'HR') $(prev).remove();
-							if (prev2 && prev2.tagName === 'HR') $(prev2).remove();
-						}
-
-						this.keydown.removeInvisibleSpace();
-						this.keydown.removeEmptyListInTable(e);
-					}
-
-					this.code.sync();
-				},
-				checkEvents: function(arrow, key)
-				{
-					if (!arrow && (this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
-					{
-						this.core.addEvent(false);
-
-						if (this.keydown.checkKeyEvents(key))
-						{
-							this.buffer.set();
-						}
-					}
-				},
-				checkKeyEvents: function(key)
-				{
-					var k = this.keyCode;
-					var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
-
-					return ($.inArray(key, keys) == -1) ? true : false;
-
-				},
-				addArrowsEvent: function(arrow)
-				{
-					if (!arrow) return;
-
-					if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
-					{
-						this.core.addEvent(false);
-						return;
-					}
-
-				    this.core.addEvent('arrow');
-				},
-				setupBuffer: function(e, key)
-				{
-					if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.opts.buffer.length) // z key
-					{
-						e.preventDefault();
-						this.buffer.undo();
-						return;
-					}
-					// undo
-					else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.opts.rebuffer.length !== 0)
-					{
-						e.preventDefault();
-						this.buffer.redo();
-						return;
-					}
-					else if (!this.keydown.ctrl)
-					{
-						if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey))
-						{
-							this.buffer.set();
-						}
-					}
-				},
-				setupSelectAll: function(e, key)
-				{
-					if (this.keydown.ctrl && key === 65)
-					{
-						this.utils.enableSelectAll();
-					}
-					else if (key != this.keyCode.LEFT_WIN && !this.keydown.ctrl)
-					{
-						this.utils.disableSelectAll();
-					}
-				},
-				onArrowDown: function()
-				{
-					var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
-
-					for (var i = 0; i < tags.length; i++)
-					{
-						if (tags[i])
-						{
-							this.keydown.insertAfterLastElement(tags[i]);
-							return false;
-						}
-					}
-				},
-				onShiftEnter: function(e)
-				{
-					this.buffer.set();
-
-					if (this.utils.isEndOfElement())
-					{
-						return this.keydown.insertDblBreakLine(e);
-					}
-
-					return this.keydown.insertBreakLine(e);
-				},
-				onTab: function(e, key)
-				{
-					if (!this.opts.tabKey) return true;
-					if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
-
-					e.preventDefault();
-
-					var node;
-					if (this.keydown.pre && !e.shiftKey)
-					{
-						node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
-						this.insert.node(node);
-						this.code.sync();
-					}
-					else if (this.opts.tabAsSpaces !== false)
-					{
-						node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
-						this.insert.node(node);
-						this.code.sync();
-					}
-					else
-					{
-						if (e.metaKey && key === 219) this.indent.decrease();
-						else if (e.metaKey && key === 221) this.indent.increase();
-						else if (!e.shiftKey) this.indent.increase();
-						else this.indent.decrease();
-					}
-
-					return false;
-				},
-				replaceDivToBreakLine: function()
-				{
-					var blockElem = this.selection.getBlock();
-					var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
-					if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
-					{
-						var br = document.createElement('br');
-
-						$(blockElem).replaceWith(br);
-						this.caret.setBefore(br);
-
-						this.code.sync();
-
-						return false;
-					}
-				},
-				replaceDivToParagraph: function()
-				{
-					var blockElem = this.selection.getBlock();
-					var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
-					if (blockElem.tagName === 'DIV' && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-editor'))
-					{
-						var p = document.createElement('p');
-						p.innerHTML = this.opts.invisibleSpace;
-
-						$(blockElem).replaceWith(p);
-						this.caret.setStart(p);
-
-						this.code.sync();
-
-						return false;
-					}
-					else if (this.opts.cleanStyleOnEnter && blockElem.tagName == 'P')
-					{
-						$(blockElem).removeAttr('class').removeAttr('style');
-					}
-				},
-				insertParagraph: function(e)
-				{
-					e.preventDefault();
-
-					this.selection.get();
-
-					var p = document.createElement('p');
-					p.innerHTML = this.opts.invisibleSpace;
-
-					this.range.deleteContents();
-					this.range.insertNode(p);
-
-					this.caret.setStart(p);
-
-					this.code.sync();
-
-					return false;
-				},
-				exitFromBlockquote: function(e)
-				{
-					if (!this.utils.isEndOfElement()) return;
-
-					var tmp = $.trim($(this.keydown.block).html());
-					if (tmp.search(/(<br\s?\/?>){2}$/i) != -1)
-					{
-						e.preventDefault();
-
-						if (this.opts.linebreaks)
-						{
-							var br = document.createElement('br');
-							$(this.keydown.blockquote).after(br);
-
-							this.caret.setBefore(br);
-							$(this.keydown.block).html(tmp.replace(/<br\s?\/?>$/i, ''));
-						}
-						else
-						{
-							var node = $(this.opts.emptyHtml);
-							$(this.keydown.blockquote).after(node);
-							this.caret.setStart(node);
-						}
-
-						return true;
-
-					}
-
-					return;
-
-				},
-				insertAfterLastElement: function(element)
-				{
-					if (!this.utils.isEndOfElement()) return;
-
-					this.buffer.set();
-
-					if (this.opts.linebreaks)
-					{
-						var contents = $('<div>').append($.trim(this.$editor.html())).contents();
-						var last = contents.last()[0];
-						if (last.tagName == 'SPAN' && last.innerHTML === '')
-						{
-							last = contents.prev()[0];
-						}
-
-						if (this.utils.getOuterHtml(last) != this.utils.getOuterHtml(element)) return;
-
-						var br = document.createElement('br');
-						$(element).after(br);
-						this.caret.setAfter(br);
-
-					}
-					else
-					{
-						if (this.$editor.contents().last()[0] !== element) return;
-
-						var node = $(this.opts.emptyHtml);
-						$(element).after(node);
-						this.caret.setStart(node);
-					}
-				},
-				insertNewLine: function(e)
-				{
-					e.preventDefault();
-
-					var node = document.createTextNode('\n');
-
-					this.selection.get();
-
-					this.range.deleteContents();
-					this.range.insertNode(node);
-
-					this.caret.setAfter(node);
-
-					this.code.sync();
-
-					return false;
-				},
-				insertBreakLine: function(e)
-				{
-					return this.keydown.insertBreakLineProcessing(e);
-				},
-				insertDblBreakLine: function(e)
-				{
-					return this.keydown.insertBreakLineProcessing(e, true);
-				},
-				insertBreakLineProcessing: function(e, dbl)
-				{
-					e.stopPropagation();
-
-					this.selection.get();
-
-					var br1 = document.createElement('br');
-
-					if (this.utils.browser('msie'))
-					{
-						this.range.collapse(false);
-						this.range.setEnd(this.range.endContainer, this.range.endOffset);
-					}
-					else
-					{
-						this.range.deleteContents();
-					}
-
-					this.range.insertNode(br1);
-
-					// move br outside A tag
-					var $parentA = $(br1).parent("a");
-
-					if ($parentA.length > 0)
-					{
-						$parentA.find(br1).remove();
-						$parentA.after(br1);
-					}
-
-					if (dbl === true)
-					{
-						var $next = $(br1).next();
-						if ($next.length !== 0 && $next[0].tagName === 'BR' && this.utils.isEndOfEditor())
-						{
-							this.caret.setAfter(br1);
-							this.code.sync();
-							return false;
-						}
-
-						var br2 = document.createElement('br');
-
-						this.range.insertNode(br2);
-						this.caret.setAfter(br2);
-					}
-					else
-					{
-						// caret does not move after the br visual
-						if (this.utils.browser('msie'))
-						{
-							var space = document.createElement('span');
-							space.innerHTML = '&#x200b;';
-
-							$(br1).after(space);
-
-							this.range.setStartAfter(space);
-							this.range.setEndAfter(space);
-							$(space).remove();
-						}
-						else
-						{
-							var range = document.createRange();
-							range.setStartAfter(br1);
-							range.collapse(true);
-							var selection = window.getSelection();
-							selection.removeAllRanges();
-							selection.addRange(range);
-						}
-					}
-
-					this.code.sync();
-					return false;
-				},
-				removeInvisibleSpace: function()
-				{
-					var $current = $(this.keydown.current);
-					if ($current.text().search(/^\u200B$/g) === 0)
-					{
-						$current.remove();
-					}
-				},
-				removeEmptyListInTable: function(e)
-				{
-					var $current = $(this.keydown.current);
-					var $parent = $(this.keydown.parent);
-					var td = $current.closest('td', this.$editor[0]);
-
-					if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1)
-					{
-						if (!this.utils.isEmpty($current.text())) return;
-
-						e.preventDefault();
-
-						$current.remove();
-						$parent.remove();
-
-						this.caret.setStart(td);
-					}
-				}
-			};
-		},
-		keyup: function()
-		{
-			return {
-				init: function(e)
-				{
-
-					if (this.rtePaste) return;
-
-					var key = e.which;
-
-					this.keyup.current = this.selection.getCurrent();
-					this.keyup.parent = this.selection.getParent();
-					var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent());
-
-					// callback
-					var keyupStop = this.core.setCallback('keyup', e);
-					if (keyupStop === false)
-					{
-						e.preventDefault();
-						return false;
-					}
-
-					// replace to p before / after the table or body
-					if (!this.opts.linebreaks && this.keyup.current.nodeType === 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
-					{
-						this.keyup.replaceToParagraph();
-					}
-
-					// replace div after lists
-					if (!this.opts.linebreaks && this.utils.isRedactorParent(this.keyup.current) && this.keyup.current.tagName === 'DIV')
-					{
-						this.keyup.replaceToParagraph(false);
-					}
-
-
-					if (!this.opts.linebreaks && $(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
-					{
-						$(this.keyup.parent).contents().unwrap();
-						this.keyup.replaceToParagraph();
-					}
-
-					// linkify
-					if (this.linkify.isEnabled() && this.linkify.isKey(key)) this.linkify.format();
-
-					if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
-					{
-						if (this.utils.browser('mozilla'))
-						{
-							var td = $(this.keydown.current).closest('td', this.$editor[0]);
-							if (td.size() !== 0 && td.text() !== '')
-							{
-								e.preventDefault();
-								return false;
-							}
-						}
-
-						// clear unverified
-						this.clean.clearUnverified();
-
-						if (this.observe.image)
-						{
-							e.preventDefault();
-
-							this.image.hideResize();
-
-							this.buffer.set();
-							this.image.remove(this.observe.image);
-							this.observe.image = false;
-
-							return false;
-						}
-
-						// remove empty paragraphs
-						this.$editor.find('p').each($.proxy(function(i, s)
-						{
-							this.utils.removeEmpty(i, $(s).html());
-						}, this));
-
-						// remove invisible space
-						if (this.opts.linebreaks && this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML))
-						{
-							$(this.keyup.current).after(this.selection.getMarkerAsHtml());
-							this.selection.restore();
-							$(this.keyup.current).remove();
-						}
-
-						this.keyup.removeEmptyLists();
-
-						// if empty
-						return this.keyup.formatEmpty(e);
-					}
-				},
-				replaceToParagraph: function(clone)
-				{
-					var $current = $(this.keyup.current);
-
-					var node;
-					if (clone === false)
-					{
-						node = $('<p>').append($current.html());
-					}
-					else
-					{
-						node = $('<p>').append($current.clone());
-					}
-
-					$current.replaceWith(node);
-					var next = $(node).next();
-					if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
-					{
-						next.remove();
-					}
-
-					this.caret.setEnd(node);
-				},
-				removeEmptyLists: function()
-				{
-					var removeIt = function()
-					{
-						var html = $.trim(this.innerHTML).replace(/\/t\/n/g, '');
-						if (html === '')
-						{
-							$(this).remove();
-						}
-					};
-
-					this.$editor.find('li').each(removeIt);
-					this.$editor.find('ul, ol').each(removeIt);
-				},
-				formatEmpty: function(e)
-				{
-					var html = $.trim(this.$editor.html());
-
-					if (!this.utils.isEmpty(html)) return;
-
-					e.preventDefault();
-
-					if (this.opts.linebreaks)
-					{
-						this.$editor.html(this.selection.getMarkerAsHtml());
-						this.selection.restore();
-					}
-					else
-					{
-						this.$editor.html(this.opts.emptyHtml);
-						this.focus.setStart();
-					}
-
-					this.code.sync();
-
-					return false;
-				}
-			};
-		},
-		lang: function()
-		{
-			return {
-				load: function()
-				{
-					this.opts.curLang = this.opts.langs[this.opts.lang];
-				},
-				get: function(name)
-				{
-					return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
-				}
-			};
-		},
-		line: function()
-		{
-			return {
-				insert: function()
-				{
-					this.buffer.set();
-
-					var blocks = this.selection.getBlocks();
- 					if (blocks[0] !== false && this.line.isExceptLastOrFirst(blocks))
-	 				{
-	 					if (!this.utils.browser('msie')) this.$editor.focus();
-	 					return;
-					}
-
-					if (this.utils.browser('msie'))
-					{
-						this.line.insertInIe();
-					}
-					else
-					{
-						this.line.insertInOthersBrowsers();
-					}
-				},
-				isExceptLastOrFirst: function(blocks)
-				{
-					var exceptTags = ['li', 'td', 'th', 'blockquote', 'figcaption', 'pre', 'dl', 'dt', 'dd'];
-
-					var first = blocks[0].tagName.toLowerCase();
-					var last = this.selection.getLastBlock();
-
-					last = (typeof last == 'undefined') ? first : last.tagName.toLowerCase();
-
-					var firstFound = $.inArray(first, exceptTags) != -1;
-					var lastFound = $.inArray(last, exceptTags) != -1;
-
-					if ((firstFound && lastFound) || firstFound)
-					{
-						return true;
-					}
-				},
-				insertInIe: function()
-				{
-					this.utils.saveScroll();
-					this.buffer.set();
-
-					this.insert.node(document.createElement('hr'));
-
-					this.utils.restoreScroll();
-					this.code.sync();
-				},
-				insertInOthersBrowsers: function()
-				{
-					this.buffer.set();
-
-					var extra = '<p id="redactor-insert-line"><br /></p>';
-					if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
-
-					document.execCommand('insertHtml', false, '<hr>' + extra);
-
-					this.line.setFocus();
-					this.code.sync();
-				},
-				setFocus: function()
-				{
-					var node = this.$editor.find('#redactor-insert-line');
-					var next = $(node).next()[0];
-					var target = next;
-					if (this.utils.browser('mozilla') && next && next.innerHTML === '')
-					{
-						target = $(next).next()[0];
-						$(next).remove();
-					}
-
-					if (target)
-					{
-						node.remove();
-
-						if (!this.opts.linebreaks)
-						{
-							this.$editor.focus();
-							this.line.setStart(target);
-						}
-
-					}
-					else
-					{
-
-						node.removeAttr('id');
-						this.line.setStart(node[0]);
-					}
-				},
-				setStart: function(node)
-				{
-					if (typeof node === 'undefined') return;
-
-					var textNode = document.createTextNode('\u200B');
-
-					this.selection.get();
-					this.range.setStart(node, 0);
-					this.range.insertNode(textNode);
-					this.range.collapse(true);
-					this.selection.addRange();
-
-				}
-			};
-		},
-		link: function()
-		{
-			return {
-				show: function(e)
-				{
-					if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
-
-					if (!this.observe.isCurrent('a'))
-					{
-						this.modal.load('link', this.lang.get('link_insert'), 600);
-					}
-					else
-					{
-						this.modal.load('link', this.lang.get('link_edit'), 600);
-					}
-
-					this.modal.createCancelButton();
-
-					var buttonText = !this.observe.isCurrent('a') ? this.lang.get('insert') : this.lang.get('edit');
-
-					this.link.buttonInsert = this.modal.createActionButton(buttonText);
-
-					this.selection.get();
-
-					this.link.getData();
-					this.link.cleanUrl();
-
-					if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
-
-					this.link.$inputUrl = $('#redactor-link-url');
-					this.link.$inputText = $('#redactor-link-url-text');
-
-					this.link.$inputText.val(this.link.text);
-					this.link.$inputUrl.val(this.link.url);
-
-					this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
-
-					// hide link's tooltip
-					$('.redactor-link-tooltip').remove();
-
-					// show modal
-					this.selection.save();
-					this.modal.show();
-					this.link.$inputUrl.focus();
-				},
-				cleanUrl: function()
-				{
-					var thref = self.location.href.replace(/\/$/i, '');
-
-					if (typeof this.link.url !== "undefined")
-					{
-						this.link.url = this.link.url.replace(thref, '');
-						this.link.url = this.link.url.replace(/^\/#/, '#');
-						this.link.url = this.link.url.replace('mailto:', '');
-
-						// remove host from href
-						if (!this.opts.linkProtocol)
-						{
-							var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
-							this.link.url = this.link.url.replace(re, '');
-						}
-					}
-				},
-				getData: function()
-				{
-					this.link.$node = false;
-
-					var $el = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
-					if ($el.length !== 0 && $el[0].tagName === 'A')
-					{
-						this.link.$node = $el;
-
-						this.link.url = $el.attr('href');
-						this.link.text = $el.text();
-						this.link.target = $el.attr('target');
-					}
-					else
-					{
-						this.link.text = this.sel.toString();
-						this.link.url = '';
-						this.link.target = '';
-					}
-
-				},
-				insert: function()
-				{
-					this.placeholder.remove();
-
-					var target = '';
-					var link = this.link.$inputUrl.val();
-					var text = this.link.$inputText.val().replace(/(<([^>]+)>)/ig,"");
-
-					if ($.trim(link) === '')
-					{
-						this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
-						{
-							$(this).removeClass('redactor-input-error');
-							$(this).off('keyup');
-
-						});
-
-						return;
-					}
-
-					// mailto
-					if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
-					{
-						link = link.replace('mailto:', '');
-						link = 'mailto:' + link;
-					}
-					// url, not anchor
-					else if (link.search('#') !== 0)
-					{
-						if ($('#redactor-link-blank').prop('checked'))
-						{
-							target = '_blank';
-						}
-
-						// test url (add protocol)
-						var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
-						var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
-						var re2 = new RegExp('^' + pattern, 'i');
-						var re3 = new RegExp('\.(html|php)$', 'i');
-						if (link.search(re) == -1 && link.search(re3) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
-						{
-							link = this.opts.linkProtocol + '://' + link;
-						}
-					}
-
-					this.link.set(text, link, target);
-					this.modal.close();
-				},
-				set: function(text, link, target)
-				{
-					text = $.trim(text.replace(/<|>/g, ''));
-
-					this.selection.restore();
-					var blocks = this.selection.getBlocks();
-
-					if (text === '' && link === '') return;
-					if (text === '' && link !== '') text = link;
-
-					if (this.link.$node)
-					{
-						this.buffer.set();
-
-						var $link = this.link.$node,
-							$el   = $link.children();
-
-						if ($el.length > 0)
-						{
-							while ($el.length)
-							{
-								$el = $el.children();
-							}
-
-							$el = $el.end();
-						}
-						else
-						{
-							$el = $link;
-						}
-
-						$link.attr('href', link);
-						$el.text(text);
-
-						if (target !== '')
-						{
-							$link.attr('target', target);
-						}
-						else
-						{
-							$link.removeAttr('target');
-						}
-
-						this.selection.selectElement($link);
-
-						this.code.sync();
-					}
-					else
-					{
-						if (this.utils.browser('mozilla') && this.link.text === '')
-						{
-							var $a = $('<a />').attr('href', link).text(text);
-							if (target !== '') $a.attr('target', target);
-
-							$a = $(this.insert.node($a));
-
-							if (this.opts.linebreaks)
-							{
-								$a.after('&nbsp;');
-							}
-
-							this.selection.selectElement($a);
-						}
-						else
-						{
-							var $a;
-							if (this.utils.browser('msie'))
-							{
-								$a = $('<a href="' + link + '">').text(text);
-								if (target !== '') $a.attr('target', target);
-
-								$a = $(this.insert.node($a));
-
-								if (this.selection.getText().match(/\s$/))
-								{
-									$a.after(" ");
-								}
-
-								this.selection.selectElement($a);
-							}
-							else
-							{
-								document.execCommand('createLink', false, link);
-
-								$a = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
-								if (this.utils.browser('mozilla'))
-								{
-									$a = $('a[_moz_dirty=""]');
-								}
-
-								if (target !== '') $a.attr('target', target);
-								$a.removeAttr('style').removeAttr('_moz_dirty');
-
-								if (this.selection.getText().match(/\s$/))
-								{
-									$a.after(" ");
-								}
-
-								if (this.link.text !== '' || this.link.text != text)
-								{
-									if (!this.opts.linebreaks && blocks && blocks.length <= 1)
-									{
-										$a.text(text);
-									}
-									else if (this.opts.linebreaks)
-									{
-										$a.text(text);
-									}
-
-									this.selection.selectElement($a);
-								}
-							}
-						}
-
-						this.code.sync();
-						this.core.setCallback('insertedLink', $a);
-
-					}
-
-					// link tooltip
-					setTimeout($.proxy(function()
-					{
-						this.observe.links();
-
-					}, this), 5);
-				},
-				unlink: function(e)
-				{
-					if (typeof e != 'undefined' && e.preventDefault)
-					{
-						e.preventDefault();
-					}
-
-					var nodes = this.selection.getNodes();
-					if (!nodes) return;
-
-					this.buffer.set();
-
-					var len = nodes.length;
-					var links = [];
-					for (var i = 0; i < len; i++)
-					{
-						if (nodes[i].tagName === 'A')
-						{
-							links.push(nodes[i]);
-						}
-
-						var $node = $(nodes[i]).closest('a', this.$editor[0]);
-						$node.replaceWith($node.contents());
-					}
-
-					this.core.setCallback('deletedLink', links);
-
-					// hide link's tooltip
-					$('.redactor-link-tooltip').remove();
-
-					this.code.sync();
-
-				},
-				toggleClass: function(className)
-				{
-					this.link.setClass(className, 'toggleClass');
-				},
-				addClass: function(className)
-				{
-					this.link.setClass(className, 'addClass');
-				},
-				removeClass: function(className)
-				{
-					this.link.setClass(className, 'removeClass');
-				},
-				setClass: function(className, func)
-				{
-					var links = this.selection.getInlinesTags(['a']);
-					if (links === false) return;
-
-					$.each(links, function()
-					{
-						$(this)[func](className);
-					});
-				}
-			};
-		},
-		linkify: function()
-		{
-			return {
-				isKey: function(key)
-				{
-					return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
-				},
-				isEnabled: function()
-				{
-					return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
-				},
-				format: function()
-				{
-					var linkify = this.linkify,
-						opts    = this.opts;
-
-					this.$editor
-						.find(":not(iframe,img,a,pre)")
-						.addBack()
-						.contents()
-						.filter(function()
-						{
-							return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
-						})
-						.each(function()
-						{
-							var text = $(this).text(),
-								html = text;
-
-							if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) )
-							{
-								html = linkify.convertVideoLinks(html);
-							}
-							else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
-							{
-								html = linkify.convertImages(html);
-							}
-							else if (opts.convertUrlLinks)
-							{
-								html = linkify.convertLinks(html);
-							}
-
-							$(this).before(text.replace(text, html))
-								   .remove();
-						});
-
-
-					var objects = this.$editor.find('.redactor-linkify-object').each(function()
-					{
-						var $el = $(this);
-						$el.removeClass('redactor-linkify-object');
-						if ($el.attr('class') === '') $el.removeAttr('class');
-
-						return $el[0];
-
-					});
-
-					// callback
-					setTimeout($.proxy(function()
-					{
-						this.observe.load();
-						this.core.setCallback('linkify', objects);
-					}, this), 100);
-
-					// sync
-					this.code.sync();
-				},
-				convertVideoLinks: function(html)
-				{
-					var iframeStart = '<iframe class="redactor-linkify-object" width="500" height="281" src="',
-						iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
-
-					if (html.match(this.opts.linkify.regexps.youtube))
-					{
-						html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
-					}
-
-					if (html.match(this.opts.linkify.regexps.vimeo))
-					{
-						html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
-					}
-
-					return html;
-				},
-				convertImages: function(html)
-				{
-					var matches = html.match(this.opts.linkify.regexps.image);
-
-					if (matches)
-					{
-						html = html.replace(html, '<img src="' + matches + '" class="redactor-linkify-object" />');
-
-						if (this.opts.linebreaks)
-						{
-							if (!this.utils.isEmpty(this.code.get()))
-							{
-								html = '<br>' + html;
-							}
-						}
-
-						html += '<br>';
-					}
-
-					return html;
-				},
-				convertLinks: function(html)
-				{
-					var matches = html.match(this.opts.linkify.regexps.url);
-
-					if (matches)
-					{
-						matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
-
-						var length = matches.length;
-
-						for (var i = 0; i < length; i++)
-						{
-							var href = matches[i],
-								text = href,
-								linkProtocol = this.opts.linkProtocol + '://';
-
-							if (href.match(/(https?|ftp):\/\//i) !== null)
-							{
-								linkProtocol = "";
-							}
-
-							if (text.length > this.opts.linkSize)
-							{
-								text = text.substring(0, this.opts.linkSize) + '...';
-							}
-
-							if (text.search('%') === -1)
-							{
-								text = decodeURIComponent(text);
-							}
-
-							var regexB = "\\b";
-
-							if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1)
-							{
-								regexB = "";
-							}
-
-							// escaping url
-							var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
-
-							html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '" class="redactor-linkify-object">' + $.trim(text) + '</a>');
-						}
-					}
-
-					return html;
-				}
-			};
-		},
-		list: function()
-		{
-			return {
-				toggle: function(cmd)
-				{
-					this.placeholder.remove();
-
-					if (!this.utils.browser('msie') && !this.opts.linebreaks)
-					{
-						this.$editor.focus();
-					}
-
-					this.buffer.set();
-					this.selection.save();
-
-					var parent = this.selection.getParent();
-					var $list = $(parent).closest('ol, ul', this.$editor[0]);
-
-					if (!this.utils.isRedactorParent($list) && $list.length !== 0)
-					{
-						$list = false;
-					}
-
-					var isUnorderedCmdOrdered, isOrderedCmdUnordered;
-					var remove = false;
-					if ($list && $list.length)
-					{
-						remove = true;
-						var listTag = $list[0].tagName;
-
-						isUnorderedCmdOrdered = (cmd === 'orderedlist' && listTag === 'UL');
-						isOrderedCmdUnordered = (cmd === 'unorderedlist' && listTag === 'OL');
-					}
-
-					if (isUnorderedCmdOrdered)
-					{
-						this.utils.replaceToTag($list, 'ol');
-					}
-					else if (isOrderedCmdUnordered)
-					{
-						this.utils.replaceToTag($list, 'ul');
-					}
-					else
-					{
-						if (remove)
-						{
-							this.list.remove(cmd, $list);
-						}
-						else
-						{
-							this.list.insert(cmd);
-						}
-					}
-
-					this.selection.restore();
-					this.code.sync();
-
-				},
-				insert: function(cmd)
-				{
-					var current = this.selection.getCurrent();
-					var $td = $(current).closest('td, th', this.$editor[0]);
-
-					if (this.utils.browser('msie') && this.opts.linebreaks)
-					{
-						this.list.insertInIe(cmd);
-					}
-					else
-					{
-						document.execCommand('insert' + cmd);
-					}
-
-					var parent = this.selection.getParent();
-					var $list = $(parent).closest('ol, ul', this.$editor[0]);
-					if ($td.length !== 0)
-					{
-						var newTd = $td.clone();
-						$td.after(newTd).remove('');
-					}
-
-
-					if (this.utils.isEmpty($list.find('li').text()))
-					{
-						var $children = $list.children('li');
-						$children.find('br').remove();
-						$children.append(this.selection.getMarkerAsHtml());
-
-						if (this.opts.linebreaks && this.utils.browser('mozilla') && $children.size() == 2 && this.utils.isEmpty($children.eq(1).text()))
-						{
-							$children.eq(1).remove();
-						}
-					}
-
-					if ($list.length)
-					{
-						// remove block-element list wrapper
-						var $listParent = $list.parent();
-						if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName != 'LI' && this.utils.isBlock($listParent[0]))
-						{
-							$listParent.replaceWith($listParent.contents());
-						}
-					}
-
-					if (!this.utils.browser('msie'))
-					{
-						this.$editor.focus();
-					}
-
-
-					this.clean.clearUnverified();
-				},
-				insertInIe: function(cmd)
-				{
-					var wrapper = this.selection.wrap('div');
-					var wrapperHtml = $(wrapper).html();
-
-					var tmpList = (cmd == 'orderedlist') ? $('<ol>') : $('<ul>');
-					var tmpLi = $('<li>');
-
-					if ($.trim(wrapperHtml) === '')
-					{
-						tmpLi.append(this.selection.getMarkerAsHtml());
-						tmpList.append(tmpLi);
-						this.$editor.find('#selection-marker-1').replaceWith(tmpList);
-					}
-					else
-					{
-						var items = wrapperHtml.split(/<br\s?\/?>/gi);
-						if (items)
-						{
-							for (var i = 0; i < items.length; i++)
-							{
-								if ($.trim(items[i]) !== '')
-								{
-									tmpList.append($('<li>').html(items[i]));
-								}
-							}
-						}
-						else
-						{
-							tmpLi.append(wrapperHtml);
-							tmpList.append(tmpLi);
-						}
-
-						$(wrapper).replaceWith(tmpList);
-					}
-				},
-				remove: function(cmd, $list)
-				{
-					if ($.inArray('ul', this.selection.getBlocks())) cmd = 'unorderedlist';
-
-					document.execCommand('insert' + cmd);
-
-					var $current = $(this.selection.getCurrent());
-					this.indent.fixEmptyIndent();
-
-					if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0)
-					{
-						document.execCommand('formatblock', false, 'p');
-						this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this));
-					}
-
-					var $table = $(this.selection.getCurrent()).closest('table', this.$editor[0]);
-					var $prev = $table.prev();
-					if (!this.opts.linebreaks && $table.length !== 0 && $prev.length !== 0 && $prev[0].tagName == 'BR')
-					{
-						$prev.remove();
-					}
-
-					this.clean.clearUnverified();
-
-
-				}
-			};
-		},
-		modal: function()
-		{
-			return {
-				callbacks: {},
-				loadTemplates: function()
-				{
-					this.opts.modal = {
-						imageEdit: String()
-						+ '<section id="redactor-modal-image-edit">'
-							+ '<label>' + this.lang.get('title') + '</label>'
-							+ '<input type="text" id="redactor-image-title" />'
-							+ '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
-							+ '<input type="text" id="redactor-image-link" class="redactor-image-link-option" aria-label="' + this.lang.get('link') + '" />'
-							+ '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link_new_tab') + '"> ' + this.lang.get('link_new_tab') + '</label>'
-							+ '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
-							+ '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image_position') + '">'
-								+ '<option value="none">' + this.lang.get('none') + '</option>'
-								+ '<option value="left">' + this.lang.get('left') + '</option>'
-								+ '<option value="center">' + this.lang.get('center') + '</option>'
-								+ '<option value="right">' + this.lang.get('right') + '</option>'
-							+ '</select>'
-						+ '</section>',
-
-						image: String()
-						+ '<section id="redactor-modal-image-insert">'
-							+ '<div id="redactor-modal-image-droparea"></div>'
- 						+ '</section>',
-
-						file: String()
-						+ '<section id="redactor-modal-file-insert">'
-							+ '<div id="redactor-modal-file-upload-box">'
-								+ '<label>' + this.lang.get('filename') + '</label>'
-								+ '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>'
-								+ '<div id="redactor-modal-file-upload"></div>'
-							+ '</div>'
-						+ '</section>',
-
-						link: String()
-						+ '<section id="redactor-modal-link-insert">'
-							+ '<label>URL</label>'
-							+ '<input type="url" id="redactor-link-url" aria-label="URL" />'
-							+ '<label>' + this.lang.get('text') + '</label>'
-							+ '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />'
-							+ '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
-						+ '</section>'
-					};
-
-
-					$.extend(this.opts, this.opts.modal);
-
-				},
-				addCallback: function(name, callback)
-				{
-					this.modal.callbacks[name] = callback;
-				},
-				createTabber: function($modal)
-				{
-					this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
-
-					$modal.prepend(this.modal.$tabber);
-				},
-				addTab: function(id, name, active)
-				{
-					var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
-					if (active)
-					{
-						$tab.addClass('active');
-					}
-
-					var self = this;
-					$tab.on('click', function(e)
-					{
-						e.preventDefault();
-						$('.redactor-tab').hide();
-						$('.redactor-' + $(this).attr('rel')).show();
-
-						self.modal.$tabber.find('a').removeClass('active');
-						$(this).addClass('active');
-
-					});
-
-					this.modal.$tabber.append($tab);
-				},
-				addTemplate: function(name, template)
-				{
-					this.opts.modal[name] = template;
-				},
-				getTemplate: function(name)
-				{
-					return this.opts.modal[name];
-				},
-				getModal: function()
-				{
-					return this.$modalBody.find('section');
-				},
-				load: function(templateName, title, width)
-				{
-					this.modal.templateName = templateName;
-					this.modal.width = width;
-
-					this.modal.build();
-					this.modal.enableEvents();
-					this.modal.setTitle(title);
-					this.modal.setDraggable();
-					this.modal.setContent();
-
-					// callbacks
-					if (typeof this.modal.callbacks[templateName] != 'undefined')
-					{
-						this.modal.callbacks[templateName].call(this);
-					}
-
-				},
-				show: function()
-				{
-					this.utils.disableBodyScroll();
-
-					if (this.utils.isMobile())
-					{
-						this.modal.showOnMobile();
-					}
-					else
-					{
-						this.modal.showOnDesktop();
-					}
-
-					if (this.opts.highContrast)
-					{
-						this.$modalBox.addClass("redactor-modal-contrast");
-					}
-
-					this.$modalOverlay.show();
-					this.$modalBox.show();
-
-					this.$modal.attr('tabindex', '-1');
-					this.$modal.focus();
-
-					this.modal.setButtonsWidth();
-
-					this.utils.saveScroll();
-
-					// resize
-					if (!this.utils.isMobile())
-					{
-						setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
-						$(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
-					}
-
-					// modal shown callback
-					this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
-
-					// fix bootstrap modal focus
-					$(document).off('focusin.modal');
-
-					// enter
-					this.$modal.find('input[type=text],input[type=url],input[type=email]').on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this));
-				},
-				showOnDesktop: function()
-				{
-					var height = this.$modal.outerHeight();
-					var windowHeight = $(window).height();
-					var windowWidth = $(window).width();
-
-					if (this.modal.width > windowWidth)
-					{
-						this.$modal.css({
-							width: '96%',
-							marginTop: (windowHeight/2 - height/2) + 'px'
-						});
-						return;
-					}
-
-					if (height > windowHeight)
-					{
-						this.$modal.css({
-							width: this.modal.width + 'px',
-							marginTop: '20px'
-						});
-					}
-					else
-					{
-						this.$modal.css({
-							width: this.modal.width + 'px',
-							marginTop: (windowHeight/2 - height/2) + 'px'
-						});
-					}
-				},
-				showOnMobile: function()
-				{
-					this.$modal.css({
-						width: '96%',
-						marginTop: '2%'
-					});
-
-				},
-				resize: function()
-				{
-					if (this.utils.isMobile())
-					{
-						this.modal.showOnMobile();
-					}
-					else
-					{
-						this.modal.showOnDesktop();
-					}
-				},
-				setTitle: function(title)
-				{
-					this.$modalHeader.html(title);
-				},
-				setContent: function()
-				{
-					this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
-				},
-				setDraggable: function()
-				{
-					if (typeof $.fn.draggable === 'undefined') return;
-
-					this.$modal.draggable({ handle: this.$modalHeader });
-					this.$modalHeader.css('cursor', 'move');
-				},
-				setEnter: function(e)
-				{
-					if (e.which != 13) return;
-
-					e.preventDefault();
-					this.$modal.find('button.redactor-modal-action-btn').click();
-				},
-				createCancelButton: function()
-				{
-					var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
-					button.on('click', $.proxy(this.modal.close, this));
-
-					this.$modalFooter.append(button);
-				},
-				createDeleteButton: function(label)
-				{
-					return this.modal.createButton(label, 'delete');
-				},
-				createActionButton: function(label)
-				{
-					return this.modal.createButton(label, 'action');
-				},
-				createButton: function(label, className)
-				{
-					var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
-					this.$modalFooter.append(button);
-
-					return button;
-				},
-				setButtonsWidth: function()
-				{
-					var buttons = this.$modalFooter.find('button');
-					var buttonsSize = buttons.length;
-					if (buttonsSize === 0) return;
-
-					buttons.css('width', (100/buttonsSize) + '%');
-				},
-				build: function()
-				{
-					this.modal.buildOverlay();
-
-					this.$modalBox = $('<div id="redactor-modal-box"/>').hide();
-					this.$modal = $('<div id="redactor-modal" role="dialog" aria-labelledby="redactor-modal-header" />');
-					this.$modalHeader = $('<header id="redactor-modal-header"/>');
-					this.$modalClose = $('<button type="button" id="redactor-modal-close" tabindex="1" aria-label="Close" />').html('&times;');
-					this.$modalBody = $('<div id="redactor-modal-body" />');
-					this.$modalFooter = $('<footer />');
-
-					this.$modal.append(this.$modalHeader);
-					this.$modal.append(this.$modalClose);
-					this.$modal.append(this.$modalBody);
-					this.$modal.append(this.$modalFooter);
-					this.$modalBox.append(this.$modal);
-					this.$modalBox.appendTo(document.body);
-				},
-				buildOverlay: function()
-				{
-					this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
-					$('body').prepend(this.$modalOverlay);
-				},
-				enableEvents: function()
-				{
-					this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
-					$(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
-					this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
-					this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
-				},
-				disableEvents: function()
-				{
-					this.$modalClose.off('click.redactor-modal');
-					$(document).off('keyup.redactor-modal');
-					this.$editor.off('keyup.redactor-modal');
-					this.$modalBox.off('click.redactor-modal');
-					$(window).off('resize.redactor-modal');
-				},
-				closeHandler: function(e)
-				{
-					if (e.which != this.keyCode.ESC) return;
-
-					this.modal.close(false);
-				},
-				close: function(e)
-				{
-					if (e)
-					{
-						if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
-						{
-							return;
-						}
-
-						e.preventDefault();
-					}
-
-					if (!this.$modalBox) return;
-
-					this.modal.disableEvents();
-					this.utils.enableBodyScroll();
-
-					this.$modalOverlay.remove();
-
-					this.$modalBox.fadeOut('fast', $.proxy(function()
-					{
-						this.$modalBox.remove();
-
-						setTimeout($.proxy(this.utils.restoreScroll, this), 0);
-
-						if (e !== undefined) this.selection.restore();
-
-						$(document.body).css('overflow', this.modal.bodyOveflow);
-						this.core.setCallback('modalClosed', this.modal.templateName);
-
-					}, this));
-
-				}
-			};
-		},
-		observe: function()
-		{
-			return {
-				load: function()
-				{
-					if (typeof this.opts.destroyed != "undefined") return;
-
-					if (this.utils.browser('msie'))
-					{
-						var self = this;
-						this.$editor.find('pre, code').on('mouseover',function()
-						{
-							self.$editor.attr('contenteditable', false);
-							$(this).attr('contenteditable', true);
-
-						}).on('mouseout',function()
-						{
-							self.$editor.attr('contenteditable', true);
-							$(this).removeAttr('contenteditable');
-
-						});
-					}
-
-					this.observe.images();
-					this.observe.links();
-				},
-				toolbar: function(e, btnName)
-				{
-					this.observe.buttons(e, btnName);
-					this.observe.dropdowns();
-				},
-				isCurrent: function($el, $current)
-				{
-					if (typeof $current == 'undefined')
-					{
-						var $current = $(this.selection.getCurrent());
-					}
-
-					return $current.is($el) || $current.parents($el).length > 0;
-				},
-				dropdowns: function()
-				{
-					var $current = $(this.selection.getCurrent());
-
-					$.each(this.opts.observe.dropdowns, $.proxy(function(key, value)
-					{
-						var observe = value.observe,
-							element = observe.element,
-							$item   = value.item,
-							inValues = typeof observe['in'] != 'undefined' ? observe['in'] : false,
-							outValues = typeof observe['out'] != 'undefined' ? observe['out'] : false;
-
-						if ($current.closest(element).size() > 0)
-						{
-							this.observe.setDropdownProperties($item, inValues, outValues);
-						}
-						else
-						{
-							this.observe.setDropdownProperties($item, outValues, inValues);
-						}
-					}, this));
-				},
-				setDropdownProperties: function($item, addProperties, deleteProperties)
-				{
-					if (deleteProperties && typeof deleteProperties['attr'] != 'undefined')
-					{
-						this.observe.setDropdownAttr($item, deleteProperties.attr, true);
-					}
-
-					if (typeof addProperties['attr'] != 'undefined')
-					{
-						this.observe.setDropdownAttr($item, addProperties.attr);
-					}
-
-					if (typeof addProperties['title'] != 'undefined')
-					{
-						$item.text(addProperties['title']);
-					}
-				},
-				setDropdownAttr: function($item, properties, isDelete)
-				{
-					$.each(properties, function(key, value)
-					{
-						if (key == 'class')
-						{
-							if (!isDelete)
-							{
-								$item.addClass(value);
-							}
-							else
-							{
-								$item.removeClass(value);
-							}
-						}
-						else
-						{
-							if (!isDelete)
-							{
-								$item.attr(key, value);
-							}
-							else
-							{
-								$item.removeAttr(key);
-							}
-						}
-					});
-				},
-				addDropdown: function($item, btnName, btnObject)
-				{
-					if (typeof btnObject.observe == "undefined") return;
-
-					btnObject.item = $item;
-
-					this.opts.observe.dropdowns.push(btnObject);
-				},
-				buttons: function(e, btnName)
-				{
-					var current = this.selection.getCurrent();
-					var parent = this.selection.getParent();
-
-					if (e !== false)
-					{
-						this.button.setInactiveAll();
-					}
-					else
-					{
-						this.button.setInactiveAll(btnName);
-					}
-
-					if (e === false && btnName !== 'html')
-					{
-						if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
-						return;
-					}
-
-					//var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
-					//$('body').find('a.redactor-dropdown-link').text(linkButtonName);
-
-					$.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
-					{
-						var parentEl = $(parent).closest(key, this.$editor[0]);
-						var currentEl = $(current).closest(key, this.$editor[0]);
-
-						if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) return;
-						if (!this.utils.isRedactorParent(currentEl)) return;
-						if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0)
-						{
-							this.button.setActive(value);
-						}
-
-					}, this));
-
-					var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-					if (this.utils.isRedactorParent(parent) && $parent.length)
-					{
-						var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
-						this.button.setActive('align' + align);
-					}
-				},
-				addButton: function(tag, btnName)
-				{
-					this.opts.activeButtons.push(btnName);
-					this.opts.activeButtonsStates[tag] = btnName;
-				},
-				images: function()
-				{
-					this.$editor.find('img').each($.proxy(function(i, img)
-					{
-						var $img = $(img);
-
-						// IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
-						$img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); });
-
-						if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
-
-						this.image.setEditable($img);
-
-					}, this));
-
-					$(document).on('click.redactor-image-delete.' + this.uuid, $.proxy(function(e)
-					{
-						this.observe.image = false;
-						if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
-						{
-							this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
-						}
-
-					}, this));
-
-				},
-				links: function()
-				{
-					if (!this.opts.linkTooltip) return;
-
-					this.$editor.find('a').on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this));
-					this.$editor.on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
-					$(document).on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
-				},
-				getTooltipPosition: function($link)
-				{
-					return $link.offset();
-				},
-				showTooltip: function(e)
-				{
-					var $el = $(e.target);
-
-					if ($el[0].tagName == 'IMG')
-						return;
-
-					if ($el[0].tagName !== 'A')
-						$el = $el.closest('a', this.$editor[0]);
-
-					if ($el[0].tagName !== 'A')
-						return;
-
-					var $link = $el;
-
-					var pos = this.observe.getTooltipPosition($link);
-					var tooltip = $('<span class="redactor-link-tooltip"></span>');
-
-					var href = $link.attr('href');
-					if (href === undefined)
-					{
-						href = '';
-					}
-
-					if (href.length > 24) href = href.substring(0, 24) + '...';
-
-					var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
-					var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
-					var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
-
-					tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
-					tooltip.css({
-						top: (pos.top + parseInt($link.css('line-height'), 10)) + 'px',
-						left: pos.left + 'px'
-					});
-
-					$('.redactor-link-tooltip').remove();
-					$('body').append(tooltip);
-				},
-				closeTooltip: function(e)
-				{
-					e = e.originalEvent || e;
-
-					var target = e.target;
-					var $parent = $(target).closest('a', this.$editor[0]);
-					if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
-					{
-						return;
-					}
-					else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action'))
-					{
-						return;
-					}
-
-					$('.redactor-link-tooltip').remove();
-				}
-
-			};
-		},
-		paragraphize: function()
-		{
-			return {
-				load: function(html)
-				{
-					if (this.opts.linebreaks) return html;
-					if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
-
-					html = html + "\n";
-
-					this.paragraphize.safes = [];
-					this.paragraphize.z = 0;
-
-					html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
-
-					html = this.paragraphize.getSafes(html);
-					html = this.paragraphize.getSafesComments(html);
-					html = this.paragraphize.replaceBreaksToNewLines(html);
-					html = this.paragraphize.replaceBreaksToParagraphs(html);
-					html = this.paragraphize.clear(html);
-					html = this.paragraphize.restoreSafes(html);
-
-					html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.opts.paragraphizeBlocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>');
-
-					return $.trim(html);
-				},
-				getSafes: function(html)
-				{
-					var $div = $('<div />').append(html);
-
-					// remove paragraphs in blockquotes
-					$div.find('blockquote p').replaceWith(function()
-					{
-						return $(this).append('<br />').contents();
-					});
-
-					html = $div.html();
-
-					$div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
-					{
-						this.paragraphize.z++;
-						this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
-						html = html.replace(s.outerHTML, '\n{replace' + this.paragraphize.z + '}');
-
-					}, this));
-
-					return html;
-				},
-				getSafesComments: function(html)
-				{
-					var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
-
-					if (!commentsMatches) return html;
-
-					$.each(commentsMatches, $.proxy(function(i,s)
-					{
-						this.paragraphize.z++;
-						this.paragraphize.safes[this.paragraphize.z] = s;
-						html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
-					}, this));
-
-					return html;
-				},
-				restoreSafes: function(html)
-				{
-					$.each(this.paragraphize.safes, function(i,s)
-					{
-						s = (typeof s !== 'undefined') ? s.replace(/\$/g, '&#36;') : s;
-						html = html.replace('{replace' + i + '}', s);
-
-					});
-
-					return html;
-				},
-				replaceBreaksToParagraphs: function(html)
-				{
-					var htmls = html.split(new RegExp('\n', 'g'), -1);
-
-					html = '';
-					if (htmls)
-					{
-						var len = htmls.length;
-						for (var i = 0; i < len; i++)
-						{
-							if (!htmls.hasOwnProperty(i)) return;
-
-							if (htmls[i].search('{replace') == -1)
-							{
-								htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
-								htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
-
-								if (htmls[i] !== '')
-								{
-									html += '<p>' +  htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
-								}
-							}
-							else html += htmls[i];
-						}
-					}
-
-					return html;
-				},
-				replaceBreaksToNewLines: function(html)
-				{
-					html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
-					html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
-
-					html = html.replace(new RegExp("\r\n", 'g'), "\n");
-					html = html.replace(new RegExp("\r", 'g'), "\n");
-					html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
-
-					return html;
-				},
-				clear: function(html)
-				{
-					html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
-					html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
-					html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
-					html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
-
-					html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
-					html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
-					html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
-					html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
-					html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
-					html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
-					html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
-
-					return html;
-				}
-			};
-		},
-		paste: function()
-		{
-			return {
-				init: function(e)
-				{
-					if (!this.opts.cleanOnPaste)
-					{
-						setTimeout($.proxy(this.code.sync, this), 1);
-						return;
-					}
-
-					this.rtePaste = true;
-
-					this.buffer.set();
-					this.selection.save();
-					this.utils.saveScroll();
-
-					this.paste.createPasteBox();
-
-					$(window).on('scroll.redactor-freeze', $.proxy(function()
-					{
-						$(window).scrollTop(this.saveBodyScroll);
-
-					}, this));
-
-					setTimeout($.proxy(function()
-					{
-						var html = this.$pasteBox.html();
-
-						this.$pasteBox.remove();
-
-						this.selection.restore();
-						this.utils.restoreScroll();
-
-						this.paste.insert(html);
-
-						$(window).off('scroll.redactor-freeze');
-
-						if (this.linkify.isEnabled())
-						{
-							this.linkify.format();
-						}
-
-					}, this), 1);
-				},
-				createPasteBox: function()
-				{
-					this.$pasteBox = $('<div>').html('').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
-
-					if (this.utils.browser('msie'))
-					{
-						this.$box.append(this.$pasteBox);
-					}
-					else
-					{
-						// bootstrap modal
-						var $visibleModals = $('.modal-body:visible');
-						if ($visibleModals.length > 0)
-						{
-							$visibleModals.append(this.$pasteBox);
-						}
-						else
-						{
-							$('body').append(this.$pasteBox);
-						}
-					}
-
-					this.$pasteBox.get(0).focus();
-				},
-				insert: function(html)
-				{
-					html = this.core.setCallback('pasteBefore', html);
-
-					// clean
-					html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
-
-					html = this.core.setCallback('paste', html);
-
-					if (this.utils.isSelectAll())
-					{
-						this.insert.set(html, false);
-					}
-					else
-					{
-						this.insert.html(html, false);
-					}
-
-					this.utils.disableSelectAll();
-					this.rtePaste = false;
-
-					setTimeout($.proxy(this.clean.clearUnverified, this), 10);
-
-					// clean empty spans
-					setTimeout($.proxy(function()
-					{
-						var spans = this.$editor.find('span');
-						$.each(spans, function(i,s)
-						{
-							var html = s.innerHTML.replace(/\u200B/, '');
-							if (html === '' && s.attributes.length === 0) $(s).remove();
-
-						});
-
-					}, this), 10);
-
-				}
-			};
-		},
-		placeholder: function()
-		{
-			return {
-				enable: function()
-				{
-					if (!this.placeholder.is()) return;
-
-					this.$editor.attr('placeholder', this.$element.attr('placeholder'));
-
-					this.placeholder.toggle();
-					this.$editor.on('keydown.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
-				},
-				toggle: function()
-				{
-					setTimeout($.proxy(function()
-					{
-						var func = this.utils.isEmpty(this.$editor.html(), false) ? 'addClass' : 'removeClass';
-						this.$editor[func]('redactor-placeholder');
-
-					}, this), 5);
-				},
-				remove: function()
-				{
-					this.$editor.removeClass('redactor-placeholder');
-				},
-				is: function()
-				{
-					if (this.opts.placeholder)
-					{
-						return this.$element.attr('placeholder', this.opts.placeholder);
-					}
-					else
-					{
-						return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
-					}
-				}
-			};
-		},
-		progress: function()
-		{
-			return {
-				show: function()
-				{
-					$(document.body).append($('<div id="redactor-progress"><span></span></div>'));
-					$('#redactor-progress').fadeIn();
-				},
-				hide: function()
-				{
-					$('#redactor-progress').fadeOut(1500, function()
-					{
-						$(this).remove();
-					});
-				}
-
-			};
-		},
-		selection: function()
-		{
-			return {
-				get: function()
-				{
-					this.sel = document.getSelection();
-
-					if (document.getSelection && this.sel.getRangeAt && this.sel.rangeCount)
-					{
-						this.range = this.sel.getRangeAt(0);
-					}
-					else
-					{
-						this.range = document.createRange();
-					}
-				},
-				addRange: function()
-				{
-					try {
-						this.sel.removeAllRanges();
-					} catch (e) {}
-
-					this.sel.addRange(this.range);
-				},
-				getCurrent: function()
-				{
-					var el = false;
-
-					this.selection.get();
-
-					if (this.sel && this.sel.rangeCount > 0)
-					{
-						el = this.sel.getRangeAt(0).startContainer;
-					}
-
-					return this.utils.isRedactorParent(el);
-				},
-				getParent: function(elem)
-				{
-					elem = elem || this.selection.getCurrent();
-					if (elem)
-					{
-						return this.utils.isRedactorParent($(elem).parent()[0]);
-					}
-
-					return false;
-				},
-				getPrev: function()
-				{
-					return  window.getSelection().anchorNode.previousSibling;
-				},
-				getNext: function()
-				{
-					return window.getSelection().anchorNode.nextSibling;
-				},
-				getBlock: function(node)
-				{
-					node = node || this.selection.getCurrent();
-
-					while (node)
-					{
-						if (this.utils.isBlockTag(node.tagName))
-						{
-							return ($(node).hasClass('redactor-editor')) ? false : node;
-						}
-
-						node = node.parentNode;
-					}
-
-					return false;
-				},
-				getInlines: function(nodes, tags)
-				{
-					this.selection.get();
-
-					if (this.range && this.range.collapsed)
-					{
-						return false;
-					}
-
-					var inlines = [];
-					nodes = (typeof nodes == 'undefined' || nodes === false) ? this.selection.getNodes() : nodes;
-					var inlineTags = this.opts.inlineTags;
-					inlineTags.push('span');
-
-					if (typeof tags !== 'undefined')
-					{
-						for (var i = 0; i < tags.length; i++)
-						{
-							inlineTags.push(tags[i]);
-						}
-					}
-
-					$.each(nodes, $.proxy(function(i,node)
-					{
-						if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1)
-						{
-							inlines.push(node);
-						}
-
-					}, this));
-
-					return (inlines.length === 0) ? false : inlines;
-				},
-				getInlinesTags: function(tags)
-				{
-					this.selection.get();
-
-					if (this.range && this.range.collapsed)
-					{
-						return false;
-					}
-
-					var inlines = [];
-					var nodes =  this.selection.getNodes();
-					$.each(nodes, $.proxy(function(i,node)
-					{
-						if ($.inArray(node.tagName.toLowerCase(), tags) != -1)
-						{
-							inlines.push(node);
-						}
-
-					}, this));
-
-					return (inlines.length === 0) ? false : inlines;
-				},
-				getBlocks: function(nodes)
-				{
-					this.selection.get();
-
-					if (this.range && this.range.collapsed)
-					{
-						return [this.selection.getBlock()];
-					}
-
-					var blocks = [];
-					nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
-
-					$.each(nodes, $.proxy(function(i,node)
-					{
-						if (this.utils.isBlock(node))
-						{
-							blocks.push(node);
-						}
-
-					}, this));
-
-					return (blocks.length === 0) ? [this.selection.getBlock()] : blocks;
-				},
-				getLastBlock: function()
-				{
-					return this.selection.lastBlock;
-				},
-				getNodes: function()
-				{
-					this.selection.get();
-
-					var startNode = this.selection.getNodesMarker(1);
-					var endNode = this.selection.getNodesMarker(2);
-
-					if (this.range.collapsed === false)
-					{
-					   if (window.getSelection) {
-					        var sel = window.getSelection();
-					        if (sel.rangeCount > 0) {
-
-					            var range = sel.getRangeAt(0);
-					            var startPointNode = range.startContainer, startOffset = range.startOffset;
-
-					            var boundaryRange = range.cloneRange();
-					            boundaryRange.collapse(false);
-					            boundaryRange.insertNode(endNode);
-					            boundaryRange.setStart(startPointNode, startOffset);
-					            boundaryRange.collapse(true);
-					            boundaryRange.insertNode(startNode);
-
-					            // Reselect the original text
-					            range.setStartAfter(startNode);
-					            range.setEndBefore(endNode);
-					            sel.removeAllRanges();
-					            sel.addRange(range);
-					        }
-					    }
-					}
-					else
-					{
-						this.selection.setNodesMarker(this.range, startNode, true);
-						endNode = startNode;
-					}
-
-					var nodes = [];
-					var counter = 0;
-
-					var self = this;
-					this.$editor.find('*').each(function()
-					{
-						if (this == startNode)
-						{
-							var parent = $(this).parent();
-							if (parent.length !== 0 && parent[0].tagName != 'BODY' && self.utils.isRedactorParent(parent[0]))
-							{
-								nodes.push(parent[0]);
-							}
-
-							nodes.push(this);
-							counter = 1;
-						}
-						else
-						{
-							if (counter > 0)
-							{
-								nodes.push(this);
-								counter = counter + 1;
-							}
-						}
-
-						if (this == endNode)
-						{
-							return false;
-						}
-
-					});
-
-					var finalNodes = [];
-					var len = nodes.length;
-					for (var i = 0; i < len; i++)
-					{
-						if (nodes[i].id != 'nodes-marker-1' && nodes[i].id != 'nodes-marker-2')
-						{
-							finalNodes.push(nodes[i]);
-						}
-					}
-
-					this.selection.removeNodesMarkers();
-
-					return finalNodes;
-
-				},
-				getNodesMarker: function(num)
-				{
-					return $('<span id="nodes-marker-' + num + '" class="redactor-nodes-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
-				},
-				setNodesMarker: function(range, node, type)
-				{
-					var range = range.cloneRange();
-
-					try {
-						range.collapse(type);
-						range.insertNode(node);
-					}
-					catch (e) {}
-				},
-				removeNodesMarkers: function()
-				{
-					$(document).find('span.redactor-nodes-marker').remove();
-					this.$editor.find('span.redactor-nodes-marker').remove();
-				},
-				fromPoint: function(start, end)
-				{
-					this.caret.setOffset(start, end);
-				},
-				wrap: function(tag)
-				{
-					this.selection.get();
-
-					if (this.range.collapsed) return false;
-
-					var wrapper = document.createElement(tag);
-					wrapper.appendChild(this.range.extractContents());
-					this.range.insertNode(wrapper);
-
-					return wrapper;
-				},
-				selectElement: function(node)
-				{
-					if (this.utils.browser('mozilla'))
-					{
-						node = node[0] || node;
-
-						var range = document.createRange();
-						range.selectNodeContents(node);
-					}
-					else
-					{
-						this.caret.set(node, 0, node, 1);
-					}
-				},
-				selectAll: function()
-				{
-					this.selection.get();
-					this.range.selectNodeContents(this.$editor[0]);
-					this.selection.addRange();
-				},
-				remove: function()
-				{
-					this.selection.get();
-					this.sel.removeAllRanges();
-				},
-				save: function()
-				{
-					this.selection.createMarkers();
-				},
-				createMarkers: function()
-				{
-					this.selection.get();
-
-					var node1 = this.selection.getMarker(1);
-
-					this.selection.setMarker(this.range, node1, true);
-					if (this.range.collapsed === false)
-					{
-						var node2 = this.selection.getMarker(2);
-						this.selection.setMarker(this.range, node2, false);
-					}
-
-					this.savedSel = this.$editor.html();
-				},
-				getMarker: function(num)
-				{
-					if (typeof num == 'undefined') num = 1;
-
-					return $('<span id="selection-marker-' + num + '" class="redactor-selection-marker"  data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
-				},
-				getMarkerAsHtml: function(num)
-				{
-					return this.utils.getOuterHtml(this.selection.getMarker(num));
-				},
-				setMarker: function(range, node, type)
-				{
-					range = range.cloneRange();
-
-					try {
-						range.collapse(type);
-						range.insertNode(node);
-
-					}
-					catch (e)
-					{
-						this.focus.setStart();
-					}
-
-				},
-				restore: function()
-				{
-					var node1 = this.$editor.find('span#selection-marker-1');
-					var node2 = this.$editor.find('span#selection-marker-2');
-
-					if (this.utils.browser('mozilla'))
-					{
-						this.$editor.focus();
-					}
-
-					if (node1.length !== 0 && node2.length !== 0)
-					{
-						this.caret.set(node1, 0, node2, 0);
-					}
-					else if (node1.length !== 0)
-					{
-						this.caret.set(node1, 0, node1, 0);
-					}
-					else
-					{
-						this.$editor.focus();
-					}
-
-					this.selection.removeMarkers();
-					this.savedSel = false;
-
-				},
-				removeMarkers: function()
-				{
-					this.$editor.find('span.redactor-selection-marker').each(function(i,s)
-					{
-						var text = $(s).text().replace(/\u200B/g, '');
-						if (text === '') $(s).remove();
-						else $(s).replaceWith(function() { return $(this).contents(); });
-					});
-				},
-				getText: function()
-				{
-					this.selection.get();
-
-					return this.sel.toString();
-				},
-				getHtml: function()
-				{
-					var html = '';
-
-					this.selection.get();
-					if (this.sel.rangeCount)
-					{
-						var container = document.createElement('div');
-						var len = this.sel.rangeCount;
-						for (var i = 0; i < len; ++i)
-						{
-							container.appendChild(this.sel.getRangeAt(i).cloneContents());
-						}
-
-						html = container.innerHTML;
-					}
-
-					return this.clean.onSync(html);
-				},
-				replaceSelection: function(html)
-				{
-					this.selection.get();
-					this.range.deleteContents();
-					var div = document.createElement("div");
-					div.innerHTML = html;
-					var frag = document.createDocumentFragment(), child;
-					while ((child = div.firstChild)) {
-						frag.appendChild(child);
-					}
-
-					this.range.insertNode(frag);
-				},
-				replaceWithHtml: function(html)
-				{
-					html = this.selection.getMarkerAsHtml(1) + html + this.selection.getMarkerAsHtml(2);
-
-					this.selection.get();
-
-					if (window.getSelection && window.getSelection().getRangeAt)
-					{
-						this.selection.replaceSelection(html);
-					}
-					else if (document.selection && document.selection.createRange)
-					{
-						this.range.pasteHTML(html);
-					}
-
-					this.selection.restore();
-					this.code.sync();
-				}
-			};
-		},
-		shortcuts: function()
-		{
-			return {
-				init: function(e, key)
-				{
-					// disable browser's hot keys for bold and italic
-					if (!this.opts.shortcuts)
-					{
-						if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
-						return false;
-					}
-
-					$.each(this.opts.shortcuts, $.proxy(function(str, command)
-					{
-						var keys = str.split(',');
-						var len = keys.length;
-						for (var i = 0; i < len; i++)
-						{
-							if (typeof keys[i] === 'string')
-							{
-								this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
-								{
-									var func;
-									if (command.func.search(/\./) != '-1')
-									{
-										func = command.func.split('.');
-										if (typeof this[func[0]] != 'undefined')
-										{
-											this[func[0]][func[1]].apply(this, command.params);
-										}
-									}
-									else
-									{
-										this[command.func].apply(this, command.params);
-									}
-
-								}, this));
-							}
-
-						}
-
-					}, this));
-				},
-				handler: function(e, keys, origHandler)
-				{
-					// based on https://github.com/jeresig/jquery.hotkeys
-					var hotkeysSpecialKeys =
-					{
-						8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
-						20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
-						37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
-						96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
-						104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
-						112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
-						120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
-						188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
-					};
-
-
-					var hotkeysShiftNums =
-					{
-						"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
-						"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
-						".": ">",  "/": "?",  "\\": "|"
-					};
-
-					keys = keys.toLowerCase().split(" ");
-					var special = hotkeysSpecialKeys[e.keyCode],
-						character = String.fromCharCode( e.which ).toLowerCase(),
-						modif = "", possible = {};
-
-					$.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
-					{
-						if (e[specialKey + 'Key'] && special !== specialKey)
-						{
-							modif += specialKey + '+';
-						}
-					});
-
-
-					if (special) possible[modif + special] = true;
-					if (character)
-					{
-						possible[modif + character] = true;
-						possible[modif + hotkeysShiftNums[character]] = true;
-
-						// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
-						if (modif === "shift+")
-						{
-							possible[hotkeysShiftNums[character]] = true;
-						}
-					}
-
-					for (var i = 0, len = keys.length; i < len; i++)
-					{
-						if (possible[keys[i]])
-						{
-							e.preventDefault();
-							return origHandler.apply(this, arguments);
-						}
-					}
-				}
-			};
-		},
-		tabifier: function()
-		{
-			return {
-				get: function(code)
-				{
-					if (!this.opts.tabifier) return code;
-
-					// clean setup
-					var ownLine = ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'];
-					var contOwnLine = ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'];
-					var newLevel = ['p', 'blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'pre', 'select', 'td', 'th', 'tr', 'ul'];
-
-					this.tabifier.lineBefore = new RegExp('^<(/?' + ownLine.join('|/?' ) + '|' + contOwnLine.join('|') + ')[ >]');
-					this.tabifier.lineAfter = new RegExp('^<(br|/?' + ownLine.join('|/?' ) + '|/' + contOwnLine.join('|/') + ')[ >]');
-					this.tabifier.newLevel = new RegExp('^</?(' + newLevel.join('|' ) + ')[ >]');
-
-					var i = 0,
-					codeLength = code.length,
-					point = 0,
-					start = null,
-					end = null,
-					tag = '',
-					out = '',
-					cont = '';
-
-					this.tabifier.cleanlevel = 0;
-
-					for (; i < codeLength; i++)
-					{
-						point = i;
-
-						// if no more tags, copy and exit
-						if (-1 == code.substr(i).indexOf( '<' ))
-						{
-							out += code.substr(i);
-
-							return this.tabifier.finish(out);
-						}
-
-						// copy verbatim until a tag
-						while (point < codeLength && code.charAt(point) != '<')
-						{
-							point++;
-						}
-
-						if (i != point)
-						{
-							cont = code.substr(i, point - i);
-							if (!cont.match(/^\s{2,}$/g))
-							{
-								if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
-								else if ('\n' == cont.charAt(0))
-								{
-									out += '\n' + this.tabifier.getTabs();
-									cont = cont.replace(/^\s+/, '');
-								}
-
-								out += cont;
-							}
-
-							if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
-						}
-
-						start = point;
-
-						// find the end of the tag
-						while (point < codeLength && '>' != code.charAt(point))
-						{
-							point++;
-						}
-
-						tag = code.substr(start, point - start);
-						i = point;
-
-						var t;
-
-						if ('!--' == tag.substr(1, 3))
-						{
-							if (!tag.match(/--$/))
-							{
-								while ('-->' != code.substr(point, 3))
-								{
-									point++;
-								}
-								point += 2;
-								tag = code.substr(start, point - start);
-								i = point;
-							}
-
-							if ('\n' != out.charAt(out.length - 1)) out += '\n';
-
-							out += this.tabifier.getTabs();
-							out += tag + '>\n';
-						}
-						else if ('!' == tag[1])
-						{
-							out = this.tabifier.placeTag(tag + '>', out);
-						}
-						else if ('?' == tag[1])
-						{
-							out += tag + '>\n';
-						}
-						else if (t = tag.match(/^<(script|style|pre)/i))
-						{
-							t[1] = t[1].toLowerCase();
-							tag = this.tabifier.cleanTag(tag);
-							out = this.tabifier.placeTag(tag, out);
-							end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
-
-							if (end)
-							{
-								cont = code.substr(i + 1, end);
-								i += end;
-								out += cont;
-							}
-						}
-						else
-						{
-							tag = this.tabifier.cleanTag(tag);
-							out = this.tabifier.placeTag(tag, out);
-						}
-					}
-
-					return this.tabifier.finish(out);
-				},
-				getTabs: function()
-				{
-					var s = '';
-					for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
-					{
-						s += '\t';
-					}
-
-					return s;
-				},
-				finish: function(code)
-				{
-					code = code.replace(/\n\s*\n/g, '\n');
-					code = code.replace(/^[\s\n]*/, '');
-					code = code.replace(/[\s\n]*$/, '');
-					code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
-
-					this.tabifier.cleanlevel = 0;
-
-					return code;
-				},
-				cleanTag: function (tag)
-				{
-					var tagout = '';
-					tag = tag.replace(/\n/g, ' ');
-					tag = tag.replace(/\s{2,}/g, ' ');
-					tag = tag.replace(/^\s+|\s+$/g, ' ');
-
-					var suffix = '';
-					if (tag.match(/\/$/))
-					{
-						suffix = '/';
-						tag = tag.replace(/\/+$/, '');
-					}
-
-					var m;
-					while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
-					{
-						if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
-						else if (m[1]) tagout += m[1].toLowerCase();
-
-						tagout += ' ';
-						tag = tag.substr(m[0].length);
-					}
-
-					return tagout.replace(/\s*$/, '') + suffix + '>';
-				},
-				placeTag: function (tag, out)
-				{
-					var nl = tag.match(this.tabifier.newLevel);
-
-					if (tag.match(this.tabifier.lineBefore) || nl)
-					{
-						out = out.replace(/\s*$/, '');
-						out += '\n';
-					}
-
-					if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
-					if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
-					if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
-
-					out += tag;
-
-					if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
-					{
-						out = out.replace(/ *$/, '');
-						//out += '\n';
-					}
-
-					return out;
-				}
-			};
-		},
-		tidy: function()
-		{
-			return {
-				setupAllowed: function()
-				{
-					var index = $.inArray('span', this.opts.removeEmpty);
-					if (index !== -1)
-					{
-						this.opts.removeEmpty.splice(index, 1);
-					}
-
-					if (this.opts.allowedTags) this.opts.deniedTags = false;
-					if (this.opts.allowedAttr) this.opts.removeAttr = false;
-
-					if (this.opts.linebreaks) return;
-
-					var tags = ['p', 'section'];
-					if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
-					if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
-
-				},
-				addToAllowed: function(tags)
-				{
-					var len = tags.length;
-					for (var i = 0; i < len; i++)
-					{
-						if ($.inArray(tags[i], this.opts.allowedTags) == -1)
-						{
-							this.opts.allowedTags.push(tags[i]);
-						}
-					}
-				},
-				removeFromDenied: function(tags)
-				{
-					var len = tags.length;
-					for (var i = 0; i < len; i++)
-					{
-						var pos = $.inArray(tags[i], this.opts.deniedTags);
-						if (pos != -1)
-						{
-							this.opts.deniedTags.splice(pos, 1);
-						}
-					}
-				},
-				load: function(html, options)
-				{
-					this.tidy.settings = {
-						deniedTags: this.opts.deniedTags,
-						allowedTags: this.opts.allowedTags,
-						removeComments: this.opts.removeComments,
-						replaceTags: this.opts.replaceTags,
-						replaceStyles: this.opts.replaceStyles,
-						removeDataAttr: this.opts.removeDataAttr,
-						removeAttr: this.opts.removeAttr,
-						allowedAttr: this.opts.allowedAttr,
-						removeWithoutAttr: this.opts.removeWithoutAttr,
-						removeEmpty: this.opts.removeEmpty
-					};
-
-					$.extend(this.tidy.settings, options);
-
-					html = this.tidy.removeComments(html);
-
-					// create container
-					this.tidy.$div = $('<div />').append(html);
-
-					// clean
-					this.tidy.replaceTags();
-					this.tidy.replaceStyles();
-					this.tidy.removeTags();
-
-					this.tidy.removeAttr();
-					this.tidy.removeEmpty();
-					this.tidy.removeParagraphsInLists();
-					this.tidy.removeDataAttr();
-					this.tidy.removeWithoutAttr();
-
-					html = this.tidy.$div.html();
-					this.tidy.$div.remove();
-
-					return html;
-				},
-				removeComments: function(html)
-				{
-					if (!this.tidy.settings.removeComments) return html;
-
-					return html.replace(/<!--[\s\S]*?-->/gi, '');
-				},
-				replaceTags: function(html)
-				{
-					if (!this.tidy.settings.replaceTags) return html;
-
-					var len = this.tidy.settings.replaceTags.length;
-					var replacement = [], rTags = [];
-					for (var i = 0; i < len; i++)
-					{
-						rTags.push(this.tidy.settings.replaceTags[i][1]);
-						replacement.push(this.tidy.settings.replaceTags[i][0]);
-					}
-
-					$.each(replacement, $.proxy(function(key, value)
-					{
-						this.tidy.$div.find(value).replaceWith(function()
-						{
-							return $("<" + rTags[key] + " />", {html: $(this).html()});
-						});
-					}, this));
-				},
-				replaceStyles: function()
-				{
-					if (!this.tidy.settings.replaceStyles) return;
-
-					var len = this.tidy.settings.replaceStyles.length;
-					this.tidy.$div.find('span').each($.proxy(function(n,s)
-					{
-						var $el = $(s);
-						var style = $el.attr('style');
-						for (var i = 0; i < len; i++)
-						{
-							if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
-							{
-								var tagName = this.tidy.settings.replaceStyles[i][1];
-								$el.replaceWith(function()
-								{
-									var tag = document.createElement(tagName);
-									return $(tag).append($(this).contents());
-								});
-							}
-						}
-
-					}, this));
-
-				},
-				removeTags: function()
-				{
-					if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
-					{
-						this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).each(function(i, s)
-						{
-							if (s.innerHTML === '') $(s).remove();
-							else $(s).contents().unwrap();
-						});
-					}
-
-					if (this.tidy.settings.deniedTags)
-					{
-						this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).each(function(i, s)
-						{
-							if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker')) return;
-
-							if (s.innerHTML === '') $(s).remove();
-							else $(s).contents().unwrap();
-						});
-					}
-				},
-				removeAttr: function()
-				{
-					var len;
-					if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
-					{
-
-						var allowedAttrTags = [], allowedAttrData = [];
-						len = this.tidy.settings.allowedAttr.length;
-						for (var i = 0; i < len; i++)
-						{
-							allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
-							allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
-						}
-
-
-						this.tidy.$div.find('*').each($.proxy(function(n,s)
-						{
-							var $el = $(s);
-							var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
-							var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
-
-							if (attributesRemove)
-							{
-								$.each(attributesRemove, function(z,f) {
-									$el.removeAttr(f);
-								});
-							}
-						}, this));
-					}
-
-					if (this.tidy.settings.removeAttr)
-					{
-						len = this.tidy.settings.removeAttr.length;
-						for (var i = 0; i < len; i++)
-						{
-							var attrs = this.tidy.settings.removeAttr[i][1];
-							if ($.isArray(attrs)) attrs = attrs.join(' ');
-
-							this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
-						}
-					}
-
-				},
-				removeAttrGetRemoves: function(pos, allowed, $el)
-				{
-					var attributesRemove = [];
-
-					// remove all attrs
-					if (pos == -1)
-					{
-						$.each($el[0].attributes, function(i, item)
-						{
-							attributesRemove.push(item.name);
-						});
-
-					}
-					// allow all attrs
-					else if (allowed[pos] == '*')
-					{
-						attributesRemove = [];
-					}
-					// allow specific attrs
-					else
-					{
-						$.each($el[0].attributes, function(i, item)
-						{
-							if ($.isArray(allowed[pos]))
-							{
-								if ($.inArray(item.name, allowed[pos]) == -1)
-								{
-									attributesRemove.push(item.name);
-								}
-							}
-							else if (allowed[pos] != item.name)
-							{
-								attributesRemove.push(item.name);
-							}
-
-						});
-					}
-
-					return attributesRemove;
-				},
-				removeAttrs: function (el, regex)
-				{
-					regex = new RegExp(regex, "g");
-					return el.each(function()
-					{
-						var self = $(this);
-						var len = this.attributes.length - 1;
-						for (var i = len; i >= 0; i--)
-						{
-							var item = this.attributes[i];
-							if (item && item.specified && item.name.search(regex)>=0)
-							{
-								self.removeAttr(item.name);
-							}
-						}
-					});
-				},
-				removeEmpty: function()
-				{
-					if (!this.tidy.settings.removeEmpty) return;
-
-					this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
-					{
-						var $el = $(this);
-						var text = $el.text();
-						text = text.replace(/\u200B/g, '');
-						text = text.replace(/&nbsp;/gi, '');
-						text = text.replace(/\s/g, '');
-
-		    	    	if (text === '' && $el.children().length === 0)
-		    	    	{
-			    	    	$el.remove();
-		    	    	}
-					});
-				},
-				removeParagraphsInLists: function()
-				{
-					this.tidy.$div.find('li p').contents().unwrap();
-				},
-				removeDataAttr: function()
-				{
-					if (!this.tidy.settings.removeDataAttr) return;
-
-					var tags = this.tidy.settings.removeDataAttr;
-					if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
-
-					this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
-
-				},
-				removeWithoutAttr: function()
-				{
-					if (!this.tidy.settings.removeWithoutAttr) return;
-
-					this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
-					{
-						if (this.attributes.length === 0)
-						{
-							$(this).contents().unwrap();
-						}
-					});
-				}
-			};
-		},
-		toolbar: function()
-		{
-			return {
-				init: function()
-				{
-					return {
-						html:
-						{
-							title: this.lang.get('html'),
-							func: 'code.toggle'
-						},
-						formatting:
-						{
-							title: this.lang.get('formatting'),
-							dropdown:
-							{
-								p:
-								{
-									title: this.lang.get('paragraph'),
-									func: 'block.format'
-								},
-								blockquote:
-								{
-									title: this.lang.get('quote'),
-									func: 'block.format'
-								},
-								pre:
-								{
-									title: this.lang.get('code'),
-									func: 'block.format'
-								},
-								h1:
-								{
-									title: this.lang.get('header1'),
-									func: 'block.format'
-								},
-								h2:
-								{
-									title: this.lang.get('header2'),
-									func: 'block.format'
-								},
-								h3:
-								{
-									title: this.lang.get('header3'),
-									func: 'block.format'
-								},
-								h4:
-								{
-									title: this.lang.get('header4'),
-									func: 'block.format'
-								},
-								h5:
-								{
-									title: this.lang.get('header5'),
-									func: 'block.format'
-								}
-							}
-						},
-						bold:
-						{
-							title: this.lang.get('bold'),
-							func: 'inline.format'
-						},
-						italic:
-						{
-							title: this.lang.get('italic'),
-							func: 'inline.format'
-						},
-						deleted:
-						{
-							title: this.lang.get('deleted'),
-							func: 'inline.format'
-						},
-						underline:
-						{
-							title: this.lang.get('underline'),
-							func: 'inline.format'
-						},
-						unorderedlist:
-						{
-							title: '&bull; ' + this.lang.get('unorderedlist'),
-							func: 'list.toggle'
-						},
-						orderedlist:
-						{
-							title: '1. ' + this.lang.get('orderedlist'),
-							func: 'list.toggle'
-						},
-						outdent:
-						{
-							title: '< ' + this.lang.get('outdent'),
-							func: 'indent.decrease'
-						},
-						indent:
-						{
-							title: '> ' + this.lang.get('indent'),
-							func: 'indent.increase'
-						},
-						image:
-						{
-							title: this.lang.get('image'),
-							func: 'image.show'
-						},
-						file:
-						{
-							title: this.lang.get('file'),
-							func: 'file.show'
-						},
-						link:
-						{
-							title: this.lang.get('link'),
-							dropdown:
-							{
-								link:
-								{
-									title: this.lang.get('link_insert'),
-									func: 'link.show',
-									observe: {
-										element: 'a',
-										in: {
-											title: this.lang.get('link_edit'),
-										},
-										out: {
-											title: this.lang.get('link_insert')
-										}
-									}
-								},
-								unlink:
-								{
-									title: this.lang.get('unlink'),
-									func: 'link.unlink',
-									observe: {
-										element: 'a',
-										out: {
-											attr: {
-												'class': 'redactor-dropdown-link-inactive',
-												'aria-disabled': true
-											}
-										}
-									}
-								}
-							}
-						},
-						alignment:
-						{
-							title: this.lang.get('alignment'),
-							dropdown:
-							{
-								left:
-								{
-									title: this.lang.get('align_left'),
-									func: 'alignment.left'
-								},
-								center:
-								{
-									title: this.lang.get('align_center'),
-									func: 'alignment.center'
-								},
-								right:
-								{
-									title: this.lang.get('align_right'),
-									func: 'alignment.right'
-								},
-								justify:
-								{
-									title: this.lang.get('align_justify'),
-									func: 'alignment.justify'
-								}
-							}
-						},
-						horizontalrule:
-						{
-							title: this.lang.get('horizontalrule'),
-							func: 'line.insert'
-						}
-					};
-				},
-				build: function()
-				{
-					this.toolbar.hideButtons();
-					this.toolbar.hideButtonsOnMobile();
-					this.toolbar.isButtonSourceNeeded();
-
-					if (this.opts.buttons.length === 0) return;
-
-					this.$toolbar = this.toolbar.createContainer();
-
-					this.toolbar.setOverflow();
-					this.toolbar.append();
-					this.toolbar.setFormattingTags();
-					this.toolbar.loadButtons();
-					this.toolbar.setFixed();
-
-					// buttons response
-					if (this.opts.activeButtons)
-					{
-						this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.toolbar, this));
-					}
-
-				},
-				createContainer: function()
-				{
-					return $('<ul>').addClass('redactor-toolbar').attr({'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar'});
-				},
-				setFormattingTags: function()
-				{
-					$.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
-					{
-						if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
-					}, this));
-
-				},
-				loadButtons: function()
-				{
-					$.each(this.opts.buttons, $.proxy(function(i, btnName)
-					{
-						if (!this.opts.toolbar[btnName]) return;
-
-						if (btnName === 'file')
-						{
-							 if (this.opts.fileUpload === false) return;
-							 else if (!this.opts.fileUpload && this.opts.s3 === false) return;
-						}
-
-						if (btnName === 'image')
-						{
-							 if (this.opts.imageUpload === false) return;
-							 else if (!this.opts.imageUpload && this.opts.s3 === false) return;
-						}
-
-						var btnObject = this.opts.toolbar[btnName];
-						this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
-
-					}, this));
-				},
-				append: function()
-				{
-					if (this.opts.toolbarExternal)
-					{
-						this.$toolbar.addClass('redactor-toolbar-external');
-						$(this.opts.toolbarExternal).html(this.$toolbar);
-					}
-					else
-					{
-						this.$box.prepend(this.$toolbar);
-					}
-				},
-				setFixed: function()
-				{
-					if (!this.utils.isDesktop()) return;
-					if (this.opts.toolbarExternal) return;
-					if (!this.opts.toolbarFixed) return;
-
-					this.toolbar.observeScroll();
-					$(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, $.proxy(this.toolbar.observeScroll, this));
-
-				},
-				setOverflow: function()
-				{
-					if (this.utils.isMobile() && this.opts.toolbarOverflow)
-					{
-						this.$toolbar.addClass('redactor-toolbar-overflow');
-					}
-				},
-				isButtonSourceNeeded: function()
-				{
-					if (this.opts.source) return;
-
-					var index = this.opts.buttons.indexOf('html');
-					if (index !== -1)
-					{
-						this.opts.buttons.splice(index, 1);
-					}
-				},
-				hideButtons: function()
-				{
-					if (this.opts.buttonsHide.length === 0) return;
-
-					$.each(this.opts.buttonsHide, $.proxy(function(i, s)
-					{
-						var index = this.opts.buttons.indexOf(s);
-						this.opts.buttons.splice(index, 1);
-
-					}, this));
-				},
-				hideButtonsOnMobile: function()
-				{
-					if (!this.utils.isMobile() || this.opts.buttonsHideOnMobile.length === 0) return;
-
-					$.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
-					{
-						var index = this.opts.buttons.indexOf(s);
-						this.opts.buttons.splice(index, 1);
-
-					}, this));
-				},
-				observeScroll: function()
-				{
-					var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
-					var boxTop = 1;
-
-					if (this.opts.toolbarFixedTarget === document)
-					{
-						boxTop = this.$box.offset().top;
-					}
-
-					if ((scrollTop + this.opts.toolbarFixedTopOffset) > boxTop)
-					{
-						this.toolbar.observeScrollEnable(scrollTop, boxTop);
-					}
-					else
-					{
-						this.toolbar.observeScrollDisable();
-					}
-				},
-				observeScrollEnable: function(scrollTop, boxTop)
-				{
-					var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop;
-					var left = 0;
-					var end = boxTop + this.$box.height() - 32;
-					var width = this.$box.innerWidth();
-
-					this.$toolbar.addClass('toolbar-fixed-box');
-					this.$toolbar.css({
-						position: 'absolute',
-						width: width,
-						top: top + 'px',
-						left: left
-					});
-
-					if (scrollTop > end)
-						$('.redactor-dropdown-' + this.uuid + ':visible').hide();
-
-					this.toolbar.setDropdownsFixed();
-					this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
-				},
-				observeScrollDisable: function()
-				{
-					this.$toolbar.css({
-						position: 'relative',
-						width: 'auto',
-						top: 0,
-						left: 0,
-						visibility: 'visible'
-					});
-
-					this.toolbar.unsetDropdownsFixed();
-					this.$toolbar.removeClass('toolbar-fixed-box');
-				},
-				setDropdownsFixed: function()
-				{
-					var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
-					var position = 'fixed';
-					if (this.opts.toolbarFixedTarget !== document)
-					{
-						top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
-						position = 'absolute';
-					}
-
-					$('.redactor-dropdown-' + this.uuid).each(function()
-					{
-						$(this).css({ position: position, top: top + 'px' });
-					});
-				},
-				unsetDropdownsFixed: function()
-				{
-					var top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top);
-					$('.redactor-dropdown-' + this.uuid).each(function()
-					{
-						$(this).css({ position: 'absolute', top: top + 'px' });
-					});
-				}
-			};
-		},
-		upload: function()
-		{
-			return {
-				init: function(id, url, callback)
-				{
-					this.upload.direct = false;
-					this.upload.callback = callback;
-					this.upload.url = url;
-					this.upload.$el = $(id);
-					this.upload.$droparea = $('<div id="redactor-droparea" />');
-
-					this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload_label'));
-					this.upload.$input = $('<input type="file" name="file" />');
-
-					this.upload.$placeholdler.append(this.upload.$input);
-					this.upload.$droparea.append(this.upload.$placeholdler);
-					this.upload.$el.append(this.upload.$droparea);
-
-					this.upload.$droparea.off('redactor.upload');
-					this.upload.$input.off('redactor.upload');
-
-					this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
-					this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
-
-					// change
-					this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
-					{
-						e = e.originalEvent || e;
-						this.upload.traverseFile(this.upload.$input[0].files[0], e);
-					}, this));
-
-					// drop
-					this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
-					{
-						e.preventDefault();
-
-						this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
-						this.upload.onDrop(e);
-
-					}, this));
-				},
-				directUpload: function(file, e)
-				{
-					this.upload.direct = true;
-					this.upload.traverseFile(file, e);
-				},
-				onDrop: function(e)
-				{
-					e = e.originalEvent || e;
-					var files = e.dataTransfer.files;
-
-					this.upload.traverseFile(files[0], e);
-				},
-				traverseFile: function(file, e)
-				{
-					if (this.opts.s3)
-					{
-						this.upload.setConfig(file);
-						this.upload.s3uploadFile(file);
-						return;
-					}
-
-					var formData = !!window.FormData ? new FormData() : null;
-					if (window.FormData)
-					{
-						this.upload.setConfig(file);
-
-						var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
-						formData.append(name, file);
-					}
-
-					this.progress.show();
-					this.core.setCallback('uploadStart', e, formData);
-					this.upload.sendData(formData, e);
-				},
-				setConfig: function(file)
-				{
-					this.upload.getType(file);
-
-					if (this.upload.direct)
-					{
-						this.upload.url = (this.upload.type == 'image') ? this.opts.imageUpload : this.opts.fileUpload;
-						this.upload.callback = (this.upload.type == 'image') ? this.image.insert : this.file.insert;
-					}
-				},
-				getType: function(file)
-				{
-					this.upload.type = 'image';
-					if (this.opts.imageTypes.indexOf(file.type) == -1)
-					{
-						this.upload.type = 'file';
-					}
-				},
-				getHiddenFields: function(obj, fd)
-				{
-					if (obj === false || typeof obj !== 'object') return fd;
-
-					$.each(obj, $.proxy(function(k, v)
-					{
-						if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
-						fd.append(k, v);
-
-					}, this));
-
-					return fd;
-
-				},
-				sendData: function(formData, e)
-				{
-					// append hidden fields
-					if (this.upload.type == 'image')
-					{
-						formData = this.upload.getHiddenFields(this.opts.uploadImageFields, formData);
-						formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
-					}
-					else
-					{
-						formData = this.upload.getHiddenFields(this.opts.uploadFileFields, formData);
-						formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
-					}
-
-					var xhr = new XMLHttpRequest();
-					xhr.open('POST', this.upload.url);
-					xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-
-					// complete
-					xhr.onreadystatechange = $.proxy(function()
-					{
-					    if (xhr.readyState == 4)
-					    {
-					        var data = xhr.responseText;
-
-							data = data.replace(/^\[/, '');
-							data = data.replace(/\]$/, '');
-
-							var json;
-							try
-							{
-								json = (typeof data === 'string' ? $.parseJSON(data) : data);
-							}
-							catch(err)
-							{
-								json = {
-									error: true
-								};
-							}
-
-
-							this.progress.hide();
-
-							if (!this.upload.direct)
-							{
-								this.upload.$droparea.removeClass('drag-drop');
-							}
-
-							this.upload.callback(json, this.upload.direct, e);
-					    }
-					}, this);
-
-
-					/*
-					xhr.upload.onprogress = $.proxy(function(e)
-					{
-						if (e.lengthComputable)
-						{
-							var complete = (e.loaded / e.total * 100 | 0);
-							//progress.value = progress.innerHTML = complete;
-						}
-
-					}, this);
-					*/
-
-
-					xhr.send(formData);
-				},
-				onDrag: function(e)
-				{
-					e.preventDefault();
-					this.upload.$droparea.addClass('drag-hover');
-				},
-				onDragLeave: function(e)
-				{
-					e.preventDefault();
-					this.upload.$droparea.removeClass('drag-hover');
-				},
-				clearImageFields: function()
-				{
-					this.upload.imageFields = {};
-				},
-				addImageFields: function(name, value)
-				{
-					this.upload.imageFields[name] = value;
-				},
-				removeImageFields: function(name)
-				{
-					delete this.upload.imageFields[name];
-				},
-				clearFileFields: function()
-				{
-					this.upload.fileFields = {};
-				},
-				addFileFields: function(name, value)
-				{
-					this.upload.fileFields[name] = value;
-				},
-				removeFileFields: function(name)
-				{
-					delete this.upload.fileFields[name];
-				},
-
-
-				// S3
-				s3uploadFile: function(file)
-				{
-					this.upload.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
-					{
-						this.upload.s3uploadToS3(file, signedURL);
-					}, this));
-				},
-				s3executeOnSignedUrl: function(file, callback)
-				{
-					var xhr = new XMLHttpRequest();
-					var mark = (this.opts.s3.search(/\?/) !== '-1') ? '?' : '&';
-
-					xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
-
-					// Hack to pass bytes through unprocessed.
-					if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
-
-					var that = this;
-					xhr.onreadystatechange = function(e)
-					{
-						if (this.readyState == 4 && this.status == 200)
-						{
-							that.progress.show();
-							callback(decodeURIComponent(this.responseText));
-						}
-						else if (this.readyState == 4 && this.status != 200)
-						{
-							//setProgress(0, 'Could not contact signing script. Status = ' + this.status);
-						}
-					};
-
-					xhr.send();
-				},
-				s3createCORSRequest: function(method, url)
-				{
-					var xhr = new XMLHttpRequest();
-					if ("withCredentials" in xhr)
-					{
-						xhr.open(method, url, true);
-					}
-					else if (typeof XDomainRequest != "undefined")
-					{
-						xhr = new XDomainRequest();
-						xhr.open(method, url);
-					}
-					else
-					{
-						xhr = null;
-					}
-
-					return xhr;
-				},
-				s3uploadToS3: function(file, url)
-				{
-					var xhr = this.upload.s3createCORSRequest('PUT', url);
-					if (!xhr)
-					{
-						//setProgress(0, 'CORS not supported');
-					}
-					else
-					{
-						xhr.onload = $.proxy(function()
-						{
-							if (xhr.status == 200)
-							{
-								//setProgress(100, 'Upload completed.');
-
-								this.progress.hide();
-
-								var s3file = url.split('?');
-
-								if (!s3file[0])
-								{
-									 // url parsing is fail
-									 return false;
-								}
-
-
-								if (!this.upload.direct)
-								{
-									this.upload.$droparea.removeClass('drag-drop');
-								}
-
-								var json = { filelink: s3file[0] };
-								if (this.upload.type == 'file')
-								{
-									var arr = s3file[0].split('/');
-									json.filename = arr[arr.length-1];
-								}
-
-								this.upload.callback(json, this.upload.direct, false);
-
-
-							}
-							else
-							{
-								//setProgress(0, 'Upload error: ' + xhr.status);
-							}
-						}, this);
-
-						xhr.onerror = function() {};
-
-						xhr.upload.onprogress = function(e) {};
-
-						xhr.setRequestHeader('Content-Type', file.type);
-						xhr.setRequestHeader('x-amz-acl', 'public-read');
-
-						xhr.send(file);
-					}
-				}
-			};
-		},
-		utils: function()
-		{
-			return {
-				isMobile: function()
-				{
-					return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
-				},
-				isDesktop: function()
-				{
-					return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
-				},
-				isString: function(obj)
-				{
-					return Object.prototype.toString.call(obj) == '[object String]';
-				},
-				isEmpty: function(html, removeEmptyTags)
-				{
-					html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
-					html = html.replace(/&nbsp;/gi, '');
-					html = html.replace(/<\/?br\s?\/?>/g, '');
-					html = html.replace(/\s/g, '');
-					html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
-					html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
-					html = html.replace(/<source(.*?[^>])>$/i, 'source');
-
-					// remove empty tags
-					if (removeEmptyTags !== false)
-					{
-						html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-						html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-					}
-
-					html = $.trim(html);
-
-					return html === '';
-				},
-				normalize: function(str)
-				{
-					if (typeof(str) === 'undefined') return 0;
-					return parseInt(str.replace('px',''), 10);
-				},
-				hexToRgb: function(hex)
-				{
-					if (typeof hex == 'undefined') return;
-					if (hex.search(/^#/) == -1) return hex;
-
-					var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
-					hex = hex.replace(shorthandRegex, function(m, r, g, b)
-					{
-						return r + r + g + g + b + b;
-					});
-
-					var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-					return 'rgb(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ')';
-				},
-				getOuterHtml: function(el)
-				{
-					return $('<div>').append($(el).eq(0).clone()).html();
-				},
-				getAlignmentElement: function(el)
-				{
-					if ($.inArray(el.tagName, this.opts.alignmentTags) !== -1)
-					{
-						return $(el);
-					}
-					else
-					{
-						return $(el).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-					}
-				},
-				removeEmptyAttr: function(el, attr)
-				{
-					var $el = $(el);
-					if (typeof $el.attr(attr) == 'undefined')
-					{
-						return true;
-					}
-
-					if ($el.attr(attr) === '')
-					{
-						$el.removeAttr(attr);
-						return true;
-					}
-
-					return false;
-				},
-				removeEmpty: function(i, s)
-				{
-					var $s = $($.parseHTML(s));
-
-					$s.find('.redactor-invisible-space').removeAttr('style').removeAttr('class');
-
-					if ($s.find('hr, br, img, iframe, source').length !== 0) return;
-					var text = $.trim($s.text());
-
-					if (this.utils.isEmpty(text, false))
-					{
-						$s.remove();
-					}
-				},
-
-				// save and restore scroll
-				saveScroll: function()
-				{
-					this.saveEditorScroll = this.$editor.scrollTop();
-					this.saveBodyScroll = $(window).scrollTop();
-
-					if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop();
-				},
-				restoreScroll: function()
-				{
-					if (typeof this.saveScroll === 'undefined' && typeof this.saveBodyScroll === 'undefined') return;
-
-					$(window).scrollTop(this.saveBodyScroll);
-					this.$editor.scrollTop(this.saveEditorScroll);
-
-					if (this.opts.scrollTarget) $(this.opts.scrollTarget).scrollTop(this.saveTargetScroll);
-				},
-
-				// get invisible space element
-				createSpaceElement: function()
-				{
-					var space = document.createElement('span');
-					space.className = 'redactor-invisible-space';
-					space.innerHTML = this.opts.invisibleSpace;
-
-					return space;
-				},
-
-				// replace
-				removeInlineTags: function(node)
-				{
-					var tags = this.opts.inlineTags;
-					tags.push('span');
-
-					if (node.tagName == 'PRE') tags.push('a');
-
-					$(node).find(tags.join(',')).not('span.redactor-selection-marker').contents().unwrap();
-				},
-				replaceWithContents: function(node, removeInlineTags)
-				{
-					var self = this;
-					$(node).replaceWith(function()
-					{
-						if (removeInlineTags === true) self.utils.removeInlineTags(this);
-
-						return $(this).contents();
-					});
-
-					return $(node);
-				},
-				replaceToTag: function(node, tag, removeInlineTags)
-				{
-					var replacement;
-					var self = this;
-					$(node).replaceWith(function()
-					{
-						replacement = $('<' + tag + ' />').append($(this).contents());
-
-						for (var i = 0; i < this.attributes.length; i++)
-						{
-							replacement.attr(this.attributes[i].name, this.attributes[i].value);
-						}
-
-						if (removeInlineTags === true) self.utils.removeInlineTags(replacement);
-
-						return replacement;
-					});
-
-					return replacement;
-				},
-
-				// start and end of element
-				isStartOfElement: function()
-				{
-					var block = this.selection.getBlock();
-					if (!block) return false;
-
-					var offset = this.caret.getOffsetOfElement(block);
-
-					return (offset === 0) ? true : false;
-				},
-				isEndOfElement: function(element)
-				{
-					if (typeof element == 'undefined')
-					{
-						var element = this.selection.getBlock();
-						if (!element) return false;
-					}
-
-					var offset = this.caret.getOffsetOfElement(element);
-					var text = $.trim($(element).text()).replace(/\n\r\n/g, '');
-
-					return (offset == text.length) ? true : false;
-				},
-				isStartOfEditor: function()
-				{
-					var offset = this.caret.getOffsetOfElement(this.$editor[0]);
-
-					return (offset === 0) ? true : false;
-				},
-				isEndOfEditor: function()
-				{
-					var block = this.$editor[0];
-
-					var offset = this.caret.getOffsetOfElement(block);
-					var text = $.trim($(block).html().replace(/(<([^>]+)>)/gi,''));
-
-					return (offset == text.length) ? true : false;
-				},
-
-				// test blocks
-				isBlock: function(block)
-				{
-					block = block[0] || block;
-
-					return block && this.utils.isBlockTag(block.tagName);
-				},
-				isBlockTag: function(tag)
-				{
-					if (typeof tag == 'undefined') return false;
-
-					return this.reIsBlock.test(tag);
-				},
-
-				// tag detection
-				isTag: function(current, tag)
-				{
-					var element = $(current).closest(tag, this.$editor[0]);
-					if (element.length == 1)
-					{
-						return element[0];
-					}
-
-					return false;
-				},
-
-				// select all
-				isSelectAll: function()
-				{
-					return this.selectAll;
-				},
-				enableSelectAll: function()
-				{
-					this.selectAll = true;
-				},
-				disableSelectAll: function()
-				{
-					this.selectAll = false;
-				},
-
-				// parents detection
-				isRedactorParent: function(el)
-				{
-					if (!el)
-					{
-						return false;
-					}
-
-					if ($(el).parents('.redactor-editor').length === 0 || $(el).hasClass('redactor-editor'))
-					{
-						return false;
-					}
-
-					return el;
-				},
-				isCurrentOrParentHeader: function()
-				{
-					return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
-				},
-				isCurrentOrParent: function(tagName)
-				{
-					var parent = this.selection.getParent();
-					var current = this.selection.getCurrent();
-
-					if ($.isArray(tagName))
-					{
-						var matched = 0;
-						$.each(tagName, $.proxy(function(i, s)
-						{
-							if (this.utils.isCurrentOrParentOne(current, parent, s))
-							{
-								matched++;
-							}
-						}, this));
-
-						return (matched === 0) ? false : true;
-					}
-					else
-					{
-						return this.utils.isCurrentOrParentOne(current, parent, tagName);
-					}
-				},
-				isCurrentOrParentOne: function(current, parent, tagName)
-				{
-					tagName = tagName.toUpperCase();
-
-					return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
-				},
-
-
-				// browsers detection
-				isOldIe: function()
-				{
-					return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 9) ? true : false;
-				},
-				isLessIe10: function()
-				{
-					return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 10) ? true : false;
-				},
-				isIe11: function()
-				{
-					return !!navigator.userAgent.match(/Trident\/7\./);
-				},
-				browser: function(browser)
-				{
-					var ua = navigator.userAgent.toLowerCase();
-					var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
-		            /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
-		            /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
-		            /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
-		            /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
-		            /(msie) ([\w.]+)/.exec( ua ) ||
-		            ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
-		            ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
-		            [];
-
-					if (browser == 'safari') return (typeof match[3] != 'undefined') ? match[3] == 'safari' : false;
-					if (browser == 'version') return match[2];
-					if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'opr' || match[1] == 'webkit');
-					if (match[1] == 'rv') return browser == 'msie';
-					if (match[1] == 'opr') return browser == 'webkit';
-
-					return browser == match[1];
-				},
-				strpos: function(haystack, needle, offset)
-				{
-					var i = haystack.indexOf(needle, offset);
-					return i >= 0 ? i : false;
-				},
-				disableBodyScroll: function()
-				{
-
-					var $body = $('html');
-					var windowWidth = window.innerWidth;
-					if (!windowWidth)
-					{
-						var documentElementRect = document.documentElement.getBoundingClientRect();
-						windowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
-					}
-
-					var isOverflowing = document.body.clientWidth < windowWidth;
-					var scrollbarWidth = this.utils.measureScrollbar();
-
-					$body.css('overflow', 'hidden');
-					if (isOverflowing) $body.css('padding-right', scrollbarWidth);
-
-
-				},
-				measureScrollbar: function()
-				{
-					var $body = $('body');
-					var scrollDiv = document.createElement('div');
-					scrollDiv.className = 'redactor-scrollbar-measure';
-
-					$body.append(scrollDiv);
-					var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
-					$body[0].removeChild(scrollDiv);
-					return scrollbarWidth;
-				},
-				enableBodyScroll: function()
-				{
-					$('html').css({ 'overflow': '', 'padding-right': '' });
-					$('body').remove('redactor-scrollbar-measure');
-				}
-			};
-		}
-	};
-
-	$(window).on('load.tools.redactor', function()
-	{
-		$('[data-tools="redactor"]').redactor();
-	});
-
-	// constructor
-	Redactor.prototype.init.prototype = Redactor.prototype;
-
-})(jQuery);

+ 232 - 0
priv/js/sortable.js

@@ -0,0 +1,232 @@
+"use strict";
+
+function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
+
+function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+window.addEventListener('touchmove', function () {});
+
+var Sortable =
+/*#__PURE__*/
+function () {
+  function Sortable(list, options) {
+    _classCallCheck(this, Sortable);
+    this.list = typeof list === 'string' ? document.querySelector(list) : list;
+    this.items = Array.from(this.list.children);
+    this.animation = false;
+    this.options = Object.assign({
+      animationSpeed: 200,
+      animationEasing: 'ease-out'
+    }, options || {});
+    this.dragStart = this.dragStart.bind(this);
+    this.dragMove = this.dragMove.bind(this);
+    this.dragEnd = this.dragEnd.bind(this);
+    this.list.addEventListener('touchstart', this.dragStart, false);
+    this.list.addEventListener('mousedown', this.dragStart, false);
+  }
+
+  _createClass(Sortable, [{
+    key: "dragStart",
+    value: function dragStart(e) {
+      var _this = this;
+
+      if (this.animation) return;
+      if (e.type === 'mousedown' && e.which !== 1) return;
+      if (e.type === 'touchstart' && e.touches.length > 1) return;
+      this.handle = null;
+      var el = e.target;
+
+      while (el) {
+        if (el.hasAttribute('data-sortable-handle')) this.handle = el;
+        if (el.hasAttribute('data-sortable-item')) this.item = el;
+        if (el.hasAttribute('data-sortable-list')) break;
+        el = el.parentElement;
+      }
+
+      if (!this.handle) return;
+      this.list.style.position = 'relative';
+      this.list.style.height = this.list.offsetHeight + 'px';
+      this.item.classList.add('is-dragging');
+      this.itemHeight = this.items[1].offsetTop;
+      this.listHeight = this.list.offsetHeight;
+      this.startTouchY = this.getDragY(e);
+      this.startTop = this.item.offsetTop;
+      var offsetsTop = this.items.map(function (item) {
+        return item.offsetTop;
+      });
+      this.items.forEach(function (item, index) {
+        item.style.position = 'absolute';
+        item.style.top = 0;
+        item.style.left = 0;
+        item.style.width = '100%';
+        item.style.transform = "translateY(".concat(offsetsTop[index], "px)");
+        item.style.zIndex = item == _this.item ? 2 : 1;
+      });
+      setTimeout(function () {
+        _this.items.forEach(function (item) {
+          if (_this.item == item) return;
+          item.style.transition = "transform ".concat(_this.options.animationSpeed, "ms ").concat(_this.options.animationEasing);
+        });
+      });
+      this.positions = this.items.map(function (item, index) {
+        return index;
+      });
+      this.position = Math.round(this.startTop / this.listHeight * this.items.length);
+      this.touch = e.type == 'touchstart';
+      window.addEventListener(this.touch ? 'touchmove' : 'mousemove', this.dragMove, {
+        passive: false
+      });
+      window.addEventListener(this.touch ? 'touchend' : 'mouseup', this.dragEnd, false);
+    }
+  }, {
+    key: "dragMove",
+    value: function dragMove(e) {
+      var _this2 = this;
+
+      if (this.animation) return;
+      var top = this.startTop + this.getDragY(e) - this.startTouchY;
+      var newPosition = Math.round(top / this.listHeight * this.items.length);
+      this.item.style.transform = "translateY(".concat(top, "px)");
+      this.positions.forEach(function (index) {
+        if (index == _this2.position || index != newPosition) return;
+
+        _this2.swapElements(_this2.positions, _this2.position, index);
+
+        _this2.position = index;
+      });
+      this.items.forEach(function (item, index) {
+        if (item == _this2.item) return;
+        item.style.transform = "translateY(".concat(_this2.positions.indexOf(index) * _this2.itemHeight, "px)");
+      });
+      e.preventDefault();
+    }
+  }, {
+    key: "dragEnd",
+    value: function dragEnd(e) {
+      var _this3 = this;
+
+      this.animation = true;
+      this.item.style.transition = "all ".concat(this.options.animationSpeed, "ms ").concat(this.options.animationEasing);
+      this.item.style.transform = "translateY(".concat(this.position * this.itemHeight, "px)");
+      this.item.classList.remove('is-dragging');
+      setTimeout(function () {
+        _this3.list.style.position = '';
+        _this3.list.style.height = '';
+
+        _this3.items.forEach(function (item) {
+          item.style.top = '';
+          item.style.left = '';
+          item.style.right = '';
+          item.style.position = '';
+          item.style.transform = '';
+          item.style.transition = '';
+          item.style.width = '';
+          item.style.zIndex = '';
+        });
+
+        _this3.positions.map(function (i) {
+          return _this3.list.appendChild(_this3.items[i]);
+        });
+
+        _this3.items = Array.from(_this3.list.children);
+        _this3.animation = false;
+      }, this.options.animationSpeed);
+      window.removeEventListener(this.touch ? 'touchmove' : 'mousemove', this.dragMove, {
+        passive: false
+      });
+      window.removeEventListener(this.touch ? 'touchend' : 'mouseup', this.dragEnd, false);
+    }
+  }, {
+    key: "swapElements",
+    value: function swapElements(array, a, b) {
+      var temp = array[a];
+      array[a] = array[b];
+      array[b] = temp;
+    }
+  }, {
+    key: "getDragY",
+    value: function getDragY(e) {
+      return e.touches ? (e.touches[0] || e.changedTouches[0]).pageY : e.pageY;
+    }
+  }, {
+    key: "removeItem",
+    value: function removeItem(item) {
+      this.items.splice(this.items.indexOf(item),1);
+      item.remove();
+    }
+  }, {
+    key: "addItemFrom",
+    value: function addItemFrom(input) {
+      var value = querySourceRaw(input);
+      var bind = '';
+      if (value && value.hasOwnProperty('text') && value.hasOwnProperty('bind')) {
+        bind = value.bind;
+        value = value.text;
+      }
+      var inputElement = document.getElementById(input);
+      if (bind !== '' && bind !== 'null') {
+        if (inputElement) {
+          inputElement.value = '';
+          inputElement.removeAttribute("data-bind");
+        }
+        appendItemFromBind(this.list.id,value,bind);
+      }
+    }
+  }, {
+    key: "getValues",
+    value: function getValues() {
+      return Array.from(this.items.map(function(item) {
+        let text = item.children[1].firstChild.innerHTML;
+        let bind = item.getAttribute('data-bind');
+        if (bind) return { 'text': text, 'bind': bind };
+        return text;
+      }));
+    }
+  }]);
+
+  return Sortable;
+}();
+
+var SortableMap = new Map([]);
+
+function createSortable(list) {
+  SortableMap.set(list, new Sortable(list));
+}
+
+function removeSortableItem(list, item) {
+  SortableMap.get(list).removeItem(item);
+}
+
+function addSortableItemFrom(list, input) {
+  var value = querySourceRaw(input);
+  var isAdded = SortableMap.get(list).items.map(el => el.textContent).includes(value.text || value);
+  if(qi(input).value != '' & !isAdded)
+    SortableMap.get(list).addItemFrom(input);
+}
+
+function getSortableValues(list) {
+  let sortable = SortableMap.get(list)
+  if(sortable) {
+    return sortable.getValues();
+  } else {
+    return Array.from([]);
+  }
+}
+
+function appendItemFromBind(dom,value,bind) {
+  var sortable = SortableMap.get('#'+dom);
+  var template = document.createElement('template');
+  template.innerHTML =
+    '<div class="list__item" data-sortable-item="data-sortable-item" style="" data-bind="'+ bind + '">' +
+       '<div class="list__item-close" onclick="removeSortableItem(\'#' + sortable.list.id + '\', this.parentNode);"></div>' +
+       '<div class="list__item-content"><div class="list__item-title">' + value + '</div></div>' +
+       '<div class="list__item-handle" data-sortable-handle="data-sortable-handle"></div>' +
+    '</div>'
+  var new_item = template.content.firstChild;
+  sortable.list.appendChild(new_item);
+  sortable.items.push(new_item);
+}

+ 20 - 0
priv/js/validation.js

@@ -0,0 +1,20 @@
+
+function validateSources(list) {
+    return list.reduce(function(acc,x) {
+        var event = new CustomEvent('validation');
+            event.initCustomEvent('validation',true,true,querySourceRaw(x));
+        var el = qi(x),
+            listener = el && el.validation,
+            res = !listener || listener && el.dispatchEvent(event);
+        if (!res) { console.log("Validation failed:" + x); }
+        if (el) el.style.background = res ? '' : 'pink';
+        return res && acc; },true); }
+
+(function () {
+   function CustomEvent ( event, params ) {
+       params = params || { bubbles: false, cancelable: false, detail: undefined };
+       var evt = document.createEvent( 'CustomEvent' );
+       evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+       return evt;  };
+  CustomEvent.prototype = window.Event.prototype;
+  window.CustomEvent = CustomEvent; })();

+ 1 - 0
src/actions/action_alert.erl

@@ -2,6 +2,7 @@
 %%-author('Rusty Klophaus').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1

+ 4 - 3
src/actions/action_api.erl

@@ -2,6 +2,7 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1
@@ -9,7 +10,7 @@
 
 
 render_action( #api{name = Name, delegate = Delegate} ) ->
-  PostbackScript = wf_event:new(Name, "document", Delegate, api_event,
-                                "utf8_toByteArray(JSON.stringify(data))", []),
-  [nitro:to_binary(Name), "=function(data){", PostbackScript, "};"].
+  Data = "string(JSON.stringify(data))",
+  PostbackScript = wf_event:new(Name, "document", Delegate, api_event, Data, []),
+  erlang:iolist_to_binary(["document.", nitro:to_binary(Name), "=function(data){", PostbackScript, "};"]).
 

+ 15 - 2
src/actions/action_bind.erl

@@ -2,14 +2,27 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1
 ]).
 
 
+render_action( #bind{postback = Code, target = {g, _} = Control, type = Type, source = Src} ) ->
+  %% rebind same control to same handler on same target
+  G = wf_event:target(Control),
+  V = nitro:to_binary(Src),
+  E = nitro:to_binary(Type),
+  ["{ if('", V, "' in ", G, "){",
+        G, ".removeEventListener('", E, "',", V, ");"
+        "delete ", G, "[", V, "]};",
+      V, "={handleEvent:(event) => { if(event.type === '", E, "'){", nitro:to_binary(Code), "}}};",
+      G, ".addEventListener('", E, "',", V, ");",
+      G, ".", V, "=", V, ";}"];
+
 render_action( #bind{postback = Code, target = Control, type = Type} ) ->
   ["{var x=", wf_event:target(Control), ";"
-   "x && x.addEventListener('", nitro:to_binary(Type), "',"
-   "function(event){", nitro:to_binary(Code), "});}" ].
+      "x && x.addEventListener('", nitro:to_binary(Type), "',"
+        "function(event){", nitro:to_binary(Code), "});}" ].
 

+ 1 - 0
src/actions/action_confirm.erl

@@ -2,6 +2,7 @@
 %%-author('Rusty Klophaus').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1

+ 7 - 5
src/actions/action_event.erl

@@ -3,6 +3,7 @@
 %%-author('Andrey Martemyanov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1
@@ -15,14 +16,15 @@ render_action( #event{postback = Postback, actions = _A, source = Source, target
   ValidationSource = [ S || S <- Source, not is_tuple(S) ],
   PostbackBin = wf_event:new(Postback, E, D, event, data(E, Source), ValidationSource, V),
   ["{var x=qi('", E, "');"
-    "x && x.addEventListener('", nitro:to_binary(Type), "',"
-      "function (event){ ", PostbackBin, "});};"].
+     "x && x.addEventListener('", nitro:to_binary(Type), "',"
+       "function (event){ event.preventDefault(); ", PostbackBin, "});};"].
 
 data(E, SourceList) ->
-  Type = fun(A) when is_atom(A) -> [ "atom('", atom_to_list(A), "')" ];
-            (A) -> [ "utf8_toByteArray('", A, "')" ]
+  Type = fun(A) when erlang:is_atom(A)   -> [ "atom('", erlang:atom_to_list(A), "')" ];
+            (A) when erlang:is_binary(A) -> [ "bin('", erlang:binary_to_list(A), "')" ];
+                                     (A) -> [ "string('", A, "')" ]
   end,
-  list_to_binary([ "[tuple(tuple(utf8_toByteArray('", E, "'),bin('detail')),[])",
+  erlang:list_to_binary([ "[tuple(tuple(string('", E, "'),bin('detail')),[])",
     [ case S of {Id, Code} -> [ ",tuple(", Type(Id), ",", Code, ")" ];
                          _ -> [ ",tuple(", Type(S), ",querySource('", nitro:to_binary(S), "'))" ]
       end || S <- SourceList ],

+ 7 - 2
src/actions/action_jq.erl

@@ -14,12 +14,17 @@ render_action( #jq{property = undefined, target = T, method = Methods, args = Ar
     "'~s'" -> [ nitro:render(Args0) ];
     _ -> Args0
   end,
-  Format = fun(A) when is_tuple(A) orelse is_integer(A) ->
+  Format = fun(A) when erlang:is_tuple(A) orelse erlang:is_integer(A) ->
                      nitro:render(A);
               (A) -> nitro:to_list(A)
   end,
   RenderedArgs = string:join([ Format(A) || A <- Args], ","),
-  [ [ wf_event:target(T), ".", nitro:to_binary(M), "(", nitro:f(F, [RenderedArgs]), ");" ] || M <- Methods];
+  {Op, Op2} = case T of
+    {qa, _} -> {".map(i=>i.", ");"};
+    {_, {qa, _}, _} -> {".map(i=>i.", ");"};
+    _ -> {".", ";"}
+  end,
+  [ [ wf_event:target(T), Op, nitro:to_binary(M), "(", nitro:f(F, [RenderedArgs]), ")", Op2 ] || M <- Methods];
 
 render_action( #jq{target = T, method = undefined, property = P, right = R, args = simple}) -> [ nitro:to_binary(T), ".", nitro:to_binary(P), "='", nitro:render(R), "';" ];
 render_action( #jq{target = T, method = undefined, property = P, right = undefined} )       -> [ wf_event:target(T), ".", nitro:to_binary(P), ";" ];

+ 3 - 3
src/actions/action_transfer.erl

@@ -11,11 +11,11 @@
 render_action(Record) ->
   case Record#transfer.state of
     undefined -> ok;
-    List when is_list(List) -> [ erlang:put(K, V) || {K, V} <- List ];
-    Single -> erlang:put(state,Single)
+    List when erlang:is_list(List) -> [ erlang:put(K, V) || {K, V} <- List ];
+    Single -> erlang:put(state, Single)
   end,
   Events = case Record#transfer.events of
-    E when is_list(E) -> E;
+    E when erlang:is_list(E) -> E;
     E -> [E]
   end,
   [ self() ! M || M <- Events ],

+ 1 - 1
src/actions/action_ui.erl

@@ -9,5 +9,5 @@
 
 
 render_action( #focus{target = T} ) ->
-  ["window.setTimeout(function(){var x=", wf_event:target(T), ";x && x.focus();},70);"].
+  ["window.setTimeout(()=>{var x=", wf_event:target(T), ";x && x.focus();},70);"].
 

+ 4 - 3
src/actions/action_wire.erl

@@ -2,6 +2,7 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1,
@@ -10,14 +11,14 @@
 
 
 render_action( #wire{actions = Actions} ) -> nitro:render(Actions);
-render_action(S) when is_list(S) -> S;
+render_action(S) when erlang:is_list(S) -> S;
 render_action(_) -> [].
 
 wire(A) ->
-  Actions = case get(actions) of
+  Actions = case erlang:get(actions) of
     undefined -> [];
     E -> E
   end,
-  put(actions, Actions ++ [ #wire{actions = A} ]),
+  erlang:put(actions, Actions ++ [ #wire{actions = A} ]),
   [].
 

+ 32 - 30
src/elements/element_calendar.erl → src/elements/combo/element_calendar.erl

@@ -1,17 +1,21 @@
 -module(element_calendar).
 %%-author('G-Grand').
 
+-include_lib("nitro/include/calendar.hrl").
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([render_element/1]).
 
 
+render_element(Record) when Record#calendar.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#calendar.postback of
-    undefined -> Record#calendar.id;
+    [] -> Record#calendar.id;
     Postback ->
       ID0 = case Record#calendar.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire(#event{type = click, postback = Postback, target = ID0,
@@ -29,7 +33,7 @@ render_element(Record) ->
        case Record#calendar.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#calendar.contextmenu},
@@ -38,21 +42,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#calendar.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#calendar.dropzone},
     {<<"hidden">>,
        case Record#calendar.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#calendar.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#calendar.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#calendar.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#calendar.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#calendar.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#calendar.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#calendar.form},
@@ -99,13 +103,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#calendar.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#calendar.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#calendar.step},
@@ -118,24 +122,20 @@ render_element(Record) ->
   wf_tags:emit_tag(<<"input">>, nitro:render(Record#calendar.body), List).
 
 
-init(Id0, #calendar{minDate = Min, maxDate = Max, lang = Lang, format = Form,
-                   value = Value, onSelect = SelectFn, disableDayFn = DisDayFn,
-                   position = Pos,reposition = Repos, yearRange = YearRange}) ->
+init(Id0, #calendar{minDate = Min, maxDate = Max, lang = Lang,
+                    format = Form, value = Value, onSelect = SelectFn,
+                    disableDayFn = DisDayFn, firstDay = FirstDay, position = Pos,
+                    reposition = Repos, yearRange = YearRange} = _Calendar) ->
   ID = nitro:to_list(Id0),
   I18n = case Lang of
-    undefined  -> "clLangs.ua";
+    []  -> "clLangs.ua";
     Lang -> "clLangs." ++ nitro:to_list(Lang)
   end,
   
-  Format = case Form  of
-    undefined -> "YYYY-MM-DD";
-    Form -> Form
-  end,
-  
   DefaultDate = case Value of
     {Yv, Mv, Dv} ->
       nitro:f("new Date(~s,~s,~s)", [nitro:to_list(Yv), nitro:to_list(Mv - 1), nitro:to_list(Dv) ]);
-    _ -> "null"
+    _ -> "''"
   end,
   
   MinDate = case Min of
@@ -147,37 +147,39 @@ init(Id0, #calendar{minDate = Min, maxDate = Max, lang = Lang, format = Form,
   MaxDate = case Max of
     {Y1, M1, D1} ->
       nitro:f("new Date(~s,~s,~s)", [nitro:to_list(Y1), nitro:to_list(M1 - 1), nitro:to_list(D1) ]);
-    _ -> "new Date(2125, 4, 13)"
+    _ -> "new Date(2135, 4, 13)"
   end,
   
   OnSelect = case SelectFn of
-    undefined -> "null";
+    [] -> "null";
     _ -> SelectFn
   end,
   
   DisDay = case DisDayFn of
-    undefined -> "null";
+    [] -> "null";
     _ ->
       nitro:f("function(thisDate){return ~s(thisDate);}", [DisDayFn] )
   end,
   
   Position = case Pos of
-    undefined -> "bottom left";
+    [] -> "bottom left";
     _ -> nitro:to_list(Pos)
   end,
   
   Reposition = case Repos of
-    undefined -> "true";
+    [] -> "true";
     _ -> nitro:to_list(Repos)
   end,
   
+  WeekFirstDay = nitro:to_list(FirstDay),
+  
   nitro:wire(nitro:f(
     "pickers['~s'] = new Pikaday({"
       "field: document.getElementById('~s'),"
-      "firstDay: 1,"
       "i18n: ~s,"
       "defaultDate: ~s,"
       "setDefaultDate: true,"
+      "firstDay: ~s,"
       "minDate: ~s,"
       "maxDate: ~s,"
       "format: '~s',"
@@ -187,5 +189,5 @@ init(Id0, #calendar{minDate = Min, maxDate = Max, lang = Lang, format = Form,
       "reposition: ~s,"
       "yearRange: ~s"
     "});",
-    [ID, ID, I18n, DefaultDate, MinDate, MaxDate, Format, OnSelect, DisDay, Position, Reposition, nitro:to_list(YearRange) ] )).
+    [ID, ID, I18n, DefaultDate, WeekFirstDay, MinDate, MaxDate, Form, OnSelect, DisDay, Position, Reposition, nitro:to_list(YearRange) ] )).
 

+ 50 - 0
src/elements/combo/element_comboLookup.erl

@@ -0,0 +1,50 @@
+-module(element_comboLookup).
+
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/nitro.hrl").
+
+-export([
+  render_element/1,
+  proto/1
+]).
+
+
+proto( #comboKey{delegate = Module} = Msg)    -> Module:proto(Msg);
+proto( #comboKeyup{delegate = Module} = Msg)  -> Module:proto(Msg);
+proto( #comboSelect{delegate = Module} = Msg) -> Module:proto(Msg).
+
+render_element( #comboLookup{id = Id, style = Style, value = Val, bind = Object,
+                             feed = Feed, disabled = Disabled, delegate = Module} = _Data) ->
+  nitro:render(
+    #panel{id = form:atom([lookup, Id]),
+           class = [dropdown],
+           body = [
+             #input{id = Id, disabled = Disabled, type = "comboLookup",
+                    autocomplete = "off",
+                    onkeyup = nitro:jse("comboLookupKeyup('"
+                      ++ nitro:to_list(Id) ++ "','"
+                      ++ nitro:to_list(Feed) ++ "','"
+                      ++ nitro:to_list(Module) ++ "')"),
+                    onkeydown= nitro:jse("comboLookupKeydown('"
+                      ++ nitro:to_list(Id) ++ "','"
+                      ++ nitro:to_list(Feed) ++ "','"
+                      ++ nitro:to_list(Module) ++ "')"),
+                    onchange= nitro:jse("comboLookupChange('"
+                      ++ nitro:to_list(Id) ++ "')"),
+                    bind = Object,
+                    value = Val,
+                    style = Style,
+                    class = column },
+             #panel{class = ['triangle'],
+                    body="&blacktriangledown;",
+                    onclick = case Disabled of
+                      true -> [];
+                      _ -> nitro:jse("comboLookupClick('"
+                        ++ nitro:to_list(Id) ++ "','"
+                        ++ nitro:to_list(Feed) ++ "','"
+                        ++ nitro:to_list(Module) ++ "')")
+                    end},
+             #panel{id = form:atom([comboContainer, Id]),
+                    class = ['dropdown-content']}
+           ]}).
+

+ 51 - 0
src/elements/combo/element_comboLookupEdit.erl

@@ -0,0 +1,51 @@
+-module(element_comboLookupEdit).
+
+-include_lib("nitro/include/comboLookupEdit.hrl").
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+-export([
+  render_element/1
+]).
+
+
+render_element( #comboLookupEdit{id = Id, input = Input, disabled = Disabled,
+                                 validation = Validation, form = Form,
+                                 values = Values, multiple = Multiple}) ->
+  ListId = form:atom([Id, "list"]),
+  InputId = erlang:element(#element.id, Input),
+  nitro:render(
+    #panel{
+      id = Id,
+      validation = Validation,
+      data_fields = [{<<"data-edit-input">>, <<"data-edit-input">>}],
+      body = [
+        #panel{
+          style = "display: flex; position: relative; width: 100%; justify-content: center;",
+          body = [
+            Input,
+            case Multiple of
+              true ->
+                #link{class = [button, sgreen],
+                      style = "min-width: 40px; text-align: center; height: fit-content; margin-left: 5px;",
+                      onclick = nitro:jse("addSortableItemFrom('#" ++ ListId ++ "', '" ++ InputId ++ "')"),
+                      body = <<"+">> };
+              false -> []
+            end,
+            case Disabled of
+              true -> [];
+              _ ->
+                #panel{id = form:atom([InputId, "form"]),
+                       class = ['dropdown-content'],
+                       body = #panel{class = ['dropdown-item'], body = Form} }
+            end
+        ]},
+        case Multiple of
+          true ->
+            #sortable_list{id = ListId, values = Values, closeable = true, disabled = Disabled};
+          false -> []
+        end
+      ]}).
+

+ 27 - 0
src/elements/combo/element_comboLookupText.erl

@@ -0,0 +1,27 @@
+-module(element_comboLookupText).
+
+-include_lib("nitro/include/comboLookupText.hrl").
+-include_lib("nitro/include/nitro.hrl").
+
+-export([render_element/1]).
+
+
+render_element( #comboLookupText{id = Id, input = Input, disabled = Disabled, validation = Validation, textarea = Textarea, values = _Values} ) ->
+  %InputId = erlang:element(#element.id, Input),
+  nitro:render(
+    #panel{id = "wrap_" ++ Id ++ "_comboLookupText",
+           validation = Validation,
+           data_fields = [{<<"data-text-input">>, <<"data-text-input">>}],
+           body = [
+             #panel{id = "wrap_" ++ Id ++ "_lookup",
+                    style = "display:flex;width:100%;justify-content:center;",
+                    body =
+                      case Disabled of
+                        true -> [];
+                        _ -> [Input]
+                      end},
+             #panel{id = "wrap_" ++ Id ++ "_textarea",
+                    style = "display:flex;width:100%;justify-content:center;",
+                    body = [Textarea]}
+           ]}).
+

+ 40 - 0
src/elements/combo/element_comboLookupVec.erl

@@ -0,0 +1,40 @@
+-module(element_comboLookupVec).
+
+-include_lib("nitro/include/comboLookupVec.hrl").
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+-export([
+  render_element/1
+]).
+
+
+render_element( #comboLookupVec{id = Id, input = Input, disabled = Disabled, validation = Validation, values = Values} ) ->
+  ListId = form:atom([Id, "list"]),
+  nitro:render(
+    #panel{
+      id = Id,
+      validation = Validation,
+      data_fields = [{<<"data-vector-input">>, <<"data-vector-input">>}],
+      body = [
+        #panel{
+          style = "display: flex; width: 100%; justify-content: center;",
+          body =
+            case Disabled of
+              true -> [];
+              _ ->
+                [ Input,
+                  #link{
+                    class = [button, sgreen],
+                    style = "min-width: 40px; text-align: center; height: fit-content; margin-left: 5px;",
+                    onclick = nitro:jse("addSortableItemFrom('#" ++ ListId ++ "', '" ++ erlang:element(#element.id, Input) ++ "')"),
+                    body = <<"+">>}
+                 ]
+            end
+        },
+        %% TODO: Add validation for each list_item and/or "+" button
+        %% TODO?: Maybe show message "Empty list" when Values == []
+        #sortable_list{id = ListId, values = Values, closeable = true, disabled = Disabled}
+      ]}).
+

+ 25 - 0
src/elements/combo/element_koatuu.erl

@@ -0,0 +1,25 @@
+-module(element_koatuu).
+
+-include_lib("nitro/include/comboLookup.hrl").
+-include_lib("nitro/include/koatuuControl.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+-export([
+  render_element/1
+]).
+
+
+render_element( #koatuu{id = Id, style = _Style, postback = Postback, delegate = _Module} = _Data) ->
+  Options = [ #option{value = <<"Хмельницька"/utf8>>,
+                      body = <<"Хмельницька"/utf8>>,
+                      selected = true}], % 25 regions from const feed
+  nitro:render(
+    #panel{id = form:atom([koatuu, Id]),
+           body=[ #select{id = form:atom([koatuu_select, Id]),
+                          postback = Postback,
+                          body = Options},
+
+                  #comboLookup{ }
+                ]}).
+

+ 52 - 0
src/elements/combo/element_sortable_item.erl

@@ -0,0 +1,52 @@
+-module(element_sortable_item).
+
+-include_lib("nitro/include/sortable_item.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+-export([
+  render_element/1
+]).
+
+
+render_element( #sortable_item{list_id = ListId, value = Value, bind = Bind, closeable = Close, disabled = Disabled} ) ->
+  Item = case Disabled of
+    true ->
+      #panel{
+        class = <<"list__item">>,
+        body = #panel{
+            class = <<"list__item-content">>,
+            style = <<"width:100%">>,
+            body = #panel{ class = <<"list__item-title">>, body = Value}
+        }};
+    _ ->
+      #panel{
+        class = <<"list__item">>,
+        data_fields = [ {<<"data-sortable-item">>, <<"data-sortable-item">>} |
+          case Bind of
+            [] -> [];
+            _ -> [{<<"data-bind">>, base64:encode(erlang:term_to_binary(Bind))}]
+          end ],
+        body = [
+          case Close of
+            true -> 
+              #panel{
+                class = <<"list__item-close">>,
+                onclick = nitro:jse("removeSortableItem('#" ++ ListId ++ "', this.parentNode);")};
+            _ -> []
+          end,
+          #panel{
+            class = <<"list__item-content">>,
+            style = case Close of
+              true -> [];
+              _ -> <<"width:100% - 40px">>
+            end,
+            body = #panel{ class = <<"list__item-title">>, body = Value}},
+          #panel{
+            class = <<"list__item-handle">>,
+            data_fields = [{<<"data-sortable-handle">>, <<"data-sortable-handle">>}]
+          }
+        ]}
+  end,
+  nitro:render(Item).
+

+ 30 - 0
src/elements/combo/element_sortable_list.erl

@@ -0,0 +1,30 @@
+-module(element_sortable_list).
+
+-include_lib("nitro/include/sortable_list.hrl").
+-include_lib("nitro/include/sortable_item.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
+
+-export([
+  render_element/1
+]).
+
+
+render_element(#sortable_list{id = Id, values = Values, closeable = Close, disabled = Disabled}) ->
+  Closeable = case Disabled of
+    true -> false;
+    _ ->
+      nitro:wire("createSortable('#" ++ Id ++ "');"),
+      Close
+  end,
+  ProtoItem = #sortable_item{ list_id = Id, closeable = Closeable, disabled = Disabled},
+  Body = case Values of
+    {view_value_pairs, List} ->
+      [ ProtoItem#sortable_item{value = Val, bind = Bind} || {Val, Bind} <- List ];
+    _ ->
+      [ ProtoItem#sortable_item{value = Val} || Val <- Values ]
+  end,
+  nitro:render( #panel{id = Id,
+                       data_fields = [{<<"data-sortable-list">>, <<"data-sortable-list">>}],
+                       body = Body }).
+

+ 9 - 12
src/elements/element_del.erl → src/elements/edit/element_del.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#del.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#del.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#del.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#del.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#del.dropzone},
     {<<"hidden">>,
        case Record#del.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#del.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#del.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#del.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#del.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -67,10 +69,5 @@ render_element(Record) ->
     {<<"datetime">>, Record#del.datetime} | Record#del.data_fields
   ],
   
-  wf_tags:emit_tag(<<"del">>,
-    nitro:render(
-      case Record#del.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"del">>, nitro:render(Record#del.body), List).
 

+ 9 - 12
src/elements/element_ins.erl → src/elements/edit/element_ins.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#ins.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#ins.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#ins.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#ins.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#ins.dropzone},
     {<<"hidden">>,
        case Record#ins.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#ins.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#ins.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#ins.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#ins.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -67,10 +69,5 @@ render_element(Record) ->
     {<<"datetime">>, Record#ins.datetime} | Record#ins.data_fields
   ],
   
-  wf_tags:emit_tag(<<"ins">>,
-    nitro:render(
-      case Record#ins.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"ins">>, nitro:render(Record#ins.body), List).
 

+ 9 - 7
src/elements/element_area.erl → src/elements/embed/element_area.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#area.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#area.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#area.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#area.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#area.dropzone},
     {<<"hidden">>,
        case Record#area.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#area.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#area.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#area.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#area.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -75,7 +77,7 @@ render_element(Record) ->
          "circle" -> "circle";
          "poly" -> "poly";
          "default" -> "default";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"target">>, Record#area.target},

+ 14 - 17
src/elements/element_audio.erl → src/elements/embed/element_audio.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#audio.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#audio.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#audio.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#audio.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#audio.dropzone},
     {<<"hidden">>,
        case Record#audio.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#audio.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#audio.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#audio.style},
@@ -59,33 +61,33 @@ render_element(Record) ->
        case Record#audio.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"autoplay">>,
        case Record#audio.autoplay of
          true -> "autoplay";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"controls">>,
       case Record#audio.controls of
         true -> "controls";
-        _ -> undefined
+        _ -> []
       end},
     
     {<<"loop">>,
        case Record#audio.loop of
          true -> "loop";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"mediagroup">>, Record#audio.mediagroup},
     {<<"muted">>,
        case Record#audio.muted of
          true -> "muted";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"preload">>,
@@ -93,17 +95,12 @@ render_element(Record) ->
          "auto" -> "auto";
          "none" -> "none";
          "metadata" -> "metadata";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"src">>, Record#audio.src},
     {<<"width">>, Record#audio.width} | Record#audio.data_fields
   ],
 
-  wf_tags:emit_tag(<<"audio">>,
-    nitro:render(
-      case Record#audio.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"audio">>, nitro:render(Record#audio.body), List).
 

+ 9 - 7
src/elements/element_canvas.erl → src/elements/embed/element_canvas.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#canvas.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#canvas.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#canvas.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#canvas.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#canvas.dropzone},
     {<<"hidden">>,
        case Record#canvas.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#canvas.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#canvas.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#canvas.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#canvas.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -70,7 +72,7 @@ render_element(Record) ->
   wf_tags:emit_tag(<<"canvas">>,
     nitro:render(
       case Record#canvas.body of
-        undefined -> [];
+        [] -> [];
         B -> B
       end), List).
 

+ 8 - 6
src/elements/element_embed.erl → src/elements/embed/element_embed.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#embed.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#embed.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#embed.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#embed.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#embed.dropzone},
     {<<"hidden">>,
        case Record#embed.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#embed.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#embed.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#embed.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#embed.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 9 - 7
src/elements/element_iframe.erl → src/elements/embed/element_iframe.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#iframe.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#iframe.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#iframe.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#iframe.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#iframe.dropzone},
     {<<"hidden">>,
        case Record#iframe.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#iframe.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#iframe.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#iframe.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#iframe.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -68,7 +70,7 @@ render_element(Record) ->
     {<<"seamless">>,
        case Record#iframe.seamless of
          true -> "seamless";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"src">>, Record#iframe.src},

+ 2 - 0
src/elements/element_image.erl → src/elements/embed/element_image.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#image.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Attributes = [
     {<<"id">>, Record#image.id},

+ 10 - 13
src/elements/element_map.erl → src/elements/embed/element_map.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#map.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#map.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#map.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#map.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#map.dropzone},
     {<<"hidden">>,
        case Record#map.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#map.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#map.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#map.style},
@@ -59,17 +61,12 @@ render_element(Record) ->
        case Record#map.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
-    {<<"name">>,Record#map.name} | Record#map.data_fields
+    {<<"name">>, Record#map.name} | Record#map.data_fields
   ],
   
-  wf_tags:emit_tag(<<"map">>,
-    nitro:render(
-      case Record#map.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"map">>, nitro:render(Record#map.body), List).
 

+ 9 - 12
src/elements/element_object.erl → src/elements/embed/element_object.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#object.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#object.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#object.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#object.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#object.dropzone},
     {<<"hidden">>,
        case Record#object.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#object.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#object.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#object.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#object.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -72,10 +74,5 @@ render_element(Record) ->
     {<<"width">>, Record#object.width} | Record#object.data_fields
   ],
   
-  wf_tags:emit_tag(<<"object">>,
-    nitro:render(
-      case Record#object.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"object">>, nitro:render(Record#object.body), List).
 

+ 8 - 6
src/elements/element_param.erl → src/elements/embed/element_param.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#param.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#param.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#param.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#param.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#param.dropzone},
     {<<"hidden">>,
        case Record#param.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#param.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#param.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#param.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#param.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 8 - 6
src/elements/element_source.erl → src/elements/embed/element_source.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#source.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#source.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#source.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#source.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#source.dropzone},
     {<<"hidden">>,
        case Record#source.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#source.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#source.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#source.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#source.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 10 - 8
src/elements/element_track.erl → src/elements/embed/element_track.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#track.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#track.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#track.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#track.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#track.dropzone},
     {<<"hidden">>,
        case Record#track.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#track.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#track.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#track.style},
@@ -59,14 +61,14 @@ render_element(Record) ->
        case Record#track.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"default">>,
        case Record#track.default of
          true -> "default";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"kind">>,
@@ -76,7 +78,7 @@ render_element(Record) ->
          "descriptions" -> "descriptions";
          "chapters" -> "chapters";
          "metadata" -> "metadata";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"label">>, Record#track.label},

+ 14 - 17
src/elements/element_video.erl → src/elements/embed/element_video.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#video.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#video.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#video.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#video.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#video.dropzone},
     {<<"hidden">>,
        case Record#video.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#video.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#video.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#video.style},
@@ -59,34 +61,34 @@ render_element(Record) ->
        case Record#video.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"autoplay">>,
        case Record#video.autoplay of
          true -> "autoplay";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"controls">>,
        case Record#video.controls of
          true -> "controls";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"height">>, Record#video.height},
     {<<"loop">>,
        case Record#video.loop of
          true -> "loop";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"mediagroup">>, Record#video.mediagroup},
     {<<"muted">>,
        case Record#video.muted of
          true -> "muted";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"poster">>, Record#video.poster},
@@ -95,17 +97,12 @@ render_element(Record) ->
          "auto" -> "auto";
          "none" -> "none";
          "metadata" -> "metadata";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"src">>, Record#video.src},
     {<<"width">>, Record#video.width} | Record#video.data_fields
   ],
   
-  wf_tags:emit_tag(<<"video">>,
-    nitro:render(
-      case Record#video.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"video">>, nitro:render(Record#video.body), List).
 

+ 8 - 6
src/elements/element_button.erl → src/elements/form/element_button.erl

@@ -2,23 +2,25 @@
 %%-author('Andrew Zadorozhny').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#button.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#button.postback of
-    undefined -> Record#button.id;
     [] -> Record#button.id;
     Postback ->
       ID0 = case Record#button.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
-      nitro:wire(#event{type = click, postback = Postback, target = ID0,
-                 source = Record#button.source, delegate = Record#button.delegate }),
+      nitro:wire( #event{type = click, postback = Postback, target = ID0,
+                         source = Record#button.source, delegate = Record#button.delegate} ),
       ID0
   end,
   
@@ -33,7 +35,7 @@ render_element(Record) ->
     {<<"disabled">>,
       case Record#button.disabled of
         true -> "disabled";
-        _ -> undefined
+        _ -> []
       end},
-    {<<"value">>, Record#button.value}  | Record#button.data_fields ]).
+    {<<"value">>, Record#button.value} | Record#button.data_fields ]).
 

+ 15 - 17
src/elements/element_fieldset.erl → src/elements/form/element_fieldset.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#fieldset.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#fieldset.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#fieldset.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#fieldset.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#fieldset.dropzone},
     {<<"hidden">>,
        case Record#fieldset.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#fieldset.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#fieldset.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#fieldset.style},
@@ -59,29 +61,25 @@ render_element(Record) ->
        case Record#fieldset.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"disabled">>,
        case Record#fieldset.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#fieldset.form},
     {<<"name">>, Record#fieldset.name} | Record#fieldset.data_fields
   ],
   
-  wf_tags:emit_tag(<<"fieldset">>,
-    [case Record#fieldset.legend of
-       undefined -> [];
-       B -> wf_tags:emit_tag(<<"legend">>, nitro:render(B), [])
-     end,
-     nitro:render(
-       case Record#fieldset.body of
-         undefined -> [];
-         B -> B
-       end)
+  wf_tags:emit_tag(<<"fieldset">>, [
+    case Record#fieldset.legend of
+      [] -> [];
+      B -> wf_tags:emit_tag(<<"legend">>, nitro:render(B), [])
+    end,
+    nitro:render(Record#fieldset.body)
     ], List).
 

+ 26 - 10
src/elements/element_form.erl → src/elements/form/element_form.erl

@@ -2,13 +2,29 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#form.show_if == false -> [<<>>];
+
 render_element(Record) ->
+  ID = case Record#form.id of
+    [] -> nitro:temp_id();
+    I -> I
+  end,
+  case Record#form.postback of
+    [] -> skip;
+    Postback ->
+      nitro:wire( #event{type = submit, target = ID,
+                         postback = Postback,
+                         delegate = Record#form.delegate,
+                         source = Record#form.source} )
+  end,
+  
   List = [
     %% global
     {<<"accesskey">>, Record#form.accesskey},
@@ -17,7 +33,7 @@ render_element(Record) ->
        case Record#form.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#form.contextmenu},
@@ -26,30 +42,30 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#form.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#form.dropzone},
     {<<"hidden">>,
        case Record#form.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
-    {<<"id">>, Record#form.id},
+    {<<"id">>, ID},
     {<<"lang">>, Record#form.lang},
     {<<"spellcheck">>,
        case Record#form.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#form.style},
@@ -59,7 +75,7 @@ render_element(Record) ->
        case Record#form.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -69,7 +85,7 @@ render_element(Record) ->
        case Record#form.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"enctype">>,
@@ -77,7 +93,7 @@ render_element(Record) ->
          "application/x-www-form-urlencoded" -> "application/x-www-form-urlencoded";
          "multipart/form-data" -> "multipart/form-data";
          "text/plain" -> "text/plain";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"method">>,
@@ -90,7 +106,7 @@ render_element(Record) ->
     {<<"novalidate">>,
        case Record#form.novalidate of
          true -> "novalidate";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"target">>, Record#form.target} | Record#form.data_fields

+ 15 - 11
src/elements/element_keygen.erl → src/elements/form/element_keygen.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#keygen.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#keygen.postback of
-    undefined -> Record#keygen.id;
+    [] -> Record#keygen.id;
     Postback ->
       ID0 = case Record#keygen.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#keygen.source, delegate = Record#keygen.delegate} ),
+                         source = Record#keygen.source,
+                         delegate = Record#keygen.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#keygen.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#keygen.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#keygen.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#keygen.dropzone},
     {<<"hidden">>,
        case Record#keygen.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#keygen.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#keygen.style},
@@ -70,21 +74,21 @@ render_element(Record) ->
        case Record#keygen.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"autofocus">>,
        case Record#keygen.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"challenge">>, Record#keygen.challenge},
     {<<"disabled">>,
        case Record#keygen.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#keygen.form},

+ 2 - 0
src/elements/element_label.erl → src/elements/form/element_label.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#label.show_if == false -> [<<>>];
+
 render_element(Record) ->
   wf_tags:emit_tag(<<"label">>, nitro:render(Record#label.body), [
     {<<"id">>, Record#label.id},

+ 2 - 0
src/elements/element_legend.erl → src/elements/form/element_legend.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#legend.show_if == false -> [<<>>];
+
 render_element(Record) ->
   wf_tags:emit_tag(<<"legend">>, nitro:render(Record#legend.body), [
     {<<"id">>, Record#legend.id},

+ 7 - 12
src/elements/element_meter.erl → src/elements/form/element_meter.erl

@@ -17,7 +17,7 @@ render_element(Record) ->
        case Record#meter.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#meter.contextmenu},
@@ -26,21 +26,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#meter.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#meter.dropzone},
     {<<"hidden">>,
        case Record#meter.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#meter.id},
@@ -49,7 +49,7 @@ render_element(Record) ->
        case Record#meter.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#meter.style},
@@ -59,7 +59,7 @@ render_element(Record) ->
        case Record#meter.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -71,10 +71,5 @@ render_element(Record) ->
     {<<"value">>, Record#meter.value} | Record#meter.data_fields
   ],
   
-  wf_tags:emit_tag(<<"meter">>,
-    nitro:render(
-      case Record#meter.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"meter">>, nitro:render(Record#meter.body), List).
 

+ 12 - 15
src/elements/element_output.erl → src/elements/form/element_output.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#output.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#output.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#output.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#output.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#output.dropzone},
     {<<"hidden">>,
        case Record#output.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#output.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#output.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#output.style},
@@ -59,19 +61,14 @@ render_element(Record) ->
        case Record#output.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
-    {<<"for">>,Record#output.for},
-    {<<"form">>,Record#output.form},
-    {<<"name">>,Record#output.name} | Record#output.data_fields
+    {<<"for">>, Record#output.for},
+    {<<"form">>, Record#output.form},
+    {<<"name">>, Record#output.name} | Record#output.data_fields
   ],
   
-  wf_tags:emit_tag(<<"output">>,
-    nitro:render(
-      case Record#output.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"output">>, nitro:render(Record#output.body), List).
 

+ 9 - 12
src/elements/element_progress.erl → src/elements/form/element_progress.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#progress.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#progress.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#progress.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#progress.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#progress.dropzone},
     {<<"hidden">>,
        case Record#progress.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#progress.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#progress.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#progress.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#progress.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -67,10 +69,5 @@ render_element(Record) ->
     {<<"value">>, Record#progress.value} | Record#progress.data_fields
   ],
   
-  wf_tags:emit_tag(<<"progress">>,
-    nitro:render(
-      case Record#progress.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"progress">>, nitro:render(Record#progress.body), List).
 

+ 26 - 27
src/elements/element_select.erl → src/elements/form/element_select.erl

@@ -2,24 +2,26 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#select.show_if == false -> [<<>>];
+
 render_element(Record = #select{}) ->
   ID = case Record#select.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     I -> I
   end,
   case Record#select.postback of
-    undefined -> skip;
+    [] -> skip;
     Postback ->
-      nitro:wire( #event{type = change,
-                         target = ID,
+      nitro:wire( #event{type = change, target = ID,
                          postback = Postback,
-                         source = [ nitro:to_atom(ID) ],
+                         source = [ nitro:to_atom(ID)|Record#select.source ],
                          delegate = Record#select.delegate} )
   end,
   Props = [
@@ -32,19 +34,19 @@ render_element(Record = #select{}) ->
     {<<"required">>,
        case Record#select.required of
          true -> <<"required">>;
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#select.disabled of
          true -> <<"disabled">>;
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"multiple">>,
        case Record#select.multiple of
          true -> <<"multiple">>;
-         _ -> undefined
+         _ -> []
        end} | Record#select.data_fields
   ],
   wf_tags:emit_tag(<<"select">>, nitro:render(Record#select.body), Props);
@@ -55,7 +57,7 @@ render_element(Group = #optgroup{}) ->
     {<<"disabled">>,
        case Group#optgroup.disabled of
          true -> <<"disabled">>;
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"label">>, Group#optgroup.label}
@@ -63,22 +65,19 @@ render_element(Group = #optgroup{}) ->
 
 
 render_element(O = #option{}) ->
-  wf_tags:emit_tag(<<"option">>, nitro:render(O#option.body), [
-    {<<"id">>, O#option.id},
-    {<<"disabled">>,
-       case O#option.disabled of
-         true -> <<"disabled">>;
-         _ -> undefined
-       end},
-    
-    {<<"label">>, O#option.label},
-    {<<"title">>, O#option.title},
-    {<<"selected">>,
-       case O#option.selected of
-         true -> <<"selected">>;
-         _ -> undefined
-       end},
-    
-    {<<"value">>, O#option.value} | O#option.data_fields
-  ]).
+  wf_tags:emit_tag(<<"option">>, nitro:render(O#option.body), lists:flatten([get_attrs(O) | O#option.data_fields])).
+
+
+get_attrs(O) ->
+  ValueAttr = case {O#option.selected, O#option.disabled} of
+    {true, true} -> <<"selected disabled value">>;
+    {true, _} -> <<"selected value">>;
+    {_, true} -> <<"disabled value">>;
+    _ -> <<"value">>
+  end,
+  [{<<"id">>, O#option.id},
+   {<<"label">>, O#option.label},
+   {<<"title">>, O#option.title},
+   {ValueAttr, O#option.value}
+  ].
 

+ 14 - 17
src/elements/element_textarea.erl → src/elements/form/element_textarea.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#textarea.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#textarea.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#textarea.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#textarea.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#textarea.dropzone},
     {<<"hidden">>,
        case Record#textarea.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#textarea.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#textarea.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#textarea.style},
@@ -59,14 +61,14 @@ render_element(Record) ->
        case Record#textarea.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"autofocus">>,
        case Record#textarea.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"cols">>, Record#textarea.cols},
@@ -74,7 +76,7 @@ render_element(Record) ->
     {<<"disabled">>,
        case Record#textarea.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#textarea.form},
@@ -84,13 +86,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#textarea.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#textarea.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"rows">>, Record#textarea.rows},
@@ -100,14 +102,9 @@ render_element(Record) ->
        case Record#textarea.wrap of
          "hard" -> "hard";
          "soft" -> "soft";
-         _ -> undefined
+         _ -> []
        end} | Record#textarea.data_fields
   ],
   
-  wf_tags:emit_tag(<<"textarea">>,
-    nitro:render(
-      case Record#textarea.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"textarea">>, nitro:render(Record#textarea.body), List).
 

+ 2 - 2
src/elements/element_blockquote.erl → src/elements/group/element_blockquote.erl

@@ -1,7 +1,7 @@
 -module (element_blockquote).
 %%-author('Andrew Zadorozhny').
 
--include("nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
 
 -export([
   render_element/1
@@ -13,6 +13,6 @@ render_element(Record) ->
     {<<"id">>, Record#blockquote.id},
     {<<"class">>, Record#blockquote.class},
     {<<"style">>, Record#blockquote.style},
-    {<<"cite">>, Record#blockquote.cite}  | Record#blockquote.data_fields
+    {<<"cite">>, Record#blockquote.cite} | Record#blockquote.data_fields
   ]).
 

+ 4 - 2
src/elements/element_dtl.erl → src/elements/group/element_dtl.erl

@@ -1,15 +1,17 @@
 -module(element_dtl).
 %%-author('Maxim Sokhatsky').
 
--include("nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#dtl.show_if == false -> [<<>>];
+
 render_element(Record = #dtl{}) ->
-  M = list_to_atom(nitro:to_list(Record#dtl.file) ++ "_view"),
+  M = erlang:list_to_atom(nitro:to_list(Record#dtl.file) ++ "_view"),
   %File = case code:lib_dir(nitro:to_atom(Record#dtl.app)) of
   %  {error,bad_name} -> nitro:to_list(Record#dtl.app);
   %  A -> A

+ 9 - 12
src/elements/element_html.erl → src/elements/group/element_html.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#html.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#html.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#html.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#html.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#html.dropzone},
     {<<"hidden">>,
        case Record#html.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#html.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#html.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#html.style},
@@ -59,17 +61,12 @@ render_element(Record) ->
        case Record#html.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"manifest">>, Record#html.manifest} | Record#html.data_fields
   ],
   
-  wf_tags:emit_tag(<<"html">>,
-    nitro:render(
-      case Record#html.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"html">>, nitro:render(Record#html.body), List).
 

+ 2 - 0
src/elements/element_li.erl → src/elements/group/element_li.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#li.show_if == false -> [<<>>];
+
 render_element(Record) ->
   wf_tags:emit_tag(<<"li">>, nitro:render(Record#li.body), [
     {<<"class">>, Record#li.class},

+ 14 - 13
src/elements/element_script.erl → src/elements/group/element_script.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#script.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#script.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#script.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#script.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#script.dropzone},
     {<<"hidden">>,
        case Record#script.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#script.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#script.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#script.style},
@@ -59,21 +61,21 @@ render_element(Record) ->
        case Record#script.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"async">>,
        case Record#script.async of
          true -> "async";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"charset">>, Record#script.charset},
     {<<"defer">>,
        case Record#script.defer of
          true -> "defer";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"src">>, Record#script.src},
@@ -81,9 +83,8 @@ render_element(Record) ->
   ],
   
   wf_tags:emit_tag(<<"script">>,
-    nitro:render(
-      case Record#script.body of
-        undefined -> [];
-        B -> B
-      end), List).
+    case Record#script.src of
+      [] -> nitro:render(Record#script.body);
+      _ -> []
+    end, List).
 

+ 22 - 18
src/elements/element_checkbox.erl → src/elements/input/element_checkbox.erl

@@ -2,22 +2,26 @@
 %%-author('Rusty Klophaus, Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#checkbox.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#checkbox.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     I -> I
   end,
   case Record#checkbox.postback of
-      undefined -> ignore;
+      [] -> skip;
       Postback ->
         nitro:wire( #event{ type = change, postback = Postback, target = Id,
-                            source = Record#checkbox.source, delegate = Record#checkbox.delegate })
+                            source = [Id | Record#checkbox.source],
+                            delegate = Record#checkbox.delegate} )
   end,
   Label = [ wf_tags:emit_tag(<<"input">>, [], [
     %% global
@@ -27,7 +31,7 @@ render_element(Record) ->
        case Record#checkbox.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#checkbox.contextmenu},
@@ -36,21 +40,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#checkbox.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#checkbox.dropzone},
     {<<"hidden">>,
        case Record#checkbox.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -59,7 +63,7 @@ render_element(Record) ->
        case Record#checkbox.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#checkbox.style},
@@ -69,7 +73,7 @@ render_element(Record) ->
        case Record#checkbox.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -77,14 +81,14 @@ render_element(Record) ->
     {<<"checked">>,
        case Record#checkbox.checked of
          true -> <<"checked">>;
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"data-toggle">>, <<"checkbox">>},
     {<<"disabled">>,
        case Record#checkbox.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#checkbox.form},
@@ -92,17 +96,17 @@ render_element(Record) ->
     {<<"required">>,
        case Record#checkbox.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"type">>, <<"checkbox">>},
     {<<"value">>, Record#checkbox.value} | Record#checkbox.data_fields
-    ]),
-    
-    case Record#checkbox.body of
-      undefined -> [];
-      B -> B
-    end
+  ]),
+  
+  case Record#checkbox.body of
+    [] -> [];
+    B -> B
+  end
   ],
   
   wf_tags:emit_tag(<<"label">>, nitro:render(Label), [

+ 16 - 12
src/elements/element_color.erl → src/elements/input/element_color.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#color.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#color.postback of
-    undefined -> Record#color.id;
+    [] -> Record#color.id;
     Postback ->
       ID0 = case Record#color.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#color.source, delegate = Record#color.delegate }),
+                         source = Record#color.source,
+                         delegate = Record#color.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#color.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#color.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#color.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#color.dropzone},
     {<<"hidden">>,
        case Record#color.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#color.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#color.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#color.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
         case Record#color.autocomplete of
           true -> "on";
           false -> "off";
-          _ -> undefined
+          _ -> []
         end},
     
     {<<"autofocus">>,
        case Record#color.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#color.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#color.form},

+ 18 - 14
src/elements/element_date.erl → src/elements/input/element_date.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#date.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#date.postback of
-    undefined -> Record#date.id;
+    [] -> Record#date.id;
     Postback ->
       ID0 = case Record#date.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#date.source, delegate = Record#date.delegate }),
+                         source = Record#date.source,
+                         delegate = Record#date.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#date.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#date.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#date.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#date.dropzone},
     {<<"hidden">>,
        case Record#date.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#date.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#date.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#date.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#date.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#date.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#date.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#date.form},
@@ -101,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#date.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#date.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#date.step},

+ 17 - 14
src/elements/element_datetime.erl → src/elements/input/element_datetime.erl

@@ -2,22 +2,25 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
+render_element(Record) when Record#datetime.show_if == false -> [<<>>];
 
 render_element(Record) ->
   Id = case Record#datetime.postback of
-    undefined -> Record#datetime.id;
+    [] -> Record#datetime.id;
     Postback ->
       ID0 = case Record#datetime.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#datetime.source, delegate = Record#datetime.delegate} ),
+                         source = Record#datetime.source,
+                         delegate = Record#datetime.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +31,7 @@ render_element(Record) ->
        case Record#datetime.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#datetime.contextmenu},
@@ -37,21 +40,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#datetime.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#datetime.dropzone},
     {<<"hidden">>,
        case Record#datetime.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +63,7 @@ render_element(Record) ->
        case Record#datetime.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#datetime.style},
@@ -70,7 +73,7 @@ render_element(Record) ->
        case Record#datetime.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +81,19 @@ render_element(Record) ->
        case Record#datetime.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#datetime.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#datetime.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#datetime.form},
@@ -102,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#datetime.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#datetime.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#datetime.step},

+ 18 - 14
src/elements/element_datetime_local.erl → src/elements/input/element_datetime_local.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#datetime_local.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#datetime_local.postback of
-    undefined -> Record#datetime_local.id;
+    [] -> Record#datetime_local.id;
     Postback ->
       ID0 = case Record#datetime_local.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#datetime_local.source, delegate = Record#datetime_local.delegate} ),
+                         source = Record#datetime_local.source,
+                         delegate = Record#datetime_local.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#datetime_local.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#datetime_local.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#datetime_local.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#datetime_local.dropzone},
     {<<"hidden">>,
        case Record#datetime_local.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
         case Record#datetime_local.spellcheck of
           true -> "true";
           false -> "false";
-          _ -> undefined
+          _ -> []
         end},
     
     {<<"style">>, Record#datetime_local.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#datetime_local.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#datetime_local.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#datetime_local.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#datetime_local.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#datetime_local.form},
@@ -102,13 +106,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#datetime_local.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#datetime_local.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#datetime_local.step},

+ 11 - 8
src/elements/element_dropdown.erl → src/elements/input/element_dropdown.erl

@@ -2,22 +2,26 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#dropdown.show_if == false -> [<<>>];
+
 render_element(Record = #dropdown{}) ->
   ID = case Record#dropdown.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     I -> I
   end,
   case Record#dropdown.postback of
-    undefined -> skip;
+    [] -> skip;
     Postback ->
       nitro:wire( #event{type = click, postback = Postback, target = ID,
-                         source = Record#dropdown.source, delegate = Record#dropdown.delegate} )
+                         source = Record#dropdown.source,
+                         delegate = Record#dropdown.delegate} )
   end,
   
   Opts = [ wf_tags:emit_tag(<<"option">>, [O#option.label], [
@@ -26,7 +30,7 @@ render_element(Record = #dropdown{}) ->
     {<<"selected">>,
       case O#option.selected of
         true -> <<"selected">>;
-        _ -> undefined
+        _ -> []
       end},
     {<<"value">>, O#option.value}
     ]) || O = #option{show_if = Visible} <- Record#dropdown.options, Visible == true],
@@ -39,13 +43,12 @@ render_element(Record = #dropdown{}) ->
     {<<"disabled">>,
        case Record#dropdown.disabled of
          true -> <<"disabled">>;
-         _ -> undefined
+         _ -> []
        end},
     {<<"multiple">>,
        case Record#dropdown.multiple of
          true -> <<"multiple">>;
-         _ -> undefined
-       end}|
-    Record#dropdown.data_fields
+         _ -> []
+       end} | Record#dropdown.data_fields
   ]).
 

+ 19 - 15
src/elements/element_email.erl → src/elements/input/element_email.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#email.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#email.postback of
-    undefined -> Record#email.id;
+    [] -> Record#email.id;
     Postback ->
       ID0 = case Record#email.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#email.source, delegate = Record#email.delegate} ),
+                         source = Record#email.source,
+                         delegate = Record#email.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#email.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#email.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#email.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#email.dropzone},
     {<<"hidden">>,
        case Record#email.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#email.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#email.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#email.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#email.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#email.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#email.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#email.form},
@@ -99,7 +103,7 @@ render_element(Record) ->
     {<<"multiple">>,
        case Record#email.multiple of
          true -> "multiple";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"name">>, Record#email.name},
@@ -108,13 +112,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#email.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#email.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end}, 
     
     {<<"size">>, Record#email.size},

+ 17 - 13
src/elements/element_file.erl → src/elements/input/element_file.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#file.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#file.postback of
-    undefined -> Record#file.id;
+    [] -> Record#file.id;
     Postback ->
       ID0 = case Record#file.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#file.source, delegate = Record#file.delegate} ),
+                         source = Record#file.source,
+                         delegate = Record#file.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#file.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#file.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#file.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#file.dropzone},
     {<<"hidden">>,
        case Record#file.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#file.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#file.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#file.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,27 +82,27 @@ render_element(Record) ->
     {<<"autofocus">>,
        case Record#file.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#file.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#file.form},
     {<<"multiple">>,
        case Record#file.multiple of
          true -> "multiple";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"name">>, Record#file.name},
     {<<"required">>,
        case Record#file.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"type">>, <<"file">>} | Record#file.data_fields

+ 9 - 7
src/elements/element_hidden.erl → src/elements/input/element_hidden.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#hidden.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#hidden.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#hidden.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#hidden.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#hidden.dropzone},
     {<<"hidden">>,
        case Record#hidden.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#hidden.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#hidden.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#hidden.style},
@@ -59,14 +61,14 @@ render_element(Record) ->
        case Record#hidden.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"disabled">>,
        case Record#hidden.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#hidden.form},

+ 33 - 10
src/elements/element_input.erl → src/elements/input/element_input.erl

@@ -2,23 +2,26 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#input.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#input.postback of
-    undefined -> Record#input.id;
     [] -> Record#input.id;
     Postback ->
       ID0 = case Record#input.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#input.source, delegate = Record#input.delegate} ),
+                         source = Record#input.source,
+                         delegate = Record#input.delegate} ),
       ID0
   end,
   List = [
@@ -29,7 +32,7 @@ render_element(Record) ->
        case Record#input.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#input.contextmenu},
@@ -38,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#input.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#input.dropzone},
     {<<"hidden">>,
        case Record#input.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -61,7 +64,7 @@ render_element(Record) ->
        case Record#input.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#input.style},
@@ -71,30 +74,50 @@ render_element(Record) ->
        case Record#input.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
+    {<<"autocomplete">>,Record#input.autocomplete},
     {<<"autofocus">>, Record#input.autofocus},
     {<<"disabled">>,
        case Record#input.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"name">>, Record#input.name},
     {<<"type">>, Record#input.type},
     {<<"accept">>, Record#input.accept},
     {<<"max">>, Record#input.max},
+    {<<"checked">>,
+       case Record#input.checked of
+         true -> true; %% todo return string
+         _ -> []
+       end},
+    
+    {<<"aria-states">>, Record#input.aria_states},
     {<<"placeholder">>, Record#input.placeholder},
     {<<"min">>, Record#input.min},
     {<<"multiple">>, Record#input.multiple},
     {<<"pattern">>, Record#input.pattern},
     {<<"value">>, Record#input.value},
+    {<<"data-bind">>,
+       case Record#input.bind of
+         [] -> [];
+         X -> base64:encode( erlang:term_to_binary(X) )
+       end},
+    
     {<<"onkeypress">>, Record#input.onkeypress},
     {<<"onkeyup">>, Record#input.onkeyup},
     {<<"onkeydown">>, Record#input.onkeydown},
     {<<"onclick">>, Record#input.onclick},
+    {<<"required">>,
+      case Record#input.required of
+        true -> "required";
+        _ -> []
+      end},
+    
     {<<"onchange">>, Record#input.onchange} | Record#input.data_fields
   ],
   

+ 14 - 10
src/elements/element_input_button.erl → src/elements/input/element_input_button.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#input_button.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#input_button.postback of
-    undefined -> Record#input_button.id;
+    [] -> Record#input_button.id;
     Postback ->
       ID0 = case Record#input_button.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#input_button.source, delegate = Record#input_button.delegate} ),
+                         source = Record#input_button.source,
+                         delegate = Record#input_button.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#input_button.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#input_button.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#input_button.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#input_button.dropzone},
     {<<"hidden">>,
        case Record#input_button.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#input_button.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#input_button.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#input_button.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,7 +82,7 @@ render_element(Record) ->
     {<<"disabled">>,
        case Record#input_button.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"name">>, Record#input_button.name},

+ 15 - 11
src/elements/element_input_image.erl → src/elements/input/element_input_image.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#input_image.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#input_image.postback of
-    undefined -> Record#input_image.id;
+    [] -> Record#input_image.id;
     Postback ->
       ID0 = case Record#input_image.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#input_image.source, delegate = Record#input_image.delegate} ),
+                         source = Record#input_image.source,
+                         delegate = Record#input_image.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#input_image.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#input_image.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#input_image.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#input_image.dropzone},
     {<<"hidden">>,
        case Record#input_image.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#input_image.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#input_image.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#input_image.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,13 +82,13 @@ render_element(Record) ->
     {<<"autofocus">>,
        case Record#input_image.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#input_image.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#input_image.form},

+ 18 - 14
src/elements/element_input_time.erl → src/elements/input/element_input_time.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#input_time.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#input_time.postback of
-    undefined -> Record#input_time.id;
+    [] -> Record#input_time.id;
     Postback ->
       ID0 = case Record#input_time.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#input_time.source, delegate = Record#input_time.delegate} ),
+                         source = Record#input_time.source,
+                         delegate = Record#input_time.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#input_time.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#input_time.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#input_time.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#input_time.dropzone},
     {<<"hidden">>,
        case Record#input_time.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#input_time.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#input_time.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#input_time.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#input_time.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#input_time.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#input_time.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#input_time.form},
@@ -101,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#input_time.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#input_time.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#input_time.step},

+ 13 - 9
src/elements/element_link.erl → src/elements/input/element_link.erl

@@ -2,22 +2,26 @@
 %%-author('Rusty Klophaus').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#link.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#link.postback of
-    undefined -> Record#link.id;
+    [] -> Record#link.id;
     Postback ->
       ID0 = case Record#link.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{ type = click, postback = Postback, target = ID0,
-                          source = Record#link.source, delegate = Record#link.delegate,
+                          source = Record#link.source,
+                          delegate = Record#link.delegate,
                           validation = Record#link.validate} ),
       ID0
   end,
@@ -29,7 +33,7 @@ render_element(Record) ->
        case Record#link.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#link.contextmenu},
@@ -38,21 +42,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#link.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#link.dropzone},
     {<<"hidden">>,
        case Record#link.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -61,7 +65,7 @@ render_element(Record) ->
        case Record#link.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#link.style},
@@ -71,7 +75,7 @@ render_element(Record) ->
        case Record#link.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 2 - 0
src/elements/element_list.erl → src/elements/input/element_list.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#list.show_if == false -> [<<>>];
+
 render_element(Record = #list{}) ->
   Tag = case Record#list.numbered of
     true -> <<"ol">>;

+ 2 - 0
src/elements/element_literal.erl → src/elements/input/element_literal.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#literal.show_if == false -> [<<>>];
+
 render_element(Record = #literal{}) ->
   case Record#literal.html_encode of
     true -> nitro:html_encode(Record#literal.body);

+ 17 - 13
src/elements/element_month.erl → src/elements/input/element_month.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#month.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#month.postback of
-    undefined -> Record#month.id;
+    [] -> Record#month.id;
     Postback ->
       ID0 = case Record#month.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#month.source, delegate = Record#month.delegate} ),
+                         source = Record#month.source,
+                         delegate = Record#month.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#month.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#month.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#month.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#month.dropzone},
     {<<"hidden">>,
        case Record#month.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#month.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#month.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#month.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,13 +82,13 @@ render_element(Record) ->
     {<<"autofocus">>,
        case Record#month.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#month.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#month.form},
@@ -94,13 +98,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#month.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#month.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#month.step},

+ 18 - 14
src/elements/element_number.erl → src/elements/input/element_number.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#number.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#number.postback of
-    undefined -> Record#number.id;
+    [] -> Record#number.id;
     Postback ->
       ID0 = case Record#number.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#number.source, delegate = Record#number.delegate} ),
+                         source = Record#number.source,
+                         delegate = Record#number.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#number.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#number.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#number.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#number.dropzone},
     {<<"hidden">>,
        case Record#number.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#number.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#number.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#number.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#number.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#number.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#number.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#number.form},
@@ -102,13 +106,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#number.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#number.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#number.step},

+ 18 - 14
src/elements/element_password.erl → src/elements/input/element_password.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#password.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#password.postback of
-    undefined -> Record#password.id;
+    [] -> Record#password.id;
     Postback ->
       ID0 = case Record#password.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#password.source, delegate = Record#password.delegate} ),
+                         source = Record#password.source,
+                         delegate = Record#password.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#password.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#password.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#password.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#password.dropzone},
     {<<"hidden">>,
        case Record#password.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#password.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#password.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#password.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#password.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#password.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#password.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#password.form},
@@ -101,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#password.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#password.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"size">>, Record#password.size},

+ 9 - 7
src/elements/element_radio.erl → src/elements/input/element_radio.erl

@@ -2,26 +2,28 @@
 %%-author('Rusty Klophaus').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#radio.show_if == false -> [<<>>];
+
 render_element(Record) ->
   ID = case Record#radio.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     RadioID -> RadioID
   end,
   
   case Record#radio.postback of
-    undefined -> ignore;
+    [] -> skip;
     Postback ->
-      nitro:wire( #event{type = change, postback = Postback,
-                         target = ID, delegate = Record#radio.delegate} )
+      nitro:wire( #event{type = change, postback = Postback, target = ID,
+                         delegate = Record#radio.delegate} )
   end,
   
-  Content = nitro:render(Record#radio.body),
   TypeChecked = case Record#radio.checked of
     true -> [{<<"checked">>, <<"">>}, {<<"type">>, <<"radio">>}];
     _    -> [{<<"type">>, <<"radio">>}]
@@ -30,7 +32,7 @@ render_element(Record) ->
     _    -> []
   end,
   
-  [wf_tags:emit_tag(<<"input">>, Content, TypeChecked ++ [
+  [wf_tags:emit_tag(<<"input">>, nitro:render(Record#radio.body), TypeChecked ++ [
     {<<"id">>, ID},
     {<<"value">>, Record#radio.value},
     {<<"name">>, nitro:coalesce( [Record#radio.html_name, Record#radio.name] )},
@@ -40,7 +42,7 @@ render_element(Record) ->
     {<<"required">>,
        case Record#radio.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end}
   ]) ].
 

+ 11 - 8
src/elements/element_radiogroup.erl → src/elements/input/element_radiogroup.erl

@@ -8,15 +8,18 @@
 ]).
 
 
+render_element(Record) when Record#radiogroup.show_if == false -> [<<>>];
+
 render_element(Record) ->
   ID = Record#radiogroup.id,
   Body = apply_name(ID, Record#radiogroup.body),
-  wf_render_elements:render_element( #panel{
-    id = ID,
-    class = [radiogroup, Record#radiogroup.class],
-    style = Record#radiogroup.style,
-    body = Body
-  }).
+  wf_render_elements:render_element(
+    #panel{
+      id = ID,
+      class = [radiogroup, Record#radiogroup.class],
+      style = Record#radiogroup.style,
+      body = Body
+    }).
 
 %% TODO: This whole thing needs to be replaced with a smarter recursive search.
 %% As it is, it won't dive into the bodies of subelements. A recursive map (ie: nitro:map_body) would be
@@ -24,7 +27,7 @@ render_element(Record) ->
 
 apply_name(Name, Terms) -> [ do_apply(Name, X) || X <- Terms ].
 
-do_apply(Name, X) when is_record(X, radio) -> X#radio{name = Name};
-do_apply(Name, List) when is_list(List) -> apply_name(Name, List);
+do_apply(Name, X) when erlang:is_record(X, radio) -> X#radio{name = Name};
+do_apply(Name, List) when erlang:is_list(List) -> apply_name(Name, List);
 do_apply(_Name, X) -> X.
 

+ 16 - 12
src/elements/element_range.erl → src/elements/input/element_range.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#range.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#range.postback of
-    undefined -> Record#range.id;
+    [] -> Record#range.id;
     Postback ->
       ID0 = case Record#range.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#range.source, delegate = Record#range.delegate} ),
+                         source = Record#range.source,
+                         delegate = Record#range.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#range.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#range.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#range.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#range.dropzone},
     {<<"hidden">>,
        case Record#range.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#range.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#range.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#range.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#range.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#range.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#range.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#range.form},

+ 15 - 11
src/elements/element_reset.erl → src/elements/input/element_reset.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#reset.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#reset.postback of
-    undefined -> Record#reset.id;
+    [] -> Record#reset.id;
     Postback ->
       ID0 = case Record#reset.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#reset.source, delegate = Record#reset.delegate} ),
+                         source = Record#reset.source,
+                         delegate = Record#reset.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#reset.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#reset.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#reset.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#reset.dropzone},
     {<<"hidden">>,
        case Record#reset.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#reset.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#reset.style},
@@ -70,20 +74,20 @@ render_element(Record) ->
        case Record#reset.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"autofocus">>,
        case Record#reset.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#reset.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#reset.form},

+ 18 - 14
src/elements/element_search.erl → src/elements/input/element_search.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#search.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#search.postback of
-    undefined -> Record#search.id;
+    [] -> Record#search.id;
     Postback ->
       ID0 = case Record#search.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#search.source, delegate = Record#search.delegate} ),
+                         source = Record#search.source,
+                         delegate = Record#search.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#search.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#search.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#search.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#search.dropzone},
     {<<"hidden">>,
        case Record#search.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#search.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#search.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#search.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,20 +82,20 @@ render_element(Record) ->
        case Record#search.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#search.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dirname">>, Record#search.dirname},
     {<<"disabled">>,
        case Record#search.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#search.form},
@@ -103,13 +107,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#search.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#search.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"size">>, Record#search.size},

+ 7 - 5
src/elements/element_submit.erl → src/elements/input/element_submit.erl

@@ -2,27 +2,29 @@
 %%-author('Andrew Zadorozhny').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#submit.show_if == false -> [<<>>];
+
 render_element(Record) ->
   ID = case Record#submit.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     I -> I
   end,
   case Record#submit.postback of
-    undefined -> skip;
+    [] -> skip;
     Postback ->
-      nitro:wire( #event{type = click,
-                         target = ID,
+      nitro:wire( #event{type = click, target = ID,
                          postback = Postback,
                          source = Record#submit.source} )
   end,
   case Record#submit.click of
-    undefined -> ignore;
+    [] -> skip;
     ClickActions ->
       nitro:wire( #event{target = ID, type = click, actions = ClickActions} )
   end,

+ 16 - 13
src/elements/element_tel.erl → src/elements/input/element_tel.erl

@@ -2,18 +2,21 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#tel.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#tel.postback of
-    undefined -> Record#tel.id;
+    [] -> Record#tel.id;
     Postback ->
       ID0 = case Record#tel.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
@@ -28,7 +31,7 @@ render_element(Record) ->
        case Record#tel.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#tel.contextmenu},
@@ -37,21 +40,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#tel.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#tel.dropzone},
     {<<"hidden">>,
        case Record#tel.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +63,7 @@ render_element(Record) ->
        case Record#tel.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#tel.style},
@@ -70,7 +73,7 @@ render_element(Record) ->
        case Record#tel.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +81,19 @@ render_element(Record) ->
        case Record#tel.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#tel.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#tel.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#tel.form},
@@ -102,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#tel.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#tel.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"size">>, Record#tel.size},

+ 5 - 3
src/elements/element_textbox.erl → src/elements/input/element_textbox.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#textbox.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     {<<"id">>, Record#textbox.id},
@@ -22,18 +24,18 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#textbox.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#textbox.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"class">>, Record#textbox.class} | Record#textbox.data_fields
   ] ++ case Record#textbox.disabled of
-    undefined -> [];
+    [] -> [];
     _ -> [{<<"disabled">>, <<"disabled">>}]
   end,
   

+ 8 - 6
src/elements/element_time.erl → src/elements/input/element_time.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#time.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#time.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#time.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#time.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#time.dropzone},
     {<<"hidden">>,
        case Record#time.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#time.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#time.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#time.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#time.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 18 - 14
src/elements/element_url.erl → src/elements/input/element_url.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#url.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#url.postback of
-    undefined -> Record#url.id;
+    [] -> Record#url.id;
     Postback ->
       ID0 = case Record#url.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#url.source, delegate = Record#url.delegate} ),
+                         source = Record#url.source,
+                         delegate = Record#url.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#url.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#url.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#url.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#url.dropzone},
     {<<"hidden">>,
        case Record#url.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#url.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#url.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#url.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#url.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#url.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#url.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#url.form},
@@ -102,13 +106,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#url.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#url.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"size">>,Record#url.size},

+ 18 - 14
src/elements/element_week.erl → src/elements/input/element_week.erl

@@ -2,22 +2,26 @@
 %%-author('Vladimir Galunshchikov').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#week.show_if == false -> [<<>>];
+
 render_element(Record) ->
   Id = case Record#week.postback of
-    undefined -> Record#week.id;
+    [] -> Record#week.id;
     Postback ->
       ID0 = case Record#week.id of
-        undefined -> nitro:temp_id();
+        [] -> nitro:temp_id();
         I -> I
       end,
       nitro:wire( #event{type = click, postback = Postback, target = ID0,
-                         source = Record#week.source, delegate = Record#week.delegate} ),
+                         source = Record#week.source,
+                         delegate = Record#week.delegate} ),
       ID0
   end,
   List = [
@@ -28,7 +32,7 @@ render_element(Record) ->
        case Record#week.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#week.contextmenu},
@@ -37,21 +41,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#week.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#week.dropzone},
     {<<"hidden">>,
        case Record#week.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Id},
@@ -60,7 +64,7 @@ render_element(Record) ->
        case Record#week.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#week.style},
@@ -70,7 +74,7 @@ render_element(Record) ->
        case Record#week.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -78,19 +82,19 @@ render_element(Record) ->
        case Record#week.autocomplete of
          true -> "on";
          false -> "off";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"autofocus">>,
        case Record#week.autofocus of
          true -> "autofocus";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"disabled">>,
        case Record#week.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"form">>, Record#week.form},
@@ -101,13 +105,13 @@ render_element(Record) ->
     {<<"readonly">>,
        case Record#week.readonly of
          true -> "readonly";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"required">>,
        case Record#week.required of
          true -> "required";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"step">>, Record#week.step},

+ 10 - 8
src/elements/element_command.erl → src/elements/interactive/element_command.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#command.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#command.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#command.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#command.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#command.dropzone},
     {<<"hidden">>,
        case Record#command.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#command.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#command.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#command.style},
@@ -59,14 +61,14 @@ render_element(Record) ->
        case Record#command.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"disabled">>,
        case Record#command.disabled of
          true -> "disabled";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"icon">>, Record#command.icon},
@@ -77,7 +79,7 @@ render_element(Record) ->
          "command" -> "command";
          "radio" -> "radio";
          "checkbox" -> "checkbox";
-         _ -> undefined
+         _ -> []
        end} | Record#command.data_fields
   ],
   

+ 10 - 8
src/elements/element_details.erl → src/elements/interactive/element_details.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#details.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#details.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#details.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#details.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#details.dropzone},
     {<<"hidden">>,
        case Record#details.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#details.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#details.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#details.style},
@@ -59,21 +61,21 @@ render_element(Record) ->
        case Record#details.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"open">>,
        case Record#details.open of
          true -> "open";
-         _ -> undefined
+         _ -> []
        end} | Record#details.data_fields
   ],
   
   wf_tags:emit_tag(<<"details">>,
     nitro:render(
       case Record#details.body of
-        undefined -> [];
+        [] -> [];
         B -> B
       end), List).
 

+ 10 - 13
src/elements/element_menu.erl → src/elements/interactive/element_menu.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#menu.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#menu.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#menu.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#menu.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#menu.dropzone},
     {<<"hidden">>,
        case Record#menu.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#menu.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#menu.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#menu.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#menu.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -68,14 +70,9 @@ render_element(Record) ->
        case Record#menu.type of
          "toolbar" -> "toolbar";
          "context" -> "context";
-         _ -> undefined
+         _ -> []
        end} | Record#menu.data_fields
   ],
   
-  wf_tags:emit_tag(<<"menu">>,
-    nitro:render(
-      case Record#menu.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"menu">>, nitro:render(Record#menu.body), List).
 

+ 9 - 12
src/elements/element_summary.erl → src/elements/interactive/element_summary.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#summary.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#summary.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#summary.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#summary.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#summary.dropzone},
     {<<"hidden">>,
        case Record#summary.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#summary.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#summary.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#summary.style},
@@ -59,14 +61,9 @@ render_element(Record) ->
        case Record#summary.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end} | Record#summary.data_fields
   ],
   
-  wf_tags:emit_tag(<<"summary">>,
-    nitro:render(
-      case Record#summary.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"summary">>, nitro:render(Record#summary.body), List).
 

+ 5 - 2
src/elements/element_upload.erl → src/elements/interactive/element_upload.erl

@@ -2,15 +2,18 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#upload.show_if == false -> [<<>>];
+
 render_element(#upload{id=Id}) ->
   Uid = case Id of
-    undefined -> wf:temp_id();
+    [] -> wf:temp_id();
     I -> I
   end,
   
@@ -27,7 +30,7 @@ render_element(#upload{id=Id}) ->
              #span { body = [
                #button{ id = ftp_open,  body = "Browse" },
                #button{ id = ftp_start, body = "Upload" },
-               #button{ id = ftp_stop,  body = "Stop" }
+               #button{ id = ftp_stop,  body = "Stop" }     %% todo customisation
              ]}
   ]},
   wf:render(Upload).

+ 8 - 6
src/elements/element_meta.erl → src/elements/meta/element_meta.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#meta.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#meta.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#meta.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#meta.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#meta.dropzone},
     {<<"hidden">>,
        case Record#meta.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#meta.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#meta.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#meta.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#meta.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 6 - 6
src/elements/element_meta_base.erl → src/elements/meta/element_meta_base.erl

@@ -17,7 +17,7 @@ render_element(Record) ->
        case Record#base.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#base.contextmenu},
@@ -26,21 +26,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#base.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#base.dropzone},
     {<<"hidden">>,
        case Record#base.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#base.id},
@@ -49,7 +49,7 @@ render_element(Record) ->
        case Record#base.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#base.style},
@@ -59,7 +59,7 @@ render_element(Record) ->
        case Record#base.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 8 - 6
src/elements/element_meta_link.erl → src/elements/meta/element_meta_link.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#meta_link.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#meta_link.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#meta_link.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#meta_link.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#meta_link.dropzone},
     {<<"hidden">>,
        case Record#meta_link.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#meta_link.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#meta_link.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#meta_link.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#meta_link.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec

+ 10 - 13
src/elements/element_style.erl → src/elements/meta/element_style.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#style.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#style.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#style.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#style.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#style.dropzone},
     {<<"hidden">>,
        case Record#style.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#style.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#style.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#style.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#style.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -67,16 +69,11 @@ render_element(Record) ->
     {<<"scoped">>,
        case Record#style.scoped of
          true -> "scoped";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"type">>, Record#style.type} | Record#style.data_fields
   ],
   
-  wf_tags:emit_tag(<<"style">>,
-    nitro:render(
-      case Record#style.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"style">>, nitro:render(Record#style.body), List).
 

+ 9 - 7
src/elements/element_col.erl → src/elements/table/element_col.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#col.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#col.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#col.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#col.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#col.dropzone},
     {<<"hidden">>,
        case Record#col.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#col.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#col.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#col.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#col.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -69,7 +71,7 @@ render_element(Record) ->
   wf_tags:emit_tag(<<"col">>,
     nitro:render(
       case Record#col.body of
-        undefined -> [];
+        [] -> [];
         B -> B
       end), List).
 

+ 9 - 7
src/elements/element_colgroup.erl → src/elements/table/element_colgroup.erl

@@ -8,6 +8,8 @@
 ]).
 
 
+render_element(Record) when Record#colgroup.show_if == false -> [<<>>];
+
 render_element(Record) ->
   List = [
     %% global
@@ -17,7 +19,7 @@ render_element(Record) ->
        case Record#colgroup.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#colgroup.contextmenu},
@@ -26,21 +28,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#colgroup.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#colgroup.dropzone},
     {<<"hidden">>,
        case Record#colgroup.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#colgroup.id},
@@ -49,7 +51,7 @@ render_element(Record) ->
        case Record#colgroup.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#colgroup.style},
@@ -59,7 +61,7 @@ render_element(Record) ->
        case Record#colgroup.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
@@ -69,7 +71,7 @@ render_element(Record) ->
   wf_tags:emit_tag(<<"colgroup">>,
     nitro:render(
       case Record#colgroup.body of
-        undefined -> [];
+        [] -> [];
         B -> B
       end), List).
 

+ 8 - 7
src/elements/element_table.erl → src/elements/table/element_table.erl

@@ -8,19 +8,20 @@
 ]).
 
 
+render_element(Record) when Record#table.show_if == false -> [<<>>];
+
 render_element(Record = #table{}) ->
   Header = case Record#table.header of
-    undefined -> "";
-    H when is_tuple(H) -> H;
+    [] -> "";
+    H when erlang:is_tuple(H) -> H;
     _ -> wf_tags:emit_tag(<<"thead">>, nitro:render(Record#table.header), [])
   end,
   Footer = case Record#table.footer of
-    undefined -> "";
+    [] -> "";
     _ -> wf_tags:emit_tag(<<"tfoot">>, nitro:render(Record#table.footer), [])
   end,
   Bodies = case Record#table.body of
-    #tbody{} = B -> B;
-    undefined -> #tbody{};
+    #tbody{} = B0 -> B0;
     [] -> #tbody{};
     Rows ->
       [case B of
@@ -29,11 +30,11 @@ render_element(Record = #table{}) ->
        end  || B <- Rows]
   end,
   Caption = case Record#table.caption of
-    undefined -> "";
+    [] -> "";
     _ -> wf_tags:emit_tag(<<"caption">>, nitro:render(Record#table.caption), [])
   end,
   Colgroup = case Record#table.colgroup of
-    undefined -> "";
+    [] -> "";
     _ -> wf_tags:emit_tag(<<"colgroup">>, nitro:render(Record#table.colgroup), [])
   end,
   wf_tags:emit_tag( <<"table">>, nitro:render([Caption, Colgroup, Header, Footer, Bodies]), [

+ 3 - 1
src/elements/element_td.erl → src/elements/table/element_td.erl

@@ -1,13 +1,15 @@
 -module(element_td).
 %%-author('Maxim Sokhatsky').
 
--include("nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#td.show_if == false -> [<<>>];
+
 render_element(Record) ->
   wf_tags:emit_tag(<<"td">>, nitro:render(Record#td.body), [
     {<<"id">>, Record#td.id},

+ 3 - 1
src/elements/element_th.erl → src/elements/table/element_th.erl

@@ -1,13 +1,15 @@
 -module(element_th).
 %%-author('Maxim Sokhatsky').
 
--include("nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
 
 -export([
   render_element/1
 ]).
 
 
+render_element(Record) when Record#th.show_if == false -> [<<>>];
+
 render_element(Record) ->
   wf_tags:emit_tag(<<"th">>, nitro:render(Record#th.body), [
     {<<"id">>, Record#th.id},

+ 11 - 5
src/elements/element_tr.erl → src/elements/table/element_tr.erl

@@ -1,27 +1,33 @@
 -module(element_tr).
 %%-author('Maxim Sokhatsky').
 
--include("nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_element/1
 ]).
 
 
-render_element(Record = #tr{postback= Postback}) ->
+render_element(Record) when Record#tr.show_if == false -> [<<>>];
+
+render_element(Record = #tr{postback = Postback}) ->
   Id = case Record#tr.id of
-    undefined -> nitro:temp_id();
+    [] -> nitro:temp_id();
     I -> I
   end,
   Cursor = case Postback of
-    undefined -> "";
+    [] -> "";
     P ->
-      nitro:wire( #event{type = click, postback = P, target = Id, delegate = Record#tr.delegate} ),
+      nitro:wire( #event{type = click, postback = P, target = Id,
+                         delegate = Record#tr.delegate} ),
       "cursor:pointer;"
   end,
   wf_tags:emit_tag(<<"tr">>, nitro:render(Record#tr.cells), [
     {<<"id">>, Id},
     {<<"class">>, Record#tr.class},
+    {<<"onmouseover">>, Record#tr.onmouseover},
+    {<<"onmouseout">>, Record#tr.onmouseout},
     {<<"style">>, [ Record#tr.style, Cursor ]} | Record#tr.data_fields
   ]).
 

+ 7 - 12
src/elements/element_q.erl → src/elements/text/element_q.erl

@@ -17,7 +17,7 @@ render_element(Record) ->
        case Record#q.contenteditable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"contextmenu">>, Record#q.contextmenu},
@@ -26,21 +26,21 @@ render_element(Record) ->
          "ltr" -> "ltr";
          "rtl" -> "rtl";
          "auto" -> "auto";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"draggable">>,
        case Record#q.draggable of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"dropzone">>, Record#q.dropzone},
     {<<"hidden">>,
        case Record#q.hidden of
          "hidden" -> "hidden";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"id">>, Record#q.id},
@@ -49,7 +49,7 @@ render_element(Record) ->
        case Record#q.spellcheck of
          true -> "true";
          false -> "false";
-         _ -> undefined
+         _ -> []
        end},
     
     {<<"style">>, Record#q.style},
@@ -59,17 +59,12 @@ render_element(Record) ->
        case Record#q.contenteditable of
          "yes" -> "yes";
          "no" -> "no";
-         _ -> undefined
+         _ -> []
        end},
     
     %% spec
     {<<"cite">>, Record#q.cite} | Record#q.data_fields
   ],
   
-  wf_tags:emit_tag(<<"q">>,
-    nitro:render(
-      case Record#q.body of
-        undefined -> [];
-        B -> B
-      end), List).
+  wf_tags:emit_tag(<<"q">>, nitro:render(Record#q.body), List).
 

+ 2 - 2
src/nitro.app.src

@@ -1,6 +1,6 @@
 {application, nitro, [
-    {description,  "NITRO HTML5 DSL"},
-    {vsn,          "0.10"},
+    {description,  "NITRO Nitrogen HTML5 Web Framework"},
+    {vsn,          "6.6.1"},
     {applications, [kernel, stdlib]},
     {modules, []},
     {registered,   []},

+ 428 - 62
src/nitro.erl

@@ -3,98 +3,258 @@
 
 -behaviour(application).
 
--include("nitro.hrl").
-
+-include_lib("nitro/include/cx.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([start/2, stop/1, init/1]).
--compile([export_all, nowarn_export_all]).
+-export([atom/1, q/1, q/2, qc/1, qc/2,
+  jse/1, hte/1, config/3, pickle/1, depickle/1, unique_integer/0, temp_id/0,
+  script/0, script/1, actions/0, actions/1, state/1, state/2,
+  redirect/1, cookie_expire/1, cookie/2, cookie/3, cookies/0, cookie/1,
+  f/1, f/2, coalesce/1,
+  to_list/1, to_atom/1, to_binary/1, to_integer/1,
+  join/2, replace/3, indexof/2, indexof/3, append/3, os_env/1, os_env/2,
+  prolongate/0, authenticate/2,
+  render/1, wire/1, hex/1, unhex/1, js_escape/1, js_escape/2,
+  html_encode/1, html_encode/2, url_encode/1, url_decode/1,
+  update/2, insert_top/2, insert_top/3, insert_bottom/2, insert_bottom/3,
+  insert_before/2, insert_after/2, insert_adjacent/3, insert_adjacent/4,
+  clear/1, remove/1, setAttr/3, style/2, style/3, display/2, show/1, hide/1,
+  compact/1, meg/1, num/1 ]).
 
 
-start(_StartType, _StartArgs) ->
-  supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+atom(List) when erlang:is_list(List) ->
+  string:join([ nitro:to_list(L) || L <- List], "_");
+atom(Scalar) -> nitro:to_list(Scalar).
 
-stop(_State) -> ok.
 
+q(Key) -> q(Key, []). %% todo unwrap
+q(Key, Def) -> case get(Key) of undefined -> Def; Val -> Val end.
+
+qc(Key) -> CX = get(context), qc(Key,CX#cx.req).
+qc(Key, Req) -> proplists:get_value(nitro:to_binary(Key),cowboy_req:parse_qs(Req)).
+
+jse(X) -> js_escape(X). %% todo unwrap
+hte(X) when erlang:is_binary(X) -> nitro:to_binary(html_encode(X));
+hte(X) -> html_encode(X).
+config(App, Key, Default) -> application:get_env(App, Key, Default).
+%%js_escape(Value) -> nitro_conv:js_escape(Value). %% todo mv to this module
+
+start(_StartType, _StartArgs) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% todo mv other module
+stop(_State) -> ok.
 init([]) -> {ok, {{one_for_one, 5, 10}, []}}.
 
+
+%% Convert and Utils API
+
 f(S) -> f(S, []).
+
 f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
 
+
+coalesce({_, Y}) -> Y;
+coalesce(false) -> undefined;
 coalesce([]) -> undefined;
 coalesce([H]) -> H;
 coalesce([undefined|T]) -> coalesce(T);
 coalesce([[]|T]) -> coalesce(T);
 coalesce([H|_]) -> H.
 
-js_escape(undefined) -> [];
-js_escape(Value) when is_list(Value) -> binary_to_list(js_escape(iolist_to_binary(Value)));
-js_escape(Value) -> js_escape(Value, <<>>).
-js_escape(<<"\\", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\\">>);
-js_escape(<<"\r", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\r">>);
-js_escape(<<"\n", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\n">>);
-js_escape(<<"\"", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\"">>);
-js_escape(<<"'",Rest/binary>>,Acc) -> js_escape(Rest, <<Acc/binary, "\\'">>);
-js_escape(<<"<script", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "<scr\" + \"ipt">>);
-js_escape(<<"script>", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "scr\" + \"ipt>">>);
-js_escape(<<C, Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, C>>);
-js_escape(<<>>, Acc) -> Acc.
 
--define(IS_STRING(Term), (is_list(Term) andalso Term /= [] andalso is_integer(hd(Term)))).
+%% to_list to_atom to_binary to_integer
+
+-define(IS_STRING(Term),
+  (erlang:is_list(Term) andalso Term /= [] andalso erlang:is_integer(hd(Term)))).
 
 to_list(L) when ?IS_STRING(L) -> L;
-to_list(L) when is_list(L) -> SubLists = [inner_to_list(X) || X <- L], lists:flatten(SubLists);
+to_list(L) when erlang:is_list(L) ->
+  SubLists = [inner_to_list(X) || X <- L],
+  lists:flatten(SubLists);
 to_list(A) -> inner_to_list(A).
-inner_to_list(A) when is_atom(A) -> atom_to_list(A);
-inner_to_list(B) when is_binary(B) -> binary_to_list(B);
-inner_to_list(I) when is_integer(I) -> integer_to_list(I);
-inner_to_list(L) when is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
-inner_to_list(L) when is_list(L) -> L;
-inner_to_list(F) when is_float(F) -> float_to_list(F, [{decimals, 9}, compact]).
-
-to_atom(A) when is_atom(A) -> A;
-to_atom(B) when is_binary(B) -> to_atom(binary_to_list(B));
-to_atom(I) when is_integer(I) -> to_atom(integer_to_list(I));
-to_atom(F) when is_float(F) -> to_atom(float_to_list(F, [{decimals, 9}, compact]));
-to_atom(L) when is_list(L) -> list_to_atom(binary_to_list(list_to_binary(L))).
-
-to_binary(A) when is_atom(A) -> atom_to_binary(A,latin1);
-to_binary(B) when is_binary(B) -> B;
-to_binary(I) when is_integer(I) -> to_binary(integer_to_list(I));
-to_binary(F) when is_float(F) -> float_to_binary(F, [{decimals, 9}, compact]);
-to_binary(L) when is_list(L) ->  iolist_to_binary(L).
+
+inner_to_list(A) when erlang:is_atom(A) -> erlang:atom_to_list(A);
+inner_to_list(B) when erlang:is_binary(B) -> erlang:binary_to_list(B);
+inner_to_list(I) when erlang:is_integer(I) -> erlang:integer_to_list(I);
+inner_to_list(L) when erlang:is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
+inner_to_list(L) when erlang:is_list(L) -> L;
+inner_to_list(F) when erlang:is_float(F) -> erlang:float_to_list(F, [{decimals, 9}, compact]).
+
+to_atom(A) when erlang:is_atom(A) -> A;
+to_atom(B) when erlang:is_binary(B) -> erlang:binary_to_atom(B);
+to_atom(I) when erlang:is_integer(I) -> to_atom(erlang:integer_to_list(I));
+to_atom(F) when erlang:is_float(F) -> to_atom(erlang:float_to_list(F, [{decimals, 9}, compact]));
+to_atom(L) when erlang:is_list(L) -> erlang:list_to_atom(L).
+
+to_binary(A) when erlang:is_atom(A) -> erlang:atom_to_binary(A, latin1);
+to_binary(B) when erlang:is_binary(B) -> B;
+to_binary(I) when erlang:is_integer(I) -> erlang:integer_to_binary(I);
+to_binary(F) when erlang:is_float(F) -> erlang:float_to_binary(F, [{decimals, 9}, compact]);
+to_binary(L) when erlang:is_list(L) ->  erlang:iolist_to_binary(L);
+to_binary(X) when erlang:is_tuple(X) ->  erlang:term_to_binary(X).
+
+to_integer(A) when erlang:is_atom(A) -> to_integer(erlang:atom_to_list(A));
+to_integer(B) when erlang:is_binary(B) -> to_integer(erlang:binary_to_list(B));
+to_integer(I) when erlang:is_integer(I) -> I;
+to_integer([]) -> 0;
+to_integer(L) when erlang:is_list(L) -> erlang:list_to_integer(L);
+to_integer(F) when erlang:is_float(F) -> erlang:round(F).
+
+
+%% JOIN
+%% join(List, Delimiter)
+join([], _) -> [];
+join([Item], _Delim) -> [Item];
+join([Item|Items], Delim) ->
+  [Item, Delim | join(Items, Delim)].
+
+
+%% replace(String, S1, S2)
+replace([], _, _) -> [];
+replace(String, S1, S2) when erlang:is_list(String), erlang:is_list(S1), erlang:is_list(S2) ->
+  Length = erlang:length(S1),
+  case string:substr(String, 1, Length) of
+    S1 ->
+      S2 ++ replace(string:substr(String, Length + 1), S1, S2);
+    _ ->
+      [erlang:hd(String) | replace(erlang:tl(String), S1, S2)]
+  end.
+
+
+indexof(Key, Fields) -> indexof(Key, Fields, 2).
+
+indexof(_, [], _) -> undefined;
+indexof(Key, [Key|_], N) -> N;
+indexof(Key, [_|T], N) -> indexof(Key, T, N + 1).
+
+append(List, Key, Value) ->
+  case Value of
+    undefined -> List;
+    _A -> [{Key, Value}|List]
+  end.
+
+
+os_env(Key) -> os_env(Key, "").
+
+os_env(Key, Default) ->
+  case os:getenv(Key) of
+    false -> Default;
+    V -> V
+  end.
+
 
 -ifndef(PICKLER).
 -define(PICKLER, (application:get_env(n2o, pickler, nitro_pickle))).
 -endif.
 
 pickle(Data) -> ?PICKLER:pickle(Data).
-
 depickle(SerializedData) -> ?PICKLER:depickle(SerializedData).
-depickle(SerializedData, TTLSeconds) -> ?PICKLER:depickle(SerializedData, TTLSeconds).
+
+
+prolongate() ->
+  case application:get_env(n2o, session) of
+    {ok, M} -> M:prolongate();
+    undefined -> false
+  end.
+
+authenticate(I, Auth) ->
+  (application:get_env(n2o, session, n2o_session)):authenticate(I, Auth).
+
 
 render(X) -> wf_render:render(X).
 
+
+% Wire JavaScript nitro:wire
 wire(Actions) -> action_wire:wire(Actions).
 
-unique_integer() ->
-  try
-    erlang:unique_integer()
-  catch _:_ ->
-    {MS, S, US} = erlang:timestamp(),
-    (MS * 1000000 + S) * 1000000 + US
-  end.
 
+unique_integer() -> erlang:unique_integer().
 temp_id() -> "auto" ++ integer_to_list(unique_integer() rem 1000000).
 
-html_encode(L, Fun) when is_function(Fun) -> Fun(L);
-html_encode(L, EncType) when is_atom(L) -> html_encode(nitro:to_list(L), EncType);
-html_encode(L, EncType) when is_integer(L) -> html_encode(integer_to_list(L), EncType);
-html_encode(L, EncType) when is_float(L) -> html_encode(float_to_list(L, [{decimals, 9}, compact]), EncType);
-html_encode(L, false) -> L;
-html_encode(L, true) -> L;
-html_encode(L, whites) -> html_encode_whites(nitro:to_list(lists:flatten([L]))).
-html_encode(<<>>) -> [];
-html_encode([]) -> [];
+
+%% Fast HEX
+
+%digit(0) -> $0;
+%digit(1) -> $1;
+%digit(2) -> $2;
+%digit(3) -> $3;
+%digit(4) -> $4;
+%digit(5) -> $5;
+%digit(6) -> $6;
+%digit(7) -> $7;
+%digit(8) -> $8;
+%digit(9) -> $9;
+%digit(10) -> $a;
+%digit(11) -> $b;
+%digit(12) -> $c;
+%digit(13) -> $d;
+%digit(14) -> $e;
+%digit(15) -> $f.
+digit(X) when X >= 0 andalso X =< 9 -> X + 48;
+digit(X) when X >= 10 andalso X =< 15 -> X + 87.
+
+hex(Bin) ->
+  << << (digit(A1)), (digit(A2)) >> || <<A1:4, A2:4>> <= Bin >>.
+unhex(Hex) ->
+  << << (erlang:list_to_integer([H1, H2], 16)) >> || <<H1, H2>> <= Hex >>.
+
+
+%% JavaScript escape
+
+js_escape(undefined) -> [];
+js_escape(Value) when erlang:is_list(Value) ->
+  erlang:binary_to_list( js_escape( erlang:iolist_to_binary(Value) ) );
+js_escape(Value) -> js_escape(Value, <<>>).
+
+js_escape(<<"\\", Rest/binary>>, Acc) -> %"
+  js_escape(Rest, <<Acc/binary, "\\\\">>); %"
+js_escape(<<"\r", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\r">>);
+js_escape(<<"\n", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\n">>);
+js_escape(<<"\"", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\\"">>);
+js_escape(<<"'", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\'">>);
+js_escape(<<"`", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\`">>);
+js_escape(<<"<script", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "<script">>);
+js_escape(<<"script>", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "script>">>);
+js_escape(<<C, Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, C>>);
+js_escape(<<>>, Acc) -> Acc.
+
+
+%% HTML encode/decode
+
+%% html_encode(B, normal)
+html_encode(L, Fun) when erlang:is_function(Fun) ->
+  Fun(L);
+html_encode(L, EncType) when erlang:is_atom(L) ->
+  html_encode(nitro:to_list(L), EncType);
+html_encode(L, EncType) when erlang:is_integer(L) ->
+  html_encode(erlang:integer_to_list(L), EncType);
+html_encode(L, EncType) when erlang:is_float(L) ->
+  html_encode(erlang:float_to_list(L, [{decimals, 9}, compact]), EncType);
+html_encode(L, false) ->
+  L;
+html_encode(L, true) ->
+  L;
+html_encode(L, whites) ->
+  html_encode_whites(nitro:to_list(lists:flatten([L]))).
+
+html_encode(<<>>) ->
+  [];
+html_encode([]) ->
+  [];
+html_encode(B) when is_binary(B) ->
+  nitro:to_binary(
+    html_encode(erlang:binary_to_list(B))
+  );
+html_encode([$\n |T]) ->
+  "<br>" ++ html_encode(T);
 html_encode([H|T]) ->
   case H of
     $< -> "&lt;" ++ html_encode(T);
@@ -102,13 +262,15 @@ html_encode([H|T]) ->
     $" -> "&quot;" ++ html_encode(T);
     $' -> "&#39;" ++ html_encode(T);
     $& -> "&amp;" ++ html_encode(T);
-    BigNum when is_integer(BigNum) andalso BigNum > 255 ->
-      %% Any integers above 255 are converted to their HTML encode equivilant,
+    $\\ -> "&#92;" ++ html_encode(T);
+    BigNum when erlang:is_integer(BigNum) andalso BigNum > 255 ->
+      %% Any integers above 255 are converted to their HTML encode equivalent
       %% Example: 7534 gets turned into &#7534;
       [$&, $# | nitro:to_list(BigNum)] ++ ";" ++ html_encode(T);
-    Tup when is_tuple(Tup) ->
-      throw({html_encode, encountered_tuple, Tup});
-    _ -> [H|html_encode(T)]
+    Tup when erlang:is_tuple(Tup) ->
+      erlang:throw({html_encode, encountered_tuple, Tup});
+    _ ->
+      [H|html_encode(T)]
   end.
 
 html_encode_whites([]) -> [];
@@ -122,8 +284,212 @@ html_encode_whites([H|T]) ->
     $' -> "&#39;" ++ html_encode_whites(T);
     $& -> "&amp;" ++ html_encode_whites(T);
     $\n -> "<br>" ++ html_encode_whites(T);
+    $\\ -> "&#92;" ++ html_encode_whites(T);
     _ -> [H|html_encode_whites(T)]
   end.
 
+
+%% URL encode/decode
+
+-define(PERCENT, 37).  % $\%
+-define(FULLSTOP, 46). % $\.
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+    (C >= $a andalso C =< $f) orelse
+    (C >= $A andalso C =< $F))).
+-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse
+    (C >= $A andalso C =< $Z) orelse
+    (C >= $0 andalso C =< $9) orelse
+    (C =:= ?FULLSTOP orelse C =:= $- orelse
+     C =:= $~ orelse C =:= $_))).
+
+url_encode(Atom) when erlang:is_atom(Atom) ->
+  url_encode(erlang:atom_to_list(Atom));
+url_encode(Int) when erlang:is_integer(Int) ->
+  url_encode(erlang:integer_to_list(Int));
+url_encode(Bin) when erlang:is_binary(Bin) ->
+  url_encode(erlang:binary_to_list(Bin));
+url_encode(String) ->
+  url_encode(String, []).
+
+url_encode([], Acc) ->
+  lists:reverse(Acc);
+url_encode([C | Rest], Acc) when ?QS_SAFE(C) ->
+  url_encode(Rest, [C | Acc]);
+url_encode([$\s | Rest], Acc) ->
+  url_encode(Rest, [$+ | Acc]);
+url_encode([C | Rest], Acc) ->
+  <<Hi:4, Lo:4>> = <<C>>,
+  url_encode(Rest, [digit(Lo), digit(Hi), ?PERCENT | Acc]).
+
+url_decode(Binary) when erlang:is_binary(Binary) ->
+  url_decode(erlang:binary_to_list(Binary));
+url_decode(String) -> url_decode_h(lists:reverse(String)).
+
+unhexdigit(C) when C >= $0, C =< $9 ->
+  C - $0;
+unhexdigit(C) when C >= $a, C =< $f ->
+  C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F ->
+  C - $A + 10.
+
+url_decode_h(S) -> url_decode_h(S, []).
+
+url_decode_h([], Acc) -> Acc;
+url_decode_h([$+ | Rest], Acc) ->
+  url_decode_h(Rest, [$\s | Acc]);
+url_decode_h([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
+  url_decode_h(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+url_decode_h([C | Rest], Acc) -> url_decode_h(Rest, [C | Acc]).
+
+
 script() -> get(script).
-script(Script) -> put(script,Script).
+script(Script) -> put(script, Script).
+
+
+%% Update DOM nitro:update
+
+update(Target, Elements) ->
+  nitro:wire(#jq{target = Target, property = outerHTML, right = Elements, format = "`~s`"}).
+
+insert_top(Tag, Target, Elements) ->
+  {Render, _Ref, Actions} = render_html(Elements),
+  nitro:wire(nitro:f(
+    "qi('~s').insertBefore("
+    "(function(){var div = qn('~s'); div.innerHTML = `~s`; return div.firstChild; })(),"
+    "qi('~s').firstChild);",
+    [Target, Tag, Render, Target])),
+  nitro:wire(nitro:render(Actions)).
+
+
+insert_bottom(Tag, Target, Elements) ->
+  {Render, _Ref, Actions} = render_html(Elements),
+  nitro:wire(nitro:f(
+    "(function(){ var div = qn('~s'); div.innerHTML = `~s`;"
+    "qi('~s').appendChild(div.firstChild); })();",
+    [Tag, Render, Target])),
+  nitro:wire(nitro:render(Actions)).
+
+
+insert_before(Target, Elements) -> insert_adjacent(beforebegin, Target, Elements).
+insert_after(Target, Elements) -> insert_adjacent(afterend, Target, Elements).
+
+
+insert_adjacent(Command, Target, Elements) ->
+  insert_adjacent(Command, Target, Elements, "qi").
+insert_adjacent(Command, Target, Elements, Q) ->
+  {Render, _Ref, Actions} = render_html(Elements),
+  nitro:wire(nitro:f("~s('~s').insertAdjacentHTML('~s', `~s`);", [Q, Target, Command, Render])),
+  nitro:wire(nitro:render(Actions)).
+
+
+render_html(Elements) ->
+  Pid = erlang:self(),
+  Ref = erlang:make_ref(),
+  erlang:spawn(fun() ->
+    R = nitro:render(Elements),
+    Pid ! {R, Ref, erlang:get(actions)}
+    end),
+  {Render, Ref, Actions} = receive {_, Ref, _} = A -> A end,
+  {Render, Ref, Actions}.
+
+
+actions() -> get(actions).
+actions(Ac) -> put(actions, Ac).
+
+
+insert_top(Target, Elements) when erlang:element(1, Elements) == tr ->
+  insert_top(tbody, Target, Elements);
+insert_top(Target, Elements) ->
+  insert_top('div', Target, Elements).
+
+
+insert_bottom(Target, Elements) when erlang:element(1, Elements) == tr ->
+  insert_bottom(tbody, Target, Elements);
+insert_bottom(Target, Elements) ->
+  insert_bottom('div', Target, Elements).
+
+
+clear(Target) ->
+  nitro:wire("var x = qi('" ++ nitro:to_list(Target) ++ "');"
+             "while(x && x.firstChild) x.removeChild(x.firstChild);").
+
+remove(Target) ->
+  nitro:wire("var x = qi('" ++ nitro:to_list(Target) ++ "');"
+             "x && x.parentNode.removeChild(x);").
+
+
+%% Wire JavaScript nitro:wire
+
+state(Key) -> erlang:get(Key).
+state(Key,Value) -> erlang:put(Key, Value).
+
+
+%% Redirect and purge connection nitro:redirect
+
+redirect(Url) -> nitro:wire(#jq{target = 'window', property = location, args = simple, right = Url}).
+%header(K,V) -> nitro:context((?CTX)#cx{req = cowboy_req:set_resp_header(K, V, ?CTX#cx.req)}).
+
+
+setAttr(Element, Attr, Value) ->
+  nitro:wire("{ var x = qi('" ++ nitro:to_list(Element) ++ "');"
+             "if(x) x.setAttribute('" ++ nitro:to_list(Attr) ++ "', '" ++ nitro:to_list(Value) ++ "'); }").
+
+
+style(Element, Style) ->
+  setAttr(Element, "style", Style).
+
+style(Element, Style, Value) -> 
+  nitro:wire("{ var x = qi('" ++ nitro:to_list(Element) ++ "');"
+    "if(x) x.style." ++ nitro:to_list(Style) ++ " = '" ++ nitro:to_list(Value) ++ "'; }").
+
+
+display(Element, Status) -> style(Element, "display", Status).
+
+
+show(Element) -> display(Element, block).
+hide(Element) -> display(Element, none).
+
+
+compact([]) -> "[]";
+compact("\n") -> "[]";
+compact([X|_] = Y) when erlang:is_tuple(X) ->
+  [ compact(F) || F <- Y ];
+compact(Bin) when erlang:is_binary(Bin) ->
+  unicode:characters_to_binary(Bin);
+compact(Tuple) when erlang:is_tuple(Tuple) ->
+  Min = erlang:min(9, erlang:size(Tuple)),
+  Fields = lists:zip(lists:seq(1, Min),
+    lists:sublist(erlang:tuple_to_list(Tuple), 1, Min)),
+  "{" ++ string:join([ io_lib:format("~s", [compact(F)]) || {_, F} <- Fields ], ",") ++ "}";
+compact(T) ->
+  nitro:js_escape(nitro:to_list(T)).
+
+
+meg(X) -> erlang:integer_to_list(X div 1000000) ++ "M".
+
+num(S) -> case lists:reverse(S) of
+    [$K|K] -> erlang:list_to_integer(lists:reverse(K)) * 1000;
+    [$M|M] -> erlang:list_to_integer(lists:reverse(M)) * 1000 * 1000;
+    [$G|G] -> erlang:list_to_integer(lists:reverse(G)) * 1000 * 1000 * 1000;
+    [$T|T] -> erlang:list_to_integer(lists:reverse(T)) * 1000 * 1000 * 1000 * 1000
+  end.
+
+
+cookie_expire(SecondsToLive) ->
+  Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
+  DateTime = calendar:gregorian_seconds_to_datetime(Seconds + SecondsToLive),
+  cow_date:rfc2109(DateTime).
+
+cookie(Id, Value) -> cookie(Id, Value, 2147483647). %% expire never
+cookie(Id, Value, Expire) ->
+  Format = "document.cookie='~s=~s; path=/; expires=~s';",
+  nitro:wire(nitro:f(Format, [nitro:to_list(Id), nitro:to_list(Value), cookie_expire(Expire)])).
+
+cookies() -> cowboy_req:parse_cookies((erlang:get(context))#cx.req).
+
+cookie(Key) ->
+  case lists:keyfind(Key, 1, cowboy_req:parse_cookies((erlang:get(context))#cx.req)) of
+    false -> undefined;
+    {_, Value} -> Value
+  end.
+

+ 279 - 0
src/nitro_conv.erl

@@ -0,0 +1,279 @@
+-module(nitro_conv).
+%%-author('Maxim Sokhatsky').
+
+%% N2O Formatter: JSON, BERT
+
+-compile(export_all).
+-include_lib("nitro/include/nitro.hrl").
+
+% WF to_list to_atom to_binary to_integer
+
+-define(IS_STRING(Term),
+  (erlang:is_list(Term) andalso Term /= [] andalso erlang:is_integer(hd(Term)))).
+
+-ifndef(N2O_JSON).
+-define(N2O_JSON, (application:get_env(n2o,json,jsone))).
+-endif.
+
+
+to_list(L) when ?IS_STRING(L) -> L;
+to_list(L) when erlang:is_list(L) ->
+  SubLists = [inner_to_list(X) || X <- L],
+  lists:flatten(SubLists);
+to_list(A) -> inner_to_list(A).
+
+inner_to_list(A) when erlang:is_atom(A) -> erlang:atom_to_list(A);
+inner_to_list(B) when erlang:is_binary(B) -> erlang:binary_to_list(B);
+inner_to_list(I) when erlang:is_integer(I) -> erlang:integer_to_list(I);
+inner_to_list(L) when erlang:is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
+inner_to_list(L) when erlang:is_list(L) -> L;
+inner_to_list(F) when erlang:is_float(F) -> erlang:float_to_list(F, [{decimals, 9}, compact]).
+
+to_atom(A) when erlang:is_atom(A) -> A;
+to_atom(B) when erlang:is_binary(B) -> erlang:binary_to_atom(B);
+to_atom(I) when erlang:is_integer(I) -> to_atom(erlang:integer_to_list(I));
+to_atom(F) when erlang:is_float(F) -> to_atom(erlang:float_to_list(F, [{decimals, 9}, compact]));
+to_atom(L) when erlang:is_list(L) -> erlang:list_to_atom(L).
+
+to_binary(A) when erlang:is_atom(A) -> erlang:atom_to_binary(A, latin1);
+to_binary(B) when erlang:is_binary(B) -> B;
+to_binary(I) when erlang:is_integer(I) -> erlang:integer_to_binary(I);
+to_binary(F) when erlang:is_float(F) -> erlang:float_to_binary(F, [{decimals, 9}, compact]);
+to_binary(L) when erlang:is_list(L) ->  erlang:iolist_to_binary(L);
+to_binary(X) when erlang:is_tuple(X) ->  erlang:term_to_binary(X).
+
+to_integer(A) when erlang:is_atom(A) -> to_integer(erlang:atom_to_list(A));
+to_integer(B) when erlang:is_binary(B) -> to_integer(erlang:binary_to_list(B));
+to_integer(I) when erlang:is_integer(I) -> I;
+to_integer([]) -> 0;
+to_integer(L) when erlang:is_list(L) -> erlang:list_to_integer(L);
+to_integer(F) when erlang:is_float(F) -> erlang:round(F).
+
+
+%% HTML encode/decode
+
+%% html_encode(B, normal)
+html_encode(L, Fun) when erlang:is_function(Fun) ->
+  Fun(L);
+html_encode(L, EncType) when erlang:is_atom(L) ->
+  html_encode(nitro:to_list(L), EncType);
+html_encode(L, EncType) when erlang:is_integer(L) ->
+  html_encode(erlang:integer_to_list(L), EncType);
+html_encode(L, EncType) when erlang:is_float(L) ->
+  html_encode(erlang:float_to_list(L, [{decimals, 9}, compact]), EncType);
+html_encode(L, false) ->
+  L;
+html_encode(L, true) ->
+  L;
+html_encode(L, whites) ->
+  html_encode_whites(nitro:to_list(lists:flatten([L]))).
+
+html_encode(<<>>) ->
+  [];
+html_encode([]) ->
+  [];
+html_encode(B) when is_binary(B) ->
+  nitro:to_binary(
+    html_encode(erlang:binary_to_list(B))
+  );
+html_encode([$\n |T]) ->
+  "<br>" ++ html_encode(T);
+html_encode([H|T]) ->
+  case H of
+    $< -> "&lt;" ++ html_encode(T);
+    $> -> "&gt;" ++ html_encode(T);
+    $" -> "&quot;" ++ html_encode(T);
+    $' -> "&#39;" ++ html_encode(T);
+    $& -> "&amp;" ++ html_encode(T);
+    $\\ -> "&#92;" ++ html_encode(T);
+    BigNum when erlang:is_integer(BigNum) andalso BigNum > 255 ->
+      %% Any integers above 255 are converted to their HTML encode equivalent
+      %% Example: 7534 gets turned into &#7534;
+      [$&, $# | nitro:to_list(BigNum)] ++ ";" ++ html_encode(T);
+    Tup when erlang:is_tuple(Tup) ->
+      erlang:throw({html_encode, encountered_tuple, Tup});
+    _ ->
+      [H|html_encode(T)]
+  end.
+
+html_encode_whites([]) -> [];
+html_encode_whites([H|T]) ->
+  case H of
+    $\s -> "&nbsp;" ++ html_encode_whites(T);
+    $\t -> "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" ++ html_encode_whites(T);
+    $< -> "&lt;" ++ html_encode_whites(T);
+    $> -> "&gt;" ++ html_encode_whites(T);
+    $" -> "&quot;" ++ html_encode_whites(T);
+    $' -> "&#39;" ++ html_encode_whites(T);
+    $& -> "&amp;" ++ html_encode_whites(T);
+    $\n -> "<br>" ++ html_encode_whites(T);
+    $\\ -> "&#92;" ++ html_encode_whites(T);
+    _ -> [H|html_encode_whites(T)]
+  end.
+
+
+%% URL encode/decode
+
+-define(PERCENT, 37).  % $\%
+-define(FULLSTOP, 46). % $\.
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+    (C >= $a andalso C =< $f) orelse
+    (C >= $A andalso C =< $F))).
+-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse
+    (C >= $A andalso C =< $Z) orelse
+    (C >= $0 andalso C =< $9) orelse
+    (C =:= ?FULLSTOP orelse C =:= $- orelse
+     C =:= $~ orelse C =:= $_))).
+
+url_encode(Atom) when erlang:is_atom(Atom) ->
+  url_encode(erlang:atom_to_list(Atom));
+url_encode(Int) when erlang:is_integer(Int) ->
+  url_encode(erlang:integer_to_list(Int));
+url_encode(Bin) when erlang:is_binary(Bin) ->
+  url_encode(erlang:binary_to_list(Bin));
+url_encode(String) ->
+  url_encode(String, []).
+
+url_encode([], Acc) ->
+  lists:reverse(Acc);
+url_encode([C | Rest], Acc) when ?QS_SAFE(C) ->
+  url_encode(Rest, [C | Acc]);
+url_encode([$\s | Rest], Acc) ->
+  url_encode(Rest, [$+ | Acc]);
+url_encode([C | Rest], Acc) ->
+  <<Hi:4, Lo:4>> = <<C>>,
+  url_encode(Rest, [digit(Lo), digit(Hi), ?PERCENT | Acc]).
+
+url_decode(Binary) when erlang:is_binary(Binary) ->
+  url_decode(erlang:binary_to_list(Binary));
+url_decode(String) -> url_decode_h(lists:reverse(String)).
+
+unhexdigit(C) when C >= $0, C =< $9 ->
+  C - $0;
+unhexdigit(C) when C >= $a, C =< $f ->
+  C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F ->
+  C - $A + 10.
+
+url_decode_h(S) -> url_decode_h(S, []).
+
+url_decode_h([], Acc) -> Acc;
+url_decode_h([$+ | Rest], Acc) ->
+  url_decode_h(Rest, [$\s | Acc]);
+url_decode_h([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
+  url_decode_h(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+url_decode_h([C | Rest], Acc) -> url_decode_h(Rest, [C | Acc]).
+
+
+%% JavaScript escape
+
+js_escape(undefined) -> [];
+js_escape(Value) when erlang:is_list(Value) ->
+  erlang:binary_to_list( js_escape( erlang:iolist_to_binary(Value) ) );
+js_escape(Value) -> js_escape(Value, <<>>).
+
+js_escape(<<"\\", Rest/binary>>, Acc) -> %"
+  js_escape(Rest, <<Acc/binary, "\\\\">>); %"
+js_escape(<<"\r", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\r">>);
+js_escape(<<"\n", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\n">>);
+js_escape(<<"\"", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\\"">>);
+js_escape(<<"'", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\'">>);
+js_escape(<<"`", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "\\`">>);
+js_escape(<<"<script", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "<script">>);
+js_escape(<<"script>", Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, "script>">>);
+js_escape(<<C, Rest/binary>>, Acc) ->
+  js_escape(Rest, <<Acc/binary, C>>);
+js_escape(<<>>, Acc) -> Acc.
+
+
+%% JOIN
+
+%% join(List, Delimiter)
+join([], _) -> [];
+join([Item], _Delim) -> [Item];
+join([Item|Items], Delim) ->
+  [Item, Delim | join(Items, Delim)].
+
+
+%% Fast HEX
+
+%digit(0) -> $0;
+%digit(1) -> $1;
+%digit(2) -> $2;
+%digit(3) -> $3;
+%digit(4) -> $4;
+%digit(5) -> $5;
+%digit(6) -> $6;
+%digit(7) -> $7;
+%digit(8) -> $8;
+%digit(9) -> $9;
+%digit(10) -> $a;
+%digit(11) -> $b;
+%digit(12) -> $c;
+%digit(13) -> $d;
+%digit(14) -> $e;
+%digit(15) -> $f.
+digit(X) when X >= 0 andalso X =< 9 -> X + 48;
+digit(X) when X >= 10 andalso X =< 15 -> X + 87.
+
+hex(Bin) ->
+  << << (digit(A1)), (digit(A2)) >> || <<A1:4, A2:4>> <= Bin >>.
+unhex(Hex) ->
+  << << (erlang:list_to_integer([H1, H2], 16)) >> || <<H1, H2>> <= Hex >>.
+
+
+f(S) -> f(S, []).
+
+f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
+
+
+replace([], _, _) -> [];
+replace(String, S1, S2) when erlang:is_list(String), erlang:is_list(S1), erlang:is_list(S2) ->
+  Length = erlang:length(S1),
+  case string:substr(String, 1, Length) of
+    S1 -> S2 ++ replace(string:substr(String, Length + 1), S1, S2);
+    _ -> [hd(String)|replace(tl(String), S1, S2)]
+  end.
+
+
+coalesce({_, Y}) -> Y;
+coalesce(false) -> undefined;
+coalesce([]) -> undefined;
+coalesce([H]) -> H;
+coalesce([undefined|T]) -> coalesce(T);
+coalesce([[]|T]) -> coalesce(T);
+coalesce([H|_]) -> H.
+
+
+indexof(Key, Fields) -> indexof(Key, Fields, 2).
+
+indexof(_, [], _) -> undefined;
+indexof(Key, [Key|_], N) -> N;
+indexof(Key, [_|T], N) -> indexof(Key, T, N + 1).
+
+
+config(App, Key, Default) -> application:get_env(App, Key, Default).
+
+os_env(Key) -> os_env(Key, "").
+
+os_env(Key, Default) ->
+  case os:getenv(Key) of
+    false -> Default;
+    V -> V
+  end.
+
+%% base64 encode/decode
+pickle(Data) -> base64:encode(erlang:term_to_binary({Data, os:timestamp()}, [compressed])).
+depickle(PickledData) ->
+  try {Data, _PickleTime} = erlang:binary_to_term(base64:decode(nitro:to_binary(PickledData))),
+    Data
+  catch _:_ -> undefined
+  end.
+

+ 171 - 0
src/nitro_n2o.erl

@@ -0,0 +1,171 @@
+-module(nitro_n2o).
+%% N2O Nitrogen Web Framework Protocol
+
+-include_lib("nitro/include/n2o.hrl").
+
+-export([
+  info/3,
+  render_actions/1,
+  io/1,
+  io/2,
+  event/1
+]).
+
+
+% Nitrogen pickle handler
+
+info({text, <<"N2O,", Auth/binary>>}, Req, State) ->
+  info(#init{token = Auth}, Req, State);
+
+info(#init{token = Auth}, Req, State) ->
+  {'Token', Token} = nitro:authenticate([], Auth),
+  Sid = case nitro:depickle(Token) of
+    {{S, _}, _} -> S;
+    X -> X
+  end,
+  New = State#cx{session = Sid, token = Auth},
+  erlang:put(context, New),
+  {reply, {bert,
+     case io(init, State) of
+       {io, _, {stack, _}} = Io -> Io;
+       {io, Code, _} -> {io, Code, {'Token', Token}}
+     end},
+   Req, New};
+
+info(#client{data = Message}, Req, State) ->
+  nitro:actions([]),
+  {reply, {bert, io(#client{data = Message}, State)}, Req, State};
+
+info(#pickle{} = Event, Req, State) ->
+  nitro:actions([]),
+  {reply, {bert, html_events(Event, State)}, Req, State};
+
+info(#flush{data = Actions}, Req, State) ->
+  nitro:actions(Actions),
+  {reply, {bert, io(<<>>)}, Req, State};
+
+info(#direct{data = Message}, Req, State) ->
+  nitro:actions([]),
+  {reply, {bert,
+     case io(Message, State) of
+       {io, _, {stack, _}} = Io -> Io;
+       {io, Code, Res} -> {io, Code, {direct, Res}}
+     end},
+   Req, State};
+
+info(Message, Req, State) -> {unknown, Message, Req, State}.
+
+
+%% double render: actions could generate actions
+
+render_actions(Actions) ->
+  nitro:actions([]),
+  First  = nitro:render(Actions),
+  Second = nitro:render(nitro:actions()),
+  nitro:actions([]),
+  nitro:to_binary([First,Second]).
+
+
+%% n2o events
+
+html_events(#pickle{source = Source, pickled = Pickled, args = Linked}, State = #cx{token = Token}) ->
+  Ev  = nitro:depickle(Pickled),
+  L   = nitro:prolongate(),
+  Res = case Ev of
+    #ev{} when L =:= false ->
+      render_ev(Ev, Source, Linked, State),
+      <<>>;
+    #ev{} ->
+      render_ev(Ev, Source, Linked, State),
+      nitro:authenticate([], Token);
+    _CustomEnvelop ->
+      %?LOG_ERROR("EV expected: ~p~n",[CustomEnvelop]),
+      {error, "EV expected"}
+  end,
+  io(Res).
+
+
+%% calling user code in exception-safe manner
+
+-ifdef(OTP_RELEASE).
+
+render_ev(#ev{module = M, name = F, msg = P, trigger = T}, _Source, Linked, State) ->
+  try case F of
+    api_event ->
+      M:F(P, Linked, State);
+    event ->
+      [erlang:put(K, V) || {K, V} <- Linked],
+      M:F(P);
+    _ ->
+      M:F(P, T, State)
+  end
+  catch E:R:S ->
+    ?LOG_EXCEPTION(E, R, S),
+    {stack, S}
+  end.
+
+
+io(Event, #cx{module = Module}) ->
+  try
+    X = Module:event(Event),
+    {io, render_actions(nitro:actions()), X}
+  catch E:R:S ->
+    ?LOG_EXCEPTION(E, R, S),
+    {io, [], {stack, S}}
+  end.
+
+
+io(Data) ->
+  try {io, render_actions(nitro:actions()), Data}
+  catch E:R:S ->
+    ?LOG_EXCEPTION(E, R, S),
+    {io, [], {stack, S}}
+  end.
+
+-else.
+
+
+render_ev(#ev{module = M, name = F, msg = P, trigger = T}, _Source, Linked, State) ->
+  try
+    case F of
+      api_event ->
+        M:F(P, Linked, State);
+      event ->
+        [erlang:put(K, V) || {K, V} <- Linked],
+        M:F(P);
+      _ ->
+        M:F(P, T, State)
+    end
+  catch E:R ->
+    S = erlang:get_stacktrace(),
+    ?LOG_EXCEPTION(E, R, S),
+    {stack, S}
+  end.
+
+
+io(Event, #cx{module = Module}) ->
+  try
+    X = Module:event(Event),
+    {io, render_actions(nitro:actions()), X}
+  catch E:R ->
+    S = erlang:get_stacktrace(),
+    ?LOG_EXCEPTION(E, R, S),
+    {io, <<>>, {stack, S}}
+  end.
+
+
+io(Data) ->
+  try {io, render_actions(nitro:actions()), Data}
+  catch E:R ->
+    S = erlang:get_stacktrace(),
+    ?LOG_EXCEPTION(E, R, S),
+    {io, <<>>, {stack, S}}
+  end.
+
+-endif.
+
+
+%% event Nitrogen Web Framework protocol
+
+event(_) -> [].
+

+ 2 - 2
src/nitro_pickle.erl

@@ -7,11 +7,11 @@
 ]).
 
 
-pickle(Data) -> base64:encode(term_to_binary({Data, os:timestamp()}, [compressed])).
+pickle(Data) -> base64:encode(erlang:term_to_binary({Data, os:timestamp()}, [compressed])).
 
 depickle(PickledData) ->
   try
-    {Data, _PickleTime} = binary_to_term( base64:decode( nitro:to_binary(PickledData) ) ),
+    {Data, _PickleTime} = erlang:binary_to_term( base64:decode( nitro:to_binary(PickledData) ) ),
     Data
   catch _:_ ->
     undefined

+ 105 - 0
src/nitro_static.erl

@@ -0,0 +1,105 @@
+-module(nitro_static).
+%%-author('Maxim Sokhatsky').
+
+%% NITRO Static bridge for MAD containers
+
+-include_lib("kernel/include/file.hrl").
+
+-export([
+  init/3,
+  rest_init/2,
+  malformed_request/2,
+  forbidden/2,
+  fix/1,
+  content_types_provided/2,
+  resource_exists/2,
+  generate_etag/2,
+  generate_default_etag/2,
+  last_modified/2,
+  get_file/2
+]).
+
+
+init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
+
+
+rest_init(Req, {dir, Path, Extra}) when erlang:is_binary(Path) ->
+  rest_init(Req, {dir, erlang:binary_to_list(Path), Extra});
+rest_init(Req, {dir, Path, Extra}) ->
+  {PathInfo, Req2} = cowboy_req:path_info(Req),
+  Info = {ok, #file_info{type = regular, size = 0}},
+  FileName = filename:join([Path|PathInfo]),
+  {ok, Req2, {FileName, Info, Extra}}.
+
+malformed_request(Req, State) -> {State =:= error, Req, State}.
+
+forbidden(Req, State = {_, {ok, #file_info{type = directory}}, _}) -> {true, Req, State};
+forbidden(Req, State = {_, {error, eacces}, _}) -> {true, Req, State};
+forbidden(Req, State = {_, {ok, #file_info{access = Access}}, _}) when Access =:= write; Access =:= none -> {true, Req, State};
+forbidden(Req, State) -> {false, Req, State}.
+
+
+
+fixpath(Path) ->
+  {Module,Fun} = application:get_env(n2o, fixpath, {nitro_static, fix}),
+  Module:Fun(Path).
+
+
+fix(A) -> A.
+
+
+content_types_provided(Req, State = {Path, _, Extra}) ->
+  case lists:keyfind(mimetypes, 1, Extra) of
+    false ->
+      {[{cow_mimetypes:web(Path), get_file}], Req, State};
+    {mimetypes, Module, Function} ->
+      {[{Module:Function(fixpath(Path)), get_file}], Req, State};
+    {mimetypes, Type} ->
+      {[{Type, get_file}], Req, State}
+  end.
+
+
+resource_exists(Req, State = {_, {ok, #file_info{type = regular}}, _}) -> {true, Req, State};
+resource_exists(Req, State) -> {false, Req, State}.
+
+
+generate_etag(Req, State = {Path, {ok, #file_info{size = Size, mtime = Mtime}}, Extra}) ->
+  case lists:keyfind(etag, 1, Extra) of
+    false ->
+      {generate_default_etag(Size, Mtime), Req, State};
+    {etag, Module, Function} ->
+      {Module:Function(Path, Size, Mtime), Req, State};
+    {etag, false} ->
+      {undefined, Req, State}
+  end.
+
+
+generate_default_etag(Size, Mtime) ->
+  {strong, erlang:list_to_binary( erlang:integer_to_list(erlang:phash2({Size, Mtime}, 16#ffffffff)) )}.
+
+
+last_modified(Req, State = {_, {ok, #file_info{mtime = Modified}}, _}) -> {Modified, Req, State}.
+
+
+get_file(Req, State = {P, {ok, #file_info{size = _Size}}, _}) ->
+  Path = fixpath(P),
+  StringPath = nitro:to_list(unicode:characters_to_binary(Path,utf8,utf8)),
+  [_Type,Name|RestPath] = filename:split(StringPath),
+  FileName = filename:absname(StringPath),
+  Raw = case file:read_file(FileName) of
+    {ok, Bin} -> Bin;
+    {error, _} ->
+      case mad_repl:load_file(StringPath) of
+        {ok, ETSFile} -> ETSFile;
+        {error, _} ->
+          case file:read_file(filename:join([ code:lib_dir(erlang:list_to_atom(Name)) |RestPath])) of
+            {ok, ReleaseFile} -> ReleaseFile;
+            {error, _} -> <<>>
+          end
+      end
+  end,
+  Sendfile = fun(Socket, Transport) ->
+    Transport:send(Socket, Raw)
+  end,
+  {{stream, erlang:size(Raw), Sendfile}, Req, State}.
+

+ 29 - 10
src/render/wf_event.erl

@@ -2,7 +2,8 @@
 %%-author('Maxim Sokhatsky').
 %%-author('Andrey Martemyanov').
 
--include_lib ("nitro/include/nitro.hrl").
+-include_lib("nitro/include/cx.hrl").
+-include_lib("nitro/include/nitro.hrl").
 
 -export([
   target/1,
@@ -15,17 +16,31 @@
 
 
 -record(ev, { module, msg, trigger, name }).
--record(cx, { handlers, actions, req, module, lang, path, session, formatter, params, form, state = [] }).
--define(CTX, (get(context))).
 
 
-target({qs, S}) -> [ "qs('", nitro:js_escape( nitro:to_list(S) ),  "')"];
-target(Id)      -> [ "qi('", nitro:js_escape( nitro:to_list(Id) ), "')"].
+target({ps, {qa, _Id} = T, Ps}) ->
+  ["var t=", target(T), ";t.map(ts => '", nitro:js_escape(Ps), "'.split('.')"
+   ".reduce((a,p)=>(a&&a[p]?a[p]:null),ts)).filter(o=>o)"];
+target({ps, Id, Ps}) ->
+  ["var ts=", target(Id), ",ps = '", nitro:js_escape(Ps), "'.split('.')"
+   ".reduce((a,p)=>(a&&a[p]?a[p]:null),ts); ps&&ps"];
+target({qs, S}) ->
+  ["qs('", nitro:js_escape(nitro:to_list(S)), "')"];
+target({qa, S}) ->
+  ["Array.from(qa('", nitro:js_escape(nitro:to_list(S)), "'))"];
+target({g, T}) ->
+  nitro:js_escape(nitro:to_list(T));
+target(Id) ->
+  ["qi('", nitro:js_escape( nitro:to_list(Id) ), "')"].
+
+
+new(bin, Data) ->
+  <<"ws.send(enc(tuple(atom('bin'),bin('", (nitro:pickle(Data))/binary, "'))));">>.
 
 new(P, E, D, N, Data, Source) -> new(P, E, D, N, Data, Source, <<>>).
 
-new(bin, Data) -> <<"ws.send(enc(tuple(atom('bin'),bin('", (nitro:pickle(Data))/binary, "'))));">>.
-new(undefined, _, _, _, _, _, _) -> <<>>;
+new([], _, _, _, _, _, _) -> <<>>;
+new(undefined, _, _, _, _, _, _) -> <<>>; %% todo check maybe rm?
 new(Postback, Element, Delegate, Name, Data, Source, Validation) ->
   Module = nitro:coalesce([Delegate, ?CTX#cx.module]),
   Join = fun([])    -> [];
@@ -33,8 +48,12 @@ new(Postback, Element, Delegate, Name, Data, Source, Validation) ->
             ([H|T]) -> [[$'|H] ++ [$']] ++ [ [$,,$'|E] ++ [$'] || E <- T ]
   end,
   Event = #ev{name = Name, module = Module, msg = Postback, trigger = Element},
-  erlang:list_to_binary(["{if(validateSources([", Join([ nitro:to_list(S) || S <- Source, S =/= [] ]),
-                         "])){", nitro:to_binary(Validation), " ws.send(enc(tuple(atom('", nitro:to_binary(application:get_env(n2o, event, pickle)), "'),bin('",
-                         Element, "'),bin('", nitro:pickle(Event), "'),", Data, ")));}else console.log('Validation Error');}"
+  erlang:list_to_binary(["{if(validateSources([",
+    Join([ nitro:to_list(S) || S <- Source, S =/= [] ]),
+    "])){", nitro:to_binary(Validation),
+    " ws.send(enc(tuple(atom('",
+    nitro:to_binary(application:get_env(n2o, event, pickle)),
+    "'),bin('", Element, "'),bin('", nitro:pickle(Event), "'),", Data, ")));"
+    "}else console.log('Validation Error');}"
   ]).
 

+ 7 - 4
src/render/wf_render.erl

@@ -9,12 +9,15 @@
 ]).
 
 
-render_item(E) when element(2, E) == element -> wf_render_elements:render_element(E);
-render_item(E) when element(2, E) == action  -> wf_render_actions:render_action(E);
+render_item([]) -> <<>>;
+render_item(undefined) -> <<>>;
+render_item(E) when erlang:element(2, E) =:= element -> wf_render_elements:render_element(E);
+render_item(E) when erlang:element(2, E) =:= action  -> wf_render_actions:render_action(E);
 render_item(E) -> E.
 
+render([]) -> <<>>;
+render(undefined) -> <<>>;
 render(<<E/binary>>) -> E;
-render(undefined) -> [];
-render(Elements) when is_list(Elements) -> [ render_item(E) || E <- lists:flatten(Elements) ];
+render(Elements) when erlang:is_list(Elements) -> [ render_item(E) || E <- Elements, E /= undefined ];
 render(Elements) -> render_item(Elements).
 

+ 6 - 5
src/render/wf_render_actions.erl

@@ -1,7 +1,8 @@
 -module(wf_render_actions).
 %%-author('Andrew Zadorozhny').
 
--include_lib ("nitro/include/nitro.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("nitro/include/event.hrl").
 
 -export([
   render_action/1
@@ -9,12 +10,12 @@
 
 
 render_action(Action) ->
-  Module = element(#action.module,Action),
+  Module = erlang:element(#action.module, Action),
   Res = Module:render_action(Action),
   case Res of
-    Res when is_tuple(Res) -> render_action(Res);
-    Bin when is_binary(Bin) -> Bin;
-    Str when is_list(Str) -> Str;
+    Res when erlang:is_tuple(Res) -> render_action(Res);
+    Bin when erlang:is_binary(Bin) -> Bin;
+    Str when erlang:is_list(Str) -> Str;
     _ -> []
   end.
 

+ 74 - 38
src/render/wf_render_elements.erl

@@ -2,40 +2,70 @@
 %%-author('Maxim Sokhatsky').
 
 -include_lib ("nitro/include/nitro.hrl").
+-include_lib ("nitro/include/comboLookupEdit.hrl").
+
 
 -export([
   render_element/1
 ]).
 
 
-render_element(E) when is_list(E) -> E;
-render_element(Element) when is_tuple(Element) ->
-  Id = case element(#element.id, Element) of
-    undefined -> undefined;
-    L when is_list(L) -> L;
+render_element(E) when erlang:is_list(E) -> E;
+render_element(Element) when erlang:is_tuple(Element) ->
+  Id = case erlang:element(#element.id, Element) of
+    [] -> [];
+    undefined -> undefined;  %% todo check maybe rm?
+    L when erlang:is_list(L) -> L;
     Other -> nitro:to_list(Other)
   end,
-  case element(#element.actions, Element) of
-    undefined -> skip;
+  case erlang:element(#element.actions, Element) of
+    [] -> skip;
     Actions -> nitro:wire(Actions)
   end,
-  Tag = case element(#element.html_tag, Element) of
-    undefined -> nitro:to_binary(element(1, Element));
+  Tag = case erlang:element(#element.html_tag, Element) of
+    [] -> nitro:to_binary(erlang:element(1, Element));
     T -> T
   end,
-  case element(#element.validation, Element) of
-    [] -> skip;
-    Code ->
-      nitro:wire(
-        nitro:f("{var name='~s';qi(name)"
-                ".addEventListener('validation',"
-                "function(e){if(!(~s)) e.preventDefault();});"
-                "qi(name).validation = true;}", [Id, Code]))
+  
+  case erlang:element(1, Element) of
+    comboLookupEdit ->
+      lists:map(fun(El) ->
+        Input = case erlang:element(#element.body, El) of
+          Body when not (Body == []) and erlang:is_list(Body) ->
+            case erlang:element(#element.body, hd(lists:flatten(tl(Body)))) of
+              X when erlang:is_tuple(X)-> X;
+              _ -> El
+            end;
+          _ -> El
+        end,
+        
+        InputId = erlang:element(#element.id, Input),
+        
+        case erlang:element(#element.validation, Input) of
+          [] -> skip;
+          InputCode ->
+            nitro:wire(nitro:f("{var name='~s'; qi(name) && qi(name).addEventListener('validation',"
+                               "function(e) { if (!(~s)) e.preventDefault(); });"
+                               "if(qi(name)) qi(name).validation = true;}", [InputId, InputCode]))
+        end
+      
+      end, lists:flatten(tl(erlang:element(#element.body, erlang:element(#comboLookupEdit.form, Element)))));
+    
+    _ ->
+      case erlang:element(#element.validation, Element) of
+        [] -> skip;
+        Code ->
+          nitro:wire(nitro:f("{var name='~s'; qi(name) && qi(name).addEventListener('validation',"
+                             "function(e) { if (!(~s)) e.preventDefault(); });"
+                             "if(qi(name)) qi(name).validation = true;}", [Id, Code]))
+      end
   end,
+  
   case element(#element.module, Element) of
-    undefined -> default_render(Tag, Element);
+    [] -> default_render(Tag, Element);
+    undefined -> default_render(Tag, Element); %% todo check maybe rm?
     Module ->
-      nitro:to_binary( Module:render_element( setelement(#element.id, Element, Id) ) )
+      nitro:to_binary( Module:render_element( erlang:setelement(#element.id, Element, Id) ) )
   end;
 
 render_element(Element) ->
@@ -43,24 +73,30 @@ render_element(Element) ->
 
 
 default_render(Tag, Record) ->
-  wf_tags:emit_tag(Tag, nitro:render(element(#element.body, Record)),
+  wf_tags:emit_tag(Tag, nitro:render(lists:flatten( [erlang:element(#element.body, Record)] )),
     lists:append([
-      [{<<"id">>,              element(#element.id, Record)},
-       {<<"class">>,           element(#element.class, Record)},
-       {<<"style">>,           element(#element.style, Record)},
-       {<<"title">>,           element(#element.title, Record)},
-       {<<"accesskey">>,       element(#element.accesskey, Record)},
-       {<<"contenteditable">>, element(#element.contenteditable, Record)},
-       {<<"contextmenu">>,     element(#element.contextmenu, Record)},
-       {<<"dir">>,             element(#element.dir, Record)},
-       {<<"draggable">>,       element(#element.draggable, Record)},
-       {<<"dropzone">>,        element(#element.dropzone, Record)},
-       {<<"hidden">>,          element(#element.hidden, Record)},
-       {<<"lang">>,            element(#element.lang, Record)},
-       {<<"spellcheck">>,      element(#element.spellcheck, Record)},
-       {<<"translate">>,       element(#element.translate, Record)},
-       {<<"tabindex">>,        element(#element.tabindex, Record)},
-       {<<"role">>,            element(#element.role, Record)}],
-      element(#element.data_fields, Record),
-      element(#element.aria_states, Record) ]) ).
+      [{<<"id">>,              erlang:element(#element.id, Record)},  %% todo check bench/compare erlang asm/refactoring
+       {<<"data-bind">>,       erlang:element(#element.bind, Record)},
+       {<<"class">>,           erlang:element(#element.class, Record)},
+       {<<"style">>,           erlang:element(#element.style, Record)},
+       {<<"title">>,           erlang:element(#element.title, Record)},
+       {<<"accesskey">>,       erlang:element(#element.accesskey, Record)},
+       {<<"contenteditable">>, erlang:element(#element.contenteditable, Record)},
+       {<<"contextmenu">>,     erlang:element(#element.contextmenu, Record)},
+       {<<"dir">>,             erlang:element(#element.dir, Record)},
+       {<<"draggable">>,       erlang:element(#element.draggable, Record)},
+       {<<"dropzone">>,        erlang:element(#element.dropzone, Record)},
+       {<<"hidden">>,          erlang:element(#element.hidden, Record)},
+       {<<"lang">>,            erlang:element(#element.lang, Record)},
+       {<<"onclick">>,         erlang:element(#element.onclick, Record)},
+       {<<"spellcheck">>,      erlang:element(#element.spellcheck, Record)},
+       {<<"translate">>,       erlang:element(#element.translate, Record)},
+       {<<"tabindex">>,        erlang:element(#element.tabindex, Record)},
+       {<<"onmouseout">>,      erlang:element(#element.onmouseout, Record)},
+       {<<"onmouseover">>,     erlang:element(#element.onmouseover, Record)},
+       {<<"onmousemove">>,     erlang:element(#element.onmousemove, Record)},
+       {<<"role">>,            erlang:element(#element.role, Record)}],
+      erlang:element(#element.data_fields, Record),
+      erlang:element(#element.aria_states, Record)
+    ]) ).
 

+ 25 - 13
src/render/wf_tags.erl

@@ -18,22 +18,34 @@
              orelse Tag == <<"option">> orelse Tag == <<"keygen">>
              orelse Tag == <<"source">>)).
 
-emit_tag(TagName, Props) -> [<<"<">>, TagName] ++ write_props(Props) ++ [<<"/>">>].
-emit_tag(TagName, undefined, Props) -> emit_tag(TagName, [], Props);
-emit_tag(TagName, [undefined], Props) -> emit_tag(TagName, [], Props);
-emit_tag(TagName, [], Props) when ?VOID(TagName) -> emit_tag(TagName, Props);
-emit_tag(TagName, [], Props) -> [<<"<">>, TagName, write_props(Props), <<">">>, <<"</">>, TagName, <<">">>];
-emit_tag(TagName, Content, Props) -> [<<"<">>, TagName, write_props(Props), <<">">>, Content,<<"</">>, TagName, <<">">>].
+
+emit_tag(TagName, Props) ->
+  [<<"<">>, TagName] ++ write_props(Props) ++ [<<"/>">>].
+
+emit_tag(TagName, C, Props) when C == undefined orelse C == [[]] orelse C == [undefined] ->
+  emit_tag(TagName, [], Props);
+emit_tag(TagName, C, Props) when (C == [] orelse C == <<>>) and ?VOID(TagName) ->
+  emit_tag(TagName, Props);
+emit_tag(TagName, [], Props) ->
+  [<<"<">>, TagName, write_props(Props), <<">">>, <<"</">>, TagName, <<">">>];
+emit_tag(TagName, Content, Props) ->
+  [<<"<">>, TagName, write_props(Props), <<">">>, Content,<<"</">>, TagName, <<">">>].
+
 
 write_props(Props) -> lists:map(fun display_property/1, Props).
 
-display_property({_, undefined}) -> [];
-display_property({_, []}) -> [];
-display_property({<<"class">> = Id, Value}) -> prop({Id, Value});
-display_property({<<"data-toggle">> = Id, Value}) -> prop({Id, Value});
+display_property({_, undefined}) ->
+  [];
+display_property({_, []}) ->
+  [];
+display_property({Id, _Value} = P) when Id == <<"class">> orelse Id == <<"data-toggle">> ->
+  prop(P);
 display_property({Prop, Value}) -> [<<" ">>, nitro:to_binary(Prop), <<"=\"">>, nitro:to_binary(Value), <<"\"">>].
 
-prop({Id, Value}) when is_atom(Value)   -> [<<" ">>, Id, <<"=\"">>, nitro:to_binary(Value), <<"\"">>];
-prop({Id, Value}) when is_binary(Value) -> [<<" ">>, Id, <<"=\"">>, Value, <<"\"">>];
-prop({Id, Value}) -> [<<" ">>, Id, <<"=\"">>, string:join([ nitro:to_list(V) || V <- Value ], " "), <<"\"">>].
+prop({Id, Value}) when erlang:is_atom(Value) ->
+  [<<" ">>, Id, <<"=\"">>, nitro:to_binary(Value), <<"\"">>];
+prop({Id, Value}) when erlang:is_binary(Value) ->
+  [<<" ">>, Id, <<"=\"">>, Value, <<"\"">>];
+prop({Id, Value}) ->
+  [<<" ">>, Id, <<"=\"">>, string:join([ nitro:to_list(V) || V <- Value ], " "), <<"\"">>].