diff --git a/extension.json b/extension.json index db32684..5d6923c 100644 --- a/extension.json +++ b/extension.json @@ -99,6 +99,15 @@ "mobile" ] }, + "ext.isekai.information.infobox": { + "styles": [ + "information/ext.isekai.information.infobox.less" + ], + "targets": [ + "desktop", + "mobile" + ] + }, "ext.isekai.previewCard": { "scripts": [ "previewCard/ext.isekai.previewCard.js" diff --git a/i18n/en.json b/i18n/en.json index f5c879c..8a8d230 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -24,6 +24,6 @@ "isekai-font-error-invalid-params": "Please specify name attributes.", "isekai-font-error-font-name-invalid": "Font name cannot contain special characters.", "isekai-font-error-font-not-imported": "Font \"$1\" not imported.", - + "isekai-information-title-base-information": "Base Data", "isekai-information-error-invalid-type": "Information \"type\" unsupported." } \ No newline at end of file diff --git a/i18n/zh-hans.json b/i18n/zh-hans.json index ec19021..45e5047 100644 --- a/i18n/zh-hans.json +++ b/i18n/zh-hans.json @@ -27,5 +27,6 @@ "isekai-font-error-font-name-invalid": "字体名中不能包含特殊字符。", "isekai-font-error-font-not-imported": "未导入字体: \"$1\"。", + "isekai-information-title-base-information": "基本资料", "isekai-information-error-invalid-type": "提供的信息框类型错误" } \ No newline at end of file diff --git a/i18n/zh-hant.json b/i18n/zh-hant.json index 9cb438f..1ca06eb 100644 --- a/i18n/zh-hant.json +++ b/i18n/zh-hant.json @@ -25,5 +25,6 @@ "isekai-font-error-font-name-invalid": "字體名中不能包含特殊字元。", "isekai-font-error-font-not-imported": "未導入字體: \"$1\"。", + "isekai-information-title-base-information": "基本資料", "isekai-information-error-invalid-type": "提供的信息框類型錯誤" } \ No newline at end of file diff --git a/includes/InformationWidget.php b/includes/InformationWidget.php index bdfb34f..eb6b4e9 100644 --- a/includes/InformationWidget.php +++ b/includes/InformationWidget.php @@ -1,12 +1,13 @@ $line) { @@ -19,8 +20,8 @@ class InformationWidget { 'label' => $key, 'text' => $value, ]; - $prevData = &$data; - $finalData[] = &$data; + $finalData[] = $data; + $prevDataKey = count($finalData) - 1; continue; } @@ -28,6 +29,9 @@ class InformationWidget { $sep = Utils::strContains($line, ['=']); if ($sep) { list($key, $value) = Utils::getKeyValue($sep, $line); + if ($key === '') { // While text is '= key' + $key = $value; + } if (isset($dataMap[$value])) { $data = [ 'type' => 'pair', @@ -41,16 +45,18 @@ class InformationWidget { 'text' => '#' . $value, ]; } - $prevData = &$data; - $finalData[] = &$data; + $finalData[] = $data; + $prevDataKey = count($finalData) - 1; continue; } // 多行数据,附加到上一行 - if (preg_match('/^[ \t]+/', $line) && $prevData && isset($prevData['text'])) { - $prevData['text'] .= "\n\n" . trim($line); + /* 暂时仅支持
+ if (preg_match('/^[ \t]+/', $line) && $prevDataKey !== null) { + $finalData[$prevDataKey]['text'] .= "\n\n" . trim($line); continue; } + */ if ($lineNum === 0) { $title = trim($line); @@ -62,38 +68,99 @@ class InformationWidget { 'type' => 'banner', 'text' => trim($line) ]; - $prevData = &$data; - $finalData[] = &$data; + $finalData[] = $data; + $prevDataKey = count($finalData) - 1; } return [$finalData, $title]; } + + public static function parseMap($dataMap) { + $finalData = []; + foreach ($dataMap as $key => $value) { + $finalData[] = [ + 'type' => 'pair', + 'label' => $key, + 'text' => $value + ]; + } + return $finalData; + } public static function buildText(\Parser $parser, \PPFrame $frame, array $dataMap, $title, $picture, $float) { - $config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig('IsekaiWidget'); - $sep = $config->get('IsekaiWidgetInformationTextSeparator'); + global $wgIsekaiWidgetInformationTextSeparator; + $sep = $wgIsekaiWidgetInformationTextSeparator; - $stringBuilder = []; + $lines = []; foreach ($dataMap as $information) { if ($information['type'] === 'pair') { - $stringBuilder[] = $information['label'] . $sep . - Utils::makeParagraph($information['text'], false, true); + $lines[] = $information['label'] . $sep . $information['text']; } } - return [implode('', $stringBuilder), 'markerType' => 'nowiki']; + $html = implode("\n\n", $lines); + $html = str_replace("\n", "\r\n", $html); + $html = $parser->recursiveTagParseFully($html, $frame); + return [$html, 'markerType' => 'nowiki']; } - public static function buildTable(\Parser $parser, \PPFrame $frame, array $dataMap, $title, $picture, $float) { + public static function buildInfoBox(\Parser $parser, \PPFrame $frame, array $dataMap, $title, $picture, $float) { + $parser->getOutput()->addModules(['ext.isekai.information.infobox']); + $tableClasses = ['wikitable-container', 'infobox']; + if ($float === 'right') { + $tableClasses[] = 'infobox-float-right'; + } else if ($float === 'left') { + $tableClasses[] = 'infobox-float-left'; + } + $htmlBuilder = [ + Html::openElement('div', [ + 'class' => implode(' ', $tableClasses) + ]) . Html::openElement('table', [ + 'class' => 'wikitable' + ]) + ]; + if (is_string($title) && $title !== '') { + $htmlBuilder[] = Html::rawElement('thead', [], + Html::rawElement('th', ['colspan' => 2, 'class' => 'infobox-title'], + $parser->recursiveTagParse($title, $frame) + ) + ); + } + $htmlBuilder[] = Html::openElement('tbody'); + if (is_string($picture) && $picture !== '') { + $htmlBuilder[] = Html::rawElement('tr', [], + Html::rawElement('td', ['colspan' => 2, 'class' => 'infobox-picture'], $parser->recursiveTagParse("[[$picture|frameless]]", $frame)) + ); + } + foreach ($dataMap as $information) { + switch ($information['type']) { + case 'pair': + $htmlBuilder[] = Html::rawElement('tr', [], + Html::rawElement('td', [], $parser->recursiveTagParse($information['label'], $frame)) . + Html::rawElement('td', ['style' => 'text-align:center'], $parser->recursiveTagParse($information['text'], $frame)) + ); + break; + case 'banner': + $htmlBuilder[] = Html::rawElement('tr', [], + Html::rawElement('td', ['colspan' => 2, 'class' => 'infobox-banner'], $parser->recursiveTagParse($information['text'], $frame)) + ); + break; + + } + } + $htmlBuilder[] = Html::closeElement('tbody') . Html::closeElement('table') . Html::closeElement('div'); + $html = implode('', $htmlBuilder); + return [$html, 'markerType' => 'nowiki']; } /** + * @param string $content + * @param array $args * @param \Parser $parser * @param \PPFrame $frame - * @param $args * @return array|string */ - public static function create(\Parser $parser, \PPFrame $frame, $args) { - $configKeys = ['type', 'float', 'content', 'title_key', 'picture']; + public static function create(string $content, array $args, \Parser $parser, \PPFrame $frame) { + $configKeys = ['type', 'float', 'title_key', 'picture']; $configArgs = []; $infoArgs = []; @@ -105,16 +172,21 @@ class InformationWidget { } } - $type = $configArgs['type'] ?? 'text'; + $type = strtolower($configArgs['type']) ?? 'text'; $picture = $configArgs['picture'] ?? null; $float = $configArgs['float'] ?? ''; $titleKey = $configArgs['title_key'] ?? null; $title = null; - // 文本模式中,没有title if ($type === 'text') { + // 文本模式中,没有title $titleKey = null; + } else if ($type === 'infobox') { + // 信息框默认居右 + if ($float === '') { + $float = 'right'; + } } $dataMap = []; @@ -126,13 +198,23 @@ class InformationWidget { $dataMap[$key] = $value; } } - if (isset($configArgs['content'])) { - list($dataMap, $title) = static::parseContent($configArgs['content'], $dataMap, $title); + if (trim($content) !== '') { + list($dataMap, $title) = static::parseContent($content, $dataMap, $title); + } else { + $dataMap = static::parseMap($dataMap); + if ($type === 'infobox') { + array_unshift($dataMap, [ + 'type' => 'banner', + 'text' => wfMessage('isekai-information-title-base-information')->parse() + ]); + } } switch ($type) { case 'text': return static::buildText($parser, $frame, $dataMap, $title, $picture, $float); + case 'infobox': + return static::buildInfoBox($parser, $frame, $dataMap, $title, $picture, $float); default: return '' . wfMessage('isekai-information-error-invalid-type')->parse() . ''; } diff --git a/includes/Widgets.php b/includes/Widgets.php index 5cd102a..8e10a8c 100644 --- a/includes/Widgets.php +++ b/includes/Widgets.php @@ -27,7 +27,7 @@ class Widgets { $parser->setHook('details', [Html5Widget::class, 'createDetails']); $parser->setHook('summary', [Html5Widget::class, 'createSummary']); - $parser->setFunctionHook('information', [InformationWidget::class, 'create'], SFH_OBJECT_ARGS); + $parser->setHook('information', [InformationWidget::class, 'create']); return true; } @@ -35,6 +35,7 @@ class Widgets { public static function onLoad(\OutputPage $outputPage) { $outputPage->addModuleStyles([ "ext.isekai.widgets.global", + "ext.isekai.information.infobox", "ext.isekai.collapse" ]); } diff --git a/modules/information/ext.isekai.information.infobox.less b/modules/information/ext.isekai.information.infobox.less new file mode 100644 index 0000000..513fe6f --- /dev/null +++ b/modules/information/ext.isekai.information.infobox.less @@ -0,0 +1,52 @@ +.infobox { + @media (max-width: 767px) { + width: 100%; + } + + @media (min-width: 768px) { + &.infobox-float-left { + float: left; + } + &.infobox-float-right { + float: right; + } + } + + .infobox-title { + color: #fff; + background-color: #404244; + + ruby { + display: inline-flex; + flex-direction: column-reverse; + } + + rb, rt { + display: inline; + line-height: 1; + } + } + + .infobox-picture { + text-align: center; + + img { + max-width: 100%; + max-height: 350px; + } + } + + .infobox-banner { + text-align: center; + color: #000; + font-weight: bold; + background-color: #eaecf0; + } + + @media (prefers-color-scheme: dark) { + .infobox-banner { + color: #fff; + background-color: #202122; + } + } +} \ No newline at end of file diff --git a/modules/tile/style.less b/modules/tile/style.less index 8f4bd23..0c0920f 100644 --- a/modules/tile/style.less +++ b/modules/tile/style.less @@ -5,6 +5,10 @@ a { &.tile-large, &.tile-app { color: #fff; + + &:visited:hover { + color: #fff; + } } }