Mooeypoo has uploaded a new change for review. https://gerrit.wikimedia.org/r/224971
Change subject: [HACKATHON-WIP] Create an OOUI module for WikiEditor ...................................................................... [HACKATHON-WIP] Create an OOUI module for WikiEditor Change-Id: Id631290c42a0aba2cc31faf532e24f13eaa0c43c --- M .jshintrc M WikiEditor.hooks.php M extension.json A modules/ooui-module/mw.wikiEditor.TriggerListener.js A modules/ooui-module/mw.wikiEditor.init.Target.js A modules/ooui-module/mw.wikiEditor.js A modules/ooui-module/mw.wikiEditor.ui.DesktopSurface.js A modules/ooui-module/mw.wikiEditor.ui.Overlay.js A modules/ooui-module/mw.wikiEditor.ui.Trigger.js 9 files changed, 1,188 insertions(+), 2 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikiEditor refs/changes/71/224971/1 diff --git a/.jshintrc b/.jshintrc index 81d1fa7..daaa348 100644 --- a/.jshintrc +++ b/.jshintrc @@ -29,6 +29,7 @@ "globals": { "mediaWiki": false, "jQuery": false, - "QUnit": false + "QUnit": false, + "OO": false } } diff --git a/WikiEditor.hooks.php b/WikiEditor.hooks.php index 25e340f..c693889 100644 --- a/WikiEditor.hooks.php +++ b/WikiEditor.hooks.php @@ -110,6 +110,11 @@ * T99257: Extension registration does not properly support 2d arrays so set it as a global for now */ public static function onRegistration() { + global $wgWikiEditorOOUI; + if ( $wgWikiEditorOOUI == true ) { + + return; + } // Each module may be configured individually to be globally on/off or user preference based $features = array( @@ -142,7 +147,6 @@ } else { $wgWikiEditorFeatures = $features; } - } /** @@ -235,10 +239,15 @@ * @return bool */ public static function editPageShowEditFormInitial( $editPage, $outputPage ) { + global $wgWikiEditorOOUI; if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) { return true; } + if ( $wgWikiEditorOOUI == true ) { + $outputPage->addModules( 'ooui.wikiEditor' ); + return true; + } // Add modules for enabled features foreach ( self::$features as $name => $feature ) { if ( !self::isEnabled( $name ) ) { diff --git a/extension.json b/extension.json index 0d0ccfe..867995d 100644 --- a/extension.json +++ b/extension.json @@ -12,6 +12,9 @@ "descriptionmsg": "wikieditor-desc", "type": "other", "callback": "WikiEditorHooks::onRegistration", + "config": { + "WikiEditorOOUI": false + }, "MessagesDirs": { "WikiEditor": [ "i18n" @@ -47,6 +50,12 @@ ] }, "ResourceModules": { + "ooui.wikiEditor": { + "group": "ext.wikiEditor", + "dependencies": [ + "oojs-ui" + ] + }, "jquery.wikiEditor": { "group": "ext.wikiEditor", "scripts": "jquery.wikiEditor.js", diff --git a/modules/ooui-module/mw.wikiEditor.TriggerListener.js b/modules/ooui-module/mw.wikiEditor.TriggerListener.js new file mode 100644 index 0000000..48e3014 --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.TriggerListener.js @@ -0,0 +1,82 @@ +( function ( mw ) { + /*! + * VisualEditor UserInterface TriggerListener class. + * + * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org + */ + + /** + * Trigger listener + * + * @class + * + * @constructor + * @param {string[]} commands Commands to listen to triggers for + */ + mw.wikiEditor.TriggerListener = function MwWikiEditorUiTriggerListener( commands ) { + // Properties + this.commands = []; + this.commandsByTrigger = {}; + this.triggers = {}; + + this.setupCommands( commands ); + }; + + /* Inheritance */ + + OO.initClass( mw.wikiEditor.TriggerListener ); + + /* Methods */ + + /** + * Setup commands + * + * @param {string[]} commands Commands to listen to triggers for + */ + mw.wikiEditor.TriggerListener.prototype.setupCommands = function ( commands ) { + var i, j, command, triggers; + this.commands = commands; + if ( commands.length ) { + for ( i = this.commands.length - 1; i >= 0; i-- ) { + command = this.commands[i]; + triggers = mw.wikiEditor.triggerRegistry.lookup( command ); + if ( triggers ) { + for ( j = triggers.length - 1; j >= 0; j-- ) { + this.commandsByTrigger[triggers[j].toString()] = mw.wikiEditor.commandRegistry.lookup( command ); + } + this.triggers[command] = triggers; + } + } + } + }; + + /** + * Get list of commands. + * + * @returns {string[]} Commands + */ + mw.wikiEditor.TriggerListener.prototype.getCommands = function () { + return this.commands; + }; + + /** + * Get command associated with trigger string. + * + * @method + * @param {string} trigger Trigger string + * @returns {ve.ui.Command|undefined} Command + */ + mw.wikiEditor.TriggerListener.prototype.getCommandByTrigger = function ( trigger ) { + return this.commandsByTrigger[trigger]; + }; + + /** + * Get triggers for a specified name. + * + * @param {string} name Trigger name + * @returns {ve.ui.Trigger[]|undefined} Triggers + */ + mw.wikiEditor.TriggerListener.prototype.getTriggers = function ( name ) { + return this.triggers[name]; + }; +} )( mediaWiki ); diff --git a/modules/ooui-module/mw.wikiEditor.init.Target.js b/modules/ooui-module/mw.wikiEditor.init.Target.js new file mode 100644 index 0000000..b827ed4 --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.init.Target.js @@ -0,0 +1,301 @@ +( function ( mw, $ ) { + /*! + * VisualEditor Initialization Target class. + * + * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org + */ + + /** + * Generic Wikitext initialization target. + * + * @class + * @abstract + * @extends OO.ui.Element + * @mixins OO.EventEmitter + * + * @constructor + * @param {Object} toolbarConfig Configuration options for the toolbar + */ + mw.wikiEditor.init.Target = function VeInitTarget( toolbarConfig ) { + // Parent constructor + OO.ui.Element.call( this ); + + // Mixin constructors + OO.EventEmitter.call( this ); + + // Properties + this.surfaces = []; + mw.wikiEditor.init.target = this; + this.surface = null; + this.toolbar = null; + this.toolbarConfig = toolbarConfig; +/* this.documentTriggerListener = new ve.TriggerListener( this.constructor.static.documentCommands ); + this.targetTriggerListener = new ve.TriggerListener( this.constructor.static.targetCommands ); + + // Initialization + this.$element.addClass( 've-init-target' ); + + if ( ve.init.platform.constructor.static.isInternetExplorer() ) { + this.$element.addClass( 've-init-target-ie' ); + } + + // Events + this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); + this.onTargetKeyDownHandler = this.onTargetKeyDown.bind( this ); + this.bindHandlers(); + + // Register + ve.init.target = this;*/ + }; + + /* Inheritance */ + + OO.inheritClass( mw.wikiEditor.init.Target, OO.ui.Element ); + + OO.mixinClass( mw.wikiEditor.init.Target, OO.EventEmitter ); + + /* Events */ + + /** + * @event addSurface + * @param {mw.wikiEditor.ui.Surface} surface Added surface + */ + + /* Static Properties */ + + // TODO: Grab VE's toolbar definition + mw.wikiEditor.init.Target.static.toolbarGroups = [ + // History + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-history' ), + include: [ 'undo', 'redo' ] + }, + // Format + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-paragraph-format' ), + type: 'menu', + indicator: 'down', + title: OO.ui.deferMsg( 'visualeditor-toolbar-format-tooltip' ), + include: [ { group: 'format' } ], + promote: [ 'paragraph' ], + demote: [ 'preformatted', 'blockquote' ] + }, + // Text style + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-text-style' ), + title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ), + include: [ 'bold', 'italic', 'moreTextStyle' ] + }, + // Link + { + header: OO.ui.deferMsg( 'visualeditor-linkinspector-title' ), + include: [ 'link' ] + }, + // Structure + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-structure' ), + type: 'list', + icon: 'listBullet', + indicator: 'down', + include: [ { group: 'structure' } ], + demote: [ 'outdent', 'indent' ] + }, + // Insert + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ), + type: 'list', + icon: 'insert', + label: '', + title: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ), + indicator: 'down', + include: '*' + }, + // Special character toolbar + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-insert' ), + include: [ 'specialCharacter' ] + }, + // Table + { + header: OO.ui.deferMsg( 'visualeditor-toolbar-table' ), + type: 'list', + icon: 'table', + indicator: 'down', + include: [ { group: 'table' } ], + demote: [ 'deleteTable' ] + } + ]; + + /** + * List of commands which can be triggered anywhere from within the document + * + * @type {string[]} List of command names + */ + //mw.wikiEditor.init.Target.static.documentCommands = [ 'commandHelp' ]; + + /** + * List of commands to exclude from the target entirely + * + * @type {string[]} List of command names + */ + mw.wikiEditor.init.Target.static.excludeCommands = []; + + /** + * Surface import rules + * + * One set for external (non-VE) paste sources and one for all paste sources. + * + * @see ve.dm.ElementLinearData#sanitize + * @type {Object} + */ + // mw.wikiEditor.init.Target.static.importRules = { + // external: { + // blacklist: [ + // // Annotations + // // TODO: allow spans + // 'textStyle/span', 'textStyle/font', + // // Nodes + // 'alienInline', 'alienBlock', 'comment' + // ] + // }, + // all: null + // }; + + /* Methods */ + + /** + * Bind event handlers to target and document + */ + mw.wikiEditor.init.Target.prototype.bindHandlers = function () { + $( this.getElementDocument() ).on( 'keydown', this.onDocumentKeyDownHandler ); + this.$element.on( 'keydown', this.onTargetKeyDownHandler ); + }; + + /** + * Unbind event handlers on target and document + */ + mw.wikiEditor.init.Target.prototype.unbindHandlers = function () { + $( this.getElementDocument() ).off( 'keydown', this.onDocumentKeyDownHandler ); + this.$element.off( 'keydown', this.onTargetKeyDownHandler ); + }; + + /** + * Destroy the target + */ + mw.wikiEditor.init.Target.prototype.destroy = function () { + this.clearSurfaces(); + if ( this.toolbar ) { + this.toolbar.destroy(); + this.toolbar = null; + } + this.$element.remove(); + this.unbindHandlers(); + mw.wikiEditor.init.target = null; + }; + + /** + * Create a surface. + * + * @method + * @param {ve.dm.Document} dmDoc Document model (Not in use) + * @param {Object} [config] Configuration options + * @returns {ve.ui.Surface} + */ + mw.wikiEditor.init.Target.prototype.createSurface = function ( dmDoc, config ) { + // config = ve.extendObject( { + // excludeCommands: OO.simpleArrayUnion( + // this.constructor.static.excludeCommands, + // this.constructor.static.documentCommands, + // this.constructor.static.targetCommands + // ), + // importRules: this.constructor.static.importRules + // }, config ); + return new mw.wikiEditor.ui.DesktopSurface( config ); + }; + + /** + * Add a surface to the target + * + * @param {ve.dm.Document} dmDoc Document model + * @param {Object} [config] Configuration options + * @fires addSurface + * @returns {ve.ui.Surface} + */ + mw.wikiEditor.init.Target.prototype.addSurface = function ( dmDoc, config ) { + var surface = this.createSurface( dmDoc, config ); + this.surfaces.push( surface ); + + this.emit( 'addSurface', surface ); + return surface; + }; + + /** + * Destroy and remove all surfaces from the target + */ + mw.wikiEditor.init.Target.clearSurfaces = function () { + while ( this.surfaces.length ) { + this.surfaces.pop().destroy(); + } + }; + + /** + * Handle focus events from a surface's view + * + * @param {ve.ui.Surface} surface Surface firing the event + */ + mw.wikiEditor.init.Target.prototype.onSurfaceViewFocus = function ( surface ) { + this.setSurface( surface ); + }; + + /** + * Set the target's active surface + * + * @param {mw.wikiEditor.ui.Surface} surface Surface + */ + mw.wikiEditor.init.Target.prototype.setSurface = function ( surface ) { + if ( surface !== this.surface ) { + this.surface = surface; + this.setupToolbar( surface ); + } + }; + + /** + * Get the target's active surface, if it exists + * + * @return {ve.ui.Surface|null} Surface + */ + mw.wikiEditor.init.Target.prototype.getSurface = function () { + return this.surface; + }; + + /** + * Get the target's toolbar + * + * @return {ve.ui.TargetToolbar} Toolbar + */ + mw.wikiEditor.init.Target.prototype.getToolbar = function () { + if ( !this.toolbar ) { + this.toolbar = new mw.wikiEditor.ui.TargetToolbar( this, this.toolbarConfig ); + } + return this.toolbar; + }; + + /** + * Set up the toolbar, attaching it to a surface. + * + * @param {ve.ui.Surface} surface Surface + */ + mw.wikiEditor.init.Target.prototype.setupToolbar = function ( surface ) { + this.getToolbar().setup( this.constructor.static.toolbarGroups, surface ); + this.attachToolbar( surface ); + // TODO: Figure out how to reuse VE dialogs from outside VE + // this.getToolbar().$bar.append( surface.getToolbarDialogs().$element ); + }; + + /** + * Attach the toolbar to the DOM + */ + mw.wikiEditor.init.Target.prototype.attachToolbar = function () { + this.getToolbar().$element.insertBefore( this.getToolbar().getSurface().$element ); + }; +} )( mediaWiki, jQuery ); diff --git a/modules/ooui-module/mw.wikiEditor.js b/modules/ooui-module/mw.wikiEditor.js new file mode 100644 index 0000000..19376ff --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.js @@ -0,0 +1,9 @@ +( function ( mw ) { + mw.wikiEditor = { + init: {}, + ui: {} + }; + mw.wikiEditor.triggerRegistry = new OO.Registry(); + mw.wikiEditor.commandRegistry = new OO.Registry(); + +} )( mediaWiki ); diff --git a/modules/ooui-module/mw.wikiEditor.ui.DesktopSurface.js b/modules/ooui-module/mw.wikiEditor.ui.DesktopSurface.js new file mode 100644 index 0000000..2d78dfc --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.ui.DesktopSurface.js @@ -0,0 +1,344 @@ +( function ( mw, $ ) { + /*! + * VisualEditor UserInterface Surface class. + * + * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org + */ + + /** + * A surface is a top-level object which contains both a surface model and a surface view. + * + * @class + * @abstract + * @extends OO.ui.Element + * @mixins OO.EventEmitter + * + * @constructor + * @param {string} data Initial data in the surface + * @param {Object} [config] Configuration options + * @cfg {string[]} [excludeCommands] List of commands to exclude + * @cfg {Object} [importRules] Import rules + * @cfg {string} [placeholder] Placeholder text to display when the surface is empty + * @cfg {string} [inDialog] The name of the dialog this surface is in + */ + mw.wikiEditor.ui.DesktopSurface = function mwWikiEditorUiSurface( data, config ) { + config = config || {}; + + // Parent constructor + OO.ui.Element.call( this, config ); + + // Mixin constructor + OO.EventEmitter.call( this, config ); + + this.textInput = new OO.ui.TextInputWidget( { + multiline: true, + placeholder: config.placeholder, + classes: [ 'mw-wikiEditor-ui-textInput' ] + } ); + + // Properties + this.inDialog = config.inDialog || ''; + this.globalOverlay = new mw.wikiEditor.ui.Overlay( { classes: [ 'mw-wikiEditor-ui-overlay-global' ] } ); + this.triggerListener = new mw.wikiEditor.TriggerListener( OO.simpleArrayDifference( + Object.keys( mw.wikiEditor.ui.commandRegistry.registry ), config.excludeCommands || [] + ) ); + + // this.dialogs = this.createDialogWindowManager(); + this.enabled = true; + this.progresses = []; + this.showProgressDebounced = OO.ui.debounce( this.showProgress.bind( this ) ); + + this.toolbarHeight = 0; + // TODO: Work with dialogs (make sure they are not dependent on VE) + // this.toolbarDialogs = new ve.ui.ToolbarDialogWindowManager( this, { + // factory: ve.ui.windowFactory, + // modal: false + // } ); + + // Initialization + this.$element + .addClass( 'mw-wikiEditor-ui-desktopSurface' ) + .append( this.textInput.$element ); + + // this.globalOverlay.$element.append( this.dialogs.$element ); + }; + + /* Inheritance */ + + OO.inheritClass( mw.wikiEditor.ui.DesktopSurface, OO.ui.Element ); + + OO.mixinClass( mw.wikiEditor.ui.DesktopSurface, OO.EventEmitter ); + + /* Events */ + + /** + * When a surface is destroyed. + * + * @event destroy + */ + + /* Methods */ + + /** + * Destroy the surface, releasing all memory and removing all DOM elements. + * + * @method + * @fires destroy + */ + mw.wikiEditor.ui.DesktopSurface.prototype.destroy = function () { + + // Destroy the ce.Surface, the ui.Context and window managers + // this.dialogs.destroy(); + // this.toolbarDialogs.destroy(); + + // Remove DOM elements + this.$element.remove(); + this.globalOverlay.$element.remove(); + + // Let others know we have been destroyed + this.emit( 'destroy' ); + }; + + /** + * Initialize surface. + * + * This must be called after the surface has been attached to the DOM. + */ + mw.wikiEditor.ui.DesktopSurface.prototype.initialize = function () { + // Attach globalOverlay to the global <body>, not the local frame's <body> + $( 'body' ).append( this.globalOverlay.$element ); + }; + + /** + * Create a dialog window manager. + * + * @method + * @abstract + * @return {ve.ui.WindowManager} Dialog window manager + */ + // ve.ui.Surface.prototype.createDialogWindowManager = null; + + /** + * Check if editing is enabled. + * + * @method + * @returns {boolean} Editing is enabled + */ + mw.wikiEditor.ui.DesktopSurface.prototype.isEnabled = function () { + return this.enabled; + }; + + /** + * Get dialogs window set. + * + * @method + * @returns {ve.ui.WindowManager} Dialogs window set + */ + // mw.wikiEditor.ui.DesktopSurface.prototype.getDialogs = function () { + // return this.dialogs; + // }; + + /** + * Get toolbar dialogs window set. + * @returns {ve.ui.WindowManager} Toolbar dialogs window set + */ + // ve.ui.Surface.prototype.getToolbarDialogs = function () { + // return this.toolbarDialogs; + // }; + + /** + * Get the global overlay. + * + * Global overlays are attached to the top-most frame. + * + * @method + * @returns {ve.ui.Overlay} Global overlay + */ + mw.wikiEditor.ui.DesktopSurface.prototype.getGlobalOverlay = function () { + return this.globalOverlay; + }; + + /** + * Disable editing. + * + * @method + */ + mw.wikiEditor.ui.DesktopSurface.prototype.disable = function () { + this.enabled = false; + this.textInput.setDisabled( !this.enabled ); + }; + + /** + * Enable editing. + * + * @method + */ + mw.wikiEditor.ui.DesktopSurface.prototype.enable = function () { + this.enabled = true; + this.textInput.setDisabled( !this.enabled ); + }; + + /** + * Execute an action or command. + * + * @method + * @param {ve.ui.Trigger|string} triggerOrAction Trigger or symbolic name of action + * @param {string} [method] Action method name + * @param {Mixed...} [args] Additional arguments for action + * @returns {boolean} Action or command was executed + */ + ve.ui.Surface.prototype.execute = function ( triggerOrAction, method ) { + var command, obj, ret; + + if ( !this.enabled ) { + return; + } + + if ( triggerOrAction instanceof ve.ui.Trigger ) { + command = this.triggerListener.getCommandByTrigger( triggerOrAction.toString() ); + if ( command ) { + // Have command call execute with action arguments + return command.execute( this ); + } + } else if ( typeof triggerOrAction === 'string' && typeof method === 'string' ) { + // Validate method + if ( ve.ui.actionFactory.doesActionSupportMethod( triggerOrAction, method ) ) { + // Create an action object and execute the method on it + obj = ve.ui.actionFactory.create( triggerOrAction, this ); + ret = obj[method].apply( obj, Array.prototype.slice.call( arguments, 2 ) ); + return ret === undefined || !!ret; + } + } + return false; + }; + + /** + * Set the current height of the toolbar. + * + * Used for scroll-into-view calculations. + * + * @param {number} toolbarHeight Toolbar height + */ + ve.ui.Surface.prototype.setToolbarHeight = function ( toolbarHeight ) { + this.toolbarHeight = toolbarHeight; + }; + + /** + * Create a progress bar in the progress dialog + * + * @param {jQuery.Promise} progressCompletePromise Promise which resolves when the progress action is complete + * @param {jQuery|string|Function} label Progress bar label + * @return {jQuery.Promise} Promise which resolves with a progress bar widget and a promise which fails if cancelled + */ + ve.ui.Surface.prototype.createProgress = function ( progressCompletePromise, label ) { + var progressBarDeferred = $.Deferred(); + + this.progresses.push( { + label: label, + progressCompletePromise: progressCompletePromise, + progressBarDeferred: progressBarDeferred + } ); + + this.showProgressDebounced(); + + return progressBarDeferred.promise(); + }; + + ve.ui.Surface.prototype.showProgress = function () { + var dialogs = this.dialogs, + progresses = this.progresses; + + dialogs.openWindow( 'progress', { progresses: progresses } ); + this.progresses = []; + }; + + /** + * Get sanitization rules for rich paste + * + * @returns {Object} Import rules + */ + ve.ui.Surface.prototype.getImportRules = function () { + return this.importRules; + }; + + /** + * Surface 'dir' property (GUI/User-Level Direction) + * + * @returns {string} 'ltr' or 'rtl' + */ + ve.ui.Surface.prototype.getDir = function () { + return this.$element.css( 'direction' ); + }; + + ve.ui.Surface.prototype.initFilibuster = function () { + var surface = this; + this.filibuster = new ve.Filibuster() + .wrapClass( ve.EventSequencer ) + .wrapNamespace( ve.dm, 've.dm', [ + // blacklist + ve.dm.LinearSelection.prototype.getDescription, + ve.dm.TableSelection.prototype.getDescription, + ve.dm.NullSelection.prototype.getDescription + ] ) + .wrapNamespace( ve.ce, 've.ce' ) + .wrapNamespace( ve.ui, 've.ui', [ + // blacklist + ve.ui.Surface.prototype.startFilibuster, + ve.ui.Surface.prototype.stopFilibuster + ] ) + .setObserver( 'dm doc', function () { + return JSON.stringify( ve.Filibuster.static.clonePlain( + surface.model.documentModel.data.data + ) ); + } ) + .setObserver( 'dm selection', function () { + var selection = surface.model.selection; + if ( !selection ) { + return null; + } + return selection.getDescription(); + } ) + .setObserver( 'DOM doc', function () { + return ve.serializeNodeDebug( surface.view.$element[0] ); + } ) + .setObserver( 'DOM selection', function () { + var nativeRange, + nativeSelection = surface.view.nativeSelection; + if ( nativeSelection.rangeCount === 0 ) { + return null; + } + nativeRange = nativeSelection.getRangeAt( 0 ); + return JSON.stringify( { + startContainer: ve.serializeNodeDebug( nativeRange.startContainer ), + startOffset: nativeRange.startOffset, + endContainer: ( + nativeRange.startContainer === nativeRange.endContainer ? + '(=startContainer)' : + ve.serializeNodeDebug( nativeRange.endContainer ) + ), + endOffset: nativeRange.endOffset + } ); + } ); + }; + + ve.ui.Surface.prototype.startFilibuster = function () { + if ( !this.filibuster ) { + this.initFilibuster(); + } else { + this.filibuster.clearLogs(); + } + this.filibuster.start(); + }; + + ve.ui.Surface.prototype.stopFilibuster = function () { + this.filibuster.stop(); + }; + + /** + * Get the name of the dialog this surface is in + * @return {string} The name of the dialog this surface is in + */ + ve.ui.Surface.prototype.getInDialog = function () { + return this.inDialog; + }; +} )( mediaWiki, jQuery ); diff --git a/modules/ooui-module/mw.wikiEditor.ui.Overlay.js b/modules/ooui-module/mw.wikiEditor.ui.Overlay.js new file mode 100644 index 0000000..27bfca0 --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.ui.Overlay.js @@ -0,0 +1,27 @@ +/*! + * VisualEditor UserInterface Overlay class. + * + * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org + */ + +/** + * Container for content that should appear in front of everything else. + * + * @class + * @abstract + * @extends OO.ui.Element + * + * @constructor + * @param {Object} [config] Configuration options + */ +mw.wikiEditor.ui.Overlay = function MwWikiEditorUiOverlay( config ) { + // Parent constructor + OO.ui.Element.call( this, config ); + + // Initialization + this.$element.addClass( 'mw-wikiEditor-ui-overlay' ); +}; + +/* Inheritance */ + +OO.inheritClass( mw.wikiEditor.ui.Overlay, OO.ui.Element ); diff --git a/modules/ooui-module/mw.wikiEditor.ui.Trigger.js b/modules/ooui-module/mw.wikiEditor.ui.Trigger.js new file mode 100644 index 0000000..1a1c39b --- /dev/null +++ b/modules/ooui-module/mw.wikiEditor.ui.Trigger.js @@ -0,0 +1,404 @@ +( function ( mw, $ ) { + /*! + * VisualEditor UserInterface Trigger class. + * + * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org + */ + + /** + * Key trigger. + * + * @class + * + * @constructor + * @param {jQuery.Event|string} [e] Event or string to create trigger from + * @param {boolean} [allowInvalidPrimary] Allow invalid primary keys + */ + mw.wikiEditor.ui.Trigger = function MwWikiEditorUiTrigger( e, allowInvalidPrimary ) { + // Properties + this.modifiers = { + meta: false, + ctrl: false, + alt: false, + shift: false + }; + this.primary = false; + + // Initialization + var i, len, key, parts, + keyAliases = mw.wikiEditor.ui.Trigger.static.keyAliases, + primaryKeys = mw.wikiEditor.ui.Trigger.static.primaryKeys, + primaryKeyMap = mw.wikiEditor.ui.Trigger.static.primaryKeyMap; + if ( e instanceof jQuery.Event ) { + this.modifiers.meta = e.metaKey || false; + this.modifiers.ctrl = e.ctrlKey || false; + this.modifiers.alt = e.altKey || false; + this.modifiers.shift = e.shiftKey || false; + this.primary = primaryKeyMap[e.which] || false; + } else if ( typeof e === 'string' ) { + // Normalization: remove whitespace and force lowercase + parts = e.replace( /\s*/g, '' ).toLowerCase().split( '+' ); + for ( i = 0, len = parts.length; i < len; i++ ) { + key = parts[i]; + // Resolve key aliases + if ( Object.prototype.hasOwnProperty.call( keyAliases, key ) ) { + key = keyAliases[key]; + } + // Apply key to trigger + if ( Object.prototype.hasOwnProperty.call( this.modifiers, key ) ) { + // Modifier key + this.modifiers[key] = true; + } else if ( primaryKeys.indexOf( key ) !== -1 || allowInvalidPrimary ) { + // WARNING: Only the last primary key will be used + this.primary = key; + } + } + } + }; + + /* Inheritance */ + + OO.initClass( mw.wikiEditor.ui.Trigger ); + + /* Static Properties */ + + /** + * Symbolic modifier key names. + * + * The order of this array affects the canonical order of a trigger string. + * + * @static + * @property + * @inheritable + */ + mw.wikiEditor.ui.Trigger.static.modifierKeys = [ 'meta', 'ctrl', 'alt', 'shift' ]; + + /** + * Symbolic primary key names. + * + * @static + * @property + * @inheritable + */ + mw.wikiEditor.ui.Trigger.static.primaryKeys = [ + // Special keys + 'backspace', + 'tab', + 'enter', + 'escape', + 'page-up', + 'page-down', + 'end', + 'home', + 'left', + 'up', + 'right', + 'down', + 'delete', + 'clear', + // Numbers + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + // Letters + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + // Numpad special keys + 'multiply', + 'add', + 'subtract', + 'decimal', + 'divide', + // Function keys + 'f1', + 'f2', + 'f3', + 'f4', + 'f5', + 'f6', + 'f7', + 'f8', + 'f9', + 'f10', + 'f11', + 'f12', + // Punctuation + ';', + '=', + ',', + '-', + '.', + '/', + '`', + '[', + '\\', + ']', + '\'' + ]; + + /** + * Filter to use when rendering string for a specific platform. + * + * @static + * @property + * @inheritable + */ + mw.wikiEditor.ui.Trigger.static.platformFilters = { + mac: ( function () { + var names = { + meta: '⌘', + shift: '⇧', + backspace: '⌫', + ctrl: '^', + alt: '⎇', + escape: '⎋' + }; + return function ( keys ) { + var i, len; + for ( i = 0, len = keys.length; i < len; i++ ) { + keys[i] = names[keys[i]] || keys[i]; + } + return keys.join( '' ).toUpperCase(); + }; + } )() + }; + + /** + * Aliases for modifier or primary key names. + * + * @static + * @property + * @inheritable + */ + mw.wikiEditor.ui.Trigger.static.keyAliases = { + // Platform differences + command: 'meta', + apple: 'meta', + windows: 'meta', + option: 'alt', + return: 'enter', + // Shorthand + esc: 'escape', + cmd: 'meta', + del: 'delete', + // Longhand + control: 'ctrl', + alternate: 'alt', + // Symbols + '⌘': 'meta', + '⎇': 'alt', + '⇧': 'shift', + '⏎': 'enter', + '⌫': 'backspace', + '⎋': 'escape' + }; + + /** + * Mapping of key codes and symbolic key names. + * + * @static + * @property + * @inheritable + */ + mw.wikiEditor.ui.Trigger.static.primaryKeyMap = { + // Special keys + 8: 'backspace', + 9: 'tab', + 12: 'clear', + 13: 'enter', + 27: 'escape', + 33: 'page-up', + 34: 'page-down', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 46: 'delete', + // Numbers + 48: '0', + 49: '1', + 50: '2', + 51: '3', + 52: '4', + 53: '5', + 54: '6', + 55: '7', + 56: '8', + 57: '9', + // Punctuation + 59: ';', + 61: '=', + // Letters + 65: 'a', + 66: 'b', + 67: 'c', + 68: 'd', + 69: 'e', + 70: 'f', + 71: 'g', + 72: 'h', + 73: 'i', + 74: 'j', + 75: 'k', + 76: 'l', + 77: 'm', + 78: 'n', + 79: 'o', + 80: 'p', + 81: 'q', + 82: 'r', + 83: 's', + 84: 't', + 85: 'u', + 86: 'v', + 87: 'w', + 88: 'x', + 89: 'y', + 90: 'z', + // Numpad numbers + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9', + // Numpad special keys + 106: 'multiply', + 107: 'add', + 109: 'subtract', + 110: 'decimal', + 111: 'divide', + // Function keys + 112: 'f1', + 113: 'f2', + 114: 'f3', + 115: 'f4', + 116: 'f5', + 117: 'f6', + 118: 'f7', + 119: 'f8', + 120: 'f9', + 121: 'f10', + 122: 'f11', + 123: 'f12', + // Punctuation + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }; + + /* Methods */ + + /** + * Check if trigger is complete. + * + * For a trigger to be complete, there must be a valid primary key. + * + * @returns {boolean} Trigger is complete + */ + mw.wikiEditor.ui.Trigger.prototype.isComplete = function () { + return this.primary !== false; + }; + + /** + * Get a trigger string. + * + * Trigger strings are canonical representations of triggers made up of the symbolic names of all + * active modifier keys and the primary key joined together with a '+' sign. + * + * To normalize a trigger string simply create a new trigger from a string and then run this method. + * + * An incomplete trigger will return an empty string. + * + * @returns {string} Canonical trigger string + */ + mw.wikiEditor.ui.Trigger.prototype.toString = function () { + var i, len, + modifierKeys = mw.wikiEditor.ui.Trigger.static.modifierKeys, + keys = []; + // Add modifier keywords in the correct order + for ( i = 0, len = modifierKeys.length; i < len; i++ ) { + if ( this.modifiers[modifierKeys[i]] ) { + keys.push( modifierKeys[i] ); + } + } + // Check that there were modifiers and the primary key is whitelisted + if ( this.primary ) { + // Add a symbolic name for the primary key + keys.push( this.primary ); + return keys.join( '+' ); + } + // Alternatively return an empty string + return ''; + }; + + /** + * Get a trigger message. + * + * This is similar to #toString but the resulting string will be formatted in a way that makes it + * appear more native for the platform. + * + * @returns {string} Message for trigger + */ + mw.wikiEditor.ui.Trigger.prototype.getMessage = function () { + var keys, + platformFilters = mw.wikiEditor.ui.Trigger.static.platformFilters, + // platform = ve.getSystemPlatform(); + // HACK: This should be done better, but we don't want to copy + // platform over too + platform = $.client.profile().platform; + + keys = this.toString().split( '+' ); + if ( Object.prototype.hasOwnProperty.call( platformFilters, platform ) ) { + return platformFilters[platform]( keys ); + } + return keys.map( function ( key ) { + return key[0].toUpperCase() + key.slice( 1 ).toLowerCase(); + } ).join( '+' ); + }; +} )( mediaWiki, jQuery ); -- To view, visit https://gerrit.wikimedia.org/r/224971 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id631290c42a0aba2cc31faf532e24f13eaa0c43c Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/WikiEditor Gerrit-Branch: master Gerrit-Owner: Mooeypoo <mor...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits