Skip to content

Commit

Permalink
Inbox merging #1
Browse files Browse the repository at this point in the history
  • Loading branch information
it-spiderman committed May 13, 2024
1 parent 018e9da commit 8719596
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 13 deletions.
1 change: 1 addition & 0 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@
"ui/Inbox/ComparePanel.js",
"ui/Inbox/TreePanel.js",
"ui/Inbox/TreeNode.js",
"ui/Inbox/CombinedTreeNode.js",
"ext.DataAccounting.inbox.compare.js"
],
"styles": [
Expand Down
276 changes: 276 additions & 0 deletions includes/Inbox/HTMLDiffFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
<?php

namespace DataAccounting\Inbox;

use DiffOp;

class HTMLDiffFormatter extends \DiffFormatter {
/** @var int[] */
protected $stats = [ 'add' => 0, 'delete' => 0 ];
/** @var array */
protected $arrayData = [];
/** @var int */
protected $idCounter = 0;
/** @var string */
protected $html = '';

/**
* Parses diff to HTML
*
* @param \Diff $diff
* @param bool $block If true, every line will be its own block
* @return string
*/
public function format( $diff, $block = true ) {
$this->html = '';

$this->html = \Html::openElement( 'div', [
'class' => 'da-diff',
'id' => 'da-diff'
] );

$this->idCounter = 0;

foreach ( $diff->getEdits() as $edit ) {
switch ( $edit->getType() ) {
case 'add':
if ( $block ) {
$this->blockAdd( $edit );
} else {
$this->lineByLineAdd( $edit );
}
break;
case 'delete':
if ( $block ) {
$this->blockDelete( $edit );
} else {
$this->lineByLineDelete( $edit );
}
break;
case 'change':
[ $orig, $closing ] = $this->conflateChange( $edit );
if ( $block ) {
$this->blockChange( $orig, $closing );
} else {
$this->lineByLineChange( $orig, $closing );
}
break;
case 'copy':
// Copy is always rendered as block
$this->blockCopy( $edit );
}
}

$this->html .= \Html::closeElement( 'div' );
return $this->html;
}

/**
*
* @return array
*/
public function getArrayData() {
return $this->arrayData;
}

/**
*
* @return array
*/
public function getChangeCount() {
$changeCount = [ 'add' => 0, 'delete' => 0 ];
foreach ( $this->arrayData as $changeId => $change ) {
if ( $change[ 'type' ] === 'add' ) {
$changeCount[ 'add' ]++;
} elseif ( $change[ 'type' ] === 'delete' ) {
$changeCount[ 'delete' ]++;
} elseif ( $change[ 'type' ] === 'change' ) {
$changeCount[ 'add' ]++;
$changeCount[ 'delete' ]++;
}
}
return $changeCount;
}

/**
*
* @param string $diff
* @param string $type
* @param bool|false $counter
* @return string
*/
protected function getDiffHTML( $diff, $type, $counter = true ) {
$attrs = [
'class' => "da-diff-$type",
'data-diff' => $type
];
if ( $counter ) {
$attrs['data-diff-id'] = $this->idCounter;
}

$html = \Html::openElement( 'p', $attrs );
foreach ( explode( "\n", $diff ) as $ln ) {
if ( empty( $ln ) ) {
$html .= \Html::element( 'span', [ 'class' => 'empty-line' ], $ln );
} else {
$html .= \Html::element( 'span', [], $ln );
}
}
$html .= \Html::closeElement( 'p' );
return $html;
}

/**
*
* @param \DiffOp $edit
* @return array
*/
protected function conflateChange( \DiffOp $edit ) {
$orig = $edit->getOrig();
$closing = $edit->getClosing();

if ( count( $orig ) > count( $closing ) ) {
return array_reverse( $this->mergeUneven( $closing, $orig ) );
} elseif ( count( $closing ) > count( $orig ) ) {
return $this->mergeUneven( $orig, $closing );
}
return [ $orig, $closing ];
}

/**
*
* @param array $ar1
* @param array $ar2
* @return array
*/
protected function mergeUneven( $ar1, $ar2 ) {
$i = 0;
$res = [];
while ( $i < count( $ar1 ) - 1 ) {
$i++;
$res[] = $ar2[$i];
}
$res[] = implode( "\n", array_diff( $ar2, $res ) );
return [ $ar1, $res ];
}

/**
*
* @param DiffOp $edit
*/
protected function blockAdd( $edit ) {
$closingAll = implode( "\n", $edit->getClosing() );
$this->idCounter++;
$this->html .= $this->getDiffHTML( $closingAll, 'add' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'add',
'new' => $closingAll
];
}

/**
*
* @param DiffOp $edit
*/
protected function lineByLineAdd( $edit ) {
foreach ( $edit->getClosing() as $line ) {
$this->idCounter++;
$this->html .= $this->getDiffHTML( $line, 'add' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'add',
'new' => $line
];
}
}

/**
*
* @param DiffOp $edit
*/
protected function blockDelete( $edit ) {
$origAll = implode( "\n", $edit->getOrig() );
$this->idCounter++;
$this->html .= $this->getDiffHTML( $origAll, 'delete' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'delete',
'old' => $origAll
];
}

/**
*
* @param DiffOp $edit
*/
protected function lineByLineDelete( $edit ) {
foreach ( $edit->getOrig() as $line ) {
$this->idCounter++;
$this->html .= $this->getDiffHTML( $line, 'delete' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'delete',
'old' => $line
];
}
}

/**
*
* @param array $orig
* @param array $closing
*/
protected function blockChange( $orig, $closing ) {
$origAll = implode( "\n", $orig );
$closingAll = implode( "\n", $closing );
$this->idCounter++;
$this->html .= \Html::openElement( 'div', [
'class' => 'da-diff-change',
'data-diff' => 'change',
'data-diff-id' => $this->idCounter
] );
$this->html .= $this->getDiffHTML( $origAll, 'delete', false );
$this->html .= $this->getDiffHTML( $closingAll, 'add', false );
$this->html .= \Html::closeElement( 'div' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'change',
'old' => $origAll,
'new' => $closingAll
];
}

/**
*
* @param array $orig
* @param array $closing
*/
protected function lineByLineChange( $orig, $closing ) {
foreach ( $orig as $key => $line ) {
$this->idCounter++;
$this->html .= \Html::openElement( 'div', [
'class' => 'da-diff-change',
'data-diff' => 'change',
'data-diff-id' => $this->idCounter
] );
$this->html .= $this->getDiffHTML( $line, 'delete', false );
$this->html .= $this->getDiffHTML( $closing[$key], 'add', false );
$this->html .= \Html::closeElement( 'div' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'change',
'old' => $line,
'new' => $closing[$key]
];
}
}

/**
*
* @param DiffOp $edit
*/
protected function blockCopy( $edit ) {
$text = implode( "\n", $edit->getOrig() );
$this->idCounter++;
$this->html .= $this->getDiffHTML( $text, 'copy' );
$this->arrayData[ $this->idCounter ] = [
'type' => 'copy',
'old' => $text
];
}
}
5 changes: 4 additions & 1 deletion includes/Inbox/TreeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ public function buildPreImportTree( Title $remote, Title $local, Language $langu
] );
$localEntities = $this->verifyAndReduce( $localEntities );

