first draft of the new inline editing toolbar

This commit is contained in:
did 2012-01-16 23:59:59 +01:00
parent 4060b1859f
commit ebe149110b
1659 changed files with 287704 additions and 58 deletions

View File

@ -13,8 +13,8 @@ gem 'mongo', '~> 1.5.2'
gem 'bson_ext', '~> 1.5.2' gem 'bson_ext', '~> 1.5.2'
gem 'mongoid', '~> 2.4.0' gem 'mongoid', '~> 2.4.0'
gem 'locomotive_mongoid_acts_as_tree', :git => 'git@github.com:locomotivecms/mongoid_acts_as_tree.git' gem 'locomotive_mongoid_acts_as_tree', :git => 'git@github.com:locomotivecms/mongoid_acts_as_tree.git'
# gem 'custom_fields', :path => '../gems/custom_fields' # DEV gem 'custom_fields', :path => '../gems/custom_fields' # DEV
gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental' # gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => 'experimental'
gem 'kaminari' gem 'kaminari'
gem 'haml', '~> 3.1.3' gem 'haml', '~> 3.1.3'

View File

@ -0,0 +1,20 @@
#= require jquery
#= require jquery-ui
#= require jquery_ujs
#= require underscore
#= require backbone
#= require locomotive/growl
#= require locomotive/handlebars
#= require locomotive/ICanHandlebarz
#= require locomotive/resize
#= require locomotive/toggle
#= require_self
#= require_tree ./utils
#= require_tree ./models
#= require_tree ./views/inline_editor
window.Locomotive =
mounted_on: '/locomotive' # default path
Models: {}
Collections: {}
Views: {}

View File

@ -43,5 +43,25 @@
//this will reload the page, it's likely better to store this until finished //this will reload the page, it's likely better to store this until finished
document.location.search = kvp.join('&'); document.location.search = kvp.join('&');
} }
window.addJavascript = function(doc, src, options) {
var script = doc.createElement('script');
script.type = 'text/javascript';
script.src = src;
for (var key in options) {
script.setAttribute(key, options[key]);
}
doc.body.appendChild(script);
}
window.addStylesheet = function(doc, src, options) {
var stylesheet = doc.createElement('link');
stylesheet.style = 'text/css';
stylesheet.href = src;
stylesheet.media = 'screen';
stylesheet.rel = 'stylesheet';
doc.head.appendChild(stylesheet);
}
})(); })();

View File

@ -0,0 +1,133 @@
Locomotive.Views.InlinEditor ||= {}
#= require ./toolbar_view
class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View
el: 'body'
initialize: ->
super
@iframe = @$('#page iframe')
@toolbar_view = new Locomotive.Views.InlinEditor.ToolbarView(target: @iframe)
render: ->
super
@decorate_iframe()
set_page: (attributes) ->
@page = new Locomotive.Models.Page(attributes)
window.foo = @page
@toolbar_view.model = @page
@$('#toolbar .inner').html(@toolbar_view.refresh().render().el)
decorate_iframe: ->
console.log('decorating iframe')
iframe = @iframe
iframe.load =>
# add js / css
doc = iframe[0].contentWindow.document
window.addJavascript doc, '/assets/locomotive/aloha/lib/aloha.js',
'data-aloha-plugins': 'common/format,common/highlighteditables,common/list,common/link,common/undo,common/paste'
window.addStylesheet doc, '/assets/locomotive/aloha/css/aloha.css'
# bind the resize event. When the iFrame's size changes, update its height
iframe_content = iframe.contents().find('body')
iframe_content.resize ->
elem = $(this)
if elem.outerHeight(true) > $('body').outerHeight(true) # Resize the iFrame.
iframe.css height: elem.outerHeight(true)
# Resize the iFrame immediately.
iframe_content.resize()
# render: ->
# super
#
# console.log('rendering')
#
# @enable_iframe_auto_height()
#
# @foo()
#
# @toolbar_view = new Locomotive.Views.
#
# set_page: (attributes) ->
# @page = new Locomotive.Models.Page(attributes)
#
# window.foo = @page
#
# @$('#toolbar .inner').html(ich.toolbar(@page.toJSON()))
#
# enable_iframe_auto_height: ->
# console.log('decorating iframe')
#
# console.log(@$('#page iframe html'))
#
# # @$('#page iframe').iframeAutoHeight
# # debug: true
# # callback: (callbackObject) =>
# # $('body').css('overflow': 'visible')
#
# iframe = @$('#page iframe')
# iframe.load =>
# iframe_content = iframe.contents().find('body')
#
# # Bind the resize event. When the iframe's size changes, update its height as
# # well as the corresponding info div.
# iframe_content.resize ->
# elem = $(this)
#
# # Resize the IFrame.
# if elem.outerHeight(true) > $('body').outerHeight(true)
# iframe.css height: elem.outerHeight(true)
#
# # Update the info div width and height.
# # $('#iframe-info').text( 'IFRAME width: ' + elem.width() + ', height: ' + elem.height() );
#
# # Resize the Iframe and update the info div immediately.
# iframe_content.resize()
#
# foo: ->
# @$('#page iframe').load =>
# # console.log 'iframe loaded'
# doc = @$('#page iframe')[0].contentWindow.document
#
# script = doc.createElement('script')
# script.type = 'text/javascript'
# script.src = '/assets/locomotive/aloha/lib/aloha.js'
# script.setAttribute('data-aloha-plugins', 'common/format,common/highlighteditables,common/list,common/link,common/undo,common/paste')
# doc.body.appendChild(script)
#
# stylesheet = doc.createElement('link')
# stylesheet.style = 'text/css'
# stylesheet.href = '/assets/locomotive/aloha/css/aloha.css'
# stylesheet.media = 'screen'
# stylesheet.rel = 'stylesheet'
# doc.head.appendChild(stylesheet)
#
# # <link href="/assets/locomotive/aloha/css/aloha.css" media="screen" rel="stylesheet" type="text/css" />
# # {{ '/assets/locomotive/aloha/css/aloha.css' | stylesheet_tag }}
#
# # $("body", doc).append(script)
#
# # $('body', doc).append('<script src="assets/locomotive/aloha/lib/aloha.js" data-aloha-plugins="common/format,common/highlighteditables,common/list,common/link,common/undo,common/paste,common/block"></script>')
#
# # <script src="/assets/locomotive/aloha/lib/aloha.js" data-aloha-plugins="common/format,common/highlighteditables,common/list,common/link,common/undo,common/paste,common/block"></script>
# # $('body', doc).append('<p>Hello world</p>');
#
#
# # register_page_content: (iframe) ->
# #
# #
# # console.log 'he he'
#
#
#

View File

@ -0,0 +1,43 @@
Locomotive.Views.InlinEditor ||= {}
class Locomotive.Views.InlinEditor.ToolbarView extends Backbone.View
tagName: 'div'
className: 'toolbar-view'
events:
'change .edit input[type=checkbox]': 'toggle_inline_editing'
initialize: ->
super
render: ->
super
$(@el).html(ich.toolbar(@model.toJSON()))
@enable_edit_checkbox()
@
toggle_inline_editing: (event) ->
console.log('toggle_inline_editing !!!')
if $(event.target).is(':checked')
@editable_elements().aloha()
else
@editable_elements().removeClass('aloha-editable-highlight').mahalo()
editable_elements: ->
@options.target[0].contentWindow.Aloha.jQuery('.editable-long-text, .editable-short-text')
enable_edit_checkbox: ->
@$('.edit input[type=checkbox]').checkToggle
on_label_color: '#fff'
off_label_color: '#bbb'
refresh: ->
console.log('refreshing...')
@
remove: ->
super

View File

@ -16,6 +16,8 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
@model = new Locomotive.Models.Page(@options.page) @model = new Locomotive.Models.Page(@options.page)
window.foo = @model
@touched_url = false @touched_url = false
@image_picker_view = new Locomotive.Views.ThemeAssets.ImagePickerView @image_picker_view = new Locomotive.Views.ThemeAssets.ImagePickerView

View File

@ -10,5 +10,5 @@
*= require codemirror/themes/default *= require codemirror/themes/default
*= require locomotive/toggle.css *= require locomotive/toggle.css
*= require locomotive/liquid_mode.css *= require locomotive/liquid_mode.css
*= require_tree ./locomotive *= require_tree ./locomotive/backoffice
*/ */

View File

@ -0,0 +1,8 @@
/*
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require locomotive/blueprint/screen.css
*= require locomotive/toggle.css
*= require_tree ./inline_editor
*/

View File

@ -0,0 +1,16 @@
body {
position: relative;
background: transparent;
// overflow: hidden;
}
#page {
position: relative;
padding-top: 50px;
iframe {
width: 100%;
height: 100%;
display: block;
}
}

View File

