diff --git a/.bashrc b/.bashrc new file mode 100644 index 0000000..607ec62 --- /dev/null +++ b/.bashrc @@ -0,0 +1 @@ +[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" diff --git a/Gemfile b/Gemfile index 0960f7d..fde5586 100644 --- a/Gemfile +++ b/Gemfile @@ -8,8 +8,7 @@ gem 'bcrypt-ruby', '~> 3.0.0' gem 'mysql2' gem 'sass-rails' - gem 'compass-rails' - gem 'zurb-foundation', ">= 3.2" + # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' @@ -30,18 +29,15 @@ gem 'turbolinks' gem 'jbuilder', '~> 1.0.1' gem "therubyracer" -gem "less-rails" -gem "twitter-bootstrap-rails" +#gem "less-rails" +#gem "twitter-bootstrap-rails" gem 'haml-rails' gem 'nokogiri' gem 'acts_as_tree' -gem 'formtastic', :git => 'git://github.com/justinfrench/formtastic.git' -gem 'kaminari-bootstrap' - gem "htmlentities" @@ -80,7 +76,9 @@ gem "geocoder" gem 'pygments.rb' gem 'redcarpet' gem "non-stupid-digest-assets" - +gem "twitter-bootstrap-rails" +gem 'formtastic', "2.3.0" +gem 'formtastic-bootstrap' # Use debugger # gem 'debugger', group: [:development, :test] diff --git a/Gemfile.lock b/Gemfile.lock index a398b8e..e3786a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: git://github.com/justinfrench/formtastic.git - revision: f630fb0bee233c75301cc0a362cfbe58ae7b5001 - specs: - formtastic (2.3.0.rc2) - actionpack (>= 3.0) - GEM remote: https://rubygems.org/ specs: @@ -32,44 +25,46 @@ GEM multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.37) - acts_as_commentable (4.0.1) - acts_as_tree (1.5.1) + acts_as_commentable (4.0.2) + acts_as_tree (2.1.0) activerecord (>= 3.0.0) arel (4.0.2) - bcrypt (3.1.7) + bcrypt (3.1.9) bcrypt-ruby (3.0.1) builder (3.1.4) cancan (1.6.10) + capistrano (2.15.5) + highline + net-scp (>= 1.0.0) + net-sftp (>= 2.0.0) + net-ssh (>= 2.0.14) + net-ssh-gateway (>= 1.1.0) carrierwave (0.9.0) activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) - chunky_png (1.3.0) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) - coffee-script (2.2.0) + coffee-script (2.3.0) coffee-script-source execjs - coffee-script-source (1.7.0) - commonjs (0.2.7) - compass (0.12.5) - chunky_png (~> 1.2) - fssm (>= 0.2.7) - sass (~> 3.2.19) - compass-rails (1.1.6) - compass (>= 0.12.2) - devise (3.2.4) + coffee-script-source (1.8.0) + devise (3.4.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) + responders thread_safe (~> 0.1) warden (~> 1.2.3) erubis (2.7.0) - execjs (2.0.2) - fssm (0.2.10) - geocoder (1.1.9) - haml (4.0.5) + execjs (2.2.2) + formtastic (2.3.0) + actionpack (>= 3.0) + formtastic-bootstrap (3.0.0) + formtastic (>= 2.2) + geocoder (1.2.6) + haml (4.0.6) tilt haml-rails (0.5.1) actionpack (~> 4.0.0) @@ -78,44 +73,46 @@ GEM railties (~> 4.0.0) highline (1.6.21) hike (1.2.3) - htmlentities (4.3.1) - i18n (0.6.9) + htmlentities (4.3.2) + i18n (0.6.11) jbuilder (1.0.2) activesupport (>= 3.0.0) - jquery-rails (3.1.0) + jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (4.2.0) + jquery-ui-rails (5.0.3) railties (>= 3.2.16) json (1.8.1) - kaminari (0.15.1) + kaminari (0.16.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) kaminari-bootstrap (3.0.1) kaminari (>= 0.13.0) rails - kgio (2.8.0) - less (2.5.0) - commonjs (~> 0.2.7) - less-rails (2.5.0) - actionpack (>= 3.1) - less (~> 2.5.0) - libv8 (3.16.14.3) + kgio (2.9.2) + libv8 (3.16.14.7) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.25.1) - mini_portile (0.5.3) + mini_portile (0.6.1) minitest (4.7.5) - multi_json (1.9.2) - mysql2 (0.3.15) - nokogiri (1.6.1) - mini_portile (~> 0.5.0) + multi_json (1.10.1) + mysql2 (0.3.17) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (2.9.1) + net-ssh-gateway (1.2.0) + net-ssh (>= 2.6.5) + nokogiri (1.6.5) + mini_portile (~> 0.6.0) non-stupid-digest-assets (1.0.4) orm_adapter (0.5.0) - polyglot (0.3.4) - posix-spawn (0.3.8) - pygments.rb (0.5.4) + polyglot (0.3.5) + posix-spawn (0.3.9) + pygments.rb (0.6.0) posix-spawn (~> 0.3.6) yajl-ruby (~> 1.1.0) rack (1.5.2) @@ -134,24 +131,27 @@ GEM activesupport (= 4.0.0) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.11.0) - rake (10.1.0) - rdoc (4.1.1) + raindrops (0.13.0) + rake (10.4.2) + rdoc (4.1.2) json (~> 1.4) - redcarpet (3.1.1) + redcarpet (3.2.1) ref (1.0.5) - rmagick (2.13.2) - rvm-capistrano (1.5.1) + responders (1.1.2) + railties (>= 3.2, < 4.2) + rmagick (2.13.4) + rvm-capistrano (1.5.5) capistrano (~> 2.15.4) sass (3.2.19) - sass-rails (4.0.1) + sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) - sass (>= 3.1.10) - sprockets-rails (~> 2.0.0) - sdoc (0.4.0) - json (~> 1.8) - rdoc (~> 4.0, < 5.0) - sprockets (2.12.0) + sass (~> 3.2.2) + sprockets (~> 2.8, < 3.0) + sprockets-rails (~> 2.0) + sdoc (0.4.1) + json (~> 1.7, >= 1.7.7) + rdoc (~> 4.0) + sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) @@ -164,20 +164,20 @@ GEM libv8 (~> 3.16.14.0) ref thor (0.19.1) - thread_safe (0.3.3) + thread_safe (0.3.4) tilt (1.4.1) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - turbolinks (2.2.2) + turbolinks (2.5.2) coffee-rails twitter-bootstrap-rails (2.2.8) actionpack (>= 3.1) execjs rails (>= 3.1) railties (>= 3.1) - tzinfo (0.3.39) - uglifier (2.5.0) + tzinfo (0.3.42) + uglifier (2.5.3) execjs (>= 0.3.0) json (>= 1.8.0) unicorn (4.6.3) @@ -187,8 +187,6 @@ GEM warden (1.2.3) rack (>= 1.0) yajl-ruby (1.1.0) - zurb-foundation (4.3.2) - sass (>= 3.2.0) PLATFORMS ruby @@ -201,9 +199,9 @@ DEPENDENCIES capistrano carrierwave (= 0.9.0) coffee-rails (~> 4.0.0) - compass-rails devise - formtastic! + formtastic (= 2.3.0) + formtastic-bootstrap geocoder haml-rails htmlentities @@ -212,7 +210,6 @@ DEPENDENCIES jquery-ui-rails kaminari kaminari-bootstrap - less-rails mysql2 nokogiri non-stupid-digest-assets @@ -228,4 +225,3 @@ DEPENDENCIES twitter-bootstrap-rails uglifier (>= 1.3.0) unicorn (= 4.6.3) - zurb-foundation (>= 3.2) diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index de912ab..634a807 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -1,6 +1,5 @@ /* =require jquery -= require jquery.ui.all = require twitter/bootstrap diff --git a/app/assets/javascripts/connexion.coffee b/app/assets/javascripts/connexion.coffee index 5127ddf..943ed17 100644 --- a/app/assets/javascripts/connexion.coffee +++ b/app/assets/javascripts/connexion.coffee @@ -1,5 +1,4 @@ #= require jquery -#= require jquery.ui.all #= require twitter/bootstrap #= require ./shared/jquery.backstretch.js diff --git a/app/assets/javascripts/forum.coffee b/app/assets/javascripts/forum.coffee index 069120f..86fd268 100644 --- a/app/assets/javascripts/forum.coffee +++ b/app/assets/javascripts/forum.coffee @@ -16,7 +16,7 @@ $(document).on "scroll", () -> if( $(this).scrollTop() <= 40 ) - top = 40 - $(this).scrollTop() + top = 50 - $(this).scrollTop() else top = 0 @@ -28,7 +28,7 @@ $(document).on "scroll", () -> $(".images .user_image").click -> src = undefined src = $(this).data("src-large") - $("#message_form .content").redactor().insertHtml "" + $("#message_form .content").redactor('insert.html', ""); false return diff --git a/app/assets/javascripts/redactor.js b/app/assets/javascripts/redactor.js index ec3659a..41fae96 100755 --- a/app/assets/javascripts/redactor.js +++ b/app/assets/javascripts/redactor.js @@ -1,4269 +1,8084 @@ /* - Redactor v8.2.2 - Updated: January 17, 2013 + Redactor v10.0.5 + Updated: November 18, 2014 - http://redactorjs.com/ + http://imperavi.com/redactor/ - Copyright (c) 2009-2013, Imperavi Inc. - License: http://redactorjs.com/license/ + Copyright (c) 2009-2014, Imperavi LLC. + License: http://imperavi.com/redactor/license/ Usage: $('#content').redactor(); */ -var rwindow, rdocument; - -if (typeof RELANG === 'undefined') +(function($) { - var RELANG = {}; -} + 'use strict'; -var RLANG = { - html: 'HTML', - video: 'Insert Video', - image: 'Insert Image', - table: 'Table', - link: 'Link', - link_insert: 'Insert link', - unlink: 'Unlink', - formatting: 'Formatting', - paragraph: 'Paragraph', - quote: 'Quote', - code: 'Code', - header1: 'Header 1', - header2: 'Header 2', - header3: 'Header 3', - header4: 'Header 4', - 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', - image_web_link: 'Image Web Link', - text: 'Text', - mailto: 'Email', - web: 'URL', - video_html_code: 'Video Embed Code', - 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' -}; + if (!Function.prototype.bind) + { + Function.prototype.bind = function(scope) + { + var fn = this; + return function() + { + return fn.apply(scope); + }; + }; + } -(function($){ + var uuid = 0; + + var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig; + var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/; // Plugin - jQuery.fn.redactor = function(option) + $.fn.redactor = function(options) { - return this.each(function() - { - var $obj = $(this); + var val = []; + var args = Array.prototype.slice.call(arguments, 1); - var data = $obj.data('redactor'); - if (!data) + if (typeof options === 'string') + { + this.each(function() { - $obj.data('redactor', (data = new Redactor(this, option))); - } - }); - }; + 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]; + } - // Initialization - var Redactor = function(element, options) - { - // Element - this.$el = $(element); - - // Lang - if (typeof options !== 'undefined' && typeof options.lang !== 'undefined' && options.lang !== 'en' && typeof RELANG[options.lang] !== 'undefined') + 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 { - RLANG = RELANG[options.lang]; + this.each(function() + { + $.data(this, 'redactor', {}); + $.data(this, 'redactor', Redactor(this, options)); + }); } - // Options - this.opts = $.extend({ + if (val.length === 0) return this; + else if (val.length === 1) return val[0]; + else return val; - iframe: false, - css: false, // url + }; - lang: 'en', - direction: 'ltr', // ltr or rtl + // Initialization + function Redactor(el, options) + { + return new Redactor.prototype.init(el, options); + } - callback: false, // function - keyupCallback: false, // function - keydownCallback: false, // function - execCommandCallback: false, // function + // Functionality + $.Redactor = Redactor; + $.Redactor.VERSION = '10.0.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', 'list', 'modal', 'observe', 'paragraphize', + 'paste', 'placeholder', 'progress', 'selection', 'shortcuts', + 'tabifier', 'tidy', 'toolbar', 'upload', 'utils']; - plugins: false, - cleanup: true, + $.Redactor.opts = { - focus: false, - tabindex: false, - autoresize: true, - minHeight: false, - fixed: false, - fixedTop: 0, // pixels - fixedBox: false, - source: true, - shortcuts: true, + // settings + lang: 'en', + direction: 'ltr', // ltr or rtl - mobile: true, - air: false, // true or toolbar - wym: false, + plugins: false, // array - convertLinks: true, - convertDivs: true, - protocol: 'http://', // for links http or https or ftp or false + focus: false, + focusEnd: false, - autosave: false, // false or url - autosaveCallback: false, // function - interval: 60, // seconds + placeholder: false, - imageGetJson: false, // url (ex. /folder/images.json ) or false + visual: true, + tabindex: false, - imageUpload: false, // url - imageUploadCallback: false, // function - imageUploadErrorCallback: false, // function + minHeight: false, + maxHeight: false, - fileUpload: false, // url - fileUploadCallback: false, // function - fileUploadErrorCallback: false, // function + linebreaks: false, + replaceDivs: true, + paragraphize: true, + cleanStyleOnEnter: false, + enterKey: true, - uploadCrossDomain: false, - uploadFields: false, + cleanOnPaste: true, + cleanSpaces: true, + pastePlainText: false, - observeImages: true, - overlay: true, // modal overlay + autosave: false, // false or url + autosaveName: false, + autosaveInterval: 60, // seconds + autosaveOnChange: false, - allowedTags: ["form", "input", "button", "select", "option", "datalist", "output", "textarea", "fieldset", "legend", - "section", "header", "hgroup", "aside", "footer", "article", "details", "nav", "progress", "time", "canvas", - "code", "span", "div", "label", "a", "br", "p", "b", "i", "del", "strike", "u", - "img", "video", "source", "track", "audio", "iframe", "object", "embed", "param", "blockquote", - "mark", "cite", "small", "ul", "ol", "li", "hr", "dl", "dt", "dd", "sup", "sub", - "big", "pre", "code", "figure", "figcaption", "strong", "em", "table", "tr", "td", - "th", "tbody", "thead", "tfoot", "h1", "h2", "h3", "h4", "h5", "h6"], + linkTooltip: true, + linkProtocol: 'http', + linkNofollow: false, + linkSize: 50, - toolbarExternal: false, // ID selector + imageEditable: true, + imageLink: true, + imagePosition: true, + imageFloatMargin: '10px', + imageResizable: true, - buttonsCustom: {}, - buttonsAdd: [], - buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', - 'image', 'video', 'file', 'table', 'link', '|', - 'fontcolor', 'backcolor', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify' + imageUpload: false, + imageUploadParam: 'file', - airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'fontcolor', 'backcolor'], + uploadImageField: false, - formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4'], + dragImageUpload: true, - 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' - }, + fileUpload: false, + fileUploadParam: 'file', - 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'], + dragFileUpload: true, - // private - emptyHtml: '


', - buffer: false, - visual: true, + s3: false, - // modal windows container - modal_file: String() + - '
' + - '
' + - '' + - '' + - '
' + - '' + - '
' + - '

' + - '
', + convertLinks: true, + convertUrlLinks: true, + convertImageLinks: true, + convertVideoLinks: true, - modal_image_edit: String() + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '', + preSpaces: 4, // or false + tabAsSpaces: false, // true or number of spaces + tabFocus: true, - modal_image: String() + - '
' + - '
' + - '' + RLANG.upload + '' + - '' + RLANG.choose + '' + - '' + RLANG.link + '' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '' + - '
' + - '', + scrollTarget: false, - modal_link: String() + - '
' + - '
' + - '
' + - 'URL' + - 'Email' + - '' + RLANG.anchor + '' + - '
' + - '' + - '
' + - '' + - '' + - '' + - '
' + - '' + - '' + - '
' + - '
' + - '', + toolbar: true, + toolbarFixed: true, + toolbarFixedTarget: document, + toolbarFixedTopOffset: 0, // pixels + toolbarExternal: false, // ID selector + toolbarOverflow: false, - modal_table: String() + - '
' + - '' + - '' + - '' + - '' + - '
' + - '', + buttonSource: false, + buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', + 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline' - modal_video: String() + - '
' + - '
' + - '' + - '' + - '
' + - '
'+ - '', + buttonsHide: [], + buttonsHideOnMobile: [], - toolbar: { - html: - { - title: RLANG.html, - func: 'toggle' - }, - formatting: - { - title: RLANG.formatting, - func: 'show', - dropdown: - { - p: - { - title: RLANG.paragraph, - exec: 'formatblock' - }, - blockquote: - { - title: RLANG.quote, - exec: 'formatblock', - className: 'redactor_format_blockquote' - }, - pre: - { - title: RLANG.code, - exec: 'formatblock', - className: 'redactor_format_pre' - }, - h1: - { - title: RLANG.header1, - exec: 'formatblock', - className: 'redactor_format_h1' - }, - h2: - { - title: RLANG.header2, - exec: 'formatblock', - className: 'redactor_format_h2' - }, - h3: - { - title: RLANG.header3, - exec: 'formatblock', - className: 'redactor_format_h3' - }, - h4: - { - title: RLANG.header4, - exec: 'formatblock', - className: 'redactor_format_h4' - } - } - }, - bold: - { - title: RLANG.bold, - exec: 'bold' - }, - italic: - { - title: RLANG.italic, - exec: 'italic' - }, - deleted: - { - title: RLANG.deleted, - exec: 'strikethrough' - }, - underline: - { - title: RLANG.underline, - exec: 'underline' - }, - unorderedlist: - { - title: '• ' + RLANG.unorderedlist, - exec: 'insertunorderedlist' - }, - orderedlist: - { - title: '1. ' + RLANG.orderedlist, - exec: 'insertorderedlist' - }, - outdent: - { - title: '< ' + RLANG.outdent, - exec: 'outdent' - }, - indent: - { - title: '> ' + RLANG.indent, - exec: 'indent' - }, - image: - { - title: RLANG.image, - func: 'showImage' - }, - video: - { - title: RLANG.video, - func: 'showVideo' - }, - file: - { - title: RLANG.file, - func: 'showFile' - }, - table: - { - title: RLANG.table, - func: 'show', - dropdown: - { - insert_table: - { - title: RLANG.insert_table, - func: 'showTable' - }, - separator_drop1: - { - name: 'separator' - }, - insert_row_above: - { - title: RLANG.insert_row_above, - func: 'insertRowAbove' - }, - insert_row_below: - { - title: RLANG.insert_row_below, - func: 'insertRowBelow' - }, - insert_column_left: - { - title: RLANG.insert_column_left, - func: 'insertColumnLeft' - }, - insert_column_right: - { - title: RLANG.insert_column_right, - func: 'insertColumnRight' - }, - separator_drop2: - { - name: 'separator' - }, - add_head: - { - title: RLANG.add_head, - func: 'addHead' - }, - delete_head: - { - title: RLANG.delete_head, - func: 'deleteHead' - }, - separator_drop3: - { - name: 'separator' - }, - delete_column: - { - title: RLANG.delete_column, - func: 'deleteColumn' - }, - delete_row: - { - title: RLANG.delete_row, - func: 'deleteRow' - }, - delete_table: - { - title: RLANG.delete_table, - func: 'deleteTable' - } - } - }, - link: - { - title: RLANG.link, - func: 'show', - dropdown: - { - link: - { - title: RLANG.link_insert, - func: 'showLink' - }, - unlink: - { - title: RLANG.unlink, - exec: 'unlink' - } - } - }, - fontcolor: - { - title: RLANG.fontcolor, - func: 'show' - }, - backcolor: - { - title: RLANG.backcolor, - func: 'show' - }, - alignment: - { - title: RLANG.alignment, - func: 'show', - dropdown: - { - alignleft: - { - title: RLANG.align_left, - exec: 'JustifyLeft' - }, - aligncenter: - { - title: RLANG.align_center, - exec: 'JustifyCenter' - }, - alignright: - { - title: RLANG.align_right, - exec: 'JustifyRight' - }, - justify: - { - title: RLANG.align_justify, - exec: 'JustifyFull' - } - } - }, - alignleft: - { - exec: 'JustifyLeft', - title: RLANG.align_left - }, - aligncenter: - { - exec: 'JustifyCenter', - title: RLANG.align_center - }, - alignright: - { - exec: 'JustifyRight', - title: RLANG.align_right - }, - justify: - { - exec: 'JustifyFull', - title: RLANG.align_justify - }, - horizontalrule: - { - exec: 'inserthorizontalrule', - title: RLANG.horizontalrule - } + formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + formattingAdd: false, + + tabifier: true, + + deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'], + allowedTags: false, // or array + + removeComments: false, + replaceTags: [ + ['strike', 'del'] + ], + 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: '

