/*! * 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 * * Aloha Link Plugin * ----------------- * This plugin provides an interface to allow the user to insert, edit and * remove links within an active editable. * It presents its user interface in the Floating menu, in a Sidebar panel. * Clicking on any links inside the editable activates the this plugin's * floating menu scope. * * @todo Consider whether it would be better to have the target options in the * sidebar panel be a selection box rather than radio buttons. */ define( [ 'aloha', 'aloha/plugin', 'aloha/jquery', 'aloha/floatingmenu', 'i18n!link/nls/i18n', 'i18n!aloha/nls/i18n', 'aloha/console', 'css!link/css/link.css', 'link/../extra/linklist' ], function ( Aloha, Plugin, jQuery, FloatingMenu, i18n, i18nCore, console ) { var GENTICS = window.GENTICS, pluginNamespace = 'aloha-link', oldValue = '', newValue; return Plugin.create( 'link', { /** * Configure the available languages */ languages: [ 'en', 'de', 'fr', 'ru', 'pl' ], /** * Default configuration allows links everywhere */ config: [ 'a' ], /** * all links that match the targetregex will get set the target * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com */ targetregex: '', /** * this target is set when either targetregex matches or not set * e.g. _blank opens all links in new window */ target: '', /** * all links that match the cssclassregex will get set the css class * e.g. ^(?!.*aloha-editor.com).* matches all href except aloha-editor.com */ cssclassregex: '', /** * this target is set when either cssclassregex matches or not set */ cssclass: '', /** * the defined object types to be used for this instance */ objectTypeFilter: [], /** * handle change on href change * called function ( obj, href, item ); */ onHrefChange: null, /** * This variable is used to ignore one selection changed event. We need * to ignore one selectionchanged event when we set our own selection. */ ignoreNextSelectionChangedEvent: false, /** * Internal update interval reference to work around an ExtJS bug */ hrefUpdateInt: null, /** * Initialize the plugin */ init: function () { var that = this; if ( typeof this.settings.targetregex != 'undefined' ) { this.targetregex = this.settings.targetregex; } if ( typeof this.settings.target != 'undefined' ) { this.target = this.settings.target; } if ( typeof this.settings.cssclassregex != 'undefined' ) { this.cssclassregex = this.settings.cssclassregex; } if ( typeof this.settings.cssclass != 'undefined' ) { this.cssclass = this.settings.cssclass; } if ( typeof this.settings.objectTypeFilter != 'undefined' ) { this.objectTypeFilter = this.settings.objectTypeFilter; } if ( typeof this.settings.onHrefChange != 'undefined' ) { this.onHrefChange = this.settings.onHrefChange; } this.createButtons(); this.subscribeEvents(); this.bindInteractions(); Aloha.ready( function () { that.initSidebar( Aloha.Sidebar.right ); } ); }, nsSel: function () { var stringBuilder = [], prefix = pluginNamespace; jQuery.each( arguments, function () { stringBuilder.push( '.' + ( this == '' ? prefix : prefix + '-' + this ) ); } ); return stringBuilder.join( ' ' ).trim(); }, //Creates string with this component's namepsace prefixed the each classname nsClass: function () { var stringBuilder = [], prefix = pluginNamespace; jQuery.each( arguments, function () { stringBuilder.push( this == '' ? prefix : prefix + '-' + this ); } ); return stringBuilder.join( ' ' ).trim(); }, initSidebar: function ( sidebar ) { var pl = this; pl.sidebar = sidebar; sidebar.addPanel( { id : pl.nsClass( 'sidebar-panel-target' ), title : i18n.t( 'floatingmenu.tab.link' ), content : '', expanded : true, activeOn : 'a, link', onInit: function () { var that = this, content = this.setContent( '
' + '' ).content; jQuery( pl.nsSel( 'framename' ) ).live( 'keyup', function () { jQuery( that.effective ).attr( 'target', jQuery( this ).val().replace( '\"', '"' ).replace( "'", "'" ) ); } ); jQuery( pl.nsSel( 'radioTarget' ) ).live( 'change', function () { if ( jQuery( this ).val() == 'framename' ) { jQuery( pl.nsSel( 'framename' ) ).slideDown(); } else { jQuery( pl.nsSel( 'framename' ) ).slideUp().val( '' ); jQuery( that.effective ).attr( 'target', jQuery( this ).val() ); } } ); jQuery( pl.nsSel( 'linkTitle' ) ).live( 'keyup', function () { jQuery( that.effective ).attr( 'title', jQuery( this ).val().replace( '\"', '"' ).replace( "'", "'" ) ); } ); }, onActivate: function ( effective ) { var that = this; that.effective = effective; if ( jQuery( that.effective ).attr( 'target' ) != null ) { var isFramename = true; jQuery( pl.nsSel( 'framename' ) ).hide().val( '' ); jQuery( pl.nsSel( 'radioTarget' ) ).each( function () { jQuery( this ).removeAttr('checked'); if ( jQuery( this ).val() === jQuery( that.effective ).attr( 'target' ) ) { isFramename = false; jQuery( this ).attr( 'checked', 'checked' ); } } ); if ( isFramename ) { jQuery( pl.nsSel( 'radioTarget[value="framename"]' ) ).attr( 'checked', 'checked' ); jQuery( pl.nsSel( 'framename' ) ) .val( jQuery( that.effective ).attr( 'target' ) ) .show(); } } else { jQuery( pl.nsSel( 'radioTarget' ) ).first().attr( 'checked', 'checked' ); jQuery( that.effective ).attr( 'target', jQuery( pl.nsSel( 'radioTarget' ) ).first().val() ); } var that = this; that.effective = effective; jQuery( pl.nsSel( 'linkTitle' ) ).val( jQuery( that.effective ).attr( 'title' ) ); } } ); sidebar.show(); }, /** * Subscribe for events */ subscribeEvents: function () { var that = this; // add the event handler for creation of editables Aloha.bind( 'aloha-editable-created', function ( event, editable ) { var config; config = that.getEditableConfig( editable.obj ); if ( jQuery.inArray( 'a', config ) == -1 ) { return; } // CTRL+L editable.obj.keydown( function ( e ) { if ( e.metaKey && e.which == 76 ) { if ( that.findLinkMarkup() ) { // open the tab containing the href FloatingMenu.activateTabOfButton( 'href' ); that.hrefField.focus(); } else { that.insertLink(); } // prevent from further handling // on a MAC Safari cursor would jump to location bar. Use ESC then META+L return false; } } ); editable.obj.find( 'a' ).each( function ( i ) { that.addLinkEventHandlers( this ); } ); } ); Aloha.bind( 'aloha-editable-activated', function ( event, rangeObject ) { var config; // show/hide the button according to the configuration config = that.getEditableConfig( Aloha.activeEditable.obj ); if ( jQuery.inArray( 'a', config ) != -1 ) { that.formatLinkButton.show(); that.insertLinkButton.show(); FloatingMenu.hideTab = false; } else { that.formatLinkButton.hide(); that.insertLinkButton.hide(); FloatingMenu.hideTab = i18n.t( 'floatingmenu.tab.link' ); } }); // add the event handler for selection change Aloha.bind( 'aloha-selection-changed', function ( event, rangeObject ) { var config, foundMarkup; if ( Aloha.activeEditable && Aloha.activeEditable.obj ) { config = that.getEditableConfig( Aloha.activeEditable.obj ); } else { config = {}; } // Check if we need to ignore this selection changed event for // now and check whether the selection was placed within a // editable area. if ( !that.ignoreNextSelectionChangedEvent && Aloha.Selection.isSelectionEditable() && Aloha.activeEditable != null && jQuery.inArray( 'a', config ) !== -1 ) { foundMarkup = that.findLinkMarkup( rangeObject ); if ( foundMarkup ) { that.toggleLinkScope( true ); // remember the current tab selected by the user var currentTab = FloatingMenu.userActivatedTab; // switch to the href tab (so that we make sure that the href field gets created) FloatingMenu.activateTabOfButton( 'href' ); if ( currentTab ) { // switch back to the original tab FloatingMenu.userActivatedTab = currentTab; } // now we are ready to set the target object that.hrefField.setTargetObject( foundMarkup, 'href' ); // if the selection-changed event was raised by the first click interaction on this page // the hrefField component might not be initialized. When the user switches to the link // tab to edit the link the field would be empty. We check for that situation and add a // special interval check to set the value once again if ( jQuery( '#' + that.hrefField.extButton.id ).length == 0 ) { // there must only be one update interval running at the same time if ( that.hrefUpdateInt !== null ) { clearInterval( that.hrefUpdateInt ); } // register a timeout that will set the value as soon as the href field was initialized that.hrefUpdateInt = setInterval( function () { if ( jQuery( '#' + that.hrefField.extButton.id ).length > 0 ) { // the object was finally created that.hrefField.setTargetObject( foundMarkup, 'href' ); clearInterval( that.hrefUpdateInt ); } }, 200 ); } Aloha.trigger( 'aloha-link-selected' ); } else { that.toggleLinkScope( false ); that.hrefField.setTargetObject( null ); Aloha.trigger( 'aloha-link-unselected' ); } } that.ignoreNextSelectionChangedEvent = false; } ); }, /** * lets you toggle the link scope to true (link buttons are visible) * or false (link buttons are hidden) * @param show bool true to show link buttons, false otherwise */ toggleLinkScope: function ( show ) { if ( show ) { this.insertLinkButton.hide(); this.hrefField.show(); this.removeLinkButton.show(); this.formatLinkButton.setPressed( true ); } else { this.insertLinkButton.show(); this.hrefField.hide(); this.removeLinkButton.hide(); this.formatLinkButton.setPressed( false ); } }, /** * Add event handlers to the given link object * @param link object */ addLinkEventHandlers: function ( link ) { var that = this; // show pointer on mouse over jQuery( link ).mouseenter( function ( e ) { Aloha.Log.debug( that, 'mouse over link.' ); that.mouseOverLink = link; that.updateMousePointer(); } ); // in any case on leave show text cursor jQuery( link ).mouseleave( function ( e ) { Aloha.Log.debug( that, 'mouse left link.' ); that.mouseOverLink = null; that.updateMousePointer(); } ); // follow link on ctrl or meta + click jQuery( link ).click( function ( e ) { if ( e.metaKey ) { // blur current editable. user is waiting for the link to load Aloha.activeEditable.blur(); // hack to guarantee a browser history entry window.setTimeout( function () { location.href = e.target; }, 0 ); e.stopPropagation(); return false; } } ); }, /** * Initialize the buttons */ createButtons: function () { var that = this; // format Link Button - this button behaves like // a formatting button like (bold, italics, etc) this.formatLinkButton = new Aloha.ui.Button( { 'name': 'a', 'iconClass': 'aloha-button aloha-button-a', 'size': 'small', 'onclick': function () { that.formatLink(); }, 'tooltip': i18n.t( 'button.addlink.tooltip' ), 'toggle': true } ); FloatingMenu.addButton( 'Aloha.continuoustext', this.formatLinkButton, i18nCore.t( 'floatingmenu.tab.format' ), 1 ); // insert Link // always inserts a new link this.insertLinkButton = new Aloha.ui.Button( { 'name': 'insertLink', 'iconClass': 'aloha-button aloha-button-a', 'size': 'small', 'onclick': function () { that.insertLink( false ); }, 'tooltip': i18n.t( 'button.addlink.tooltip' ), 'toggle': false } ); FloatingMenu.addButton( 'Aloha.continuoustext', this.insertLinkButton, i18nCore.t( 'floatingmenu.tab.insert' ), 1 ); this.hrefField = new Aloha.ui.AttributeField( { 'name': 'href', 'width': 320, 'valueField': 'url', 'cls': 'aloha-link-href-field' } ); this.hrefField.setTemplate( '{name}