@ -0,0 +1,67 @@
@import "compass/css3/images";
@import "compass/css3/opacity";
@import "compass/css3/box-shadow";
@import "compass/css3/text-shadow";
#toolbar {
position: fixed;
top: 0px;
left: 0px;
height: 50px;
width: 100%;
.background {
z-index: 100;
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
@include background-image(linear-gradient(#303138, #23242B));
@include box-shadow(rgba(0, 0, 0, 0.6) 0px -1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 4px 6px 4px, rgba(255, 255, 255, 0.4) 0 1px 0 0 inset);
@include opacity(0.95);
}
.inner {
position: relative;
height: 100%;
z-index: 200;
color: #fff;
h1 {
float: left;
margin: 0 0 0 10px;
position: relative;
top: -2px;
width: 200px;
line-height: 50px;
font-size: 18px;
font-weight: bold;
color: #fff;
@include single-text-shadow(#000, 0px, 1px, 0px);
}
.edit {
float: right;
margin-right: 10px;
line-height: 50px;
.toggleSwitch {
position: relative;
top: 0px;
margin-left: 3px;
display: inline-block;
div.switchArea {
@include box-shadow(rgba(0, 0, 0, 0.8) 0px 1px 0px 0px);
}
}
}
}
}

View File

@ -1,6 +1,6 @@
module Locomotive module Locomotive
module Public module Public
class RenderingController < ApplicationController class PagesController < ApplicationController
include Locomotive::Routing::SiteDispatcher include Locomotive::Routing::SiteDispatcher
@ -8,7 +8,7 @@ module Locomotive
before_filter :require_site before_filter :require_site
before_filter :authenticate_admin!, :only => [:edit] before_filter :authenticate_locomotive_account!, :only => [:edit]
before_filter :validate_site_membership, :only => [:edit] before_filter :validate_site_membership, :only => [:edit]
@ -17,8 +17,12 @@ module Locomotive
end end
def edit def edit
@editing = true if params[:noiframe]
render_locomotive_page @editing = true
render_locomotive_page
else
render :layout => false
end
end end
end end

View File

@ -0,0 +1,34 @@
module Locomotive
class InlineEditorMiddleware
def initialize(app, opts = {})
@app = app
end
def call(env)
response = @app.call(env)
# if env['warden'].authenticated?(:locomotive_account) &&
# !env['PATH_INFO'].starts_with?("/assets") &&
# !env['PATH_INFO'].starts_with?("/#{Locomotive.mounted_on}/") &&
# !env['PATH_INFO'].ends_with?("/edit")
Rails.logger.debug "headers = #{response[1].inspect}"
unless response[1]['Editable'].blank?
Rails.logger.debug "==> #{ENV['PATH_INFO'].inspect}, warden ? #{env['warden'].inspect} /#{env['warden'].user} / #{env['warden'].authenticated?(:locomotive_account)}"
html = response.last.body.to_s.gsub '</body>', %(
<a href="_edit">Edit</a>
</body>
)
[response[0], response[1], [html]]
else
response
end
end
end
end

View File

@ -1,7 +1,7 @@
module Locomotive module Locomotive
class PagePresenter < BasePresenter class PagePresenter < BasePresenter
delegate :title, :slug, :fullpath, :raw_template, :published, :template_changed, :cache_strategy, :to => :source delegate :title, :slug, :fullpath, :raw_template, :published, :listed, :templatized, :redirect, :redirect_url, :template_changed, :cache_strategy, :to => :source
def escaped_raw_template def escaped_raw_template
h(self.source.raw_template) h(self.source.raw_template)
@ -12,7 +12,7 @@ module Locomotive
end end
def included_methods def included_methods
super + %w(title slug fullpath raw_template published published cache_strategy template_changed editable_elements) super + %w(title slug fullpath raw_template published listed templatized redirect redirect_url cache_strategy template_changed editable_elements)
end end
def as_json_for_html_view def as_json_for_html_view

View File

@ -0,0 +1,50 @@
!!! XML
!!!
%html{ :xmlns => 'http://www.w3.org/1999/xhtml' }
%head
%title Locomotive
= csrf_meta_tag
- key = Rails.application.config.session_options[:key]
%meta{ :name => 'key-param', :content => Rails.application.config.session_options[:key] }
%meta{ :name => 'key-token', :content => cookies[key] }
= stylesheet_link_tag 'locomotive/inline_editor', :media => 'screen'
= javascript_include_tag 'locomotive/inline_editor'
%script{ :type => 'text/javascript' }
:plain
window.locale = '#{I18n.locale}';
Locomotive.mounted_on = '#{Locomotive.mounted_on}';
Locomotive.current_site = new Locomotive.Models.Site(#{current_site.to_json});
Locomotive.current_account = new Locomotive.Models.Account(#{current_locomotive_account.to_json});
$(document).ready(function() {
window.application_view = new Locomotive.Views.InlinEditor.ApplicationView({
flash: #{flash.to_json}
});
window.application_view.render();
});
%script{ :type => 'text/html', :id => 'toolbar' }
%h1 {{title}}
.edit
%label Editing mode:
= check_box_tag 'edit', '1', false
%body
#page
%iframe{ :src => %(#{request.fullpath}?noiframe=true') , :scrolling => 'no', :frameborder => '0' }
#toolbar
.background
.inner

View File

@ -65,7 +65,10 @@ Rails.application.routes.draw do
resources :locomotive_entry_submissions, :controller => 'locomotive/public/content_entries', :path => 'entry_submissions/:slug' resources :locomotive_entry_submissions, :controller => 'locomotive/public/content_entries', :path => 'entry_submissions/:slug'
# magic urls # magic urls
match '/' => 'locomotive/public/rendering#show' # match '/editable' => 'locomotive/public/rendering#edit', :path => '/'
match '*path/edit' => 'locomotive/public/rendering#edit' match '/' => 'locomotive/public/pages#show'
match '*path' => 'locomotive/public/rendering#show' match '/edit' => 'locomotive/public/pages#edit'
match '*path/edit' => 'locomotive/public/pages#edit'
# match '_/*path' => 'locomotive/public/rendering#show'
match '*path' => 'locomotive/public/pages#show'
end end

View File

@ -4,45 +4,55 @@ module Liquid
class InlineEditor < ::Liquid::Tag class InlineEditor < ::Liquid::Tag
def render(context) def render(context)
if context.registers[:current_locomotive_account] if context.registers[:current_locomotive_account] && context.registers[:inline_editor]
output = %{ %{
<meta name="locale" content="#{context.registers[:current_locomotive_account].locale}" /> <script type="text/javascript">
<meta name="page-fullpath" content="/#{context.registers[:page].fullpath}" /> window.parent.application_view.set_page(#{context.registers[:page].to_json});
<meta name="edit-page-url" content="#{context.registers[:controller].send(:edit_page_url, context.registers[:page])}" /> </script>
}
if context.registers[:inline_editor]
controller = context.registers[:controller]
output << %{
<meta name="page-url" content="#{context.registers[:controller].send(:page_url, context.registers[:page], :json)}" />
<meta name="page-elements-count" content="#{context.registers[:page].editable_elements.size}" />
<script type="text/javascript" src="/javascripts/admin/aloha/aloha.js"></script>
<script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Format/plugin.js"></script>
<script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Table/plugin.js"></script>
<script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.List/plugin.js"></script>
<script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Link/plugin.js"></script>
<script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.HighlightEditables/plugin.js"></script>
}
if controller.send(:protect_against_forgery?)
output << %(<meta name="csrf-param" content="#{Rack::Utils.escape_html(controller.send(:request_forgery_protection_token))}"/>\n<meta name="csrf-token" content="#{Rack::Utils.escape_html(controller.send(:form_authenticity_token))}"/>).html_safe
end
else
output << %{
<script src="/javascripts/admin/jquery.js" type="text/javascript"></script>
<script src="/javascripts/admin/plugins/cookie.js" type="text/javascript"></script>
}
end
output << %{
<script src="/javascripts/admin/rails.js" type="text/javascript"></script>
<script type="text/javascript" src="/javascripts/admin/inline_editor_toolbar.js"></script>
<script type="text/javascript" src="/javascripts/admin/inline_editor.js"></script>
<link href="/stylesheets/admin/inline_editor.css" media="screen" rel="stylesheet" type="text/css" />
} }
else
''
end end
# if context.registers[:current_locomotive_account]
# output = %{
# <meta name="locale" content="#{context.registers[:current_locomotive_account].locale}" />
# <meta name="page-fullpath" content="/#{context.registers[:page].fullpath}" />
# <meta name="edit-page-url" content="#{context.registers[:controller].send(:edit_page_url, context.registers[:page])}" />
# }
#
# if context.registers[:inline_editor]
# controller = context.registers[:controller]
#
# output << %{
# <meta name="page-url" content="#{context.registers[:controller].send(:page_url, context.registers[:page], :json)}" />
# <meta name="page-elements-count" content="#{context.registers[:page].editable_elements.size}" />
#
# <script type="text/javascript" src="/javascripts/admin/aloha/aloha.js"></script>
# <script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Format/plugin.js"></script>
# <script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Table/plugin.js"></script>
# <script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.List/plugin.js"></script>
# <script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.Link/plugin.js"></script>
# <script type="text/javascript" src="/javascripts/admin/aloha/plugins/com.gentics.aloha.plugins.HighlightEditables/plugin.js"></script>
# }
#
# if controller.send(:protect_against_forgery?)
# output << %(<meta name="csrf-param" content="#{Rack::Utils.escape_html(controller.send(:request_forgery_protection_token))}"/>\n<meta name="csrf-token" content="#{Rack::Utils.escape_html(controller.send(:form_authenticity_token))}"/>).html_safe
# end
# else
# output << %{
# <script src="/javascripts/admin/jquery.js" type="text/javascript"></script>
# <script src="/javascripts/admin/plugins/cookie.js" type="text/javascript"></script>
# }
# end
#
# output << %{
# <script src="/javascripts/admin/rails.js" type="text/javascript"></script>
# <script type="text/javascript" src="/javascripts/admin/inline_editor_toolbar.js"></script>
# <script type="text/javascript" src="/javascripts/admin/inline_editor.js"></script>
# <link href="/stylesheets/admin/inline_editor.css" media="screen" rel="stylesheet" type="text/css" />
# }
# end
end end
end end

View File

@ -33,7 +33,7 @@ module Locomotive
path.gsub!(/\.[a-zA-Z][a-zA-Z0-9]{2,}$/, '') # remove the page extension path.gsub!(/\.[a-zA-Z][a-zA-Z0-9]{2,}$/, '') # remove the page extension
path.gsub!(/^\//, '') # remove the leading slash path.gsub!(/^\//, '') # remove the leading slash
path = 'index' if path.blank? path = 'index' if path.blank? || path == 'edit'
if path != 'index' if path != 'index'
dirname = File.dirname(path).gsub(/^\.$/, '') # also look for templatized page path dirname = File.dirname(path).gsub(/^\.$/, '') # also look for templatized page path
@ -73,8 +73,6 @@ module Locomotive
assigns.merge!(Locomotive.config.context_assign_extensions) assigns.merge!(Locomotive.config.context_assign_extensions)
Rails.logger.debug flash.to_hash.stringify_keys.inspect
assigns.merge!(flash.to_hash.stringify_keys) # data from public submissions assigns.merge!(flash.to_hash.stringify_keys) # data from public submissions
if @page.templatized? # add instance from content type if @page.templatized? # add instance from content type
@ -96,7 +94,8 @@ module Locomotive
def prepare_and_set_response(output) def prepare_and_set_response(output)
flash.discard flash.discard
response.headers['Content-Type'] = 'text/html; charset=utf-8' response.headers['Content-Type'] = 'text/html; charset=utf-8'
response.headers['Editable'] = 'true' unless self.editing_page?
if @page.with_cache? if @page.with_cache?
fresh_when :etag => @page, :last_modified => @page.updated_at.utc, :public => true fresh_when :etag => @page, :last_modified => @page.updated_at.utc, :public => true

View File

@ -43,6 +43,8 @@ module Dummy
# Version of your assets, change this if you want to expire all your assets # Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0' config.assets.version = '1.0'
config.middleware.use 'Locomotive::InlineEditorMiddleware'
end end
end end

View File

@ -0,0 +1,44 @@
.aloha-editable {
min-height: 1.2em;
outline: none;
}
.aloha-editable ::selection,
.aloha-editable ::-moz-selection {
background: #80B5F2;
color: white;
}
.aloha-editable-zerowidthfix {
padding: 0px 5px 0px 5px !important;
}
.aloha-logo {
width:37px !important;
height:25px !important;
background-image: url('../img/gentics-logo.png') !important;
}
.aloha-maximize {
background-image: url('../img/gentics-logo.png') !important;
}
.aloha-fade-out {
background-image: url('../img/fade-out.png') !important;
width:20px !important;
height:20px !important;
}
.aloha-fade-in {
background-image: url('../img/fade-in.png') !important;
width:20px !important;
height:20px !important;
}
.aloha-maximize {
width:20px !important;
height:20px !important;
background-image: url('../img/maximize.png') !important;
}
.aloha-textarea {
overflow: hidden;
border:1px solid #ccc;
padding:6px;
overflow:auto;
}

View File

@ -0,0 +1,284 @@
.aloha-sidebar-bar {
z-index: 999999999;
position: fixed;
top: 0;
left: 0;
text-align: left;
font-family: Arial, sans-serif;
font-size: 12px;
border-right: 1px solid #888;
-moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
}
.aloha-sidebar-bar.aloha-sidebar-right {
left: auto;
right: 0;
border-right-width: 0;
border-left: 1px solid #888;
}
.aloha-sidebar-inner {
position: relative;
background-color: #ccc;
background-image: -webkit-radial-gradient(
rgba(0, 0, 0, 0.0) 20%,
rgba(0, 0, 0, 0.2) 80%,
rgba(0, 0, 0, 0.3) 100%
);
background-image: -moz-radial-gradient(
rgba(0, 0, 0, 0.0) 20%,
rgba(0, 0, 0, 0.2) 80%,
rgba(0, 0, 0, 0.3) 100%
);
}
.aloha-sidebar-panels {
margin: 0;
padding: 15px 0;
}
.aloha-sidebar-panels li {
overflow: hidden;
margin: 0;
padding: 0 15px;
}
.aloha-sidebar-panel-top {
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
}
.aloha-sidebar-panel-content.aloha-sidebar-panel-bottom {
padding-bottom: 0;
-webkit-border-bottom-left-radius: 5px;
-webkit-border-bottom-right-radius: 5px;
-moz-border-radius-bottomleft: 5px;
-moz-border-radius-bottomright: 5px;
border-bottom-width: 1px;
}
.aloha-sidebar-panel-title {
position: relative;
overflow: hidden;
padding: 8px 0 0 8px;
border: 1px solid rgba(0, 0, 0, 0.3);
border-bottom-width: 0;
background-color: #303539;
background-image: -webkit-gradient(
linear,
center top,
center bottom,
color-stop(0.00, #6c6f74),
color-stop(0.05, #4c4f54),
color-stop(0.10, #3f4448),
color-stop(0.45, #383d41),
color-stop(0.50, #303539),
color-stop(0.95, #33363b)
);
background-image: -moz-linear-gradient(
center top,
#6c6f74 0%,
#4c4f54 5%,
#3f4448 10%,
#383d41 45%,
#303539 50%,
#33363b 95%
);
color: #ccc;
cursor: pointer;
font-size: 13px;
font-weight: bold;
line-height: 1.5em;
text-shadow: 0 0 6px #23262b;
}
.aloha-sidebar-panel-title:hover {
color: #fff;
}
.aloha-sidebar-panel-title-arrow {
position: absolute;
top: 8px;
left: 8px;
width: 16px;
height: 16px;
background: url(../img/arrow.png) no-repeat center center;
opacity: 0.3;
}
.aloha-sidebar-panel-title:hover .aloha-sidebar-panel-title-arrow {
opacity: 0.9;
}
.aloha-sidebar-panel-title .aloha-sidebar-panel-title-arrow.aloha-sidebar-panel-title-arrow-down {
background-image: url(../img/arrow-down.png);
}
.aloha-sidebar-panel-title-text {
margin-left: 24px;
white-space: nowrap;
}
.aloha-sidebar-panel-content {
overflow: hidden;
height: 5px;
background: #303539;
}
.aloha-sidebar-panel-content-inner {
margin: 5px 0 0;
padding: 0;
color: #888;
background: #fff;
background-image: -webkit-radial-gradient(
rgba(0, 0, 0, 0.0) 50%,
rgba(0, 0, 0, 0.1) 90%,
rgba(0, 0, 0, 0.2) 100%
);
background-image: -moz-radial-gradient(
rgba(0, 0, 0, 0.0) 50%,
rgba(0, 0, 0, 0.1) 90%,
rgba(0, 0, 0, 0.2) 100%
);
}
.aloha-sidebar-panel-content input {
margin: 5px;
padding: 5px;
}
.aloha-sidebar-handle {
position: absolute;
top: 30px;
right: -30px;
width: 40px;
height: 30px;
overflow: hidden;
background-color: #303539;
background-image: -webkit-gradient(
linear,
center top,
center bottom,
color-stop(0.00, #6c6f74),
color-stop(0.05, #4c4f54),
color-stop(0.10, #3f4448),
color-stop(0.45, #383d41),
color-stop(0.50, #303539),
color-stop(0.95, #33363b)
);
background-image: -moz-linear-gradient(
center top,
#6c6f74 0%,
#4c4f54 5%,
#3f4448 10%,
#383d41 45%,
#303539 50%,
#33363b 95%
);
cursor: pointer;
-moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
box-shadow: 0px 0px 10px rgba(0, 0, 0, .5);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
-moz-border-radius-topright: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-top-right-radius: 5px;
-webkit-border-bottom-right-radius: 5px;
}
.aloha-sidebar-right .aloha-sidebar-handle {
left: -30px;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
-moz-border-radius-topright: 0px;
-moz-border-radius-bottomright: 0px;
-webkit-border-top-right-radius: 0px;
-webkit-border-bottom-right-radius: 0px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-bottomleft: 5px;
-webkit-border-top-left-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
.aloha-sidebar-handle-icon {
display: block;
position: absolute;
top: 5px;
right: 5px;
background: url(../img/arrow.png) no-repeat center center;
width: 20px;
height: 20px;
opacity: 0.5;
}
.aloha-sidebar-right .aloha-sidebar-handle-icon {
left: 5px;
right: auto;
}
.aloha-sidebar-handle-icon.aloha-sidebar-handle-icon-left {
background-image: url(../img/arrow-left.png);
}
.aloha-sidebar-panel-parent-path {
background-color: #303539;
background-image: -webkit-gradient(
linear,
center top,
center bottom,
color-stop(0.00, #6c6f74),
color-stop(0.05, #4c4f54),
color-stop(0.10, #3f4448),
color-stop(0.45, #383d41),
color-stop(0.50, #303539),
color-stop(0.95, #33363b)
);
background-image: -moz-linear-gradient(
center top,
#6c6f74 0%,
#4c4f54 5%,
#3f4448 10%,
#383d41 45%,
#303539 50%,
#33363b 95%
);
color: #fff;
cursor: pointer;
font-size: 12px;
text-shadow: 0 0 6px #23262b;
white-space: nowrap;
line-height: 1em;
}
.aloha-sidebar-panel-parent-path:hover {
opacity: 0.9;
}
.aloha-sidebar-panel-parent-path span {
display: inline-block;
padding: 0 10px 0 5px;
background: url(../img/breadcrumb-divider.png) no-repeat right center;
opacity: 0.25;
line-height: 1.6em;
}
.aloha-sidebar-panel-parent-path span:first-child {
font-weight: bold;
opacity: 0.8;
}
.aloha-sidebar-panel-parent-path span:last-child {
background: none;
}
.aloha-sidebar-panel-parent-content {
padding: 4px;
background-image: -webkit-gradient(
linear,
center top,
center bottom,
color-stop(0.0, rgba(0, 0, 0, 0.25)),
color-stop(0.05, rgba(0, 0, 0, 0.0))
);
background-image: -moz-linear-gradient(
center top,
rgba(0, 0, 0, 0.25) 0%,
rgba(0, 0, 0, 0.0) 5%
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
.aloha-floatingmenu table, .aloha-floatingmenu tr, .aloha-floatingmenu td,
.ext-root table, .ext-root tr, .ext-root td,
table.x-layer, .x-layer tr, .x-layer td,
table.x-window, .x-window tr, .x-window td,
table.x-toolbar-ct, .x-toolbar-ct tr, .x-toolbar-ct td,
table.x-toolbar-left, .x-toolbar-left tr, .x-toolbar-left td,
table.x-table-layout, .x-table-layout tr, .x-table-layout td,
table.x-toolbar-ct table, .x-toolbar-cell td, .x-table-layout-cell td {
width: 0 !important;
height: 0 !important;
border: 0 !important;
padding: 0 !important;
margin: 0 !important;
background-color: transparent !important;
}
/* reset for floatingmenu */
div.aloha-floatingmenu, div.aloha-shadow {
min-width: 0px;
}
/* more specific for browsers that support nth-child. */
.ext-root tr:nth-child(2n), .ext-root tr:nth-child(3n), .ext-root tr:nth-child(4n), .ext-root tr:nth-child(5n) {
width: 0 !important;
height: 0 !important;
border: 0 !important;
padding: 0 !important;
margin: 0 !important;
background-color: transparent !important;
}
.ext-root ol, .ext-root ul, .x-menu ul, .x-menu ol {
list-style:none;
margin:0;
padding:0;
}

View File

@ -0,0 +1,357 @@
.aloha-floatingmenu {
position: absolute;
}
.aloha-floatingmenu.fixed {
position: fixed !important;
}
.aloha-floatingmenu .x-tab-panel-header {
border-width: 0px 0px 1px 0px !important;
background: none !important;
}
.aloha-floatingmenu .x-tab-panel-header .x-tab-strip-wrap, .aloha-floatingmenu ul.x-tab-strip-top {
background: none !important;
}
.x-tab-strip-wrap ul li a:hover {
border: none;
text-decoration:none;
}
.aloha-floatingmenu .x-tab-panel-header {
padding-bottom: 0px !important;
}
.aloha-floatingmenu:hover {
background-color: #E0E0E0;
-moz-border-radius-topleft: 3px;
-moz-border-radius-topright: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-top-right-radius: 3px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
background: url(../img/grabhandle.png) no-repeat scroll center 5px rgba(0, 0, 0, 0.4);
}
.aloha-floatingmenu:hover .aloha-floatingmenu-pin {
display: block;
}
.aloha-floatingmenu-pin {
background: url("../img/pin.png") no-repeat scroll 0 6px transparent;
cursor: pointer;
margin-left: 5px !important;
width: 16px;
position: absolute !important;
right: 10px;
display: none;
}
.aloha-floatingmenu-pinned {
background-position: -16px 6px;
}
#aloha-floatingmenu-shadow {
position: absolute;
top: -1000;
left: -1000;
display: none;
z-index: 8800;
}
#aloha-floatingmenu-shadow.fixed {
position: fixed !important;
}
.x-tree-root-ct {
padding: 0px;
}
.x-tree-node {
list-style: none;
}
.x-tree-node-ct {
padding: 0px;
}
.aloha-button a:focus {
outline: none;
}
button.aloha-button {
background: url('../img/base.png') no-repeat !important;
}
button.aloha-button-big {
background: url('../img/base-big.png') no-repeat !important;
}
button.aloha-button-b {
background-position: 0px 0px;
}
button.aloha-button-i {
background-position: -16px 0px !important;
}
button.aloha-button-u {
background-position: -32px 0px !important;
}
button.aloha-button-del {
background-position: -48px 0px !important;
}
button.aloha-button-sub {
background-position: -64px 0px !important;
}
button.aloha-button-sup {
background-position: -80px 0px !important;
}
button.aloha-button-ul {
background-position: -256px 0px !important;
}
button.aloha-button-ol {
background-position: -272px 0px !important;
}
button.aloha-button-indent-list {
background-image: url(../img/text_indent.png) !important;
}
button.aloha-button-outdent-list {
background-image: url(../img/text_indent_remove.png) !important;
}
button.aloha-button-a {
background-position: -288px 0px !important;
}
button.aloha-button-a-remove {
background-position: -304px 0px !important;
}
button.aloha-button-p {
background-position: 0px 0 !important;
}
button.aloha-button-p-de {
background-position: 0px -42px !important;
}
button.aloha-button-h1 {
background-position: -52px 0 !important;
}
button.aloha-button-h1-de {
background-position: -52px -42px !important;
}
button.aloha-button-h2 {
background-position: -104px 0 !important;
}
button.aloha-button-h2-de {
background-position: -104px -42px !important;
}
button.aloha-button-h3 {
background-position: -156px 0 !important;
}
button.aloha-button-h3-de {
background-position: -156px -42px !important;
}
button.aloha-button-h4 {
background-position: -208px 0 !important;
}
button.aloha-button-h4-de {
background-position: -208px -42px !important;
}
button.aloha-button-h5 {
background-position: -260px 0 !important;
}
button.aloha-button-h5-de {
background-position: -260px -42px !important;
}
button.aloha-button-h6 {
background-position: -312px 0 !important;
}
button.aloha-button-h6-de {
background-position: -312px -42px !important;
}
button.aloha-button-pre {
background-position: -364px 0 !important;
}
button.aloha-button-pre-de {
background-position: -364px -42px !important;
}
button.aloha-button-title {
background-position: -416px 0 !important;
}
button.aloha-button-title-de {
background-position: -416px -42px !important;
}
button.aloha-button-table {
background-position: -96px 0 !important;
}
button.aloha-button-addColumnLeft {
background-position: -112px 0 !important;
}
button.aloha-button-addColumnRight {
background-position: -128px 0 !important;
}
button.aloha-button-addRowBefore {
background-position: -144px 0 !important;
}
button.aloha-button-addRowAfter {
background-position: -160px 0 !important;
}
button.aloha-button-deleteRows {
background-position: -176px 0 !important;
}
button.aloha-button-deleteColumns {
background-position: -192px 0 !important;
}
button.aloha-button-addPerson {
background-position: -224px 0 !important;
}
button.aloha-button-addEvent {
background-position: -208px 0 !important;
}
button.aloha-button-abbr {
background-position: -336px 0px !important;
}
button.aloha-button-row-header {
background-position: -352px 0px !important;
}
button.aloha-button-col-header {
background-position: -368px 0px !important;
}
button.aloha-button-tree {
background-position: -0 0 !important;
}
div.aloha-button a:hover {
border: 1px solid #b2cbff;
}
.aloha-floatingmenu {
display: none;
}
.aloha-floatingmenu .pressed a {
border:1px solid #ccc;
}
html body #aloha-ribbon {
z-index:90000;
position:fixed;
top:0;
left:0;
width:100%;
padding-left:0;
padding-right:0;
display: none;
}
.x-menu-list-item {
text-align:left;
}
.aloha-shadow {
-moz-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3);
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3);
}
ul.aloha-multisplit button.aloha-button {
background-image: url(../img/base-multi.png) !important;
background-repeat: no-repeat;
background-position: 0 0;
width: 54px !important;
height: 44px !important;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.aloha-multisplit {
float: left;
list-style: none;
}
ul.aloha-multisplit, div.aloha-multisplit-wrapper {
width: 232px;
height: 46px;
overflow: hidden;
margin: 0px;
padding: 0px;
}
div.aloha-multisplit-wrapper {
width: 248px;
}
ul.aloha-multisplit li {
float: left;
padding: 0px 1px;
margin: 0px;
}
ul.aloha-multisplit button.aloha-multisplit-wide {
font: 11px tahoma,arial,helvetica;
height: 18px !important;
width: 234px !important;
border: 0;
border-top: 1px solid #cccccc;
cursor: pointer;
background-color: white;
background-repeat: no-repeat;
background-position: 6px 1px !important;
text-align: left;
margin-left: -2px;
padding-left: 26px;
}
button.aloha-multisplit-toggle {
float: right;
margin: 0;
padding: 0;
width: 15px;
height: 12px;
margin-top: 32px;
cursor: pointer;
border: 0;
}
button.aloha-multisplit-toggle-open {
background-image: url(../img/multisplit-open.gif);
}
button.aloha-multisplit-toggle-close {
background-image: url(../img/multisplit-close.gif);
}
ul.aloha-multisplit button {
border: 1px solid #cccccc;
margin: 1px;
cursor: pointer;
}
ul.aloha-multisplit button:hover {
border: 1px solid #666666;
}
ul.aloha-multisplit button.aloha-multisplit-wide:hover {
border: 0;
border-top: 1px solid #cccccc;
}
ul.aloha-multisplit .aloha-multisplit-activeitem button {
border: 2px solid #3B73D7;
}
ul.aloha-multisplit-expanded {
position: absolute;
top: 0px;
left: 0px;
z-index: 20000;
background-color: white;
border: 1px solid #afafaf;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
ul.aloha-multisplit button.aloha-button-removeFormat {
background-image: url(../img/removeformat.png) !important;
}
/**
* combo-list box
*/
.x-combo-list {
text-align: left;
}
.x-combo-list-inner {
padding: 3px;
}
.x-combo-list-inner .loading-indicator {
margin: 0;
}
.x-combo-selected {
color: #fff !important;
background-color: #3B73D7 !important;
}
/**
* ui-attribute field
*/
.x-form-field.x-form-text {
color: #333 !important;
padding: 3px;
}
.x-form-field.x-form-text::selection {
color: #fff;
background: #3B73D7;
}
.x-form-field.x-form-text::-moz-selection {
color: #fff;
background: #3B73D7;
}
.x-form-field.x-form-text::-webkit-selection {
color: #fff;
background: #3B73D7;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,246 @@
/*!
* CommandManager file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with CommandManager program. If not, see <http://www.gnu.org/licenses/>.
*/
define( [ 'aloha/core', 'aloha/registry', 'aloha/engine', 'util/dom', 'aloha/contenthandlermanager' ],
function( Aloha, Registry, Engine, Dom, ContentHandlerManager ) {
// Action: What the command does when executed via execCommand(). Every command defined
// in CommandManager specification has an action defined for it in the relevant section. For example,
// the bold command's action generally makes the current selection bold, or removes bold if
// the selection is already bold. An editing toolbar might provide buttons that execute the
// action for a command if clicked, or a script might run an action without user interaction
// to achieve some particular effect.
//
// Indeterminate: A boolean value returned by queryCommandIndeterm(), depending on the
// current state of the document. Generally, a command that has a state defined will be
// indeterminate if the state is true for part but not all of the current selection, and a
// command that has a value defined will be indeterminate if different parts of the
// selection have different values. An editing toolbar might display a button or control
// in a special way if the command is indeterminate, like showing a "bold" button as
// partially depressed, or leaving a font size selector blank instead of showing the font
// size of the current selection. As a rule, a command can only be indeterminate if its
// state is false, supposing it has a state.
//
// State: A boolean value returned by queryCommandState(), depending on the current state
// of the document. The state of a command is true if it is already in effect, in some
// sense specific to the command. Most commands that have a state defined will take opposite
// actions depending on whether the state is true or false, such as making the selection
// bold if the state is false and removing bold if the state is true. Others will just
// have no effect if the state is true, like the justifyCenter command. Still others will
// have the same effect regardless, like the styleWithCss command. An editing toolbar might
// display a button or control differently depending on the state and indeterminacy of the
// command.
//
// Value: A string returned by queryCommandValue(), depending on the current state of the
// document. A command usually has a value instead of a state if the property it modifies
// can take more than two different values, like the foreColor command. If the command is
// indeterminate, its value is generally based on the start of the selection. Otherwise,
// in most cases the value holds true for the entire selection, but see the justifyCenter
// command and its three companions for an exception. An editing toolbar might display the
// value of a command as selected in a drop-down or filled in in a text box, if the command
// isn't indeterminate.
//
// Relevant CSS property: CommandManager is defined for certain inline formatting commands, and
// is used in algorithms specific to those commands. It is an implementation detail, and
// is not exposed to authors. If a command does not have a relevant CSS property
// specified, it defaults to null.
var CommandManager = {
execCommand: function( commandId, showUi, value, range ) {
// Read current selection if not passed
if ( !range ) {
if ( !Aloha.getSelection().getRangeCount() ) {
return;
}
range = Aloha.getSelection().getRangeAt( 0 );
}
// For the insertHTML command we provide contenthandler API
if ( commandId == 'insertHTML' ) {
//if (typeof Aloha.settings.contentHandler.insertHtml === 'undefined') {
// use all registered content handler; used for copy & paste atm (or write log message)
// Aloha.settings.contentHandler.insertHtml = Aloha.defaults.contentHandler.insertHtml;
//}
value = ContentHandlerManager.handleContent( value, {
contenthandler: Aloha.settings.contentHandler.insertHtml
});
}
Engine.execCommand( commandId, showUi, value, range );
// Read range after engine modification
range = Aloha.getSelection().getRangeAt( 0 );
// FIX: doCleanup should work with W3C range
var startnode = range.commonAncestorContainer.parentNode;
var rangeObject = new window.GENTICS.Utils.RangeObject();
rangeObject.startContainer = range.startContainer;
rangeObject.startOffset = range.startOffset;
rangeObject.endContainer = range.endContainer;
rangeObject.endOffset = range.endOffset;
Dom.doCleanup({merge:true, removeempty: false}, rangeObject, startnode);
rangeObject.select();
Aloha.trigger('aloha-command-executed', commandId);
},
// If command is available and not disabled or the active range is not null
// the command is enabled
queryCommandEnabled: function( commandId, range ) {
// Take current selection if not passed
if ( !range ) {
if ( !Aloha.getSelection().getRangeCount() ) {
return;
}
range = Aloha.getSelection().getRangeAt(0);
}
return Engine.queryCommandEnabled( commandId, range );
},
// "Return true if command is indeterminate, otherwise false."
queryCommandIndeterm: function( commandId, range ) {
// Take current selection if not passed
if ( !range ) {
if ( !Aloha.getSelection().getRangeCount() ) {
return;
}
range = Aloha.getSelection().getRangeAt(0);
}
return Engine.queryCommandIndeterm( commandId, range );
},
queryCommandState: function( commandId, range ) {
// Take current selection if not passed
if ( !range ) {
if ( !Aloha.getSelection().getRangeCount() ) {
return;
}
range = Aloha.getSelection().getRangeAt(0);
}
return Engine.queryCommandState( commandId, range );
},
// "When the queryCommandSupported(command) method on the HTMLDocument
// interface is invoked, the user agent must return true if command is
// supported, and false otherwise."
queryCommandSupported: function( commandId ) {
return Engine.queryCommandSupported( commandId );
},
queryCommandValue: function( commandId, range ) {
// Take current selection if not passed
if ( !range ) {
if ( !Aloha.getSelection().getRangeCount() ) {
return;
}
range = Aloha.getSelection().getRangeAt(0);
}
// "Return command's value."
return Engine.queryCommandValue( commandId, range );
},
querySupportedCommands: function() {
var
commands = [],
command;
for ( command in Engine.commands ) {
commands.push( command );
}
return commands;
}
};
// create an instance
CommandManager = new ( Registry.extend( CommandManager ) )();
/**
* Executes a registered command.
* http://aryeh.name/spec/editing/editing.html#methods-of-the-htmldocument-interface
* @method
* @param command name of the command
* @param showUI has no effect for Aloha Editor and is only here because in spec...
* @param value depends on the used command and it impementation
* @range optional a range on which the command will be executed if not specified
* the current selection will be used as range
*/
Aloha.execCommand = CommandManager.execCommand;
/**
* Check wheater the command in enabled.
* If command is not supported, raise a NOT_SUPPORTED_ERR exception.
* @param command name of the command
* @return true if command is enabled, false otherwise.
*/
Aloha.queryCommandEnabled = CommandManager.queryCommandEnabled;
/**
* Check if the command has an indetermed state.
* If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
* If command has no indeterminacy, INVALID_ACCESS_ERR exception is thrown
* If command is not enabled, return false.
* @param command name of the command
* @range optional a range on which the command will be executed if not specified
* the current selection will be used as range
* @return true if command is indeterminate, otherwise false.
*/
Aloha.queryCommandIndeterm = CommandManager.queryCommandIndeterm;
/**
* Returns the state of a given command
* If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
* If command has no state, an INVALID_ACCESS_ERR exception is thrown
* If command is not enabled, return false
* If the state override for command is set, it returns the state
* @param command name of the command
* @return state override or true if command's state is true, otherwise false.
*/
Aloha.queryCommandState = CommandManager.queryCommandState;
/**
* Check if a given command is supported
* @return true if command is supported, and false otherwise.
*/
Aloha.queryCommandSupported = CommandManager.queryCommandSupported;
/**
* Returns the Value of a given Command
* If command is not supported, a NOT_SUPPORTED_ERR exception is thrown
* If command is not enabled, returns an empty string
* If command is "fontSize" and its value override is set, an integer
* number of pixels is returned as font size for the result.
* If the value override for command is set, it returns that.
* @return command's value.
*/
Aloha.queryCommandValue = CommandManager.queryCommandValue;
Aloha.querySupportedCommands = CommandManager.querySupportedCommands;
return CommandManager;
});

View File

@ -0,0 +1,330 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/core', 'util/class', 'aloha/jquery'],
function(Aloha, Class, jQuery ) {
var
// $ = jQuery,
// Aloha = window.Aloha,
console = window.console;
// Class = window.Class
// GENTICS = window.GENTICS;
/**
* This is the aloha Log
* @namespace Aloha
* @class Log
* @singleton
*/
var alohaConsole = Class.extend({
/**
* Initialize the logging
* @hide
*/
init: function() {
// initialize the logging settings (if not present)
if (typeof Aloha.settings.logLevels === 'undefined' || !Aloha.settings.logLevels) {
Aloha.settings.logLevels = {'error' : true, 'warn' : true};
}
// initialize the logHistory settings (if not present)
if (typeof Aloha.settings.logHistory === 'undefined' || !Aloha.settings.logHistory) {
Aloha.settings.logHistory = {};
}
// set the default values for the loghistory
if (!Aloha.settings.logHistory.maxEntries) {
Aloha.settings.logHistory.maxEntries = 100;
}
if (!Aloha.settings.logHistory.highWaterMark) {
Aloha.settings.logHistory.highWaterMark = 90;
}
if (!Aloha.settings.logHistory.levels) {
Aloha.settings.logHistory.levels = {'error' : true, 'warn' : true};
}
this.flushLogHistory();
Aloha.trigger('aloha-logger-ready');
},
/**
* Log History as array of Message Objects. Every object has the properties
* 'level', 'component' and 'message'
* @property
* @type Array
* @hide
*/
logHistory: [],
/**
* Flag, which is set as soon as the highWaterMark for the log history is reached.
* This flag is reset on every call of flushLogHistory()
* @hide
*/
highWaterMarkReached: false,
/**
* Logs a message to the console
* @method
* @param {String} level Level of the log ('error', 'warn' or 'info', 'debug')
* @param {String} component Component that calls the log
* @param {String} message log message
*/
log: function(level, component, message) {
// log ('Logging message');
if ( typeof component === 'undefined' ) {
message = level;
}
if ( typeof component !== 'string' && component && component.toString ) {
component = component.toString();
}
// log ('warn', 'Warning message');
if ( typeof message === 'undefined' ) {
message = component;
component = undefined;
}
if (typeof level === 'undefined' || !level) {
level = 'log';
}
level = level.toLowerCase();
if ( typeof Aloha.settings.logLevels === "undefined" ) {
return;
}
// now check whether the log level is activated
if ( !Aloha.settings.logLevels[ level ] ) {
return;
}
component = component || "Unkown Aloha Component";
this.addToLogHistory({'level' : level, 'component' : component, 'message' : message, 'date' : new Date()});
switch (level) {
case 'error':
if (window.console && console.error) {
// FIXME:
// Using console.error rather than throwing an error is very
// problematic because we get not stack.
// We ought to consider doing the following:
// throw component + ': ' + message;
if(!component && !message) {
console.error("Error occured without message and component");
} else {
console.error(component + ': ' + message);
}
}
break;
case 'warn':
if (window.console && console.warn) {
console.warn(component + ': ' + message);
}
break;
case 'info':
if (window.console && console.info) {
console.info(component + ': ' + message);
}
break;
case 'debug':
if (window.console && console.log) {
console.log(component + ' [' + level + ']: ' + message);
}
break;
default:
if (window.console && console.log) {
console.log(component + ' [' + level + ']: ' + message);
}
break;
}
},
/**
* Log a message of log level 'error'
* @method
* @param {String} component Component that calls the log
* @param {String} message log message
*/
error: function(component, message) {
this.log('error', component, message);
},
/**
* Log a message of log level 'warn'
* @method
* @param {String} component Component that calls the log
* @param {String} message log message
*/
warn: function(component, message) {
this.log('warn', component, message);
},
/**
* Log a message of log level 'info'
* @method
* @param {String} component Component that calls the log
* @param {String} message log message
*/
info: function(component, message) {
this.log('info', component, message);
},
/**
* Log a message of log level 'debug'
* @param {String} component Component that calls the log
* @param {String} message log message
*/
debug: function(component, message) {
this.log('debug', component, message);
},
/**
* Methods to mark function as deprecated for developers.
* @param {String} component String that calls the log
* @param {String} message log message
*/
deprecated: function(component, message) {
this.log( 'warn', component, message );
// help the developer to locate the call.
if ( Aloha.settings.logLevels[ 'deprecated' ] ) {
throw new Error ( message );
}
},
/**
* Check whether the given log level is currently enabled
* @param {String} level
* @return true when log level is enabled, false if not
*/
isLogLevelEnabled: function(level) {
return Aloha.settings && Aloha.settings.logLevels && Aloha.settings.logLevels[level];
},
/**
* Check whether error logging is enabled
* @return true if error logging is enabled, false if not
*/
isErrorEnabled: function() {
return this.isLogLevelEnabled('error');
},
/**
* Check whether warn logging is enabled
* @return true if warn logging is enabled, false if not
*/
isWarnEnabled: function() {
return this.isLogLevelEnabled('warn');
},
/**
* Check whether info logging is enabled
* @return true if info logging is enabled, false if not
*/
isInfoEnabled: function() {
return this.isLogLevelEnabled('info');
},
/**
* Check whether debug logging is enabled
* @return true if debug logging is enabled, false if not
*/
isDebugEnabled: function() {
return this.isLogLevelEnabled('debug');
},
/**
* Add the given entry to the log history. Check whether the highWaterMark has been reached, and fire an event if yes.
* @param {Object} entry entry to be added to the log history
* @hide
*/
addToLogHistory: function(entry) {
if ( !Aloha.settings.logHistory ) {
this.init();
}
// when maxEntries is set to something illegal, we do nothing (log history is disabled)
// check whether the level is one we like to have logged
if ( Aloha.settings.logHistory.maxEntries <= 0
|| !Aloha.settings.logHistory.levels[ entry.level ]
) {
return;
}
// first add the entry as last element to the history array
this.logHistory.push( entry );
// check whether the highWaterMark was reached, if so, fire an event
if ( !this.highWaterMarkReached ) {
if ( this.logHistory.length >= Aloha.settings.logHistory.maxEntries * Aloha.settings.logHistory.highWaterMark / 100 ) {
// fire the event
Aloha.trigger('aloha-log-full');
// set the flag (so we will not fire the event again until the logHistory is flushed)
this.highWaterMarkReached = true;
}
}
// check whether the log is full and eventually remove the oldest entries
while ( this.logHistory.length > Aloha.settings.logHistory.maxEntries ) {
this.logHistory.shift();
}
},
/**
* Get the log history
* @return log history as array of objects
* @hide
*/
getLogHistory: function() {
return this.logHistory;
},
/**
* Flush the log history. Remove all log entries and reset the flag for the highWaterMark
* @return void
* @hide
*/
flushLogHistory: function() {
this.logHistory = [];
this.highWaterMarkReached = false;
}
});
/**
* Create the Log object
* @hide
*/
alohaConsole = new alohaConsole();
// add to log namespace for compatiblility.
return Aloha.Log = Aloha.Console = alohaConsole;
});

View File

@ -0,0 +1,65 @@
/*!
* Aloha Editor
* Author & Copyright (c) 2010 Gentics Software GmbH
* aloha-sales@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/
define(
['aloha/jquery', 'aloha/registry'],
function( jQuery, Registry ) {
/**
* Create an contentHandler from the given definition. Acts as a factory method
* for contentHandler.
*
* @param {Object} definition
*/
return new ( Registry.extend({
createHandler: function( definition ) {
if ( typeof definition.handleContent != 'function' ) {
throw 'ContentHandler has no function handleContent().';
}
var AbstractContentHandler = Class.extend({
handleContent: function( content ) {
// Implement in subclass!
}
}, definition);
return new AbstractContentHandler();
},
handleContent: function ( content, options ) {
var handler,
handlers = this.getEntries();
if ( typeof options.contenthandler === 'undefined') {
options.contenthandler = [];
for ( handler in handlers ) {
if ( handlers.hasOwnProperty(handler) ) {
options.contenthandler.push(handler);
}
}
}
for ( handler in handlers ) {
if ( handlers.hasOwnProperty(handler) ) {
if (jQuery.inArray( handler, options.contenthandler ) < 0 ) {
continue;
}
if ( typeof handlers[handler].handleContent === 'function') {
content = handlers[handler].handleContent( content, options );
} else {
console.error( 'A valid content handler needs the method handleContent.' );
}
}
}
return content;
}
}))();
});

View File

@ -0,0 +1,560 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
[
'aloha/jquery',
'aloha/pluginmanager'
],
function ( jQuery, PluginManager ) {
//----------------------------------------
// Private variables
//----------------------------------------
/**
* Hash table that will be populated through the loadPlugins method.
* Maps the names of plugins with their urls for easy assess in the getPluginsUrl method
*/
var pluginPaths = {};
/**
* Base Aloha Object
* @namespace Aloha
* @class Aloha The Aloha base object, which contains all the core functionality
* @singleton
*/
jQuery.extend(true, Aloha, {
/**
* The Aloha Editor Version we are using
* It should be set by us and updated for the particular branch
* @property
*/
version: '0.10.0',
/**
* Array of editables that are managed by Aloha
* @property
* @type Array
*/
editables: [],
/**
* The currently active editable is referenced here
* @property
* @type Aloha.Editable
*/
activeEditable: null,
/**
* settings object, which will contain all Aloha settings
* @cfg {Object} object Aloha's settings
*/
settings: {},
/**
* defaults object, which will contain all Aloha defaults
* @cfg {Object} object Aloha's settings
*/
defaults: {},
/**
* Namespace for ui components
*/
ui: {},
/**
* This represents the name of the users OS. Could be:
* 'Mac', 'Linux', 'Win', 'Unix', 'Unknown'
* @property
* @type string
*/
OSName: 'Unknown',
/**
* Which stage is the aloha init process at?
* @property
* @type string
*/
stage: 'loadingAloha',
/**
* A list of loaded plugin names. Available after the
* "loadPlugins" stage.
*
* @property
* @type array
* @internal
*/
loadedPlugins: [],
requirePaths: [],
/**
* Initialize the initialization process
*/
init: function () {
// merge defaults and settings and provide all in settings
Aloha.settings = jQuery.extendObjects( true, {}, Aloha.defaults, Aloha.settings );
// initialize rangy. This is probably necessary here,
// because due to the current loading mechanism, rangy
// doesn't initialize itself in all browsers
if (window.rangy) {
window.rangy.init();
}
// Load & Initialise
Aloha.stage = 'loadPlugins';
Aloha.loadPlugins(function(){
Aloha.stage = 'initAloha';
Aloha.initAloha(function(){
Aloha.stage = 'initPlugins';
Aloha.initPlugins(function(){
Aloha.stage = 'initGui';
Aloha.initGui(function(){
Aloha.stage = 'alohaReady';
Aloha.trigger('aloha-ready');
});
});
});
});
},
/**
* Load Plugins
*/
loadPlugins: function (next) {
// contains an array like [common/format, common/block]
var configuredPluginsWithBundle = this.getPluginsToBeLoaded();
if (configuredPluginsWithBundle.length) {
var paths = {},
pluginNames = [],
requiredInitializers = [],
pathsToPlugins = {};
// Background: We do not use CommonJS packages for our Plugins
// as this breaks the loading order when these modules have
// other dependencies.
// We "emulate" the commonjs modules with the path mapping.
/* require(
* { paths: {
* 'format': 'plugins/common/format/lib',
* 'format/nls': 'plugins/common/format/nls',
* ... for every plugin ...
* }
* },
* ['format/format-plugin', ... for every plugin ...],
* next <-- when everything is loaded, we continue
*/
jQuery.each(configuredPluginsWithBundle, function (i, configuredPluginWithBundle) {
var tmp, bundleName, pluginName, bundlePath = '';
tmp = configuredPluginWithBundle.split('/');
bundleName = tmp[0];
pluginName = tmp[1];
// TODO assertion if pluginName or bundleName NULL _-> ERROR!!
if (Aloha.settings.basePath) {
bundlePath = Aloha.settings.basePath;
}
if (Aloha.settings.bundles && Aloha.settings.bundles[bundleName]) {
bundlePath += Aloha.settings.bundles[bundleName];
} else {
bundlePath += '../plugins/' + bundleName;
}
pluginNames.push(pluginName);
paths[pluginName] = bundlePath + '/' + pluginName + '/lib';
pathsToPlugins[pluginName] = bundlePath + '/' + pluginName;
// As the "nls" path lies NOT inside /lib/, but is a sibling to /lib/, we need
// to register it explicitely. The same goes for the "css" folder.
jQuery.each(['nls', 'css', 'vendor', 'res'], function() {
paths[pluginName + '/' + this] = bundlePath + '/' + pluginName + '/' + this;
});
requiredInitializers.push(pluginName + '/' + pluginName + '-plugin');
});
this.loadedPlugins = pluginNames;
this.requirePaths = paths;
// Main Require.js loading call, which fetches all the plugins.
require(
{
context: 'aloha',
paths: paths,
locale: this.settings.locale || this.defaults.locale || 'en'
},
requiredInitializers,
next
);
pluginPaths = pathsToPlugins;
} else {
next();
}
},
/**
* Fetches plugins the user wants to have loaded. Returns all plugins the user
* has specified with the data-plugins property as array, with the bundle
* name in front.
*
* @return array
* @internal
*/
getPluginsToBeLoaded: function() {
// look for data-aloha-plugins attributes and load values
var
plugins = jQuery('[data-aloha-plugins]').data('aloha-plugins');
// Determine Plugins
if ( typeof plugins === 'string' && plugins !== "") {
return plugins.replace(/\s+/g, '').split(',');
}
// Return
return [];
},
/**
* Returns list of loaded plugins (without Bundle name)
*
* @return array
*/
getLoadedPlugins: function() {
return this.loadedPlugins;
},
/**
* Returns true if a certain plugin is loaded, false otherwise.
*/
isPluginLoaded: function(pluginName) {
var found = false;
jQuery.each(this.loadedPlugins, function() {
if (pluginName.toString() === this.toString()) {
found = true;
}
});
return found;
},
/**
* Initialise Aloha
*/
initAloha: function(next){
// check browser version on init
// this has to be revamped, as
if (jQuery.browser.webkit && parseFloat(jQuery.browser.version) < 532.5 || // Chrome/Safari 4
jQuery.browser.mozilla && parseFloat(jQuery.browser.version) < 1.9 || // FF 3.5
jQuery.browser.msie && jQuery.browser.version < 7 || // IE 7
jQuery.browser.opera && jQuery.browser.version < 11 ) { // right now, Opera needs some work
if (window.console && window.console.log) {
window.console.log( 'Your browser is not supported.' );
}
}
// register the body click event to blur editables
jQuery('html').mousedown(function(e) {
// if an Ext JS modal is visible, we don't want to loose the focus on
// the editable as we assume that the user must have clicked somewhere
// in the modal... where else could he click?
// loosing the editable focus in this case hinders correct table
// column/row deletion, as the table module will clean it's selection
// as soon as the editable is deactivated. Fusubscriberthermore you'd have to
// refocus the editable again, which is just strange UX
if (Aloha.activeEditable && !Aloha.isMessageVisible() && !Aloha.eventHandled) {
Aloha.activeEditable.blur();
Aloha.activeEditable = null;
}
}).mouseup(function(e) {
Aloha.eventHandled = false;
});
// Initialise the base path to the aloha files
Aloha.settings.base = Aloha.getAlohaUrl();
// initialize the Log
Aloha.Log.init();
// initialize the error handler for general javascript errors
if ( Aloha.settings.errorhandling ) {
window.onerror = function (msg, url, linenumber) {
Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber);
// TODO eventually add a message to the message line?
return true;
};
}
// OS detection
if (navigator.appVersion.indexOf('Win') != -1) {
Aloha.OSName = 'Win';
}
if (navigator.appVersion.indexOf('Mac') != -1) {
Aloha.OSName = 'Mac';
}
if (navigator.appVersion.indexOf('X11') != -1) {
Aloha.OSName = 'Unix';
}
if (navigator.appVersion.indexOf('Linux') != -1) {
Aloha.OSName = 'Linux';
}
try {
// this will disable browsers image resizing facilities
// disable resize handles
var supported;
try {
supported = document.queryCommandSupported( 'enableObjectResizing' );
} catch ( e ) {
supported = false;
Aloha.Log.log( 'enableObjectResizing is not supported.' );
}
if ( supported ) {
document.execCommand( 'enableObjectResizing', false, false);
Aloha.Log.log( 'enableObjectResizing disabled.' );
}
} catch (e) {
Aloha.Log.error( e, 'Could not disable enableObjectResizing' );
// this is just for others, who will not support disabling enableObjectResizing
}
// Forward
next();
},
/**
* Loads plugins Aloha
* @return void
*/
initPlugins: function (next) {
PluginManager.init(function(){
next();
}, this.getLoadedPlugins() );
},
/**
* Loads GUI components
* @return void
*/
initGui: function (next) {
Aloha.RepositoryManager.init();
// activate registered editables
for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
if ( !Aloha.editables[i].ready ) {
Aloha.editables[i].init();
}
}
// Forward
next();
},
/**
* Activates editable and deactivates all other Editables
* @param {Editable} editable the Editable to be activated
* @return void
*/
activateEditable: function (editable) {
// blur all editables, which are currently active
for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
if (Aloha.editables[i] != editable && Aloha.editables[i].isActive) {
Aloha.editables[i].blur();
}
}
Aloha.activeEditable = editable;
},
/**
* Returns the current Editable
* @return {Editable} returns the active Editable
*/
getActiveEditable: function() {
return Aloha.activeEditable;
},
/**
* deactivated the current Editable
* @return void
*/
deactivateEditable: function () {
if ( typeof Aloha.activeEditable === 'undefined' || Aloha.activeEditable === null ) {
return;
}
// blur the editable
Aloha.activeEditable.blur();
Aloha.activeEditable = null;
},
/**
* Gets an editable by an ID or null if no Editable with that ID registered.
* @param {string} id the element id to look for.
* @return {Aloha.Editable} editable
*/
getEditableById: function (id) {
// if the element is a textarea than route to the editable div
if (jQuery('#'+id).get(0).nodeName.toLowerCase() === 'textarea' ) {
id = id + '-aloha';
}
// serach all editables for id
for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
if (Aloha.editables[i].getId() == id) {
return Aloha.editables[i];
}
}
return null;
},
/**
* Checks wheater an object is a registered Aloha Editable.
* @param {jQuery} obj the jQuery object to be checked.
* @return {boolean}
*/
isEditable: function (obj) {
for (var i=0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
if ( Aloha.editables[i].originalObj.get(0) === obj ) {
return true;
}
}
return false;
},
/**
* Logs a message to the console
* @param level Level of the log ("error", "warn" or "info", "debug")
* @param component Component that calls the log
* @param message log message
* @return void
* @hide
*/
log: function(level, component, message) {
if (typeof Aloha.Log !== "undefined")
Aloha.Log.log(level, component, message);
},
/**
* Register the given editable
* @param editable editable to register
* @return void
* @hide
*/
registerEditable: function (editable) {
Aloha.editables.push(editable);
},
/**
* Unregister the given editable. It will be deactivated and removed from editables.
* @param editable editable to unregister
* @return void
* @hide
*/
unregisterEditable: function (editable) {
// Find the index
var id = Aloha.editables.indexOf( editable );
// Remove it if really found!
if (id != -1) {
Aloha.editables.splice(id, 1);
}
},
/**
* String representation
* @hide
*/
toString: function () {
return 'Aloha';
},
/**
* Check whether at least one editable was modified
* @method
* @return {boolean} true when at least one editable was modified, false if not
*/
isModified: function () {
// check if something needs top be saved
for (var i = 0; i < Aloha.editables.length; i++) {
if (Aloha.editables[i].isModified && Aloha.editables[i].isModified()) {
return true;
}
}
return false;
},
/**
* Determines the Aloha Url
* @method
* @return {String} alohaUrl
*/
getAlohaUrl: function( suffix ) {
// aloha base path is defined by a script tag with 2 data attributes
var requireJs = jQuery('[data-aloha-plugins]'),
baseUrl = ( requireJs.length ) ? requireJs[0].src.replace( /\/?aloha.js$/ , '' ) : '';
return baseUrl;
},
/**
* Gets the Plugin Url
* @method
* @param {String} name
* @return {String} url
*/
getPluginUrl: function (name) {
var url;
if (name) {
url = pluginPaths[name];
if(url) {
//Check if url is absolute and attach base url if it is not
if(!url.match("^(\/|http[s]?:).*")) {
url = Aloha.getAlohaUrl() + '/' + url;
}
}
}
return url;
}
});
return Aloha;
});

View File

@ -0,0 +1,194 @@
//Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}
// Add ECMA262-5 string trim if not supported natively
//
if (!('trim' in String.prototype)) {
String.prototype.trim= function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
}
// Add ECMA262-5 Array methods if not supported natively
//
if (!('indexOf' in Array.prototype)) {
Array.prototype.indexOf= function(find, i /*opt*/) {
if (i===undefined) i= 0;
if (i<0) i+= this.length;
if (i<0) i= 0;
for (var n= this.length; i<n; i++)
if (i in this && this[i]===find)
return i;
return -1;
};
}
if (!('lastIndexOf' in Array.prototype)) {
Array.prototype.lastIndexOf= function(find, i /*opt*/) {
if (i===undefined) i= this.length-1;
if (i<0) i+= this.length;
if (i>this.length-1) i= this.length-1;
for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
if (i in this && this[i]===find)
return i;
return -1;
};
}
if (!('forEach' in Array.prototype)) {
Array.prototype.forEach= function(action, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this)
action.call(that, this[i], i, this);
};
}
if (!('map' in Array.prototype)) {
Array.prototype.map= function(mapper, that /*opt*/) {
var other= new Array(this.length);
for (var i= 0, n= this.length; i<n; i++)
if (i in this)
other[i]= mapper.call(that, this[i], i, this);
return other;
};
}
if (!('filter' in Array.prototype)) {
Array.prototype.filter= function(filter, that /*opt*/) {
var other= [], v;
for (var i=0, n= this.length; i<n; i++)
if (i in this && filter.call(that, v= this[i], i, this))
other.push(v);
return other;
};
}
if (!('every' in Array.prototype)) {
Array.prototype.every= function(tester, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this && !tester.call(that, this[i], i, this))
return false;
return true;
};
}
if (!('some' in Array.prototype)) {
Array.prototype.some= function(tester, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this && tester.call(that, this[i], i, this))
return true;
return false;
};
}
if (!Node) {
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
var Node = {
'ELEMENT_NODE' : 1,
'ATTRIBUTE_NODE': 2,
'TEXT_NODE': 3,
'CDATA_SECTION_NODE': 4,
'ENTITY_REFERENCE_NODE': 5,
'ENTITY_NODE': 6,
'PROCESSING_INSTRUCTION_NODE': 7,
'COMMENT_NODE': 8,
'DOCUMENT_NODE': 9,
'DOCUMENT_TYPE_NODE': 10,
'DOCUMENT_FRAGMENT_NODE': 11,
'NOTATION_NODE': 12,
//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
'DOCUMENT_POSITION_DISCONNECTED': 0x01,
//The second node precedes the reference node.
'DOCUMENT_POSITION_PRECEDING': 0x02,
//The node follows the reference node.
'DOCUMENT_POSITION_FOLLOWING': 0x04,
//The node contains the reference node. A node which contains is always preceding, too.
'DOCUMENT_POSITION_CONTAINS': 0x08,
//The node is contained by the reference node. A node which is contained is always following, too.
'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
//The determination of preceding versus following is implementation-specific.
'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
};
}
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
// FIXME: Check if the DOMNode prototype can be set.
window.compareDocumentPosition = function(node1, node2) {
if ('compareDocumentPosition' in document.documentElement ) {
return node1.compareDocumentPosition(node2);
}
if (!("contains" in document.documentElement)) {
throw 'compareDocumentPosition nor contains is not supported by this browser.';
}
if (node1 == node2) return 0;
//if they don't have the same parent, there's a disconnect
if (getRootParent(node1) != getRootParent(node2)) return 1;
//use this if both nodes have a sourceIndex (text nodes don't)
if ("sourceIndex" in node1 && "sourceIndex" in node2) {
return comparePosition(node1, node2);
}
//document will definitely contain the other node
if (node1 == document) return 20;
else if (node2 == document) return 10;
//get sourceIndexes to use for both nodes
var useNode1 = getUseNode(node1), useNode2 = getUseNode(node2);
//call this function again to get the result
var result = comparePosition(useNode1, useNode2);
//clean up if needed
if (node1 != useNode1) useNode1.parentNode.removeChild(useNode1);
if (node2 != useNode2) useNode2.parentNode.removeChild(useNode2);
return result;
//node.ownerDocument gives the document object, which isn't the right info for a disconnect
function getRootParent(node) {
do { var parent = node; }
while (node = node.parentNode);
return parent;
}
//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
//Already checked for equality and disconnect
function comparePosition(node1, node2) {
return (node1.contains(node2) && 16) +
(node2.contains(node1) && 8) +
(node1.sourceIndex >= 0 && node2.sourceIndex >= 0 ?
(node1.sourceIndex < node2.sourceIndex && 4) +
(node1.sourceIndex > node2.sourceIndex && 2) :
1);
}
//get a node with a sourceIndex to use
function getUseNode(node) {
//if the node already has a sourceIndex, use that node
if ("sourceIndex" in node) return node;
//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
return node.parentNode.insertBefore(document.createComment(""), node);
}
};
if (!('getComputedStyle' in window)) {
window.getComputedStyle = function (node, style) {
if( node.currentStyle ) {
return node.currentStyle;
}
return null;
}
}

View File

@ -0,0 +1,280 @@
define([],
function(){
var shims = {
// Function bind
bind: function(owner){
var obj = this.obj || this;
var native_method = Function.prototype.bind;
var args= Array.prototype.slice.call(arguments, 1);
if(native_method){
return native_method.apply(obj, arguments);
}
else{
return function() {
return obj.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
}
}
},
// String trim
trim: function(){
var obj = this.obj || this;
var native_method = String.prototype.trim;
if(native_method){
return native_method.call(obj);
}
else {
return obj.replace(/^\s+/, '').replace(/\s+$/, '');
}
},
// Array methods
indexOf: function(find, i /*opt*/){
var obj = this.obj || this;
var native_method = Array.prototype.indexOf;
if(native_method){
return native_method.call(obj, find, i);
}
else {
if (i===undefined) i= 0;
if (i<0) i+= obj.length;
if (i<0) i= 0;
for (var n = obj.length; i<n; i++)
if (i in obj && obj[i]===find)
return i;
return -1;
}
},
forEach: function(action, that /*opt*/){
var obj = this.obj || this;
var native_method = Array.prototype.forEach;
if(native_method){
return native_method.call(obj, action, that);
}
else {
for (var i= 0, n = obj.length; i<n; i++)
if (i in obj)
action.call(that, obj[i], i, obj);
}
},
map: function(mapper, that /*opt*/, chain /*opt */){
var obj = this.obj || this;
var native_method = Array.prototype.map;
var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
var result = [];
if(native_method){
result = native_method.call(obj, mapper, that);
}
else {
var other= new Array(obj.length);
for (var i= 0, n= obj.length; i<n; i++)
if (i in obj)
other[i]= mapper.call(that, obj[i], i, obj);
result = other;
}
return returnWrapper ? $_(result) : result;
},
filter: function(filterFunc, that /*opt*/, chain /*opt */){
var obj = this.obj || this;
var native_method = Array.prototype.filter;
var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
var result = [];
if(native_method){
result = native_method.call(obj, filterFunc, that);
}
else {
var other= [], v;
for (var i=0, n= obj.length; i<n; i++)
if (i in obj && filterFunc.call(that, v= obj[i], i, obj))
other.push(v);
result = other;
}
return returnWrapper ? $_(result) : result;
},
every: function(tester, that /*opt*/) {
var obj = this.obj || this;
var native_method = Array.prototype.every;
if(native_method){
return native_method.call(obj, tester, that);
}
else {
for (var i= 0, n= obj.length; i<n; i++)
if (i in obj && !tester.call(that, obj[i], i, obj))
return false;
return true;
}
},
some: function(tester, that /*opt*/){
var obj = this.obj || this;
var native_method = Array.prototype.some;
if(native_method){
return native_method.call(obj, tester, that);
}
else {
for (var i= 0, n= obj.length; i<n; i++)
if (i in obj && tester.call(that, obj[i], i, obj))
return true;
return false;
}
},
// Since IE7 doesn't support 'hasAttribute' method on nodes
// TODO: raise an exception if the object is not an node
hasAttribute: function(attr){
var obj = this.obj || this;
var native_method = obj.hasAttribute;
if(native_method){
return obj.hasAttribute(attr);
}
else {
return (typeof obj.attributes[attr] != "undefined")
}
}
};
var $_ = function(obj) {
var wrapper = function() {};
wrapper.prototype = shims;
var wrapper_instance = new wrapper();
wrapper_instance.obj = obj;
return wrapper_instance;
};
for (var shim in shims) {
$_[shim] = shims[shim];
}
// Node constants
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
if(typeof Node != 'undefined'){
$_.Node = Node;
}
else {
$_.Node = {
'ELEMENT_NODE' : 1,
'ATTRIBUTE_NODE': 2,
'TEXT_NODE': 3,
'CDATA_SECTION_NODE': 4,
'ENTITY_REFERENCE_NODE': 5,
'ENTITY_NODE': 6,
'PROCESSING_INSTRUCTION_NODE': 7,
'COMMENT_NODE': 8,
'DOCUMENT_NODE': 9,
'DOCUMENT_TYPE_NODE': 10,
'DOCUMENT_FRAGMENT_NODE': 11,
'NOTATION_NODE': 12,
//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
'DOCUMENT_POSITION_DISCONNECTED': 0x01,
//The second node precedes the reference node.
'DOCUMENT_POSITION_PRECEDING': 0x02,
//The node follows the reference node.
'DOCUMENT_POSITION_FOLLOWING': 0x04,
//The node contains the reference node. A node which contains is always preceding, too.
'DOCUMENT_POSITION_CONTAINS': 0x08,
//The node is contained by the reference node. A node which is contained is always following, too.
'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
//The determination of preceding versus following is implementation-specific.
'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
}
};
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
// FIXME: Check if the DOMNode prototype can be set.
$_.compareDocumentPosition = function(node1, node2) {
if ('compareDocumentPosition' in document.documentElement ) {
return node1.compareDocumentPosition(node2);
}
if (!("contains" in document.documentElement)) {
throw 'compareDocumentPosition nor contains is not supported by this browser.';
}
if (node1 == node2) return 0;
//if they don't have the same parent, there's a disconnect
if (getRootParent(node1) != getRootParent(node2)) return 1;
//use this if both nodes have a sourceIndex (text nodes don't)
if ("sourceIndex" in node1 && "sourceIndex" in node2) {
return comparePosition(node1, node2);
}
//document will definitely contain the other node
if (node1 == document) return 20;
else if (node2 == document) return 10;
//get sourceIndexes to use for both nodes
var useNode1 = getUseNode(node1), useNode2 = getUseNode(node2);
//call this function again to get the result
var result = comparePosition(useNode1, useNode2);
//clean up if needed
if (node1 != useNode1) useNode1.parentNode.removeChild(useNode1);
if (node2 != useNode2) useNode2.parentNode.removeChild(useNode2);
return result;
//node.ownerDocument gives the document object, which isn't the right info for a disconnect
function getRootParent(node) {
do { var parent = node; }
while (node = node.parentNode);
return parent;
}
//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
//Already checked for equality and disconnect
function comparePosition(node1, node2) {
return (node1.contains(node2) && 16) +
(node2.contains(node1) && 8) +
(node1.sourceIndex >= 0 && node2.sourceIndex >= 0 ?
(node1.sourceIndex < node2.sourceIndex && 4) +
(node1.sourceIndex > node2.sourceIndex && 2) :
1);
}
//get a node with a sourceIndex to use
function getUseNode(node) {
//if the node already has a sourceIndex, use that node
if ("sourceIndex" in node) return node;
//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
return node.parentNode.insertBefore(document.createComment(""), node);
}
};
$_.getComputedStyle = function(node, style){
if('getComputedStyle' in window) {
return window.getComputedStyle(node, style);
}
else {
if( node.currentStyle ) {
return node.currentStyle;
}
return null;
}
};
return $_;
});

View File

@ -0,0 +1,878 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* ( at your option ) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define( [
'aloha/core',
'util/class',
'aloha/jquery',
'aloha/pluginmanager',
'aloha/floatingmenu',
'aloha/selection',
'aloha/markup',
'aloha/contenthandlermanager',
'aloha/console'
], function( Aloha, Class, jQuery, PluginManager, FloatingMenu, Selection,
Markup, ContentHandlerManager, console ) {
var unescape = window.unescape,
GENTICS = window.GENTICS,
// True, if the next editable activate event should not be handled
ignoreNextActivateEvent = false;
// default supported and custom content handler settings
// @TODO move to new config when implemented in Aloha
Aloha.defaults.contentHandler = {};
Aloha.defaults.contentHandler.initEditable = [ 'sanitize' ];
Aloha.defaults.contentHandler.getContents = [ 'sanitize' ];
// The insertHtml contenthandler ( paste ) will, by default, use all
// registered content handlers.
//Aloha.defaults.contentHandler.insertHtml = void 0;
if ( typeof Aloha.settings.contentHandler === 'undefined' ) {
Aloha.settings.contentHandler = {};
}
var defaultContentSerializer = function(editableElement){
return jQuery(editableElement).html();
};
var contentSerializer = defaultContentSerializer;
/**
* Editable object
* @namespace Aloha
* @class Editable
* @method
* @constructor
* @param {Object} obj jQuery object reference to the object
*/
Aloha.Editable = Class.extend( {
_constructor: function( obj ) {
// check wheter the object has an ID otherwise generate and set
// globally unique ID
if ( !obj.attr( 'id' ) ) {
obj.attr( 'id', GENTICS.Utils.guid() );
}
// store object reference
this.obj = obj;
this.originalObj = obj;
this.ready = false;
// delimiters, timer and idle for smartContentChange
// smartContentChange triggers -- tab: '\u0009' - space: '\u0020' - enter: 'Enter'
this.sccDelimiters = [ ':', ';', '.', '!', '?', ',',
unescape( '%u0009' ), unescape( '%u0020' ), 'Enter' ];
this.sccIdle = 5000;
this.sccDelay = 500;
this.sccTimerIdle = false;
this.sccTimerDelay = false;
// see keyset http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
this.keyCodeMap = {
93 : "Apps", // The Application key
18 : "Alt", // The Alt ( Menu ) key.
20 : "CapsLock", // The Caps Lock ( Capital ) key.
17 : "Control", // The Control ( Ctrl ) key.
40 : "Down", // The Down Arrow key.
35 : "End", // The End key.
13 : "Enter", // The Enter key.
112 : "F1", // The F1 key.
113 : "F2", // The F2 key.
114 : "F3", // The F3 key.
115 : "F4", // The F4 key.
116 : "F5", // The F5 key.
117 : "F6", // The F6 key.
118 : "F7", // The F7 key.
119 : "F8", // The F8 key.
120 : "F9", // The F9 key.
121 : "F10", // The F10 key.
122 : "F11", // The F11 key.
123 : "F12", // The F12 key.
// Anybody knows the keycode for F13-F24?
36 : "Home", // The Home key.
45 : "Insert", // The Insert ( Ins ) key.
37 : "Left", // The Left Arrow key.
224 : "Meta", // The Meta key.
34 : "PageDown", // The Page Down ( Next ) key.
33 : "PageUp", // The Page Up key.
19 : "Pause", // The Pause key.
44 : "PrintScreen", // The Print Screen ( PrintScrn, SnapShot ) key.
39 : "Right", // The Right Arrow key.
145 : "Scroll", // The scroll lock key
16 : "Shift", // The Shift key.
38 : "Up", // The Up Arrow key.
91 : "Win", // The left Windows Logo key.
92 : "Win" // The right Windows Logo key.
};
this.placeholderClass = 'aloha-placeholder';
Aloha.registerEditable( this );
this.init();
},
/**
* Initialize the editable
* @return void
* @hide
*/
init: function() {
var me = this;
// TODO make editables their own settings.
this.settings = Aloha.settings;
// smartContentChange settings
// @TODO move to new config when implemented in Aloha
if ( Aloha.settings && Aloha.settings.smartContentChange ) {
if ( Aloha.settings.smartContentChange.delimiters ) {
this.sccDelimiters = Aloha.settings.smartContentChange.delimiters;
} /* else {
this.sccDelimiters = this.sccDelimiters;
} */
if ( Aloha.settings.smartContentChange.idle ) {
this.sccIdle = Aloha.settings.smartContentChange.idle;
} /* else {
this.sccIdle = this.sccIdle;
} */
if ( Aloha.settings.smartContentChange.delay ) {
this.sccDelay = Aloha.settings.smartContentChange.delay;
} /* else {
this.sccDelay = this.sccDelay;
} */
}
// check if Aloha can handle the obj as Editable
if ( !this.check( this.obj ) ) {
//Aloha.log( 'warn', this, 'Aloha cannot handle {' + this.obj[0].nodeName + '}' );
this.destroy();
return;
}
// apply content handler to clean up content
var content = me.obj.html();
if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) {
Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
}
content = ContentHandlerManager.handleContent( content, {
contenthandler: Aloha.settings.contentHandler.initEditable
} );
me.obj.html( content );
// only initialize the editable when Aloha is fully ready ( including plugins )
Aloha.bind( 'aloha-ready', function() {
// initialize the object
me.obj.addClass( 'aloha-editable' ).contentEditable( true );
// add focus event to the object to activate
me.obj.mousedown( function( e ) {
// check whether the mousedown was already handled
if ( !Aloha.eventHandled ) {
Aloha.eventHandled = true;
return me.activate( e );
}
} );
me.obj.mouseup( function( e ) {
Aloha.eventHandled = false;
} );
me.obj.focus( function( e ) {
return me.activate( e );
} );
// by catching the keydown we can prevent the browser from doing its own thing
// if it does not handle the keyStroke it returns true and therefore all other
// events ( incl. browser's ) continue
me.obj.keydown( function( event ) {
me.keyCode = event.which;
return Markup.preProcessKeyStrokes( event );
} );
// handle keypress
me.obj.keypress( function( event ) {
// triggers a smartContentChange to get the right charcode
// To test try http://www.w3.org/2002/09/tests/keys.html
Aloha.activeEditable.smartContentChange( event );
} );
// handle shortcut keys
me.obj.keyup( function( event ) {
if ( event.keyCode === 27 ) {
Aloha.deactivateEditable();
return false;
}
} );
// register the onSelectionChange Event with the Editable field
me.obj.contentEditableSelectionChange( function( event ) {
Selection.onChange( me.obj, event );
return me.obj;
} );
// mark the editable as unmodified
me.setUnmodified();
// we don't do the sanitizing on aloha ready, since some plugins add elements into the content and bind events to it.
// if we sanitize by replacing the html, all events would get lost. TODO: think about a better solution for the sanitizing, without
// destroying the events
// // apply content handler to clean up content
// var content = me.obj.html();
// if ( typeof Aloha.settings.contentHandler.initEditable === 'undefined' ) {
// Aloha.settings.contentHandler.initEditable = Aloha.defaults.contentHandler.initEditable;
// }
// content = ContentHandlerManager.handleContent( content, {
// contenthandler: Aloha.settings.contentHandler.initEditable
// } );
// me.obj.html( content );
me.snapshotContent = me.getContents();
// FF bug: check for empty editable contents ( no <br>; no whitespace )
if ( jQuery.browser.mozilla ) {
me.initEmptyEditable();
}
me.initPlaceholder();
me.ready = true;
// throw a new event when the editable has been created
/**
* @event editableCreated fires after a new editable has been created, eg. via $( '#editme' ).aloha()
* The event is triggered in Aloha's global scope Aloha
* @param {Event} e the event object
* @param {Array} a an array which contains a reference to the currently created editable on its first position
*/
Aloha.trigger( 'aloha-editable-created', [ me ] );
} );
},
/**
* True, if this editable is active for editing
* @property
* @type boolean
*/
isActive: false,
/**
* stores the original content to determine if it has been modified
* @hide
*/
originalContent: null,
/**
* every time a selection is made in the current editable the selection has to
* be saved for further use
* @hide
*/
range: undefined,
/**
* Check if object can be edited by Aloha Editor
* @return {boolean } editable true if Aloha Editor can handle else false
* @hide
*/
check: function() {
/* TODO check those elements
'map', 'meter', 'object', 'output', 'progress', 'samp',
'time', 'area', 'datalist', 'figure', 'kbd', 'keygen',
'mark', 'math', 'wbr', 'area',
*/
// Extract El
var me = this,
obj = this.obj,
el = obj.get( 0 ),
nodeName = el.nodeName.toLowerCase(),
// supported elements
textElements = [ 'a', 'abbr', 'address', 'article', 'aside',
'b', 'bdo', 'blockquote', 'cite', 'code', 'command',
'del', 'details', 'dfn', 'div', 'dl', 'em', 'footer',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'i',
'ins', 'menu', 'nav', 'p', 'pre', 'q', 'ruby',
'section', 'small', 'span', 'strong', 'sub', 'sup',
'var' ],
i, div;
for ( i = 0; i < textElements.length; i++ ) {
if ( nodeName === textElements[ i ] ) {
return true;
}
}
// special handled elements
switch ( nodeName ) {
case 'label':
case 'button':
// TODO need some special handling.
break;
case 'textarea':
// Create a div alongside the textarea
div = jQuery( '<div id="' + this.getId() +
'-aloha" class="aloha-textarea" />' )
.insertAfter( obj );
// Resize the div to the textarea and
// Populate the div with the value of the textarea
// Then, hide the textarea
div.height( obj.height() )
.width( obj.width() )
.html( obj.val() );
obj.hide();
// Attach a onsubmit to the form to place the HTML of the
// div back into the textarea
obj.parents( 'form:first' ).submit( function() {
obj.val( me.getContents() );
} );
// Swap textarea reference with the new div
this.obj = div;
// Supported
return true;
default:
break;
}
// the following elements are not supported
/*
'canvas', 'audio', 'br', 'embed', 'fieldset', 'hgroup', 'hr',
'iframe', 'img', 'input', 'map', 'script', 'select', 'style',
'svg', 'table', 'ul', 'video', 'ol', 'form', 'noscript',
*/
return false;
},
/**
* Init Placeholder
*
* @return void
*/
initPlaceholder: function() {
if ( Aloha.settings.placeholder && this.isEmpty() ) {
this.addPlaceholder();
}
},
/**
* Check if the conteneditable is empty.
*
* @return {Boolean}
*/
isEmpty: function() {
var editableTrimedContent = jQuery.trim( this.getContents() ),
onlyBrTag = ( editableTrimedContent === '<br>' ) ? true : false;
return ( editableTrimedContent.length === 0 || onlyBrTag );
},
/**
* Check if the editable div is not empty. Fixes a FF browser bug
* see issue: https://github.com/alohaeditor/Aloha-Editor/issues/269
*
* @return {undefined}
*/
initEmptyEditable: function( ) {
var obj = this.obj;
if ( this.empty( this.getContents() ) ) {
jQuery( obj ).prepend( '<br class="aloha-cleanme" />' );
}
},
/**
* Add placeholder in editable
*
* @return void
*/
addPlaceholder: function() {
var div = jQuery( '<div>' ),
span = jQuery( '<span>' ),
el,
obj = this.obj;
if ( GENTICS.Utils.Dom.allowsNesting( obj[0], div[0] ) ) {
el = div;
} else {
el = span;
}
jQuery( obj ).append( el.addClass( this.placeholderClass ) );
jQuery.each(
Aloha.settings.placeholder,
function( selector, selectorConfig ) {
if ( obj.is( selector ) ) {
el.html( selectorConfig );
}
}
);
// remove browser br
jQuery( 'br', obj ).remove();
// delete div, span, el;
},
/**
* remove placeholder from contenteditable. If setCursor is true,
* will also set the cursor to the start of the selection. However,
* this will be ASYNCHRONOUS, so if you rely on the fact that
* the placeholder is removed after calling this method, setCursor
* should be false ( or not set )
*
* @return void
*/
removePlaceholder: function( obj, setCursor ) {
var placeholderClass = this.placeholderClass,
range;
// // remove browser br
// jQuery( 'br', obj ).remove();
// set the cursor // remove placeholder
if ( setCursor === true ) {
range = Selection.getRangeObject();
if ( !range.select ) {
return;
}
range.startContainer = range.endContainer = obj.get( 0 );
range.startOffset = range.endOffset = 0;
range.select();
window.setTimeout( function() {
jQuery( '.' + placeholderClass, obj ).remove();
}, 20 );
} else {
jQuery( '.' + placeholderClass, obj ).remove();
}
},
/**
* destroy the editable
* @return void
*/
destroy: function() {
// leave the element just to get sure
if ( this === Aloha.getActiveEditable() ) {
this.blur();
// also hide the floating menu if the current editable was active
FloatingMenu.hide();
}
// special handled elements
switch ( this.originalObj.get( 0 ).nodeName.toLowerCase() ) {
case 'label':
case 'button':
// TODO need some special handling.
break;
case 'textarea':
// restore content to original textarea
this.originalObj.val( this.getContents() );
this.obj.remove();
this.originalObj.show();
break;
default:
break;
}
// now the editable is not ready any more
this.ready = false;
// remove the placeholder if needed.
this.removePlaceholder( this.obj );
// initialize the object and disable contentEditable
// unbind all events
// TODO should only unbind the specific handlers.
this.obj.removeClass( 'aloha-editable' )
.contentEditable( false )
.unbind( 'mousedown click dblclick focus keydown keypress keyup' );
/* TODO remove this event, it should implemented as bind and unbind
// register the onSelectionChange Event with the Editable field
this.obj.contentEditableSelectionChange( function( event ) {
Aloha.Selection.onChange( me.obj, event );
return me.obj;
} );
*/
// throw a new event when the editable has been created
/**
* @event editableCreated fires after a new editable has been destroyes, eg. via $( '#editme' ).mahalo()
* The event is triggered in Aloha's global scope Aloha
* @param {Event} e the event object
* @param {Array} a an array which contains a reference to the currently created editable on its first position
*/
Aloha.trigger( 'aloha-editable-destroyed', [ this ] );
// finally register the editable with Aloha
Aloha.unregisterEditable( this );
},
/**
* marks the editables current state as unmodified. Use this method to inform the editable
* that it's contents have been saved
* @method
*/
setUnmodified: function() {
this.originalContent = this.getContents();
},
/**
* check if the editable has been modified during the edit process#
* @method
* @return boolean true if the editable has been modified, false otherwise
*/
isModified: function() {
return this.originalContent !== this.getContents();
},
/**
* String representation of the object
* @method
* @return Aloha.Editable
*/
toString: function() {
return 'Aloha.Editable';
},
/**
* check whether the editable has been disabled
*/
isDisabled: function() {
return !this.obj.contentEditable()
|| this.obj.contentEditable() === 'false';
},
/**
* disable this editable
* a disabled editable cannot be written on by keyboard
*/
disable: function() {
return this.isDisabled() || this.obj.contentEditable( false );
},
/**
* enable this editable
* reenables a disabled editable to be writteable again
*/
enable: function() {
return this.isDisabled() && this.obj.contentEditable( true );
},
/**
* activates an Editable for editing
* disables all other active items
* @method
*/
activate: function( e ) {
// get active Editable before setting the new one.
var oldActive = Aloha.getActiveEditable();
// We need to ommit this call when this flag is set to true.
// This flag will only be set to true before the removePlaceholder method
// is called since that method invokes a focus event which will again trigger
// this method. We want to avoid double invokation of this method.
if ( ignoreNextActivateEvent ) {
ignoreNextActivateEvent = false;
return;
}
// handle special case in which a nested editable is focused by a click
// in this case the "focus" event would be triggered on the parent element
// which actually shifts the focus away to it's parent. this if is here to
// prevent this situation
if ( e && e.type === 'focus' && oldActive !== null
&& oldActive.obj.parent().get( 0 ) === e.currentTarget ) {
return;
}
// leave immediately if this is already the active editable
if ( this.isActive || this.isDisabled() ) {
// we don't want parent editables to be triggered as well, so return false
return;
}
this.obj.addClass( 'aloha-editable-active' );
Aloha.activateEditable( this );
ignoreNextActivateEvent = true;
this.removePlaceholder ( this.obj, true );
ignoreNextActivateEvent = false;
this.isActive = true;
/**
* @event editableActivated fires after the editable has been activated by clicking on it.
* This event is triggered in Aloha's global scope Aloha
* @param {Event} e the event object
* @param {Array} a an array which contains a reference to last active editable on its first position, as well
* as the currently active editable on it's second position
*/
// trigger a 'general' editableActivated event
Aloha.trigger( 'aloha-editable-activated', {
'oldActive' : oldActive,
'editable' : this
} );
},
/**
* handle the blur event
* this must not be attached to the blur event, which will trigger far too often
* eg. when a table within an editable is selected
* @hide
*/
blur: function() {
this.obj.blur();
this.isActive = false;
this.initPlaceholder();
this.obj.removeClass( 'aloha-editable-active' );
/**
* @event editableDeactivated fires after the editable has been activated by clicking on it.
* This event is triggered in Aloha's global scope Aloha
* @param {Event} e the event object
* @param {Array} a an array which contains a reference to this editable
*/
Aloha.trigger( 'aloha-editable-deactivated', { editable : this } );
/**
* @event smartContentChanged
*/
Aloha.activeEditable.smartContentChange( { type : 'blur' }, null );
},
/**
* check if the string is empty
* used for zerowidth check
* @return true if empty or string is null, false otherwise
* @hide
*/
empty: function( str ) {
// br is needed for chrome
return ( null === str )
|| ( jQuery.trim( str ) === '' || str === '<br/>' );
},
/**
* Get the contents of this editable as a HTML string
* @method
* @return contents of the editable
*/
getContents: function( asObject ) {
var clonedObj = this.obj.clone( false );
// do core cleanup
clonedObj.find( '.aloha-cleanme' ).remove();
this.removePlaceholder( clonedObj );
PluginManager.makeClean( clonedObj );
return asObject ? clonedObj.contents() : contentSerializer(clonedObj[0]);
},
/**
* Get the id of this editable
* @method
* @return id of this editable
*/
getId: function() {
return this.obj.attr( 'id' );
},
/**
* Generates and signals a smartContentChange event.
*
* A smart content change occurs when a special editing action, or a
* combination of interactions are performed by the user during the
* course of editing within an editable.
* The smart content change event would therefore signal to any
* component that is listening to this event, that content has been
* inserted into the editable that may need to be prococessed in a
* special way
* This is used for smart actions within the content/while editing.
* @param {Event} event
* @hide
*/
smartContentChange: function( event ) {
var me = this,
uniChar = null,
re,
match;
// ignore meta keys like crtl+v or crtl+l and so on
if ( event && ( event.metaKey || event.crtlKey || event.altKey ) ) {
return false;
}
if ( event && event.originalEvent ) {
// regex to strip unicode
re = new RegExp( "U\\+(\\w{4})" );
match = re.exec( event.originalEvent.keyIdentifier );
// Use keyIdentifier if available
if ( event.originalEvent.keyIdentifier && 1 === 2 ) {
// @fixme: Because of "&& 1 === 2" above, all the below is
// unreachable code
if ( match !== null ) {
uniChar = unescape( '%u' + match[1] );
}
if ( uniChar === null ) {
uniChar = event.originalEvent.keyIdentifier;
}
// FF & Opera don't support keyIdentifier
} else {
// Use among browsers reliable which http://api.jquery.com/keypress
uniChar = ( this.keyCodeMap[ this.keyCode ] ||
String.fromCharCode( event.which ) || 'unknown' );
}
}
// handle "Enter" -- it's not "U+1234" -- when returned via "event.originalEvent.keyIdentifier"
// reference: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html
if ( jQuery.inArray( uniChar, this.sccDelimiters ) >= 0 ) {
clearTimeout( this.sccTimerIdle );
clearTimeout( this.sccTimerDelay );
this.sccTimerDelay = setTimeout( function() {
Aloha.trigger( 'aloha-smart-content-changed', {
'editable' : me,
'keyIdentifier' : event.originalEvent.keyIdentifier,
'keyCode' : event.keyCode,
'char' : uniChar,
'triggerType' : 'keypress', // keypress, timer, blur, paste
'snapshotContent' : me.getSnapshotContent()
} );
console.debug( 'Aloha.Editable',
'smartContentChanged: event type keypress triggered' );
/*
var r = Aloha.Selection.rangeObject;
if ( r.isCollapsed()
&& r.startContainer.nodeType == 3 ) {
var posDummy = jQuery( '<span id="GENTICS-Aloha-PosDummy" />' );
GENTICS.Utils.Dom.insertIntoDOM(
posDummy,
r,
this.obj,
null,
false,
false
);
console.log( posDummy.offset().top, posDummy.offset().left );
GENTICS.Utils.Dom.removeFromDOM(
posDummy,
r,
false
);
r.select();
}
*/
}, this.sccDelay );
} else if ( event && event.type === 'paste' ) {
Aloha.trigger( 'aloha-smart-content-changed', {
'editable' : me,
'keyIdentifier' : null,
'keyCode' : null,
'char' : null,
'triggerType' : 'paste',
'snapshotContent' : me.getSnapshotContent()
} );
} else if ( event && event.type === 'blur' ) {
Aloha.trigger( 'aloha-smart-content-changed', {
'editable' : me,
'keyIdentifier' : null,
'keyCode' : null,
'char' : null,
'triggerType' : 'blur',
'snapshotContent' : me.getSnapshotContent()
} );
} else if ( uniChar !== null ) {
// in the rare case idle time is lower then delay time
clearTimeout( this.sccTimerDelay );
clearTimeout( this.sccTimerIdle );
this.sccTimerIdle = setTimeout( function() {
Aloha.trigger( 'aloha-smart-content-changed', {
'editable' : me,
'keyIdentifier' : null,
'keyCode' : null,
'char' : null,
'triggerType' : 'idle',
'snapshotContent' : me.getSnapshotContent()
} );
}, this.sccIdle );
}
},
/**
* Get a snapshot of the active editable as a HTML string
* @hide
* @return snapshot of the editable
*/
getSnapshotContent: function() {
var ret = this.snapshotContent;
this.snapshotContent = this.getContents();
return ret;
}
} );
/**
* Sets the serializer function to be used for the contents of all editables.
*
* The default content serializer will just call the jQuery.html()
* function on the editable element (which gets the innerHTML property).
*
* This method is a static class method and will affect the result
* of editable.getContents() for all editables that have been or
* will be constructed.
*
* @param serializerFunction
* A function that accepts a DOM element and returns the serialized
* XHTML of the element contents (excluding the start and end tag of
* the passed element).
*/
Aloha.Editable.setContentSerializer = function( serializerFunction ) {
contentSerializer = serializerFunction;
};
} );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright <EFBFBD> 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define( [
'aloha/jquery',
'aloha/ext',
'aloha/repositorymanager',
'aloha/console',
'i18n!aloha/nls/i18n'
], function ( jQuery, Ext, RepositoryManager, console ) {
Ext.data.AlohaProxy = function () {
// Must define a dummy api with "read" action to satisfy
// Ext.data.Api#prepare *before* calling super
var api = {};
api[ Ext.data.Api.actions.read ] = true;
Ext.data.AlohaProxy.superclass.constructor.call( this, { api: api } );
this.params = {
queryString : null,
objectTypeFilter : null,
filter : null,
inFolderId : null,
orderBy : null,
maxItems : null,
skipCount : null,
renditionFilter : null,
repositoryId : null
};
};
var i18n = Aloha.require( 'i18n!aloha/nls/i18n' );
Ext.extend( Ext.data.AlohaProxy, Ext.data.DataProxy, {
doRequest: function ( action, rs, params, reader, cb, scope, arg ) {
jQuery.extend( this.params, params );
try {
RepositoryManager.query( this.params, function ( items ) {
cb.call( scope, reader.readRecords( items ), arg, true );
} );
} catch ( ex ) {
console.error( 'Ext.data.AlohaProxy',
'An error occured while querying repositories.' );
this.fireEvent( 'loadexception', this, null, arg, ex );
this.fireEvent( 'exception', this, 'response', action, arg, null, ex );
return false;
}
},
setObjectTypeFilter: function ( otFilter ) {
this.params.objectTypeFilter = otFilter;
},
getObjectTypeFilter: function () {
return this.params.objectTypeFilter;
},
setParams: function ( p ) {
jQuery.extend( this.params, p );
}
} );
} );

View File

@ -0,0 +1,50 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright <EFBFBD> 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/ext'],
function(Ext) {
Ext.data.AlohaObjectReader = function(meta, recordType) {
meta = {};
Ext.applyIf(meta, {
idProperty: 'id',
root: 'items',
totalProperty: 'results',
// TODO implement all defined optional attributes
fields: [
'id',
'url',
'name',
'type',
'weight',
'path',
'repositoryId'
]
});
Ext.data.JsonReader.superclass.constructor.call(this, meta, meta.fields);
};
Ext.extend(Ext.data.AlohaObjectReader, Ext.data.JsonReader, {
// extend of necessary
});
});

View File

@ -0,0 +1,77 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/ext', 'aloha/repositorymanager'],
function(Ext, RepositoryManager) {
Ext.tree.AlohaTreeLoader = function(config) {
Ext.apply(this, config);
Ext.tree.AlohaTreeLoader.superclass.constructor.call(this);
};
Ext.extend( Ext.tree.AlohaTreeLoader, Ext.tree.TreeLoader, {
paramOrder: ['node', 'id'],
nodeParameter: 'id',
directFn : function(node, id, callback) {
var
params = {
inFolderId: node.id,
objectTypeFilter: this.objectTypeFilter,
repositoryId: node.repositoryId
};
RepositoryManager.getChildren ( params, function( items ) {
var response = {};
response = {
status: true,
scope: this,
argument: {callback: callback, node: node}
};
if(typeof callback === 'function'){
callback(items, response);
}
});
},
createNode: function(node) {
if ( node.name ) {
node.text = node.name;
}
if ( node.hasMoreItems ) {
node.leaf = !node.hasMoreItems;
}
if ( node.objectType ) {
node.cls = node.objectType;
}
return Ext.tree.TreeLoader.prototype.createNode.call(this, node);
},
objectTypeFilter : null,
setObjectTypeFilter : function (otFilter) {
this.objectTypeFilter = otFilter;
},
getObjectTypeFilter : function () {
return this.objectTypeFilter;
}
});
});

View File

@ -0,0 +1,32 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('aloha/ext',[], function() {
// Ext seems to have an onClick handler that uses
// QuickTips, but the handler doesn't initialize
// QuickTips and therefore causes an error.
// The bug occurred with the Gentics Content Node
// integration, but if it's really a bug in Ext, then
// it's a good idea to always initialize QuickTips here.
Ext.QuickTips.init();
return Ext;
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,546 @@
/*!
* This file is part of Aloha Editor
* Author & Copyright (c) 2010 Gentics Software GmbH, aloha@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/
(function(window, undefined) {
var
jQuery = window.alohaQuery || window.jQuery, $ = jQuery,
// GENTICS = window.GENTICS,
// Aloha = window.Aloha,
DOMUtils, TextRangeUtils, selection, DOMRange, RangeIterator, DOMSelection;
/*
* Only execute the following code if we are in IE (check for
* document.attachEvent, this is a microsoft event and therefore only available
* in IE).
*/
if(document.attachEvent && document.selection) {
/*!
* DOM Ranges for Internet Explorer (m2)
*
* Copyright (c) 2009 Tim Cameron Ryan
* Released under the MIT/X License
* available at http://code.google.com/p/ierange/
*/
/*
Range reference:
http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsRange.cpp
https://developer.mozilla.org/En/DOM:Range
Selection reference:
http://trac.webkit.org/browser/trunk/WebCore/page/DOMSelection.cpp
TextRange reference:
http://msdn.microsoft.com/en-us/library/ms535872.aspx
Other links:
http://jorgenhorstink.nl/test/javascript/range/range.js
http://jorgenhorstink.nl/2006/07/05/dom-range-implementation-in-ecmascript-completed/
http://dylanschiemann.com/articles/dom2Range/dom2RangeExamples.html
*/
//[TODO] better exception support
/*
DOM functions
*/
DOMUtils = {
findChildPosition: function (node) {
for (var i = 0; node = node.previousSibling; i++)
continue;
return i;
},
isDataNode: function (node) {
return node && node.nodeValue !== null && node.data !== null;
},
isAncestorOf: function (parent, node) {
return !DOMUtils.isDataNode(parent) &&
(parent.contains(DOMUtils.isDataNode(node) ? node.parentNode : node) ||
node.parentNode == parent);
},
isAncestorOrSelf: function (root, node) {
return DOMUtils.isAncestorOf(root, node) || root == node;
},
findClosestAncestor: function (root, node) {
if (DOMUtils.isAncestorOf(root, node))
while (node && node.parentNode != root)
node = node.parentNode;
return node;
},
getNodeLength: function (node) {
return DOMUtils.isDataNode(node) ? node.length : node.childNodes.length;
},
splitDataNode: function (node, offset) {
if (!DOMUtils.isDataNode(node))
return false;
var newNode = node.cloneNode(false);
node.deleteData(offset, node.length);
newNode.deleteData(0, offset);
node.parentNode.insertBefore(newNode, node.nextSibling);
}
};
/*
Text Range utilities
functions to simplify text range manipulation in ie
*/
TextRangeUtils = {
convertToDOMRange: function (textRange, document) {
var domRange,adoptBoundary;
adoptBoundary = function(domRange, textRange, bStart) {
// iterate backwards through parent element to find anchor location
var cursorNode = document.createElement('a'),
cursor = textRange.duplicate(),
parent;
cursor.collapse(bStart);
parent = cursor.parentElement();
do {
parent.insertBefore(cursorNode, cursorNode.previousSibling);
cursor.moveToElementText(cursorNode);
} while (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRange) > 0 && cursorNode.previousSibling);
// when we exceed or meet the cursor, we've found the node
if (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRange) == -1 && cursorNode.nextSibling) {
// data node
cursor.setEndPoint(bStart ? 'EndToStart' : 'EndToEnd', textRange);
domRange[bStart ? 'setStart' : 'setEnd'](cursorNode.nextSibling, cursor.text.length);
} else {
// element
domRange[bStart ? 'setStartBefore' : 'setEndBefore'](cursorNode);
}
cursorNode.parentNode.removeChild(cursorNode);
};
// return a DOM range
domRange = new DOMRange(document);
adoptBoundary(domRange, textRange, true);
adoptBoundary(domRange, textRange, false);
return domRange;
},
convertFromDOMRange: function (domRange) {
function adoptEndPoint(textRange, domRange, bStart) {
// find anchor node and offset
var container = domRange[bStart ? 'startContainer' : 'endContainer'],
offset = domRange[bStart ? 'startOffset' : 'endOffset'], textOffset = 0,
anchorNode = DOMUtils.isDataNode(container) ? container : container.childNodes[offset],
anchorParent = DOMUtils.isDataNode(container) ? container.parentNode : container,
cursorNode, cursor;
// visible data nodes need a text offset
if (container.nodeType == 3 || container.nodeType == 4) {
textOffset = offset;
}
// create a cursor element node to position range (since we can't select text nodes)
cursorNode = domRange._document.createElement('a');
anchorParent.insertBefore(cursorNode, anchorNode);
cursor = domRange._document.body.createTextRange();
cursor.moveToElementText(cursorNode);
cursorNode.parentNode.removeChild(cursorNode);
// move range
textRange.setEndPoint(bStart ? 'StartToStart' : 'EndToStart', cursor);
textRange[bStart ? 'moveStart' : 'moveEnd']('character', textOffset);
}
// return an IE text range
var textRange = domRange._document.body.createTextRange();
adoptEndPoint(textRange, domRange, true);
adoptEndPoint(textRange, domRange, false);
return textRange;
}
};
/*
DOM Range
*/
DOMRange = function(document) {
// save document parameter
this._document = document;
// initialize range
//[TODO] this should be located at document[0], document[0]
this.startContainer = this.endContainer = document.body;
this.endOffset = DOMUtils.getNodeLength(document.body);
};
DOMRange.START_TO_START = 0;
DOMRange.START_TO_END = 1;
DOMRange.END_TO_END = 2;
DOMRange.END_TO_START = 3;
DOMRange.prototype = {
// public properties
startContainer: null,
startOffset: 0,
endContainer: null,
endOffset: 0,
commonAncestorContainer: null,
collapsed: false,
// private properties
_document: null,
// private methods
_refreshProperties: function () {
// collapsed attribute
this.collapsed = (this.startContainer == this.endContainer && this.startOffset == this.endOffset);
// find common ancestor
var node = this.startContainer;
while (node && node != this.endContainer && !DOMUtils.isAncestorOf(node, this.endContainer))
node = node.parentNode;
this.commonAncestorContainer = node;
},
// range methods
//[TODO] collapse if start is after end, end is before start
setStart: function(container, offset) {
this.startContainer = container;
this.startOffset = offset;
this._refreshProperties();
},
setEnd: function(container, offset) {
this.endContainer = container;
this.endOffset = offset;
this._refreshProperties();
},
setStartBefore: function (refNode) {
// set start to beore this node
this.setStart(refNode.parentNode, DOMUtils.findChildPosition(refNode));
},
setStartAfter: function (refNode) {
// select next sibling
this.setStart(refNode.parentNode, DOMUtils.findChildPosition(refNode) + 1);
},
setEndBefore: function (refNode) {
// set end to beore this node
this.setEnd(refNode.parentNode, DOMUtils.findChildPosition(refNode));
},
setEndAfter: function (refNode) {
// select next sibling
this.setEnd(refNode.parentNode, DOMUtils.findChildPosition(refNode) + 1);
},
selectNode: function (refNode) {
this.setStartBefore(refNode);
this.setEndAfter(refNode);
},
selectNodeContents: function (refNode) {
this.setStart(refNode, 0);
this.setEnd(refNode, DOMUtils.getNodeLength(refNode));
},
collapse: function (toStart) {
if (toStart)
this.setEnd(this.startContainer, this.startOffset);
else
this.setStart(this.endContainer, this.endOffset);
},
// editing methods
cloneContents: function () {
// clone subtree
return (function cloneSubtree(iterator) {
for (var node, frag = document.createDocumentFragment(); node = iterator.next(); ) {
node = node.cloneNode(!iterator.hasPartialSubtree());
if (iterator.hasPartialSubtree())
node.appendChild(cloneSubtree(iterator.getSubtreeIterator()));
frag.appendChild(node);
}
return frag;
})(new RangeIterator(this));
},
extractContents: function () {
// cache range and move anchor points
var range = this.cloneRange();
if (this.startContainer != this.commonAncestorContainer)
this.setStartAfter(DOMUtils.findClosestAncestor(this.commonAncestorContainer, this.startContainer));
this.collapse(true);
// extract range
return (function extractSubtree(iterator) {
for (var node, frag = document.createDocumentFragment(); node = iterator.next(); ) {
if ( iterator.hasPartialSubtree() ) {
node = node.cloneNode(false);
}
else {
iterator.remove();
}
if (iterator.hasPartialSubtree())
node.appendChild(extractSubtree(iterator.getSubtreeIterator()));
frag.appendChild(node);
}
return frag;
})(new RangeIterator(range));
},
deleteContents: function () {
// cache range and move anchor points
var range = this.cloneRange();
if (this.startContainer != this.commonAncestorContainer)
this.setStartAfter(DOMUtils.findClosestAncestor(this.commonAncestorContainer, this.startContainer));
this.collapse(true);
// delete range
(function deleteSubtree(iterator) {
while (iterator.next()) {
if ( iterator.hasPartialSubtree() ) {
deleteSubtree(iterator.getSubtreeIterator());
}
else {
iterator.remove();
}
}
})(new RangeIterator(range));
},
insertNode: function (newNode) {
// set original anchor and insert node
if (DOMUtils.isDataNode(this.startContainer)) {
DOMUtils.splitDataNode(this.startContainer, this.startOffset);
this.startContainer.parentNode.insertBefore(newNode, this.startContainer.nextSibling);
} else {
this.startContainer.insertBefore(newNode, this.startContainer.childNodes[this.startOffset]);
}
// resync start anchor
this.setStart(this.startContainer, this.startOffset);
},
surroundContents: function (newNode) {
// extract and surround contents
var content = this.extractContents();
this.insertNode(newNode);
newNode.appendChild(content);
this.selectNode(newNode);
},
// other methods
compareBoundaryPoints: function (how, sourceRange) {
// get anchors
var containerA, offsetA, containerB, offsetB;
switch (how) {
case DOMRange.START_TO_START:
case DOMRange.START_TO_END:
containerA = this.startContainer;
offsetA = this.startOffset;
break;
case DOMRange.END_TO_END:
case DOMRange.END_TO_START:
containerA = this.endContainer;
offsetA = this.endOffset;
break;
}
switch (how) {
case DOMRange.START_TO_START:
case DOMRange.END_TO_START:
containerB = sourceRange.startContainer;
offsetB = sourceRange.startOffset;
break;
case DOMRange.START_TO_END:
case DOMRange.END_TO_END:
containerB = sourceRange.endContainer;
offsetB = sourceRange.endOffset;
break;
}
// compare
return containerA.sourceIndex < containerB.sourceIndex ? -1 :
containerA.sourceIndex == containerB.sourceIndex ?
offsetA < offsetB ? -1 : offsetA == offsetB ? 0 : 1
: 1;
},
cloneRange: function () {
// return cloned range
var range = new DOMRange(this._document);
range.setStart(this.startContainer, this.startOffset);
range.setEnd(this.endContainer, this.endOffset);
return range;
},
detach: function () {
//[TODO] Releases Range from use to improve performance.
},
toString: function () {
return TextRangeUtils.convertFromDOMRange(this).text;
},
createContextualFragment: function (tagString) {
// parse the tag string in a context node
var
content = (DOMUtils.isDataNode(this.startContainer) ? this.startContainer.parentNode : this.startContainer).cloneNode(false),
fragment;
content.innerHTML = tagString;
// return a document fragment from the created node
for (fragment = this._document.createDocumentFragment(); content.firstChild; )
fragment.appendChild(content.firstChild);
return fragment;
}
};
/*
Range iterator
*/
RangeIterator = function(range) {
this.range = range;
if (range.collapsed) {
return;
}
//[TODO] ensure this works
// get anchors
var root = range.commonAncestorContainer;
this._next = range.startContainer == root && !DOMUtils.isDataNode(range.startContainer) ?
range.startContainer.childNodes[range.startOffset] :
DOMUtils.findClosestAncestor(root, range.startContainer);
this._end = range.endContainer == root && !DOMUtils.isDataNode(range.endContainer) ?
range.endContainer.childNodes[range.endOffset] :
DOMUtils.findClosestAncestor(root, range.endContainer).nextSibling;
};
RangeIterator.prototype = {
// public properties
range: null,
// private properties
_current: null,
_next: null,
_end: null,
// public methods
hasNext: function () {
return !!this._next;
},
next: function () {
// move to next node
var current = this._current = this._next;
this._next = this._current && this._current.nextSibling != this._end ?
this._current.nextSibling : null;
// check for partial text nodes
if (DOMUtils.isDataNode(this._current)) {
if (this.range.endContainer == this._current)
(current = current.cloneNode(true)).deleteData(this.range.endOffset, current.length - this.range.endOffset);
if (this.range.startContainer == this._current)
(current = current.cloneNode(true)).deleteData(0, this.range.startOffset);
}
return current;
},
remove: function () {
var end, start;
// check for partial text nodes
if (DOMUtils.isDataNode(this._current) &&
(this.range.startContainer == this._current || this.range.endContainer == this._current)) {
start = this.range.startContainer == this._current ? this.range.startOffset : 0;
end = this.range.endContainer == this._current ? this.range.endOffset : this._current.length;
this._current.deleteData(start, end - start);
} else
this._current.parentNode.removeChild(this._current);
},
hasPartialSubtree: function () {
// check if this node be partially selected
return !DOMUtils.isDataNode(this._current) &&
(DOMUtils.isAncestorOrSelf(this._current, this.range.startContainer) ||
DOMUtils.isAncestorOrSelf(this._current, this.range.endContainer));
},
getSubtreeIterator: function () {
// create a new range
var subRange = new DOMRange(this.range._document);
subRange.selectNodeContents(this._current);
// handle anchor points
if (DOMUtils.isAncestorOrSelf(this._current, this.range.startContainer))
subRange.setStart(this.range.startContainer, this.range.startOffset);
if (DOMUtils.isAncestorOrSelf(this._current, this.range.endContainer))
subRange.setEnd(this.range.endContainer, this.range.endOffset);
// return iterator
return new RangeIterator(subRange);
}
};
/*
DOM Selection
*/
//[NOTE] This is a very shallow implementation of the Selection object, based on Webkit's
// implementation and without redundant features. Complete selection manipulation is still
// possible with just removeAllRanges/addRange/getRangeAt.
DOMSelection = function (document) {
// save document parameter
this._document = document;
// add DOM selection handler
var selection = this;
document.attachEvent('onselectionchange', function () { selection._selectionChangeHandler(); });
};
DOMSelection.prototype = {
// public properties
rangeCount: 0,
// private properties
_document: null,
// private methods
_selectionChangeHandler: function () {
// check if there exists a range
this.rangeCount = this._selectionExists(this._document.selection.createRange()) ? 1 : 0;
},
_selectionExists: function (textRange) {
// checks if a created text range exists or is an editable cursor
return textRange.compareEndPoints('StartToEnd', textRange) !== 0 ||
textRange.parentElement().isContentEditable;
},
// public methods
addRange: function (range) {
// add range or combine with existing range
var selection = this._document.selection.createRange(), textRange = TextRangeUtils.convertFromDOMRange(range);
if (!this._selectionExists(selection))
{
// select range
textRange.select();
}
else
{
// only modify range if it intersects with current range
if (textRange.compareEndPoints('StartToStart', selection) == -1)
if (textRange.compareEndPoints('StartToEnd', selection) > -1 &&
textRange.compareEndPoints('EndToEnd', selection) == -1)
selection.setEndPoint('StartToStart', textRange);
else
if (textRange.compareEndPoints('EndToStart', selection) < 1 &&
textRange.compareEndPoints('EndToEnd', selection) > -1)
selection.setEndPoint('EndToEnd', textRange);
selection.select();
}
},
removeAllRanges: function () {
// remove all ranges
this._document.selection.empty();
},
getRangeAt: function (index) {
// return any existing selection, or a cursor position in content editable mode
var textRange = this._document.selection.createRange();
if (this._selectionExists(textRange))
return TextRangeUtils.convertToDOMRange(textRange, this._document);
return null;
},
toString: function () {
// get selection text
return this._document.selection.createRange().text;
}
};
/*
scripting hooks
*/
document.createRange = function () {
return new DOMRange(document);
};
selection = new DOMSelection(document);
window.getSelection = function () {
return selection;
};
//[TODO] expose DOMRange/DOMSelection to window.?
}
})(window);

View File

@ -0,0 +1,324 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
[ 'aloha/core', 'aloha/selection', 'aloha/jquery', 'aloha/console' ],
function( Aloha, Selection, jQuery, console ) {
var
// $ = jQuery,
// Aloha = window.Aloha,
// console = window.console,
XMLSerializer = window.XMLSerializer;
/**
* jQuery between Extension
*
* insert either html code, a dom object OR a jQuery object inside of an existing text node.
* if the chained jQuery object is not a text node, nothing will happen.
*
* @param content HTML Code, DOM object or jQuery object to be inserted
* @param offset character offset from the start where the content should be inserted
*/
jQuery.fn.between = function(content, offset) {
var
offSize,
fullText;
if (this[0].nodeType !== 3) {
// we are not in a text node, just insert the element at the corresponding position
offSize = this.children().size();
if (offset > offSize) {
offset = offSize;
}
if (offset <= 0) {
this.prepend(content);
} else {
this.children().eq(offset -1).after(content);
}
} else {
// we are in a text node so we have to split it at the correct position
if (offset <= 0) {
this.before(content);
} else if (offset >= this[0].length) {
this.after(content);
} else {
fullText = this[0].data;
this[0].data = fullText.substring(0, offset);
this.after(fullText.substring(offset, fullText.length));
this.after(content);
}
}
};
/**
* Make the object contenteditable. Care about browser version (name of contenteditable attribute depends on it)
*/
jQuery.fn.contentEditable = function( b ) {
// ie does not understand contenteditable but contentEditable
// contentEditable is not xhtml compatible.
var $el = jQuery(this);
var ce = 'contenteditable';
// Check
if (jQuery.browser.msie && parseInt(jQuery.browser.version,10) == 7 ) {
ce = 'contentEditable';
}
if (typeof b === 'undefined' ) {
// For chrome use this specific attribute. The old ce will only
// return 'inherit' for nested elements of a contenteditable.
// The isContentEditable is a w3c standard compliant property which works in IE7,8,FF36+, Chrome 12+
if (typeof $el[0] === 'undefined' ) {
console.error('The jquery object did not contain any valid elements.');
return undefined;
}
if (typeof $el[0].isContentEditable === 'undefined') {
console.warn('Could not determine whether the is editable or not. I assume it is.');
return true;
} else {
return $el[0].isContentEditable;
}
} else if (b === '') {
$el.removeAttr(ce);
} else {
if (b && b !== 'false') {
b = 'true';
} else {
b = 'false';
}
$el.attr(ce, b);
}
return $el;
};
/**
* jQuery Aloha Plugin
*
* turn all dom elements to continous text
* @return jQuery object for the matched elements
* @api
*/
jQuery.fn.aloha = function() {
var $this = jQuery( this );
Aloha.bind( 'aloha-ready', function() {
$this.each( function() {
// create a new aloha editable object for each passed object
if ( !Aloha.isEditable( this ) ) {
new Aloha.Editable( jQuery( this ) );
}
});
});
// Chain
return $this;
};
/**
* jQuery destroy elements as editable
*
* destroy all mached elements editable capabilities
* @return jQuery object for the matched elements
* @api
*/
jQuery.fn.mahalo = function() {
return this.each(function() {
if (Aloha.isEditable(this)) {
Aloha.getEditableById(jQuery(this).attr('id')).destroy();
}
});
};
/**
* jQuery Extension
* new Event which is triggered whenever a selection (length >= 0) is made in
* an Aloha Editable element
*/
jQuery.fn.contentEditableSelectionChange = function(callback) {
var that = this;
// update selection when keys are pressed
this.keyup(function(event){
var rangeObject = Selection.getRangeObject();
callback(event);
});
// update selection on doubleclick (especially important for the first automatic selection, when the Editable is not active yet, but is at the same time activated as the selection occurs
this.dblclick(function(event) {
callback(event);
});
// update selection when text is selected
this.mousedown(function(event){
// remember that a selection was started
that.selectionStarted = true;
});
jQuery(document).mouseup(function(event) {
Selection.eventOriginalTarget = that;
if (that.selectionStarted) {
callback(event);
}
Selection.eventOriginalTarget = false;
that.selectionStarted = false;
});
return this;
};
/**
* Fetch the outerHTML of an Element
* @version 1.0.0
* @date February 01, 2011
* @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle}
* @author Benjamin Arthur Lupton {@link http://balupton.com}
* @copyright 2011 Benjamin Arthur Lupton {@link http://balupton.com}
* @license MIT License {@link http://creativecommons.org/licenses/MIT/}
* @return {String} outerHtml
*/
jQuery.fn.outerHtml = jQuery.fn.outerHtml || function(){
var
$el = jQuery(this),
el = $el.get(0);
if (typeof el.outerHTML != 'undefined') {
return el.outerHTML;
} else {
try {
// Gecko-based browsers, Safari, Opera.
return (new XMLSerializer()).serializeToString(el);
} catch (e) {
try {
// Internet Explorer.
return el.xml;
} catch (e) {}
}
}
};
jQuery.fn.zap = function () {
return this.each(function(){ jQuery(this.childNodes).insertBefore(this); }).remove();
};
jQuery.fn.textNodes = function(excludeBreaks, includeEmptyTextNodes) {
var
ret = [],
doSomething = function(el){
if (
(el.nodeType === 3 && jQuery.trim(el.data) && !includeEmptyTextNodes) ||
(el.nodeType === 3 && includeEmptyTextNodes) ||
(el.nodeName =="BR" && !excludeBreaks)) {
ret.push(el);
} else {
for (var i=0, childLength = el.childNodes.length; i < childLength; ++i) {
doSomething(el.childNodes[i]);
}
}
};
doSomething(this[0]);
return jQuery(ret);
};
/**
* extendObjects is like jQuery.extend, but it does not extend arrays
*/
jQuery.extendObjects = jQuery.fn.extendObjects = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
if (jQuery.isArray(copy)) {
// don't extend arrays
target[ name ] = copy;
} else {
target[ name ] = jQuery.extendObjects( deep, clone, copy );
}
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.isBoolean = function(b) {
return b === true || b === false;
},
jQuery.isNumeric = function(o) {
return ! isNaN (o-0);
}
});

View File

@ -0,0 +1,24 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// define jquery and ext modules. They need to be available in global namespace
define('aloha/jquery',[], function() {
return Aloha.jQuery;
});

View File

@ -0,0 +1,115 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// use specified jQuery or load jQuery
define(
[ 'aloha/jquery' ],
function( jQuery ) {
//PATCH FOR A JQUERY BUG IN 1.6.1 & 1.6.2
//An additional sanity check was introduced to prevent IE from crashing when cache[id] does not exist
jQuery.data = ( function( jQuery ) {
return function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
//if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
if ( (!id || (pvt && id && (!cache[id] || !cache[ id ][ internalKey ]))) && getByName && data === undefined ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
elem[ jQuery.expando ] = id = ++jQuery.uuid;
} else {
id = jQuery.expando;
}
}
if ( !cache[ id ] ) {
cache[ id ] = {};
// TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
// metadata on plain JS objects when the object is serialized using
// JSON.stringify
if ( !isNode ) {
cache[ id ].toJSON = jQuery.noop;
}
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
} else {
cache[ id ] = jQuery.extend(cache[ id ], name);
}
}
thisCache = cache[ id ];
// Internal jQuery data is stored in a separate object inside the object's data
// cache in order to avoid key collisions between internal data and user-defined
// data
if ( pvt ) {
if ( !thisCache[ internalKey ] ) {
thisCache[ internalKey ] = {};
}
thisCache = thisCache[ internalKey ];
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
// not attempt to inspect the internal events object using jQuery.data, as this
// internal data object is undocumented and subject to change.
if ( name === "events" && !thisCache[name] ) {
return thisCache[ internalKey ] && thisCache[ internalKey ].events;
}
return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache;
};
})( jQuery );
});

View File

@ -0,0 +1,898 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
[ 'aloha/core', 'util/class', 'aloha/jquery' ],
function( Aloha, Class, jQuery ) {
var
// Aloha = window.Aloha,
// Class = window.Class,
GENTICS = window.GENTICS;
/**
* Markup object
*/
Aloha.Markup = Class.extend({
/**
* Key handlers for special key codes
*/
keyHandlers: {},
/**
* Add a key handler for the given key code
* @param keyCode key code
* @param handler handler function
*/
addKeyHandler: function (keyCode, handler) {
if (!this.keyHandlers[keyCode]) {
this.keyHandlers[keyCode] = [];
}
this.keyHandlers[keyCode].push(handler);
},
insertBreak: function () {
var range = Aloha.Selection.rangeObject,nonWSIndex,nextTextNode,newBreak;
if (!range.isCollapsed()) {
this.removeSelectedMarkup();
}
newBreak = jQuery('<br/>');
GENTICS.Utils.Dom.insertIntoDOM(newBreak, range, Aloha.activeEditable.obj);
nextTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(newBreak.parent().get(0), GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1, false);
if (nextTextNode) {
// trim leading whitespace
nonWSIndex = nextTextNode.data.search(/\S/);
if (nonWSIndex > 0) {
nextTextNode.data = nextTextNode.data.substring(nonWSIndex);
}
}
range.startContainer = range.endContainer = newBreak.get(0).parentNode;
range.startOffset = range.endOffset = GENTICS.Utils.Dom.getIndexInParent(newBreak.get(0)) + 1;
range.correctRange();
range.clearCaches();
range.select();
},
/**
* first method to handle key strokes
* @param event DOM event
* @param rangeObject as provided by Aloha.Selection.getRangeObject();
* @return "Aloha.Selection"
*/
preProcessKeyStrokes: function(event) {
if (event.type != 'keydown') {
return false;
}
var rangeObject = Aloha.Selection.rangeObject,
handlers,
i;
if (this.keyHandlers[event.keyCode]) {
handlers = this.keyHandlers[event.keyCode];
for ( i = 0; i < handlers.length; ++i) {
if (!handlers[i](event)) {
return false;
}
}
}
// handle left (37) and right (39) keys for block detection
if (event.keyCode === 37 || event.keyCode === 39) {
return this.processCursor(rangeObject, event.keyCode);
}
// BACKSPACE
if (event.keyCode === 8) {
event.preventDefault(); // prevent history.back() even on exception
Aloha.execCommand( 'delete', false );
return false;
}
// DELETE
if (event.keyCode === 46) {
Aloha.execCommand( 'forwarddelete', false );
return false;
}
// ENTER
if (event.keyCode === 13 ) {
if (event.shiftKey) {
Aloha.execCommand( 'insertlinebreak', false );
return false;
} else {
Aloha.execCommand( 'insertparagraph', false );
return false;
}
}
return true;
},
/**
* Processing of cursor keys
* will currently detect blocks (elements with contenteditable=false)
* and selects them (normally the cursor would jump right past them)
*
* For each block an 'aloha-block-selected' event will be triggered.
*
* @param range the current range object
* @param keyCode keyCode of current keypress
* @return false if a block was found to prevent further events, true otherwise
*/
processCursor: function(range, keyCode) {
var rt = range.getRangeTree(), // RangeTree reference
i = 0,
cursorLeft = keyCode === 37,
cursorRight = keyCode === 39,
nextSiblingIsBlock = false, // check whether the next sibling is a block (contenteditable = false)
cursorIsWithinBlock = false, // check whether the cursor is positioned within a block (contenteditable = false)
cursorAtLastPos = false, // check if the cursor is within the last position of the currently active dom element
obj; // will contain references to dom objects
if ( !range.isCollapsed() ) {
return true;
}
for (;i < rt.length; i++) {
if ( typeof rt[i].domobj !== 'undefined' ) {
cursorAtLastPos = range.startOffset === rt[i].domobj.length;
if (cursorAtLastPos) {
nextSiblingIsBlock = jQuery( rt[i].domobj.nextSibling ).attr('contenteditable') === 'false';
cursorIsWithinBlock = jQuery( rt[i].domobj ).parents('[contenteditable=false]').length > 0;
if ( cursorRight && nextSiblingIsBlock ) {
obj = rt[i].domobj.nextSibling;
GENTICS.Utils.Dom.selectDomNode( obj );
Aloha.trigger( 'aloha-block-selected', obj );
Aloha.Selection.preventSelectionChanged();
return false;
}
if ( cursorLeft && cursorIsWithinBlock ) {
obj = jQuery( rt[i].domobj ).parents('[contenteditable=false]').get(0);
GENTICS.Utils.Dom.selectDomNode( obj );
Aloha.trigger( 'aloha-block-selected', obj );
Aloha.Selection.preventSelectionChanged();
return false;
}
}
}
}
},
/**
* method handling shiftEnter
* @param Aloha.Selection.SelectionRange of the current selection
* @return void
*/
processShiftEnter: function(rangeObject) {
this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
},
/**
* method handling Enter
* @param Aloha.Selection.SelectionRange of the current selection
* @return void
*/
processEnter: function(rangeObject) {
if (rangeObject.splitObject) {
// now comes a very evil hack for ie, when the enter is pressed in a text node in an li element, we just append an empty text node
// if (jQuery.browser.msie
// && GENTICS.Utils.Dom
// .isListElement(rangeObject.splitObject)) {
// jQuery(rangeObject.splitObject).append(
// jQuery(document.createTextNode('')));
// }
this.splitRangeObject(rangeObject);
} else { // if there is no split object, the Editable is the paragraph type itself (e.g. a p or h2)
this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject);
}
},
/**
* Insert the given html markup at the current selection
* @param html html markup to be inserted
*/
insertHTMLCode: function (html) {
var rangeObject = Aloha.Selection.rangeObject;
this.insertHTMLBreak(rangeObject.getSelectionTree(), rangeObject, jQuery(html));
},
/**
* insert an HTML Break <br /> into current selection
* @param Aloha.Selection.SelectionRange of the current selection
* @return void
*/
insertHTMLBreak: function(selectionTree, rangeObject, inBetweenMarkup) {
var i, treeLength, el, jqEl, jqElBefore, jqElAfter, tmpObject, offset, checkObj;
inBetweenMarkup = inBetweenMarkup ? inBetweenMarkup: jQuery('<br/>');
for ( i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
el = selectionTree[i];
jqEl = el.domobj ? jQuery(el.domobj) : undefined;
if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
if (el.selection == 'collapsed') {
// collapsed selection found (between nodes)
if (i > 0) {
// not at the start, so get the element to the left
jqElBefore = jQuery(selectionTree[i-1].domobj);
// and insert the break after it
jqElBefore.after(inBetweenMarkup);
} else {
// at the start, so get the element to the right
jqElAfter = jQuery(selectionTree[1].domobj);
// and insert the break before it
jqElAfter.before(inBetweenMarkup);
}
// now set the range
rangeObject.startContainer = rangeObject.endContainer = inBetweenMarkup[0].parentNode;
rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(inBetweenMarkup[0]) + 1;
rangeObject.correctRange();
} else if (el.domobj && el.domobj.nodeType === 3) { // textNode
// when the textnode is immediately followed by a blocklevel element (like p, h1, ...) we need to add an additional br in between
if (el.domobj.nextSibling
&& el.domobj.nextSibling.nodeType == 1
&& Aloha.Selection.replacingElements[el.domobj.nextSibling.nodeName
.toLowerCase()]) {
// TODO check whether this depends on the browser
jqEl.after('<br/>');
}
if (this.needEndingBreak()) {
// when the textnode is the last inside a blocklevel element
// (like p, h1, ...) we need to add an additional br as very
// last object in the blocklevel element
checkObj = el.domobj;
while (checkObj) {
if (checkObj.nextSibling) {
checkObj = false;
} else {
// go to the parent
checkObj = checkObj.parentNode;
// found a blocklevel or list element, we are done
if (GENTICS.Utils.Dom.isBlockLevelElement(checkObj) || GENTICS.Utils.Dom.isListElement(checkObj)) {
break;
}
// reached the limit object, we are done
if (checkObj === rangeObject.limitObject) {
checkObj = false;
}
}
}
// when we found a blocklevel element, insert a break at the
// end. Mark the break so that it is cleaned when the
// content is fetched.
if (checkObj) {
jQuery(checkObj).append('<br class="aloha-cleanme"/>');
}
}
// insert the break
jqEl.between(inBetweenMarkup, el.startOffset);
// correct the range
// count the number of previous siblings
offset = 0;
tmpObject = inBetweenMarkup[0];
while (tmpObject) {
tmpObject = tmpObject.previousSibling;
offset++;
}
rangeObject.startContainer = inBetweenMarkup[0].parentNode;
rangeObject.endContainer = inBetweenMarkup[0].parentNode;
rangeObject.startOffset = offset;
rangeObject.endOffset = offset;
rangeObject.correctRange();
} else if (el.domobj && el.domobj.nodeType === 1) { // other node, normally a break
if (jqEl.parent().find('br.aloha-ephemera').length === 0) {
// but before putting it, remove all:
jQuery(rangeObject.limitObject).find('br.aloha-ephemera').remove();
// now put it:
jQuery(rangeObject.commonAncestorContainer).append(this.getFillUpElement(rangeObject.splitObject));
}
jqEl.after(inBetweenMarkup);
// now set the selection. Since we just added one break do the currect el
// the new position must be el's position + 1. el's position is the index
// of the el in the selection tree, which is i. then we must add
// another +1 because we want to be AFTER the object, not before. therefor +2
rangeObject.startContainer = rangeObject.commonAncestorContainer;
rangeObject.endContainer = rangeObject.startContainer;
rangeObject.startOffset = i+2;
rangeObject.endOffset = i+2;
rangeObject.update();
}
}
}
rangeObject.select();
},
/**
* Check whether blocklevel elements need breaks at the end to visibly render a newline
* @return true if an ending break is necessary, false if not
*/
needEndingBreak: function () {
// currently, all browser except IE need ending breaks
return !jQuery.browser.msie;
},
/**
* Get the currently selected text or false if nothing is selected (or the selection is collapsed)
* @return selected text
*/
getSelectedText: function () {
var rangeObject = Aloha.Selection.rangeObject;
if (rangeObject.isCollapsed()) {
return false;
}
return this.getFromSelectionTree(rangeObject.getSelectionTree(), true);
},
/**
* Recursive function to get the selected text from the selection tree starting at the given level
* @param selectionTree array of selectiontree elements
* @param astext true when the contents shall be fetched as text, false for getting as html markup
* @return selected text from that level (incluiding all sublevels)
*/
getFromSelectionTree: function (selectionTree, astext) {
var text = '', i, treeLength, el, clone;
for ( i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
el = selectionTree[i];
if (el.selection == 'partial') {
if (el.domobj.nodeType === 3) {
// partial text node selected, get the selected part
text += el.domobj.data.substring(el.startOffset, el.endOffset);
} else if (el.domobj.nodeType === 1 && el.children) {
// partial element node selected, do the recursion into the children
if (astext) {
text += this.getFromSelectionTree(el.children, astext);
} else {
// when the html shall be fetched, we create a clone of the element and remove all the children
clone = jQuery(el.domobj).clone(false).empty();
// then we do the recursion and add the selection into the clone
clone.html(this.getFromSelectionTree(el.children, astext));
// finally we get the html of the clone
text += clone.outerHTML();
}
}
} else if (el.selection == 'full') {
if (el.domobj.nodeType === 3) {
// full text node selected, get the text
text += jQuery(el.domobj).text();
} else if (el.domobj.nodeType === 1 && el.children) {
// full element node selected, get the html of the node and all children
text += astext ? jQuery(el.domobj).text() : jQuery(el.domobj).outerHTML();
}
}
}
return text;
},
/**
* Get the currently selected markup or false if nothing is selected (or the selection is collapsed)
* @return selected markup
*/
getSelectedMarkup: function () {
var rangeObject = Aloha.Selection.rangeObject;
if (rangeObject.isCollapsed()) {
return false;
}
return this.getFromSelectionTree(rangeObject.getSelectionTree(), false);
},
/**
* Remove the currently selected markup
*/
removeSelectedMarkup: function () {
var rangeObject = Aloha.Selection.rangeObject, newRange;
if (rangeObject.isCollapsed()) {
return;
}
newRange = new Aloha.Selection.SelectionRange();
// remove the selection
this.removeFromSelectionTree(rangeObject.getSelectionTree(), newRange);
// do a cleanup now (starting with the commonancestorcontainer)
newRange.update();
GENTICS.Utils.Dom.doCleanup({'merge' : true, 'removeempty' : true}, Aloha.Selection.rangeObject);
Aloha.Selection.rangeObject = newRange;
// need to set the collapsed selection now
newRange.correctRange();
newRange.update();
newRange.select();
Aloha.Selection.updateSelection();
},
/**
* Recursively remove the selected items, starting with the given level in the selectiontree
* @param selectionTree current level of the selectiontree
* @param newRange new collapsed range to be set after the removal
*/
removeFromSelectionTree: function (selectionTree, newRange) {
// remember the first found partially selected element node (in case we need
// to merge it with the last found partially selected element node)
var firstPartialElement, newdata, i, el, adjacentTextNode, treeLength;
// iterate through the selection tree
for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
el = selectionTree[i];
// check the type of selection
if (el.selection == 'partial') {
if (el.domobj.nodeType === 3) {
// partial text node selected, so remove the selected portion
newdata = '';
if (el.startOffset > 0) {
newdata += el.domobj.data.substring(0, el.startOffset);
}
if (el.endOffset < el.domobj.data.length) {
newdata += el.domobj.data.substring(el.endOffset, el.domobj.data.length);
}
el.domobj.data = newdata;
// eventually set the new range (if not done before)
if (!newRange.startContainer) {
newRange.startContainer = newRange.endContainer = el.domobj;
newRange.startOffset = newRange.endOffset = el.startOffset;
}
} else if (el.domobj.nodeType === 1 && el.children) {
// partial element node selected, so do the recursion into the children
this.removeFromSelectionTree(el.children, newRange);
if (firstPartialElement) {
// when the first parially selected element is the same type
// of element, we need to merge them
if (firstPartialElement.nodeName == el.domobj.nodeName) {
// merge the nodes
jQuery(firstPartialElement).append(jQuery(el.domobj).contents());
// and remove the latter one
jQuery(el.domobj).remove();
}
} else {
// remember this element as first partially selected element
firstPartialElement = el.domobj;
}
}
} else if (el.selection == 'full') {
// eventually set the new range (if not done before)
if (!newRange.startContainer) {
adjacentTextNode = GENTICS.Utils.Dom.searchAdjacentTextNode(el.domobj.parentNode, GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1, false, {'blocklevel' : false});
if (adjacentTextNode) {
newRange.startContainer = newRange.endContainer = adjacentTextNode;
newRange.startOffset = newRange.endOffset = 0;
} else {
newRange.startContainer = newRange.endContainer = el.domobj.parentNode;
newRange.startOffset = newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(el.domobj) + 1;
}
}
// full node selected, so just remove it (will also remove all children)
jQuery(el.domobj).remove();
}
}
},
/**
* split passed rangeObject without or with optional markup
* @param Aloha.Selection.SelectionRange of the current selection
* @param markup object (jQuery) to insert in between the split elements
* @return void
*/
splitRangeObject: function(rangeObject, markup) {
// UAAAA: first check where the markup can be inserted... *grrrrr*, then decide where to split
// object which is split up
var
splitObject = jQuery(rangeObject.splitObject),
selectionTree, insertAfterObject, followUpContainer;
// update the commonAncestor with the splitObject (so that the selectionTree is correct)
rangeObject.update(rangeObject.splitObject); // set the splitObject as new commonAncestorContainer and update the selectionTree
// calculate the selection tree. NOTE: it is necessary to do this before
// getting the followupcontainer, since getting the selection tree might
// possibly merge text nodes, which would lead to differences in the followupcontainer
selectionTree = rangeObject.getSelectionTree();
// object to be inserted after the splitObject
followUpContainer = this.getSplitFollowUpContainer(rangeObject);
// now split up the splitObject into itself AND the followUpContainer
this.splitRangeObjectHelper(selectionTree, rangeObject, followUpContainer); // split the current object into itself and the followUpContainer
// check whether the followupcontainer is still marked for removal
if (followUpContainer.hasClass('preparedForRemoval')) {
// TODO shall we just remove the class or shall we not use the followupcontainer?
followUpContainer.removeClass('preparedForRemoval');
}
// now let's find the place, where the followUp is inserted afterwards. normally that's the splitObject itself, but in
// some cases it might be their parent (e.g. inside a list, a <p> followUp must be inserted outside the list)
insertAfterObject = this.getInsertAfterObject(rangeObject, followUpContainer);
// now insert the followUpContainer
jQuery(followUpContainer).insertAfter(insertAfterObject); // attach the followUpContainer right after the insertAfterObject
// in some cases, we want to remove the "empty" splitObject (e.g. LIs, if enter was hit twice)
if (rangeObject.splitObject.nodeName.toLowerCase() === 'li' && !Aloha.Selection.standardTextLevelSemanticsComparator(rangeObject.splitObject, followUpContainer)) {
jQuery(rangeObject.splitObject).remove();
}
rangeObject.startContainer = null;
// first check whether the followUpContainer starts with a <br/>
// if so, place the cursor right before the <br/>
var followContents = followUpContainer.contents();
if (followContents.length > 0
&& followContents.get(0).nodeType == 1
&& followContents.get(0).nodeName.toLowerCase() === 'br') {
rangeObject.startContainer = followUpContainer.get(0);
}
if (!rangeObject.startContainer) {
// find a possible text node in the followUpContainer and set the selection to it
// if no textnode is available, set the selection to the followup container itself
rangeObject.startContainer = followUpContainer.textNodes(true, true).first().get(0);
}
if (!rangeObject.startContainer) { // if no text node was found, select the parent object of <br class="aloha-ephemera" />
rangeObject.startContainer = followUpContainer.textNodes(false).first().parent().get(0);
}
if (rangeObject.startContainer) {
// the cursor is always at the beginning of the followUp
rangeObject.endContainer = rangeObject.startContainer;
rangeObject.startOffset = 0;
rangeObject.endOffset = 0;
} else {
rangeObject.startContainer = rangeObject.endContainer = followUpContainer.parent().get(0);
rangeObject.startOffset = rangeObject.endOffset = GENTICS.Utils.Dom.getIndexInParent(followUpContainer.get(0));
}
// finally update the range object again
rangeObject.update();
// now set the selection
rangeObject.select();
},
/**
* method to get the object after which the followUpContainer can be inserted during splitup
* this is a helper method, not needed anywhere else
* @param rangeObject Aloha.Selection.SelectionRange of the current selection
* @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
* @return object after which the followUpContainer can be inserted
*/
getInsertAfterObject: function(rangeObject, followUpContainer) {
var passedSplitObject,i,el;
for (i = 0; i < rangeObject.markupEffectiveAtStart.length; i++) {
el = rangeObject.markupEffectiveAtStart[ i ];
// check if we have already passed the splitObject (some other markup might come before)
if (el === rangeObject.splitObject){
passedSplitObject = true;
}
// if not passed splitObject, skip this markup
if (!passedSplitObject) {
continue;
}
// once we are passed, check if the followUpContainer is allowed to be inserted into the currents el's parent
if (Aloha.Selection.canTag1WrapTag2(jQuery(el).parent()[0].nodeName, followUpContainer[0].nodeName)) {
return el;
}
}
return false;
},
/**
* method to get the html code for a fillUpElement. this is needed for empty paragraphs etc., so that they take up their expected height
* @param splitObject split object (dom object)
* @return fillUpElement HTML Code
*/
getFillUpElement: function(splitObject) {
if (jQuery.browser.msie) {
return false;
} else {
return jQuery('<br class="aloha-cleanme"/>');
}
},
/**
* removes textNodes from passed array, which only contain contentWhiteSpace (e.g. a \n between two tags)
* @param domArray array of domObjects
* @return void
*/
removeElementContentWhitespaceObj: function(domArray) {
var correction = 0,
removeLater = [],
i,
el, removeIndex;
for (i = 0; i < domArray.length; i++) {
el = domArray[i];
if (el.isElementContentWhitespace) {
removeLater[removeLater.length] = i;
}
}
for (i = 0; i < removeLater.length; i++) {
removeIndex = removeLater[i];
domArray.splice(removeIndex - correction, 1);
correction++;
}
},
/**
* recursive method to parallelly walk through two dom subtrees, leave elements before startContainer in first subtree and move rest to other
* @param selectionTree tree to iterate over as contained in rangeObject. must be passed separately to allow recursion in the selection tree, but not in the rangeObject
* @param rangeObject Aloha.Selection.SelectionRange of the current selection
* @param followUpContainer optional jQuery object; if provided the rangeObject will be split and the second part will be insert inside of this object
* @param inBetweenMarkup jQuery object to be inserted between the two split parts. will be either a <br> (if no followUpContainer is passed) OR e.g. a table, which must be inserted between the splitobject AND the follow up
* @return void
*/
splitRangeObjectHelper: function(selectionTree, rangeObject, followUpContainer, inBetweenMarkup) {
if (!followUpContainer) {
Aloha.Log.warn(this, 'no followUpContainer, no inBetweenMarkup, nothing to do...');
}
var fillUpElement = this.getFillUpElement(rangeObject.splitObject),
splitObject = jQuery(rangeObject.splitObject),
startMoving = false,
el, i, completeText, jqObj, mirrorLevel, parent, treeLength;
if (selectionTree.length > 0) {
mirrorLevel = followUpContainer.contents();
// if length of mirrorLevel and selectionTree are not equal, the mirrorLevel must be corrected. this happens, when the mirrorLevel contains whitespace textNodes
if (mirrorLevel.length !== selectionTree.length) {
this.removeElementContentWhitespaceObj(mirrorLevel);
}
for (i = 0, treeLength = selectionTree.length; i < treeLength; i++) {
el = selectionTree[i];
// remove all objects in the mirrorLevel, which are BEFORE the cursor
// OR if the cursor is at the last position of the last Textnode (causing an empty followUpContainer to be appended)
if (
(el.selection === 'none' && startMoving === false) ||
(el.domobj && el.domobj.nodeType === 3 && el === selectionTree[ (selectionTree.length-1) ] && el.startOffset === el.domobj.data.length)
) {
// iteration is before cursor, leave this part inside the splitObject, remove from followUpContainer
// however if the object to remove is the last existing textNode within the followUpContainer, insert a BR instead
// otherwise the followUpContainer is invalid and takes up no vertical space
if (followUpContainer.textNodes().length > 1 || (el.domobj.nodeType === 1 && el.children.length === 0)) {
// note: the second part of the if (el.domobj.nodeType === 1 && el.children.length === 0) covers a very special condition,
// where an empty tag is located right before the cursor when pressing enter. In this case the empty tag would not be
// removed correctly otherwise
mirrorLevel.eq(i).remove();
} else if (GENTICS.Utils.Dom.isSplitObject(followUpContainer[0])) {
if (fillUpElement) {
followUpContainer.html(fillUpElement); // for your zoological german knowhow: ephemera = Eintagsfliege
} else {
followUpContainer.empty();
}
} else {
followUpContainer.empty();
followUpContainer.addClass('preparedForRemoval');
}
continue;
} else {
// split objects, which are AT the cursor Position or directly above
if (el.selection !== 'none') { // before cursor, leave this part inside the splitObject
// TODO better check for selection == 'partial' here?
if (el.domobj && el.domobj.nodeType === 3 && el.startOffset !== undefined) {
completeText = el.domobj.data;
if (el.startOffset > 0) {// first check, if there will be some text left in the splitObject
el.domobj.data = completeText.substr(0, el.startOffset);
} else if (selectionTree.length > 1) { // if not, check if the splitObject contains more than one node, because then it can be removed. this happens, when ENTER is pressed inside of a textnode, but not at the borders
jQuery(el.domobj).remove();
} else { // if the "empty" textnode is the last node left in the splitObject, replace it with a ephemera break
// if the parent is a blocklevel element, we insert the fillup element
parent = jQuery(el.domobj).parent();
if (GENTICS.Utils.Dom.isSplitObject(parent[0])) {
if (fillUpElement) {
parent.html(fillUpElement);
} else {
parent.empty();
}
} else {
// if the parent is no blocklevel element and would be empty now, we completely remove it
parent.remove();
}
}
if (completeText.length - el.startOffset > 0) {
// first check if there is text left to put in the followUpContainer's textnode. this happens, when ENTER is pressed inside of a textnode, but not at the borders
mirrorLevel[i].data = completeText.substr(el.startOffset, completeText.length);
} else if (mirrorLevel.length > 1) {
// if not, check if the followUpContainer contains more than one node, because if yes, the "empty" textnode can be removed
mirrorLevel.eq( (i) ).remove();
} else if (GENTICS.Utils.Dom.isBlockLevelElement(followUpContainer[0])) {
// if the "empty" textnode is the last node left in the followUpContainer (which is a blocklevel element), replace it with a ephemera break
if (fillUpElement) {
followUpContainer.html(fillUpElement);
} else {
followUpContainer.empty();
}
} else {
// if the "empty" textnode is the last node left in a non-blocklevel element, mark it for removal
followUpContainer.empty();
followUpContainer.addClass('preparedForRemoval');
}
}
startMoving = true;
if (el.children.length > 0) {
this.splitRangeObjectHelper(el.children, rangeObject, mirrorLevel.eq(i), inBetweenMarkup);
}
} else {
// remove all objects in the origin, which are AFTER the cursor
if (el.selection === 'none' && startMoving === true) {
// iteration is after cursor, remove from splitObject and leave this part inside the followUpContainer
jqObj = jQuery(el.domobj).remove();
}
}
}
}
} else {
Aloha.Log.error(this, 'can not split splitObject due to an empty selection tree');
}
// and finally cleanup: remove all fillUps > 1
splitObject.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one
followUpContainer.find('br.aloha-ephemera:gt(0)').remove(); // remove all elements greater than (gt) 0, that also means: leave one
// remove objects prepared for removal
splitObject.find('.preparedForRemoval').remove();
followUpContainer.find('.preparedForRemoval').remove();
// if splitObject / followUp are empty, place a fillUp inside
if (splitObject.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(splitObject[0]) && fillUpElement) {
splitObject.html(fillUpElement);
}
if (followUpContainer.contents().length === 0 && GENTICS.Utils.Dom.isSplitObject(followUpContainer[0]) && fillUpElement) {
followUpContainer.html(fillUpElement);
}
},
/**
* returns a jQuery object fitting the passed splitObject as follow up object
* examples,
* - when passed a p it will return an empty p (clone of the passed p)
* - when passed an h1, it will return either an h1 (clone of the passed one) or a new p (if the collapsed selection was at the end)
* @param rangeObject Aloha.RangeObject
* @return void
*/
getSplitFollowUpContainer: function(rangeObject) {
var
tagName = rangeObject.splitObject.nodeName.toLowerCase(),
returnObj, inside, lastObj;
switch(tagName) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
// get the last textnode in the splitobject, but don't consider aloha-cleanme elements
lastObj = jQuery(rangeObject.splitObject).textNodes(':not(.aloha-cleanme)').last()[0];
// special case: when enter is hit at the end of a heading, the followUp should be a <p>
if (lastObj && rangeObject.startContainer === lastObj && rangeObject.startOffset === lastObj.length) {
returnObj = jQuery('<p></p>');
inside = jQuery(rangeObject.splitObject).clone().contents();
returnObj.append(inside);
return returnObj;
}
break;
case 'li':
// TODO check whether the li is the last one
// special case: if enter is hit twice inside a list, the next item should be a <p> (and inserted outside the list)
if (rangeObject.startContainer.nodeName.toLowerCase() === 'br' && jQuery(rangeObject.startContainer).hasClass('aloha-ephemera')) {
returnObj = jQuery('<p></p>');
inside = jQuery(rangeObject.splitObject).clone().contents();
returnObj.append(inside);
return returnObj;
}
// when the li is the last one and empty, we also just return a <p>
if (!rangeObject.splitObject.nextSibling && jQuery.trim(jQuery(rangeObject.splitObject).text()).length === 0) {
returnObj = jQuery('<p></p>');
return returnObj;
}
}
return jQuery(rangeObject.splitObject).clone();
},
/**
* Transform the given domobj into an object with the given new nodeName.
* Preserves the content and all attributes. If a range object is given, also the range will be preserved
* @param domobj dom object to transform
* @param nodeName new node name
* @param range range object
* @api
* @return new object as jQuery object
*/
transformDomObject: function (domobj, nodeName, range) {
// first create the new element
var
jqOldObj = jQuery(domobj),
jqNewObj = jQuery('<' + nodeName + '></' + nodeName + '>'),
i;
// TODO what about events? css properties?
// copy attributes
if (jqOldObj[0].attributes) {
for (i = 0; i < jqOldObj[0].attributes.length; i++) {
jqNewObj.attr(jqOldObj[0].attributes[i].nodeName, jqOldObj[0].attributes[i].nodeValue);
}
}
// copy inline CSS
if (jqOldObj[0].style && jqOldObj[0].style.cssText) {
jqNewObj[0].style.cssText = jqOldObj[0].style.cssText;
}
// now move the contents of the old dom object into the new dom object
jqOldObj.contents().appendTo(jqNewObj);
// finally replace the old object with the new one
jqOldObj.replaceWith(jqNewObj);
// preserve the range
if (range) {
if (range.startContainer == domobj) {
range.startContainer = jqNewObj.get(0);
}
if (range.endContainer == domobj) {
range.endContainer = jqNewObj.get(0);
}
}
return jqNewObj;
},
/**
* String representation
* @return "Aloha.Selection"
*/
toString: function() {
return 'Aloha.Markup';
}
});
Aloha.Markup = new Aloha.Markup();
return Aloha.Markup;
});

View File

@ -0,0 +1,115 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/core', 'util/class', 'aloha/jquery'],
function( Aloha, Class, jQuery ) {
var
// Aloha = window.Aloha,
// Class = window.Class,
GENTICS = window.GENTICS;
/**
* Message Object
* @namespace Aloha
* @class Message
* @constructor
* @param {Object} data object which contains the parts of the message
* title: the title
* text: the message text to be displayed
* type: one of Aloha.Message.Type
* callback: callback function, which will be triggered after the message was confirmed, closed or accepted
*/
Aloha.Message = Class.extend({
_constructor: function (data) {
this.title = data.title;
this.text = data.text;
this.type = data.type;
this.callback = data.callback;
},
/**
* Returns a textual representation of the message
* @return textual representation of the message
* @hide
*/
toString: function () {
return this.type + ': ' + this.message;
}
});
/**
* Message types enum. Contains all allowed types of messages
* @property
*/
Aloha.Message.Type = {
// reserved for messages
// SUCCESS : 'success',
// INFO : 'info',
// WARN : 'warn',
// CRITICAL : 'critical',
CONFIRM : 'confirm', // confirm dialog, like js confirm()
ALERT : 'alert', // alert dialog like js alert()
WAIT : 'wait' // wait dialog with loading bar. has to be hidden via Aloha.hideMessage()
};
/**
* This is the message line
* @hide
*/
Aloha.MessageLine = Class.extend({
messages: [],
/**
* Add a new message to the message line
* @param message message to add
* @return void
* @hide
*/
add: function(message) {
var messageline = '',
messagesLength = this.messages.length,
i;
// dummy implementation to add a message
this.messages[messagesLength] = message;
while(messagesLength > 4) {
this.messages.shift();
--messagesLength;
}
for ( i = 0; i < messagesLength; i++) {
messageline += this.messages[i].toString() + '<br/>';
}
jQuery('#gtx_aloha_messageline').html(messageline);
}
});
/**
* Message Line Object
* @hide
*/
Aloha.MessageLine = new Aloha.MessageLine();
});

View File

@ -0,0 +1,10 @@
define( {
'floatingmenu.tab.format': 'Formatieren',
'floatingmenu.tab.insert': 'Einf\u00fcgen',
'yes': 'Ja',
'no': 'Nein',
'cancel': 'Abbrechen',
'repository.no_item_found': 'Keinen Eintrag gefunden.',
'repository.loading': 'Es wird geladen',
'repository.no_items_found_yet': 'Noch keine Eintr\u00e4ge gefunden...'
} );

View File

@ -0,0 +1,20 @@
define( {
'root': {
'plugin.abbr.floatingmenu.tab.abbr': 'Abbreviation',
'floatingmenu.tab.format': 'Format',
'floatingmenu.tab.insert': 'Insert',
'yes': 'Yes',
'no': 'No',
'cancel': 'Cancel',
'repository.no_item_found': 'No item found.',
'repository.loading': 'Loading',
'repository.no_items_found_yet': 'No items found yet...'
},
'de': true
/* 'eo': true,
'fi': true,
'fr': true,
'it': true,
'pl': true,
'ru': true*/
} );

View File

@ -0,0 +1,106 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright <EFBFBD> 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/jquery'],
function(jQuery, undefined) {
var
$ = jQuery;
return {
_eventHandlers: null,
/**
* Attach a handler to an event
*
* @param {String} eventType A string containing the event name to bind to
* @param {Function} handler A function to execute each time the event is triggered
* @param {Object} scope Optional. Set the scope in which handler is executed
*/
bind: function(eventType, handler, scope) {
this._eventHandlers = this._eventHandlers || {};
if (!this._eventHandlers[eventType]) {
this._eventHandlers[eventType] = [];
}
this._eventHandlers[eventType].push({
handler: handler,
scope: (scope ? scope : window)
});
},
/**
* Remove a previously-attached event handler
*
* @param {String} eventType A string containing the event name to unbind
* @param {Function} handler The function that is to be no longer executed. Optional. If not given, unregisters all functions for the given event.
*/
unbind: function(eventType, handler) {
this._eventHandlers = this._eventHandlers || {};
if (!this._eventHandlers[eventType]) {
return;
}
if (!handler) {
// No handler function given, unbind all event handlers for the eventType
this._eventHandlers[eventType] = [];
} else {
this._eventHandlers[eventType] = $.grep(this._eventHandlers[eventType], function(element) {
if (element.handler === handler) {
return false;
}
return true;
});
}
},
/**
* Execute all handlers attached to the given event type.
* All arguments except the eventType are directly passed to the callback function.
*
* @param (String} eventType A string containing the event name for which the event handlers should be invoked.
*/
trigger: function(eventType) {
this._eventHandlers = this._eventHandlers || {};
if (!this._eventHandlers[eventType]) {
return;
}
// preparedArguments contains all arguments except the first one.
var preparedArguments = [];
$.each(arguments, function(i, argument) {
if (i>0) {
preparedArguments.push(argument);
}
});
$.each(this._eventHandlers[eventType], function(index, element) {
element.handler.apply(element.scope, preparedArguments);
});
},
/**
* Clears all event handlers. Call this method when cleaning up.
*/
unbindAll: function() {
this._eventHandlers = null;
}
};
});

View File

@ -0,0 +1,276 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
['aloha/core', 'aloha/jquery', 'util/class', 'aloha/pluginmanager', 'aloha/console'],
function(Aloha, jQuery, Class, PluginManager, console ) {
/**
* Abstract Plugin Object
* @namespace Aloha
* @class Plugin
* @constructor
* @param {String} pluginPrefix unique plugin prefix
*/
var Plugin = Class.extend({
name: null,
/**
* contains the plugin's default settings object
* @cfg {Object} default settings for the plugin
*/
defaults: {},
/**
* contains the plugin's settings object
* @cfg {Object} settings the plugins settings stored in an object
*/
settings: {},
/**
* Names of other plugins which must be loaded in order for this plugin to
* function.
* @cfg {Array}
*/
dependencies: [],
_constructor: function( name ) {
/**
* Settings of the plugin
*/
if (typeof name !== "string") {
console.error('Cannot initialise unnamed plugin, skipping');
} else {
this.name = name;
}
},
/**
* @return true if dependencies satisfied, false otherwise
*/
checkDependencies: function() {
var
dependenciesSatisfied = true,
that = this;
jQuery.each(this.dependencies, function() {
if (!Aloha.isPluginLoaded(this)) {
dependenciesSatisfied = false;
console.error('plugin.' + that.name, 'Required plugin "' + this + '" not found.');
}
});
return dependenciesSatisfied;
},
/**
* Init method of the plugin. Called from Aloha Core to initialize this plugin
* @return void
* @hide
*/
init: function() {},
/**
* Get the configuration settings for an editable obj.
* Handles both conf arrays or conf objects
* <ul>
* <li>Array configuration parameters are:
* <pre>
* "list": {
* config : [ 'b', 'h1' ],
* editables : {
* '#title' : [ ],
* 'div' : [ 'b', 'i' ],
* '.article' : [ 'h1' ]
* }
* }
* </pre>
*
* The hash keys of the editables are css selectors. For a
*
* <pre>
* <div class="article">content</div>
* </pre>
*
* the selectors 'div' and '.article' match and the returned configuration is
*
* <pre>
* [ 'b', 'i', 'h1']
* </pre>
*
* The '#title' object would return an empty configuration.
*
* <pre>
* [ ]
* </pre>
*
* All other objects would get the 'config' configuration. If config is not set
* the plugin default configuration is returned.
*
* <pre>
* [ 'b', 'h1']
* </pre></li>
* <li>Object configuration parameters are :
* <pre>
* "image": {
* config : { 'img': { 'max_width': '50px',
* 'max_height': '50px' }},
* editables : {
* '#title': {},
* 'div': {'img': {}},
* '.article': {'img': { 'max_width': '150px',
* 'max_height': '150px' }}
* }
* }
* </pre>
* The '#title' object would return an empty configuration.<br/>
* The 'div' object would return the default configuration.<br/>
* the '.article' would return :
* <pre>
* {'img': { 'max_width': '150px',
* 'max_height': '150px' }}
* </pre>
* </li>
*
* @param {jQuery} obj jQuery object of an Editable Object
* @return {Array} config A Array with configuration entries
*/
getEditableConfig: function (obj) {
var configObj = null,
configSpecified = false,
that = this;
if ( this.settings.editables ) {
// check if the editable's selector matches and if so add its configuration to object configuration
jQuery.each( this.settings.editables, function (selector, selectorConfig) {
if ( obj.is(selector) ) {
configSpecified = true;
if (selectorConfig instanceof Array) {
configObj = [];
configObj = jQuery.merge(configObj, selectorConfig);
} else if (typeof selectorConfig === "object") {
configObj = {};
for (var k in selectorConfig) {
if ( selectorConfig.hasOwnProperty(k) ) {
if (selectorConfig[k] instanceof Array) {
} else if (typeof selectorConfig[k] === "object") {
configObj[k] = {};
configObj[k] = jQuery.extend(true, configObj[k], that.config[k], selectorConfig[k]);
} else {
configObj[k] = selectorConfig[k];
}
}
}
} else {
configObj = selectorConfig;
}
}
});
}
// fall back to default configuration
if ( !configSpecified ) {
if ( typeof this.settings.config === 'undefined' || !this.settings.config ) {
configObj = this.config;
} else {
configObj = this.settings.config;
}
}
return configObj;
},
/**
* Make the given jQuery object (representing an editable) clean for saving
* @param obj jQuery object to make clean
* @return void
*/
makeClean: function ( obj ) {},
/**
* Make a system-wide unique id out of a plugin-wide unique id by prefixing it with the plugin prefix
* @param id plugin-wide unique id
* @return system-wide unique id
* @hide
* @deprecated
*/
getUID: function(id) {
console.deprecated ('plugin', 'getUID() is deprecated. Use plugin.name instead.');
return this.name;
},
/**
* Localize the given key for the plugin.
* @param key key to be localized
* @param replacements array of replacement strings
* @return localized string
* @hide
* @deprecated
*/
i18n: function(key, replacements) {
console.deprecated ('plugin', 'i18n() is deprecated. Use plugin.t() instead.');
return Aloha.i18n(this, key, replacements);
},
/**
* Return string representation of the plugin, which is the prefix
* @return name
* @hide
* @deprecated
*/
toString: function() {
return this.name;
},
/**
* Log a plugin message to the logger
* @param level log level
* @param message log message
* @return void
* @hide
* @deprecated
*/
log: function (level, message) {
console.deprecated ('plugin', 'log() is deprecated. Use Aloha.console instead.');
console.log(level, this, message);
}
});
/**
* Static method used as factory to create plugins.
*
* @param {String} pluginName name of the plugin
* @param {Object} definition definition of the plugin, should have at least an "init" and "destroy" method.
*/
Plugin.create = function(pluginName, definition) {
var pluginInstance = new ( Plugin.extend( definition ) )( pluginName );
pluginInstance.settings = jQuery.extendObjects( true, pluginInstance.defaults, Aloha.settings[pluginName] );
PluginManager.register( pluginInstance );
return pluginInstance;
};
return Plugin;
});

