跳转到内容
打开/关闭搜索
搜索
打开/关闭菜单
13
条目
4
用户
232
编辑
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
首页
子服一览
总规章
常见问题
历史资料
全部条目
分类
最近更改
打开/关闭外观设置菜单
无法加载偏好设置。请检查您的网络连接并重试。
重试
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
讨论
贡献
创建账号
登录
查看“︁MediaWiki:Common.js”︁的源代码
MediaWiki界面页面
查看
阅读
查看源代码
查看历史
associated-pages
系统消息
讨论
更多操作
更多
工具
链入页面
相关更改
页面信息
←
MediaWiki:Common.js
因为以下原因,您没有权限编辑该页面:
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此JavaScript页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
/* 在 Citizen 顶栏的官方预留槽位 .citizen-header__start 内注入横向主导航。 注意:不要插到 .citizen-header 根节点上——那里是 logo / 搜索 / 抽屉的 flex 行, 插进去会跟它们抢空间导致错位。__start 是皮肤留空的导航槽。 */ ( function () { 'use strict'; var LINKS = [ { text: '首页', page: '首页', match: /^(首页|Main_Page)$/ }, { text: '子服一览', page: '子服一览', match: /^(子服一览|趣味生存|粘液空岛|纯净生存|机械动力|职业战争|起床战争|僵尸末日|避暑山庄)(\/|$)/ }, { text: '总规章', page: 'KukeMC群组服总规章', match: /^KukeMC群组服总规章(\/|$)/ }, { text: '常见问题', page: '服务器常见问题', match: /^服务器常见问题(\/|$)/ }, { text: '历史资料', page: '历史资料展览馆', match: /^历史资料展览馆(\/|$)/ }, { text: '全部条目', page: 'Special:AllPages', match: /^Special:AllPages$/i }, { text: '分类', page: 'Special:Categories', match: /^Special:Categories$/i }, { text: '最近更改', page: 'Special:RecentChanges', match: /^Special:RecentChanges$/i } ]; function build() { var slot = document.querySelector( '.citizen-header__start' ); if ( !slot || slot.querySelector( '.kuke-nav' ) ) { return; } var page = mw.config.get( 'wgPageName' ); var nav = document.createElement( 'nav' ); nav.className = 'kuke-nav'; nav.setAttribute( 'aria-label', '主导航' ); LINKS.forEach( function ( item ) { var a = document.createElement( 'a' ); a.className = 'kuke-nav__link'; a.href = mw.util.getUrl( item.page ); a.textContent = item.text; if ( item.match.test( page ) ) { a.classList.add( 'kuke-nav__link--active' ); a.setAttribute( 'aria-current', 'page' ); } nav.appendChild( a ); } ); slot.appendChild( nav ); } if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', build ); } else { build(); } }() ); /* 横版登录 / 注册弹窗。 表单在本地直接渲染(不再抓取 Special 页面的 HTML),提交走 Action API clientlogin / createaccount,token 在页面加载时就后台预取, 因此弹窗打开即可用,没有等待。 独立页面 Special:UserLogin / Special:CreateAccount 保留,无 JS 时仍可用。 */ ( function () { 'use strict'; var LOGO = 'https://m.ccw.site/gandi_application/user_assets/2a6bb37880317d2bb5525ab560618e04.png'; var MODES = { login: { page: 'Special:UserLogin', tab: '登录', title: '登录到 Kuke Wiki', sub: '登录后即可编辑条目、参与讨论并同步个人偏好设置。', submit: '登录', busy: '正在登录…' }, signup: { page: 'Special:CreateAccount', tab: '注册', title: '创建账号', sub: '加入 KukeMC 官方百科的编辑与维护,只需一分钟。', submit: '创建账号', busy: '正在创建…' } }; var FIELDS = { login: [ { name: 'username', label: '用户名', type: 'text', autocomplete: 'username', required: true }, { name: 'password', label: '密码', type: 'password', autocomplete: 'current-password', required: true }, { name: 'rememberMe', label: '在此浏览器上保持登录', type: 'checkbox' } ], signup: [ { name: 'username', label: '用户名', type: 'text', autocomplete: 'username', required: true }, { name: 'password', label: '密码', type: 'password', autocomplete: 'new-password', required: true }, { name: 'retype', label: '确认密码', type: 'password', autocomplete: 'new-password', required: true }, { name: 'email', label: '电子邮件', type: 'email', autocomplete: 'email', hint: '选填,用于找回密码' }, { name: 'captchaWord', label: '验证码', type: 'captcha', required: true } ] }; var POINTS = [ { icon: 'pencil', text: '编辑与创建条目,完善服务器资料' }, { icon: 'chat', text: '参与讨论页,与其他编辑者协作' }, { icon: 'star', text: '保存个人偏好、监视列表与编辑历史' }, { icon: 'clock', text: '追踪最近更改,第一时间了解更新' } ]; var ICONS = { pencil: 'M13.5 2.5a2.12 2.12 0 0 1 3 3L7 15l-4 1 1-4Z', chat: 'M3 5.5A1.5 1.5 0 0 1 4.5 4h11A1.5 1.5 0 0 1 17 5.5v7a1.5 1.5 0 0 1-1.5 1.5H8l-4 3v-3h-.5A1.5 1.5 0 0 1 3 12.5Z', star: 'm10 2.6 2.3 4.66 5.15.75-3.72 3.63.88 5.12L10 14.34l-4.6 2.42.87-5.12L2.55 8l5.15-.75Z', clock: 'M10 2.5a7.5 7.5 0 1 1 0 15 7.5 7.5 0 0 1 0-15Zm0 3.6v4.2l3 1.8', close: 'M5 5l10 10M15 5L5 15', eye: 'M1.8 10S4.9 4.7 10 4.7 18.2 10 18.2 10 15.1 15.3 10 15.3 1.8 10 1.8 10Zm8.2 2.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z', eyeOff: 'M3 3l14 14M8.2 8.3A2.2 2.2 0 0 0 10 12.2c.6 0 1.1-.2 1.6-.6M6.1 6.2C3.6 7.6 1.8 10 1.8 10s3.1 5.3 8.2 5.3c1.4 0 2.6-.4 3.7-1M15.5 13C17.3 11.8 18.2 10 18.2 10S15.1 4.7 10 4.7c-.7 0-1.4.1-2 .3', alert: 'M10 6.6v4.2m0 2.6h.01M8.6 3.2 2.2 14.4A1.6 1.6 0 0 0 3.6 16.8h12.8a1.6 1.6 0 0 0 1.4-2.4L11.4 3.2a1.6 1.6 0 0 0-2.8 0Z' }; var root = null; var main = null; var body = null; var titleEl = null; var subEl = null; var alertEl = null; var lastFocus = null; var current = null; var api = null; var apiReady = null; var tokens = {}; var captcha = null; var submitting = false; function svg( d, cls, size ) { var s = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); s.setAttribute( 'viewBox', '0 0 20 20' ); s.setAttribute( 'width', size || '18' ); s.setAttribute( 'height', size || '18' ); s.setAttribute( 'aria-hidden', 'true' ); s.setAttribute( 'fill', 'none' ); s.setAttribute( 'stroke', 'currentColor' ); s.setAttribute( 'stroke-width', '1.6' ); s.setAttribute( 'stroke-linecap', 'round' ); s.setAttribute( 'stroke-linejoin', 'round' ); if ( cls ) { s.setAttribute( 'class', cls ); } var p = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); p.setAttribute( 'd', d ); s.appendChild( p ); return s; } function el( tag, cls, text ) { var n = document.createElement( tag ); if ( cls ) { n.className = cls; } if ( text ) { n.textContent = text; } return n; } function returnTarget() { var page = mw.config.get( 'wgPageName' ); if ( /^Special:(UserLogin|CreateAccount|UserLogout)$/i.test( page ) ) { return mw.config.get( 'wgMainPageTitle' ) || '首页'; } return page; } function returnUrl() { return location.origin + mw.util.getUrl( returnTarget() ); } /* ---------- API ---------- */ function ready() { if ( !apiReady ) { apiReady = mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () { api = new mw.Api(); return api; } ); } return apiReady; } function token( type ) { if ( tokens[ type ] ) { return tokens[ type ]; } tokens[ type ] = ready().then( function () { return api.get( { action: 'query', meta: 'tokens', type: type, formatversion: 2 } ); } ).then( function ( d ) { return d.query.tokens[ type + 'token' ]; } ); return tokens[ type ]; } /* QuestyCaptcha 的题面与 id,每次进入注册态重新取(旧 id 会失效) */ function loadCaptcha() { return ready().then( function () { return api.get( { action: 'query', meta: 'authmanagerinfo', amirequestsfor: 'create', formatversion: 2 } ); } ).then( function ( d ) { var reqs = ( d.query && d.query.authmanagerinfo && d.query.authmanagerinfo.requests ) || []; var hit = null; reqs.forEach( function ( r ) { if ( r.id && r.id.indexOf( 'Captcha' ) !== -1 && r.fields && r.fields.captchaId ) { hit = r; } } ); if ( !hit ) { return null; } return { id: hit.fields.captchaId.value, question: hit.fields.captchaInfo ? hit.fields.captchaInfo.value : '', label: ( hit.fields.captchaWord && hit.fields.captchaWord.label ) || '验证码' }; } ).catch( function () { return null; } ); } /* ---------- 结构 ---------- */ function build() { root = el( 'div', 'kuke-auth' ); root.hidden = true; var backdrop = el( 'div', 'kuke-auth__backdrop' ); backdrop.addEventListener( 'click', close ); root.appendChild( backdrop ); var dialog = el( 'div', 'kuke-auth__dialog' ); dialog.setAttribute( 'role', 'dialog' ); dialog.setAttribute( 'aria-modal', 'true' ); dialog.setAttribute( 'aria-labelledby', 'kuke-auth-title' ); var closeBtn = el( 'button', 'kuke-auth__close' ); closeBtn.type = 'button'; closeBtn.setAttribute( 'aria-label', '关闭' ); closeBtn.appendChild( svg( ICONS.close ) ); closeBtn.addEventListener( 'click', close ); dialog.appendChild( closeBtn ); // 左:品牌栏 var aside = el( 'aside', 'kuke-auth__aside' ); var logo = el( 'img', 'kuke-auth__logo' ); logo.src = LOGO; logo.alt = ''; logo.width = 44; logo.height = 44; aside.appendChild( logo ); aside.appendChild( el( 'p', 'kuke-auth__brand', 'Kuke Wiki' ) ); aside.appendChild( el( 'p', 'kuke-auth__tagline', 'KukeMC 群组服官方百科' ) ); var ul = el( 'ul', 'kuke-auth__points' ); POINTS.forEach( function ( item ) { var li = el( 'li', 'kuke-auth__point' ); li.appendChild( svg( ICONS[ item.icon ], 'kuke-auth__point-icon' ) ); li.appendChild( el( 'span', null, item.text ) ); ul.appendChild( li ); } ); aside.appendChild( ul ); aside.appendChild( el( 'p', 'kuke-auth__note', '本站内容由社区共同维护,欢迎参与。' ) ); dialog.appendChild( aside ); // 右:表单栏 main = el( 'div', 'kuke-auth__main' ); var head = el( 'div', 'kuke-auth__head' ); var tabs = el( 'div', 'kuke-auth__tabs' ); tabs.setAttribute( 'role', 'tablist' ); Object.keys( MODES ).forEach( function ( key ) { var b = el( 'button', 'kuke-auth__tab', MODES[ key ].tab ); b.type = 'button'; b.dataset.mode = key; b.setAttribute( 'role', 'tab' ); b.addEventListener( 'click', function () { render( key ); } ); tabs.appendChild( b ); } ); head.appendChild( tabs ); titleEl = el( 'h2', 'kuke-auth__title' ); titleEl.id = 'kuke-auth-title'; head.appendChild( titleEl ); subEl = el( 'p', 'kuke-auth__sub' ); head.appendChild( subEl ); main.appendChild( head ); body = el( 'div', 'kuke-auth__body' ); main.appendChild( body ); dialog.appendChild( main ); root.appendChild( dialog ); document.body.appendChild( root ); } /* ---------- 表单 ---------- */ function passwordToggle( input ) { var btn = el( 'button', 'kuke-auth__peek' ); btn.type = 'button'; btn.setAttribute( 'aria-label', '显示密码' ); btn.appendChild( svg( ICONS.eye, null, '17' ) ); btn.addEventListener( 'click', function () { var show = input.type === 'password'; input.type = show ? 'text' : 'password'; btn.setAttribute( 'aria-label', show ? '隐藏密码' : '显示密码' ); btn.textContent = ''; btn.appendChild( svg( show ? ICONS.eyeOff : ICONS.eye, null, '17' ) ); input.focus(); } ); return btn; } function field( spec, form ) { if ( spec.type === 'checkbox' ) { var wrap = el( 'label', 'kuke-auth__check' ); var box = el( 'input' ); box.type = 'checkbox'; box.name = spec.name; wrap.appendChild( box ); wrap.appendChild( el( 'span', null, spec.label ) ); return wrap; } var id = 'kuke-auth-' + spec.name; var row = el( 'div', 'kuke-auth__field' ); var label = el( 'label', 'kuke-auth__label', spec.label ); label.htmlFor = id; if ( !spec.required ) { label.appendChild( el( 'span', 'kuke-auth__optional', '选填' ) ); } row.appendChild( label ); var control = el( 'div', 'kuke-auth__control' ); var input = el( 'input', 'kuke-auth__input' ); input.id = id; input.name = spec.name; input.type = spec.type === 'captcha' ? 'text' : spec.type; if ( spec.autocomplete ) { input.autocomplete = spec.autocomplete; } if ( spec.required ) { input.required = true; } if ( spec.type === 'captcha' ) { input.autocomplete = 'off'; input.inputMode = 'text'; row.classList.add( 'kuke-auth__field--captcha' ); } control.appendChild( input ); if ( spec.type === 'password' ) { control.classList.add( 'kuke-auth__control--peek' ); control.appendChild( passwordToggle( input ) ); } row.appendChild( control ); if ( spec.type === 'captcha' ) { var q = el( 'p', 'kuke-auth__captcha' ); q.appendChild( el( 'span', 'kuke-auth__captcha-q', '正在获取…' ) ); row.appendChild( q ); form.captchaQuestion = q.firstChild; loadCaptcha().then( function ( data ) { if ( !form.isConnected ) { return; } captcha = data; q.firstChild.textContent = data ? data.question : '(暂不可用,请稍后重试)'; } ); } if ( spec.hint ) { row.appendChild( el( 'p', 'kuke-auth__hint', spec.hint ) ); } return row; } function showAlert( text ) { if ( !alertEl ) { return; } alertEl.textContent = ''; if ( !text ) { alertEl.hidden = true; return; } alertEl.appendChild( svg( ICONS.alert, 'kuke-auth__alert-icon' ) ); alertEl.appendChild( el( 'span', null, text ) ); alertEl.hidden = false; } function setSubmitting( btn, mode, on ) { submitting = on; btn.disabled = on; btn.classList.toggle( 'kuke-auth__submit--busy', on ); btn.textContent = ''; if ( on ) { btn.appendChild( el( 'span', 'kuke-auth__spinner' ) ); btn.appendChild( el( 'span', null, MODES[ mode ].busy ) ); } else { btn.appendChild( el( 'span', null, MODES[ mode ].submit ) ); } } function values( form ) { var out = {}; Array.prototype.forEach.call( form.querySelectorAll( 'input' ), function ( i ) { out[ i.name ] = i.type === 'checkbox' ? ( i.checked ? '1' : '' ) : i.value; } ); return out; } function submit( mode, form, btn ) { if ( submitting ) { return; } var v = values( form ); if ( mode === 'signup' && v.password !== v.retype ) { showAlert( '两次输入的密码不一致。' ); return; } showAlert( '' ); setSubmitting( btn, mode, true ); var type = mode === 'login' ? 'login' : 'createaccount'; ready().then( function () { return token( type ); } ).then( function ( tk ) { var params; if ( mode === 'login' ) { params = { action: 'clientlogin', loginreturnurl: returnUrl(), logintoken: tk, username: v.username, password: v.password, formatversion: 2, errorformat: 'html', errorlang: 'zh' }; if ( v.rememberMe ) { params.rememberMe = '1'; } } else { params = { action: 'createaccount', createreturnurl: returnUrl(), createtoken: tk, username: v.username, password: v.password, retype: v.retype, formatversion: 2, errorformat: 'html', errorlang: 'zh' }; if ( v.email ) { params.email = v.email; } if ( captcha ) { params.captchaId = captcha.id; params.captchaWord = v.captchaWord; } } return api.post( params ); } ).then( function ( d ) { var res = d.clientlogin || d.createaccount || {}; if ( res.status === 'PASS' ) { location.href = returnUrl(); return; } // 需要额外步骤(如二次验证)时交给完整页面处理 if ( res.status === 'UI' || res.status === 'REDIRECT' ) { location.href = mw.util.getUrl( MODES[ mode ].page, { returnto: returnTarget() } ); return; } setSubmitting( btn, mode, false ); showAlert( res.message || '提交失败,请稍后重试。' ); // token 与验证码一次性,失败后重取 delete tokens[ type ]; if ( mode === 'signup' ) { refreshCaptcha( form ); } } ).catch( function ( code, data ) { setSubmitting( btn, mode, false ); var msg = ( data && data.error && data.error.info ) || '网络异常,请稍后重试。'; showAlert( msg ); delete tokens[ type ]; } ); } function refreshCaptcha( form ) { var q = form.captchaQuestion; if ( !q ) { return; } q.textContent = '正在获取…'; loadCaptcha().then( function ( data ) { captcha = data; if ( q.isConnected ) { q.textContent = data ? data.question : '(暂不可用,请稍后重试)'; } } ); } function render( mode ) { if ( current === mode ) { return; } current = mode; captcha = null; titleEl.textContent = MODES[ mode ].title; subEl.textContent = MODES[ mode ].sub; Array.prototype.forEach.call( root.querySelectorAll( '.kuke-auth__tab' ), function ( b ) { var on = b.dataset.mode === mode; b.classList.toggle( 'kuke-auth__tab--active', on ); b.setAttribute( 'aria-selected', on ? 'true' : 'false' ); } ); body.textContent = ''; alertEl = el( 'div', 'kuke-auth__alert' ); alertEl.setAttribute( 'role', 'alert' ); alertEl.hidden = true; body.appendChild( alertEl ); var form = el( 'form', 'kuke-auth__form' ); form.setAttribute( 'novalidate', 'novalidate' ); FIELDS[ mode ].forEach( function ( spec ) { form.appendChild( field( spec, form ) ); } ); var btn = el( 'button', 'kuke-auth__submit' ); btn.type = 'submit'; btn.appendChild( el( 'span', null, MODES[ mode ].submit ) ); form.appendChild( btn ); form.addEventListener( 'submit', function ( e ) { e.preventDefault(); submit( mode, form, btn ); } ); body.appendChild( form ); var foot = el( 'p', 'kuke-auth__foot' ); if ( mode === 'login' ) { var reset = el( 'a', null, '忘记密码?' ); reset.href = mw.util.getUrl( 'Special:PasswordReset' ); foot.appendChild( reset ); foot.appendChild( el( 'span', 'kuke-auth__dot', '·' ) ); var toSignup = el( 'a', null, '还没有账号?立即注册' ); toSignup.href = '#'; toSignup.addEventListener( 'click', function ( e ) { e.preventDefault(); render( 'signup' ); } ); foot.appendChild( toSignup ); } else { foot.appendChild( el( 'span', null, '已有账号?' ) ); var toLogin = el( 'a', null, '直接登录' ); toLogin.href = '#'; toLogin.addEventListener( 'click', function ( e ) { e.preventDefault(); render( 'login' ); } ); foot.appendChild( toLogin ); } body.appendChild( foot ); main.scrollTop = 0; // 预热对应的 token token( mode === 'login' ? 'login' : 'createaccount' ); var first = form.querySelector( 'input' ); if ( first ) { first.focus(); } } /* ---------- 开关 ---------- */ function onKey( e ) { if ( e.key === 'Escape' ) { close(); return; } if ( e.key !== 'Tab' || root.hidden ) { return; } var items = root.querySelectorAll( 'a[href], button:not([disabled]), input:not([type=hidden]), select, textarea' ); if ( !items.length ) { return; } var first = items[ 0 ]; var last = items[ items.length - 1 ]; if ( e.shiftKey && document.activeElement === first ) { e.preventDefault(); last.focus(); } else if ( !e.shiftKey && document.activeElement === last ) { e.preventDefault(); first.focus(); } } function open( mode ) { if ( !root ) { build(); } lastFocus = document.activeElement; root.hidden = false; document.body.classList.add( 'kuke-auth-open' ); document.addEventListener( 'keydown', onKey ); render( mode ); } function close() { if ( !root || root.hidden ) { return; } root.hidden = true; current = null; document.body.classList.remove( 'kuke-auth-open' ); document.removeEventListener( 'keydown', onKey ); if ( lastFocus && lastFocus.focus ) { lastFocus.focus(); } } function modeOf( href ) { var target; try { target = new URL( href, location.href ); } catch ( e ) { return null; } if ( target.origin !== location.origin ) { return null; } var path = decodeURIComponent( target.pathname + '?' + target.search ); if ( /Special:UserLogin|特殊:用户登录|Special:登录/i.test( path ) ) { return 'login'; } if ( /Special:CreateAccount|特殊:创建账户|Special:创建账号/i.test( path ) ) { return 'signup'; } return null; } function onClick( e ) { if ( e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey ) { return; } var a = e.target.closest ? e.target.closest( 'a[href]' ) : null; if ( !a || a.target === '_blank' ) { return; } var mode = modeOf( a.getAttribute( 'href' ) ); if ( !mode ) { return; } e.preventDefault(); open( mode ); } function init() { if ( mw.config.get( 'wgUserName' ) ) { return; } document.addEventListener( 'click', onClick ); // 后台预取登录 token,弹窗打开时表单已可直接提交 token( 'login' ); var direct = /[?&]kukeauth=(login|signup)/.exec( location.search ); if ( direct ) { open( direct[ 1 ] ); } } if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', init ); } else { init(); } }() ); /* TEMP DEBUG */ ( function () { if ( !/[?&]kukeautotest=2/.test( location.search ) ) { return; } var t = 0; var iv = setInterval( function () { t++; var f = document.querySelector( '.kuke-auth__form' ); var u = f && f.querySelector( 'input[name=username]' ); if ( u ) { clearInterval( iv ); u.value = 'Admin'; f.querySelector( 'input[name=password]' ).value = 'wrong-password-xyz'; f.dispatchEvent( new Event( 'submit', { cancelable: true, bubbles: true } ) ); } else if ( t > 100 ) { clearInterval( iv ); } }, 100 ); }() );
返回
MediaWiki:Common.js
。
查看“︁MediaWiki:Common.js”︁的源代码
MediaWiki界面页面