', + invisibleSpace: '​', + 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'], + + + // 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' } - - - }, options, this.$el.data()); - - this.dropdowns = []; - - // Init - this.init(); + } }; // Functionality - Redactor.prototype = { + Redactor.fn = $.Redactor.prototype = { + keyCode: { + BACKSPACE: 8, + DELETE: 46, + DOWN: 40, + ENTER: 13, + SPACE: 32, + ESC: 27, + TAB: 9, + CTRL: 17, + META: 91, + SHIFT: 16, + ALT: 18, + LEFT: 37, + LEFT_WIN: 91 + }, // Initialization - init: function() + init: function(el, options) { - // get dimensions - this.height = this.$el.css('height'); - this.width = this.$el.css('width'); + this.$element = $(el); + this.uuid = uuid++; - rdocument = this.document = document; - rwindow = this.window = window; + // if paste event detected = true + this.rtePaste = false; + this.$pasteBox = false; - // mobile - if (this.opts.mobile === false && this.isMobile()) + 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(); + + // 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) { - this.build(true); - return false; + 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; - // iframe - if (this.opts.iframe) - { - this.opts.autoresize = false; - } + // init module + this[module] = this[module](); - // extend buttons - if (this.opts.air) + var methods = this.getModuleMethods(this[module]); + var len = methods.length; + + // bind methods + for (var z = 0; z < len; z++) { - this.opts.buttons = this.opts.airButtons; + this[module][methods[z]] = this[module][methods[z]].bind(this); } - else if (this.opts.toolbar !== false) - { - if (this.opts.source === false) + }, + + alignment: function() + { + return { + left: function() { - var index = this.opts.buttons.indexOf('html'); - var next = this.opts.buttons[index+1]; - this.opts.buttons.splice(index, 1); - if (typeof next !== 'undefined' && next === '|') + this.alignment.set(''); + }, + right: function() + { + this.alignment.set('right'); + }, + center: function() + { + this.alignment.set('center'); + }, + justify: function() + { + this.alignment.set('justify'); + }, + set: function(type) + { + if (!this.utils.browser('msie')) this.$editor.focus(); + + this.buffer.set(); + this.selection.save(); + + this.alignment.blocks = this.selection.getBlocks(); + if (this.opts.linebreaks && this.alignment.blocks[0] === false) { - this.opts.buttons.splice(index, 1); + this.alignment.setText(type); + } + else + { + this.alignment.setBlocks(type); + } + + this.selection.restore(); + this.code.sync(); + }, + setText: function(type) + { + var wrapper = this.selection.wrap('div'); + $(wrapper).attr('data-tagblock', 'redactor'); + $(wrapper).css('text-align', type); + }, + setBlocks: function(type) + { + $.each(this.alignment.blocks, $.proxy(function(i, el) + { + var $el = this.utils.getAlignmentElement(el); + + if (!$el) return; + + if (type === '' && typeof($el.data('tagblock')) !== 'undefined') + { + $el.replaceWith($el.html()); + } + else + { + $el.css('text-align', type); + this.utils.removeEmptyAttr($el, 'style'); + } + + + }, this)); + } + }; + }, + autosave: function() + { + return { + enable: function() + { + if (!this.opts.autosave) return; + + this.autosave.html = false; + this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name'); + + if (!this.opts.autosaveOnChange) + { + this.autosaveInterval = setInterval($.proxy(this.autosave.load, this), this.opts.autosaveInterval * 1000); + } + }, + onChange: function() + { + if (!this.opts.autosaveOnChange) return; + + this.autosave.load(); + }, + load: function() + { + var html = this.code.get(); + if (this.autosave.html === html) return; + if (this.utils.isEmpty(html)) return; + + $.ajax({ + url: this.opts.autosave, + type: 'post', + data: 'name=' + this.autosave.name + '&' + this.autosave.name + '=' + escape(encodeURIComponent(html)), + success: $.proxy(function(data) + { + this.autosave.success(data, html); + + }, this) + }); + }, + success: function(data, html) + { + 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 = html; + }, + disable: function() + { + clearInterval(this.autosaveInterval); + } + }; + }, + block: function() + { + return { + formatting: function(name) + { + 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 (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(); + + 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(); + + }, + 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) + { + 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).prepend('
').append('
'); + 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).prepend('
').append('
'); + 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)); + } + + }, + 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).prepend('
').append('
'); + 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('
').append('
'); + 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) + { + if (this.opts.linebreaks) + { + $(s).prepend('
').append('
'); + this.utils.replaceWithContents(s); + } + else + { + this.utils.replaceToTag(s, 'p'); + } + + }, 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).size(); + + 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(); + + + }, this)); + } + } + }, + setForce: function($el) + { + 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) + { + 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'); + + $(block).find('ul, ol').contents().unwrap(); + $(block).find('li').append($('
')).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'); + + if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote') + { + $elements.append('
'); + } + + $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); + } + + }, + formatTableWrapping: function($formatted) + { + if ($formatted.closest('table').size() === 0) return; + + if ($formatted.closest('tr').size() === 0) $formatted.wrap(''); + if ($formatted.closest('td').size() === 0 && $formatted.closest('th').size() === 0) + { + $formatted.wrap(''); + } + }, + 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 { + 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 = $('
'); + }, + createTextarea: function() + { + this.$textarea = $('').css('height', this.height); - } - - if (this.$editor) - { - this.$editor.addClass('redactor_editor').attr('contenteditable', true).attr('dir', this.opts.direction); - } - - if (this.opts.tabindex !== false) - { - this.$content.attr('tabindex', this.opts.tabindex); - } - - if (this.opts.minHeight !== false) - { - this.$content.css('min-height', this.opts.minHeight + 'px'); - } - - if (this.opts.wym === true) - { - this.$content.addClass('redactor_editor_wym'); - } - - if (this.opts.autoresize === false) - { - this.$content.css('height', this.height); - } - - // hide textarea - this.$el.hide(); - - // append box and frame - var html = ''; - if (this.textareamode) - { - // get html - html = this.$el.val(); - html = this.savePreCode(html); - - this.$box.insertAfter(this.$el).append(this.$content).append(this.$el); - } - else - { - // get html - html = this.$editor.html(); - html = this.savePreCode(html); - - this.$box.insertAfter(this.$content).append(this.$el).append(this.$editor); } - - // conver newlines to p - html = this.paragraphy(html); - - // enable - if (this.$editor) - { - this.$editor.html(html); - } - - if (this.textareamode === false) - { - this.syncCode(); - } - } - else - { - if (this.$el.get(0).tagName !== 'TEXTAREA') - { - var html = this.$el.val(); - var textarea = $('').css('height', this.height).val(html); - this.$el.hide(); - this.$el.after(textarea); - } - } - - if (whendone && this.$editor) - { - whendone.call(this); - } - + }; }, - enableAir: function() + observe: function() { - if (this.opts.air === false) - { - return false; - } - - this.air.hide(); - - this.$editor.bind('textselect', $.proxy(function(e) - { - this.showAir(e); - - }, this)); - - this.$editor.bind('textunselect', $.proxy(function() - { - this.air.hide(); - - }, this)); - - }, - showAir: function(e) - { - $('.redactor_air').hide(); - - var width = this.air.innerWidth(); - var left = e.clientX; - - if ($(this.document).width() < (left + width)) - { - left = left - width; - } - - var top = e.clientY + $(document).scrollTop() + 14; - if (this.opts.iframe === true) - { - top = top + this.$box.position().top; - left = left + this.$box.position().left; - } - - this.air.css({ left: left + 'px', top: top + 'px' }).show(); - }, - syncCode: function() - { - this.$el.val(this.$editor.html()); - }, - - // API functions - setCode: function(html) - { - html = this.stripTags(html); - this.$editor.html(html).focus(); - - this.syncCode(); - }, - getCode: function() - { - var html = ''; - if (this.opts.visual) - { - html = this.$editor.html() - } - else - { - html = this.$el.val(); - } - - return this.stripTags(html); - }, - insertHtml: function(html) - { - this.$editor.focus(); - this.pasteHtmlAtCaret(html); - this.observeImages(); - this.syncCode(); - }, - - pasteHtmlAtCaret: function (html) - { - var sel, range; - if (this.document.getSelection) - { - sel = this.window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) + return { + load: function() { - range = sel.getRangeAt(0); - range.deleteContents(); - var el = this.document.createElement("div"); - el.innerHTML = html; - var frag = this.document.createDocumentFragment(), node, lastNode; - while (node = el.firstChild) + this.observe.images(); + this.observe.links(); + }, + buttons: function(e, btnName) + { + var current = this.selection.getCurrent(); + var parent = this.selection.getParent(); + + this.button.setInactiveAll(btnName); + + if (e === false && btnName !== 'html') { - lastNode = frag.appendChild(node); + if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName); + return; } - range.insertNode(frag); - if (lastNode) + //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) { - range = range.cloneRange(); - range.setStartAfter(lastNode); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); + var parentEl = $(parent).closest(key); + var currentEl = $(current).closest(key); + + if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) return; + if (!this.utils.isRedactorParent(currentEl)) return; + if (parentEl.length !== 0 || currentEl.closest(key).length !== 0) + { + this.button.setActive(value); + } + + }, this)); + + var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase()); + if (this.utils.isRedactorParent(parent) && $parent.length) + { + var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align'); + this.button.setActive('align' + align); } - } - } - else if (this.document.selection && this.document.selection.type != "Control") - { - this.document.selection.createRange().pasteHTML(html); - } - }, - - destroy: function() - { - var html = this.getCode(); - - if (this.textareamode) - { - this.$box.after(this.$el); - this.$box.remove(); - this.$el.height(this.height).val(html).show(); - } - else - { - this.$box.after(this.$editor); - this.$box.remove(); - this.$editor.removeClass('redactor_editor').removeClass('redactor_editor_wym').attr('contenteditable', false).html(html).show(); - } - - if (this.opts.toolbarExternal) - { - $(this.opts.toolbarExternal).empty(); - } - - $('.redactor_air').remove(); - - for (var i = 0; i < this.dropdowns.length; i++) - { - this.dropdowns[i].remove(); - delete(this.dropdowns[i]); - } - - if (this.opts.autosave !== false) - { - clearInterval(this.autosaveInterval); - } - - }, - // end API functions - - // OBSERVERS - observeFormatting: function() - { - var parent = this.getCurrentNode(); - - this.inactiveAllButtons(); - - $.each(this.opts.activeButtonsStates, $.proxy(function(i,s) - { - if ($(parent).closest(i,this.$editor.get()[0]).length != 0) + }, + addButton: function(tag, btnName) { - this.setBtnActive(s); - } - - }, this)); - - var tag = $(parent).closest(['p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'td']); - - if (typeof tag[0] !== 'undefined' && typeof tag[0].elem !== 'undefined' && $(tag[0].elem).size() != 0) - { - var align = $(tag[0].elem).css('text-align'); - - switch (align) + this.opts.activeButtons.push(btnName); + this.opts.activeButtonsStates[tag] = btnName; + }, + images: function() { - case 'right': - this.setBtnActive('alignright'); - break; - case 'center': - this.setBtnActive('aligncenter'); - break; - case 'justify': - this.setBtnActive('justify'); - break; - default: - this.setBtnActive('alignleft'); - break; - } - } - }, - observeImages: function() - { - /*if (this.opts.observeImages === false) - { - return false; - } - - this.$editor.find('img').each($.proxy(function(i,s) - { - if (this.browser('msie')) - { - $(s).attr('unselectable', 'on'); - } - - this.resizeImage(s); - - }, this));*/ - - }, - observeTables: function() - { - this.$editor.find('table').click($.proxy(this.tableObserver, this)); - }, - observeScroll: function() - { - var scrolltop = $(this.document).scrollTop(); - var boxtop = this.$box.offset().top; - var left = 0; - - if (scrolltop > boxtop) - { - var width = '100%'; - if (this.opts.fixedBox) - { - left = this.$box.offset().left; - width = this.$box.innerWidth(); - } - - this.fixed = true; - this.$toolbar.css({ position: 'fixed', width: width, zIndex: 1005, top: this.opts.fixedTop + 'px', left: left }); - } - else - { - this.fixed = false; - this.$toolbar.css({ position: 'relative', width: 'auto', zIndex: 1, top: 0, left: left }); - } - }, - - // BUFFER - setBuffer: function() - { - this.saveSelection(); - this.opts.buffer = this.$editor.html(); - }, - getBuffer: function() - { - if (this.opts.buffer === false) - { - return false; - } - - this.$editor.html(this.opts.buffer); - - if (!this.browser('msie')) - { - this.restoreSelection(); - } - - this.opts.buffer = false; - }, - - - - // EXECCOMMAND - execCommand: function(cmd, param) - { - if (this.opts.visual == false) - { - this.$el.focus(); - return false; - } - - try - { - - var parent; - - if (cmd === 'inserthtml') - { - if (this.browser('msie')) + this.$editor.find('img').each($.proxy(function(i, img) { - this.$editor.focus(); - this.document.selection.createRange().pasteHTML(param); + 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').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', $.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 click', $.proxy(this.observe.showTooltip, this)); + this.$editor.on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this)); + $(document).on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this)); + }, + getTooltipPosition: function($link) + { + return $link.offset(); + }, + showTooltip: function(e) + { + var $link = $(e.target); + var $parent = $link.closest('a'); + if ($parent.size() !== 0 && $parent[0].tagName === 'A' && $link[0].tagName !== 'A') + { + $link = $parent; + } + else if ($link.size() === 0 || $link[0].tagName !== 'A') + { + return; + } + + var pos = this.observe.getTooltipPosition($link); + var tooltip = $(''); + + var href = $link.attr('href'); + if (href === undefined) + { + href = ''; + } + + if (href.length > 24) href = href.substring(0, 24) + '...'; + + var aLink = $('').html(href).addClass('redactor-link-tooltip-action'); + var aEdit = $('').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action'); + var aUnlink = $('').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 + 20) + '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'); + if ($parent.size() !== 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 === '

