Upload all files.

master
落雨楓 4 years ago
commit b58080c51e

@ -0,0 +1,15 @@
<?php
$magicWords = [];
$magicWords['en'] = [
'nocreditbox' => [1, '__NOCREDITBOX__'],
];
$magicWords['zh-hans'] = [
'nocreditbox' => [1, '__无编辑者框__'],
];
$magicWords['zh-hant'] = [
'nocreditbox' => [1, '__無編輯者框__'],
];

@ -0,0 +1,74 @@
{
"name": "IsekaiContributors",
"namemsg": "isekaicontrib-name",
"author": "Hyperzlib",
"requires": {
"MediaWiki": ">= 1.35.0"
},
"url": "/",
"descriptionmsg": "isekaicontrib-desc",
"license-name": "MIT",
"type": "other",
"APIPropModules": {
"pagecredit": "Isekai\\Contributors\\Api\\ApiPageCredit"
},
"Hooks": {
"SidebarBeforeOutput": "Isekai\\Contributors\\Hooks::onSidebarBeforeOutput",
"SkinTemplateOutputPageBeforeExec": "Isekai\\Contributors\\Hooks::onSkinTemplateOutputPageBeforeExec",
"BeforePageDisplay": "Isekai\\Contributors\\Hooks::onBeforePageDisplay",
"ResourceLoaderGetConfigVars": "Isekai\\Contributors\\Hooks::onResourceLoaderGetConfigVars",
"GetDoubleUnderscoreIDs": "Isekai\\Contributors\\Hooks::onGetDoubleUnderscoreIDs"
},
"ExtensionMessagesFiles": {
"IsekaiContributorsMagic": "IsekaiContributors.i18n.magic.php"
},
"MessagesDirs": {
"IsekaiContributors": [
"i18n",
"i18n/api"
]
},
"AutoloadNamespaces": {
"Isekai\\Contributors\\": "includes/"
},
"ResourceModules": {
"ext.isekai.contrib.styles": {
"styles": ["ext.isekai.contributor.panel.less"],
"targets": [ "desktop", "mobile" ]
},
"ext.isekai.contrib.dialog": {
"scripts": ["ext.isekai.contributor.dialog.js"],
"styles": ["ext.isekai.contributor.dialog.less"],
"targets": [ "desktop", "mobile" ],
"dependencies": [
"oojs-ui-core",
"oojs-ui-windows",
"oojs-ui-widgets"
],
"messages": [
"isekaicontrib-dialog-title",
"isekaicontrib-dialog-cancel",
"isekaicontrib-dialog-subtitle-creator",
"isekaicontrib-dialog-subtitle-last-editor",
"isekaicontrib-dialog-subtitle-contributors"
]
},
"ext.isekai.contrib.images": {
"selector": ".isekai-img-{name}",
"class": "ResourceLoaderImageModule",
"images": {
"more-contrib": "more.svg"
}
}
},
"ResourceFileModulePaths": {
"localBasePath": "modules",
"remoteExtPath": "IsekaiContributors"
},
"config": {
"IsekaiContributorAvatar": {
"value": "/avatar/%s"
}
},
"manifest_version": 2
}

@ -0,0 +1,5 @@
{
"apihelp-query+pagecredit-description": "获得页面的贡献者列表。",
"apihelp-query+pagecredit-summary": "获得页面的贡献者列表。",
"apihelp-query+pagecredit-param-limit": "获取的贡献者数量限制"
}

@ -0,0 +1,13 @@
{
"isekaicontrib-name": "异世界百科 编写者信息",
"isekaicontrib-desc": "在页面上显示一个所有编写者的信息框",
"isekaicontrib-sidebar-contrib": "编写者",
"isekaicontrib-viewall": "查看所有编写者",
"isekai-contrib": "编写者",
"isekaicontrib-dialog-title": "本页面的编写者",
"isekaicontrib-dialog-cancel": "关闭",
"isekaicontrib-dialog-subtitle-creator": "创建者",
"isekaicontrib-dialog-subtitle-last-editor": "最后编写者",
"isekaicontrib-dialog-subtitle-contributors": "其他编写者"
}

