diff --git a/IsekaiWidgets.i18n.php b/IsekaiWidgets.i18n.php index d803955..5403fcb 100644 --- a/IsekaiWidgets.i18n.php +++ b/IsekaiWidgets.i18n.php @@ -3,5 +3,6 @@ $magicWords = [ 'en' => [ 'htmldetails' => [0, 'htmldetails'], 'htmlsummary' => [0, 'htmlsummary'], + 'information' => [0, 'information'] ], ]; \ No newline at end of file diff --git a/extension.json b/extension.json index 25f34f7..db32684 100644 --- a/extension.json +++ b/extension.json @@ -1,5 +1,5 @@ { - "name": "Isekai Discover Box", + "name": "Isekai Widgets", "namemsg": "isekai-widgets", "author": "Hyperzlib", "version": "1.0.3", @@ -29,6 +29,10 @@ "ext.isekai.widgets.global": { "styles": [ "ext.isekai.widgets.global.less" + ], + "targets": [ + "desktop", + "mobile" ] }, "ext.isekai.createPage": { @@ -56,42 +60,43 @@ }, "ext.isekai.discover": { "scripts": [ - "feedList/ext.isekai.feedList.js" + "discover/ext.isekai.discover.js", + "discover/ext.isekai.discover.base.js" ], "styles": [ - "feedList/ext.isekai.feedList.less" + "discover/ext.isekai.discover.base.less" ], "dependencies": [ "oojs", "oojs-ui-core", - "vue" + "oojs-ui.styles.icons-interactions" ], "targets": [ "desktop", "mobile" + ], + "messages": [ + "isekai-discover-change-btn", + "isekai-discover-readmore-btn", + "isekai-discover-error-cannotload" ] }, "ext.isekai.feedList": { "scripts": [ - "discover/ext.isekai.discover.js", - "discover/ext.isekai.discover.base.js" + "feedList/ext.isekai.feedList.js" ], "styles": [ - "discover/ext.isekai.discover.base.less" + "feedList/ext.isekai.feedList.less" ], "dependencies": [ "oojs", "oojs-ui-core", - "oojs-ui.styles.icons-interactions" + "oojs-ui.styles.icons-movement", + "vue" ], "targets": [ "desktop", "mobile" - ], - "messages": [ - "isekai-discover-change-btn", - "isekai-discover-readmore-btn", - "isekai-discover-error-cannotload" ] }, "ext.isekai.previewCard": { @@ -149,5 +154,10 @@ } } }, + "config": { + "IsekaiWidgetInformationTextSeparator": { + "value": ": " + } + }, "manifest_version": 2 } \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json index 9e4b305..f5c879c 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -23,5 +23,7 @@ "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-font-error-font-not-imported": "Font \"$1\" not imported.", + + "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 ca2d425..ec19021 100644 --- a/i18n/zh-hans.json +++ b/i18n/zh-hans.json @@ -25,5 +25,7 @@ "isekai-font-error-invalid-params": "请提供name参数。", "isekai-font-error-font-name-invalid": "字体名中不能包含特殊字符。", - "isekai-font-error-font-not-imported": "未导入字体: \"$1\"。" + "isekai-font-error-font-not-imported": "未导入字体: \"$1\"。", + + "isekai-information-error-invalid-type": "提供的信息框类型错误" } \ No newline at end of file diff --git a/i18n/zh-hant.json b/i18n/zh-hant.json index 79c6789..9cb438f 100644 --- a/i18n/zh-hant.json +++ b/i18n/zh-hant.json @@ -23,5 +23,7 @@ "isekai-font-error-invalid-params": "請提供name參數。", "isekai-font-error-font-name-invalid": "字體名中不能包含特殊字元。", - "isekai-font-error-font-not-imported": "未導入字體: \"$1\"。" + "isekai-font-error-font-not-imported": "未導入字體: \"$1\"。", + + "isekai-information-error-invalid-type": "提供的信息框類型錯誤" } \ No newline at end of file diff --git a/includes/CreatePageWidget.php b/includes/CreatePageWidget.php index fb03520..5abb730 100644 --- a/includes/CreatePageWidget.php +++ b/includes/CreatePageWidget.php @@ -9,8 +9,8 @@ class CreatePageWidget { return [$template, "markerType" => 'nowiki']; } - public static function create($text, $params, $parser, $frame){ - $parser->getOutput()->addModules('ext.isekai.createPage'); + public static function create($text, $params, \Parser $parser, \PPFrame $frame){ + $parser->getOutput()->addModules(['ext.isekai.createPage']); return self::getHtml(); } diff --git a/includes/DiscoverWidget.php b/includes/DiscoverWidget.php index 4f1e09d..8e6e650 100644 --- a/includes/DiscoverWidget.php +++ b/includes/DiscoverWidget.php @@ -9,8 +9,8 @@ class DiscoverWidget { return [$template, "markerType" => 'nowiki']; } - public static function create($text, $params, \Parser $parser, $frame){ - $parser->getOutput()->addModules('ext.isekai.discover'); + public static function create($text, $params, \Parser $parser, \PPFrame $frame){ + $parser->getOutput()->addModules(['ext.isekai.discover']); return self::getHtml(); } diff --git a/includes/ExtraFontWidget.php b/includes/ExtraFontWidget.php index f6ee07f..95d47c8 100644 --- a/includes/ExtraFontWidget.php +++ b/includes/ExtraFontWidget.php @@ -4,11 +4,11 @@ namespace Isekai\Widgets; use Html; class ExtraFontWidget { - public static function create($text, $params, $parser, $frame){ + public static function create($text, $params, \Parser $parser, \PPFrame $frame){ $existsFonts = $parser->extIsekaiWidgetsCache->get('extraFonts', INF, []); $content = $text = $parser->recursiveTagParse($text, $frame); - if (!isset($params['name']) || empty($params['name'])) { + if (empty($params['name'])) { return '' . wfMessage('isekai-font-error-invalid-params')->parse() . '' . $content; } diff --git a/includes/FeedListWidget.php b/includes/FeedListWidget.php index 0883562..da1819a 100644 --- a/includes/FeedListWidget.php +++ b/includes/FeedListWidget.php @@ -9,8 +9,8 @@ class FeedListWidget { return [$template, "markerType" => 'nowiki']; } - public static function create($text, $params, \Parser $parser, $frame){ - $parser->getOutput()->addModules('ext.isekai.feedList'); + public static function create($text, $params, \Parser $parser, \PPFrame $frame){ + $parser->getOutput()->addModules(['ext.isekai.feedList']); return self::getHtml(); } diff --git a/includes/FontFaceWidget.php b/includes/FontFaceWidget.php index 4af1334..2d1a410 100644 --- a/includes/FontFaceWidget.php +++ b/includes/FontFaceWidget.php @@ -10,10 +10,10 @@ class FontFaceWidget { * @param array $params * @param \Parser $parser * @param \PPFrame $frame + * @return string|string[] */ - public static function create($text, $params, $parser, $frame) { - if (!isset($params['src']) || !isset($params['name']) || - empty($params['src']) || empty($params['name'])) { + public static function create($text, $params, \Parser $parser, \PPFrame $frame) { + if (empty($params['src']) || empty($params['name'])) { return '' . wfMessage('isekai-fontface-error-invalid-params')->parse() . ''; } diff --git a/includes/Html5Widget.php b/includes/Html5Widget.php index 028622d..20e45b0 100644 --- a/includes/Html5Widget.php +++ b/includes/Html5Widget.php @@ -5,7 +5,7 @@ use Html; class Html5Widget { public static function createDetails(string $text, array $args, \Parser $parser, \PPFrame $frame) { - $parser->getOutput()->addModules('ext.isekai.collapse'); + $parser->getOutput()->addModules(['ext.isekai.collapse']); $allowedAttr = ['class']; $htmlArgs = array_filter($args, function($k) use($allowedAttr) { return in_array($k, $allowedAttr); diff --git a/includes/InformationWidget.php b/includes/InformationWidget.php new file mode 100644 index 0000000..8e2d3a4 --- /dev/null +++ b/includes/InformationWidget.php @@ -0,0 +1,140 @@ + $line) { + // 静态文本数据 + $sep = Utils::strContains($line, [':', ':']); + if ($sep) { + list($key, $value) = Utils::getKeyValue($sep, $line); + $data = [ + 'type' => 'pair', + 'label' => $key, + 'text' => $value, + ]; + $prevData = &$data; + $finalData[] = &$data; + continue; + } + + // 动态文本数据 + $sep = Utils::strContains($line, ['=']); + if ($sep) { + list($key, $value) = Utils::getKeyValue($sep, $line); + if (isset($dataMap[$value])) { + $data = [ + 'type' => 'pair', + 'label' => $key, + 'text' => $dataMap[$value], + ]; + } else { + $data = [ + 'type' => 'pair', + 'label' => $key, + 'text' => '#' . $value, + ]; + } + $prevData = &$data; + $finalData[] = &$data; + continue; + } + + // 多行数据,附加到上一行 + if (preg_match('/^[ \t]+/', $line) && $prevData && isset($prevData['text'])) { + $prevData['text'] .= "\n\n" . trim($line); + continue; + } + + if ($lineNum === 0) { + $title = trim($line); + continue; + } + + // 归类为分栏数据 + $data = [ + 'type' => 'banner', + 'text' => trim($line) + ]; + $prevData = &$data; + $finalData[] = &$data; + } + return [$finalData, $title]; + } + + public static function buildText(\Parser $parser, \PPFrame $frame, array $dataMap, $title, $picture, $float) { + $config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig('IsekaiWidget'); + $sep = $config->get('IsekaiWidgetInformationTextSeparator'); + + $stringBuilder = []; + foreach ($dataMap as $information) { + if ($information['type'] === 'pair') { + $stringBuilder[] = $information['label'] . $sep . + Utils::makeParagraph($parser->recursiveTagParse($information['text'], $frame), false, true); + } + } + return [implode('', $stringBuilder), 'markerType' => 'nowiki']; + } + + public static function buildTable(\Parser $parser, \PPFrame $frame, array $dataMap, $title, $picture, $float) { + + } + + /** + * @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']; + $configArgs = []; + $infoArgs = []; + + foreach ($args as $key => $value) { + if (in_array($key, $configKeys)) { + $configArgs[$key] = $value; + } else { + $infoArgs[$key] = $value; + } + } + + $type = $configArgs['type'] ?? 'text'; + $picture = $configArgs['picture'] ?? null; + $float = $configArgs['float'] ?? ''; + + $titleKey = $configArgs['title_key'] ?? null; + $title = null; + + // 文本模式中,没有title + if ($type === 'text') { + $titleKey = null; + } + + $dataMap = []; + foreach ($infoArgs as $key => $value) { + if ((is_int($key) && !$titleKey) || ($titleKey && $key === $titleKey)) { + $title = $value; + unset($dataMap[$key]); + } else { + $dataMap[$key] = $value; + } + } + if (isset($configArgs['content'])) { + list($dataMap, $title) = static::parseContent($configArgs['content'], $dataMap, $title); + } + + switch ($type) { + case 'text': + return static::buildText($parser, $frame, $dataMap, $title, $picture, $float); + default: + return '' . wfMessage('isekai-information-error-invalid-type')->parse() . ''; + } + } +} \ No newline at end of file diff --git a/includes/PreviewCardWidget.php b/includes/PreviewCardWidget.php index 6d4705a..904ccef 100644 --- a/includes/PreviewCardWidget.php +++ b/includes/PreviewCardWidget.php @@ -10,8 +10,8 @@ class PreviewCardWidget { return [$template, "markerType" => 'nowiki']; } - public static function create($text, $params, $parser, $frame){ - $parser->getOutput()->addModules('ext.isekai.previewCard'); + public static function create($text, $params, \Parser $parser, \PPFrame $frame) { + $parser->getOutput()->addModules(['ext.isekai.previewCard']); $titleChunk = explode('/', $text); $len = count($titleChunk); diff --git a/includes/TileWidget.php b/includes/TileWidget.php index f4b86f9..24b5342 100644 --- a/includes/TileWidget.php +++ b/includes/TileWidget.php @@ -24,7 +24,7 @@ class TileWidget { } public static function create(string $text, array $args, \Parser $parser, \PPFrame $frame){ - $parser->getOutput()->addModules('ext.isekai.tile'); + $parser->getOutput()->addModules(['ext.isekai.tile']); $content = ''; if ($text) { diff --git a/includes/Utils.php b/includes/Utils.php index 2ef555f..2f8257e 100644 --- a/includes/Utils.php +++ b/includes/Utils.php @@ -6,7 +6,7 @@ class Utils { return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($input)); } - public static function makeParagraph($text, $hasUniq = false) { + public static function makeParagraph($text, $hasUniq = false, $indent = false) { $text = str_replace("\r\n", "\n", $text); if (strpos($text, "\n\n") === false) { return $text; @@ -29,7 +29,49 @@ class Utils { $text = substr($text, strlen($matches[0])); } } + $lines = explode("\n\n", $text); - return $prepend . "
" . implode("
\n", $lines) . "
" . $append; + + $stringBuilder = [$prepend]; + foreach ($lines as $lineNum => $line) { + if ($lineNum > 0 && $indent) { + $elemAttr = [ + 'class' => 'paragraph-indent' + ]; + } else { + $elemAttr = []; + } + $stringBuilder[] = \Html::rawElement('p', $elemAttr, $line); + } + $stringBuilder[] = $append; + return implode('', $stringBuilder); + } + + public static function strContains(string $haystack, array $needle) { + foreach ($needle as $one) { + if (strpos($haystack, $one) !== false) { + return $one; + } + } + return false; + } + + public static function trimEach(array $arr) { + foreach ($arr as $key => $value) { + $arr[$key] = trim($value); + } + return $arr; + } + + public static function getKeyValue(string $separator, string $str) { + $sepLen = strlen($separator); + $sepOffset = strpos($str, $separator); + if ($sepOffset === false) { + return [null, $str]; + } else { + $key = trim(substr($str, 0, $sepOffset)); + $value = trim(substr($str, $sepOffset + $sepLen)); + return [$key, $value]; + } } } \ No newline at end of file diff --git a/includes/Widgets.php b/includes/Widgets.php index 72bd38e..5b9cd5b 100644 --- a/includes/Widgets.php +++ b/includes/Widgets.php @@ -7,6 +7,8 @@ use Parser; class Widgets { /** * @param \Parser $parser + * @return bool + * @throws \MWException */ public static function onParserSetup(&$parser){ $parser->extIsekaiWidgetsCache = new MapCacheLRU( 100 ); // 100 is arbitrary @@ -29,7 +31,9 @@ class Widgets { } public static function onLoad(\OutputPage $outputPage) { - $outputPage->addModuleStyles("ext.isekai.widgets.global"); - $outputPage->addModuleStyles("ext.isekai.collapse"); + $outputPage->addModuleStyles([ + "ext.isekai.widgets.global", + "ext.isekai.collapse" + ]); } } \ No newline at end of file diff --git a/modules/discover/ext.isekai.discover.base.less b/modules/discover/ext.isekai.discover.base.less index 3d1708e..c20c296 100644 --- a/modules/discover/ext.isekai.discover.base.less +++ b/modules/discover/ext.isekai.discover.base.less @@ -1,4 +1,5 @@ @height: 20rem; +@mobile-height: 60vh; @text-size: 0.85rem; .discover-card { @@ -65,6 +66,16 @@ margin: 0 0.4rem; font-size: @text-size; } + + @media (max-width: 850px) { + .loading { + height: @mobile-height; + } + + .card-content { + height: @mobile-height; + } + } } &.discover-card-zh { diff --git a/modules/ext.isekai.widgets.global.less b/modules/ext.isekai.widgets.global.less index e691b82..8e0ae53 100644 --- a/modules/ext.isekai.widgets.global.less +++ b/modules/ext.isekai.widgets.global.less @@ -16,6 +16,7 @@ border-bottom: 1px solid rgba(0,0,0,.125); display: flex; align-items: center; + justify-content: space-between; &:first-child { border-radius: calc(.25rem - 1px) calc(.25rem - 1px) 0 0; @@ -25,7 +26,7 @@ font-size: 1.25rem; } - .card-header-buttons { + .card-header-extra { margin-left: auto; } } diff --git a/modules/feedList/ext.isekai.feedList.less b/modules/feedList/ext.isekai.feedList.less index 6728fe8..97368bc 100644 --- a/modules/feedList/ext.isekai.feedList.less +++ b/modules/feedList/ext.isekai.feedList.less @@ -1,4 +1,5 @@ @feed-list-height: 24rem; +@feed-list-height-mobile: 70vh; .isekai-feed-list-card > .card-header { height: 2.25rem; @@ -27,6 +28,10 @@ width: 100%; } } + + @media (max-width: 850px) { + height: @feed-list-height-mobile; + } } .isekai-list { @@ -42,6 +47,7 @@ -webkit-box-align: center; -ms-flex-align: center; align-items: center; + gap: 0.25rem; -webkit-box-sizing: border-box; box-sizing: border-box; min-height: 3rem; @@ -68,8 +74,8 @@ -webkit-box-flex: 1; -ms-flex-positive: 1; flex-grow: 1; - padding-top: 0.875rem; - padding-bottom: 0.875rem; + padding-top: 0.625rem; + padding-bottom: 0.625rem; font-weight: 400; font-size: 1rem; line-height: 1.25rem; diff --git a/modules/feedList/ext.isekai.feedList.tpl b/modules/feedList/ext.isekai.feedList.tpl index 5467309..d388ea4 100644 --- a/modules/feedList/ext.isekai.feedList.tpl +++ b/modules/feedList/ext.isekai.feedList.tpl @@ -17,6 +17,9 @@