diff --git a/app/controllers/admin/theme_assets_controller.rb b/app/controllers/admin/theme_assets_controller.rb index 2efa2a3d..80e00e61 100644 --- a/app/controllers/admin/theme_assets_controller.rb +++ b/app/controllers/admin/theme_assets_controller.rb @@ -7,6 +7,10 @@ module Admin assets = current_site.theme_assets.all @non_image_assets = assets.find_all { |a| a.stylesheet? || a.javascript? } @image_assets = assets.find_all { |a| a.image? } + + if request.xhr? + render :action => 'images', :layout => false + end end def new @@ -18,7 +22,17 @@ module Admin end def create - @asset = current_site.theme_assets.build(params[:theme_asset]) + # logger.debug "request = #{request.inspect}" + # logger.debug "file size = #{request.env['rack.input'].inspect}" + + # File.cp(request.env['rack.input'], '/Users/didier/Desktop/FOO') + + if params[:file] + # params[:theme_asset][:source] = request.env['rack.input'] + @asset = current_site.theme_assets.build(:source => params[:file]) + else + @asset = current_site.theme_assets.build(params[:theme_asset]) + end if @asset.save flash_success! diff --git a/app/models/theme_asset.rb b/app/models/theme_asset.rb index 1d462a2c..c488cc0a 100644 --- a/app/models/theme_asset.rb +++ b/app/models/theme_asset.rb @@ -64,6 +64,7 @@ class ThemeAsset def filename if not self.image? + # TODO: fix that for handling not image / stylesheets / javascripts assets "#{self.slug}.#{self.stylesheet? ? 'css' : 'js'}" else "#{self.slug}#{File.extname(self.source.file.original_filename)}" diff --git a/app/uploaders/asset_uploader.rb b/app/uploaders/asset_uploader.rb index aa230047..1adf706f 100644 --- a/app/uploaders/asset_uploader.rb +++ b/app/uploaders/asset_uploader.rb @@ -30,6 +30,8 @@ class AssetUploader < CarrierWave::Uploader::Base def set_content_type value = :other + content_type = File.mime_type?(original_filename) if file.content_type == 'application/octet-stream' + self.class.content_types.each_pair do |type, rules| rules.each do |rule| case rule diff --git a/app/uploaders/theme_asset_uploader.rb b/app/uploaders/theme_asset_uploader.rb index fcfd863a..437a4723 100644 --- a/app/uploaders/theme_asset_uploader.rb +++ b/app/uploaders/theme_asset_uploader.rb @@ -36,11 +36,13 @@ class ThemeAssetUploader < CarrierWave::Uploader::Base def set_content_type value = :other + content_type = File.mime_type?(original_filename) if file.content_type == 'application/octet-stream' + self.class.content_types.each_pair do |type, rules| rules.each do |rule| case rule - when String then value = type if file.content_type == rule - when Regexp then value = type if (file.content_type =~ rule) == 0 + when String then value = type if content_type == rule + when Regexp then value = type if (content_type =~ rule) == 0 end end end diff --git a/app/views/admin/shared/_head.html.haml b/app/views/admin/shared/_head.html.haml index 2043405b..f8d5fb61 100644 --- a/app/views/admin/shared/_head.html.haml +++ b/app/views/admin/shared/_head.html.haml @@ -6,7 +6,7 @@ = stylesheet_link_tag 'blueprint/screen', :media => 'screen' / [if IE] - = stylesheet_link_tag('blueprint/ie', :media => 'screen') + = stylesheet_link_tag 'blueprint/ie', :media => 'screen' = stylesheet_link_tag 'admin/layout', 'admin/plugins/toggle', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production? diff --git a/app/views/admin/theme_assets/_asset.html.haml b/app/views/admin/theme_assets/_asset.html.haml index 0d21bdd1..95f5047c 100644 --- a/app/views/admin/theme_assets/_asset.html.haml +++ b/app/views/admin/theme_assets/_asset.html.haml @@ -1,7 +1,11 @@ -%li{ :class => "asset #{'last' if (asset_counter + 1) % 6 == 0}"} - %h4= link_to truncate(asset.slug, :length => 22), edit_admin_theme_asset_path(asset) +- per_row ||= 6 +- edit_mode = defined?(edit).nil? ? true: edit + +%li{ :class => "asset #{'last' if (asset_counter + 1) % per_row == 0}"} + %h4= link_to truncate(asset.slug, :length => 22), edit_mode ? edit_admin_theme_asset_path(asset) : asset.source.url .image .inside = vignette_tag(asset) - .actions - = link_to image_tag('admin/list/icons/cross.png'), admin_theme_asset_path(asset), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete \ No newline at end of file + - if edit_mode + .actions + = link_to image_tag('admin/list/icons/cross.png'), admin_theme_asset_path(asset), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete \ No newline at end of file diff --git a/app/views/admin/theme_assets/_form.html.haml b/app/views/admin/theme_assets/_form.html.haml index 23f3e659..b289d20a 100644 --- a/app/views/admin/theme_assets/_form.html.haml +++ b/app/views/admin/theme_assets/_form.html.haml @@ -1,5 +1,6 @@ - content_for :head do - = javascript_include_tag 'admin/plugins/codemirror/codemirror', 'admin/theme_assets.js' + = javascript_include_tag 'admin/plugins/codemirror/codemirror', 'admin/plugins/fancybox', 'admin/plugins/plupload/plupload.full.js', 'admin/theme_assets.js' + = stylesheet_link_tag 'admin/plugins/fancybox', 'admin/box' = f.hidden_field :performing_plain_text @@ -22,12 +23,14 @@ = f.select :content_type, ["stylesheet", "javascript"] = f.custom_input :plain_text, :css => 'full', :with_label => false do - %code{ :class => (@asset.new_record? || (@asset.size && @asset.size > 40000) ? 'nude' : @asset.content_type) } + %code{ :class => (@asset.size && @asset.size > 40000 ? 'nude' : (@asset.content_type || 'stylesheet')) } = f.text_area :plain_text + %li.more + = link_to t('.picker_link'), admin_theme_assets_path, :id => 'asset-picker-link' %span.alt = t('admin.theme_assets.form.choose_file') - + - if @asset.image? = f.foldable_inputs :name => "#{t('formtastic.titles.preview')} #{image_dimensions_and_size(@asset)}", :class => 'preview' do %li diff --git a/app/views/admin/theme_assets/images.html.haml b/app/views/admin/theme_assets/images.html.haml new file mode 100644 index 00000000..2371c1af --- /dev/null +++ b/app/views/admin/theme_assets/images.html.haml @@ -0,0 +1,12 @@ +#theme-images.asset-picker + %h2= t('.title') + + .actions + = admin_button_tag t('admin.theme_assets.index.new'), admin_theme_assets_url(:json), :class => 'button small add', :id => 'upload-link' + + - if @image_assets.empty? + %p.no-items= t('.no_items') + - else + %ul.assets + = render :partial => 'asset', :collection => @image_assets, :locals => { :per_row => 3, :edit => false } + %li.clear \ No newline at end of file diff --git a/config/initializers/mongoid.rb b/config/initializers/mongoid.rb index 8144f097..c69f778d 100644 --- a/config/initializers/mongoid.rb +++ b/config/initializers/mongoid.rb @@ -29,7 +29,7 @@ module Mongoid #:nodoc: end end - Rails.logger.debug "conditions = #{conditions.inspect} / #{options[:scope].inspect}" + # Rails.logger.debug "conditions = #{conditions.inspect} / #{options[:scope].inspect}" return if document.class.where(conditions).empty? diff --git a/doc/TODO b/doc/TODO index baa05e8f..b2fcf22e 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,16 +1,21 @@ BOARD: -- theme assets - theme assets picker (???) - -- assets uploader: remove old files if new one + x lightbox (http://fancybox.net/api) + x select it + - flash upload (http://www.plupload.com/example_custom.php) - theme assets: disable version if not image +- refactor theme assets / assets uploaders BACKLOG: - devise messages in French - localize devise emails + +- cucumber features for admin pages - refactoring admin crud (pages + layouts + snippets) +- icons for mime type + - new types for custom field - file - boolean @@ -18,11 +23,14 @@ BACKLOG: - refactoring: CustomFields::CustomField => CustomFields::Field - optimization custom_fields: use dynamic class for a collection instead of modifying the metaclass each time we build an item +- deploy on Heroku + BUGS: - when assigning new layout, disabled parts show up :-( (js problem) NICE TO HAVE: - asset collections: custom resizing if image +- super_finder DONE: x admin layout @@ -91,4 +99,6 @@ x liquid rendering engine x contents pagination x how to disable a page part in layout ? (BUG) x non published page (redirect to 404 ?) -x refactoring page.rb => create module pagetree \ No newline at end of file +x refactoring page.rb => create module pagetree +! assets uploader: remove old files if new one (BUG non ) +x CodeMirror: switch js -> css -> js .... (http://marijn.haverbeke.nl/codemirror/manual.html) \ No newline at end of file diff --git a/lib/locomotive.rb b/lib/locomotive.rb index 8b8e0b01..e9bea03f 100644 --- a/lib/locomotive.rb +++ b/lib/locomotive.rb @@ -3,6 +3,7 @@ require 'locomotive/configuration' require 'locomotive/liquid' require 'locomotive/mongoid' + module Locomotive class << self diff --git a/lib/locomotive/mongoid.rb b/lib/locomotive/mongoid.rb index 3402c827..65ef9d20 100644 --- a/lib/locomotive/mongoid.rb +++ b/lib/locomotive/mongoid.rb @@ -1 +1,2 @@ -require 'locomotive/mongoid/document' \ No newline at end of file +require 'locomotive/mongoid/document' +require 'locomotive/mongoid/model_extensions' \ No newline at end of file diff --git a/public/images/admin/plugins/fancybox/blank.gif b/public/images/admin/plugins/fancybox/blank.gif new file mode 100755 index 00000000..35d42e80 Binary files /dev/null and b/public/images/admin/plugins/fancybox/blank.gif differ diff --git a/public/images/admin/plugins/fancybox/fancy_close.png b/public/images/admin/plugins/fancybox/fancy_close.png new file mode 100755 index 00000000..07035307 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_close.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_loading.png b/public/images/admin/plugins/fancybox/fancy_loading.png new file mode 100755 index 00000000..25030179 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_loading.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_nav_left.png b/public/images/admin/plugins/fancybox/fancy_nav_left.png new file mode 100755 index 00000000..ebaa6a4f Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_nav_left.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_nav_right.png b/public/images/admin/plugins/fancybox/fancy_nav_right.png new file mode 100755 index 00000000..873294e9 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_nav_right.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_e.png b/public/images/admin/plugins/fancybox/fancy_shadow_e.png new file mode 100755 index 00000000..2eda0893 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_e.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_n.png b/public/images/admin/plugins/fancybox/fancy_shadow_n.png new file mode 100755 index 00000000..69aa10e2 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_n.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_ne.png b/public/images/admin/plugins/fancybox/fancy_shadow_ne.png new file mode 100755 index 00000000..79f6980a Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_ne.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_nw.png b/public/images/admin/plugins/fancybox/fancy_shadow_nw.png new file mode 100755 index 00000000..7182cd93 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_nw.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_s.png b/public/images/admin/plugins/fancybox/fancy_shadow_s.png new file mode 100755 index 00000000..d8858bfb Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_s.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_se.png b/public/images/admin/plugins/fancybox/fancy_shadow_se.png new file mode 100755 index 00000000..541e3ffd Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_se.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_sw.png b/public/images/admin/plugins/fancybox/fancy_shadow_sw.png new file mode 100755 index 00000000..b451689f Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_sw.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_shadow_w.png b/public/images/admin/plugins/fancybox/fancy_shadow_w.png new file mode 100755 index 00000000..8a4e4a88 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_shadow_w.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_title_left.png b/public/images/admin/plugins/fancybox/fancy_title_left.png new file mode 100755 index 00000000..6049223d Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_title_left.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_title_main.png b/public/images/admin/plugins/fancybox/fancy_title_main.png new file mode 100755 index 00000000..8044271f Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_title_main.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_title_over.png b/public/images/admin/plugins/fancybox/fancy_title_over.png new file mode 100755 index 00000000..d9f458f4 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_title_over.png differ diff --git a/public/images/admin/plugins/fancybox/fancy_title_right.png b/public/images/admin/plugins/fancybox/fancy_title_right.png new file mode 100755 index 00000000..e36d9db2 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancy_title_right.png differ diff --git a/public/images/admin/plugins/fancybox/fancybox-x.png b/public/images/admin/plugins/fancybox/fancybox-x.png new file mode 100755 index 00000000..c2130f86 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancybox-x.png differ diff --git a/public/images/admin/plugins/fancybox/fancybox-y.png b/public/images/admin/plugins/fancybox/fancybox-y.png new file mode 100755 index 00000000..7ef399b9 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancybox-y.png differ diff --git a/public/images/admin/plugins/fancybox/fancybox.png b/public/images/admin/plugins/fancybox/fancybox.png new file mode 100755 index 00000000..65e14f68 Binary files /dev/null and b/public/images/admin/plugins/fancybox/fancybox.png differ diff --git a/public/javascripts/admin/application.js b/public/javascripts/admin/application.js index 2bc5faca..08bee1cd 100644 --- a/public/javascripts/admin/application.js +++ b/public/javascripts/admin/application.js @@ -21,12 +21,15 @@ $.growl.settings.dockCss = { var addCodeMirrorEditor = function(type, el, parser) { var parserfile = "parse" + type + ".js"; if (parser != undefined) parserfile = parser; - if (type == 'liquid') type = 'xml'; + // if (type == 'liquid') type = 'xml'; var editor = CodeMirror.fromTextArea(el.attr('id'), { height: "400px", parserfile: parserfile, - stylesheet: ["/stylesheets/admin/plugins/codemirror/" + type + "colors.css", "/stylesheets/admin/plugins/codemirror/liquidcolors.css"], + stylesheet: [ + "/stylesheets/admin/plugins/codemirror/csscolors.css", + "/stylesheets/admin/plugins/codemirror/javascriptcolors.css", + "/stylesheets/admin/plugins/codemirror/liquidcolors.css"], path: "/javascripts/admin/plugins/codemirror/", continuousScanning: 500, reindentOnLoad: true, diff --git a/public/javascripts/admin/plugins/codemirror/codemirror.js b/public/javascripts/admin/plugins/codemirror/codemirror.js index 97e2657b..f4de2207 100644 --- a/public/javascripts/admin/plugins/codemirror/codemirror.js +++ b/public/javascripts/admin/plugins/codemirror/codemirror.js @@ -33,6 +33,8 @@ var CodeMirror = (function(){ iframeClass: null, passDelay: 200, passTime: 50, + lineNumberDelay: 200, + lineNumberTime: 50, continuousScanning: false, saveFunction: null, onChange: null, @@ -41,7 +43,7 @@ var CodeMirror = (function(){ disableSpellcheck: true, textWrapping: true, readOnly: false, - width: "100%", + width: "", height: "300px", autoMatchParens: false, parserConfig: null, @@ -50,67 +52,49 @@ var CodeMirror = (function(){ activeTokens: null, cursorActivity: null, lineNumbers: false, - indentUnit: 2 + indentUnit: 2, + domain: null }); - function wrapLineNumberDiv(place) { - return function(node) { - var container = document.createElement("DIV"), - nums = document.createElement("DIV"), - scroller = document.createElement("DIV"); - container.style.position = "relative"; - nums.style.position = "absolute"; - nums.style.height = "100%"; - if (nums.style.setExpression) { - try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} - catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions - } - nums.style.top = "0px"; - nums.style.overflow = "hidden"; - place(container); - container.appendChild(node); - container.appendChild(nums); - scroller.className = "CodeMirror-line-numbers"; - nums.appendChild(scroller); + function addLineNumberDiv(container) { + var nums = document.createElement("DIV"), + scroller = document.createElement("DIV"); + nums.style.position = "absolute"; + nums.style.height = "100%"; + if (nums.style.setExpression) { + try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} + catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions } + nums.style.top = "0px"; + nums.style.overflow = "hidden"; + container.appendChild(nums); + scroller.className = "CodeMirror-line-numbers"; + nums.appendChild(scroller); + return nums; } - function applyLineNumbers(frame) { - var win = frame.contentWindow, doc = win.document, - nums = frame.nextSibling, scroller = nums.firstChild; + function frameHTML(options) { + if (typeof options.parserfile == "string") + options.parserfile = [options.parserfile]; + if (typeof options.stylesheet == "string") + options.stylesheet = [options.stylesheet]; - var nextNum = 1, barWidth = null; - function sizeBar() { - if (!frame.offsetWidth || !win.Editor) { - for (var cur = frame; cur.parentNode; cur = cur.parentNode) { - if (cur != document) { - clearInterval(sizeInterval); - return; - } - } - } - - if (nums.offsetWidth != barWidth) { - barWidth = nums.offsetWidth; - nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px"); - } - } - function update() { - var diff = 20 + Math.max(doc.body.offsetHeight, frame.offsetHeight) - scroller.offsetHeight; - for (var n = Math.ceil(diff / 10); n > 0; n--) { - var div = document.createElement("DIV"); - div.appendChild(document.createTextNode(nextNum++)); - scroller.appendChild(div); - } - nums.scrollTop = doc.body.scrollTop || doc.documentElement.scrollTop || 0; - } - sizeBar(); - update(); - win.addEventHandler(win, "scroll", update); - win.addEventHandler(win, "resize", update); - var sizeInterval = setInterval(sizeBar, 500); + var html = [""]; + // Hack to work around a bunch of IE8-specific problems. + html.push(""); + forEach(options.stylesheet, function(file) { + html.push(""); + }); + forEach(options.basefiles.concat(options.parserfile), function(file) { + html.push(""); - }); - html.push(""); - - var doc = this.win.document; - doc.open(); - doc.write(html.join("")); - doc.close(); + if (!options.domain || !internetExplorer) { + this.win.document.open(); + this.win.document.write(frameHTML(options)); + this.win.document.close(); + } } CodeMirror.prototype = { init: function() { if (this.options.initCallback) this.options.initCallback(this); - if (this.options.lineNumbers) applyLineNumbers(this.frame); + if (this.options.lineNumbers) this.activateLineNumbers(); if (this.options.reindentOnLoad) this.reindent(); }, getCode: function() {return this.editor.getCode();}, setCode: function(code) {this.editor.importCode(code);}, - selection: function() {return this.editor.selectedText();}, + selection: function() {this.focusIfIE(); return this.editor.selectedText();}, reindent: function() {this.editor.reindent();}, - reindentSelection: function() {this.editor.reindentSelection(null);}, + reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);}, + focusIfIE: function() { + // in IE, a lot of selection-related functionality only works when the frame is focused + if (this.win.select.ie_selection) this.focus(); + }, focus: function() { this.win.focus(); if (this.editor.selectionSnapshot) // IE hack - this.win.select.selectCoords(this.win, this.editor.selectionSnapshot); + this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot); }, replaceSelection: function(text) { this.focus(); @@ -192,8 +182,8 @@ var CodeMirror = (function(){ replaceChars: function(text, start, end) { this.editor.replaceChars(text, start, end); }, - getSearchCursor: function(string, fromCursor) { - return this.editor.getSearchCursor(string, fromCursor); + getSearchCursor: function(string, fromCursor, caseFold) { + return this.editor.getSearchCursor(string, fromCursor, caseFold); }, undo: function() {this.editor.history.undo();}, @@ -204,18 +194,76 @@ var CodeMirror = (function(){ grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);}, ungrabKeys: function() {this.editor.ungrabKeys();}, - setParser: function(name) {this.editor.setParser(name);}, - - cursorPosition: function(start) { - if (this.win.select.ie_selection) this.focus(); - return this.editor.cursorPosition(start); + setParser: function(name) { this.editor.setParser(name); }, + setSpellcheck: function(on) {this.win.document.body.spellcheck = on;}, + setStylesheet: function(names) { + if (typeof names === "string") names = [names]; + var activeStylesheets = {}; + var matchedNames = {}; + var links = this.win.document.getElementsByTagName("link"); + // Create hashes of active stylesheets and matched names. + // This is O(n^2) but n is expected to be very small. + for (var x = 0, link; link = links[x]; x++) { + if (link.rel.indexOf("stylesheet") !== -1) { + for (var y = 0; y < names.length; y++) { + var name = names[y]; + if (link.href.substring(link.href.length - name.length) === name) { + activeStylesheets[link.href] = true; + matchedNames[name] = true; + } + } + } + } + // Activate the selected stylesheets and disable the rest. + for (var x = 0, link; link = links[x]; x++) { + if (link.rel.indexOf("stylesheet") !== -1) { + link.disabled = !(link.href in activeStylesheets); + } + } + // Create any new stylesheets. + for (var y = 0; y < names.length; y++) { + var name = names[y]; + if (!(name in matchedNames)) { + var link = this.win.document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = name; + this.win.document.getElementsByTagName('head')[0].appendChild(link); + } + } }, + setTextWrapping: function(on) { + if (on == this.options.textWrapping) return; + this.win.document.body.style.whiteSpace = on ? "" : "nowrap"; + this.options.textWrapping = on; + if (this.lineNumbers) { + this.setLineNumbers(false); + this.setLineNumbers(true); + } + }, + setIndentUnit: function(unit) {this.win.indentUnit = unit;}, + setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;}, + setTabMode: function(mode) {this.options.tabMode = mode;}, + setLineNumbers: function(on) { + if (on && !this.lineNumbers) { + this.lineNumbers = addLineNumberDiv(this.wrapping); + this.activateLineNumbers(); + } + else if (!on && this.lineNumbers) { + this.wrapping.removeChild(this.lineNumbers); + this.wrapping.style.marginLeft = ""; + this.lineNumbers = null; + } + }, + + cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);}, firstLine: function() {return this.editor.firstLine();}, lastLine: function() {return this.editor.lastLine();}, nextLine: function(line) {return this.editor.nextLine(line);}, prevLine: function(line) {return this.editor.prevLine(line);}, lineContent: function(line) {return this.editor.lineContent(line);}, setLineContent: function(line, content) {this.editor.setLineContent(line, content);}, + removeLine: function(line){this.editor.removeLine(line);}, insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);}, selectLines: function(startLine, startOffset, endLine, endOffset) { this.win.focus(); @@ -235,14 +283,125 @@ var CodeMirror = (function(){ } return num; }, - - // Old number-based line interface - jumpToLine: function(n) { - this.selectLines(this.nthLine(n), 0); + jumpToLine: function(line) { + if (typeof line == "number") line = this.nthLine(line); + this.selectLines(line, 0); this.win.focus(); }, - currentLine: function() { - return this.lineNumber(this.cursorPosition().line); + currentLine: function() { // Deprecated, but still there for backward compatibility + return this.lineNumber(this.cursorLine()); + }, + cursorLine: function() { + return this.cursorPosition().line; + }, + + activateLineNumbers: function() { + var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body, + nums = this.lineNumbers, scroller = nums.firstChild, self = this; + var barWidth = null; + + function sizeBar() { + if (frame.offsetWidth == 0) return; + for (var root = frame; root.parentNode; root = root.parentNode); + if (!nums.parentNode || root != document || !win.Editor) { + // Clear event handlers (their nodes might already be collected, so try/catch) + try{clear();}catch(e){} + clearInterval(sizeInterval); + return; + } + + if (nums.offsetWidth != barWidth) { + barWidth = nums.offsetWidth; + nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px"); + } + } + function doScroll() { + nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0; + } + // Cleanup function, registered by nonWrapping and wrapping. + var clear = function(){}; + sizeBar(); + var sizeInterval = setInterval(sizeBar, 500); + + function nonWrapping() { + var nextNum = 1, pending; + function update() { + var target = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)); + var endTime = new Date().getTime() + self.options.lineNumberTime; + while (scroller.offsetHeight < target && (!scroller.firstChild || scroller.offsetHeight)) { + scroller.appendChild(document.createElement("DIV")); + scroller.lastChild.innerHTML = nextNum++; + if (new Date().getTime() > endTime) { + if (pending) clearTimeout(pending); + pending = setTimeout(update, self.options.lineNumberDelay); + break; + } + } + doScroll(); + } + var onScroll = win.addEventHandler(win, "scroll", update, true), + onResize = win.addEventHandler(win, "resize", update, true); + clear = function(){onScroll(); onResize(); if (pending) clearTimeout(pending);}; + update(); + } + function wrapping() { + var node, lineNum, next, pos; + + function addNum(n) { + if (!lineNum) lineNum = scroller.appendChild(document.createElement("DIV")); + lineNum.innerHTML = n; + pos = lineNum.offsetHeight + lineNum.offsetTop; + lineNum = lineNum.nextSibling; + } + function work() { + if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return; + + var endTime = new Date().getTime() + self.options.lineNumberTime; + while (node) { + addNum(next++); + for (; node && !win.isBR(node); node = node.nextSibling) { + var bott = node.offsetTop + node.offsetHeight; + while (scroller.offsetHeight && bott - 3 > pos) addNum(" "); + } + if (node) node = node.nextSibling; + if (new Date().getTime() > endTime) { + pending = setTimeout(work, self.options.lineNumberDelay); + return; + } + } + // While there are un-processed number DIVs, or the scroller is smaller than the frame... + var target = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)); + while (lineNum || (scroller.offsetHeight < target && (!scroller.firstChild || scroller.offsetHeight))) + addNum(next++); + doScroll(); + } + function start() { + doScroll(); + node = body.firstChild; + lineNum = scroller.firstChild; + pos = 0; + next = 1; + work(); + } + + start(); + var pending = null; + function update() { + if (pending) clearTimeout(pending); + if (self.editor.allClean()) start(); + else pending = setTimeout(update, 200); + } + self.updateNumbers = update; + var onScroll = win.addEventHandler(win, "scroll", doScroll, true), + onResize = win.addEventHandler(win, "resize", update, true); + clear = function(){ + if (pending) clearTimeout(pending); + if (self.updateNumbers == update) self.updateNumbers = null; + onScroll(); + onResize(); + }; + } + (this.options.textWrapping ? wrapping : nonWrapping)(); } }; diff --git a/public/javascripts/admin/plugins/codemirror/editor.js b/public/javascripts/admin/plugins/codemirror/editor.js index 58b1d4fe..39677793 100644 --- a/public/javascripts/admin/plugins/codemirror/editor.js +++ b/public/javascripts/admin/plugins/codemirror/editor.js @@ -4,6 +4,11 @@ * plain sequences of and
elements */ +var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); +var webkit = /AppleWebKit/.test(navigator.userAgent); +var safari = /Apple Computers, Inc/.test(navigator.vendor); +var gecko = /gecko\/(\d{8})/i.test(navigator.userAgent); + // Make sure a string does not contain two consecutive 'collapseable' // whitespace characters. function makeWhiteSpace(n) { @@ -80,13 +85,13 @@ var Editor = (function(){ if (text.length) leaving = false; result.push(node); } - else if (node.nodeName == "BR" && node.childNodes.length == 0) { + else if (isBR(node) && node.childNodes.length == 0) { leaving = true; result.push(node); } else { forEach(node.childNodes, simplifyNode); - if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) { + if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) { leaving = true; if (!atEnd || !top) result.push(doc.createElement("BR")); @@ -106,7 +111,7 @@ var Editor = (function(){ // See the story.html file for some short remarks about the use of // continuation-passing style in this iterator. function traverseDOM(start){ - function yield(value, c){cc = c; return value;} + function _yield(value, c){cc = c; return value;} function push(fun, arg, c){return function(){return fun(arg, c);};} function stop(){cc = stop; throw StopIteration;}; var cc = push(scanNode, start, stop); @@ -124,6 +129,11 @@ var Editor = (function(){ } var point = null; + // This an Opera-specific hack -- always insert an empty span + // between two BRs, because Opera's cursor code gets terribly + // confused when the cursor is between two BRs. + var afterBR = true; + // Insert a normalized node at the current point. If it is a text // node, wrap it in a , and give that span a currentText // property -- this is used to cache the nodeValue, because @@ -136,6 +146,12 @@ var Editor = (function(){ select.snapshotChanged(); part = makePartSpan(part, owner); text = part.currentText; + afterBR = false; + } + else { + if (afterBR && window.opera) + point(makePartSpan("", owner)); + afterBR = true; } part.dirty = true; nodeQueue.push(part); @@ -151,7 +167,7 @@ var Editor = (function(){ forEach(simplifyDOM(node, end), function(part) { toYield.push(insertPart(part)); }); - return yield(toYield.join(""), c); + return _yield(toYield.join(""), c); } // Check whether a node is a normalized element. @@ -173,11 +189,15 @@ var Editor = (function(){ if (partNode(node)){ nodeQueue.push(node); - return yield(node.currentText, c); + afterBR = false; + return _yield(node.currentText, c); } - else if (node.nodeName == "BR") { + else if (isBR(node)) { + if (afterBR && window.opera) + node.parentNode.insertBefore(makePartSpan("", owner), node); nodeQueue.push(node); - return yield("\n", c); + afterBR = true; + return _yield("\n", c); } else { var end = !node.nextSibling; @@ -195,23 +215,20 @@ var Editor = (function(){ // Determine the text size of a processed node. function nodeSize(node) { - if (node.nodeName == "BR") - return 1; - else - return node.currentText.length; + return isBR(node) ? 1 : node.currentText.length; } // Search backwards through the top-level nodes until the next BR or // the start of the frame. function startOfLine(node) { - while (node && node.nodeName != "BR") node = node.previousSibling; + while (node && !isBR(node)) node = node.previousSibling; return node; } function endOfLine(node, container) { if (!node) node = container.firstChild; - else if (node.nodeName == "BR") node = node.nextSibling; + else if (isBR(node)) node = node.nextSibling; - while (node && node.nodeName != "BR") node = node.nextSibling; + while (node && !isBR(node)) node = node.nextSibling; return node; } @@ -223,8 +240,10 @@ var Editor = (function(){ // indicating whether anything was found, and can be called again to // skip to the next find. Use the select and replace methods to // actually do something with the found locations. - function SearchCursor(editor, string, fromCursor) { + function SearchCursor(editor, string, fromCursor, caseFold) { this.editor = editor; + this.caseFold = caseFold; + if (caseFold) string = string.toLowerCase(); this.history = editor.history; this.history.commit(); @@ -252,7 +271,8 @@ var Editor = (function(){ // For one-line strings, searching can be done simply by calling // indexOf on the current line. function() { - var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string); + var line = cleanText(self.history.textAfter(self.line).slice(self.offset)); + var match = (self.caseFold ? line.toLowerCase() : line).indexOf(string); if (match > -1) return {from: {node: self.line, offset: self.offset + match}, to: {node: self.line, offset: self.offset + match + string.length}}; @@ -262,19 +282,21 @@ var Editor = (function(){ // end of the line and the last match starts at the start. function() { var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset)); - var match = firstLine.lastIndexOf(target[0]); + var match = (self.caseFold ? firstLine.toLowerCase() : firstLine).lastIndexOf(target[0]); if (match == -1 || match != firstLine.length - target[0].length) return false; var startOffset = self.offset + match; var line = self.history.nodeAfter(self.line); for (var i = 1; i < target.length - 1; i++) { - if (cleanText(self.history.textAfter(line)) != target[i]) + var lineText = cleanText(self.history.textAfter(line)); + if ((self.caseFold ? lineText.toLowerCase() : lineText) != target[i]) return false; line = self.history.nodeAfter(line); } - if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0) + var lastLine = cleanText(self.history.textAfter(line)); + if ((self.caseFold ? lastLine.toLowerCase() : lastLine).indexOf(target[target.length - 1]) != 0) return false; return {from: {node: self.line, offset: startOffset}, @@ -351,8 +373,7 @@ var Editor = (function(){ this.doc = document; var container = this.container = this.doc.body; this.win = window; - this.history = new History(container, options.undoDepth, options.undoDelay, - this, options.onChange); + this.history = new History(container, options.undoDepth, options.undoDelay, this); var self = this; if (!Editor.Parser) @@ -364,10 +385,8 @@ var Editor = (function(){ select.setCursorPos(container, {node: null, offset: 0}); this.dirty = []; - if (options.content) - this.importCode(options.content); - else // FF acts weird when the editable document is completely empty - container.appendChild(this.doc.createElement("BR")); + this.importCode(options.content || ""); + this.history.onChange = options.onChange; if (!options.readOnly) { if (options.continuousScanning !== false) { @@ -409,6 +428,11 @@ var Editor = (function(){ addEventHandler(document.body, "mouseup", cursorActivity); addEventHandler(document.body, "cut", cursorActivity); + // workaround for a gecko bug [?] where going forward and then + // back again breaks designmode (no more cursor) + if (gecko) + addEventHandler(this.win, "pagehide", function(){self.unloaded = true;}); + addEventHandler(document.body, "paste", function(event) { cursorActivity(); var text = null; @@ -418,15 +442,14 @@ var Editor = (function(){ } catch(e) {} if (text !== null) { - self.replaceSelection(text); event.stop(); + self.replaceSelection(text); + select.scrollToCursor(self.container); } }); - addEventHandler(document.body, "beforepaste", method(this, "reroutePasteEvent")); - if (this.options.autoMatchParens) - addEventHandler(document.body, "click", method(this, "scheduleParenBlink")); + addEventHandler(document.body, "click", method(this, "scheduleParenHighlight")); } else if (!options.textWrapping) { container.style.whiteSpace = "nowrap"; @@ -491,6 +514,16 @@ var Editor = (function(){ return startOfLine(line.previousSibling); }, + visibleLineCount: function() { + var line = this.container.firstChild; + while (line && isBR(line)) line = line.nextSibling; // BR heights are unreliable + if (!line) return false; + var innerHeight = (window.innerHeight + || document.documentElement.clientHeight + || document.body.clientHeight); + return Math.floor(innerHeight / line.offsetHeight); + }, + selectLines: function(startLine, startOffset, endLine, endOffset) { this.checkLine(startLine); var start = {node: startLine, offset: startOffset}, end = null; @@ -503,10 +536,9 @@ var Editor = (function(){ }, lineContent: function(line) { - this.checkLine(line); var accum = []; for (line = line ? line.nextSibling : this.container.firstChild; - line && line.nodeName != "BR"; line = line.nextSibling) + line && !isBR(line); line = line.nextSibling) accum.push(nodeText(line)); return cleanText(accum.join("")); }, @@ -520,6 +552,18 @@ var Editor = (function(){ this.scheduleHighlight(); }, + removeLine: function(line) { + var node = line ? line.nextSibling : this.container.firstChild; + while (node) { + var next = node.nextSibling; + removeElement(node); + if (isBR(node)) break; + node = next; + } + this.addDirtyNode(line); + this.scheduleHighlight(); + }, + insertIntoLine: function(line, position, content) { var before = null; if (position == "end") { @@ -531,7 +575,7 @@ var Editor = (function(){ before = cur; break; } - var text = (cur.innerText || cur.textContent || cur.nodeValue || ""); + var text = nodeText(cur); if (text.length > position) { before = cur.nextSibling; content = text.slice(0, position) + content + text.slice(position); @@ -586,13 +630,9 @@ var Editor = (function(){ reroutePasteEvent: function() { if (this.capturingPaste || window.opera) return; this.capturingPaste = true; - var te = parent.document.createElement("TEXTAREA"); - te.style.position = "absolute"; - te.style.left = "-500px"; - te.style.width = "10px"; - te.style.top = nodeTop(frameElement) + "px"; - parent.document.body.appendChild(te); + var te = window.frameElement.CodeMirror.textareaHack; parent.focus(); + te.value = ""; te.focus(); var self = this; @@ -600,10 +640,12 @@ var Editor = (function(){ self.capturingPaste = false; self.win.focus(); if (self.selectionSnapshot) // IE hack - self.win.select.selectCoords(self.win, self.selectionSnapshot); + self.win.select.setBookmark(self.container, self.selectionSnapshot); var text = te.value; - if (text) self.replaceSelection(text); - removeElement(te); + if (text) { + self.replaceSelection(text); + select.scrollToCursor(self.container); + } }, 10); }, @@ -618,8 +660,8 @@ var Editor = (function(){ offset: lastLine.length}; }, - getSearchCursor: function(string, fromCursor) { - return new SearchCursor(this, string, fromCursor); + getSearchCursor: function(string, fromCursor, caseFold) { + return new SearchCursor(this, string, fromCursor, caseFold); }, // Re-indent the whole buffer @@ -655,7 +697,7 @@ var Editor = (function(){ forEach(this.container.childNodes, function(n) { if (n.nodeType != 3) n.dirty = true; }); - this.addDirtyNode(this.firstChild); + this.addDirtyNode(this.container.firstChild); this.scheduleHighlight(); } }, @@ -663,7 +705,7 @@ var Editor = (function(){ // Intercept enter and tab, and assign their new functions. keyDown: function(event) { if (this.frozen == "leave") this.frozen = null; - if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) { + if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) { event.stop(); this.frozen(event); return; @@ -674,7 +716,7 @@ var Editor = (function(){ this.delayScanning(); // Schedule a paren-highlight event, if configured. if (this.options.autoMatchParens) - this.scheduleParenBlink(); + this.scheduleParenHighlight(); // The various checks for !altKey are there because AltGr sets both // ctrlKey and altKey to true, and should not be recognised as @@ -690,8 +732,8 @@ var Editor = (function(){ } event.stop(); } - else if (code == 9 && this.options.tabMode != "default") { // tab - this.handleTab(!event.ctrlKey && !event.shiftKey); + else if (code == 9 && this.options.tabMode != "default" && !event.ctrlKey) { // tab + this.handleTab(!event.shiftKey); event.stop(); } else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space @@ -699,11 +741,20 @@ var Editor = (function(){ event.stop(); } else if (code == 36 && !event.shiftKey && !event.ctrlKey) { // home - if (this.home()) - event.stop(); + if (this.home()) event.stop(); + } + else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end + if (this.end()) event.stop(); + } + // Only in Firefox is the default behavior for PgUp/PgDn correct. + else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp + if (this.pageUp()) event.stop(); + } + else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgDn + if (this.pageDown()) event.stop(); } else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ] - this.blinkParens(event.shiftKey); + this.highlightParens(event.shiftKey, true); event.stop(); } else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right @@ -730,6 +781,9 @@ var Editor = (function(){ this.options.saveFunction(); event.stop(); } + else if (internetExplorer && code == 86) { + this.reroutePasteEvent(); + } } }, @@ -741,7 +795,7 @@ var Editor = (function(){ // keydown event does not prevent the associated keypress event // from happening, so we have to cancel enter and tab again // here. - if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) || + if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) || event.code == 13 || (event.code == 9 && this.options.tabMode != "default") || (event.keyCode == 32 && event.shiftKey && this.options.tabMode == "default")) event.stop(); @@ -812,17 +866,17 @@ var Editor = (function(){ if (start) insertAfter(whiteSpace, start); else this.container.insertBefore(whiteSpace, this.container.firstChild); } - if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true); + var fromNode = firstText && (firstText.firstChild || firstText); + select.snapshotMove(fromNode, whiteSpace.firstChild, newIndent, false, true); } if (indentDiff != 0) this.addDirtyNode(start); - return whiteSpace; }, // Re-highlight the selected part of the document. highlightAtCursor: function() { var pos = select.selectionTopNode(this.container, true); var to = select.selectionTopNode(this.container, false); - if (pos === false || to === false) return; + if (pos === false || to === false) return false; select.markSelection(this.win); if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false) @@ -841,12 +895,14 @@ var Editor = (function(){ this.reindentSelection(direction); }, + // Custom home behaviour that doesn't land the cursor in front of + // leading whitespace unless pressed twice. home: function() { var cur = select.selectionTopNode(this.container, true), start = cur; - if (cur === false || !(!cur || cur.isPart || cur.nodeName == "BR") || !this.container.firstChild) + if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild) return false; - while (cur && cur.nodeName != "BR") cur = cur.previousSibling; + while (cur && !isBR(cur)) cur = cur.previousSibling; var next = cur ? cur.nextSibling : this.container.firstChild; if (next && next != start && next.isPart && hasClass(next, "whitespace")) select.focusAfterNode(next, this.container); @@ -857,18 +913,88 @@ var Editor = (function(){ return true; }, - // Delay (or initiate) the next paren blink event. - scheduleParenBlink: function() { + // Some browsers (Opera) don't manage to handle the end key + // properly in the face of vertical scrolling. + end: function() { + var cur = select.selectionTopNode(this.container, true); + if (cur === false) return false; + cur = endOfLine(cur, this.container); + if (!cur) return false; + select.focusAfterNode(cur.previousSibling, this.container); + select.scrollToCursor(this.container); + return true; + }, + + pageUp: function() { + var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount(); + if (line === false || scrollAmount === false) return false; + // Try to keep one line on the screen. + scrollAmount -= 2; + for (var i = 0; i < scrollAmount; i++) { + line = this.prevLine(line); + if (line === false) break; + } + if (i == 0) return false; // Already at first line + select.setCursorPos(this.container, {node: line, offset: 0}); + select.scrollToCursor(this.container); + return true; + }, + + pageDown: function() { + var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount(); + if (line === false || scrollAmount === false) return false; + // Try to move to the last line of the current page. + scrollAmount -= 2; + for (var i = 0; i < scrollAmount; i++) { + var nextLine = this.nextLine(line); + if (nextLine === false) break; + line = nextLine; + } + if (i == 0) return false; // Already at last line + select.setCursorPos(this.container, {node: line, offset: 0}); + select.scrollToCursor(this.container); + return true; + }, + + // Delay (or initiate) the next paren highlight event. + scheduleParenHighlight: function() { if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); var self = this; - this.parenEvent = this.parent.setTimeout(function(){self.blinkParens();}, 300); + this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300); }, // Take the token before the cursor. If it contains a character in // '()[]{}', search for the matching paren/brace/bracket, and // highlight them in green for a moment, or red if no proper match // was found. - blinkParens: function(jump) { + highlightParens: function(jump, fromKey) { + var self = this; + // give the relevant nodes a colour. + function highlight(node, ok) { + if (!node) return; + if (self.options.markParen) { + self.options.markParen(node, ok); + } + else { + node.style.fontWeight = "bold"; + node.style.color = ok ? "#8F8" : "#F88"; + } + } + function unhighlight(node) { + if (!node) return; + if (self.options.unmarkParen) { + self.options.unmarkParen(node); + } + else { + node.style.fontWeight = ""; + node.style.color = ""; + } + } + if (!fromKey && self.highlighted) { + unhighlight(self.highlighted[0]); + unhighlight(self.highlighted[1]); + } + if (!window.select) return; // Clear the event property. if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); @@ -886,7 +1012,7 @@ var Editor = (function(){ return /[\(\[\{]/.test(ch); } - var ch, self = this, cursor = select.selectionTopNode(this.container, true); + var ch, cursor = select.selectionTopNode(this.container, true); if (!cursor || !this.highlightAtCursor()) return; cursor = select.selectionTopNode(this.container, true); if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor))))) @@ -899,9 +1025,9 @@ var Editor = (function(){ // have to scan, we just try, and when we find dirty nodes we // abort, parse them, and re-try. function tryFindMatch() { - var stack = [], ch, ok = true;; + var stack = [], ch, ok = true; for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) { - if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) { + if (runner.className == className && isSpan(runner) && (ch = paren(runner))) { if (forward(ch) == dir) stack.push(ch); else if (!stack.length) @@ -910,18 +1036,12 @@ var Editor = (function(){ ok = false; if (!stack.length) break; } - else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") { + else if (runner.dirty || !isSpan(runner) && !isBR(runner)) { return {node: runner, status: "dirty"}; } } return {node: runner, status: runner && ok}; } - // Temporarily give the relevant nodes a colour. - function blink(node, ok) { - node.style.fontWeight = "bold"; - node.style.color = ok ? "#8F8" : "#F88"; - self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500); - } while (true) { var found = tryFindMatch(); @@ -933,11 +1053,14 @@ var Editor = (function(){ continue; } else { - blink(cursor, found.status); - if (found.node) { - blink(found.node, found.status); - if (jump) select.focusAfterNode(found.node.previousSibling, this.container); - } + highlight(cursor, found.status); + highlight(found.node, found.status); + if (fromKey) + self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500); + else + self.highlighted = [cursor, found.node]; + if (jump && found.node) + select.focusAfterNode(found.node.previousSibling, this.container); break; } } @@ -955,20 +1078,17 @@ var Editor = (function(){ // there's nothing to indent. if (cursor === false) return; - var lineStart = startOfLine(cursor); - var whiteSpace = this.indentLineAfter(lineStart, direction); - if (cursor == lineStart && whiteSpace) - cursor = whiteSpace; - // This means the indentation has probably messed up the cursor. - if (cursor == whiteSpace) - select.focusAfterNode(cursor, this.container); + select.markSelection(this.win); + this.indentLineAfter(startOfLine(cursor), direction); + select.selectMarked(); }, // Indent all lines whose start falls inside of the current // selection. indentRegion: function(start, end, direction) { var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling); - if (end.nodeName != "BR") end = endOfLine(end, this.container); + if (!isBR(end)) end = endOfLine(end, this.container); + this.addDirtyNode(start); do { var next = endOfLine(current, this.container); @@ -983,9 +1103,16 @@ var Editor = (function(){ // Find the node that the cursor is in, mark it as dirty, and make // sure a highlight pass is scheduled. cursorActivity: function(safe) { + // pagehide event hack above + if (this.unloaded) { + this.win.document.designMode = "off"; + this.win.document.designMode = "on"; + this.unloaded = false; + } + if (internetExplorer) { this.container.createTextRange().execCommand("unlink"); - this.selectionSnapshot = select.selectionCoords(this.win); + this.selectionSnapshot = select.getBookmark(this.container); } var activity = this.options.cursorActivity; @@ -1021,6 +1148,10 @@ var Editor = (function(){ this.dirty.push(node); }, + allClean: function() { + return !this.dirty.length; + }, + // Cause a highlight pass to happen in options.passDelay // milliseconds. Clear the existing timeout, if one exists. This // way, the passes do not happen while the user is typing, and @@ -1043,7 +1174,7 @@ var Editor = (function(){ // If the node has been coloured in the meantime, or is no // longer in the document, it should not be returned. while (found && found.parentNode != this.container) - found = found.parentNode + found = found.parentNode; if (found && (found.dirty || found.nodeType == 3)) return found; } catch (e) {} @@ -1060,7 +1191,7 @@ var Editor = (function(){ highlightDirty: function(force) { // Prevent FF from raising an error when it is firing timeouts // on a page that's no longer loaded. - if (!window.select) return; + if (!window.select) return false; if (!this.options.readOnly) select.markSelection(this.win); var start, endTime = force ? null : time() + this.options.passTime; @@ -1123,17 +1254,17 @@ var Editor = (function(){ var endTime = (typeof target == "number" ? target : null); if (!container.firstChild) - return; + return false; // Backtrack to the first node before from that has a partial // parse stored. while (from && (!from.parserFromHere || from.dirty)) { - if (maxBacktrack != null && from.nodeName == "BR" && (--maxBacktrack) < 0) + if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0) return false; from = from.previousSibling; } // If we are at the end of the document, do nothing. if (from && !from.nextSibling) - return; + return false; // Check whether a part ( node) and the corresponding token // match. @@ -1178,6 +1309,11 @@ var Editor = (function(){ stream = stringStream(traversal), parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream); + function surroundedByBRs(node) { + return (node.previousSibling == null || isBR(node.previousSibling)) && + (node.nextSibling == null || isBR(node.nextSibling)); + } + // parts is an interface to make it possible to 'delay' fetching // the next DOM node until we are completely done with the one // before it. This is necessary because often the next node is @@ -1208,13 +1344,23 @@ var Editor = (function(){ // Allow empty nodes when they are alone on a line, needed // for the FF cursor bug workaround (see select.js, // insertNewlineAtCursor). - while (part && part.nodeName == "SPAN" && part.currentText == "") { - var old = part; - this.remove(); - part = this.get(); - // Adjust selection information, if any. See select.js for details. - select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0); + while (part && isSpan(part) && part.currentText == "") { + // Leave empty nodes that are alone on a line alone in + // Opera, since that browsers doesn't deal well with + // having 2 BRs in a row. + if (window.opera && surroundedByBRs(part)) { + this.next(); + part = this.get(); + } + else { + var old = part; + this.remove(); + part = this.get(); + // Adjust selection information, if any. See select.js for details. + select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0); + } } + return part; } }; @@ -1230,7 +1376,7 @@ var Editor = (function(){ if (token.value == "\n"){ // The idea of the two streams actually staying synchronized // is such a long shot that we explicitly check. - if (part.nodeName != "BR") + if (!isBR(part)) throw "Parser out of sync. Expected BR."; if (part.dirty || !part.indentation) lineDirty = true; @@ -1258,7 +1404,7 @@ var Editor = (function(){ parts.next(); } else { - if (part.nodeName != "SPAN") + if (!isSpan(part)) throw "Parser out of sync. Expected SPAN."; if (part.dirty) lineDirty = true; @@ -1314,6 +1460,6 @@ var Editor = (function(){ addEventHandler(window, "load", function() { var CodeMirror = window.frameElement.CodeMirror; - CodeMirror.editor = new Editor(CodeMirror.options); + var e = CodeMirror.editor = new Editor(CodeMirror.options); this.parent.setTimeout(method(CodeMirror, "init"), 0); }); diff --git a/public/javascripts/admin/plugins/codemirror/highlight.js b/public/javascripts/admin/plugins/codemirror/highlight.js index f0de59cc..ac915122 100644 --- a/public/javascripts/admin/plugins/codemirror/highlight.js +++ b/public/javascripts/admin/plugins/codemirror/highlight.js @@ -34,7 +34,7 @@ var indentUnit = 2; } window.highlightText = function(string, callback, parser) { - var parser = (parser || Editor.Parser).make(stringStream(normaliseString(string))); + parser = (parser || Editor.Parser).make(stringStream(normaliseString(string))); var line = []; if (callback.nodeType == 1) { var node = callback; diff --git a/public/javascripts/admin/plugins/codemirror/mirrorframe.js b/public/javascripts/admin/plugins/codemirror/mirrorframe.js index 7f6ad1ab..3d237418 100644 --- a/public/javascripts/admin/plugins/codemirror/mirrorframe.js +++ b/public/javascripts/admin/plugins/codemirror/mirrorframe.js @@ -36,7 +36,7 @@ MirrorFrame.prototype = { var first = true; do { - var cursor = this.mirror.getSearchCursor(text, first); + var cursor = this.mirror.getSearchCursor(text, first, true); first = false; while (cursor.findNext()) { cursor.select(); diff --git a/public/javascripts/admin/plugins/codemirror/parsejavascript.js b/public/javascripts/admin/plugins/codemirror/parsejavascript.js index 756639a8..2c673ffe 100644 --- a/public/javascripts/admin/plugins/codemirror/parsejavascript.js +++ b/public/javascripts/admin/plugins/codemirror/parsejavascript.js @@ -10,6 +10,8 @@ var JSParser = Editor.Parser = (function() { // Token types that can be considered to be atoms. var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + // Setting that can be used to have JSON data indent properly. + var json = false; // Constructor for the lexical context objects. function JSLexical(indented, column, type, align, prev, info) { // indentation at start of this line @@ -58,7 +60,7 @@ var JSParser = Editor.Parser = (function() { // semicolon. Actions at the end of the stack go first. It is // initialized with an infinitely looping action that consumes // whole statements. - var cc = [statements]; + var cc = [json ? singleExpr : statements]; // Context contains information about the current local scope, the // variables defined in that, and the scopes above it. var context = null; @@ -224,6 +226,9 @@ var JSParser = Editor.Parser = (function() { function statements(type){ return pass(statement, statements); } + function singleExpr(type){ + return pass(expression, statements); + } // Dispatches various types of statements based on the type of the // current token. function statement(type){ @@ -282,7 +287,7 @@ var JSParser = Editor.Parser = (function() { if (type == ",") cont(what, proceed); else if (type == end) cont(); else cont(expect(end)); - }; + } return function commaSeparated(type) { if (type == end) cont(); else pass(what, proceed); @@ -337,5 +342,11 @@ var JSParser = Editor.Parser = (function() { return parser; } - return {make: parseJS, electricChars: "{}:"}; + return { + make: parseJS, + electricChars: "{}:", + configure: function(obj) { + if (obj.json != null) json = obj.json; + } + }; })(); diff --git a/public/javascripts/admin/plugins/codemirror/parsexml.js b/public/javascripts/admin/plugins/codemirror/parsexml.js index 95a80993..994efd3b 100644 --- a/public/javascripts/admin/plugins/codemirror/parsexml.js +++ b/public/javascripts/admin/plugins/codemirror/parsexml.js @@ -125,11 +125,11 @@ var XMLParser = Editor.Parser = (function() { // parseJavaScript in parsejavascript.js (there is actually a bit more // shared code than I'd like), but it is quite a bit simpler. function parseXML(source) { - var tokens = tokenizeXML(source); + var tokens = tokenizeXML(source), token; var cc = [base]; var tokenNr = 0, indented = 0; var currentTag = null, context = null; - var consume, marked; + var consume; function push(fs) { for (var i = fs.length - 1; i >= 0; i--) @@ -144,13 +144,13 @@ var XMLParser = Editor.Parser = (function() { consume = false; } - function mark(style) { - marked = style; + function markErr() { + token.style += " xml-error"; } function expect(text) { return function(style, content) { if (content == text) cont(); - else mark("xml-error") || cont(arguments.callee); + else {markErr(); cont(arguments.callee);} }; } @@ -192,12 +192,12 @@ var XMLParser = Editor.Parser = (function() { cont(); } else if (harmlessTokens.hasOwnProperty(style)) cont(); - else mark("xml-error") || cont(); + else {markErr(); cont();} } function tagname(style, content) { if (style == "xml-name") { currentTag = content.toLowerCase(); - mark("xml-tagname"); + token.style = "xml-tagname"; cont(); } else { @@ -206,24 +206,22 @@ var XMLParser = Editor.Parser = (function() { } } function closetagname(style, content) { - if (style == "xml-name" && context && content.toLowerCase() == context.name) { - popContext(); - mark("xml-tagname"); - } - else { - mark("xml-error"); + if (style == "xml-name") { + token.style = "xml-tagname"; + if (context && content.toLowerCase() == context.name) popContext(); + else markErr(); } cont(); } function endtag(startOfLine) { return function(style, content) { if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); - else if (content == ">") pushContext(currentTag, startOfLine) || cont(); - else mark("xml-error") || cont(arguments.callee); + else if (content == ">") {pushContext(currentTag, startOfLine); cont();} + else {markErr(); cont(arguments.callee);} }; } function attributes(style) { - if (style == "xml-name") mark("xml-attname") || cont(attribute, attributes); + if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);} else pass(); } function attribute(style, content) { @@ -240,7 +238,7 @@ var XMLParser = Editor.Parser = (function() { indentation: function() {return indented;}, next: function(){ - var token = tokens.next(); + token = tokens.next(); if (token.style == "whitespace" && tokenNr == 0) indented = token.value.length; else @@ -254,13 +252,9 @@ var XMLParser = Editor.Parser = (function() { return token; while(true){ - consume = marked = false; + consume = false; cc.pop()(token.style, token.content); - if (consume){ - if (marked) - token.style = marked; - return token; - } + if (consume) return token; } }, diff --git a/public/javascripts/admin/plugins/codemirror/select.js b/public/javascripts/admin/plugins/codemirror/select.js index d513ba5f..7beaf466 100644 --- a/public/javascripts/admin/plugins/codemirror/select.js +++ b/public/javascripts/admin/plugins/codemirror/select.js @@ -37,7 +37,7 @@ var select = {}; atEnd = !element.nextSibling || !element.nextSibling.nextSibling || !element.nextSibling.nextSibling.nextSibling; // In Opera (and recent Webkit versions), BR elements *always* - // have a scrollTop property of zero. + // have a offsetTop property of zero. var compensateHack = 0; while (element && !element.offsetTop) { compensateHack++; @@ -52,7 +52,7 @@ var select = {}; while (pos && pos.offsetParent) { y += pos.offsetTop; // Don't count X offset for
nodes - if (pos.nodeName != "BR") + if (!isBR(pos)) x += pos.offsetLeft; pos = pos.offsetParent; } @@ -66,7 +66,7 @@ var select = {}; scroll = true; } if (screen_y < 0 || atEnd || screen_y > (win.innerHeight || html.clientHeight || 0) - 50) { - scroll_y = atEnd ? 1e10 : y; + scroll_y = atEnd ? 1e6 : y; scroll = true; } if (scroll) win.scrollTo(scroll_x, scroll_y); @@ -247,13 +247,17 @@ var select = {}; } if (cur) { try{range.moveToElementText(cur);} - catch(e){} + catch(e){return false;} range.collapse(false); } else range.moveToElementText(node.parentNode); if (count) range.move("character", count); } - else range.moveToElementText(node); + else { + try{range.moveToElementText(node);} + catch(e){return false;} + } + return true; } // Do a binary search through the container object, comparing @@ -262,7 +266,7 @@ var select = {}; while (start < end) { var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle]; if (!node) return false; // Don't ask. IE6 manages this sometimes. - moveToNodeStart(range2, node); + if (!moveToNodeStart(range2, node)) return false; if (range.compareEndPoints("StartToStart", range2) == 1) start = middle; else @@ -314,7 +318,7 @@ var select = {}; if (!selection) return null; var topNode = select.selectionTopNode(container, start); - while (topNode && topNode.nodeName != "BR") + while (topNode && !isBR(topNode)) topNode = topNode.previousSibling; var range = selection.createRange(), range2 = range.duplicate(); @@ -356,32 +360,15 @@ var select = {}; } // Some hacks for storing and re-storing the selection when the editor loses and regains focus. - select.selectionCoords = function (win) { - var selection = win.document.selection; - if (!selection) return null; - var start = selection.createRange(), end = start.duplicate(); - start.collapse(true); - end.collapse(false); - - var body = win.document.body; - return {start: {x: start.boundingLeft + body.scrollLeft - 1, - y: start.boundingTop + body.scrollTop}, - end: {x: end.boundingLeft + body.scrollLeft - 1, - y: end.boundingTop + body.scrollTop}}; + select.getBookmark = function (container) { + var from = select.cursorPos(container, true), to = select.cursorPos(container, false); + if (from && to) return {from: from, to: to}; }; // Restore a stored selection. - select.selectCoords = function(win, coords) { - if (!coords) return; - - var range1 = win.document.body.createTextRange(), range2 = range1.duplicate(); - // This can fail for various hard-to-handle reasons. - try { - range1.moveToPoint(coords.start.x, coords.start.y); - range2.moveToPoint(coords.end.x, coords.end.y); - range1.setEndPoint("EndToStart", range2); - range1.select(); - } catch(e) {} + select.setBookmark = function(container, mark) { + if (!mark) return; + select.setCursorPos(container, mark.from, mark.to); }; } // W3C model @@ -407,7 +394,7 @@ var select = {}; // ancestors with a suitable offset. This goes down the DOM tree // until a 'leaf' is reached (or is it *up* the DOM tree?). function normalize(point){ - while (point.node.nodeType != 3 && point.node.nodeName != "BR") { + while (point.node.nodeType != 3 && !isBR(point.node)) { var newNode = point.node.childNodes[point.offset] || point.node.nextSibling; point.offset = 0; while (!newNode && point.node.parentNode) { @@ -425,8 +412,17 @@ var select = {}; }; select.selectMarked = function () { - if (!currentSelection || !currentSelection.changed) return; - var win = currentSelection.window, range = win.document.createRange(); + var cs = currentSelection; + // on webkit-based browsers, it is apparently possible that the + // selection gets reset even when a node that is not one of the + // endpoints get messed with. the most common situation where + // this occurs is when a selection is deleted or overwitten. we + // check for that here. + function focusIssue() { + return cs.start.node == cs.end.node && cs.start.offset == 0 && cs.end.offset == 0; + } + if (!cs || !(cs.changed || (webkit && focusIssue()))) return; + var win = cs.window, range = win.document.createRange(); function setPoint(point, which) { if (point.node) { @@ -442,8 +438,8 @@ var select = {}; } } - setPoint(currentSelection.end, "End"); - setPoint(currentSelection.start, "Start"); + setPoint(cs.end, "End"); + setPoint(cs.start, "Start"); selectRange(range, win); }; @@ -452,7 +448,7 @@ var select = {}; var selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); - }; + } function selectionRange(window) { var selection = window.getSelection(); if (!selection || selection.rangeCount == 0) @@ -471,7 +467,7 @@ var select = {}; var offset = start ? range.startOffset : range.endOffset; // Work around (yet another) bug in Opera's selection model. if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 && - container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR") + container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset])) offset--; // For text nodes, we look at the node itself if the cursor is @@ -486,7 +482,7 @@ var select = {}; // Occasionally, browsers will return the HTML node as // selection. If the offset is 0, we take the start of the frame // ('after null'), otherwise, we take the last node. - else if (node.nodeName == "HTML") { + else if (node.nodeName.toUpperCase() == "HTML") { return (offset == 1 ? null : container.lastChild); } // If the given node is our 'container', we just look up the @@ -557,7 +553,7 @@ var select = {}; if (!range) return; var topNode = select.selectionTopNode(container, start); - while (topNode && topNode.nodeName != "BR") + while (topNode && !isBR(topNode)) topNode = topNode.previousSibling; range = range.cloneRange(); @@ -574,13 +570,17 @@ var select = {}; range = win.document.createRange(); function setPoint(node, offset, side) { + if (offset == 0 && node && !node.nextSibling) { + range["set" + side + "After"](node); + return true; + } + if (!node) node = container.firstChild; else node = node.nextSibling; - if (!node) - return; + if (!node) return; if (offset == 0) { range["set" + side + "Before"](node); diff --git a/public/javascripts/admin/plugins/codemirror/stringstream.js b/public/javascripts/admin/plugins/codemirror/stringstream.js index 8c1c0422..4f5bc611 100644 --- a/public/javascripts/admin/plugins/codemirror/stringstream.js +++ b/public/javascripts/admin/plugins/codemirror/stringstream.js @@ -14,7 +14,7 @@ // Make a stringstream stream out of an iterator that returns strings. // This is applied to the result of traverseDOM (see codemirror.js), // and the resulting stream is fed to the parser. -window.stringStream = function(source){ +var stringStream = function(source){ // String that's currently being iterated over. var current = ""; // Position in that string. diff --git a/public/javascripts/admin/plugins/codemirror/tokenizejavascript.js b/public/javascripts/admin/plugins/codemirror/tokenizejavascript.js index f55dfce9..019136fc 100644 --- a/public/javascripts/admin/plugins/codemirror/tokenizejavascript.js +++ b/public/javascripts/admin/plugins/codemirror/tokenizejavascript.js @@ -5,7 +5,6 @@ var tokenizeJavaScript = (function() { // backslash) is encountered, or the end of the line is reached. function nextUntilUnescaped(source, end) { var escaped = false; - var next; while (!source.endOfLine()) { var next = source.next(); if (next == end && !escaped) @@ -48,7 +47,7 @@ var tokenizeJavaScript = (function() { }(); // Some helper regexps - var isOperatorChar = /[+\-*&%\/=<>!?|]/; + var isOperatorChar = /[+\-*&%=<>!?|]/; var isHexDigit = /[0-9A-Fa-f]/; var isWordChar = /[\w\$_]/; @@ -66,7 +65,7 @@ var tokenizeJavaScript = (function() { }; } - // The token reader, inteded to be used by the tokenizer from + // The token reader, intended to be used by the tokenizer from // tokenize.js (through jsTokenState). Advances the source stream // over a token, and returns an object containing the type and style // of that token. diff --git a/public/javascripts/admin/plugins/codemirror/undo.js b/public/javascripts/admin/plugins/codemirror/undo.js index 5f717a9e..8e021808 100644 --- a/public/javascripts/admin/plugins/codemirror/undo.js +++ b/public/javascripts/admin/plugins/codemirror/undo.js @@ -26,11 +26,10 @@ // delay (of no input) after which it commits a set of changes, and, // unfortunately, the 'parent' window -- a window that is not in // designMode, and on which setTimeout works in every browser. -function History(container, maxDepth, commitDelay, editor, onChange) { +function History(container, maxDepth, commitDelay, editor) { this.container = container; this.maxDepth = maxDepth; this.commitDelay = commitDelay; this.editor = editor; this.parent = editor.parent; - this.onChange = onChange; // This line object represents the initial, empty editor. var initial = {text: "", from: null, to: null}; // As the borders between lines are represented by BR elements, the @@ -73,7 +72,7 @@ History.prototype = { // shadow in the redo history. var item = this.history.pop(); this.redoHistory.push(this.updateTo(item, "applyChain")); - if (this.onChange) this.onChange(); + this.notifyEnvironment(); return this.chainNode(item); } }, @@ -85,7 +84,7 @@ History.prototype = { // The inverse of undo, basically. var item = this.redoHistory.pop(); this.addUndoLevel(this.updateTo(item, "applyChain")); - if (this.onChange) this.onChange(); + this.notifyEnvironment(); return this.chainNode(item); } }, @@ -109,6 +108,7 @@ History.prototype = { from = end; } this.pushChains([chain], from == null && to == null); + this.notifyEnvironment(); }, pushChains: function(chains, doNotHighlight) { @@ -162,7 +162,7 @@ History.prototype = { if (chains.length) { this.addUndoLevel(this.updateTo(chains, "linkChain")); this.redoHistory = []; - if (this.onChange) this.onChange(); + this.notifyEnvironment(); } }, @@ -191,6 +191,13 @@ History.prototype = { this.editor.scheduleHighlight(); }, + notifyEnvironment: function() { + // Used by the line-wrapping line-numbering code. + if (window.frameElement && window.frameElement.CodeMirror.updateNumbers) + window.frameElement.CodeMirror.updateNumbers(); + if (this.onChange) this.onChange(); + }, + // Link a chain into the DOM nodes (or the first/last links for null // nodes). linkChain: function(chain) { @@ -250,7 +257,7 @@ History.prototype = { function buildLine(node) { var text = []; for (var cur = node ? node.nextSibling : self.container.firstChild; - cur && cur.nodeName != "BR"; cur = cur.nextSibling) + cur && !isBR(cur); cur = cur.nextSibling) if (cur.currentText) text.push(cur.currentText); return {from: node, to: cur, text: cleanText(text.join(""))}; } @@ -275,7 +282,7 @@ History.prototype = { // Get the BR element after/before the given node. function nextBR(node, dir) { var link = dir + "Sibling", search = node[link]; - while (search && search.nodeName != "BR") + while (search && !isBR(search)) search = search[link]; return search; } diff --git a/public/javascripts/admin/plugins/codemirror/util.js b/public/javascripts/admin/plugins/codemirror/util.js index 4fb47240..c7021c24 100644 --- a/public/javascripts/admin/plugins/codemirror/util.js +++ b/public/javascripts/admin/plugins/codemirror/util.js @@ -1,9 +1,5 @@ /* A few useful utility functions. */ -var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); -var webkit = /AppleWebKit/.test(navigator.userAgent); -var safari = /Apple Computers, Inc/.test(navigator.vendor); - // Capture a method on an object. function method(obj, name) { return function() {obj[name].apply(obj, arguments);}; @@ -112,7 +108,7 @@ function addEventHandler(node, type, handler, removeFunc) { } function nodeText(node) { - return node.innerText || node.textContent || node.nodeValue || ""; + return node.textContent || node.innerText || node.nodeValue || ""; } function nodeTop(node) { @@ -123,3 +119,12 @@ function nodeTop(node) { } return top; } + +function isBR(node) { + var nn = node.nodeName; + return nn == "BR" || nn == "br"; +} +function isSpan(node) { + var nn = node.nodeName; + return nn == "SPAN" || nn == "span"; +} diff --git a/public/javascripts/admin/plugins/fancybox.js b/public/javascripts/admin/plugins/fancybox.js new file mode 100755 index 00000000..688f93aa --- /dev/null +++ b/public/javascripts/admin/plugins/fancybox.js @@ -0,0 +1,1077 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * + * Version: 1.3.1 (05/03/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +(function($) { + + var tmp, loading, overlay, wrap, outer, inner, close, nav_left, nav_right, + + selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [], + + ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i, + + loadingTimer, loadingFrame = 1, + + start_pos, final_pos, busy = false, shadow = 20, fx = $.extend($('
')[0], { prop: 0 }), titleh = 0, + + isIE6 = !$.support.opacity && !window.XMLHttpRequest, + + /* + * Private methods + */ + + fancybox_abort = function() { + loading.hide(); + + imgPreloader.onerror = imgPreloader.onload = null; + + if (ajaxLoader) { + ajaxLoader.abort(); + } + + tmp.empty(); + }, + + fancybox_error = function() { + $.fancybox('