@ -0,0 +1,91 @@
<?php
namespace Isekai\Contributors\Api;
use ApiBase;
use ApiQueryBase;
use ApiQuery;
use Config;
use MediaWiki\MediaWikiServices;
use WANObjectCache;
use Wikimedia\ParamValidator\ParamValidator;
use Exception;
use Isekai\Contributors\PageCreditHelper;
class ApiPageCredit extends ApiQueryBase {
private const CACHE_VERSION = 2;
private const PREFIX = 'pc';
private $params;
/**
* @var Config
*/
private $config;
/**
* @var WANObjectCache
*/
private $cache;
/**
* @var PageCreditHelper
*/
private $helper;
/**
* @param ApiQuery $query API query module object
* @param string $moduleName Name of this query module
*/
public function __construct($query, $moduleName) {
parent::__construct($query, $moduleName, self::PREFIX);
$this->config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'textextracts' );
$this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$this->helper = new PageCreditHelper($this->getContext());
}
public function execute() {
$titles = $this->getPageSet()->getGoodTitles();
if (empty($titles)) {
return;
}
$isXml = $this->getMain()->isInternalMode()
|| $this->getMain()->getPrinter()->getFormat() == 'XML';
$result = $this->getResult();
$params = $this->params = $this->extractRequestParams();
$limit = intval($params['limit']);
$limit = ($limit < 0) ? false : $limit;
foreach ($titles as $id => $title) {
try {
$credit = $this->helper->getContributors($title, $limit);
if ($isXml) {
$result->addValue(['query', 'pages', $id], 'pagecredit', ['*' => $credit]);
} else {
$result->addValue(['query', 'pages', $id], 'pagecredit', $credit);
}
} catch(Exception $e){
$result->addValue(['query', 'pages', $id], 'pagecredit', ['error' => $e->getTraceAsString()]);
}
}
}
/**
* @param array $params Ignored parameters
* @return string
*/
public function getCacheMode($params) {
return 'public';
}
public function getAllowedParams() {
return [
'limit' => [
ParamValidator::PARAM_DEFAULT => -1,
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MIN => -1,
ApiBase::PARAM_MAX => 100,
]
];
}
}

@ -0,0 +1,86 @@
<?php
namespace Isekai\Contributors;
use Html;
use IContextSource;
use MediaWiki\MediaWikiServices;
use Title;
class ContributorsBox {
private $context;
private $title;
private $helper;
private $contributors;
public function __construct(IContextSource $context, Title $title){
$this->context = $context;
$this->title = $title;
$this->helper = new PageCreditHelper($context);
$this->contributors = $this->helper->getContributors($this->title, 5);
}
public function getSideboxPanelHtml(){
global $wgIsekaiContributorAvatar;
if(empty($this->contributors)) return '';
$userList = array_merge([$this->contributors['last_editor']], [$this->contributors['creator']],
$this->contributors['contributors']);
$userHtml = [];
$exportedUser = [];
foreach ($userList as $user){
if(!in_array($user['user_name'], $exportedUser)) {
$displayName = $user['display_name'];
if($user['user_name'] != $displayName) $displayName .= ' [@' . $user['user_name'] . ']';
$userHtml[] = Html::element('a', [
'href' => $user['user_page']->getLinkURL(),
'target' => '_blank',
'title' => $displayName,
'style' => "background-image: url('" . sprintf($wgIsekaiContributorAvatar, urlencode($user['user_name'])) . "')",
]);
$exportedUser[] = $user['user_name'];
}
}
$userHtml[] = Html::element('a', [
'href' => 'javascript:;',
'title' => wfMessage('isekaicontrib-viewall')->text(),
'class' => 'isekai-img-more-contrib isekai-contrib-open-dialog',
]);
return Html::openElement('div', ['class' => 'isekai-contrib-sidebox-panel']) .
Html::openElement('div', ['class' => 'isekai-contrib-panel']) .
implode('', $userHtml) . Html::closeElement('div') . Html::closeElement('div');
}
public function getTopPanelHtml(){
global $wgIsekaiContributorAvatar;
if(empty($this->contributors)) return '';
$userList = array_merge([$this->contributors['last_editor']], [$this->contributors['creator']],
$this->contributors['contributors']);
$userHtml = [
Html::openElement('div', ['class' => 'avatar-zone']),
Html::element('span', ['class' => 'isekai-contrib-panel-title'], wfMessage('isekai-contrib')->text())
];
$exportedUser = [];
foreach ($userList as $user){
if(!in_array($user['user_name'], $exportedUser)) {
$displayName = $user['display_name'];
if($user['user_name'] != $displayName) $displayName .= ' [@' . $user['user_name'] . ']';
$userHtml[] = Html::element('a', [
'href' => $user['user_page']->getLinkURL(),
'target' => '_blank',
'title' => $displayName,
'style' => "background-image: url('" . sprintf($wgIsekaiContributorAvatar, urlencode($user['user_name'])) . "')",
]);
$exportedUser[] = $user['user_name'];
}
}
$userHtml[] = Html::closeElement('div');
$userHtml[] = Html::element('div', ['class' => 'spacer']);
$userHtml[] = Html::element('a', [
'href' => 'javascript:;',
'title' => wfMessage('isekaicontrib-viewall')->text(),
'class' => 'isekai-img-more-contrib isekai-contrib-open-dialog',
]);
return Html::openElement('div', ['class' => 'isekai-contrib-top-panel isekai-contrib-panel']) .
implode('', $userHtml) . Html::closeElement('div');
}
}

