* mw.language.specialCharacters, deprecated in 1.33, has been removed.
Use require( 'mediawiki.language.specialCharacters' ) instead.
* The jquery.colorUtil module was removed. Use jquery.color instead.
+* The jquery.checkboxShiftClick module was removed. The functionality
+ is provided by mediawiki.page.ready instead (T232688).
* EditPage::submit(), deprecated in 1.29, has been removed. Use $this->edit()
directly.
* HTMLForm::getErrors(), deprecated in 1.28, has been removed. Use
* Get a Title object associated with the talk page of this article
*
* @deprecated since 1.34, use getTalkPageIfDefined() or NamespaceInfo::getTalkPage()
- * with NamespaceInfo::canHaveTalkPage().
+ * with NamespaceInfo::canHaveTalkPage(). Note that the new method will
+ * throw if asked for the talk page of a section-only link, or of an interwiki
+ * link.
* @return Title The object for the talk page
* @throws MWException if $target doesn't have talk pages, e.g. because it's in NS_SPECIAL
* or because it's a relative link, or an interwiki link.
*/
public function getTalkPage() {
- return self::castFromLinkTarget(
- MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkPage( $this ) );
+ // NOTE: The equivalent code in NamespaceInfo is less lenient about producing invalid titles.
+ // Instead of failing on invalid titles, let's just log the issue for now.
+ // See the discussion on T227817.
+
+ // Is this the same title?
+ $talkNS = MediaWikiServices::getInstance()->getNamespaceInfo()->getTalk( $this->mNamespace );
+ if ( $this->mNamespace == $talkNS ) {
+ return $this;
+ }
+
+ $title = self::makeTitle( $talkNS, $this->mDbkeyform );
+
+ $this->warnIfPageCannotExist( $title, __METHOD__ );
+
+ return $title;
+ // TODO: replace the above with the code below:
+ // return self::castFromLinkTarget(
+ // MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkPage( $this ) );
}
/**
* @return Title The object for the subject page
*/
public function getSubjectPage() {
- return self::castFromLinkTarget(
- MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectPage( $this ) );
+ // Is this the same title?
+ $subjectNS = MediaWikiServices::getInstance()->getNamespaceInfo()
+ ->getSubject( $this->mNamespace );
+ if ( $this->mNamespace == $subjectNS ) {
+ return $this;
+ }
+ // NOTE: The equivalent code in NamespaceInfo is less lenient about producing invalid titles.
+ // Instead of failing on invalid titles, let's just log the issue for now.
+ // See the discussion on T227817.
+ $title = self::makeTitle( $subjectNS, $this->mDbkeyform );
+
+ $this->warnIfPageCannotExist( $title, __METHOD__ );
+
+ return $title;
+ // TODO: replace the above with the code below:
+ // return self::castFromLinkTarget(
+ // MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectPage( $this ) );
+ }
+
+ /**
+ * @param Title $title
+ * @param string $method
+ *
+ * @return bool whether a warning was issued
+ */
+ private function warnIfPageCannotExist( Title $title, $method ) {
+ if ( $this->getText() == '' ) {
+ wfLogWarning(
+ $method . ': called on empty title ' . $this->getFullText() . ', returning '
+ . $title->getFullText()
+ );
+
+ return true;
+ }
+
+ if ( $this->getInterwiki() !== '' ) {
+ wfLogWarning(
+ $method . ': called on interwiki title ' . $this->getFullText() . ', returning '
+ . $title->getFullText()
+ );
+
+ return true;
+ }
+
+ return false;
}
/**
* @return Title
*/
public function getOtherPage() {
- return self::castFromLinkTarget(
- MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociatedPage( $this ) );
+ // NOTE: Depend on the methods in this class instead of their equivalent in NamespaceInfo,
+ // until their semantics has become exactly the same.
+ // See the discussion on T227817.
+ if ( $this->isSpecialPage() ) {
+ throw new MWException( 'Special pages cannot have other pages' );
+ }
+ if ( $this->isTalkPage() ) {
+ return $this->getSubjectPage();
+ } else {
+ if ( !$this->canHaveTalkPage() ) {
+ throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
+ }
+ return $this->getTalkPage();
+ }
+ // TODO: replace the above with the code below:
+ // return self::castFromLinkTarget(
+ // MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociatedPage( $this ) );
}
/**
}
protected function appendNamespaces( $property ) {
+ $nsProtection = $this->getConfig()->get( 'NamespaceProtection' );
+
$data = [
ApiResult::META_TYPE => 'assoc',
];
$data[$ns]['content'] = $nsInfo->isContent( $ns );
$data[$ns]['nonincludable'] = $nsInfo->isNonincludable( $ns );
+ if ( isset( $nsProtection[$ns] ) ) {
+ if ( is_array( $nsProtection[$ns] ) ) {
+ $specificNs = implode( "|", array_filter( $nsProtection[$ns] ) );
+ } elseif ( $nsProtection[$ns] !== '' ) {
+ $specificNs = $nsProtection[$ns];
+ }
+ if ( isset( $specificNs ) && $specificNs !== '' ) {
+ $data[$ns]['namespaceprotection'] = $specificNs;
+ }
+ }
+
$contentmodel = $nsInfo->getNamespaceContentModel( $ns );
if ( $contentmodel ) {
$data[$ns]['defaultcontentmodel'] = $contentmodel;
'𫄨' => '絺',
'𫄷' => '繶',
'𫄸' => '纁',
-'ð«\87' => 'è\94¿',
+'ð«\87' => 'è\92\8d',
'𫌀' => '襀',
'𫌨' => '覼',
'𫍙' => '訑',
'于少保' => '于少保',
'于山国' => '于山國',
'于山國' => '于山國',
+'于山岛' => '于山島',
+'于山島' => '于山島',
'于帅' => '于帥',
'于帥' => '于帥',
'于幼军' => '于幼軍',
'于志寧' => '于志寧',
'于忠肃集' => '于忠肅集',
'于忠肅集' => '于忠肅集',
-'于思' => '于思',
'于慎行' => '于慎行',
'于慧' => '于慧',
'于成龍' => '于成龍',
'于都县' => '于都縣',
'于都縣' => '于都縣',
'于里察' => '于里察',
+'于闐' => '于闐',
'于阗' => '于闐',
'于双戈' => '于雙戈',
'于雙戈' => '于雙戈',
'党進' => '党進',
'党項' => '党項',
'党项' => '党項',
+'入侵并' => '入侵並',
'内脏' => '內臟',
'内制' => '內製',
'内面包' => '內面包',
'叮叮当当' => '叮叮噹噹',
'叮当' => '叮噹',
'可紧可松' => '可緊可鬆',
+'可能干預' => '可能干預',
+'可能干预' => '可能干預',
'可自制' => '可自制',
'可鉴' => '可鑑',
'台子女' => '台子女',
'大型钟面' => '大型鐘面',
'大多只' => '大多只',
'大伙' => '大夥',
-'大干' => '大幹',
+'大干一' => '大幹一',
'大批涌到' => '大批湧到',
'大折儿' => '大摺兒',
'大明历' => '大明曆',
'恶直丑正' => '惡直醜正',
'恶斗' => '惡鬥',
'惴栗' => '惴慄',
+'意大利面临' => '意大利面臨',
+'意大利面臨' => '意大利面臨',
'意大利面' => '意大利麵',
'愛河里花子' => '愛河里花子',
'爱河里花子' => '愛河里花子',
'方向' => '方向',
'方法里' => '方法裡',
'于吉林' => '於吉林',
+'于格林' => '於格林',
+'于越南' => '於越南',
'于震中' => '於震中',
'于震前' => '於震前',
'于震后' => '於震後',
'浅淀' => '淺澱',
'清心寡欲' => '清心寡欲',
'渠冲' => '渠衝',
+'温岚' => '温嵐',
+'温嵐' => '温嵐',
'测不准' => '測不準',
'港制' => '港製',
'游离' => '游離',
'版本里' => '版本裡',
'牙签' => '牙籤',
'牛只' => '牛隻',
+'牢里' => '牢裡',
'物欲' => '物慾',
'抵牾' => '牴牾',
'抵触' => '牴觸',
'称赞' => '稱讚',
'稻谷' => '稻穀',
'稽征' => '稽徵',
-'谷人' => '穀人',
'谷保家商' => '穀保家商',
'谷仓' => '穀倉',
'谷圭' => '穀圭',
'药签' => '藥籤',
'药面儿' => '藥麵兒',
'苏昆' => '蘇崑',
+'𬞟' => '蘋',
'苹婆' => '蘋婆',
'苹果' => '蘋果',
'苹果干' => '蘋果乾',
'袋表' => '袋錶',
'袖里' => '袖裡',
'被废后' => '被廢後',
+'被卷回' => '被捲回',
'被系上' => '被繫上',
'被里' => '被裡',
'被夸' => '被誇',
'鹰雕' => '鹰鵰',
'鹰鵰' => '鹰鵰',
'咸、甜' => '鹹、甜',
+'咸吃' => '鹹吃',
'咸味' => '鹹味',
'咸嘴淡舌' => '鹹嘴淡舌',
'咸土' => '鹹土',
'葦' => '苇',
'葯' => '药',
'葷' => '荤',
+'蒍' => '𫇭',
'蒓' => '莼',
'蒔' => '莳',
'蒞' => '莅',
'孫乾' => '孙乾',
'宏碁' => '宏碁',
'官陞' => '官升',
+'尋陞' => '寻升',
'將軍抽俥' => '将军抽俥',
'將軍抽車' => '将军抽車',
'爾冬陞' => '尔冬升',
'克罗地亚' => '克羅埃西亞',
'克羅地亞' => '克羅埃西亞',
'克里斯托弗' => '克里斯多福',
+'全角' => '全形',
'万维网' => '全球資訊網',
+'全角度' => '全角度',
+'全角色' => '全角色',
'八杆' => '八桿',
'公共交通' => '公共運輸',
'六杆' => '六桿',
'塞维利亚' => '塞維亞',
'西維爾' => '塞維亞',
'塞黑' => '塞蒙',
+'多美和普林西比' => '多美普林西比',
'塔希提' => '大溪地',
'共和联邦' => '大英國協',
'英联邦' => '大英國協',
'希拉里' => '希拉蕊',
'希特拉' => '希特勒',
'残疾人奥林匹克' => '帕拉林匹克',
+'殘疾人奧林匹克' => '帕拉林匹克',
'残奥会' => '帕運會',
'殘奧會' => '帕運會',
'巴尔米拉环礁' => '帕邁拉環礁',
'施罗德' => '施洛德',
'旱烟' => '旱菸',
'旱煙' => '旱菸',
+'比勒陀利' => '普利托利',
'普利策' => '普利茲',
'普利策奖' => '普立茲獎',
'芯片' => '晶片',
'马恩岛' => '曼島',
'木杆' => '木桿',
'尾班車' => '末班車',
+'萨格勒布' => '札格瑞布',
+'薩格勒布' => '札格瑞布',
'列奥纳多' => '李奧納多',
'杜塞尔多夫' => '杜塞道夫',
'杜塞爾多夫' => '杜塞道夫',
'海洛英' => '海洛因',
'侯賽因' => '海珊',
'侯赛因' => '海珊',
+'温得和克' => '溫荷克',
+'溫得和克' => '溫荷克',
'鼠标' => '滑鼠',
'汉诺威' => '漢諾瓦',
'漢诺威' => '漢諾瓦',
'卢浮宫' => '羅浮宮',
'樂行童軍' => '羅浮童軍',
'意大利' => '義大利',
+'意大利面' => '義大利麵',
'昂山素姬' => '翁山蘇姬',
'昂山素季' => '翁山蘇姬',
'圣基茨和尼维斯' => '聖克里斯多福及尼維斯',
'聖吉斯納域斯' => '聖克里斯多福及尼維斯',
'聖佐治' => '聖喬治',
-'圣多美和普林西比' => '聖多美普林西比',
-'聖多美和普林西比' => '聖多美普林西比',
'圣文森特和格林纳丁斯' => '聖文森及格瑞那丁',
'聖文森特和格林納丁斯' => '聖文森及格瑞那丁',
'圣赫勒拿' => '聖赫倫那',
'亚拉巴马' => '阿拉巴馬',
'阿联酋' => '阿聯',
'阿聯酋' => '阿聯',
+'亚的斯亚贝巴' => '阿迪斯阿貝巴',
+'亞的斯亞貝巴' => '阿迪斯阿貝巴',
'罗纳德·里根' => '隆納·雷根',
'私隱' => '隱私',
'耶加達' => '雅加達',
'因特网' => '互聯網',
'網際網路' => '互聯網',
'井里' => '井裏',
+'阿迪斯阿貝巴' => '亞的斯亞貝巴',
'亮著' => '亮着',
'亮著《' => '亮著《',
'亮著作' => '亮著作',
'柯林頓' => '克林頓',
'克羅埃西亞' => '克羅地亞',
'奈洛比' => '內羅畢',
+'全角' => '全形',
+'全角度' => '全角度',
+'全角色' => '全角色',
'公布' => '公佈',
'公寓里' => '公寓裏',
'冒著' => '冒着',
'格瑞那達' => '格林納達',
'格莱美奖' => '格林美獎',
'葛萊美獎' => '格林美獎',
-'格鲁吉亚' => '格魯吉亞',
+'喬治亞字母' => '格魯吉亞字母',
'框里' => '框裏',
'台式电脑' => '桌上型電腦',
'台球' => '桌球',
'殺著錄' => '殺著錄',
'壳里' => '殼裏',
'殿里' => '殿裏',
+'普利托利亞' => '比勒陀利亞',
'茅利塔尼亞' => '毛里塔尼亞',
'模里西斯' => '毛里裘斯',
'毛里求斯' => '毛里裘斯',
'溢著者' => '溢著者',
'溢著述' => '溢著述',
'溢著錄' => '溢著錄',
+'溫荷克' => '溫得和克',
'演著' => '演着',
'演著作' => '演著作',
'演著名' => '演著名',
'版图里' => '版圖裏',
'版本里' => '版本裏',
'版权信息' => '版權資訊',
+'牢里' => '牢裏',
'千里達及托巴哥' => '特立尼達和多巴哥',
'牽著' => '牽着',
'牽著作' => '牽著作',
'聖克里斯多福及尼維斯' => '聖吉斯納域斯',
'聖多美普林西比' => '聖多美和普林西比',
'聖文森及格瑞那丁' => '聖文森特和格林納丁斯',
+'聖文森國' => '聖文森特和格林納丁斯',
'聖露西亞' => '聖盧西亞',
'聖馬利諾' => '聖馬力諾',
'聽不著' => '聽不着',
'肖邦' => '蕭邦',
'薛丁格' => '薛定諤',
'塞拉耶佛' => '薩拉熱窩',
+'札格瑞布' => '薩格勒布',
'萨达姆' => '薩達姆',
'藉著' => '藉着',
'藏著' => '藏着',
'網際網絡' => '互联网',
'網際網路' => '互联网',
'亞歷山卓' => '亚历山大',
+'阿迪斯阿貝巴' => '亚的斯亚贝巴',
'雅穆索戈' => '亚穆苏克罗',
'交帳' => '交账',
'亮著' => '亮着',
'聖吉斯納域斯' => '圣基茨和尼维斯',
'聖多美普林西比' => '圣多美和普林西比',
'聖文森及格瑞那丁' => '圣文森特和格林纳丁斯',
+'聖文森國' => '圣文森特和格林纳丁斯',
'聖馬利諾' => '圣马力诺',
'蓋亞那' => '圭亚那',
'坐著' => '坐着',
'格瑞那達' => '格林纳达',
'格林美獎' => '格莱美奖',
'葛萊美獎' => '格莱美奖',
+'喬治亞字母' => '格鲁吉亚字母',
'森巴舞' => '桑巴舞',
'梅赫西迪' => '梅赛德斯',
'夢著' => '梦着',
'帕運會' => '残奥会',
'帕拉林匹克' => '残疾人奥林匹克',
'庇里牛斯' => '比利牛斯',
+'普利托利亞' => '比勒陀利亚',
'披索' => '比索',
'畢卡索' => '毕加索',
'茅利塔尼亞' => '毛里塔尼亚',
'混帳' => '混账',
'清澈' => '清澈',
'清帳' => '清账',
+'溫荷克' => '温得和克',
'渴著' => '渴着',
'渴著書' => '渴著书',
'渴著作' => '渴著作',
'獲著述' => '获著述',
'菁寮' => '菁寮',
'塞拉耶佛' => '萨拉热窝',
+'札格瑞布' => '萨格勒布',
'落著' => '落着',
'落著書' => '落著书',
'落著作' => '落著作',
U+2B128𫄨|U+07D7A絺|
U+2B137𫄷|U+07E76繶|
U+2B138𫄸|U+07E81纁|
-U+2B1ED𫇭|U+0853F蔿|
+U+2B1ED𫇭|U+0848D蒍|U+0853F蔿|
U+2B300𫌀|U+08940襀|
U+2B363𫍣|U+08A77詷|
U+2B36F𫍯|U+08AF4諴|
疴
桿
錶
-蘋
詑
堖
嶴
灡
+蘋
薳
虯
凈
聖吉斯納域斯 圣基茨和尼维斯
聖克里斯多福及尼維斯 圣基茨和尼维斯
聖文森及格瑞那丁 圣文森特和格林纳丁斯
+聖文森國 圣文森特和格林纳丁斯
聖馬利諾 圣马力诺
蓋亞那 圭亚那
坦尚尼亞 坦桑尼亚
提比里西 第比利斯
巴斯拉 巴士拉
杜拜 迪拜
+喬治亞字母 格鲁吉亚字母
坚杜拜 坚杜拜
堅杜拜 坚杜拜
賽普勒斯 塞浦路斯
普立茲獎 普利策奖
富比士 福布斯
聖多美普林西比 圣多美和普林西比
+札格瑞布 萨格勒布
+溫荷克 温得和克
+普利托利亞 比勒陀利亚
+阿迪斯阿貝巴 亚的斯亚贝巴
\ No newline at end of file
镇里 鎮裏
》里 》裏
空里 空裏
+牢里 牢裏
版本里 版本裏
苑裡 苑裡
霄裡 霄裡
軟體動物 軟體動物
軟體家具 軟體家具
網路 網絡
+全角 全形
+全角度 全角度
+全角色 全角色
人工智慧 人工智能
航天飞机 穿梭機
太空梭 穿梭機
圣基茨和尼维斯 聖吉斯納域斯
聖克里斯多福及尼維斯 聖吉斯納域斯
聖文森及格瑞那丁 聖文森特和格林納丁斯
+聖文森國 聖文森特和格林納丁斯
聖馬利諾 聖馬力諾
蓋亞那 圭亞那
坦尚尼亞 坦桑尼亞
西臺人 赫梯人
阿联酋 阿聯酋
迪拜 杜拜
-格鲁吉亚 格魯吉亞
+喬治亞字母 格魯吉亞字母
提比里西 第比利斯
諾鲁 瑙魯
玻里尼西亞 波利尼西亞
普利策奖 普立茲獎
聖多美普林西比 聖多美和普林西比
塔希提 大溪地
+札格瑞布 薩格勒布
+溫荷克 溫得和克
+普利托利亞 比勒陀利亞
+阿迪斯阿貝巴 亞的斯亞貝巴
\ No newline at end of file
高陞 高升
晉陞 晋升
歷陞 历升
+尋陞 寻升
官陞 官升
榮陞 荣升
又陞 又升
三極管 三極體
软件 軟體
軟件 軟體
+全角 全形
+全角度 全角度
+全角色 全角色
人工智能 人工智慧
航天飞机 太空梭
穿梭機 太空梭
布隆迪 蒲隆地
帕劳 帛琉
意大利 義大利
+意大利面 義大利麵
所罗门群岛 索羅門群島
所羅門群島 索羅門群島
文莱 汶萊
残奥会 帕運會
殘奧會 帕運會
残疾人奥林匹克 帕拉林匹克
+殘疾人奧林匹克 帕拉林匹克
不列颠哥伦比亚省 卑詩省
登巴萨 丹帕沙
登巴薩 丹帕沙
格林納丁斯 格瑞那丁
空中客车 空中巴士
普利策奖 普立茲獎
-圣多美和普林西比 聖多美普林西比
-聖多美和普林西比 聖多美普林西比
+多美和普林西比 多美普林西比 #聖多美普林西比
塔希提 大溪地
+萨格勒布 札格瑞布
+薩格勒布 札格瑞布
+温得和克 溫荷克
+溫得和克 溫荷克
+比勒陀利 普利托利 #普利托利亚
+亚的斯亚贝巴 阿迪斯阿貝巴
+亞的斯亞貝巴 阿迪斯阿貝巴
’m ’m
’t ’t
’re ’re
+𬞟 蘋
手塚治虫 手塚治虫
寇仇 寇讎
往日无仇 往日無讎
乾象曆 乾象曆
乾象历 乾象曆
不好干預 不好干預
+可能干預 可能干預
范文瀾 范文瀾
機械系 機械系
頂多 頂多
于帥 于帥
于濤 于濤
于贈 于贈
+于闐 于闐
于會泳 于會泳
于偉國 于偉國
于光遠 于光遠
于學忠 于學忠
于小偉 于小偉
于山國 于山國
+于山島 于山島
于幼軍 于幼軍
于廣洲 于廣洲
于從濂 于從濂
更钟情 更鍾情
更钟爱 更鍾愛
更钟意 更鍾意
+温嵐 温嵐
U+08457著|U+08457著|U+07740着|
U+08460葠|U+053C2参|
U+0846F葯|U+0836F药|
+U+0848D蒍|U+2B1ED𫇭|
U+08493蒓|U+083BC莼|
U+084C6蓆|U+05E2D席|
U+084E1蓡|U+053C2参|
併為一家
併吞
並吞下
+入侵並 #分詞用
提摩太後書
裏海
不採
捲葉蛾
捲尾猴
捲積雲
+被捲回
夸父
夸克
夸特
伊府麵
藥麵兒
意大利麵
+意大利面臨
湯下麵
茶麵
麵團
鹹豬
甜鹹
鹹甜
+鹹吃
甜、鹹
鹹、甜
錦綉花園
幹仗
包幹
幹過
+大幹一
李連杰
周杰
杰倫
不占算
不好干涉
不好干預
+可能干預
不斗膽
不每只
不采聲
詞裡
》裡
空裡
+牢裡
版本裡
裏白 #植物常用名
烏蘇里 #分詞用
于衡
于贈
于越
+於越南
于靖
于勒
于格
+於格林
鳳凰于飛
于仁泰
于會泳
于小偉
于小彤
于山國
+于山島
于幼軍
于廣洲
于康震
關系科
銹病
嚐糞
+温嵐
],
'targets' => [ 'mobile', 'desktop' ],
],
- 'jquery.checkboxShiftClick' => [
- 'deprecated' => 'Please use "mediawiki.page.ready" instead.',
- 'dependencies' => [
- 'mediawiki.page.ready',
- ],
- 'targets' => [ 'desktop', 'mobile' ],
- ],
'jquery.chosen' => [
'scripts' => 'resources/lib/jquery.chosen/chosen.jquery.js',
'styles' => 'resources/lib/jquery.chosen/chosen.css',
]
],
'mediawiki.page.ready' => [
- 'scripts' => [
- 'resources/src/mediawiki.page.ready/checkboxShift.js',
- 'resources/src/mediawiki.page.ready/ready.js',
+ 'localBasePath' => "$IP/resources/src/mediawiki.page.ready",
+ 'remoteBasePath' => "$wgResourceBasePath/resources/src/mediawiki.page.ready",
+ 'packageFiles' => [
+ 'ready.js',
+ 'checkboxShift.js',
],
'dependencies' => [
'mediawiki.util',
--- /dev/null
+{
+ "parserOptions": {
+ "sourceType": "module"
+ }
+}
/**
- * @class jQuery.plugin.checkboxShiftClick
+ * @private
+ * @class mw.plugin.pageready
*/
-( function () {
-
- /**
- * Enable checkboxes to be checked or unchecked in a row by clicking one,
- * holding shift and clicking another one.
- *
- * @return {jQuery}
- * @chainable
- */
- $.fn.checkboxShiftClick = function () {
- var prevCheckbox = null,
- $box = this;
- // When our boxes are clicked..
- $box.on( 'click', function ( e ) {
- // And one has been clicked before...
- if ( prevCheckbox !== null && e.shiftKey ) {
- // Check or uncheck this one and all in-between checkboxes,
- // except for disabled ones
- $box
- .slice(
- Math.min( $box.index( prevCheckbox ), $box.index( e.target ) ),
- Math.max( $box.index( prevCheckbox ), $box.index( e.target ) ) + 1
- )
- .filter( function () {
- return !this.disabled;
- } )
- .prop( 'checked', !!e.target.checked );
- }
- // Either way, update the prevCheckbox variable to the one clicked now
- prevCheckbox = e.target;
- } );
- return $box;
- };
-
- /**
- * @class jQuery
- * @mixins jQuery.plugin.checkboxShiftClick
- */
-
-}() );
+/**
+ * Enable checkboxes to be checked or unchecked in a row by clicking one,
+ * holding shift and clicking another one.
+ *
+ * @method checkboxShift
+ * @param {jQuery} $box
+ */
+module.exports = function ( $box ) {
+ var prev;
+ // When our boxes are clicked..
+ $box.on( 'click', function ( e ) {
+ // And one has been clicked before...
+ if ( prev && e.shiftKey ) {
+ // Check or uncheck this one and all in-between checkboxes,
+ // except for disabled ones
+ $box
+ .slice(
+ Math.min( $box.index( prev ), $box.index( e.target ) ),
+ Math.max( $box.index( prev ), $box.index( e.target ) ) + 1
+ )
+ .filter( function () {
+ return !this.disabled;
+ } )
+ .prop( 'checked', e.target.checked );
+ }
+ // Either way, remember this as the last clicked one
+ prev = e.target;
+ } );
+};
-( function () {
- mw.hook( 'wikipage.content' ).add( function ( $content ) {
- var $sortable, $collapsible;
+var checkboxShift = require( './checkboxShift.js' );
+mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ var $sortable, $collapsible;
- $collapsible = $content.find( '.mw-collapsible' );
- if ( $collapsible.length ) {
- // Preloaded by Skin::getDefaultModules()
- mw.loader.using( 'jquery.makeCollapsible', function () {
- $collapsible.makeCollapsible();
- } );
- }
+ $collapsible = $content.find( '.mw-collapsible' );
+ if ( $collapsible.length ) {
+ // Preloaded by Skin::getDefaultModules()
+ mw.loader.using( 'jquery.makeCollapsible', function () {
+ $collapsible.makeCollapsible();
+ } );
+ }
- $sortable = $content.find( 'table.sortable' );
- if ( $sortable.length ) {
- // Preloaded by Skin::getDefaultModules()
- mw.loader.using( 'jquery.tablesorter', function () {
- $sortable.tablesorter();
- } );
- }
+ $sortable = $content.find( 'table.sortable' );
+ if ( $sortable.length ) {
+ // Preloaded by Skin::getDefaultModules()
+ mw.loader.using( 'jquery.tablesorter', function () {
+ $sortable.tablesorter();
+ } );
+ }
- // Run jquery.checkboxShiftClick
- $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick();
- } );
+ checkboxShift( $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ) );
+} );
- // Things outside the wikipage content
- $( function () {
- var $nodes;
+// Handle elements outside the wikipage content
+$( function () {
+ var $nodes;
- // Add accesskey hints to the tooltips
- $( '[accesskey]' ).updateTooltipAccessKeys();
+ // Add accesskey hints to the tooltips
+ $( '[accesskey]' ).updateTooltipAccessKeys();
- $nodes = $( '.catlinks[data-mw="interface"]' );
- if ( $nodes.length ) {
- /**
- * Fired when categories are being added to the DOM
- *
- * It is encouraged to fire it before the main DOM is changed (when $content
- * is still detached). However, this order is not defined either way, so you
- * should only rely on $content itself.
- *
- * This includes the ready event on a page load (including post-edit loads)
- * and when content has been previewed with LivePreview.
- *
- * @event wikipage_categories
- * @member mw.hook
- * @param {jQuery} $content The most appropriate element containing the content,
- * such as .catlinks
- */
- mw.hook( 'wikipage.categories' ).fire( $nodes );
- }
+ $nodes = $( '.catlinks[data-mw="interface"]' );
+ if ( $nodes.length ) {
+ /**
+ * Fired when categories are being added to the DOM
+ *
+ * It is encouraged to fire it before the main DOM is changed (when $content
+ * is still detached). However, this order is not defined either way, so you
+ * should only rely on $content itself.
+ *
+ * This includes the ready event on a page load (including post-edit loads)
+ * and when content has been previewed with LivePreview.
+ *
+ * @event wikipage_categories
+ * @member mw.hook
+ * @param {jQuery} $content The most appropriate element containing the content,
+ * such as .catlinks
+ */
+ mw.hook( 'wikipage.categories' ).fire( $nodes );
+ }
- $( '#t-print a' ).on( 'click', function ( e ) {
- window.print();
- e.preventDefault();
- } );
-
- // Turn logout to a POST action
- $( '#pt-logout a' ).on( 'click', function ( e ) {
- var api = new mw.Api(),
- returnUrl = $( '#pt-logout a' ).attr( 'href' );
- mw.notify(
- mw.message( 'logging-out-notify' ),
- { tag: 'logout', autoHide: false }
- );
- api.postWithToken( 'csrf', {
- action: 'logout'
- } ).then(
- function () {
- location.href = returnUrl;
- },
- function ( e ) {
- mw.notify(
- mw.message( 'logout-failed', e ),
- { type: 'error', tag: 'logout', autoHide: false }
- );
- }
- );
- e.preventDefault();
- } );
+ $( '#t-print a' ).on( 'click', function ( e ) {
+ window.print();
+ e.preventDefault();
} );
-}() );
+ // Turn logout to a POST action
+ $( '#pt-logout a' ).on( 'click', function ( e ) {
+ var api = new mw.Api(),
+ url = this.href;
+ mw.notify(
+ mw.message( 'logging-out-notify' ),
+ { tag: 'logout', autoHide: false }
+ );
+ api.postWithToken( 'csrf', {
+ action: 'logout'
+ } ).then(
+ function () {
+ location.href = url;
+ },
+ function ( err ) {
+ mw.notify(
+ mw.message( 'logout-failed', err ),
+ { type: 'error', tag: 'logout', autoHide: false }
+ );
+ }
+ );
+ e.preventDefault();
+ } );
+} );
return [
[ Title::makeTitle( NS_SPECIAL, 'Test' ) ],
[ Title::makeTitle( NS_MEDIA, 'Test' ) ],
- [ Title::makeTitle( NS_MAIN, '', 'Kittens' ) ],
- [ Title::makeTitle( NS_MAIN, 'Kittens', '', 'acme' ) ],
+ ];
+ }
+
+ public static function provideGetTalkPage_broken() {
+ // These cases *should* be bad, but are not treated as bad, for backwards compatibility.
+ // See discussion on T227817.
+ return [
+ [
+ Title::makeTitle( NS_MAIN, '', 'Kittens' ),
+ Title::makeTitle( NS_TALK, '' ), // Section is lost!
+ false,
+ ],
+ [
+ Title::makeTitle( NS_MAIN, 'Kittens', '', 'acme' ),
+ Title::makeTitle( NS_TALK, 'Kittens', '' ), // Interwiki prefix is lost!
+ true,
+ ],
];
}
$title->getTalkPage();
}
+ /**
+ * @dataProvider provideGetTalkPage_broken
+ * @covers Title::getTalkPageIfDefined
+ */
+ public function testGetTalkPage_broken( Title $title, Title $expected, $valid ) {
+ $errorLevel = error_reporting( E_ERROR );
+
+ // NOTE: Eventually we want to throw in this case. But while there is still code that
+ // calls this method without checking, we want to avoid fatal errors.
+ // See discussion on T227817.
+ $result = $title->getTalkPage();
+ $this->assertTrue( $expected->equals( $result ) );
+ $this->assertSame( $valid, $result->isValid() );
+
+ error_reporting( $errorLevel );
+ }
+
/**
* @dataProvider provideGetTalkPage_good
* @covers Title::getTalkPageIfDefined
$this->assertSame( 'Need more donations', $data['readonlyreason'] );
}
- public function testNamespaces() {
- $this->setMwGlobals( 'wgExtraNamespaces', [ '138' => 'Testing' ] );
+ public function testNamespacesBasic() {
+ $this->assertSame(
+ array_keys( MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces() ),
+ array_keys( $this->doQuery( 'namespaces' ) )
+ );
+ }
+ public function testNamespacesExtraNS() {
+ $this->setMwGlobals( 'wgExtraNamespaces', [ '138' => 'Testing' ] );
$this->assertSame(
array_keys( MediaWikiServices::getInstance()->getContentLanguage()->getFormattedNamespaces() ),
array_keys( $this->doQuery( 'namespaces' ) )
);
}
+ public function testNamespacesProtection() {
+ $this->setMwGlobals(
+ 'wgNamespaceProtection',
+ [
+ '0' => '',
+ '2' => [ '' ],
+ '4' => 'editsemiprotected',
+ '8' => [
+ 'editinterface',
+ 'noratelimit'
+ ],
+ '14' => [
+ 'move-categorypages',
+ ''
+ ]
+ ]
+ );
+ $data = $this->doQuery( 'namespaces' );
+ $this->assertArrayNotHasKey( 'namespaceprotection', $data['0'] );
+ $this->assertArrayNotHasKey( 'namespaceprotection', $data['2'] );
+ $this->assertSame( 'editsemiprotected', $data['4']['namespaceprotection'] );
+ $this->assertSame( 'editinterface|noratelimit', $data['8']['namespaceprotection'] );
+ $this->assertSame( 'move-categorypages', $data['14']['namespaceprotection'] );
+ }
+
public function testNamespaceAliases() {
global $wgNamespaceAliases;
// TODO: Once we require Node 7 or later, we can use async-await.
module.exports = {
+ /**
+ * Get a logged-in instance of `MWBot` with edit token already set up.
+ * Default username, password and base URL is used unless specified.
+ *
+ * @since 0.5.0
+ * @param {string} username - Optional
+ * @param {string} password - Optional
+ * @param {string} baseUrl - Optional
+ * @return {Promise<MWBot>}
+ */
+ bot(
+ username = browser.options.username,
+ password = browser.options.password,
+ baseUrl = browser.options.baseUrl
+ ) {
+ const bot = new MWBot();
+
+ return bot.loginGetEditToken( {
+ apiUrl: `${baseUrl}/api.php`,
+ username: username,
+ password: password
+ } ).then( function () {
+ return bot;
+ } );
+ },
+
/**
* Shortcut for `MWBot#edit( .. )`.
* Default username, password and base URL is used unless specified
password = browser.options.password,
baseUrl = browser.options.baseUrl
) {
- const bot = new MWBot();
-
- return bot.loginGetEditToken( {
- apiUrl: `${baseUrl}/api.php`,
- username: username,
- password: password
- } ).then( function () {
- return bot.edit( title, content, `Created or updated page with "${content}"` );
- } );
+ return this.bot( username, password, baseUrl )
+ .then( function ( bot ) {
+ return bot.edit( title, content, `Created or updated page with "${content}"` );
+ } );
},
/**
* @return {Object} Promise for API action=delete response data.
*/
delete( title, reason ) {
- const bot = new MWBot();
-
- return bot.loginGetEditToken( {
- apiUrl: `${browser.options.baseUrl}/api.php`,
- username: browser.options.username,
- password: browser.options.password
- } ).then( function () {
- return bot.delete( title, reason );
- } );
+ return this.bot()
+ .then( function ( bot ) {
+ return bot.delete( title, reason );
+ } );
},
/**
* @return {Object} Promise for API action=block response data.
*/
blockUser( username, expiry ) {
- const bot = new MWBot();
-
- // Log in as admin
- return bot.loginGetEditToken( {
- apiUrl: `${browser.options.baseUrl}/api.php`,
- username: browser.options.username,
- password: browser.options.password
- } ).then( () => {
- // block user. default = admin
- return bot.request( {
- action: 'block',
- user: username || browser.options.username,
- reason: 'browser test',
- token: bot.editToken,
- expiry
+ return this.bot()
+ .then( function ( bot ) {
+ // block user. default = admin
+ return bot.request( {
+ action: 'block',
+ user: username || browser.options.username,
+ reason: 'browser test',
+ token: bot.editToken,
+ expiry
+ } );
} );
- } );
},
/**
* @return {Object} Promise for API action=unblock response data.
*/
unblockUser( username ) {
- const bot = new MWBot();
-
- // Log in as admin
- return bot.loginGetEditToken( {
- apiUrl: `${browser.options.baseUrl}/api.php`,
- username: browser.options.username,
- password: browser.options.password
- } ).then( () => {
- // unblock user. default = admin
- return bot.request( {
- action: 'unblock',
- user: username || browser.options.username,
- reason: 'browser test done',
- token: bot.editToken
+ return this.bot()
+ .then( function ( bot ) {
+ // unblock user. default = admin
+ return bot.request( {
+ action: 'unblock',
+ user: username || browser.options.username,
+ reason: 'browser test done',
+ token: bot.editToken
+ } );
} );
- } );
}
};