') return this.opts.emptyHtml; + + this.paragraphize.blocks = ['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']; + + html = html + "\n"; + + this.paragraphize.safes = []; + this.paragraphize.z = 0; + + html = html.replace(/(){1,}\n?<\/blockquote>/gi, ''); + + 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('\n?<(' + this.paragraphize.blocks.join('|') + ')(.*?[^>])>', 'gi'), '


\n<$1$2>'); + + return $.trim(html); + }, + getSafes: function(html) + { + var $div = $('
').append(html); + + // remove paragraphs in blockquotes + $div.find('blockquote p').replaceWith(function() + { + return $(this).append('
').contents(); + }); + + html = $div.html(); + + $div.find(this.paragraphize.blocks.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(//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) + { + 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(/

\n\t?<\/p>/gi, ''); + htmls[i] = htmls[i].replace(/

<\/p>/gi, ''); + + if (htmls[i] !== '') + { + html += '

' + htmls[i].replace(/^\n+|\n+$/g, "") + "

"; + } + } + else html += htmls[i]; + } + } + + return html; + }, + replaceBreaksToNewLines: function(html) + { + html = html.replace(/
\s*
/gi, "\n\n"); + html = html.replace(/\n?/gi, "\n

"); + + 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('

', 'gi'), ''); + html = html.replace(new RegExp('

', 'gi'), ''); + html = html.replace(new RegExp('

', 'gi'), '
'); + html = html.replace(new RegExp('

', 'gi'), '
'); + + html = html.replace(new RegExp('

', 'gi'), '

'); + html = html.replace(new RegExp('

', 'gi'), '

'); + html = html.replace(new RegExp('

\\s?

', 'gi'), ''); + html = html.replace(new RegExp("\n

", 'gi'), '

'); + html = html.replace(new RegExp('

\t?\t?\n?

', 'gi'), '

'); + html = html.replace(new RegExp('

\t*

', 'gi'), ''); + + return html; + } + }; + }, + paste: function() + { + return { + init: function(e) + { + if (!this.opts.cleanOnPaste) 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'); + + }, this), 1); + + }, + createPasteBox: function() + { + this.$pasteBox = $('
').html(' ').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' }); + + this.$box.parent().append(this.$pasteBox); + this.$pasteBox.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.pasteHtmlAtCaret(param); - //this.execRun(cmd, param); + this.insert.html(html, false); } - this.observeImages(); + this.utils.disableSelectAll(); + this.rtePaste = false; + + setTimeout($.proxy(this.clean.clearUnverified, this), 10); + } - else if (cmd === 'unlink') + }; + }, + placeholder: function() + { + return { + enable: function() { - parent = this.getParentNode(); - if ($(parent).get(0).tagName === 'A') + if (!this.placeholder.is()) return; + + this.$editor.attr('placeholder', this.$element.attr('placeholder')); + + this.placeholder.toggle(); + this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this)); + + }, + toggle: function() + { + var func = 'removeClass'; + if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass'; + this.$editor[func]('redactor-placeholder'); + }, + remove: function() + { + this.$editor.removeClass('redactor-placeholder'); + }, + is: function() + { + if (this.opts.placeholder) { - $(parent).replaceWith($(parent).text()); + return this.$element.attr('placeholder', this.opts.placeholder); } else { - this.execRun(cmd, param); + return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === ''); } } - else if (cmd === 'JustifyLeft' || cmd === 'JustifyCenter' || cmd === 'JustifyRight' || cmd === 'JustifyFull') + }; + }, + progress: function() + { + return { + show: function() { - parent = this.getCurrentNode(); - var tag = $(parent).get(0).tagName; + $(document.body).append($('
')); + $('#redactor-progress').fadeIn(); + }, + hide: function() + { + $('#redactor-progress').fadeOut(1500, function() + { + $(this).remove(); + }); + } - if (this.opts.iframe === false && $(parent).parents('.redactor_editor').size() == 0) + }; + }, + 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; + }, + 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) + { + this.selection.get(); + + if (this.range && this.range.collapsed) { return false; } - var tagsArray = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'TD']; - if ($.inArray(tag, tagsArray) != -1) + var inlines = []; + nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes; + var inlineTags = this.opts.inlineTags; + inlineTags.push('span'); + $.each(nodes, $.proxy(function(i,node) { - var align = false; - - if (cmd === 'JustifyCenter') + if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1) { - align = 'center'; - } - else if (cmd === 'JustifyRight') - { - align = 'right'; - } - else if (cmd === 'JustifyFull') - { - align = 'justify'; + inlines.push(node); } - if (align === false) + }, 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)) { - $(parent).css('text-align', ''); - } - else - { - $(parent).css('text-align', align); + this.selection.lastBlock = 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); + + this.selection.setNodesMarker(this.range, startNode, true); + + if (this.range.collapsed === false) + { + this.selection.setNodesMarker(this.range, endNode, false); } else { - this.execRun(cmd, param); + 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 $('' + this.opts.invisibleSpace + '')[0]; + }, + setNodesMarker: function(range, node, type) + { + 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) + { + 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 $('' + this.opts.invisibleSpace + '')[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 (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').remove(); + }, + 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); + } + }; + }, + 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); + } } } - else if (cmd === 'formatblock' && param === 'blockquote') + }; + }, + tabifier: function() + { + return { + get: function(code) { - parent = this.getCurrentNode(); - if ($(parent).get(0).tagName === 'BLOCKQUOTE') + 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 = ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', '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('^]'); + + var i = 0, + codeLength = code.length, + point = 0, + start = null, + end = null, + tag = '', + out = '', + cont = ''; + + this.tabifier.cleanlevel = 0; + + for (; i < codeLength; i++) { - if (this.browser('msie')) + point = i; + + // if no more tags, copy and exit + if (-1 == code.substr(i).indexOf( '<' )) { - var node = $('