@ -0,0 +1,64 @@
<?php
namespace Isekai\Contributors;
use OutputPage;
use Skin;
use Config;
use Title;
use PageProps;
use Exception;
use MediaWiki\MediaWikiServices;
class Hooks {
public static function onBeforePageDisplay(OutputPage $outputPage){
$outputPage->addModuleStyles('ext.isekai.contrib.styles');
$outputPage->addModules(['ext.isekai.contrib.images', 'ext.isekai.contrib.dialog']);
}
public static function onSidebarBeforeOutput(Skin $skin, array &$sidebar){
if(!$skin->getOutput()->isArticle()) return;
$title = $skin->getTitle();
if($title && !in_array($title->getNamespace(), [NS_MAIN, NS_CATEGORY, NS_FILE, NS_HELP, NS_PROJECT])){
return;
}
try {
$sidebar['isekai-contrib'] = (new ContributorsBox($skin->getContext(), $title))->getSideboxPanelHtml();
} catch(Exception $e){
}
}
public static function onSkinTemplateOutputPageBeforeExec(\SkinTemplate &$skin, \QuickTemplate &$template){
if(!$skin->getOutput()->isArticle()) return;
$title = $skin->getTitle();
if($title && !in_array($title->getNamespace(), [NS_MAIN, NS_CATEGORY, NS_FILE, NS_HELP, NS_PROJECT])){
return;
}
$props = self::getPageProp($title);
if(!isset($props['nocreditbox'])){
try {
$template->extend('subtitle', (new ContributorsBox($skin->getContext(), $title))->getTopPanelHtml());;
} catch(Exception $e){
}
}
}
public static function onGetDoubleUnderscoreIDs(array &$ids){
$ids[] = 'nocreditbox';
}
public static function onResourceLoaderGetConfigVars(array &$vars, string $skin, Config $config){
$vars['wgIsekaiContributorAvatar'] = $config->get('IsekaiContributorAvatar');
}
public static function getPageProp(Title $title){
$id = $title->getArticleID();
$props = PageProps::getInstance()->getAllProperties( $title );
return $props[$id] ?? [];
}
}

@ -0,0 +1,79 @@
<?php
namespace Isekai\Contributors;
use Article;
use MediaWiki\MediaWikiServices;
use SpecialPage;
use Title;
use User;
class PageCreditHelper {
private $cache;
private $context;
public function __construct($context){
$this->cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$this->context = $context;
}
public function getContributors(Title $title, $limit = false){
return $this->cache->getWithSetCallback(
$this->cache->makeKey('isekai_page_credit', $title->getArticleID(), $title->getLatestRevID()),
$this->cache::TTL_MINUTE * 10,
function() use($title, $limit){
$wikiPage = Article::newFromTitle($title, $this->context)->getPage();
if(!$wikiPage->exists()) return [];
$creator = $wikiPage->getCreator();
$contributors = $wikiPage->getContributors();
$lastEditor = User::newFromName($wikiPage->getUserText());
$contributorInfo = [];
$count = 0;
foreach ($contributors as $user) {
$contributorInfo[] = $this->getUserInfo($user);
$count++;
if ($limit !== false && $count > $limit) {
break;
}
}
return [
'creator' => $this->getUserInfo($creator),
'last_editor' => $this->getUserInfo($lastEditor),
'contributors' => $contributorInfo,
'count' => $contributors->count(),
];
}
);
}
public function onPageEdit(Title $title){
$this->cache->delete($this->cache->makeKey('isekai_page_credit', $title->getArticleID(), $title->getLatestRevID()));
}
public function getUserInfo(User $user){
$userInfo = [];
if ($user->getRealName()) {
$userInfo['display_name'] = $user->getRealName();
} else {
$userInfo['display_name'] = $user->getName();
}
$userInfo['user_name'] = $user->getName();
$userInfo['user_page'] = $this->getUserPage($user);
return $userInfo;
}
/**
* Get a link to $user's user page
* @param User $user
* @return string Html
*/
protected function getUserPage( User $user ) {
$page = $user->isAnon()
? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
: $user->getUserPage();
return $page;
}
}

