Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
VectorComponentStickyHeader
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 6
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 msg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIconButtons
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
 getAddSectionButton
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getSearchButton
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getTemplateData
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2namespace MediaWiki\Skins\Vector\Components;
3
4use MediaWiki\Message\Message;
5use MessageLocalizer;
6
7/**
8 * VectorComponentStickyHeader component
9 */
10class VectorComponentStickyHeader implements VectorComponent {
11    private const TALK_ICON = [
12        'icon' => 'speechBubbles',
13        'id' => 'ca-talk-sticky-header',
14        'event' => 'talk-sticky-header'
15    ];
16    private const SUBJECT_ICON = [
17        'icon' => 'article',
18        'id' => 'ca-subject-sticky-header',
19        'event' => 'subject-sticky-header'
20    ];
21    private const HISTORY_ICON = [
22        'icon' => 'wikimedia-history',
23        'id' => 'ca-history-sticky-header',
24        'event' => 'history-sticky-header',
25    ];
26    // Event and icon will be updated depending on watchstar state
27    private const WATCHSTAR_ICON = [
28        'id' => 'ca-watchstar-sticky-header',
29        'event' => 'watch-sticky-header',
30        'icon' => 'wikimedia-star',
31        'is-quiet' => true,
32        'tabindex' => '-1',
33        // With the original watchstar, this class is applied to the <li> element
34        // thats the parent of the actual watchlink. In the sticky header we dont use
35        // the same markup, so its directly applied to the watchlink element
36        'class' => 'mw-watchlink'
37    ];
38    // Event and icon will be updated depending on saved state
39    private const BOOKMARK_ICON = [
40        'id' => 'ca-bookmark-sticky-header',
41        'event' => 'watch-sticky-bookmark',
42        'icon' => 'wikimedia-bookmarkOutline',
43        'is-quiet' => true,
44        'tabindex' => '-1',
45        'class' => 'reading-lists-bookmark'
46    ];
47    private const EDIT_VE_ICON = [
48        'id' => 'ca-ve-edit-sticky-header',
49        'event' => 've-edit-sticky-header',
50        'icon' => 'wikimedia-edit',
51    ];
52    private const EDIT_WIKITEXT_ICON = [
53        'id' => 'ca-edit-sticky-header',
54        'event' => 'wikitext-edit-sticky-header',
55        'icon' => 'wikimedia-wikiText',
56    ];
57    private const EDIT_PROTECTED_ICON = [
58        'href' => '#',
59        'id' => 'ca-viewsource-sticky-header',
60        'event' => 've-edit-protected-sticky-header',
61        'icon' => 'wikimedia-editLock',
62    ];
63
64    /** @var MessageLocalizer */
65    private $localizer;
66    /** @var VectorComponent */
67    private $search;
68    /** @var VectorComponent|null */
69    private $langButton;
70
71    /** @var bool */
72    private $visualEditorTabPositionFirst;
73
74    /**
75     * @param MessageLocalizer $localizer
76     * @param VectorComponent $searchBox
77     * @param VectorComponent|null $langButton
78     * @param bool $visualEditorTabPositionFirst
79     */
80    public function __construct(
81        MessageLocalizer $localizer,
82        VectorComponent $searchBox,
83        $langButton = null,
84        bool $visualEditorTabPositionFirst = false
85    ) {
86        $this->search = $searchBox;
87        $this->langButton = $langButton;
88        $this->localizer = $localizer;
89        $this->visualEditorTabPositionFirst = $visualEditorTabPositionFirst;
90    }
91
92    /**
93     * @param mixed $key
94     * @return Message
95     */
96    private function msg( $key ): Message {
97        return $this->localizer->msg( $key );
98    }
99
100    /**
101     * Creates array of Button components in the sticky header
102     *
103     * @return array
104     */
105    private function getIconButtons() {
106        $icons = [
107            self::TALK_ICON,
108            self::SUBJECT_ICON,
109            self::HISTORY_ICON,
110            self::WATCHSTAR_ICON,
111            self::BOOKMARK_ICON,
112        ];
113        $icons[] = $this->visualEditorTabPositionFirst ? self::EDIT_VE_ICON : self::EDIT_WIKITEXT_ICON;
114        $icons[] = $this->visualEditorTabPositionFirst ? self::EDIT_WIKITEXT_ICON : self::EDIT_VE_ICON;
115        $icons[] = self::EDIT_PROTECTED_ICON;
116        $iconButtons = [];
117        foreach ( $icons as $icon ) {
118            $iconButtons[] = new VectorComponentButton(
119                // Button labels will be populated in stickyHeader.js
120                "",
121                $icon[ 'icon' ],
122                $icon[ 'id' ],
123                $icon[ 'class' ] ?? '',
124                [
125                    'tabindex' => '-1',
126                    'data-event-name' => $icon[ 'event' ],
127                ],
128                'quiet',
129                'default',
130                true,
131                '#'
132            );
133        }
134        return $iconButtons;
135    }
136
137    /**
138     * Creates button data for the "Add section" button in the sticky header
139     *
140     * @return VectorComponentButton
141     */
142    private function getAddSectionButton() {
143        return new VectorComponentButton(
144            $this->msg( [ 'vector-2022-action-addsection', 'skin-action-addsection' ] )->text(),
145            'speechBubbleAdd-progressive',
146            'ca-addsection-sticky-header',
147            '',
148            [
149                'tabindex' => '-1',
150                'data-event-name' => 'addsection-sticky-header'
151            ],
152            'quiet',
153            'progressive',
154            false,
155            '#'
156        );
157    }
158
159    /**
160     * Creates button data for the "search" button in the sticky header
161     *
162     * @param array $searchBoxData
163     * @return VectorComponentButton
164     */
165    private function getSearchButton( $searchBoxData ) {
166        return new VectorComponentButton(
167            $this->msg( 'search' ),
168            'search',
169            null,
170            'vector-sticky-header-search-toggle',
171            [
172                'tabindex' => '-1',
173                'data-event-name' => 'ui.' . $searchBoxData['form-id'] . '.icon'
174            ],
175            'quiet',
176            'default',
177            true
178        );
179    }
180
181    /**
182     * @inheritDoc
183     */
184    public function getTemplateData(): array {
185        $iconButtonData = array_map( static function ( $btn ) {
186            return $btn->getTemplateData();
187        }, $this->getIconButtons() );
188        $buttonData = $this->langButton ? [ $this->langButton->getTemplateData() ] : [];
189        $buttonData[] = $this->getAddSectionButton()->getTemplateData();
190        $searchBoxData = $this->search->getTemplateData();
191        $searchButtonData = $this->getSearchButton( $searchBoxData )->getTemplateData();
192        return [
193            'array-icon-buttons' => $iconButtonData,
194            'array-buttons' => $buttonData,
195            'data-button-start' => $searchButtonData,
196            'data-search' => $searchBoxData,
197        ];
198    }
199}