' + $(parent).html() + '

'); - $(parent).replaceWith(node); + 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('\n<\/script>/gi, ''); + + 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() + { + 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) { - var node = $('

' + $(parent).html() + '

'); - $(parent2).replaceWith(node); - this.setSelection(node[0], 0, node[0], 0); + this.opts.allowedTags.push(tags[i]); } - else + } + }, + 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) { - if (this.browser('msie')) + 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 = $('
').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(//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]); + } + + this.tidy.$div.find(replacement.join(',')).each($.proxy(function(n,s) + { + var tag = rTags[n]; + $(s).replaceWith(function() + { + var replaced = $('<' + tag + ' />').append($(this).contents()); + + for (var i = 0; i < this.attributes.length; i++) { - var node = $('
' + $(parent).html() + '
'); - $(parent).replaceWith(node); + replaced.attr(this.attributes[i].name, this.attributes[i].value); + } + + return replaced; + }); + + }, this)); + + return html; + }, + 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.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-\u200D\uFEFF]/g, ''); + text = text.replace(/ /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: '• ' + 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' + }, + unlink: + { + title: this.lang.get('unlink'), + func: 'link.unlink' + } + } + }, + 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.buttons, this)); + } + + }, + createContainer: function() + { + return $('
', '', '', '', '', '', '', '
', '
', '', '', '', '', '
', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']; - for (var i = 0; i < atags.length; ++i) - { - var aaa = atags[i]; - html = html.replace(new RegExp(aaa,'gi'),aaa+lb); - } - - return html; - }, - formatting: function(html) - { - html = this.formattingRemove(html); - - // empty tags - html = this.formattingEmptyTags(html); - - // add formatting before - html = this.formattingAddBefore(html); - - // add formatting after - html = this.formattingAddAfter(html); - - // indenting - html = this.formattingIndenting(html); - - return html; - }, - - // TOGGLE - toggle: function() - { - var html; - - if (this.opts.visual) - { - var height = this.$editor.innerHeight(); - - this.$editor.hide(); - this.$content.hide(); - - html = this.$editor.html(); - //html = $.trim(this.formatting(html)); - - this.$el.height(height).val(html).show().focus(); - - this.setBtnActive('html'); - this.opts.visual = false; - } - else - { - this.$el.hide(); - var html = this.$el.val(); - - //html = this.savePreCode(html); - - // clean up - //html = this.stripTags(html); - - // set code - this.$editor.html(html).show(); - this.$content.show(); - - if (this.$editor.html() === '') - { - this.setCode(this.opts.emptyHtml); - } - - this.$editor.focus(); - - this.setBtnInactive('html'); - this.opts.visual = true; - - this.observeImages(); - this.observeTables(); - } - }, - - // AUTOSAVE - autoSave: function() - { - this.autosaveInterval = setInterval($.proxy(function() - { - $.ajax({ - url: this.opts.autosave, - type: 'post', - data: this.$el.attr('name') + '=' + escape(encodeURIComponent(this.getCode())), - success: $.proxy(function(data) - { - // callback - if (typeof this.opts.autosaveCallback === 'function') + xhr.onerror = function() { - this.opts.autosaveCallback(data, this); - } + //setProgress(0, 'XHR error.'); + }; - }, this) - }); + xhr.upload.onprogress = function(e) + { + /* + if (e.lengthComputable) + { + var percentLoaded = Math.round((e.loaded / e.total) * 100); + setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.'); + } + */ + }; + xhr.setRequestHeader('Content-Type', file.type); + xhr.setRequestHeader('x-amz-acl', 'public-read'); - }, this), this.opts.interval*1000); + xhr.send(file); + } + } + }; }, - - // TOOLBAR - buildToolbar: function() + utils: function() { - if (this.opts.toolbar === false) - { - return false; - } - - this.$toolbar = $('
    ').addClass('redactor_toolbar'); - - if (this.opts.air) - { - $(this.air).append(this.$toolbar); - $('body').append(this.air); - } - else - { - if (this.opts.toolbarExternal === false) + return { + isMobile: function() { - this.$box.prepend(this.$toolbar); - } - else + return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent); + }, + isDesktop: function() { - $(this.opts.toolbarExternal).html(this.$toolbar); - } - } - - $.each(this.opts.buttons, $.proxy(function(i,key) - { - - if (key !== '|' && typeof this.opts.toolbar[key] !== 'undefined') + return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent); + }, + isString: function(obj) { - var s = this.opts.toolbar[key]; + return Object.prototype.toString.call(obj) == '[object String]'; + }, + isEmpty: function(html, removeEmptyTags) + { + html = html.replace(/[\u200B-\u200D\uFEFF]/g, ''); + html = html.replace(/ /gi, ''); + html = html.replace(/<\/?br\s?\/?>/g, ''); + html = html.replace(/\s/g, ''); + html = html.replace(/^

    [^\W\w\D\d]*?<\/p>$/i, ''); - if (this.opts.fileUpload === false && key === 'file') + // 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 $('

    ').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; } - this.$toolbar.append($('
  • ').append(this.buildButton(key, s))); - } - - - if (key === '|') - { - this.$toolbar.append($('
  • ')); - } - - }, this)); - - }, - buildButton: function(key, s) - { - var button = $('
    '); - - if (typeof s.func === 'undefined') - { - button.click($.proxy(function() - { - if ($.inArray(key, this.opts.activeButtons) != -1) + if ($el.attr(attr) === '') { - this.inactiveAllButtons(); - this.setBtnActive(key); + $el.removeAttr(attr); + return true; } - if (this.browser('mozilla')) - { - this.$editor.focus(); - //this.restoreSelection(); - } - - this.execCommand(s.exec, key); - - }, this)); - } - else if (s.func !== 'show') - { - button.click($.proxy(function(e) { - - this[s.func](e); - - }, this)); - } - - if (typeof s.callback !== 'undefined' && s.callback !== false) - { - button.click($.proxy(function(e) { s.callback(this, e, key); }, this)); - } - - // dropdown - if (key === 'backcolor' || key === 'fontcolor' || typeof(s.dropdown) !== 'undefined') - { - var dropdown = $('