@ -0,0 +1,130 @@
if (!('isekai' in window)) window.isekai = {};
/**
* @class isekai.ContribDialog
* @param {*} config
*/
isekai.ContribDialog = function IsekaiContribDialog(config) {
isekai.ContribDialog.super.call(this, config);
this.broken = false;
};
OO.inheritClass(isekai.ContribDialog, OO.ui.ProcessDialog);
isekai.ContribDialog.static.name = 'isekaicontrib-dialog';
isekai.ContribDialog.static.title = mw.msg('isekaicontrib-dialog-title');
isekai.ContribDialog.static.actions = [
{ action: 'cancel', label: mw.msg('isekaicontrib-dialog-cancel'), flags: ['safe', 'close'] }
];
isekai.ContribDialog.prototype.initialize = function () {
isekai.ContribDialog.super.prototype.initialize.apply(this, arguments);
this.api = new mw.Api();
this.content = new OO.ui.PanelLayout({ padded: false });
//加载动画
this.loadingWidget = new OO.ui.ProgressBarWidget({
progress: false,
classes: ['isekai-contrib-loading']
});
this.content.$element.append(this.loadingWidget.$element);
//贡献者列表容器
this.content.$element.append('<div class="isekai-contrib-list"></div>');
this.contribContainer = this.content.$element.find('.isekai-contrib-list');
this.$body.append(this.content.$element);
}
isekai.ContribDialog.prototype.getUserAvatar = function (userName) {
var template = mw.config.get('wgIsekaiContributorAvatar');
if (template) {
return template.replace(/\%s/g, userName);
} else {
return '';
}
};
/**
* 生成用户栏目html
* @param {any} userData - 用户信息
* @returns {string} 生成的html
*/
isekai.ContribDialog.prototype.getUserListItem = function (userData) {
return '<a class="isekai-list-item" href="' + mw.util.getUrl(userData.user_page) + '" target="_blank">' +
'<div class="isekai-list-item-avatar"><img src="' + this.getUserAvatar(userData.user_name) + '"></div>' +
'<div class="isekai-list-item-content">' +
'<div class="isekai-list-item-title">' + userData.display_name + '</div>' +
'<div class="isekai-list-item-text">@' + userData.user_name + '</div>' +
'</div>' +
'</a>';
};
/**
* 加载贡献者列表
*/
isekai.ContribDialog.prototype.loadContributors = function () {
var currentPageId = mw.config.get('wgArticleId');
this.api.get({
action: 'query',
prop: 'pagecredit',
pageids: currentPageId,
}).done((data) => {
if('query' in data && 'pages' in data.query){
if(currentPageId in data.query.pages){
var pageData = data.query.pages[currentPageId];
if('missing' in pageData) return;
if(!pageData.pagecredit || 'error' in pageData.pagecredit) return;
var creditData = pageData.pagecredit;
this.loadingWidget.toggle(false);
this.contribContainer.empty();
if('creator' in creditData){
this.contribContainer.append('<div class="isekai-list-item-sub">' + mw.msg('isekaicontrib-dialog-subtitle-creator') + '</div>');
this.contribContainer.append(this.getUserListItem(creditData.creator));
}
if('last_editor' in creditData){
this.contribContainer.append('<div class="isekai-list-item-sub">' + mw.msg('isekaicontrib-dialog-subtitle-last-editor') + '</div>');
this.contribContainer.append(this.getUserListItem(creditData.last_editor));
}
if(('contributors' in creditData) && creditData.contributors.length > 0){
this.contribContainer.append('<div class="isekai-list-item-sub">' + mw.msg('isekaicontrib-dialog-subtitle-contributors') + '</div>');
creditData.contributors.forEach((editor) => {
this.contribContainer.append(this.getUserListItem(editor));
this.contribContainer.append('<hr class="isekai-list-item-divider">');
});
}
}
}
});
};
isekai.ContribDialog.prototype.getBodyHeight = function () {
return 400;
};
isekai.ContribDialog.prototype.getSetupProcess = function (data) {
var _this = this;
return isekai.ContribDialog.super.prototype.getSetupProcess.call(this, data).next(function(){
_this.loadContributors();
return true;
});
}
isekai.ContribDialog.prototype.getActionProcess = function (action) {
var dialog = this;
return new OO.ui.Process(function () {
dialog.close({ action: action });
});
};
// ==============================================================
var contribDialog = new isekai.ContribDialog({});
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
windowManager.addWindows([contribDialog]);
$('.isekai-contrib-open-dialog').click(function () {
windowManager.openWindow(contribDialog);
return false;
});

