Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4109738
ChangesListWrapperWidget.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
ChangesListWrapperWidget.js
View Options
const
HighlightColors
=
require
(
'../HighlightColors.js'
);
/**
* List of changes.
*
* @class mw.rcfilters.ui.ChangesListWrapperWidget
* @ignore
* @extends OO.ui.Widget
*
* @param {mw.rcfilters.dm.FiltersViewModel} filtersViewModel View model
* @param {mw.rcfilters.dm.ChangesListViewModel} changesListViewModel View model
* @param {mw.rcfilters.Controller} controller
* @param {jQuery} $changesListRoot Root element of the changes list to attach to
* @param {Object} [config] Configuration object
*/
const
ChangesListWrapperWidget
=
function
MwRcfiltersUiChangesListWrapperWidget
(
filtersViewModel
,
changesListViewModel
,
controller
,
$changesListRoot
,
config
)
{
config
=
Object
.
assign
(
{},
config
,
{
$element
:
$changesListRoot
}
);
// Parent
ChangesListWrapperWidget
.
super
.
call
(
this
,
config
);
this
.
filtersViewModel
=
filtersViewModel
;
this
.
changesListViewModel
=
changesListViewModel
;
this
.
controller
=
controller
;
// Events
this
.
filtersViewModel
.
connect
(
this
,
{
itemUpdate
:
'onItemUpdate'
,
highlightChange
:
'onHighlightChange'
}
);
this
.
changesListViewModel
.
connect
(
this
,
{
invalidate
:
'onModelInvalidate'
,
update
:
'onModelUpdate'
}
);
this
.
$element
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget'
)
// We handle our own display/hide of the empty results message
// We keep the timeout class here and remove it later, since at this
// stage it is still needed to identify that the timeout occurred.
.
removeClass
(
'mw-changeslist-empty'
);
};
/* Initialization */
OO
.
inheritClass
(
ChangesListWrapperWidget
,
OO
.
ui
.
Widget
);
/**
* Respond to the highlight feature being toggled on and off
*
* @param {boolean} highlightEnabled
*/
ChangesListWrapperWidget
.
prototype
.
onHighlightChange
=
function
(
highlightEnabled
)
{
if
(
highlightEnabled
)
{
this
.
applyHighlight
();
}
else
{
this
.
clearHighlight
();
}
};
/**
* Respond to a filter item model update
*/
ChangesListWrapperWidget
.
prototype
.
onItemUpdate
=
function
()
{
if
(
this
.
controller
.
isInitialized
()
&&
this
.
filtersViewModel
.
isHighlightEnabled
()
)
{
// this.controller.isInitialized() is still false during page load,
// we don't want to clear/apply highlights at this stage.
this
.
clearHighlight
();
this
.
applyHighlight
();
}
};
/**
* Respond to changes list model invalidate
*/
ChangesListWrapperWidget
.
prototype
.
onModelInvalidate
=
function
()
{
$
(
document
.
body
).
addClass
(
'mw-rcfilters-ui-loading'
);
};
/**
* Respond to changes list model update
*
* @param {jQuery|string} $changesListContent The content of the updated changes list
* @param {jQuery} $fieldset The content of the updated fieldset
* @param {string} noResultsDetails Type of no result error
* @param {boolean} isInitialDOM Whether $changesListContent is the existing (already attached) DOM
* @param {boolean} from Timestamp of the new changes
*/
ChangesListWrapperWidget
.
prototype
.
onModelUpdate
=
function
(
$changesListContent
,
$fieldset
,
noResultsDetails
,
isInitialDOM
,
from
)
{
const
$message
=
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-results'
),
isEmpty
=
$changesListContent
===
'NO_RESULTS'
,
// For enhanced mode, we have to load this modules, which is
// not loaded for the 'regular' mode in the backend
loaderPromise
=
mw
.
user
.
options
.
get
(
'usenewrc'
)
&&
!
OO
.
ui
.
isMobile
()
?
mw
.
loader
.
using
(
[
'mediawiki.special.changeslist.enhanced'
]
)
:
$
.
Deferred
().
resolve
(),
widget
=
this
;
this
.
$element
.
toggleClass
(
'mw-changeslist'
,
!
isEmpty
);
if
(
isEmpty
)
{
this
.
$element
.
empty
();
if
(
this
.
filtersViewModel
.
hasConflict
()
)
{
const
conflictItem
=
this
.
filtersViewModel
.
getFirstConflictedItem
();
$message
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-results-conflict'
)
.
text
(
mw
.
msg
(
'rcfilters-noresults-conflict'
)
),
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-results-message'
)
// TODO: Document possible messages
// eslint-disable-next-line mediawiki/msg-doc
.
text
(
mw
.
msg
(
conflictItem
.
getCurrentConflictResultMessage
()
)
)
);
}
else
{
$message
.
append
(
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-results-noresult'
)
// The following messages can be used here:
// * recentchanges-noresult
// * recentchanges-timeout
// * recentchanges-network
// * recentchanges-notargetpage
// * allpagesbadtitle
.
html
(
mw
.
message
(
this
.
getMsgKeyForNoResults
(
noResultsDetails
)
).
parse
()
)
);
// remove all classes matching mw-changeslist-*
// eslint-disable-next-line mediawiki/class-doc
this
.
$element
.
removeClass
(
(
elementIndex
,
allClasses
)
=>
allClasses
.
split
(
' '
)
.
filter
(
(
className
)
=>
className
.
indexOf
(
'mw-changeslist-'
)
===
0
)
.
join
(
' '
)
);
}
this
.
$element
.
append
(
$message
);
}
else
{
if
(
!
isInitialDOM
)
{
this
.
$element
.
empty
().
append
(
$changesListContent
);
if
(
from
)
{
this
.
emphasizeNewChanges
(
from
);
}
}
// Apply highlight
this
.
applyHighlight
();
}
this
.
$element
.
prepend
(
$
(
'<div>'
).
addClass
(
'mw-changeslist-overlay'
)
);
loaderPromise
.
done
(
()
=>
{
if
(
!
isInitialDOM
&&
!
isEmpty
)
{
// Make sure enhanced RC re-initializes correctly
mw
.
hook
(
'wikipage.content'
).
fire
(
widget
.
$element
);
}
$
(
document
.
body
).
removeClass
(
'mw-rcfilters-ui-loading'
);
}
);
};
/**
* Toggles overlay class on changes list
*
* @param {boolean} isVisible True if overlay should be visible
*/
ChangesListWrapperWidget
.
prototype
.
toggleOverlay
=
function
(
isVisible
)
{
this
.
$element
.
toggleClass
(
'mw-rcfilters-ui-changesListWrapperWidget--overlaid'
,
isVisible
);
};
/**
* Map a reason for having no results to its message key
*
* @param {string} reason One of the NO_RESULTS_* "constant" that represent
* a reason for having no results
* @return {string} Key for the message that explains why there is no results in this case
*/
ChangesListWrapperWidget
.
prototype
.
getMsgKeyForNoResults
=
function
(
reason
)
{
const
reasonMsgKeyMap
=
{
NO_RESULTS_NORMAL
:
'recentchanges-noresult'
,
NO_RESULTS_TIMEOUT
:
'recentchanges-timeout'
,
NO_RESULTS_NETWORK_ERROR
:
'recentchanges-network'
,
NO_RESULTS_NO_TARGET_PAGE
:
'recentchanges-notargetpage'
,
NO_RESULTS_INVALID_TARGET_PAGE
:
'allpagesbadtitle'
};
return
reasonMsgKeyMap
[
reason
];
};
/**
* Emphasize the elements (or groups) newer than the 'from' parameter
*
* @param {string} from Anything newer than this is considered 'new'
*/
ChangesListWrapperWidget
.
prototype
.
emphasizeNewChanges
=
function
(
from
)
{
const
selector
=
this
.
inEnhancedMode
()
?
'table.mw-enhanced-rc[data-mw-ts]'
:
'li[data-mw-ts]'
,
$set
=
this
.
$element
.
find
(
selector
),
length
=
$set
.
length
;
let
$firstNew
,
$newChanges
=
$
(
[]
);
$set
.
each
(
function
(
index
)
{
const
$this
=
$
(
this
),
ts
=
$this
.
data
(
'mw-ts'
);
if
(
ts
>=
from
)
{
$newChanges
=
$newChanges
.
add
(
$this
);
$firstNew
=
$this
;
// guards against putting the marker after the last element
if
(
index
===
(
length
-
1
)
)
{
$firstNew
=
null
;
}
}
}
);
if
(
$firstNew
)
{
const
$indicator
=
$
(
'<div>'
)
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-previousChangesIndicator'
);
$firstNew
.
after
(
$indicator
);
}
// FIXME: Use CSS transition
// eslint-disable-next-line no-jquery/no-fade
$newChanges
.
hide
()
.
fadeIn
(
1000
);
};
/**
* In enhanced mode, we need to check whether the grouped results all have the
* same active highlights in order to see whether the "parent" of the group should
* be grey or highlighted normally.
*
* This is called every time highlights are applied.
*/
ChangesListWrapperWidget
.
prototype
.
updateEnhancedParentHighlight
=
function
()
{
const
$enhancedTopPageCell
=
this
.
$element
.
find
(
'table.mw-enhanced-rc'
);
const
activeHighlightClasses
=
this
.
filtersViewModel
.
getCurrentlyUsedHighlightColors
().
map
(
(
color
)
=>
'mw-rcfilters-highlight-color-'
+
color
);
// Go over top pages and their children, and figure out if all subpages have the
// same highlights between themselves. If they do, the parent should be highlighted
// with all colors. If classes are different, the parent should receive a grey
// background
$enhancedTopPageCell
.
each
(
function
()
{
const
$table
=
$
(
this
);
// Collect the relevant classes from the first nested child
const
firstChildClasses
=
activeHighlightClasses
.
filter
(
// eslint-disable-next-line no-jquery/no-class-state
(
className
)
=>
$table
.
find
(
'tr'
).
eq
(
2
).
hasClass
(
className
)
);
// Filter the non-head rows and see if they all have the same classes
// to the first row
const
$rowsWithDifferentHighlights
=
$table
.
find
(
'tr:not(:first-child)'
).
filter
(
function
()
{
const
$this
=
$
(
this
);
const
classesInThisRow
=
activeHighlightClasses
.
filter
(
// eslint-disable-next-line no-jquery/no-class-state
(
className
)
=>
$this
.
hasClass
(
className
)
);
return
!
OO
.
compare
(
firstChildClasses
,
classesInThisRow
);
}
);
// If classes are different, tag the row for using grey color
$table
.
find
(
'tr:first-child'
)
.
toggleClass
(
'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey'
,
$rowsWithDifferentHighlights
.
length
>
0
);
}
);
};
/**
* @return {boolean} Whether the changes are grouped by page
*/
ChangesListWrapperWidget
.
prototype
.
inEnhancedMode
=
function
()
{
const
enhanced
=
new
URL
(
location
.
href
).
searchParams
.
get
(
'enhanced'
);
return
(
enhanced
!==
null
&&
Number
(
enhanced
)
)
||
(
enhanced
===
null
&&
Number
(
mw
.
user
.
options
.
get
(
'usenewrc'
)
)
);
};
/**
* Apply color classes based on filters highlight configuration
*/
ChangesListWrapperWidget
.
prototype
.
applyHighlight
=
function
()
{
if
(
!
this
.
filtersViewModel
.
isHighlightEnabled
()
)
{
return
;
}
this
.
filtersViewModel
.
getHighlightedItems
().
forEach
(
(
filterItem
)
=>
{
const
$elements
=
this
.
$element
.
find
(
'.'
+
filterItem
.
getCssClass
()
);
// Add highlight class to all highlighted list items
// The following classes are used here:
// * mw-rcfilters-highlight-color-c1
// * mw-rcfilters-highlight-color-c2
// * mw-rcfilters-highlight-color-c3
// * mw-rcfilters-highlight-color-c4
// * mw-rcfilters-highlight-color-c5
// * notheme - T366920 Makes highlighted list legible in dark-mode.
$elements
.
addClass
(
'mw-rcfilters-highlighted '
+
'mw-rcfilters-highlight-color-'
+
filterItem
.
getHighlightColor
()
);
// Track the filters for each item in .data( 'highlightedFilters' )
$elements
.
each
(
function
()
{
let
filters
=
$
(
this
).
data
(
'highlightedFilters'
);
if
(
!
filters
)
{
filters
=
[];
$
(
this
).
data
(
'highlightedFilters'
,
filters
);
}
if
(
filters
.
indexOf
(
filterItem
.
getLabel
()
)
===
-
1
)
{
filters
.
push
(
filterItem
.
getLabel
()
);
}
}
);
}
);
// Apply a title to each highlighted item, with a list of filters
this
.
$element
.
find
(
'.mw-rcfilters-highlighted'
).
each
(
function
()
{
const
filters
=
$
(
this
).
data
(
'highlightedFilters'
);
if
(
filters
&&
filters
.
length
)
{
$
(
this
).
attr
(
'title'
,
mw
.
msg
(
'rcfilters-highlighted-filters-list'
,
filters
.
join
(
mw
.
msg
(
'comma-separator'
)
)
)
);
}
}
);
if
(
this
.
inEnhancedMode
()
)
{
this
.
updateEnhancedParentHighlight
();
}
// Turn on highlights
this
.
$element
.
addClass
(
'mw-rcfilters-ui-changesListWrapperWidget-highlighted'
);
};
/**
* Remove all color classes
*/
ChangesListWrapperWidget
.
prototype
.
clearHighlight
=
function
()
{
// Remove highlight classes
HighlightColors
.
forEach
(
(
color
)
=>
{
// The following classes are used here:
// * mw-rcfilters-highlight-color-c1
// * mw-rcfilters-highlight-color-c2
// * mw-rcfilters-highlight-color-c3
// * mw-rcfilters-highlight-color-c4
// * mw-rcfilters-highlight-color-c5
this
.
$element
.
find
(
'.mw-rcfilters-highlight-color-'
+
color
)
.
removeClass
(
'mw-rcfilters-highlight-color-'
+
color
);
}
);
this
.
$element
.
find
(
'.mw-rcfilters-highlighted'
)
.
removeAttr
(
'title'
)
.
removeData
(
'highlightedFilters'
)
.
removeClass
(
'mw-rcfilters-highlighted'
);
// Remove grey from enhanced rows
this
.
$element
.
find
(
'.mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey'
)
.
removeClass
(
'mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey'
);
// Turn off highlights
this
.
$element
.
removeClass
(
'mw-rcfilters-ui-changesListWrapperWidget-highlighted'
);
};
module
.
exports
=
ChangesListWrapperWidget
;
File Metadata
Details
Attached
Mime Type
text/x-asm
Expires
Tue, Aug 18, 21:13 (5 d, 14 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ec/f4/8a95a4b3d7ef26b1b178c5acd4ec
Default Alt Text
ChangesListWrapperWidget.js (12 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment