Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
129 / 129
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
HtmlTalkPageResolutionView
100.00% covered (success)
100.00%
129 / 129
100.00% covered (success)
100.00%
7 / 7
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getHtml
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
1 / 1
6
 wrapRow
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 buildOrderSelector
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
1
 buildConflictingTalkRow
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 buildCopyRow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getMessageBox
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace TwoColConflict\Html;
4
5use MediaWiki\Html\Html;
6use MessageLocalizer;
7use OOUI\ButtonWidget;
8use OOUI\FieldLayout;
9use OOUI\FieldsetLayout;
10use OOUI\HtmlSnippet;
11use OOUI\MessageWidget;
12use OOUI\RadioInputWidget;
13use TwoColConflict\SplitConflictUtils;
14
15/**
16 * TODO: Clean up, maybe CSS class names should match change type, and "split" replaced with
17 *  "single" where appropriate.
18 *
19 * @license GPL-2.0-or-later
20 */
21class HtmlTalkPageResolutionView {
22
23    private HtmlEditableTextComponent $editableTextComponent;
24    private MessageLocalizer $messageLocalizer;
25
26    public function __construct(
27        HtmlEditableTextComponent $editableTextComponent,
28        MessageLocalizer $messageLocalizer
29    ) {
30        $this->editableTextComponent = $editableTextComponent;
31        $this->messageLocalizer = $messageLocalizer;
32    }
33
34    /**
35     * @param array[] $unifiedDiff A list of changes as created by the AnnotatedHtmlDiffFormatter
36     * @param int $otherIndex
37     * @param int $yourIndex
38     * @param bool $isBetaFeature
39     *
40     * @return string HTML
41     */
42    public function getHtml(
43        array $unifiedDiff,
44        int $otherIndex,
45        int $yourIndex,
46        bool $isBetaFeature
47    ): string {
48        $out = $this->getMessageBox(
49            'twocolconflict-talk-header-overview', 'error', 'mw-twocolconflict-overview' );
50        $hintMsg = $isBetaFeature ?
51            'twocolconflict-split-header-hint-beta' : 'twocolconflict-split-header-hint';
52        $out .= $this->getMessageBox( $hintMsg, 'notice' );
53
54        $rows = '';
55        foreach ( $unifiedDiff as $i => $changeSet ) {
56            $text = $changeSet['copytext'] ?? $changeSet['newtext'];
57            switch ( $i ) {
58                case $otherIndex:
59                    $rows .= $this->buildConflictingTalkRow(
60                        $text,
61                        $i,
62                        'delete',
63                        'other',
64                        true,
65                        'twocolconflict-talk-conflicting'
66                    );
67
68                    $rows .= Html::rawElement(
69                        'div',
70                        [ 'class' => 'mw-twocolconflict-single-swap-button-container' ],
71                        new ButtonWidget( [
72                            'infusable' => true,
73                            'framed' => true,
74                            'icon' => 'markup',
75                            'title' => $this->messageLocalizer->msg(
76                                'twocolconflict-talk-switch-tooltip'
77                            )->text(),
78                            'classes' => [ 'mw-twocolconflict-single-swap-button' ],
79                            'tabIndex' => '1'
80                        ] )
81                    );
82
83                    break;
84                case $yourIndex:
85                    $rows .= $this->buildConflictingTalkRow(
86                        $text,
87                        $i,
88                        'add',
89                        'your',
90                        false,
91                        'twocolconflict-talk-your'
92                    );
93                    break;
94                default:
95                    $rows .= $this->buildCopyRow( $text, $i );
96            }
97        }
98        // this will allow CSS formatting with :first-of-type
99        $out .= Html::rawElement(
100            'div',
101            [ 'class' => 'mw-twocolconflict-single-column-rows' ],
102            $rows
103        );
104
105        $out .= $this->buildOrderSelector() .
106            Html::hidden( 'mw-twocolconflict-single-column-view', true );
107
108        return Html::rawElement(
109            'div',
110            [ 'class' => 'mw-twocolconflict-split-view mw-twocolconflict-single-column-view' ],
111            $out
112        );
113    }
114
115    private function wrapRow( string $html, bool $isConflicting = false ): string {
116        $class = [ 'mw-twocolconflict-single-row' ];
117        if ( $isConflicting ) {
118            $class[] = 'mw-twocolconflict-conflicting-talk-row';
119        }
120        return Html::rawElement( 'div', [ 'class' => $class ], $html );
121    }
122
123    private function buildOrderSelector(): string {
124        $out = new FieldsetLayout( [
125            'label' => $this->messageLocalizer->msg( 'twocolconflict-talk-reorder-prompt' )->text(),
126            'items' => [
127                new FieldLayout(
128                    new RadioInputWidget( [
129                        'name' => 'mw-twocolconflict-reorder',
130                        'value' => 'reverse',
131                        'tabIndex' => '1',
132                    ] ),
133                    [
134                        'align' => 'inline',
135                        'label' => $this->messageLocalizer->msg( 'twocolconflict-talk-reverse-order' )->text(),
136                    ]
137                ),
138                new FieldLayout(
139                    new RadioInputWidget( [
140                        'name' => 'mw-twocolconflict-reorder',
141                        'value' => 'no-change',
142                        'selected' => true,
143                        'tabIndex' => '1',
144                    ] ),
145                    [
146                        'align' => 'inline',
147                        'label' => $this->messageLocalizer->msg( 'twocolconflict-talk-same-order' )->text(),
148                    ]
149                ),
150            ],
151        ] );
152
153        return Html::rawElement(
154            'div',
155            [ 'class' => 'mw-twocolconflict-order-selector' ],
156            $out
157        );
158    }
159
160    private function buildConflictingTalkRow(
161        string $rawText,
162        int $rowNum,
163        string $classSuffix,
164        string $changeType,
165        bool $isDisabled,
166        string $conflictingTalkLabel
167    ): string {
168        $out = Html::rawElement(
169            'div',
170            [ 'class' => 'mw-twocolconflict-conflicting-talk-label' ],
171            Html::rawElement(
172                'span',
173                [],
174                Html::element(
175                    'span',
176                    [ 'class' => 'mw-twocolconflict-split-' . $classSuffix ],
177                    $this->messageLocalizer->msg( $conflictingTalkLabel )->text()
178                )
179            )
180        );
181
182        $out .= Html::rawElement(
183            'div',
184            [ 'class' => 'mw-twocolconflict-split-' . $classSuffix . ' mw-twocolconflict-single-column' ],
185            $this->editableTextComponent->getHtml(
186                htmlspecialchars( $rawText ), $rawText, $rowNum, $changeType, $isDisabled )
187        );
188        return $this->wrapRow( $out, true );
189    }
190
191    private function buildCopyRow(
192        string $rawText,
193        int $rowNum
194    ): string {
195        $out = Html::rawElement(
196            'div',
197            [ 'class' => 'mw-twocolconflict-split-copy mw-twocolconflict-single-column' ],
198            $this->editableTextComponent->getHtml(
199                htmlspecialchars( $rawText ), $rawText, $rowNum, 'copy', true )
200        );
201        return $this->wrapRow( $out );
202    }
203
204    private function getMessageBox( string $messageKey, string $type, string ...$classes ): string {
205        $html = $this->messageLocalizer->msg( $messageKey )->parse();
206        // Force feedback links to be opened in a new tab, and not lose the edit
207        $html = SplitConflictUtils::addTargetBlankToLinks( $html );
208        return ( new MessageWidget( [
209            'label' => new HtmlSnippet( $html ),
210            'type' => $type,
211        ] ) )
212            ->addClasses( [ 'mw-twocolconflict-messageWidget', ...$classes ] )
213            ->toString();
214    }
215
216}