return $this->combine( $remoteEntities, $localEntities, $language, $user );
$combined = $this->combine( $remoteEntities, $localEntities, $language, $user );
$combined['remote'] = $remote;
$combined['local'] = $local;
return $combined;
}

/**
Expand Down
59 changes: 58 additions & 1 deletion includes/SpecialInbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

namespace DataAccounting;

use DataAccounting\Inbox\HTMLDiffFormatter;
use DataAccounting\Inbox\InboxImporter;
use DataAccounting\Inbox\Pager;
use DataAccounting\Verification\Entity\VerificationEntity;
use DataAccounting\Verification\VerificationEngine;
use Html;
use HTMLForm;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionLookup;
use Message;
use SpecialPage;
use TextContent;
use Title;
use TitleFactory;

class SpecialInbox extends SpecialPage {
Expand Down Expand Up @@ -105,7 +109,6 @@ private function outputCompare( VerificationEntity $draft, VerificationEntity $t
$draft->getTitle(), $target->getTitle(), $this->getLanguage(), $this->getUser()
);
$this->getOutput()->addHTML( $this->makeSummaryHeader( $tree, $draft, $target ) );

$this->getOutput()->addHTML(
Html::element( 'div', [
'id' => 'da-specialinbox-compare',
Expand All @@ -114,6 +117,15 @@ private function outputCompare( VerificationEntity $draft, VerificationEntity $t
'data-target' => $target->getTitle()->getArticleID(),
] )
);
if ( $tree['change-type'] === 'both' ) {
$diff = $this->makeDiff( $tree );
if ( $diff ) {
$this->getOutput()->addHTML( Html::rawElement( 'div', [
'id' => 'da-specialinbox-compare-diff',
'data-diff' => json_encode( $diff['diffData'] ),
], $diff['formatted'] ) );
}
}
$this->outputForm( $draft, $tree['change-type'] );
$this->getOutput()->addModules( 'ext.DataAccounting.inbox.compare' );
}
Expand Down Expand Up @@ -288,4 +300,49 @@ private function doMergeRemote() {
$this->getOutput()->redirect( $targetTitle->getFullURL() );
return true;
}

/**
* @param array $tree
* @return array|null
* @throws \MediaWiki\Diff\ComplexityException
*/
private function makeDiff( array $tree ): ?array {
$diff = $this->getDiff( $tree['local'], $tree['remote'] );
$diffFormatter = new HTMLDiffFormatter();

if ( empty( $diff ) ) {

Check failure on line 313 in includes/SpecialInbox.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Variable $diff in empty() always exists and is not falsy.
return null;
}
return [
'formatted' => $diffFormatter->format( $diff ),
'diffData' => $diffFormatter->getArrayData(),
'count' => $diffFormatter->getChangeCount()
];
}

/**
* @param Title $local
* @param Title $remote
* @return \Diff
* @throws \MediaWiki\Diff\ComplexityException
*/
protected function getDiff( \Title $local, \Title $remote ) {
$localContent = $this->getPageContentText( $local );
$remoteContent = $this->getPageContentText( $remote );

return new \Diff(
explode( "\n", $localContent ),
explode( "\n", $remoteContent )
);
}

/**
* @param Title $title
* @return string
*/
protected function getPageContentText( Title $title ): string {
$wikipage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
$content = $wikipage->getContent();
return ( $content instanceof TextContent ) ? $content->getText() : '';
}
}
Loading

0 comments on commit 8719596

Please sign in to comment.