View File

@ -0,0 +1,146 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Do not add dependencies that require depend on aloha/core
define(
[ 'aloha/jquery', 'util/class' ],
function( jQuery, Class ) {
/**
* The Plugin Manager controls the lifecycle of all Aloha Plugins.
*
* @namespace Aloha
* @class PluginManager
* @singleton
*/
return new (Class.extend({
plugins: {},
/**
* Initialize all registered plugins
* @return void
* @hide
*/
init: function(next, userPlugins) {
var
me = this,
globalSettings = ( Aloha && Aloha.settings ) ? Aloha.settings.plugins||{}: {},
i,
plugin,
pluginName;
// Global to local settings
for ( pluginName in globalSettings ) {
if ( globalSettings.hasOwnProperty( pluginName ) ) {
plugin = this.plugins[pluginName] || false;
if ( plugin ) {
plugin.settings = globalSettings[ pluginName ] || {};
}
}
}
// Default: All loaded plugins are enabled
if ( !userPlugins.length ) {
for ( pluginName in this.plugins ) {
if ( this.plugins.hasOwnProperty( pluginName ) ) {
userPlugins.push( pluginName );
}
}
}
// Enable Plugins specified by User
for ( i=0; i < userPlugins.length; ++i ) {
pluginName = userPlugins[ i ];
plugin = this.plugins[ pluginName ]||false;
if ( plugin ) {
plugin.settings = plugin.settings || {};
if ( typeof plugin.settings.enabled === 'undefined' ) {
plugin.settings.enabled = true;
}
if ( plugin.settings.enabled ) {
if ( plugin.checkDependencies() ) {
plugin.init();
}
}
}
}
next();
},
/**
* Register a plugin
* @param {Plugin} plugin plugin to register
*/
register: function( plugin ) {
if ( !plugin.name ) {
throw new Error( 'Plugin does not have an name.' );
}
if ( this.plugins[ plugin.name ]) {
throw new Error( 'Already registered the plugin "' + plugin.name + '"!' );
}
this.plugins[ plugin.name ] = plugin;
},
/**
* Pass the given jQuery object, which represents an editable to all plugins, so that they can make the content clean (prepare for saving)
* @param obj jQuery object representing an editable
* @return void
* @hide
*/
makeClean: function(obj) {
var i, plugin;
// iterate through all registered plugins
for ( plugin in this.plugins ) {
if ( this.plugins.hasOwnProperty( plugin ) ) {
if (Aloha.Log.isDebugEnabled()) {
Aloha.Log.debug(this, 'Passing contents of HTML Element with id { ' + obj.attr('id') +
' } for cleaning to plugin { ' + plugin + ' }');
}
this.plugins[plugin].makeClean(obj);
}
}
},
/**
* Expose a nice name for the Plugin Manager
* @hide
*/
toString: function() {
return 'pluginmanager';
}
}))();
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
/*!
* This file is part of Aloha Editor
* Author & Copyright (c) 2010 Gentics Software GmbH, aloha@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/
/**
* Registry base class.
* TODO: document that it also contains Observable.
*
*/
define(
['aloha/jquery', 'aloha/observable'],
function(jQuery, Observable) {
return Class.extend(Observable, {
_entries: null,
_constructor: function() {
this._entries = {};
},
/**
* @event register
* @param entry
* @param id
*/
register: function(id, entry) {
this._entries[id] = entry;
this.trigger('register', entry, id);
},
/**
* @event unregister
* @param odEntry
* @param id
*/
unregister: function(id) {
var oldEntry = this._entries[id];
delete this._entries[id];
this.trigger('unregister', oldEntry, id);
},
get: function(id) {
return this._entries[id];
},
has: function(id) {
return (this._entries[id] ? true : false);
},
getEntries: function() {
// clone the entries so the user does not accidentally modify our _entries object.
return jQuery.extend({}, this._entries);
}
});
});

View File

@ -0,0 +1,241 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define(
[ 'aloha/core', 'util/class', 'aloha/repositorymanager' ],
function( Aloha, Class, RepositoryManager ) {
// var
// $ = jQuery,
// GENTICS = window.GENTICS,
// Aloha = window.Aloha,
// Class = window.Class;
/**
* Abstract Repository Class. Implement that class for your own repository.
* @namespace Aloha.Repository
* @class Repository
* @constructor
* @param {String} repositoryId unique repository identifier
* @param {String} repositoryName (optional) is the displyed name for this Repository instance
*/
var AbstractRepository = Class.extend({
_constructor: function(repositoryId, repositoryName) {
/**
* @property repositoryId is the unique Id for this Repository instance
*/
this.repositoryId = repositoryId;
/**
* contains the repository's settings object
* @property settings {Object} the repository's settings stored in an object
*/
this.settings = {};
/**
* @property repositoryName is the name for this Repository instance
*/
this.repositoryName = (repositoryName) ? repositoryName : repositoryId;
RepositoryManager.register(this);
},
/**
* Init method of the repository. Called from Aloha Core to initialize this repository
* @return void
* @hide
*/
init: function() {},
/**
* Searches a repository for object items matching queryString if none found returns null.
* The returned object items must be an array of Aloha.Repository.Object
*
<pre><code>
// simple delicious implementation
Aloha.Repositories.myRepository.query = function (params, callback) {
// make local var of this to use in ajax function
var that = this;
// handle each word as tag
var tags = p.queryString.split(' ');
// if we have a query and no tag matching return
if ( p.queryString && tags.length == 0 ) {
callback.call( that, []);
return;
}
// no handling of objectTypeFilter, filter, inFolderId, etc...
// in real implementation you should handle all parameters
jQuery.ajax({ type: "GET",
dataType: "jsonp",
url: 'http://feeds.delicious.com/v2/json/' + tags.join('+'),
success: function(data) {
var items = [];
// convert data to Aloha objects
for (var i = 0; i < data.length; i++) {
if (typeof data[i] != 'function' ) {
items.push(new Aloha.Repository.Document ({
id: data[i].u,
name: data[i].d,
repositoryId: that.repositoryId,
type: 'website',
url: data[i].u
}));
}
}
callback.call( that, items);
}
});
};
</code></pre>
*
* @param {object} params object with properties
* <div class="mdetail-params"><ul>
* <li><code> queryString</code> : String <div class="sub-desc">The query string for full text search</div></li>
* <li><code> objectTypeFilter</code> : array (optional) <div class="sub-desc">Object types that will be returned.</div></li>
* <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
* <li><code> inFolderId</code> : boolean (optional) <div class="sub-desc">This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
* <li><code> inTreeId</code> : boolean (optional) <div class="sub-desc">This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).</div></li>
* <li><code> orderBy</code> : array (optional) <div class="sub-desc">ex. [{lastModificationDate:DESC, name:ASC}]</div></li>
* <li><code> maxItems</code> : Integer (optional) <div class="sub-desc">number items to return as result</div></li>
* <li><code> skipCount</code> : Integer (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
* <li><code> renditionFilter</code> : array (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
* </ul></div>
* @param {function} callback this method must be called with all result items</div></li>
*/
query: null,
/*
query: function( params, callback ) {
if (typeof callback === 'function') {
callback([]);
}
},
*/
/**
* Returns all children of a given motherId.
*
* @param {object} params object with properties
* <div class="mdetail-params"><ul>
* <li><code> objectTypeFilter</code> : array (optional) <div class="sub-desc">Object types that will be returned.</div></li>
* <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
* <li><code> inFolderId</code> : boolean (optional) <div class="sub-desc">This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
* <li><code> orderBy</code> : array (optional) <div class="sub-desc">ex. [{lastModificationDate:DESC, name:ASC}]</div></li>
* <li><code> maxItems</code> : Integer (optional) <div class="sub-desc">number items to return as result</div></li>
* <li><code> skipCount</code> : Integer (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
* <li><code> renditionFilter</code> : array (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
* </ul></div>
* @param {function} callback this method must be called with all result items
*/
getChildren: null,
/*
getChildren: function( params, callback ) {
if (typeof callback === 'function') {
callback([]);
}
},
*/
/**
* Make the given jQuery object (representing an object marked as object of this type)
* clean. All attributes needed for handling should be removed.
*
<pre><code>
Aloha.Repositories.myRepository.makeClean = function (obj) {
obj.removeAttr('data-myRepository-name');
};
</code></pre>
* @param {jQuery} obj jQuery object to make clean
* @return void
*/
makeClean: function (obj) {},
/**
* This method will be called when a user chooses an item from a repository and wants
* to insert this item in his content.
* Mark or modify an object as needed by that repository for handling, processing or identification.
* Objects can be any DOM object as A, SPAN, ABBR, etc. or
* special objects such as aloha-aloha_block elements.
* (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
*
<pre><code>
Aloha.Repositories.myRepository.markObject = function (obj, resourceItem) {
obj.attr('data-myRepository-name').text(resourceItem.name);
};
</code></pre>
*
*
* @param obj jQuery target object to which the repositoryItem will be applied
* @param repositoryItem The selected item. A class constructed from Document or Folder.
* @return void
*/
markObject: function (obj, repositoryItem) {},
/**
* Set a template for rendering objects of this repository
* @param {String} template
* @return void
* @method
*/
setTemplate: function (template) {
if (template) {
this.template = template;
} else {
this.template = null;
}
},
/**
* Checks whether the repository has a template
* @return {boolean} true when the repository has a template, false if not
* @method
*/
hasTemplate: function () {
return this.template ? true : false;
},
/**
* Get the parsed template
* @return {Object} parsed template
* @method
*/
getTemplate: function () {
return this.template;
},
/**
* Get the repositoryItem with given id
* @param itemId {String} id of the repository item to fetch
* @param callback {function} callback function
* @return {Aloha.Repository.Object} item with given id
*/
getObjectById: function ( itemId, callback ) { return true; }
});
// expose the AbstractRepository
Aloha.AbstractRepository = AbstractRepository;
return AbstractRepository;
});

View File

@ -0,0 +1,571 @@
/*!
* This file is part of Aloha Editor Project http://aloha-editor.org
* Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
* Contributors http://aloha-editor.org/contribution.php
* Licensed unter the terms of http://www.aloha-editor.org/license.html
*//*
* Aloha Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.*
*
* Aloha Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define( [
'aloha/core',
'util/class',
'aloha/jquery',
'aloha/console'
], function ( Aloha, Class, jQuery, console ) {
var undefined = void 0;
/**
* Repository Manager
* @namespace Aloha
* @class RepositoryManager
* @singleton
*/
Aloha.RepositoryManager = Class.extend( {
repositories : [],
/**
* Initialize all registered repositories
* Before we invoke each repositories init method, we merge the global
* repository settings into each repository's custom settings
*
* @todo: Write unit tests to check that global and custom settings are
* applied correctly
*
* @return void
* @hide
*/
init: function () {
var repositories = this.repositories,
i = 0,
j = repositories.length,
repository,
settings;
if ( Aloha.settings && Aloha.settings.repositories ) {
settings = Aloha.settings.repositories;
} else {
settings = {};
}
for ( ; i < j; ++i ) {
repository = repositories[ i ];
if ( !repository.settings ) {
repository.settings = {};
}
if ( settings[ repository.repositoryId ] ) {
jQuery.extend(
repository.settings,
settings[ repository.repositoryId ]
);
}
repository.init();
}
},
/**
* Register a Repository.
*
* @param {Aloha.Repository} repository Repository to register
*/
register: function ( repository ) {
if ( !this.getRepository( repository.repositoryId ) ) {
this.repositories.push( repository );
} else {
console.warn( this, 'A repository with name { ' +
repository.repositoryId +
' } already registerd. Ignoring this.' );
}
},
/**
* Returns the repository object identified by repositoryId.
*
* @param {String} repositoryId - the name of the repository
* @return {?Aloha.Repository} a repository or null if name not found
*/
getRepository: function ( repositoryId ) {
var repositories = this.repositories,
i = 0,
j = repositories.length;
for ( ; i < j; ++i ) {
if ( repositories[ i ].repositoryId == repositoryId ) {
return repositories[ i ];
}
}
return null;
},
/**
* Searches a all repositories for repositoryObjects matching query and
* repositoryObjectType.
*
<pre><code>
var params = {
queryString: 'hello',
objectTypeFilter: ['website'],
filter: null,
inFolderId: null,
orderBy: null,
maxItems: null,
skipCount: null,
renditionFilter: null,
repositoryId: null
};
Aloha.RepositoryManager.query( params, function( items ) {
// do something with the result items
console.log(items);
});
</code></pre>
*
* @param {Object <String,Mixed>} params object with properties
* <div class="mdetail-params"><ul>
* <li><code> queryString</code> : String <div class="sub-desc">The query string for full text search</div></li>
* <li><code> objectTypeFilter</code> : array (optional) <div class="sub-desc">Object types that will be returned.</div></li>
* <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
* <li><code> inFolderId</code> : boolean (optional) <div class="sub-desc">This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
* <li><code> inTreeId</code> : boolean (optional) <div class="sub-desc">This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).</div></li>
* <li><code> orderBy</code> : array (optional) <div class="sub-desc">ex. [{lastModificationDate:DESC, name:ASC}]</div></li>
* <li><code> maxItems</code> : Integer (optional) <div class="sub-desc">number items to return as result</div></li>
* <li><code> skipCount</code> : Integer (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
* <li><code> renditionFilter</code> : array (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
* </ul></div>
* @param {Function} callback - defines a callback function( items ) which will be called when all repositories returned their results or after a time out of 5sec.
* "items" is an Array of objects construced with Document/Folder.
* @void
*/
query: function ( params, callback ) {
var that = this,
repo,
// The merged results, collected from repository responses
allitems = [],
// the merge metainfo, collected from repository responses
allmetainfo = { numItems: 0, hasMoreItems: false },
// The set of repositories towhich we want to delegate work
repositories = [],
// A counting semaphore (working in reverse, ie: 0 means free)
numOpenCallbacks = 0,
// When this timer times-out, whatever has been collected in
// allitems will be returned to the calling client, and
// numOpenCallbacks will be reset to 0
timer,
i, j,
/**
* Invoked by each repository when it wants to present its
* results to the manager.
*
* Collects the results from each repository, and decrements
* the numOpenCallbacks semaphore to indicate that there is one
* less repository for which we are waiting a reponse.
*
* If a repository invokes this callback after all
* openCallbacks have been closed (ie: numOpenCallbacks == 0),
* then the repository was too late ("missed the ship"), and
* will be ignored.
*
* If numOpenCallbacks decrements to 0 during this call, it
* means that the the manager is ready to report the results
* back to the client through the queryCallback method.
*
* nb: "this" is reference to the calling repository.
*
* @param {Array} items - Results returned by the repository
* @param {Object<String,Number>} metainfo - optional Metainfo returned by the repository
*/
processResults = function ( items, metainfo ) {
if ( numOpenCallbacks == 0 ) {
return;
}
var j = items ? items.length : 0;
if ( j ) {
// Add the repositoryId for each item if a negligent
// repository did not do so.
if ( !items[ 0 ].repositoryId ) {
var repoId = this.repositoryId;
for ( var i = 0; i < j; ++i ) {
items[ i ].repositoryId = repoId;
}
}
jQuery.merge( allitems, items );
}
if ( metainfo && allmetainfo ) {
if ( jQuery.isNumeric( metainfo.numItems ) &&
jQuery.isNumeric( allmetainfo.numItems ) ) {
allmetainfo.numItems += metainfo.numItems;
} else {
allmetainfo.numItems = undefined;
}
if ( jQuery.isBoolean( metainfo.hasMoreItems ) &&
jQuery.isBoolean( allmetainfo.hasMoreItems ) ) {
allmetainfo.hasMoreItems = allmetainfo.hasMoreItems || metainfo.hasMoreItems;
} else {
allmetainfo.hasMoreItems = undefined;
}
} else {
// at least one repository did not return metainfo, so
// we have no aggregated metainfo at all
allmetainfo = undefined;
}
// TODO how to return the metainfo here?
if ( --numOpenCallbacks == 0 ) {
that.queryCallback( callback, allitems, allmetainfo, timer );
}
};
// Unless the calling client specifies otherwise, we will wait a
// maximum of 5 seconds for all repositories to be queried and
// respond. 5 seconds is deemed to be the reasonable time to wait
// when querying the repository manager in the context of something
// like autocomplete
var timeout = parseInt( params.timeout, 10 ) || 5000;
timer = setTimeout( function() {
numOpenCallbacks = 0;
that.queryCallback( callback, allitems, allmetainfo, timer );
}, timeout );
// If repositoryId or a list of repository ids, is not specified in
// the params object, then we will query all registered
// repositories
if ( params.repositoryId ) {
repositories.push( this.getRepository( params.repositoryId ) );
} else {
repositories = this.repositories;
}
j = repositories.length;
var repoQueue = [];
// We need to know how many callbacks we will open before invoking
// the query method on each, so that as soon as the first one does
// callback, the correct number of open callbacks will be available
// to check.
for ( i = 0; i < j; ++i ) {
repo = repositories[ i ];
if ( typeof repo.query == 'function' ) {
++numOpenCallbacks;
repoQueue.push( repo );
}
}
j = repoQueue.length;
for ( i = 0; i < j; ++i ) {
repo = repoQueue[ i ];
repo.query(
params,
function () {
processResults.apply( repo, arguments );
}
);
}
// If none of the repositories implemented the query method, then
// don't wait for the timeout, simply report to the client
if ( numOpenCallbacks == 0 ) {
this.queryCallback( callback, allitems, allmetainfo, timer );
}
},
/**
* Passes all the results we have collected to the client through the
* callback it specified
*
* @param {Function} callback - Callback specified by client when
* invoking the query method
* @param {Array} items - Results, collected from all repositories
* @param {Object<String,Number>} metainfo - optional object containing metainfo
* @param {Timer} timer - We need to clear this timer
* @return void
* @hide
*/
queryCallback: function ( callback, items, metainfo, timer ) {
if ( timer ) {
clearTimeout( timer );
timer = undefined;
}
// TODO: Implement sorting based on repository specification
// sort items by weight
//items.sort(function (a,b) {
// return (b.weight || 0) - (a.weight || 0);
//});
// prepare result data for the JSON Reader
var result = {
items : items,
results : items.length
};
if ( metainfo ) {
result.numItems = metainfo.numItems;
result.hasMoreItems = metainfo.hasMoreItems;
}
callback.call( this, result );
},
/**
* @todo: This method needs to be covered with some unit tests
*
* Returns children items. (see query for an example)
* @param {Object<String,Mixed>} params - object with properties
* <div class="mdetail-params"><ul>
* <li><code> objectTypeFilter</code> : array (optional) <div class="sub-desc">Object types that will be returned.</div></li>
* <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
* <li><code> inFolderId</code> : boolean (optional) <div class="sub-desc">This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
* <li><code> orderBy</code> : array (optional) <div class="sub-desc">ex. [{lastModificationDate:DESC, name:ASC}]</div></li>
* <li><code> maxItems</code> : Integer (optional) <div class="sub-desc">number items to return as result</div></li>
* <li><code> skipCount</code> : Integer (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
* <li><code> renditionFilter</code> : array (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
* </ul></div>
* @param {Function} callback - defines a callback function( items ) which will be called when all repositories returned their results or after a time out of 5sec.
* "items" is an Array of objects construced with Document/Folder.
* @void
*/
getChildren: function ( params, callback ) {
var that = this,
repo,
// The marged results, collected from repository responses
allitems = [],
// The set of repositories towhich we want to delegate work
repositories = [],
// A counting semaphore (working in reverse, ie: 0 means free)
numOpenCallbacks = 0,
// When this timer times-out, whatever has been collected in
// allitems will be returned to the calling client, and
// numOpenCallbacks will be reset to 0
timer,
i, j,
processResults = function ( items ) {
if ( numOpenCallbacks == 0 ) {
return;
}
jQuery.merge( allitems, items );
if ( --numOpenCallbacks == 0 ) {
that.getChildrenCallback( callback, allitems, timer );
}
};
// If the inFolderId is the default id of 'aloha', then return all
// registered repositories
if ( params.inFolderId == 'aloha' ) {
var repoFilter = params.repositoryFilter,
hasRepoFilter = ( repoFilter && repoFilter.length );
j = this.repositories.length;
for ( i = 0; i < j; ++i ) {
var repo = this.repositories[ i ];
if ( !hasRepoFilter || jQuery.inArray( repo.repositoryId, repoFilter ) > -1 ) {
repositories.push(
new Aloha.RepositoryFolder( {
id : repo.repositoryId,
name : repo.repositoryName,
repositoryId : repo.repositoryId,
type : 'repository',
hasMoreItems : true
} )
);
}
}
that.getChildrenCallback( callback, repositories, null );
return;
} else {
repositories = this.repositories;
}
var timeout = parseInt( params.timeout, 10 ) || 5000;
timer = setTimeout( function () {
numOpenCallbacks = 0;
that.getChildrenCallback( callback, allitems, timer );
}, timeout );
j = repositories.length;
for ( i = 0; i < j; ++i ) {
repo = repositories[ i ];
if ( typeof repo.getChildren === 'function' ) {
++numOpenCallbacks;
repo.getChildren(
params,
function () {
processResults.apply( repo, arguments );
}
);
}
}
if ( numOpenCallbacks == 0 ) {
this.getChildrenCallback( callback, allitems, timer );
}
},
/**
* Returns results for getChildren to calling client
*
* @return void
* @hide
*/
getChildrenCallback: function ( callback, items, timer ) {
if ( timer ) {
clearTimeout( timer );
timer = undefined;
}
callback.call( this, items );
},
/**
* @fixme: Not tested, but the code for this function does not seem to
* compute repository.makeClean will be undefined
*
* @todo: Rewrite this function header comment so that is clearer
*
* Pass an object, which represents an marked repository to corresponding
* repository, so that it can make the content clean (prepare for saving)
*
* @param {jQuery} obj - representing an editable
* @return void
*/
makeClean: function ( obj ) {
// iterate through all registered repositories
var that = this,
repository = {},
i = 0,
j = that.repositories.length;
// find all repository tags
obj.find( '[data-gentics-aloha-repository=' + this.prefix + ']' )
.each( function () {
for ( ; i < j; ++i ) {
repository.makeClean( obj );
}
console.debug( that,
'Passing contents of HTML Element with id { ' +
this.attr('id') + ' } for cleaning to repository { ' +
repository.repositoryId + ' }' );
repository.makeClean( this );
} );
},
/**
* Markes an object as repository of this type and with this item.id.
* Objects can be any DOM objects as A, SPAN, ABBR, etc. or
* special objects such as aloha-aloha_block elements.
* This method marks the target obj with two private attributes:
* (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
* * data-gentics-aloha-repository: stores the repositoryId
* * data-gentics-aloha-object-id: stores the object.id
*
* @param {DOMObject} obj - DOM object to mark
* @param {Aloha.Repository.Object} item - the item which is applied to obj,
* if set to null, the data-GENTICS-... attributes are removed
* @return void
*/
markObject: function ( obj, item ) {
if ( !obj ) {
return;
}
if ( item ) {
var repository = this.getRepository( item.repositoryId );
if ( repository ) {
jQuery( obj ).attr( {
'data-gentics-aloha-repository' : item.repositoryId,
'data-gentics-aloha-object-id' : item.id
} );
repository.markObject( obj, item );
} else {
console.error( this,
'Trying to apply a repository { ' + item.name +
' } to an object, but item has no repositoryId.' );
}
} else {
// remove the data attributes
jQuery( obj ).removeAttr( 'data-gentics-aloha-repository' );
jQuery( obj ).removeAttr( 'data-gentics-aloha-object-id' );
}
},
/**
* Get the object for which the given DOM object is marked from the
* repository.
*
* @param {DOMObject} obj - DOM object which probably is marked
* @param {Function} callback - callback function
*/
getObject: function ( obj, callback ) {
var that = this,
$obj = jQuery( obj ),
repository = this.getRepository( $obj.attr( 'data-gentics-aloha-repository' ) ),
itemId = $obj.attr( 'data-gentics-aloha-object-id' );
if ( repository && itemId ) {
// initialize the item cache (per repository) if not already done
this.itemCache = this.itemCache || [];
this.itemCache[ repository.repositoryId ] = this.itemCache[ repository.repositoryId ] || [];
// when the item is cached, we just call the callback method
if ( this.itemCache[ repository.repositoryId ][ itemId ] ) {
callback.call( this, [ this.itemCache[ repository.repositoryId ][ itemId ] ] );
} else {
// otherwise we get the object from the repository
repository.getObjectById( itemId, function ( items ) {
// make sure the item is in the cache (for subsequent calls)
that.itemCache[ repository.repositoryId ][ itemId ] = items[ 0 ];
callback.call( this, items );
} );
}
}
},
/**
* @return {String} name of repository manager object
*/
toString: function () {
return 'repositorymanager';
}
} );
Aloha.RepositoryManager = new Aloha.RepositoryManager();
// We return the constructor, not the instance of Aloha.RepositoryManager
return Aloha.RepositoryManager;
} );

