MediaWiki:Common.js:修订间差异
跳转到导航
跳转到搜索
Maintenance script(留言 | 贡献) 缩放留白收尾 |
颜色/字号改用 span 内联样式(修:模板导致可视化编辑器无法加链接) |
||
| (未显示同一用户的18个中间版本) | |||
| 第1行: | 第1行: | ||
/* | /* | ||
* 尘月维基 · 排版工具条 | |||
* 在源代码编辑器(WikiEditor)上增加:颜色 / 字号 / 对齐 / 插图 / 调图 | |||
* 依赖:ext.wikiEditor、oojs-ui-widgets、oojs-ui-windows | |||
*/ | |||
( function () { | ( function () { | ||
'use strict'; | |||
function | var COLORS = [ | ||
var | { label: '红', value: '#d32f2f' }, | ||
var | { label: '橙', value: '#ef6c00' }, | ||
if ( | { label: '黄', value: '#f9a825' }, | ||
{ label: '绿', value: '#2e7d32' }, | |||
} | { label: '青', value: '#00838f' }, | ||
{ label: '蓝', value: '#1565c0' }, | |||
{ label: '紫', value: '#6b3fa0' }, | |||
{ label: '粉', value: '#d81b60' }, | |||
{ label: '灰', value: '#666666' }, | |||
{ label: '黑', value: '#000000' } | |||
]; | |||
var SIZES = [ '80%', '100%', '120%', '150%', '200%', '250%' ]; | |||
var WIDTHS = [ '', '100', '150', '200', '250', '300', '400', '500', '600' ]; | |||
var ALIGN_OPTIONS = [ | |||
{ label: '默认', value: '' }, | |||
{ label: '居中', value: 'center' }, | |||
{ label: '靠左', value: 'left' }, | |||
{ label: '靠右', value: 'right' } | |||
]; | |||
var ALIGN_MAP = { '居中': 'center', '居左': 'left', '居右': 'right', '无': '' }; | |||
var FILE_RE = /^\s*\[\[\s*(?:File|Image|文件|图像)\s*:\s*([^|\]]+)((?:\|[^[\]]*)*)\s*\]\]\s*$/i; | |||
var ALIGN_RE = /^(?:center|left|right|none|居中|居左|居右|无)$/i; | |||
var DialogClass = null; | |||
var windowManager = null; | |||
/** | |||
* 生成"包裹选中文字"的动作,套用站内模板 | |||
* | |||
* 只用于「对齐」这类块级排版;颜色和字号改用内联样式(见 spanAction)。 | |||
* | |||
* @param {string} template 模板名 | |||
* @param {string} arg 第一个参数 | |||
* @return {Object} WikiEditor action | |||
*/ | |||
function wrapAction( template, arg ) { | |||
return { | |||
type: 'encapsulate', | |||
options: { | |||
pre: '{{' + template + '|1=' + arg + '|2=', | |||
peri: '', | |||
post: '}}' | |||
} | |||
}; | |||
} | |||
/** | |||
* 颜色 / 字号用内联样式包裹,而不是模板 | |||
* | |||
* 模板在可视化编辑器里是不可分割的块,里面的文字没法再选中加链接; | |||
* span + style 则仍然是普通文字,两个编辑器都能继续编辑。 | |||
* | |||
* @param {string} prop CSS 属性(color / font-size) | |||
* @param {string} value 值 | |||
* @return {Object} WikiEditor action | |||
*/ | |||
function spanAction( prop, value ) { | |||
return { | |||
type: 'encapsulate', | |||
options: { | |||
pre: '<span style="' + prop + ':' + value + '">', | |||
peri: '', | |||
post: '</span>' | |||
} | |||
}; | |||
} | |||
/** | |||
* 把 [{label, value}] 转成 WikiEditor select 需要的 list | |||
* | |||
* @param {Object[]} items | |||
* @param {Function} actionFactory 由 value 生成 action | |||
* @return {Object} | |||
*/ | |||
function makeList( items, actionFactory ) { | |||
var list = {}; | |||
items.forEach( function ( item, index ) { | |||
list[ 'opt' + index ] = { | |||
label: item.label, | |||
action: actionFactory( item.value ) | |||
}; | |||
} ); | |||
return list; | |||
} | |||
/** | |||
* 从 [[File:...]] 代码里解析文件名、宽度、位置、说明 | |||
* | |||
* @param {string} text | |||
* @return {Object|null} | |||
*/ | |||
function parseFileLink( text ) { | |||
var match = FILE_RE.exec( text || '' ); | |||
if ( !match ) { | |||
return null; | |||
} | |||
var parsed = { name: match[ 1 ].trim(), width: '', align: '', caption: '' }; | |||
var params = match[ 2 ] ? match[ 2 ].split( '|' ).slice( 1 ) : []; | |||
params.forEach( function ( raw ) { | |||
var param = raw.trim(); | |||
if ( !param ) { | |||
return; | |||
} | |||
if ( /^\d+\s*px$/i.test( param ) ) { | |||
parsed.width = String( parseInt( param, 10 ) ); | |||
} else if ( ALIGN_RE.test( param ) ) { | |||
parsed.align = Object.prototype.hasOwnProperty.call( ALIGN_MAP, param ) ? | |||
ALIGN_MAP[ param ] : param.toLowerCase(); | |||
} else if ( !parsed.caption ) { | |||
parsed.caption = param; | |||
} | |||
} ); | |||
return parsed; | |||
} | |||
/** | |||
* 拼出 [[File:名字|宽度px|位置|说明]] | |||
* | |||
* @param {Object} data | |||
* @return {string} | |||
*/ | |||
function buildFileLink( data ) { | |||
var parts = [ '[[File:' + data.name ]; | |||
if ( data.width ) { | |||
parts.push( data.width + 'px' ); | |||
} | |||
if ( data.align ) { | |||
parts.push( data.align ); | |||
} | |||
if ( data.caption ) { | |||
parts.push( data.caption ); | |||
} | |||
return parts.join( '|' ) + ']]'; | |||
} | |||
/** | |||
* 上传页地址 | |||
* | |||
* @return {string} | |||
*/ | |||
function uploadUrl() { | |||
if ( mw.util && typeof mw.util.getUrl === 'function' ) { | |||
return mw.util.getUrl( 'Special:Upload' ); | |||
} | } | ||
return '/Special:Upload'; | |||
} | } | ||
/** | |||
* 提示条 | |||
* | |||
* @param {string} text | |||
*/ | |||
function notify( text ) { | |||
mw.loader.using( 'mediawiki.notification' ).then( function () { | |||
mw.notify( text, { type: 'warn', autoHideSeconds: 5 } ); | |||
} ); | |||
} | } | ||
/** | |||
* 图片对话框(插入 / 调整共用一个) | |||
* | |||
* @return {Function} 对话框类 | |||
*/ | |||
function getDialogClass() { | |||
if ( DialogClass ) { | |||
return DialogClass; | |||
} | |||
/** | |||
* @param {Object} config | |||
*/ | |||
function ImageDialog( config ) { | |||
ImageDialog.super.call( this, config ); | |||
} | |||
OO.inheritClass( ImageDialog, OO.ui.ProcessDialog ); | |||
ImageDialog.static.name = 'chenyue-image'; | |||
ImageDialog.static.size = 'medium'; | |||
ImageDialog.static.title = '图片'; | |||
ImageDialog.static.actions = [ | |||
{ action: 'apply', label: '确定', flags: [ 'primary', 'progressive' ] }, | |||
{ action: 'cancel', label: '取消', flags: 'safe' } | |||
]; | |||
/** | |||
* @param {Object} context | |||
* @param {Object} data | |||
* @param {boolean} isNew | |||
* @return {ImageDialog} | |||
*/ | |||
ImageDialog.prototype.setData = function ( context, data, isNew ) { | |||
this.context = context; | |||
this.data = data; | |||
this.isNew = isNew; | |||
return this; | |||
}; | |||
ImageDialog.prototype.initialize = function () { | |||
ImageDialog.super.prototype.initialize.apply( this, arguments ); | |||
var data = this.data; | |||
this.title.setLabel( this.isNew ? '插入图片' : '调整图片' ); | |||
this.nameInput = new OO.ui.TextInputWidget( { | |||
value: data.name, | |||
placeholder: '图片文件名,例如 熊猫.png', | |||
icon: 'image' | |||
} ); | |||
this.widthInput = new OO.ui.DropdownInputWidget( { | |||
options: WIDTHS.map( function ( width ) { | |||
return { data: width, label: width ? width + ' 像素宽' : '按原始大小' }; | |||
} ), | |||
value: data.width || '' | |||
} ); | |||
this.alignInput = new OO.ui.DropdownInputWidget( { | |||
options: ALIGN_OPTIONS.map( function ( option ) { | |||
return { data: option.value, label: option.label }; | |||
} ), | |||
value: data.align || '' | |||
} ); | |||
this.captionInput = new OO.ui.TextInputWidget( { | |||
value: data.caption, | |||
placeholder: '图片说明,可留空' | |||
} ); | |||
var fieldset = new OO.ui.FieldsetLayout(); | |||
fieldset.addItems( [ | |||
new OO.ui.FieldLayout( this.nameInput, { label: '文件名', align: 'top' } ), | |||
new OO.ui.FieldLayout( this.widthInput, { label: '大小', align: 'top' } ), | |||
new OO.ui.FieldLayout( this.alignInput, { label: '位置', align: 'top' } ), | |||
new OO.ui.FieldLayout( this.captionInput, { label: '说明', align: 'top' } ) | |||
] ); | |||
var hint = new OO.ui.HtmlSnippet( | |||
'<p class="chenyue-img-hint">还没上传?' + | |||
'<a href="' + uploadUrl() + '" target="_blank" rel="noopener">去上传图片 →</a></p>' | |||
); | |||
var panel = new OO.ui.PanelLayout( { padded: true, expanded: false } ); | |||
panel.$element.append( fieldset.$element, hint.$element ); | |||
this.$body.append( panel.$element ); | |||
}; | |||
ImageDialog.prototype.getBodyHeight = function () { | |||
return 380; | |||
}; | |||
ImageDialog.prototype.getActionProcess = function ( action ) { | |||
var dialog = this; | |||
if ( action !== 'apply' ) { | |||
return new OO.ui.Process( function () { | |||
dialog.close( { action: action || 'cancel' } ); | |||
} ); | |||
} | |||
return new OO.ui.Process( function () { | |||
var name = dialog.nameInput.getValue().trim() | |||
.replace( /^(?:File|Image|文件|图像)\s*:\s*/i, '' ); | |||
if ( !name ) { | |||
dialog.nameInput.setValidityFlag( false ); | |||
dialog.nameInput.focus(); | |||
return; | |||
} | |||
var text = buildFileLink( { | |||
name: name, | |||
width: dialog.widthInput.getValue(), | |||
align: dialog.alignInput.getValue(), | |||
caption: dialog.captionInput.getValue().trim() | |||
} ); | |||
// textSelection 没有 insertText 方法,统一用 encapsulateSelection: | |||
// 有选区就替换,没选区就插在光标处 | |||
dialog.context.$textarea.textSelection( 'encapsulateSelection', { | |||
pre: text, | |||
peri: '', | |||
post: '', | |||
replace: true, | |||
splitlines: false, | |||
ownline: false | |||
} ); | |||
dialog.close( { action: 'apply' } ); | |||
} ); | |||
}; | |||
DialogClass = ImageDialog; | |||
return DialogClass; | |||
} | |||
/** | |||
* 打开图片对话框 | |||
* | |||
* @param {Object} context WikiEditor context | |||
* @param {Object|null} parsed 已有图片信息(调整时传入) | |||
*/ | |||
function openImageDialog( context, parsed ) { | |||
var isNew = !parsed; | |||
mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows' ] ).then( function () { | |||
if ( !windowManager ) { | |||
windowManager = new OO.ui.WindowManager(); | |||
$( document.body ).append( windowManager.$element ); | |||
} | |||
var ImageDialog = getDialogClass(); | |||
var dialog = new ImageDialog(); | |||
dialog.setData( context, parsed || { name: '', width: '', align: '', caption: '' }, isNew ); | |||
windowManager.addWindows( [ dialog ] ); | |||
// openWindow 返回的 promise 在窗口关闭时才 resolve,用它来销毁实例 | |||
var opened = windowManager.openWindow( dialog, {} ); | |||
if ( opened && typeof opened.then === 'function' ) { | |||
opened.then( cleanup, cleanup ); | |||
} else if ( dialog.closed && typeof dialog.closed.then === 'function' ) { | |||
dialog.closed.then( cleanup, cleanup ); | |||
} | |||
/** | |||
* 关闭后从 DOM 中移除,避免旧窗口残留 | |||
*/ | |||
function cleanup() { | |||
windowManager.removeWindows( [ dialog ] ); | |||
if ( dialog.$element && dialog.$element.parent() ) { | |||
dialog.$element.remove(); | |||
} | |||
} | |||
} ); | |||
} | |||
/** | |||
* 调整选中图片的大小 / 位置 | |||
* | |||
* @param {Object} context | |||
*/ | |||
function adjustImage( context ) { | |||
var selection = context.$textarea.textSelection( 'getSelection' ); | |||
var parsed = parseFileLink( selection ); | |||
if ( !parsed ) { | |||
notify( '先选中整段图片代码再点「调图」,例如 [[File:熊猫.png|300px|center]]' ); | |||
return; | |||
} | |||
openImageDialog( context, parsed ); | |||
} | |||
/** | |||
* 把排版工具条插进 WikiEditor | |||
*/ | |||
function addLayoutTools() { | |||
if ( $( '#wikiEditor-ui-toolbar .group-layout' ).length ) { | |||
return; | |||
} | |||
$( '#wpTextbox1' ).wikiEditor( 'addToToolbar', { | |||
section: 'main', | |||
groups: { | |||
layout: { | |||
tools: { | |||
layoutColor: { | |||
type: 'select', | |||
label: '颜色', | |||
list: makeList( COLORS, function ( value ) { | |||
return spanAction( 'color', value ); | |||
} ) | |||
}, | |||
layoutSize: { | |||
type: 'select', | |||
label: '字号', | |||
list: makeList( SIZES.map( function ( size ) { | |||
return { label: size, value: size }; | |||
} ), function ( value ) { | |||
return spanAction( 'font-size', value ); | |||
} ) | |||
}, | |||
layoutAlign: { | |||
type: 'select', | |||
label: '对齐', | |||
list: makeList( ALIGN_OPTIONS.filter( function ( option ) { | |||
return option.value !== ''; | |||
} ), function ( value ) { | |||
return wrapAction( '对齐', value ); | |||
} ) | |||
}, | |||
layoutImage: { | |||
type: 'button', | |||
label: '插图', | |||
action: { | |||
type: 'callback', | |||
execute: function ( context ) { | |||
openImageDialog( context, null ); | |||
} | |||
} | |||
}, | |||
layoutAdjust: { | |||
type: 'button', | |||
label: '调图', | |||
action: { | |||
type: 'callback', | |||
execute: function ( context ) { | |||
adjustImage( context ); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} ); | |||
} | |||
/** | |||
* 等 WikiEditor 渲染出工具条后再挂工具组 | |||
*/ | |||
function bootstrap() { | |||
if ( !document.getElementById( 'wpTextbox1' ) ) { | |||
return; | |||
} | |||
mw.loader.using( 'ext.wikiEditor' ).then( function () { | |||
var tries = 0; | |||
function waitForToolbar() { | |||
if ( document.getElementById( 'wikiEditor-ui-toolbar' ) ) { | |||
addLayoutTools(); | |||
return; | |||
} | |||
tries += 1; | |||
if ( tries <= 30 ) { | |||
window.setTimeout( waitForToolbar, 100 ); | |||
} | |||
} | |||
waitForToolbar(); | |||
} ); | |||
} | |||
$( bootstrap ); | |||
}() ); | }() ); | ||
2026年10月31日 (六) 18:39的最新版本
/*
* 尘月维基 · 排版工具条
* 在源代码编辑器(WikiEditor)上增加:颜色 / 字号 / 对齐 / 插图 / 调图
* 依赖:ext.wikiEditor、oojs-ui-widgets、oojs-ui-windows
*/
( function () {
'use strict';
var COLORS = [
{ label: '红', value: '#d32f2f' },
{ label: '橙', value: '#ef6c00' },
{ label: '黄', value: '#f9a825' },
{ label: '绿', value: '#2e7d32' },
{ label: '青', value: '#00838f' },
{ label: '蓝', value: '#1565c0' },
{ label: '紫', value: '#6b3fa0' },
{ label: '粉', value: '#d81b60' },
{ label: '灰', value: '#666666' },
{ label: '黑', value: '#000000' }
];
var SIZES = [ '80%', '100%', '120%', '150%', '200%', '250%' ];
var WIDTHS = [ '', '100', '150', '200', '250', '300', '400', '500', '600' ];
var ALIGN_OPTIONS = [
{ label: '默认', value: '' },
{ label: '居中', value: 'center' },
{ label: '靠左', value: 'left' },
{ label: '靠右', value: 'right' }
];
var ALIGN_MAP = { '居中': 'center', '居左': 'left', '居右': 'right', '无': '' };
var FILE_RE = /^\s*\[\[\s*(?:File|Image|文件|图像)\s*:\s*([^|\]]+)((?:\|[^[\]]*)*)\s*\]\]\s*$/i;
var ALIGN_RE = /^(?:center|left|right|none|居中|居左|居右|无)$/i;
var DialogClass = null;
var windowManager = null;
/**
* 生成"包裹选中文字"的动作,套用站内模板
*
* 只用于「对齐」这类块级排版;颜色和字号改用内联样式(见 spanAction)。
*
* @param {string} template 模板名
* @param {string} arg 第一个参数
* @return {Object} WikiEditor action
*/
function wrapAction( template, arg ) {
return {
type: 'encapsulate',
options: {
pre: '{{' + template + '|1=' + arg + '|2=',
peri: '',
post: '}}'
}
};
}
/**
* 颜色 / 字号用内联样式包裹,而不是模板
*
* 模板在可视化编辑器里是不可分割的块,里面的文字没法再选中加链接;
* span + style 则仍然是普通文字,两个编辑器都能继续编辑。
*
* @param {string} prop CSS 属性(color / font-size)
* @param {string} value 值
* @return {Object} WikiEditor action
*/
function spanAction( prop, value ) {
return {
type: 'encapsulate',
options: {
pre: '<span style="' + prop + ':' + value + '">',
peri: '',
post: '</span>'
}
};
}
/**
* 把 [{label, value}] 转成 WikiEditor select 需要的 list
*
* @param {Object[]} items
* @param {Function} actionFactory 由 value 生成 action
* @return {Object}
*/
function makeList( items, actionFactory ) {
var list = {};
items.forEach( function ( item, index ) {
list[ 'opt' + index ] = {
label: item.label,
action: actionFactory( item.value )
};
} );
return list;
}
/**
* 从 [[File:...]] 代码里解析文件名、宽度、位置、说明
*
* @param {string} text
* @return {Object|null}
*/
function parseFileLink( text ) {
var match = FILE_RE.exec( text || '' );
if ( !match ) {
return null;
}
var parsed = { name: match[ 1 ].trim(), width: '', align: '', caption: '' };
var params = match[ 2 ] ? match[ 2 ].split( '|' ).slice( 1 ) : [];
params.forEach( function ( raw ) {
var param = raw.trim();
if ( !param ) {
return;
}
if ( /^\d+\s*px$/i.test( param ) ) {
parsed.width = String( parseInt( param, 10 ) );
} else if ( ALIGN_RE.test( param ) ) {
parsed.align = Object.prototype.hasOwnProperty.call( ALIGN_MAP, param ) ?
ALIGN_MAP[ param ] : param.toLowerCase();
} else if ( !parsed.caption ) {
parsed.caption = param;
}
} );
return parsed;
}
/**
* 拼出 [[File:名字|宽度px|位置|说明]]
*
* @param {Object} data
* @return {string}
*/
function buildFileLink( data ) {
var parts = [ '[[File:' + data.name ];
if ( data.width ) {
parts.push( data.width + 'px' );
}
if ( data.align ) {
parts.push( data.align );
}
if ( data.caption ) {
parts.push( data.caption );
}
return parts.join( '|' ) + ']]';
}
/**
* 上传页地址
*
* @return {string}
*/
function uploadUrl() {
if ( mw.util && typeof mw.util.getUrl === 'function' ) {
return mw.util.getUrl( 'Special:Upload' );
}
return '/Special:Upload';
}
/**
* 提示条
*
* @param {string} text
*/
function notify( text ) {
mw.loader.using( 'mediawiki.notification' ).then( function () {
mw.notify( text, { type: 'warn', autoHideSeconds: 5 } );
} );
}
/**
* 图片对话框(插入 / 调整共用一个)
*
* @return {Function} 对话框类
*/
function getDialogClass() {
if ( DialogClass ) {
return DialogClass;
}
/**
* @param {Object} config
*/
function ImageDialog( config ) {
ImageDialog.super.call( this, config );
}
OO.inheritClass( ImageDialog, OO.ui.ProcessDialog );
ImageDialog.static.name = 'chenyue-image';
ImageDialog.static.size = 'medium';
ImageDialog.static.title = '图片';
ImageDialog.static.actions = [
{ action: 'apply', label: '确定', flags: [ 'primary', 'progressive' ] },
{ action: 'cancel', label: '取消', flags: 'safe' }
];
/**
* @param {Object} context
* @param {Object} data
* @param {boolean} isNew
* @return {ImageDialog}
*/
ImageDialog.prototype.setData = function ( context, data, isNew ) {
this.context = context;
this.data = data;
this.isNew = isNew;
return this;
};
ImageDialog.prototype.initialize = function () {
ImageDialog.super.prototype.initialize.apply( this, arguments );
var data = this.data;
this.title.setLabel( this.isNew ? '插入图片' : '调整图片' );
this.nameInput = new OO.ui.TextInputWidget( {
value: data.name,
placeholder: '图片文件名,例如 熊猫.png',
icon: 'image'
} );
this.widthInput = new OO.ui.DropdownInputWidget( {
options: WIDTHS.map( function ( width ) {
return { data: width, label: width ? width + ' 像素宽' : '按原始大小' };
} ),
value: data.width || ''
} );
this.alignInput = new OO.ui.DropdownInputWidget( {
options: ALIGN_OPTIONS.map( function ( option ) {
return { data: option.value, label: option.label };
} ),
value: data.align || ''
} );
this.captionInput = new OO.ui.TextInputWidget( {
value: data.caption,
placeholder: '图片说明,可留空'
} );
var fieldset = new OO.ui.FieldsetLayout();
fieldset.addItems( [
new OO.ui.FieldLayout( this.nameInput, { label: '文件名', align: 'top' } ),
new OO.ui.FieldLayout( this.widthInput, { label: '大小', align: 'top' } ),
new OO.ui.FieldLayout( this.alignInput, { label: '位置', align: 'top' } ),
new OO.ui.FieldLayout( this.captionInput, { label: '说明', align: 'top' } )
] );
var hint = new OO.ui.HtmlSnippet(
'<p class="chenyue-img-hint">还没上传?' +
'<a href="' + uploadUrl() + '" target="_blank" rel="noopener">去上传图片 →</a></p>'
);
var panel = new OO.ui.PanelLayout( { padded: true, expanded: false } );
panel.$element.append( fieldset.$element, hint.$element );
this.$body.append( panel.$element );
};
ImageDialog.prototype.getBodyHeight = function () {
return 380;
};
ImageDialog.prototype.getActionProcess = function ( action ) {
var dialog = this;
if ( action !== 'apply' ) {
return new OO.ui.Process( function () {
dialog.close( { action: action || 'cancel' } );
} );
}
return new OO.ui.Process( function () {
var name = dialog.nameInput.getValue().trim()
.replace( /^(?:File|Image|文件|图像)\s*:\s*/i, '' );
if ( !name ) {
dialog.nameInput.setValidityFlag( false );
dialog.nameInput.focus();
return;
}
var text = buildFileLink( {
name: name,
width: dialog.widthInput.getValue(),
align: dialog.alignInput.getValue(),
caption: dialog.captionInput.getValue().trim()
} );
// textSelection 没有 insertText 方法,统一用 encapsulateSelection:
// 有选区就替换,没选区就插在光标处
dialog.context.$textarea.textSelection( 'encapsulateSelection', {
pre: text,
peri: '',
post: '',
replace: true,
splitlines: false,
ownline: false
} );
dialog.close( { action: 'apply' } );
} );
};
DialogClass = ImageDialog;
return DialogClass;
}
/**
* 打开图片对话框
*
* @param {Object} context WikiEditor context
* @param {Object|null} parsed 已有图片信息(调整时传入)
*/
function openImageDialog( context, parsed ) {
var isNew = !parsed;
mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows' ] ).then( function () {
if ( !windowManager ) {
windowManager = new OO.ui.WindowManager();
$( document.body ).append( windowManager.$element );
}
var ImageDialog = getDialogClass();
var dialog = new ImageDialog();
dialog.setData( context, parsed || { name: '', width: '', align: '', caption: '' }, isNew );
windowManager.addWindows( [ dialog ] );
// openWindow 返回的 promise 在窗口关闭时才 resolve,用它来销毁实例
var opened = windowManager.openWindow( dialog, {} );
if ( opened && typeof opened.then === 'function' ) {
opened.then( cleanup, cleanup );
} else if ( dialog.closed && typeof dialog.closed.then === 'function' ) {
dialog.closed.then( cleanup, cleanup );
}
/**
* 关闭后从 DOM 中移除,避免旧窗口残留
*/
function cleanup() {
windowManager.removeWindows( [ dialog ] );
if ( dialog.$element && dialog.$element.parent() ) {
dialog.$element.remove();
}
}
} );
}
/**
* 调整选中图片的大小 / 位置
*
* @param {Object} context
*/
function adjustImage( context ) {
var selection = context.$textarea.textSelection( 'getSelection' );
var parsed = parseFileLink( selection );
if ( !parsed ) {
notify( '先选中整段图片代码再点「调图」,例如 [[File:熊猫.png|300px|center]]' );
return;
}
openImageDialog( context, parsed );
}
/**
* 把排版工具条插进 WikiEditor
*/
function addLayoutTools() {
if ( $( '#wikiEditor-ui-toolbar .group-layout' ).length ) {
return;
}
$( '#wpTextbox1' ).wikiEditor( 'addToToolbar', {
section: 'main',
groups: {
layout: {
tools: {
layoutColor: {
type: 'select',
label: '颜色',
list: makeList( COLORS, function ( value ) {
return spanAction( 'color', value );
} )
},
layoutSize: {
type: 'select',
label: '字号',
list: makeList( SIZES.map( function ( size ) {
return { label: size, value: size };
} ), function ( value ) {
return spanAction( 'font-size', value );
} )
},
layoutAlign: {
type: 'select',
label: '对齐',
list: makeList( ALIGN_OPTIONS.filter( function ( option ) {
return option.value !== '';
} ), function ( value ) {
return wrapAction( '对齐', value );
} )
},
layoutImage: {
type: 'button',
label: '插图',
action: {
type: 'callback',
execute: function ( context ) {
openImageDialog( context, null );
}
}
},
layoutAdjust: {
type: 'button',
label: '调图',
action: {
type: 'callback',
execute: function ( context ) {
adjustImage( context );
}
}
}
}
}
}
} );
}
/**
* 等 WikiEditor 渲染出工具条后再挂工具组
*/
function bootstrap() {
if ( !document.getElementById( 'wpTextbox1' ) ) {
return;
}
mw.loader.using( 'ext.wikiEditor' ).then( function () {
var tries = 0;
function waitForToolbar() {
if ( document.getElementById( 'wikiEditor-ui-toolbar' ) ) {
addLayoutTools();
return;
}
tries += 1;
if ( tries <= 30 ) {
window.setTimeout( waitForToolbar, 100 );
}
}
waitForToolbar();
} );
}
$( bootstrap );
}() );