MediaWiki:CodeEditor/library.js

/** *  * @description             Fandom code editing library for Ace. * @module                 dev.codeeditor.library * @namespace              window.dev.codeEditor * @author                 Speedit * @version                0.5.0 * @license                CC-BY-SA 3.0 * @notes                  Will support web notifications when MAIN-18542 *                         is merged. * */ define('dev.codeeditor.library', ['wikia.window', 'jquery', 'mw', 'wikia.browserDetect', 'wikia.loader'], function(window, $, mw, browserDetect, loader) {    'use strict';

// Scope limiting window.dev = window.dev || {}; if (       window.wgAction !== 'edit' ||        browserDetect.isMobile ||        $(document.body).hasClass('codeeditor') ||        window.dev.codeEditor    ) { return; }   window.dev.codeEditor = {};

// MediaWiki title object. var title = new mw.Title(window.wgPageName);

// Script events. var LOADING = $.Deferred; var FATAL = $.Deferred.reject({       error: $.msg('oasis-generic-error')    });

/**    * Library bootloader. * @param              {object} modelist Modelist submodule for CodeEdtitor. * @private */   function codeEditorInit(modelist) { window.dev.codeEditor = new CodeEditor(modelist); }

/**    * Script constructor. * @class CodeEditor */   function CodeEditor(modelist) { /**         * @property       {object} modelist Modelist submodule for *                CodeEdtitor. * @property      {string} text Name of Ace's 'ace/mode/text' *                submodule. * @property      {string} path Current path of wiki page *                (FULLPAGENAME). * @property      {object} automode Automatic mode object determined *                by modelist. * @property      {string=} automode.name Name of automatic mode. */       this.modelist = modelist; this.text = modelist.modes.text.name; this.path = title.toText; this.automode = this.modelist.getModeForPath(this.path) || {};

mw.hook('dev.codeeditor.library').fire(this); }

/**    * Conditional, configurable boot handler. * Initialises editor when all conditionals are valid. * @method             config * @param              {Object.} opts Configuration object. * @param              {String|Array} opts.mode *                     Mode(s) for Ace to initialise, by descending priority. *                     Fallbacks to automatic mode name based on     *                      SUBPAGENAME. * @param              {String|Array} opts.auto *                     Whether the mode & booting is determined automatically. *                     A mode array can be supplied in automatic mode. * @param              {String|Array} opts.pages *                     Page name to test against FULLPAGENAME. * @param              {RegExp} opts.regexp *                     Regular expression tested against FULLPAGENAME. * @returns            {jQuery.Deferred} Script bootloading event; thenable. */   CodeEditor.prototype.config = function(opts) { if (typeof opts !== 'object') { return FATAL; }       var ext = title.getExtension, automode = this.automode.name, modes = this._toArray(opts.modes || opts.mode || automode) .filter(this.modelist.modes.hasOwnProperty), bools = {}, mode;

if (opts.auto === true) { var m_i = modes.indexOf(automode); bools.auto = Boolean(automode) && m_i !== -1; }

mode = bools.auto ? modes[m_i] : modes[0] || automode;

bools.mode = Boolean(mode);

opts.extensions = this._toArray(opts.extensions || opts.ext); if (opts.extensions.length !== 0) { bools.extensions = !!ext && opts.extensions.some(this._equals(ext)); }

opts.pages = this._toArray(opts.pages || opts.page); if (opts.pages.length !== 0) { bools.pages = opts.pages.some(this._equals(this.path)); }

opts.regexp = opts.regexp || opts.rgx; if ((opts.regexp || {}) instanceof RegExp) { bools.regexp = opts.regexp.test(this.path); }

for (var i in bools) { if (!bools[i]) return FATAL; }       return this.boot(mode);

};

/** Code editing bootloader. * @method             boot * @param              {string} mode Mode Ace submodule key. * @returns            {jQuery.Deferred} Script bootloading event; thenable. */   CodeEditor.prototype.boot = function(mode) { var _; if (arguments[1] instanceof AnimationEvent) { _ = arguments[1]; _.c = _.animationName === 'dev_codeeditor'; }       if (            window.wgEnableCodePageEditor ||            typeof mode !== 'string' ||            !this.modelist.modes[mode] ||            _ && !_.c        ) { return FATAL; }       var wkEd = window.WikiaEditor, rte = wkEd.getInstance.element .hasClass('mode-wysiwyg'), $btn = $('.cke_button__modesource'), $box = $('.cke_source');

if (rte) { if (!_) { this._reboot = this.boot.bind(this, mode); document.body.addEventListener('animationstart', this._reboot); this._styles = mw.util.addCSS(                   '.rte_wysiwyg, .cke_source {' +                        'animation: dev_codeeditor 1ms ease;' +                    '}' +                    '@keyframes dev_codeeditor {' +                        'from { outline-color: currentColor; }' +                        'to   { outline-color: transparent;  }' +                    '}'                ).ownerNode; this._styles.id = 'codeEditorRteStyles'; }           if (!$btn.exists) { return LOADING; }           if ((!_ || _.c) && !$box.exists) { $btn.click; $('.cke_toolbar_mode_switch').addClass('wds-is-hidden'); return LOADING; }       } else if (this._styles) { this._styles.parentNode.removeChild(this._styles); document.body.removeEventListener('animationstart', this._reboot); delete this._styles; delete this._reboot; }

window.wgEnableCodePageEditor = true; window.WikiaAutostartDisabled = true; window.aceScriptsPath = '/__am/' + window.wgStyleVersion + '/one/' + encodeURIComponent('minify=1') + '/resources/Ace'; window.codePageType = mode; window.showPagePreview = false; this._preview = mode === 'html';

$('#EditPageToolbar').empty; $('#EditPageRail [id^="wp"]:not(#wpMinoredit)').off; $('#wpSave').on('click.dev_codeeditor', this._exit.bind(this)); $('#EditPageRail .rail-auto-height').remove; if (!this._preview) { var $nav = $(' ', {               'class': 'wikia-menu-button wikia-menu-button-submit',                'append': [                    $('#wpSave').attr({ 'class': 'codepage-publish-button' }),                   $(' ', {                        'class': 'drop', append: ' ' }),                   $('', { 'class': 'WikiaMenuElement', append: $('', {                           append: $('#wpDiff').removeAttr('class')                        }) })               ]            });            $('.preview_box').replaceWith($nav); window.WikiaButtons.add($nav); }       $(document.body).addClass('codeeditor');

var lib = document.createElement('script'); lib.addEventListener('load', this._load.bind(this)); lib.type = 'text/javascript'; lib.src = window.wgServer + window.aceScriptsPath + '/ace.js'; document.head.appendChild(lib);

return LOADING; };

/**    * AssetManager loader for Ace script group. * @method             _load * @private */   CodeEditor.prototype._load = function { loader({           type: loader.AM_GROUPS,            resources: ['ace_editor_js']        }).then(            require.bind( window, ['wikia.editpage.ace.editor'], this._init.bind(this) )       );    };

/**    * Ace editor initialiser (post-loading). * @method             _init * @private */   CodeEditor.prototype._init = function(aceEditor) { $('.editpage-widemode-trigger').off.removeAttr('style'); window.WikiaEditor.plugins.noticearea.prototype.initDom = $.noop; if (window.RTE) { $(window).off('resize mouseenter', window.RTE.repositionRTEOverlay); $(document.body).removeClass('rte rte_source'); }

aceEditor.init; this.editor = window.ace.edit('editarea'); this.session = this.editor.session; this._start;

this.ready = true; LOADING.resolve(this); mw.hook('dev.codeeditor.session').fire(this); };

/**    * Edit session start handler. * Conditional extraction of escape sequences & malformed text. * @method             _start * @private */   CodeEditor.prototype._start = function { window.showPagePreview = this._preview;

var rgx = /^[^\n]*<(nowiki|pre|syntaxhighlight)[^\n]*/, v = this.session.getValue;

if (rgx.test(v)) { this._prefix = v.match(rgx)[0]; v = v.replace(rgx, '').trim; this.session.setValue(v); }       if (window.CKEDITOR && v.trim === '') { this.session.setValue(''); }   };

/**    * Edit session exit handler. * Performs error checking * @method             _exit * @todo               Add linting. * @private */   CodeEditor.prototype._exit = function(e) { var $a = (this.session.$annotations || []); var err = $a.filter(this._annotationIsError); if (!this.ready || err.length !== 0) { if (err.length !== 0) { window.alert('[CodeEditor] ' + err[0].text); }           return; }       e.preventDefault; e.stopPropagation;

if (this._prefix) { var v = this.session.getValue; v = this._prefix + '\n' + v.trim; this.session.setValue(v); delete this._prefix; }

$('#wpSave').off('.dev_codeeditor').click; };

/**    * Array option mapping & validation utility. * @method             _toArray * @private */   CodeEditor.prototype._toArray = function(opt) { opt = $.isArray(opt) ? opt : [opt]; opt = opt.filter(Boolean).map(String); return opt; };

/**    * Equality conditional function generator. * @method             _equals * @private */   CodeEditor.prototype._equals = function(eq) { return function(str) { return str === eq; }; };

/**    * Ace annotation error filter. * @method             _annotationIsError * @private */   CodeEditor.prototype._annotationIsError = function(a) { return a.type === 'error'; };

// Script bootloader. mw.hook('dev.codeeditor.modelist').add(require.bind( window, ['dev.codeeditor.modelist'], codeEditorInit ));   importArticle({        type: 'script',        article: 'u:dev:MediaWiki:CodeEditor/modelist.js'    });

return window.dev.codeEditor; });