View File

@ -0,0 +1,146 @@
/*!
* This file is part of Aloha Editor
* Author & Copyright (c) 2010 Gentics Software GmbH, aloha@gentics.com
* Licensed unter the terms of http://www.aloha-editor.com/license.html
*/
define(
[ 'aloha/core', 'util/class'],
function( Aloha, Class ) {
var
// Aloha = window.Aloha,
// Class = window.Class,
GENTICS = window.GENTICS;
Aloha.RepositoryObject = function() {};
/**
* @namespace Aloha.Repository
* @class Document
* @constructor
*
* Abstract Document suitable for most Objects.<br /><br />
*
* Example:
*
<pre><code>
var item = new Aloha.Repository.Document({
id: 1,
repositoryId: 'myrepository',
name: 'Aloha Editor - The HTML5 Editor',
type: 'website',
url:'http://aloha-editor.com',
});
</code></pre>
*
* @param {Object} properties An object with the data.
* <div class="mdetail-params"><ul>
* <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
* <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
* <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
* <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
* <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
* <li><code>mimetype</code> : String (optional) <div class="sub-desc">MIME type of the Content Stream</div></li>
* <li><code>filename</code> : String (optional) <div class="sub-desc">File name of the Content Stream</div></li>
* <li><code>length</code> : String (optional) <div class="sub-desc">Length of the content stream (in bytes)</div></li>
* <li><code>url</code> : String (optional) <div class="sub-desc">URL of the content stream</div></li>
* <li><code>renditions</code> : Array (optional) <div class="sub-desc">Array of different renditions of this object</div></li>
* <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
* <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
* <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
* <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
* <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
* </ul></div>
*
*/
Aloha.RepositoryDocument = Class.extend({
_constructor: function (properties) {
var p = properties;
this.type = 'document';
// Basic error checking for MUST attributes
if (!p.id ||
!p.name ||
!p.repositoryId
) {
// Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
return;
}
GENTICS.Utils.applyProperties(this, properties);
this.baseType = 'document';
}
// /**
// * Not implemented method to generate this JS API doc correctly.
// */
// ,empty = function() }
});
/**
* @namespace Aloha.Repository
* @class Folder
* @constructor
* Abstract Folder suitable for most strucural Objects.<br /><br />
*
* Example:
*
<pre><code>
var item = new Aloha.Repository.Folder({
id: 2,
repositoryId: 'myrepository',
name: 'images',
type: 'directory',
parentId:'/www'
});
</code></pre>
* @param {Object} properties An object with the data.
* <div class="mdetail-params"><ul>
* <li><code>id</code> : String <div class="sub-desc">Unique identifier</div></li>
* <li><code>repositoryId</code> : String <div class="sub-desc">Unique repository identifier</div></li>
* <li><code>name</code> : String <div class="sub-desc">Name of the object. This name is used to display</div></li>
* <li><code>type</code> : String <div class="sub-desc">The specific object type</div></li>
* <li><code>partentId</code> : String (optional) <div class="sub-desc"></div></li>
* <li><code>localName</code> : String (optional) <div class="sub-desc">Name of the object. This name is used internally</div></li>
* <li><code>createdBy</code> : String (optional) <div class="sub-desc">User who created the object</div></li>
* <li><code>creationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was created</div></li>
* <li><code>lastModifiedBy</code> : String (optional) <div class="sub-desc">User who last modified the object</div></li>
* <li><code>lastModificationDate</code> : Date (optional) <div class="sub-desc">DateTime when the object was last modified</div></li>
* </ul></div>
*
*/
Aloha.RepositoryFolder = Class.extend({
_constructor: function(properties) {
var p = properties;
this.type = 'folder';
// Basic error checking for MUST attributes
if (!p.id ||
!p.name ||
!p.repositoryId
) {
// Aloha.Log.error(this, "No valid Aloha Object. Missing MUST property");
return;
}
GENTICS.Utils.applyProperties(this, properties);
this.baseType = 'folder';
}
// /**
// * Not implemented method to generate this JS API doc correctly.
// */
// ,empty = function() {};
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More