The requested content cannot be loaded.
Please try again later.

', { + 'scrolling' : 'no', + 'padding' : 20, + 'transitionIn' : 'none', + 'transitionOut' : 'none' + }); + }, + + fancybox_get_viewport = function() { + return [ $(window).width(), $(window).height(), $(document).scrollLeft(), $(document).scrollTop() ]; + }, + + fancybox_get_zoom_to = function () { + var view = fancybox_get_viewport(), + to = {}, + + margin = currentOpts.margin, + resize = currentOpts.autoScale, + + horizontal_space = (shadow + margin) * 2, + vertical_space = (shadow + margin) * 2, + double_padding = (currentOpts.padding * 2), + + ratio; + + if (currentOpts.width.toString().indexOf('%') > -1) { + to.width = ((view[0] * parseFloat(currentOpts.width)) / 100) - (shadow * 2) ; + resize = false; + + } else { + to.width = currentOpts.width + double_padding; + } + + if (currentOpts.height.toString().indexOf('%') > -1) { + to.height = ((view[1] * parseFloat(currentOpts.height)) / 100) - (shadow * 2); + resize = false; + + } else { + to.height = currentOpts.height + double_padding; + } + + if (resize && (to.width > (view[0] - horizontal_space) || to.height > (view[1] - vertical_space))) { + if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') { + horizontal_space += double_padding; + vertical_space += double_padding; + + ratio = Math.min(Math.min( view[0] - horizontal_space, currentOpts.width) / currentOpts.width, Math.min( view[1] - vertical_space, currentOpts.height) / currentOpts.height); + + to.width = Math.round(ratio * (to.width - double_padding)) + double_padding; + to.height = Math.round(ratio * (to.height - double_padding)) + double_padding; + + } else { + to.width = Math.min(to.width, (view[0] - horizontal_space)); + to.height = Math.min(to.height, (view[1] - vertical_space)); + } + } + + to.top = view[3] + ((view[1] - (to.height + (shadow * 2 ))) * 0.5); + to.left = view[2] + ((view[0] - (to.width + (shadow * 2 ))) * 0.5); + + if (currentOpts.autoScale === false) { + to.top = Math.max(view[3] + margin, to.top); + to.left = Math.max(view[2] + margin, to.left); + } + + return to; + }, + + fancybox_format_title = function(title) { + if (title && title.length) { + switch (currentOpts.titlePosition) { + case 'inside': + return title; + case 'over': + return '' + title + ''; + default: + return '' + title + ''; + } + } + + return false; + }, + + fancybox_process_title = function() { + var title = currentOpts.title, + width = final_pos.width - (currentOpts.padding * 2), + titlec = 'fancybox-title-' + currentOpts.titlePosition; + + $('#fancybox-title').remove(); + + titleh = 0; + + if (currentOpts.titleShow === false) { + return; + } + + title = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(title, currentArray, currentIndex, currentOpts) : fancybox_format_title(title); + + if (!title || title === '') { + return; + } + + $('
').css({ + 'width' : width, + 'paddingLeft' : currentOpts.padding, + 'paddingRight' : currentOpts.padding + }).html(title).appendTo('body'); + + switch (currentOpts.titlePosition) { + case 'inside': + titleh = $("#fancybox-title").outerHeight(true) - currentOpts.padding; + final_pos.height += titleh; + break; + + case 'over': + $('#fancybox-title').css('bottom', currentOpts.padding); + break; + + default: + $('#fancybox-title').css('bottom', $("#fancybox-title").outerHeight(true) * -1); + break; + } + + $('#fancybox-title').appendTo( outer ).hide(); + }, + + fancybox_set_navigation = function() { + $(document).unbind('keydown.fb').bind('keydown.fb', function(e) { + if (e.keyCode == 27 && currentOpts.enableEscapeButton) { + e.preventDefault(); + $.fancybox.close(); + + } else if (e.keyCode == 37) { + e.preventDefault(); + $.fancybox.prev(); + + } else if (e.keyCode == 39) { + e.preventDefault(); + $.fancybox.next(); + } + }); + + if ($.fn.mousewheel) { + wrap.unbind('mousewheel.fb'); + + if (currentArray.length > 1) { + wrap.bind('mousewheel.fb', function(e, delta) { + e.preventDefault(); + + if (busy || delta === 0) { + return; + } + + if (delta > 0) { + $.fancybox.prev(); + } else { + $.fancybox.next(); + } + }); + } + } + + if (!currentOpts.showNavArrows) { return; } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) { + nav_left.show(); + } + + if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) { + nav_right.show(); + } + }, + + fancybox_preload_images = function() { + var href, + objNext; + + if ((currentArray.length -1) > currentIndex) { + href = currentArray[ currentIndex + 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + + if (currentIndex > 0) { + href = currentArray[ currentIndex - 1 ].href; + + if (typeof href !== 'undefined' && href.match(imgRegExp)) { + objNext = new Image(); + objNext.src = href; + } + } + }, + + _finish = function () { + inner.css('overflow', (currentOpts.scrolling == 'auto' ? (currentOpts.type == 'image' || currentOpts.type == 'iframe' || currentOpts.type == 'swf' ? 'hidden' : 'auto') : (currentOpts.scrolling == 'yes' ? 'auto' : 'visible'))); + + if (!$.support.opacity) { + inner.get(0).style.removeAttribute('filter'); + wrap.get(0).style.removeAttribute('filter'); + } + + $('#fancybox-title').show(); + + if (currentOpts.hideOnContentClick) { + inner.one('click', $.fancybox.close); + } + if (currentOpts.hideOnOverlayClick) { + overlay.one('click', $.fancybox.close); + } + + if (currentOpts.showCloseButton) { + close.show(); + } + + fancybox_set_navigation(); + + $(window).bind("resize.fb", $.fancybox.center); + + if (currentOpts.centerOnScroll) { + $(window).bind("scroll.fb", $.fancybox.center); + } else { + $(window).unbind("scroll.fb"); + } + + if ($.isFunction(currentOpts.onComplete)) { + currentOpts.onComplete(currentArray, currentIndex, currentOpts); + } + + busy = false; + + fancybox_preload_images(); + }, + + fancybox_draw = function(pos) { + var width = Math.round(start_pos.width + (final_pos.width - start_pos.width) * pos), + height = Math.round(start_pos.height + (final_pos.height - start_pos.height) * pos), + + top = Math.round(start_pos.top + (final_pos.top - start_pos.top) * pos), + left = Math.round(start_pos.left + (final_pos.left - start_pos.left) * pos); + + wrap.css({ + 'width' : width + 'px', + 'height' : height + 'px', + 'top' : top + 'px', + 'left' : left + 'px' + }); + + width = Math.max(width - currentOpts.padding * 2, 0); + height = Math.max(height - (currentOpts.padding * 2 + (titleh * pos)), 0); + + inner.css({ + 'width' : width + 'px', + 'height' : height + 'px' + }); + + if (typeof final_pos.opacity !== 'undefined') { + wrap.css('opacity', (pos < 0.5 ? 0.5 : pos)); + } + }, + + fancybox_get_obj_pos = function(obj) { + var pos = obj.offset(); + + pos.top += parseFloat( obj.css('paddingTop') ) || 0; + pos.left += parseFloat( obj.css('paddingLeft') ) || 0; + + pos.top += parseFloat( obj.css('border-top-width') ) || 0; + pos.left += parseFloat( obj.css('border-left-width') ) || 0; + + pos.width = obj.width(); + pos.height = obj.height(); + + return pos; + }, + + fancybox_get_zoom_from = function() { + var orig = selectedOpts.orig ? $(selectedOpts.orig) : false, + from = {}, + pos, + view; + + if (orig && orig.length) { + pos = fancybox_get_obj_pos(orig); + + from = { + width : (pos.width + (currentOpts.padding * 2)), + height : (pos.height + (currentOpts.padding * 2)), + top : (pos.top - currentOpts.padding - shadow), + left : (pos.left - currentOpts.padding - shadow) + }; + + } else { + view = fancybox_get_viewport(); + + from = { + width : 1, + height : 1, + top : view[3] + view[1] * 0.5, + left : view[2] + view[0] * 0.5 + }; + } + + return from; + }, + + fancybox_show = function() { + loading.hide(); + + if (wrap.is(":visible") && $.isFunction(currentOpts.onCleanup)) { + if (currentOpts.onCleanup(currentArray, currentIndex, currentOpts) === false) { + $.event.trigger('fancybox-cancel'); + + busy = false; + return; + } + } + + currentArray = selectedArray; + currentIndex = selectedIndex; + currentOpts = selectedOpts; + + inner.get(0).scrollTop = 0; + inner.get(0).scrollLeft = 0; + + if (currentOpts.overlayShow) { + if (isIE6) { + $('select:not(#fancybox-tmp select)').filter(function() { + return this.style.visibility !== 'hidden'; + }).css({'visibility':'hidden'}).one('fancybox-cleanup', function() { + this.style.visibility = 'inherit'; + }); + } + + overlay.css({ + 'background-color' : currentOpts.overlayColor, + 'opacity' : currentOpts.overlayOpacity + }).unbind().show(); + } + + final_pos = fancybox_get_zoom_to(); + + fancybox_process_title(); + + if (wrap.is(":visible")) { + $( close.add( nav_left ).add( nav_right ) ).hide(); + + var pos = wrap.position(), + equal; + + start_pos = { + top : pos.top , + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height); + + inner.fadeOut(currentOpts.changeFade, function() { + var finish_resizing = function() { + inner.html( tmp.contents() ).fadeIn(currentOpts.changeFade, _finish); + }; + + $.event.trigger('fancybox-change'); + + inner.empty().css('overflow', 'hidden'); + + if (equal) { + inner.css({ + top : currentOpts.padding, + left : currentOpts.padding, + width : Math.max(final_pos.width - (currentOpts.padding * 2), 1), + height : Math.max(final_pos.height - (currentOpts.padding * 2) - titleh, 1) + }); + + finish_resizing(); + + } else { + inner.css({ + top : currentOpts.padding, + left : currentOpts.padding, + width : Math.max(start_pos.width - (currentOpts.padding * 2), 1), + height : Math.max(start_pos.height - (currentOpts.padding * 2), 1) + }); + + fx.prop = 0; + + $(fx).animate({ prop: 1 }, { + duration : currentOpts.changeSpeed, + easing : currentOpts.easingChange, + step : fancybox_draw, + complete : finish_resizing + }); + } + }); + + return; + } + + wrap.css('opacity', 1); + + if (currentOpts.transitionIn == 'elastic') { + start_pos = fancybox_get_zoom_from(); + + inner.css({ + top : currentOpts.padding, + left : currentOpts.padding, + width : Math.max(start_pos.width - (currentOpts.padding * 2), 1), + height : Math.max(start_pos.height - (currentOpts.padding * 2), 1) + }) + .html( tmp.contents() ); + + wrap.css(start_pos).show(); + + if (currentOpts.opacity) { + final_pos.opacity = 0; + } + + fx.prop = 0; + + $(fx).animate({ prop: 1 }, { + duration : currentOpts.speedIn, + easing : currentOpts.easingIn, + step : fancybox_draw, + complete : _finish + }); + + } else { + inner.css({ + top : currentOpts.padding, + left : currentOpts.padding, + width : Math.max(final_pos.width - (currentOpts.padding * 2), 1), + height : Math.max(final_pos.height - (currentOpts.padding * 2) - titleh, 1) + }) + .html( tmp.contents() ); + + wrap.css( final_pos ).fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish ); + } + }, + + fancybox_process_inline = function() { + tmp.width( selectedOpts.width ); + tmp.height( selectedOpts.height ); + + if (selectedOpts.width == 'auto') { + selectedOpts.width = tmp.width(); + } + if (selectedOpts.height == 'auto') { + selectedOpts.height = tmp.height(); + } + + fancybox_show(); + }, + + fancybox_process_image = function() { + busy = true; + + selectedOpts.width = imgPreloader.width; + selectedOpts.height = imgPreloader.height; + + $("").attr({ + 'id' : 'fancybox-img', + 'src' : imgPreloader.src, + 'alt' : selectedOpts.title + }).appendTo( tmp ); + + fancybox_show(); + }, + + fancybox_start = function() { + fancybox_abort(); + + var obj = selectedArray[ selectedIndex ], + href, + type, + title, + str, + emb, + selector, + data; + + selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox'))); + title = obj.title || $(obj).title || selectedOpts.title || ''; + + if (obj.nodeName && !selectedOpts.orig) { + selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj); + } + + if (title === '' && selectedOpts.orig) { + title = selectedOpts.orig.attr('alt'); + } + + if (obj.nodeName && (/^(?:javascript|#)/i).test(obj.href)) { + href = selectedOpts.href || null; + } else { + href = selectedOpts.href || obj.href || null; + } + + if (selectedOpts.type) { + type = selectedOpts.type; + + if (!href) { + href = selectedOpts.content; + } + + } else if (selectedOpts.content) { + type = 'html'; + + } else if (href) { + if (href.match(imgRegExp)) { + type = 'image'; + + } else if (href.match(swfRegExp)) { + type = 'swf'; + + } else if ($(obj).hasClass("iframe")) { + type = 'iframe'; + + } else if (href.match(/#/)) { + obj = href.substr(href.indexOf("#")); + + type = $(obj).length > 0 ? 'inline' : 'ajax'; + } else { + type = 'ajax'; + } + } else { + type = 'inline'; + } + + selectedOpts.type = type; + selectedOpts.href = href; + selectedOpts.title = title; + + if (selectedOpts.autoDimensions && selectedOpts.type !== 'iframe' && selectedOpts.type !== 'swf') { + selectedOpts.width = 'auto'; + selectedOpts.height = 'auto'; + } + + if (selectedOpts.modal) { + selectedOpts.overlayShow = true; + selectedOpts.hideOnOverlayClick = false; + selectedOpts.hideOnContentClick = false; + selectedOpts.enableEscapeButton = false; + selectedOpts.showCloseButton = false; + } + + if ($.isFunction(selectedOpts.onStart)) { + if (selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts) === false) { + busy = false; + return; + } + } + + tmp.css('padding', (shadow + selectedOpts.padding + selectedOpts.margin)); + + $('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() { + $(this).replaceWith(inner.children()); + }); + + switch (type) { + case 'html' : + tmp.html( selectedOpts.content ); + fancybox_process_inline(); + break; + + case 'inline' : + $('
').hide().insertBefore( $(obj) ).bind('fancybox-cleanup', function() { + $(this).replaceWith(inner.children()); + }).bind('fancybox-cancel', function() { + $(this).replaceWith(tmp.children()); + }); + + $(obj).appendTo(tmp); + + fancybox_process_inline(); + break; + + case 'image': + busy = false; + + $.fancybox.showActivity(); + + imgPreloader = new Image(); + + imgPreloader.onerror = function() { + fancybox_error(); + }; + + imgPreloader.onload = function() { + imgPreloader.onerror = null; + imgPreloader.onload = null; + fancybox_process_image(); + }; + + imgPreloader.src = href; + + break; + + case 'swf': + str = ''; + emb = ''; + + $.each(selectedOpts.swf, function(name, val) { + str += ''; + emb += ' ' + name + '="' + val + '"'; + }); + + str += ''; + + tmp.html(str); + + fancybox_process_inline(); + break; + + case 'ajax': + selector = href.split('#', 2); + data = selectedOpts.ajax.data || {}; + + if (selector.length > 1) { + href = selector[0]; + + if (typeof data == "string") { + data += '&selector=' + selector[1]; + } else { + data.selector = selector[1]; + } + } + + busy = false; + $.fancybox.showActivity(); + + ajaxLoader = $.ajax($.extend(selectedOpts.ajax, { + url : href, + data : data, + error : fancybox_error, + success : function(data, textStatus, XMLHttpRequest) { + if (ajaxLoader.status == 200) { + tmp.html( data ); + fancybox_process_inline(); + } + } + })); + + break; + + case 'iframe' : + $('').appendTo(tmp); + fancybox_show(); + break; + } + }, + + fancybox_animate_loading = function() { + if (!loading.is(':visible')){ + clearInterval(loadingTimer); + return; + } + + $('div', loading).css('top', (loadingFrame * -40) + 'px'); + + loadingFrame = (loadingFrame + 1) % 12; + }, + + fancybox_init = function() { + if ($("#fancybox-wrap").length) { + return; + } + + $('body').append( + tmp = $('
'), + loading = $('
'), + overlay = $('
'), + wrap = $('
') + ); + + if (!$.support.opacity) { + wrap.addClass('fancybox-ie'); + loading.addClass('fancybox-ie'); + } + + outer = $('
') + .append('
') + .appendTo( wrap ); + + outer.append( + inner = $('
'), + close = $(''), + + nav_left = $(''), + nav_right = $('') + ); + + close.click($.fancybox.close); + loading.click($.fancybox.cancel); + + nav_left.click(function(e) { + e.preventDefault(); + $.fancybox.prev(); + }); + + nav_right.click(function(e) { + e.preventDefault(); + $.fancybox.next(); + }); + + if (isIE6) { + overlay.get(0).style.setExpression('height', "document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'"); + loading.get(0).style.setExpression('top', "(-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px'"); + + outer.prepend(''); + } + }; + + /* + * Public methods + */ + + $.fn.fancybox = function(options) { + $(this) + .data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {}))) + .unbind('click.fb').bind('click.fb', function(e) { + e.preventDefault(); + + if (busy) { + return; + } + + busy = true; + + $(this).blur(); + + selectedArray = []; + selectedIndex = 0; + + var rel = $(this).attr('rel') || ''; + + if (!rel || rel == '' || rel === 'nofollow') { + selectedArray.push(this); + + } else { + selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]"); + selectedIndex = selectedArray.index( this ); + } + + fancybox_start(); + + return false; + }); + + return this; + }; + + $.fancybox = function(obj) { + if (busy) { + return; + } + + busy = true; + + var opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {}; + + selectedArray = []; + selectedIndex = opts.index || 0; + + if ($.isArray(obj)) { + for (var i = 0, j = obj.length; i < j; i++) { + if (typeof obj[i] == 'object') { + $(obj[i]).data('fancybox', $.extend({}, opts, obj[i])); + } else { + obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts)); + } + } + + selectedArray = jQuery.merge(selectedArray, obj); + + } else { + if (typeof obj == 'object') { + $(obj).data('fancybox', $.extend({}, opts, obj)); + } else { + obj = $({}).data('fancybox', $.extend({content : obj}, opts)); + } + + selectedArray.push(obj); + } + + if (selectedIndex > selectedArray.length || selectedIndex < 0) { + selectedIndex = 0; + } + + fancybox_start(); + }; + + $.fancybox.showActivity = function() { + clearInterval(loadingTimer); + + loading.show(); + loadingTimer = setInterval(fancybox_animate_loading, 66); + }; + + $.fancybox.hideActivity = function() { + loading.hide(); + }; + + $.fancybox.next = function() { + return $.fancybox.pos( currentIndex + 1); + }; + + $.fancybox.prev = function() { + return $.fancybox.pos( currentIndex - 1); + }; + + $.fancybox.pos = function(pos) { + if (busy) { + return; + } + + pos = parseInt(pos, 10); + + if (pos > -1 && currentArray.length > pos) { + selectedIndex = pos; + fancybox_start(); + } + + if (currentOpts.cyclic && currentArray.length > 1 && pos < 0) { + selectedIndex = currentArray.length - 1; + fancybox_start(); + } + + if (currentOpts.cyclic && currentArray.length > 1 && pos >= currentArray.length) { + selectedIndex = 0; + fancybox_start(); + } + + return; + }; + + $.fancybox.cancel = function() { + if (busy) { + return; + } + + busy = true; + + $.event.trigger('fancybox-cancel'); + + fancybox_abort(); + + if (selectedOpts && $.isFunction(selectedOpts.onCancel)) { + selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts); + } + + busy = false; + }; + + // Note: within an iframe use - parent.$.fancybox.close(); + $.fancybox.close = function() { + if (busy || wrap.is(':hidden')) { + return; + } + + busy = true; + + if (currentOpts && $.isFunction(currentOpts.onCleanup)) { + if (currentOpts.onCleanup(currentArray, currentIndex, currentOpts) === false) { + busy = false; + return; + } + } + + fancybox_abort(); + + $(close.add( nav_left ).add( nav_right )).hide(); + + $('#fancybox-title').remove(); + + wrap.add(inner).add(overlay).unbind(); + + $(window).unbind("resize.fb scroll.fb"); + $(document).unbind('keydown.fb'); + + function _cleanup() { + overlay.fadeOut('fast'); + + wrap.hide(); + + $.event.trigger('fancybox-cleanup'); + + inner.empty(); + + if ($.isFunction(currentOpts.onClosed)) { + currentOpts.onClosed(currentArray, currentIndex, currentOpts); + } + + currentArray = selectedOpts = []; + currentIndex = selectedIndex = 0; + currentOpts = selectedOpts = {}; + + busy = false; + } + + inner.css('overflow', 'hidden'); + + if (currentOpts.transitionOut == 'elastic') { + start_pos = fancybox_get_zoom_from(); + + var pos = wrap.position(); + + final_pos = { + top : pos.top , + left : pos.left, + width : wrap.width(), + height : wrap.height() + }; + + if (currentOpts.opacity) { + final_pos.opacity = 1; + } + + fx.prop = 1; + + $(fx).animate({ prop: 0 }, { + duration : currentOpts.speedOut, + easing : currentOpts.easingOut, + step : fancybox_draw, + complete : _cleanup + }); + + } else { + wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup); + } + }; + + $.fancybox.resize = function() { + var c, h; + + if (busy || wrap.is(':hidden')) { + return; + } + + busy = true; + + c = inner.wrapInner("
").children(); + h = c.height(); + + wrap.css({height: h + (currentOpts.padding * 2) + titleh}); + inner.css({height: h}); + + c.replaceWith(c.children()); + + $.fancybox.center(); + }; + + $.fancybox.center = function() { + busy = true; + + var view = fancybox_get_viewport(), + margin = currentOpts.margin, + to = {}; + + to.top = view[3] + ((view[1] - ((wrap.height() - titleh) + (shadow * 2 ))) * 0.5); + to.left = view[2] + ((view[0] - (wrap.width() + (shadow * 2 ))) * 0.5); + + to.top = Math.max(view[3] + margin, to.top); + to.left = Math.max(view[2] + margin, to.left); + + wrap.css(to); + + busy = false; + }; + + $.fn.fancybox.defaults = { + padding : 10, + margin : 20, + opacity : false, + modal : false, + cyclic : false, + scrolling : 'auto', // 'auto', 'yes' or 'no' + + width : 560, + height : 340, + + autoScale : true, + autoDimensions : true, + centerOnScroll : false, + + ajax : {}, + swf : { wmode: 'transparent' }, + + hideOnOverlayClick : true, + hideOnContentClick : false, + + overlayShow : true, + overlayOpacity : 0.3, + overlayColor : '#666', + + titleShow : true, + titlePosition : 'outside', // 'outside', 'inside' or 'over' + titleFormat : null, + + transitionIn : 'fade', // 'elastic', 'fade' or 'none' + transitionOut : 'fade', // 'elastic', 'fade' or 'none' + + speedIn : 300, + speedOut : 300, + + changeSpeed : 300, + changeFade : 'fast', + + easingIn : 'swing', + easingOut : 'swing', + + showCloseButton : true, + showNavArrows : true, + enableEscapeButton : true, + + onStart : null, + onCancel : null, + onComplete : null, + onCleanup : null, + onClosed : null + }; + + $(document).ready(function() { + fancybox_init(); + }); + +})(jQuery); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/plupload/gears_init.js b/public/javascripts/admin/plugins/plupload/gears_init.js new file mode 100644 index 00000000..5f44f09b --- /dev/null +++ b/public/javascripts/admin/plugins/plupload/gears_init.js @@ -0,0 +1,86 @@ +// Copyright 2007, Google Inc. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Sets up google.gears.*, which is *the only* supported way to access Gears. +// +// Circumvent this file at your own risk! +// +// In the future, Gears may automatically define google.gears.* without this +// file. Gears may use these objects to transparently fix bugs and compatibility +// issues. Applications that use the code below will continue to work seamlessly +// when that happens. + +(function() { + // We are already defined. Hooray! + if (window.google && google.gears) { + return; + } + + var factory = null; + + // Firefox + if (typeof GearsFactory != 'undefined') { + factory = new GearsFactory(); + } else { + // IE + try { + factory = new ActiveXObject('Gears.Factory'); + // privateSetGlobalObject is only required and supported on WinCE. + if (factory.getBuildInfo().indexOf('ie_mobile') != -1) { + factory.privateSetGlobalObject(this); + } + } catch (e) { + // Safari + if ((typeof navigator.mimeTypes != 'undefined') + && navigator.mimeTypes["application/x-googlegears"]) { + factory = document.createElement("object"); + factory.style.display = "none"; + factory.width = 0; + factory.height = 0; + factory.type = "application/x-googlegears"; + document.documentElement.appendChild(factory); + } + } + } + + // *Do not* define any objects if Gears is not installed. This mimics the + // behavior of Gears defining the objects in the future. + if (!factory) { + return; + } + + // Now set up the objects, being careful not to overwrite anything. + // + // Note: In Internet Explorer for Windows Mobile, you can't add properties to + // the window object. However, global objects are automatically added as + // properties of the window object in all browsers. + if (!window.google) { + google = {}; + } + + if (!google.gears) { + google.gears = {factory: factory}; + } +})(); diff --git a/public/javascripts/admin/plugins/plupload/jquery.plupload.queue.min.js b/public/javascripts/admin/plugins/plupload/jquery.plupload.queue.min.js new file mode 100644 index 00000000..4558695c --- /dev/null +++ b/public/javascripts/admin/plugins/plupload/jquery.plupload.queue.min.js @@ -0,0 +1 @@ +(function(c){var d={};function a(e){return plupload.translate(e)||e}function b(f,e){e.contents().each(function(g,h){h=c(h);if(!h.is(".plupload")){h.remove()}});e.prepend('
'+a("Select files")+'
'+a("Add files to the upload queue and click the start button.")+'
'+a("Filename")+'
 
'+a("Status")+'
'+a("Size")+'
 
    ')}c.fn.pluploadQueue=function(e){if(e){this.each(function(){var j,i,k;i=c(this);k=i.attr("id");if(!k){k=plupload.guid();i.attr("id",k)}j=new plupload.Uploader(c.extend({dragdrop:true,container:k},e));if(e.preinit){e.preinit(j)}d[k]=j;function h(l){var m;if(l.status==plupload.DONE){m="plupload_done"}if(l.status==plupload.FAILED){m="plupload_failed"}if(l.status==plupload.QUEUED){m="plupload_delete"}if(l.status==plupload.UPLOADING){m="plupload_uploading"}c("#"+l.id).attr("class",m).find("a").css("display","block")}function f(){c("span.plupload_total_status",i).html(j.total.percent+"%");c("div.plupload_progress_bar",i).css("width",j.total.percent+"%");c("span.plupload_upload_status",i).text("Uploaded "+j.total.uploaded+"/"+j.files.length+" files");if(j.total.uploaded==j.files.length){j.stop()}}function g(){var m=c("ul.plupload_filelist",i).html(""),n=0,l;c.each(j.files,function(p,o){l="";if(o.status==plupload.DONE){if(o.target_name){l+=''}l+='';l+='';n++;c("#"+k+"_count").val(n)}m.append('
  • '+o.name+'
    '+o.percent+'%
    '+plupload.formatSize(o.size)+'
     
    '+l+"
  • ");h(o);c("#"+o.id+".plupload_delete a").click(function(q){c("#"+o.id).remove();j.removeFile(o);q.preventDefault()})});c("span.plupload_total_file_size",i).html(plupload.formatSize(j.total.size));if(j.total.queued===0){c("span.plupload_add_text",i).text(a("Add files."))}else{c("span.plupload_add_text",i).text(j.total.queued+" files queued.")}c("a.plupload_start",i).toggleClass("plupload_disabled",j.files.length===0);m[0].scrollTop=m[0].scrollHeight;f();if(!j.files.length&&j.features.dragdrop&&j.settings.dragdrop){c("#"+k+"_filelist").append('
  • '+a("Drag files here.")+"
  • ")}}j.bind("UploadFile",function(l,m){c("#"+m.id).addClass("plupload_current_file")});j.bind("Init",function(l,m){b(k,i);if(!e.unique_names&&e.rename){c("#"+k+"_filelist div.plupload_file_name span",i).live("click",function(s){var q=c(s.target),o,r,n,p="";o=l.getFile(q.parents("li")[0].id);n=o.name;r=/^(.+)(\.[^.]+)$/.exec(n);if(r){n=r[1];p=r[2]}q.hide().after('');q.next().val(n).focus().blur(function(){q.show().next().remove()}).keydown(function(u){var t=c(this);if(u.keyCode==13){u.preventDefault();o.name=t.val()+p;q.text(o.name);t.blur()}})})}c("a.plupload_add",i).attr("id",k+"_browse");l.settings.browse_button=k+"_browse";if(l.features.dragdrop&&l.settings.dragdrop){l.settings.drop_element=k+"_filelist";c("#"+k+"_filelist").append('
  • '+a("Drag files here.")+"
  • ")}c("#"+k+"_container").attr("title","Using runtime: "+m.runtime);c("a.plupload_start",i).click(function(n){if(!c(this).hasClass("plupload_disabled")){j.start()}n.preventDefault()});c("a.plupload_stop",i).click(function(n){j.stop();n.preventDefault()});c("a.plupload_start",i).addClass("plupload_disabled")});j.init();if(e.setup){e.setup(j)}j.bind("Error",function(l,o){var m=o.file,n;if(m){n=o.message;if(o.details){n+=" ("+o.details+")"}c("#"+m.id).attr("class","plupload_failed").find("a").css("display","block").attr("title",n)}});j.bind("StateChanged",function(){if(j.state===plupload.STARTED){c("li.plupload_delete a,div.plupload_buttons",i).hide();c("span.plupload_upload_status,div.plupload_progress,a.plupload_stop",i).css("display","block");c("span.plupload_upload_status",i).text("Uploaded 0/"+j.files.length+" files")}else{c("a.plupload_stop,div.plupload_progress",i).hide();c("a.plupload_delete",i).css("display","block")}});j.bind("QueueChanged",g);j.bind("StateChanged",function(l){if(l.state==plupload.STOPPED){g()}});j.bind("FileUploaded",function(l,m){h(m)});j.bind("UploadProgress",function(l,m){c("#"+m.id+" div.plupload_file_status",i).html(m.percent+"%");h(m);f()})});return this}else{return d[c(this[0]).attr("id")]}}})(jQuery); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/plupload/plupload.browserplus.min.js b/public/javascripts/admin/plugins/plupload/plupload.browserplus.min.js new file mode 100644 index 00000000..19f3fc9e --- /dev/null +++ b/public/javascripts/admin/plugins/plupload/plupload.browserplus.min.js @@ -0,0 +1 @@ +(function(a){a.runtimes.BrowserPlus=a.addRuntime("browserplus",{getFeatures:function(){return{dragdrop:true,jpgresize:true,pngresize:true,chunks:true,progress:true,multipart:true}},init:function(g,i){var e=window.BrowserPlus,h={},d=g.settings,c=d.resize;function f(n){var m,l,j=[],k,o;for(l=0;l0){r(++s,u)}else{k.status=a.DONE;n.trigger("FileUploaded",k,{response:w.value.body,status:v});if(v>=400){n.trigger("Error",{code:a.HTTP_ERROR,message:"HTTP Error.",file:k,status:v})}}}else{n.trigger("Error",{code:a.GENERIC_ERROR,message:"Generic Error.",file:k,details:w.error})}})}function q(s){k.size=s.size;if(l){e.FileAccess.chunk({file:s,chunkSize:l},function(v){if(v.success){var w=v.value,t=w.length;o=Array(t);for(var u=0;u';function j(){return document.getElementById(g.id+"_flash")}function i(){if(m++>5000){l({success:false});return}if(!e){setTimeout(i,1)}}i();k=f=null;g.bind("Flash:Init",function(){var p={},o,n=g.settings.resize||{};e=true;j().setFileFilters(g.settings.filters,g.settings.multi_selection);g.bind("UploadFile",function(q,r){var s=q.settings;j().uploadFile(p[r.id],c.buildUrl(s.url,{name:r.target_name||r.name}),{chunk_size:s.chunk_size,width:n.width,height:n.height,quality:n.quality||90,multipart:s.multipart,multipart_params:s.multipart_params,file_data_name:s.file_data_name,format:/\.(jpg|jpeg)$/i.test(r.name)?"jpg":"png",headers:s.headers})});g.bind("Flash:UploadProcess",function(r,q){var s=r.getFile(p[q.id]);if(s.status!=c.FAILED){s.loaded=q.loaded;s.size=q.size;r.trigger("UploadProgress",s)}});g.bind("Flash:UploadChunkComplete",function(q,s){var t,r=q.getFile(p[s.id]);t={chunk:s.chunk,chunks:s.chunks,response:s.text};q.trigger("ChunkUploaded",r,t);if(r.status!=c.FAILED){j().uploadNextChunk()}if(s.chunk==s.chunks-1){r.status=c.DONE;q.trigger("FileUploaded",r,{response:s.text})}});g.bind("Flash:SelectFiles",function(q,t){var s,r,u=[],v;for(r=0;r":"gt","&":"amp",'"':"quot","'":"#39"},i=/[<>&\"\']/g,b;function e(){this.returnValue=false}function g(){this.cancelBubble=true}(function(k){var l=k.split(/,/),m,o,n;for(m=0;m0){d.each(l,function(o,n){k[n]=o})}});return k},cleanName:function(k){var l,m;m=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"];for(l=0;l0?"&":"?")+m}return l},each:function(n,o){var m,l,k;if(n){m=n.length;if(m===b){for(l in n){if(n.hasOwnProperty(l)){if(o(n[l],l)===false){return}}}}else{for(k=0;k1048576){return Math.round(k/1048576,1)+" MB"}if(k>1024){return Math.round(k/1024,1)+" KB"}return k+" b"},getPos:function(l,p){var q=0,o=0,s,r=document,m,n;l=l;p=p||r.body;function k(w){var u,v,t=0,z=0;if(w){v=w.getBoundingClientRect();u=r.compatMode==="CSS1Compat"?r.documentElement:r.body;t=v.left+u.scrollLeft;z=v.top+u.scrollTop}return{x:t,y:z}}if(l.getBoundingClientRect&&(navigator.userAgent.indexOf("MSIE")>0&&r.documentMode!==8)){m=k(l);n=k(p);return{x:m.x-n.x,y:m.y-n.y}}s=l;while(s&&s!=p&&s.nodeType){q+=s.offsetLeft||0;o+=s.offsetTop||0;s=s.offsetParent}s=l.parentNode;while(s&&s!=p&&s.nodeType){q-=s.scrollLeft||0;o-=s.scrollTop||0;s=s.parentNode}return{x:q,y:o}},getSize:function(k){return{w:k.clientWidth||k.offsetWidth,h:k.clientHeight||k.offsetHeight}},parseSize:function(k){var l;if(typeof(k)=="string"){k=/^([0-9]+)([mgk]+)$/.exec(k.toLowerCase().replace(/[^0-9mkg]/g,""));l=k[2];k=+k[1];if(l=="g"){k*=1073741824}if(l=="m"){k*=1048576}if(l=="k"){k*=1024}}return k},xmlEncode:function(k){return k?(""+k).replace(i,function(l){return a[l]?"&"+a[l]+";":l}):k},toArray:function(m){var l,k=[];for(l=0;l0?Math.ceil(q.uploaded/p.length*100):0}else{q.bytesPerSec=Math.ceil(q.loaded/((+new Date()-m||1)/1000));q.percent=q.size>0?Math.ceil(q.loaded/q.size*100):0}}d.extend(this,{state:d.STOPPED,features:{},files:p,settings:n,total:q,id:d.guid(),init:function(){var x=this,y,u,t,w=0,v;n.page_url=n.page_url||document.location.pathname.replace(/\/[^\/]+$/g,"/");if(!/^(\w+:\/\/|\/)/.test(n.url)){n.url=n.page_url+n.url}n.chunk_size=d.parseSize(n.chunk_size);n.max_file_size=d.parseSize(n.max_file_size);x.bind("FilesAdded",function(z,C){var B,A,F=0,E,D=n.filters;if(D&&D.length){E={};d.each(D,function(G){d.each(G.extensions.split(/,/),function(H){E[H.toLowerCase()]=true})})}for(B=0;Bn.max_file_size){z.trigger("Error",{code:d.FILE_SIZE_ERROR,message:"File size error.",file:A});continue}p.push(A);F++}if(F){x.trigger("QueueChanged");x.refresh()}});if(n.unique_names){x.bind("UploadFile",function(z,A){A.target_name=A.id+".tmp"})}x.bind("UploadProgress",function(z,A){if(A.status==d.QUEUED){A.status=d.UPLOADING}A.percent=A.size>0?Math.ceil(A.loaded/A.size*100):100;k()});x.bind("StateChanged",function(z){if(z.state==d.STARTED){m=(+new Date())}});x.bind("QueueChanged",k);x.bind("Error",function(z,A){if(A.file){A.file.status=d.FAILED;k();window.setTimeout(function(){o.call(x)})}});x.bind("FileUploaded",function(z,A){A.status=d.DONE;z.trigger("UploadProgress",A);o.call(x)});if(n.runtimes){u=[];v=n.runtimes.split(/\s?,\s?/);for(y=0;y=0;s--){if(p[s].id===t){return p[s]}}},removeFile:function(t){var s;for(s=p.length-1;s>=0;s--){if(p[s].id===t.id){return this.splice(s,1)[0]}}},splice:function(u,s){var t;t=p.splice(u,s);this.trigger("FilesRemoved",t);this.trigger("QueueChanged");return t},trigger:function(t){var v=l[t.toLowerCase()],u,s;if(v){s=Array.prototype.slice.call(arguments);s[0]=this;for(u=0;u=0;t--){if(v[t].func===u){v.splice(t,1)}}}}})};d.File=function(n,l,m){var k=this;k.id=n;k.name=l;k.size=m;k.loaded=0;k.percent=0;k.status=0};d.Runtime=function(){this.getFeatures=function(){};this.init=function(k,l){}};d.QueueProgress=function(){var k=this;k.size=0;k.loaded=0;k.uploaded=0;k.failed=0;k.queued=0;k.percent=0;k.bytesPerSec=0;k.reset=function(){k.size=k.loaded=k.uploaded=k.failed=k.queued=k.percent=k.bytesPerSec=0}};d.runtimes={};window.plupload=d})();(function(b){var c={};function a(i,e,k,j,d){var l,g,f,h;g=google.gears.factory.create("beta.canvas");g.decode(i);h=Math.min(e/g.width,k/g.height);if(h<1){e=Math.round(g.width*h);k=Math.round(g.height*h)}else{e=g.width;k=g.height}g.resize(e,k);return g.encode(d,{quality:j/100})}b.runtimes.Gears=b.addRuntime("gears",{getFeatures:function(){return{dragdrop:true,jpgresize:true,pngresize:true,chunks:true,progress:true,multipart:true}},init:function(g,i){var h;if(!window.google||!google.gears){return i({success:false})}try{h=google.gears.factory.create("beta.desktop")}catch(f){return i({success:false})}function d(k){var j,e,l=[],m;for(e=0;e0;p=Math.ceil(l.size/m);if(!e){m=l.size;p=1}if(k&&/\.(png|jpg|jpeg)$/i.test(l.name)){c[l.id]=a(c[l.id],k.width,k.height,k.quality||90,/\.png$/i.test(l.name)?"image/png":"image/jpeg")}l.size=c[l.id].length;function j(){var u,w,s=o.settings.multipart,r=0,v={name:l.target_name||l.name};function t(y){var x,C="----pluploadboundary"+b.guid(),A="--",B="\r\n",z;if(s){u.setRequestHeader("Content-Type","multipart/form-data; boundary="+C);x=google.gears.factory.create("beta.blobbuilder");b.each(o.settings.multipart_params,function(E,D){x.append(A+C+B+'Content-Disposition: form-data; name="'+D+'"'+B+B);x.append(E+B)});x.append(A+C+B+'Content-Disposition: form-data; name="'+o.settings.file_data_name+'"; filename="'+l.name+'"'+B+"Content-Type: application/octet-stream"+B+B);x.append(y);x.append(B+A+C+A+B);z=x.getAsBlob();r=z.length-y.length;y=z}u.send(y)}if(l.status==b.DONE||l.status==b.FAILED||o.state==b.STOPPED){return}if(e){v.chunk=q;v.chunks=p}w=Math.min(m,l.size-(q*m));u=google.gears.factory.create("beta.httprequest");u.open("POST",b.buildUrl(o.settings.url,v));if(!s){u.setRequestHeader("Content-Disposition",'attachment; filename="'+l.name+'"');u.setRequestHeader("Content-Type","application/octet-stream")}b.each(o.settings.headers,function(y,x){u.setRequestHeader(x,y)});u.upload.onprogress=function(x){l.loaded=n+x.loaded-r;o.trigger("UploadProgress",l)};u.onreadystatechange=function(){var x;if(u.readyState==4){if(u.status==200){x={chunk:q,chunks:p,response:u.responseText,status:u.status};o.trigger("ChunkUploaded",l,x);if(x.cancelled){l.status=b.FAILED;return}n+=w;if(++q>=p){l.status=b.DONE;o.trigger("FileUploaded",l,{response:u.responseText,status:u.status})}else{j()}}else{o.trigger("Error",{code:b.HTTP_ERROR,message:"HTTP Error.",file:l,chunk:q,chunks:p,status:u.status})}}};if(q3){h.pop()}while(h.length<4){h.push(0)}i=o.split(".");while(i.length>4){i.pop()}do{q=parseInt(i[m],10);j=parseInt(h[m],10);m++}while(m';function e(){return document.getElementById(l.id+"_silverlight").content.Upload}l.bind("Silverlight:Init",function(){var i,n={};l.bind("Silverlight:StartSelectFiles",function(o){i=[]});l.bind("Silverlight:SelectFile",function(o,r,p,q){var s;s=c.guid();n[s]=r;n[r]=s;i.push(new c.File(s,p,q))});l.bind("Silverlight:SelectSuccessful",function(){if(i.length){l.trigger("FilesAdded",i)}});l.bind("Silverlight:UploadChunkError",function(o,r,p,s,q){l.trigger("Error",{code:c.IO_ERROR,message:"IO Error.",details:q,file:o.getFile(n[r])})});l.bind("Silverlight:UploadFileProgress",function(o,s,p,r){var q=o.getFile(n[s]);if(q.status!=c.FAILED){q.size=r;q.loaded=p;o.trigger("UploadProgress",q)}});l.bind("Refresh",function(o){var p,q,r;p=document.getElementById(o.settings.browse_button);q=c.getPos(p,document.getElementById(o.settings.container));r=c.getSize(p);c.extend(document.getElementById(o.id+"_silverlight_container").style,{top:q.y+"px",left:q.x+"px",width:r.w+"px",height:r.h+"px"})});l.bind("Silverlight:UploadChunkSuccessful",function(o,r,p,u,t){var s,q=o.getFile(n[r]);s={chunk:p,chunks:u,response:t};o.trigger("ChunkUploaded",q,s);if(q.status!=c.FAILED){e().UploadNextChunk()}if(p==u-1){q.status=c.DONE;o.trigger("FileUploaded",q,{response:t})}});l.bind("Silverlight:UploadSuccessful",function(o,r,p){var q=o.getFile(n[r]);q.status=c.DONE;o.trigger("FileUploaded",q,{response:p})});l.bind("FilesRemoved",function(o,q){var p;for(p=0;p';function j(){return document.getElementById(g.id+"_flash")}function i(){if(m++>5000){l({success:false});return}if(!e){setTimeout(i,1)}}i();k=f=null;g.bind("Flash:Init",function(){var p={},o,n=g.settings.resize||{};e=true;j().setFileFilters(g.settings.filters,g.settings.multi_selection);g.bind("UploadFile",function(q,r){var s=q.settings;j().uploadFile(p[r.id],c.buildUrl(s.url,{name:r.target_name||r.name}),{chunk_size:s.chunk_size,width:n.width,height:n.height,quality:n.quality||90,multipart:s.multipart,multipart_params:s.multipart_params,file_data_name:s.file_data_name,format:/\.(jpg|jpeg)$/i.test(r.name)?"jpg":"png",headers:s.headers})});g.bind("Flash:UploadProcess",function(r,q){var s=r.getFile(p[q.id]);if(s.status!=c.FAILED){s.loaded=q.loaded;s.size=q.size;r.trigger("UploadProgress",s)}});g.bind("Flash:UploadChunkComplete",function(q,s){var t,r=q.getFile(p[s.id]);t={chunk:s.chunk,chunks:s.chunks,response:s.text};q.trigger("ChunkUploaded",r,t);if(r.status!=c.FAILED){j().uploadNextChunk()}if(s.chunk==s.chunks-1){r.status=c.DONE;q.trigger("FileUploaded",r,{response:s.text})}});g.bind("Flash:SelectFiles",function(q,t){var s,r,u=[],v;for(r=0;r0){r(++s,u)}else{k.status=a.DONE;n.trigger("FileUploaded",k,{response:w.value.body,status:v});if(v>=400){n.trigger("Error",{code:a.HTTP_ERROR,message:"HTTP Error.",file:k,status:v})}}}else{n.trigger("Error",{code:a.GENERIC_ERROR,message:"Generic Error.",file:k,details:w.error})}})}function q(s){k.size=s.size;if(l){e.FileAccess.chunk({file:s,chunkSize:l},function(v){if(v.success){var w=v.value,t=w.length;o=Array(t);for(var u=0;u";document.getElementById(e.id+"_html5").onchange=function(){d(this.files);this.value=""}});e.bind("PostInit",function(){var g=document.getElementById(e.settings.drop_element);if(g){b.addEvent(g,"dragover",function(h){h.preventDefault()});b.addEvent(g,"drop",function(i){var h=i.dataTransfer;if(h&&h.files){d(h.files)}i.preventDefault()})}});e.bind("Refresh",function(g){var h,i,j;h=document.getElementById(e.settings.browse_button);i=b.getPos(h,document.getElementById(g.settings.container));j=b.getSize(h);b.extend(document.getElementById(e.id+"_html5_container").style,{top:i.y+"px",left:i.x+"px",width:j.w+"px",height:j.h+"px"})});e.bind("UploadFile",function(g,j){var n=new XMLHttpRequest(),i=n.upload,h=g.settings.resize,m,l=0;function k(o){var s="----pluploadboundary"+b.guid(),q="--",r="\r\n",p="";if(g.settings.multipart){n.setRequestHeader("Content-Type","multipart/form-data; boundary="+s);b.each(g.settings.multipart_params,function(u,t){p+=q+s+r+'Content-Disposition: form-data; name="'+t+'"'+r+r;p+=u+r});p+=q+s+r+'Content-Disposition: form-data; name="'+g.settings.file_data_name+'"; filename="'+j.name+'"'+r+"Content-Type: application/octet-stream"+r+r+o+r+q+s+q+r;l=p.length-o.length;o=p}n.sendAsBinary(o)}if(j.status==b.DONE||j.status==b.FAILED||g.state==b.STOPPED){return}if(i){i.onprogress=function(o){j.loaded=o.loaded-l;g.trigger("UploadProgress",j)}}n.onreadystatechange=function(){var o;if(n.readyState==4){try{o=n.status}catch(p){o=0}j.status=b.DONE;j.loaded=j.size;g.trigger("UploadProgress",j);g.trigger("FileUploaded",j,{response:n.responseText,status:o});if(o>=400){g.trigger("Error",{code:b.HTTP_ERROR,message:"HTTP Error.",file:j,status:o})}}};n.open("post",b.buildUrl(g.settings.url,{name:j.target_name||j.name}),true);n.setRequestHeader("Content-Type","application/octet-stream");b.each(g.settings.headers,function(p,o){n.setRequestHeader(o,p)});m=c[j.id];if(n.sendAsBinary){if(h&&/\.(png|jpg|jpeg)$/i.test(j.name)){a(m.getAsDataURL(),h.width,h.height,/\.png$/i.test(j.name)?"image/png":"image/jpeg",function(o){if(o.success){j.size=o.data.length;k(o.data)}else{k(m.getAsBinary())}})}else{k(m.getAsBinary())}}else{n.send(m)}});f({success:true})}})})(plupload);(function(a){a.runtimes.Html4=a.addRuntime("html4",{getFeatures:function(){return{multipart:true}},init:function(f,g){var d={},c,b;function e(l){var k,j,m=[],n,h;h=l.value.replace(/\\/g,"/");h=h.substring(h.length,h.lastIndexOf("/")+1);n=a.guid();k=new a.File(n,h);d[n]=k;k.input=l;m.push(k);if(m.length){f.trigger("FilesAdded",m)}}f.bind("Init",function(p){var h,x,v,t=[],o,u,m=p.settings.filters,l,s,r=/MSIE/.test(navigator.userAgent),k="javascript",w,j=document.body,n;if(f.settings.container){j=document.getElementById(f.settings.container);j.style.position="relative"}c=(typeof p.settings.form=="string")?document.getElementById(p.settings.form):p.settings.form;if(!c){n=document.getElementById(f.settings.browse_button);for(;n;n=n.parentNode){if(n.nodeName=="FORM"){c=n}}}if(!c){c=document.createElement("form");c.style.display="inline";n=document.getElementById(f.settings.container);n.parentNode.insertBefore(c,n);c.appendChild(n)}c.setAttribute("method","post");c.setAttribute("enctype","multipart/form-data");a.each(p.settings.multipart_params,function(z,y){var i=document.createElement("input");a.extend(i,{type:"hidden",name:y,value:z});c.appendChild(i)});b=document.createElement("iframe");b.setAttribute("src",k+':""');b.setAttribute("name",p.id+"_iframe");b.setAttribute("id",p.id+"_iframe");b.style.display="none";a.addEvent(b,"load",function(B){var C=B.target,z=f.currentfile,A;try{A=C.contentWindow.document||C.contentDocument||window.frames[C.id].document}catch(y){p.trigger("Error",{code:a.SECURITY_ERROR,message:"Security error.",file:z});return}if(A.location.href=="about:blank"||!z){return}var i=A.documentElement.innerText||A.documentElement.textContent;if(i!=""){z.status=a.DONE;z.loaded=1025;z.percent=100;if(z.input){z.input.removeAttribute("name")}p.trigger("UploadProgress",z);p.trigger("FileUploaded",z,{response:i});if(c.tmpAction){c.setAttribute("action",c.tmpAction)}if(c.tmpTarget){c.setAttribute("target",c.tmpTarget)}}});c.appendChild(b);if(r){window.frames[b.id].name=b.name}x=document.createElement("div");x.id=p.id+"_iframe_container";for(o=0;o0;p=Math.ceil(l.size/m);if(!e){m=l.size;p=1}if(k&&/\.(png|jpg|jpeg)$/i.test(l.name)){c[l.id]=a(c[l.id],k.width,k.height,k.quality||90,/\.png$/i.test(l.name)?"image/png":"image/jpeg")}l.size=c[l.id].length;function j(){var u,w,s=o.settings.multipart,r=0,v={name:l.target_name||l.name};function t(y){var x,C="----pluploadboundary"+b.guid(),A="--",B="\r\n",z;if(s){u.setRequestHeader("Content-Type","multipart/form-data; boundary="+C);x=google.gears.factory.create("beta.blobbuilder");b.each(o.settings.multipart_params,function(E,D){x.append(A+C+B+'Content-Disposition: form-data; name="'+D+'"'+B+B);x.append(E+B)});x.append(A+C+B+'Content-Disposition: form-data; name="'+o.settings.file_data_name+'"; filename="'+l.name+'"'+B+"Content-Type: application/octet-stream"+B+B);x.append(y);x.append(B+A+C+A+B);z=x.getAsBlob();r=z.length-y.length;y=z}u.send(y)}if(l.status==b.DONE||l.status==b.FAILED||o.state==b.STOPPED){return}if(e){v.chunk=q;v.chunks=p}w=Math.min(m,l.size-(q*m));u=google.gears.factory.create("beta.httprequest");u.open("POST",b.buildUrl(o.settings.url,v));if(!s){u.setRequestHeader("Content-Disposition",'attachment; filename="'+l.name+'"');u.setRequestHeader("Content-Type","application/octet-stream")}b.each(o.settings.headers,function(y,x){u.setRequestHeader(x,y)});u.upload.onprogress=function(x){l.loaded=n+x.loaded-r;o.trigger("UploadProgress",l)};u.onreadystatechange=function(){var x;if(u.readyState==4){if(u.status==200){x={chunk:q,chunks:p,response:u.responseText,status:u.status};o.trigger("ChunkUploaded",l,x);if(x.cancelled){l.status=b.FAILED;return}n+=w;if(++q>=p){l.status=b.DONE;o.trigger("FileUploaded",l,{response:u.responseText,status:u.status})}else{j()}}else{o.trigger("Error",{code:b.HTTP_ERROR,message:"HTTP Error.",file:l,chunk:q,chunks:p,status:u.status})}}};if(q";document.getElementById(e.id+"_html5").onchange=function(){d(this.files);this.value=""}});e.bind("PostInit",function(){var g=document.getElementById(e.settings.drop_element);if(g){b.addEvent(g,"dragover",function(h){h.preventDefault()});b.addEvent(g,"drop",function(i){var h=i.dataTransfer;if(h&&h.files){d(h.files)}i.preventDefault()})}});e.bind("Refresh",function(g){var h,i,j;h=document.getElementById(e.settings.browse_button);i=b.getPos(h,document.getElementById(g.settings.container));j=b.getSize(h);b.extend(document.getElementById(e.id+"_html5_container").style,{top:i.y+"px",left:i.x+"px",width:j.w+"px",height:j.h+"px"})});e.bind("UploadFile",function(g,j){var n=new XMLHttpRequest(),i=n.upload,h=g.settings.resize,m,l=0;function k(o){var s="----pluploadboundary"+b.guid(),q="--",r="\r\n",p="";if(g.settings.multipart){n.setRequestHeader("Content-Type","multipart/form-data; boundary="+s);b.each(g.settings.multipart_params,function(u,t){p+=q+s+r+'Content-Disposition: form-data; name="'+t+'"'+r+r;p+=u+r});p+=q+s+r+'Content-Disposition: form-data; name="'+g.settings.file_data_name+'"; filename="'+j.name+'"'+r+"Content-Type: application/octet-stream"+r+r+o+r+q+s+q+r;l=p.length-o.length;o=p}n.sendAsBinary(o)}if(j.status==b.DONE||j.status==b.FAILED||g.state==b.STOPPED){return}if(i){i.onprogress=function(o){j.loaded=o.loaded-l;g.trigger("UploadProgress",j)}}n.onreadystatechange=function(){var o;if(n.readyState==4){try{o=n.status}catch(p){o=0}j.status=b.DONE;j.loaded=j.size;g.trigger("UploadProgress",j);g.trigger("FileUploaded",j,{response:n.responseText,status:o});if(o>=400){g.trigger("Error",{code:b.HTTP_ERROR,message:"HTTP Error.",file:j,status:o})}}};n.open("post",b.buildUrl(g.settings.url,{name:j.target_name||j.name}),true);n.setRequestHeader("Content-Type","application/octet-stream");b.each(g.settings.headers,function(p,o){n.setRequestHeader(o,p)});m=c[j.id];if(n.sendAsBinary){if(h&&/\.(png|jpg|jpeg)$/i.test(j.name)){a(m.getAsDataURL(),h.width,h.height,/\.png$/i.test(j.name)?"image/png":"image/jpeg",function(o){if(o.success){j.size=o.data.length;k(o.data)}else{k(m.getAsBinary())}})}else{k(m.getAsBinary())}}else{n.send(m)}});f({success:true})}})})(plupload); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/plupload/plupload.js b/public/javascripts/admin/plugins/plupload/plupload.js new file mode 100644 index 00000000..48c26b20 --- /dev/null +++ b/public/javascripts/admin/plugins/plupload/plupload.js @@ -0,0 +1 @@ +(function(){var c=0,h=[],j={},f={},a={"<":"lt",">":"gt","&":"amp",'"':"quot","'":"#39"},i=/[<>&\"\']/g,b;function e(){this.returnValue=false}function g(){this.cancelBubble=true}(function(k){var l=k.split(/,/),m,o,n;for(m=0;m0){d.each(l,function(o,n){k[n]=o})}});return k},cleanName:function(k){var l,m;m=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"];for(l=0;l0?"&":"?")+m}return l},each:function(n,o){var m,l,k;if(n){m=n.length;if(m===b){for(l in n){if(n.hasOwnProperty(l)){if(o(n[l],l)===false){return}}}}else{for(k=0;k1048576){return Math.round(k/1048576,1)+" MB"}if(k>1024){return Math.round(k/1024,1)+" KB"}return k+" b"},getPos:function(l,p){var q=0,o=0,s,r=document,m,n;l=l;p=p||r.body;function k(w){var u,v,t=0,z=0;if(w){v=w.getBoundingClientRect();u=r.compatMode==="CSS1Compat"?r.documentElement:r.body;t=v.left+u.scrollLeft;z=v.top+u.scrollTop}return{x:t,y:z}}if(l.getBoundingClientRect&&(navigator.userAgent.indexOf("MSIE")>0&&r.documentMode!==8)){m=k(l);n=k(p);return{x:m.x-n.x,y:m.y-n.y}}s=l;while(s&&s!=p&&s.nodeType){q+=s.offsetLeft||0;o+=s.offsetTop||0;s=s.offsetParent}s=l.parentNode;while(s&&s!=p&&s.nodeType){q-=s.scrollLeft||0;o-=s.scrollTop||0;s=s.parentNode}return{x:q,y:o}},getSize:function(k){return{w:k.clientWidth||k.offsetWidth,h:k.clientHeight||k.offsetHeight}},parseSize:function(k){var l;if(typeof(k)=="string"){k=/^([0-9]+)([mgk]+)$/.exec(k.toLowerCase().replace(/[^0-9mkg]/g,""));l=k[2];k=+k[1];if(l=="g"){k*=1073741824}if(l=="m"){k*=1048576}if(l=="k"){k*=1024}}return k},xmlEncode:function(k){return k?(""+k).replace(i,function(l){return a[l]?"&"+a[l]+";":l}):k},toArray:function(m){var l,k=[];for(l=0;l0?Math.ceil(q.uploaded/p.length*100):0}else{q.bytesPerSec=Math.ceil(q.loaded/((+new Date()-m||1)/1000));q.percent=q.size>0?Math.ceil(q.loaded/q.size*100):0}}d.extend(this,{state:d.STOPPED,features:{},files:p,settings:n,total:q,id:d.guid(),init:function(){var x=this,y,u,t,w=0,v;n.page_url=n.page_url||document.location.pathname.replace(/\/[^\/]+$/g,"/");if(!/^(\w+:\/\/|\/)/.test(n.url)){n.url=n.page_url+n.url}n.chunk_size=d.parseSize(n.chunk_size);n.max_file_size=d.parseSize(n.max_file_size);x.bind("FilesAdded",function(z,C){var B,A,F=0,E,D=n.filters;if(D&&D.length){E={};d.each(D,function(G){d.each(G.extensions.split(/,/),function(H){E[H.toLowerCase()]=true})})}for(B=0;Bn.max_file_size){z.trigger("Error",{code:d.FILE_SIZE_ERROR,message:"File size error.",file:A});continue}p.push(A);F++}if(F){x.trigger("QueueChanged");x.refresh()}});if(n.unique_names){x.bind("UploadFile",function(z,A){A.target_name=A.id+".tmp"})}x.bind("UploadProgress",function(z,A){if(A.status==d.QUEUED){A.status=d.UPLOADING}A.percent=A.size>0?Math.ceil(A.loaded/A.size*100):100;k()});x.bind("StateChanged",function(z){if(z.state==d.STARTED){m=(+new Date())}});x.bind("QueueChanged",k);x.bind("Error",function(z,A){if(A.file){A.file.status=d.FAILED;k();window.setTimeout(function(){o.call(x)})}});x.bind("FileUploaded",function(z,A){A.status=d.DONE;z.trigger("UploadProgress",A);o.call(x)});if(n.runtimes){u=[];v=n.runtimes.split(/\s?,\s?/);for(y=0;y=0;s--){if(p[s].id===t){return p[s]}}},removeFile:function(t){var s;for(s=p.length-1;s>=0;s--){if(p[s].id===t.id){return this.splice(s,1)[0]}}},splice:function(u,s){var t;t=p.splice(u,s);this.trigger("FilesRemoved",t);this.trigger("QueueChanged");return t},trigger:function(t){var v=l[t.toLowerCase()],u,s;if(v){s=Array.prototype.slice.call(arguments);s[0]=this;for(u=0;u=0;t--){if(v[t].func===u){v.splice(t,1)}}}}})};d.File=function(n,l,m){var k=this;k.id=n;k.name=l;k.size=m;k.loaded=0;k.percent=0;k.status=0};d.Runtime=function(){this.getFeatures=function(){};this.init=function(k,l){}};d.QueueProgress=function(){var k=this;k.size=0;k.loaded=0;k.uploaded=0;k.failed=0;k.queued=0;k.percent=0;k.bytesPerSec=0;k.reset=function(){k.size=k.loaded=k.uploaded=k.failed=k.queued=k.percent=k.bytesPerSec=0}};d.runtimes={};window.plupload=d})(); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/plupload/plupload.silverlight.min.js b/public/javascripts/admin/plugins/plupload/plupload.silverlight.min.js new file mode 100644 index 00000000..ed5b7e48 --- /dev/null +++ b/public/javascripts/admin/plugins/plupload/plupload.silverlight.min.js @@ -0,0 +1 @@ +(function(c){var a={};function b(l){var k,j=typeof l,h,e,g,f;if(j==="string"){k="\bb\tt\nn\ff\rr\"\"''\\\\";return'"'+l.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g,function(n,m){var i=k.indexOf(m);if(i+1){return"\\"+k.charAt(i+1)}n=m.charCodeAt().toString(16);return"\\u"+"0000".substring(n.length)+n})+'"'}if(j=="object"){e=l.length!==h;k="";if(e){for(g=0;g3){h.pop()}while(h.length<4){h.push(0)}i=o.split(".");while(i.length>4){i.pop()}do{q=parseInt(i[m],10);j=parseInt(h[m],10);m++}while(m';function e(){return document.getElementById(l.id+"_silverlight").content.Upload}l.bind("Silverlight:Init",function(){var i,n={};l.bind("Silverlight:StartSelectFiles",function(o){i=[]});l.bind("Silverlight:SelectFile",function(o,r,p,q){var s;s=c.guid();n[s]=r;n[r]=s;i.push(new c.File(s,p,q))});l.bind("Silverlight:SelectSuccessful",function(){if(i.length){l.trigger("FilesAdded",i)}});l.bind("Silverlight:UploadChunkError",function(o,r,p,s,q){l.trigger("Error",{code:c.IO_ERROR,message:"IO Error.",details:q,file:o.getFile(n[r])})});l.bind("Silverlight:UploadFileProgress",function(o,s,p,r){var q=o.getFile(n[s]);if(q.status!=c.FAILED){q.size=r;q.loaded=p;o.trigger("UploadProgress",q)}});l.bind("Refresh",function(o){var p,q,r;p=document.getElementById(o.settings.browse_button);q=c.getPos(p,document.getElementById(o.settings.container));r=c.getSize(p);c.extend(document.getElementById(o.id+"_silverlight_container").style,{top:q.y+"px",left:q.x+"px",width:r.w+"px",height:r.h+"px"})});l.bind("Silverlight:UploadChunkSuccessful",function(o,r,p,u,t){var s,q=o.getFile(n[r]);s={chunk:p,chunks:u,response:t};o.trigger("ChunkUploaded",q,s);if(q.status!=c.FAILED){e().UploadNextChunk()}if(p==u-1){q.status=c.DONE;o.trigger("FileUploaded",q,{response:t})}});l.bind("Silverlight:UploadSuccessful",function(o,r,p){var q=o.getFile(n[r]);q.status=c.DONE;o.trigger("FileUploaded",q,{response:p})});l.bind("FilesRemoved",function(o,q){var p;for(p=0;p