@ -0,0 +1,109 @@
.isekai-contrib-loading {
margin: 20px 16px;
}
.isekai-contrib-list {
margin: 0;
padding: 0 0 8px 0;
list-style: none;
background-color: transparent;
.isekai-list-item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
min-height: 48px;
padding: 0 16px;
text-decoration: none;
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,.08);
}
}
a {
color: #000;
text-decoration: none;
}
.isekai-list-item-sub {
background-color: #e6e6e6;
color: #000;
padding-left: 20px;
min-height: 28px;
font-size: 17px;
font-weight: 600;
align-items: center;
justify-content: space-between;
overflow: hidden;
}
.isekai-list-item-divider {
background-color: rgba(0,0,0,.12);
height: 1px;
margin: 0 0 0 72px;
border: none;
&:last-child {
display: none;
}
}
.isekai-list-item-avatar {
min-width: 40px;
max-width: 40px;
height: 40px;
margin-top: 8px;
margin-bottom: 8px;
color: #fff;
line-height: 40px;
text-align: center;
img {
width: 100%;
height: 100%;
border-radius: 4px;
}
&~.isekai-list-item-content {
margin-left: 16px;
}
}
.isekai-list-item-secondary {
margin-left: 4px;
color: #777;
}
.isekai-list-item-content {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
padding-top: 14px;
padding-bottom: 14px;
font-weight: 400;
font-size: 16px;
line-height: 20px;
}
.isekai-list-item-title~.isekai-list-item-text {
margin-top: 4px;
}
.isekai-list-item-text {
font-size: 14px;
opacity: .54;
-webkit-line-clamp: 1;
height: 20px;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
}
}

@ -0,0 +1,67 @@
.isekai-contrib-panel {
text-align: left;
line-height: 0;
a {
margin: 3px 2px;
display: inline-block;
width: 32px;
height: 32px;
border-radius: 3px;
background-size: contain;
}
}
.isekai-contrib-top-panel {
padding: .25rem 0.5rem;
margin-bottom: 0;
background-color: rgba(0,0,0,0.03);
display: flex;
align-items: center;
border-radius: 0.25rem;
border: 1px solid rgba(0,0,0,0.125);
.spacer {
height: 100%;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
margin: 0;
}
.isekai-contrib-panel-title {
font-weight: 500;
font-size: 18px;
padding: 0 10px;
line-height: 30px;
display: inline-block;
height: 32px;
margin: 3px 0;
color: #777;
}
.isekai-contrib-open-dialog {
flex-shrink: 0;
}
* {
overflow: hidden;
white-space: nowrap;
}
}
@media screen and (min-width: 1100px) {
.isekai-contrib-top-panel {
display: none;
}
}
.isekai-contrib-sidebox-panel {
padding-bottom: 10px;
min-width: 150px;
max-width: 180px;
}
.skin-timeless .isekai-contrib-sidebox-panel {
margin-top: -8px;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1 @@
<svg id="e1c67992-10f2-4b29-a57d-cb41e8ffbd1d" data-name="pt" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.b4d6abb5-d0c3-464f-a432-d7852fd7ae6f{fill:#ccc;}.b79d9a3f-d2af-4218-91ec-f36af9a01dfe{fill:#fff;}</style></defs><rect class="b4d6abb5-d0c3-464f-a432-d7852fd7ae6f" width="100" height="100"/><circle class="b79d9a3f-d2af-4218-91ec-f36af9a01dfe" cx="49.53" cy="50.06" r="8.77"/><circle class="b79d9a3f-d2af-4218-91ec-f36af9a01dfe" cx="18.84" cy="50.06" r="8.77"/><circle class="b79d9a3f-d2af-4218-91ec-f36af9a01dfe" cx="80.23" cy="50.06" r="8.77"/></svg>

After

Width:  |  Height:  |  Size: 583 B

Loading…
Cancel
Save