diff --git a/extension.json b/extension.json index 7a435db..69fb980 100644 --- a/extension.json +++ b/extension.json @@ -1,25 +1,26 @@ { "name": "KnowledgeGraph", - "version": "3.0.2", - "author": "Thomas-topway-it for [https://knowledge.wiki KM-A]", + "version": "3.1.0", + "author": [ + "Thomas-topway-it for [https://knowledge.wiki KM-A]", + "gesinn.it" + ], "url": "https://github.com/SemanticMediaWiki/KnowledgeGraph", "descriptionmsg": "knowledge-graph-desc", "namemsg": "knowledge-graph-name", - "type":"semantic", - "requires":{ + "type": "semantic", + "requires": { "MediaWiki": ">= 1.43" }, "MessagesDirs": { - "KnowledgeGraph":[ - "i18n" - ] + "KnowledgeGraph": ["i18n"] }, "ExtensionMessagesFiles": { "KnowledgeGraphMagic": "KnowledgeGraph.i18n.magic.php" }, "AutoloadClasses": { - "KnowledgeGraph":"includes/KnowledgeGraph.php", - "SpecialKnowledgeGraphDesigner":"includes/specials/SpecialKnowledgeGraphDesigner.php", + "KnowledgeGraph": "includes/KnowledgeGraph.php", + "SpecialKnowledgeGraphDesigner": "includes/specials/SpecialKnowledgeGraphDesigner.php", "KnowledgeGraphApiLoadNodes": "includes/api/KnowledgeGraphApiLoadNodes.php", "KnowledgeGraphApiLoadProperties": "includes/api/KnowledgeGraphApiLoadProperties.php", "KnowledgeGraphApiLoadCategories": "includes/api/KnowledgeGraphApiLoadCategories.php" @@ -32,20 +33,18 @@ "SpecialPages": { "KnowledgeGraphDesigner": "SpecialKnowledgeGraphDesigner" }, - "Hooks":{ - "BeforePageDisplay":"KnowledgeGraph::onBeforePageDisplay", + "Hooks": { + "BeforePageDisplay": "KnowledgeGraph::onBeforePageDisplay", "ParserFirstCallInit": "KnowledgeGraph::onParserFirstCallInit", "OutputPageParserOutput": "KnowledgeGraph::onOutputPageParserOutput", "SidebarBeforeOutput": "KnowledgeGraph::onSidebarBeforeOutput" }, - "ResourceModules":{ - "ext.KnowledgeGraph":{ - "localBasePath":"resources", - "remoteExtPath":"KnowledgeGraph/resources", - "styles":[ - "KnowledgeGraph.css" - ], - "scripts":[ + "ResourceModules": { + "ext.KnowledgeGraph": { + "localBasePath": "resources", + "remoteExtPath": "KnowledgeGraph/resources", + "styles": ["KnowledgeGraph.css"], + "scripts": [ "visNetwork/vis-network.min.js", "KnowledgeGraphFunctions.js", "KnowledgeGraphOptions.js", @@ -73,7 +72,7 @@ "oojs-ui.styles.icons-interactions", "oojs-ui.styles.icons-accessibility" ], - "position":"top", + "position": "top", "messages": [ "knowledgegraph-toolbar-info", "knowledgegraph-toolbar-help", @@ -126,7 +125,112 @@ }, "KnowledgeGraphShowSidebarLink": { "value": false + }, + "KnowledgeGraphColorPalettes": { + "value": { + "default": [ + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", + "#fbb4ae", + "#b3cde3", + "#ccebc5", + "#decbe4", + "#fed9a6", + "#ffffcc", + "#e5d8bd", + "#fddaec", + "#f2f2f2", + "#b3e2cd", + "#fdcdac", + "#cbd5e8", + "#a6cee3", + "#1f78b4", + "#b2df8a", + "#33a02c", + "#fb9a99", + "#e31a1c", + "#fdbf6f", + "#ff7f00", + "#cab2d6", + "#6a3d9a", + "#ffff99", + "#b15928", + "#7fc97f", + "#beaed4", + "#fdc086", + "#ffff99", + "#386cb0", + "#f0027f", + "#bf5b17", + "#666666", + "#1b9e77", + "#d95f02", + "#7570b3", + "#e7298a", + "#66a61e", + "#e6ab02", + "#a6761d", + "#666666" + ], + "schemeSet3": [ + "#fbb4ae", + "#b3cde3", + "#ccebc5", + "#decbe4", + "#fed9a6", + "#ffffcc", + "#e5d8bd", + "#fddaec", + "#f2f2f2", + "#b3e2cd", + "#fdcdac", + "#cbd5e8" + ], + "schemePaired": [ + "#a6cee3", + "#1f78b4", + "#b2df8a", + "#33a02c", + "#fb9a99", + "#e31a1c", + "#fdbf6f", + "#ff7f00", + "#cab2d6", + "#6a3d9a", + "#ffff99", + "#b15928" + ], + "schemeAccent": [ + "#7fc97f", + "#beaed4", + "#fdc086", + "#ffff99", + "#386cb0", + "#f0027f", + "#bf5b17", + "#666666" + ], + "schemeDark2": [ + "#1b9e77", + "#d95f02", + "#7570b3", + "#e7298a", + "#66a61e", + "#e6ab02", + "#a6761d", + "#666666" + ] + } } }, "manifest_version": 2 } + diff --git a/includes/KnowledgeGraph.php b/includes/KnowledgeGraph.php index 3133038..87139ae 100644 --- a/includes/KnowledgeGraph.php +++ b/includes/KnowledgeGraph.php @@ -5,6 +5,7 @@ * * @license GPL-2.0-or-later * @author thomas-topway-it for KM-A + * @author gesinn.it */ // use MediaWiki\Category\Category; @@ -205,6 +206,51 @@ public static function onSidebarBeforeOutput( $skin, &$sidebar ) { ]; } + /** + * @see https://github.com/SemanticMediaWiki/KnowledgeGraph/issues/48 + * @param array $nodes + * @return array + */ + private static function extractTitleWithCommas( $nodes ) { + $validTitles = []; + $i = 0; + $totalNodes = count( $nodes ); + + while ( $i < $totalNodes ) { + $titleText = $nodes[$i]; + $title_ = Title::newFromText( $titleText ); + + // If this title doesn't exist and there are more elements, try joining with next values + if ( ( !$title_ || !$title_->isKnown() ) && ( $i + 1 ) < $totalNodes ) { + $combinedText = $titleText; + $j = $i + 1; + + // Try joining with subsequent values until we find an existing title + while ( $j < $totalNodes ) { + $combinedText .= ', ' . $nodes[$j]; + $testTitle = Title::newFromText( $combinedText ); + + if ( $testTitle && $testTitle->isKnown() ) { + $title_ = $testTitle; + + // Skip the consumed elements + $i = $j; + break; + } + $j++; + } + } + + if ( $title_ && $title_->isKnown() ) { + $validTitles[] = $title_; + } + + $i++; + } + + return $validTitles; + } + /** * @see https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/PageProperties/+/c997fbd2583ccc088dc232288f883716ca2f5777/includes/PageProperties.php * @param Parser $parser @@ -252,18 +298,47 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv ) $params['show-toolbar'] = false; $propertyOptions = []; + $propAttributes = []; // property-related options foreach ( $values as $val ) { if ( preg_match( '/^property-options(\?(.+))?=(.+)/', $val, $match ) ) { + // allow to to express attributes in this form + // |property-options?Prop_a#color.background=#ccc + // |property-options?Prop_a#color.border=#0000FF + $isAttribute = false; + $attr = ''; + if ( strpos( $match[2], '#' ) !== false ) { + [ $match[2], $attr ] = explode( '#', $match[2], 2 ); + $isAttribute = true; + } + $title_ = Title::makeTitleSafe( \SMW_NS_PROPERTY, $match[2] ); - if ( $title_ ) { - $propertyOptions[$title_->getText()] = $match[3]; + if ( !$title_ ) { + continue; + } + + if ( $isAttribute ) { + $propAttributes[ $title_->getText() ][ $attr ] = $match[3]; + } else { + $propertyOptions[ $title_->getText() ] = $match[3]; } } } - foreach ( $params['nodes'] as $titleText ) { - $title_ = Title::newFromText( $titleText ); + foreach ( $propAttributes as $key => $value ) { + foreach ( $value as $k => $v ) { + $propAttributes[$key] = array_merge_recursive( + self::plainToNestedObj( $k, $v ), + $propAttributes[$key] + ); + unset( $propAttributes[$key][$k] ); + } + $propertyOptions[$key] = $propAttributes[$key]; + } + + $nodes = self::extractTitleWithCommas( $params['nodes'] ); + + foreach ( $nodes as $title_ ) { if ( $title_ && $title_->isKnown() ) { if ( !isset( self::$data[$title_->getFullText()] ) ) { self::setSemanticDataFromApi( $title_, $params['properties'], 0, $params['depth'] ); @@ -282,8 +357,11 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv ) } } - foreach ( $propertyOptions as $property => $titleText ) { - $title_ = Title::newFromText( $titleText, NS_MEDIAWIKI ); + foreach ( $propertyOptions as $property => $value ) { + if ( is_array( $value ) ) { + continue; + } + $title_ = Title::newFromText( $value, NS_MEDIAWIKI ); if ( $title_ && $title_->isKnown() ) { // $propertyOptions[$property] = json_decode( self::getWikipageContent( $title_ ), true ); $propertyOptions[$property] = self::getWikipageContent( $title_ ); @@ -296,8 +374,8 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv ) $params['graphOptions'] = $graphOptions; $params['propertyOptions'] = $propertyOptions; self::$graphs[] = $params; - self::$data = []; + self::$data = []; $out->setExtensionData( 'knowledgegraphs', self::$graphs ); $paletteName = $params['palette'] ?? 'default'; @@ -319,6 +397,28 @@ public static function parserFunctionKnowledgeGraph( Parser $parser, ...$argv ) ]; } + /** + * Converts strings like columns.searchPanes.show o nested objects. + */ + public static function plainToNestedObj( string $key, mixed $value ): array { + $arr = explode( '.', $key ); + $ret = []; + + // link to first level + $t = &$ret; + foreach ( $arr as $key => $k ) { + if ( !array_key_exists( $k, $t ) ) { + $t[$k] = []; + } + // link to deepest level + $t = &$t[$k]; + if ( $key === count( $arr ) - 1 ) { + $t = $value; + } + } + return $ret; + } + /** * @param string $propertyText * @param int $limit @@ -587,6 +687,7 @@ public static function articlesInCategories( $category, $limit, $offset ) { ]; $dbr = wfGetDB( DB_REPLICA ); + // @credits paladox if ( version_compare( MW_VERSION, '1.45', '>=' ) ) { $res = $dbr->select( [ 'categorylinks', 'linktarget' ], diff --git a/resources/KnowledgeGraph.js b/resources/KnowledgeGraph.js index 60cbce0..9c48366 100644 --- a/resources/KnowledgeGraph.js +++ b/resources/KnowledgeGraph.js @@ -33,6 +33,7 @@ KnowledgeGraph = function () { self.nodePropertiesCache = {}; self.id = null; self.colors = mw.config.get('wgKnowledgeGraphColorPalette'); + self.LastDepth = null; function addLegendEntry(id, label, color) { if (!self.LegendDiv) return; @@ -166,6 +167,8 @@ KnowledgeGraph = function () { } function loadNodes(obj) { + self.LastDepth = obj.depth; + if (obj.title !== null && obj.properties === null) { var payload = { action: 'knowledgegraph-load-nodes', @@ -781,7 +784,7 @@ KnowledgeGraph = function () { const text = `{{#knowledgegraph: nodes=${nodes.join(', ')} |properties=${properties.join(', ')} -|depth=0 +|depth=${self.LastDepth !== null ? self.LastDepth : self.Config.depth} |graph-options= ${propertyOptions}|show-property-type=true |width=400px @@ -1648,6 +1651,14 @@ $(document).ready(async function () { } } else if (!isPlainObject(value)) { graphData.propertyOptions[key] = {}; + + // merge with default nodes options + } else { + graphData.propertyOptions[key] = $.extend( + true, // recursive + KnowledgeGraphOptions.getDefaultOptions().nodes, + graphData.propertyOptions[key] + ); } } } else {