mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Cite
synced 2024-11-27 16:30:12 +00:00
Adding "follow" functionality to the Cite extension
* Interim state commit with experimental code. * Updates to citeParserTests.txt to check now valid follow functionality and newly passing tests. * Added to follow refs, <sup style="display: none;" about=... to suppress display of hidden sups needed for VE to use in editing follow refs. * Added code to implemented follow functionality and catch invalid usage. Bug: T51538 Change-Id: Ic3ac8237fd2c490cfaf2fe799759742f72f10686
This commit is contained in:
parent
46a9900f69
commit
467b82701b
|
@ -94,9 +94,28 @@ class Ref extends ExtensionTagHandler {
|
||||||
'inPHPBlock' => true
|
'inPHPBlock' => true
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ref follow nodes should already have their content spans stored in followContent
|
||||||
if ( is_string( $dataMw->body->html ?? null ) ) {
|
if ( is_string( $dataMw->body->html ?? null ) ) {
|
||||||
// First look for the extension's content in data-mw.body.html
|
// First look for the extension's content in data-mw.body.html
|
||||||
$src = $extApi->htmlToWikitext( $html2wtOpts, $dataMw->body->html );
|
$src = $extApi->htmlToWikitext( $html2wtOpts, $dataMw->body->html );
|
||||||
|
} elseif ( isset( $dataMw->attrs->follow ) ) {
|
||||||
|
$src = '';
|
||||||
|
if ( $node->hasAttribute( 'about' ) ) {
|
||||||
|
$about = $node->getAttribute( 'about' );
|
||||||
|
$bodyElt = [];
|
||||||
|
$followNode = DOMCompat::getElementById( $node->ownerDocument, $dataMw->body->id )
|
||||||
|
?? null;
|
||||||
|
if ( $followNode != null ) {
|
||||||
|
$bodyElt = DOMCompat::querySelectorAll( $followNode,
|
||||||
|
"span[typeof~='mw:Cite/Follow'][about='" . $about . "']" );
|
||||||
|
}
|
||||||
|
// ensure that $bodyElt was found
|
||||||
|
if ( count( $bodyElt ) > 0 ) {
|
||||||
|
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt[0], true );
|
||||||
|
$src = ltrim( $src, ' ' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} elseif ( is_string( $dataMw->body->id ?? null ) ) {
|
} elseif ( is_string( $dataMw->body->id ?? null ) ) {
|
||||||
// If the body isn't contained in data-mw.body.html, look if
|
// If the body isn't contained in data-mw.body.html, look if
|
||||||
// there's an element pointed to by body.id.
|
// there's an element pointed to by body.id.
|
||||||
|
@ -145,6 +164,14 @@ class Ref extends ExtensionTagHandler {
|
||||||
return ''; // Drop it!
|
return ''; // Drop it!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search for spans with follow content
|
||||||
|
if ( isset( $bodyElt->firstChild->nextSibling ) &&
|
||||||
|
DOMUtils::hasTypeOf( $bodyElt->firstChild->nextSibling, 'mw:Cite/Follow' ) ) {
|
||||||
|
$clonedNode = $bodyElt->cloneNode();
|
||||||
|
$clonedNode->appendChild( $bodyElt->firstChild );
|
||||||
|
$bodyElt = $clonedNode;
|
||||||
|
}
|
||||||
|
|
||||||
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true );
|
$src = $extApi->domToWikitext( $html2wtOpts, $bodyElt, true );
|
||||||
} else {
|
} else {
|
||||||
$extApi->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) );
|
$extApi->log( 'error', 'Ref body unavailable for: ' . DOMCompat::getOuterHTML( $node ) );
|
||||||
|
|
|
@ -144,17 +144,73 @@ class References extends ExtensionTagHandler {
|
||||||
// FIXME(SSS): Need to clarify semantics here.
|
// FIXME(SSS): Need to clarify semantics here.
|
||||||
// If both the containing <references> elt as well as the nested <ref>
|
// If both the containing <references> elt as well as the nested <ref>
|
||||||
// elt has a group attribute, what takes precedence?
|
// elt has a group attribute, what takes precedence?
|
||||||
$group = $refDmw->attrs->group ?? $referencesGroup ?? '';
|
$groupName = $refDmw->attrs->group ?? $referencesGroup ?? '';
|
||||||
|
|
||||||
// NOTE: This will have been trimmed in Utils::getExtArgInfo()'s call
|
// NOTE: This will have been trimmed in Utils::getExtArgInfo()'s call
|
||||||
// to TokenUtils::kvToHash() and ExtensionHandler::normalizeExtOptions()
|
// to TokenUtils::kvToHash() and ExtensionHandler::normalizeExtOptions()
|
||||||
$refName = $refDmw->attrs->name ?? '';
|
$refName = $refDmw->attrs->name ?? '';
|
||||||
|
$follow = $refDmw->attrs->follow ?? '';
|
||||||
|
|
||||||
|
$hasRefName = strlen( $refName ) > 0;
|
||||||
|
$hasFollow = strlen( $follow ) > 0;
|
||||||
|
|
||||||
// Add ref-index linkback
|
// Add ref-index linkback
|
||||||
$linkBack = $doc->createElement( 'sup' );
|
$linkBack = $doc->createElement( 'sup' );
|
||||||
|
|
||||||
|
// prechecking various combinations of un-named refs, named refs, follow and error cases to
|
||||||
|
// properly determine how to proceed with adding a ref or not and generating appropriate code
|
||||||
|
if ( $hasRefName ) {
|
||||||
|
if ( $hasFollow ) {
|
||||||
|
// having both a ref name and follow is an error, the ref should be represented
|
||||||
|
// and the follow present, but not causing any effect, but add and
|
||||||
|
// verify follow round trips anyway
|
||||||
|
$debugging = 0;
|
||||||
|
} else {
|
||||||
|
// normal named ref, so add normally
|
||||||
|
$debugging = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Either an un-named ref or a follow
|
||||||
|
if ( $hasFollow ) {
|
||||||
|
// Is a follow ref, so check if a named ref has already been defined
|
||||||
|
$group = $refsData->getRefGroup( $groupName, true );
|
||||||
|
if ( isset( $group->indexByName[$follow] ) ) {
|
||||||
|
// Yes the named ref is defined, so lets check if it has defined content
|
||||||
|
$ref = $group->indexByName[$follow];
|
||||||
|
if ( $ref->contentId ) {
|
||||||
|
// The named ref did define content, so attach follow content normally
|
||||||
|
$debugging = 2;
|
||||||
|
} else {
|
||||||
|
// The follow content follows a named ref that did not define content
|
||||||
|
// and therefore the follow content cannot be appended and must be
|
||||||
|
// handled differently
|
||||||
|
$debugging = 3;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this follow precedes a named ref being defined or possibly never being defined
|
||||||
|
// which is a type of error but still must round trip, so we must handle this case
|
||||||
|
// with special code and not call the add function.
|
||||||
|
$debugging = 4;
|
||||||
|
|
||||||
|
$html = $extApi->domToHtml( $c, true, true );
|
||||||
|
$refDmw->body = (object)[ 'html' => $html ];
|
||||||
|
DOMDataUtils::setDataMw( $linkBack, $refDmw );
|
||||||
|
DOMUtils::addTypeOf( $linkBack, 'mw:Extension/ref mw:Error' );
|
||||||
|
$node->parentNode->replaceChild( $linkBack, $node );
|
||||||
|
return;
|
||||||
|
// do not define a ref
|
||||||
|
|
||||||
|
}
|
||||||
|
// Is an unnamed ref and not a follow, so add the ref normally
|
||||||
|
} else {
|
||||||
|
// normal un-named ref, so add normally
|
||||||
|
$debugging = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$ref = $refsData->add(
|
$ref = $refsData->add(
|
||||||
$extApi, $group, $refName, $about, $nestedInReferences, $linkBack
|
$extApi, $groupName, $refName, $follow, $contentId, $about, $nestedInReferences,
|
||||||
|
$linkBack
|
||||||
);
|
);
|
||||||
|
|
||||||
$errs = [];
|
$errs = [];
|
||||||
|
@ -163,6 +219,10 @@ class References extends ExtensionTagHandler {
|
||||||
// FIXME: See T260082 for a more complete description of cause and deeper fix
|
// FIXME: See T260082 for a more complete description of cause and deeper fix
|
||||||
$missingContent = ( !empty( $cDp->empty ) || trim( $refDmw->body->extsrc ?? '' ) === '' );
|
$missingContent = ( !empty( $cDp->empty ) || trim( $refDmw->body->extsrc ?? '' ) === '' );
|
||||||
|
|
||||||
|
if ( $refName !== '' && strlen( $follow ) > 0 ) {
|
||||||
|
$errs[] = [ 'key' => 'cite_error_ref_too_many_keys' ];
|
||||||
|
}
|
||||||
|
|
||||||
if ( $missingContent ) {
|
if ( $missingContent ) {
|
||||||
// Check for missing name and content to generate error code
|
// Check for missing name and content to generate error code
|
||||||
if ( $refName === '' ) {
|
if ( $refName === '' ) {
|
||||||
|
@ -203,6 +263,10 @@ class References extends ExtensionTagHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
$lastLinkback = $ref->linkbacks[count( $ref->linkbacks ) - 1] ?? null;
|
$lastLinkback = $ref->linkbacks[count( $ref->linkbacks ) - 1] ?? null;
|
||||||
|
|
||||||
|
if ( strlen( $follow ) > 0 ) {
|
||||||
|
$linkBack->setAttribute( 'style', 'display: none;' );
|
||||||
|
}
|
||||||
DOMUtils::addAttributes( $linkBack, [
|
DOMUtils::addAttributes( $linkBack, [
|
||||||
'about' => $about,
|
'about' => $about,
|
||||||
'class' => 'mw-ref',
|
'class' => 'mw-ref',
|
||||||
|
@ -215,7 +279,6 @@ class References extends ExtensionTagHandler {
|
||||||
if ( count( $errs ) > 0 ) {
|
if ( count( $errs ) > 0 ) {
|
||||||
DOMUtils::addTypeOf( $linkBack, 'mw:Error' );
|
DOMUtils::addTypeOf( $linkBack, 'mw:Error' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$dataParsoid = new stdClass;
|
$dataParsoid = new stdClass;
|
||||||
if ( isset( $nodeDp->src ) ) {
|
if ( isset( $nodeDp->src ) ) {
|
||||||
$dataParsoid->src = $nodeDp->src;
|
$dataParsoid->src = $nodeDp->src;
|
||||||
|
@ -256,8 +319,11 @@ class References extends ExtensionTagHandler {
|
||||||
'[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']'
|
'[' . ( $ref->group ? $ref->group . ' ' : '' ) . $ref->groupIndex . ']'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ( strlen( $follow ) == 0 || count( $errs ) > 0 ) {
|
||||||
$refLink->appendChild( $refLinkSpan );
|
$refLink->appendChild( $refLinkSpan );
|
||||||
$linkBack->appendChild( $refLink );
|
$linkBack->appendChild( $refLink );
|
||||||
|
}
|
||||||
|
|
||||||
$node->parentNode->replaceChild( $linkBack, $node );
|
$node->parentNode->replaceChild( $linkBack, $node );
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Wikimedia\Parsoid\Ext\Cite;
|
||||||
|
|
||||||
use DOMElement;
|
use DOMElement;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
use Wikimedia\Parsoid\Ext\DOMUtils;
|
||||||
use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
|
use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
|
||||||
|
|
||||||
class ReferencesData {
|
class ReferencesData {
|
||||||
|
@ -53,14 +54,16 @@ class ReferencesData {
|
||||||
* @param ParsoidExtensionAPI $extApi
|
* @param ParsoidExtensionAPI $extApi
|
||||||
* @param string $groupName
|
* @param string $groupName
|
||||||
* @param string $refName
|
* @param string $refName
|
||||||
|
* @param string $follow
|
||||||
|
* @param string $contentId
|
||||||
* @param string $about
|
* @param string $about
|
||||||
* @param bool $skipLinkback
|
* @param bool $skipLinkback
|
||||||
* @param DOMElement $linkBack
|
* @param DOMElement $linkBack
|
||||||
* @return stdClass
|
* @return stdClass
|
||||||
*/
|
*/
|
||||||
public function add(
|
public function add(
|
||||||
ParsoidExtensionAPI $extApi, string $groupName, string $refName,
|
ParsoidExtensionAPI $extApi, string $groupName, string $refName, string $follow,
|
||||||
string $about, bool $skipLinkback, DOMElement $linkBack
|
string $contentId, string $about, bool $skipLinkback, DOMElement $linkBack
|
||||||
): stdClass {
|
): stdClass {
|
||||||
$group = $this->getRefGroup( $groupName, true );
|
$group = $this->getRefGroup( $groupName, true );
|
||||||
// Looks like Cite.php doesn't try to fix ids that already have
|
// Looks like Cite.php doesn't try to fix ids that already have
|
||||||
|
@ -72,6 +75,41 @@ class ReferencesData {
|
||||||
// in Parsoid: ExtensionHandler#normalizeExtOptions
|
// in Parsoid: ExtensionHandler#normalizeExtOptions
|
||||||
$refName = $extApi->sanitizeHTMLId( $refName );
|
$refName = $extApi->sanitizeHTMLId( $refName );
|
||||||
$hasRefName = strlen( $refName ) > 0;
|
$hasRefName = strlen( $refName ) > 0;
|
||||||
|
$hasFollow = strlen( $follow ) > 0;
|
||||||
|
|
||||||
|
// Is this a follow ref that has been preceeded by a named ref which defined
|
||||||
|
// content or did not define content, or a self closed named ref without content
|
||||||
|
if ( !$hasRefName && $hasFollow && isset( $group->indexByName[$follow] ) ) {
|
||||||
|
$ref = $group->indexByName[$follow];
|
||||||
|
$contentSup = $extApi->getContentDOM( $contentId );
|
||||||
|
$ownerDoc = $contentSup->ownerDocument;
|
||||||
|
$span = $ownerDoc->createElement( 'span' );
|
||||||
|
DOMUtils::addTypeOf( $span, 'mw:Cite/Follow' );
|
||||||
|
$span->setAttribute( 'about', $about );
|
||||||
|
$spaceNode = $ownerDoc->createTextNode( ' ' );
|
||||||
|
$span->appendChild( $spaceNode );
|
||||||
|
DOMUtils::migrateChildren( $contentSup, $span );
|
||||||
|
|
||||||
|
// contentSup is now empty and can be used as a container for migrate children
|
||||||
|
$contentSup->appendChild( $span );
|
||||||
|
|
||||||
|
// If the named ref has defined content
|
||||||
|
if ( $ref->contentId ) {
|
||||||
|
$refContent = $extApi->getContentDOM( $ref->contentId );
|
||||||
|
ParsoidExtensionAPI::migrateChildrenBetweenDocs( $contentSup, $refContent, false );
|
||||||
|
} else {
|
||||||
|
// Otherwise we have a follow that comes after named ref without content
|
||||||
|
// So create a sup in a fragment, set that into the environment and migrate
|
||||||
|
// the follow content into the fragment
|
||||||
|
$ref->contentId = $contentId;
|
||||||
|
}
|
||||||
|
return $ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must check for error case where $hasRefName and $hasFollow are both present
|
||||||
|
// which still needs to create the ref, but also flag it with an error due to
|
||||||
|
// [ 'key' => 'cite_error_ref_too_many_keys' ]; as trapped in References.php
|
||||||
|
// but which needs to preserve the extra key for round tripping
|
||||||
|
|
||||||
if ( $hasRefName && isset( $group->indexByName[$refName] ) ) {
|
if ( $hasRefName && isset( $group->indexByName[$refName] ) ) {
|
||||||
$ref = $group->indexByName[$refName];
|
$ref = $group->indexByName[$refName];
|
||||||
|
|
Loading…
Reference in a new issue