Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.sleazyfork.org/scripts/416105/890568/Brazen%20Base%20Search%20Enhancer.js
// ==UserScript==
// @name Brazen Base Search Enhancer
// @namespace brazenvoid
// @version 2.7.3
// @author brazenvoid
// @license GPL-3.0-only
// @description Base class for search enhancement scripts
// ==/UserScript==
const FILTER_DURATION_RANGE = 'Duration'
const FILTER_PERCENTAGE_RATING_RANGE = 'Rating'
const FILTER_TEXT_BLACKLIST = 'Blacklist'
const FILTER_TEXT_SEARCH = 'Search'
const FILTER_TEXT_SANITIZATION = 'Text Sanitization Rules'
const FILTER_TEXT_WHITELIST = 'Whitelist'
const FILTER_UNRATED = 'Unrated'
const ITEM_NAME_DOM_KEY = 'scriptItemName'
const ITEM_PROCESSED_ONCE_DOM_KEY = 'scriptProcessedOnce'
const CONFIG_PAGINATOR_LIMIT = 'Pagination Limit'
const CONFIG_PAGINATOR_THRESHOLD = 'Pagination Threshold'
const OPTION_ALWAYS_SHOW_SETTINGS_PANE = 'Always Show Settings Pane'
const OPTION_DISABLE_COMPLIANCE_VALIDATION = 'Disable All Filters'
class BrazenPaginator
{
/**
* @callback PaginatorAfterPaginationEventHandler
* @param {BrazenPaginator} paginator
*/
/**
* @callback PaginatorGetPageNoFromUrlHandler
* @param {string} pageUrl
* @param {BrazenPaginator} paginator
*/
/**
* @callback PaginatorGetPageUrlFromPageNoHandler
* @param {number} pageNo
* @param {BrazenPaginator} paginator
*/
/**
* @callback PaginatorGetPaginationElementForPageNoHandler
* @param {number} pageNo
* @param {BrazenPaginator} paginator
*/
/**
* @param {JQuery} paginationWrapper
* @param {JQuery.Selector} listSelector
* @param {JQuery.Selector} itemClassesSelector
* @param {string } lastPageUrl
* @return {BrazenPaginator}
*/
static create (paginationWrapper, listSelector, itemClassesSelector, lastPageUrl)
{
return (new BrazenPaginator).configure(paginationWrapper, listSelector, itemClassesSelector, lastPageUrl)
}
/**
*
*/
constructor ()
{
/**
* @type {number}
* @private
*/
this._currentPageNo = 0
/**
* @type {JQuery.Selector}
* @private
*/
this._itemClassesSelector = ''
/**
* @type {number}
* @private
*/
this._lastPageNo = 0
/**
* @type {string}
* @private
*/
this._lastPageUrl = ''
/**
* @type {JQuery.Selector}
* @private
*/
this._listSelector = ''
/**
* @type {boolean}
* @private
*/
this._pageConcatenated = false
/**
* @type {number}
* @private
*/
this._paginatedPageNo = 0
/**
* @type {JQuery}
* @private
*/
this._paginationWrapper = null
/**
* @type {JQuery}
* @private
*/
this._targetElement = null
// Events and callbacks
/**
* @type {PaginatorAfterPaginationEventHandler}
* @private
*/
this._onAfterPagination = null
/**
* @type {PaginatorGetPageNoFromUrlHandler}
* @private
*/
this._onGetPageNoFromUrl = null
/**
* @type {PaginatorGetPageUrlFromPageNoHandler}
* @private
*/
this._onGetPageUrlFromPageNo = null
/**
* @type {PaginatorGetPaginationElementForPageNoHandler}
* @private
*/
this._onGetPaginationElementForPageNo = null
}
_conformUIToNewPaginatedState ()
{
if (this._pageConcatenated) {
this._pageConcatenated = false
let currentPageElement = this.getPaginationElementForPageNo(this._currentPageNo)
let newSubsequentPageNo = this._paginatedPageNo + 1
let newSubsequentPageNoUrl = this.getPageUrlFromPageNo(newSubsequentPageNo)
// Mutate current page no element to show paginated page numbers
currentPageElement.text(this._currentPageNo + '-' + this._paginatedPageNo)
// Get next pages' pagination elements
let currentNextPageElements = currentPageElement.nextAll()
if (this._paginatedPageNo === this._lastPageNo) {
// Delete all pagination elements if last page is paginated
currentNextPageElements.remove()
} else {
// Determine whether the paginated page immediately precedes the last page
if (newSubsequentPageNo !== this._lastPageNo) {
// If not so, determine whether pagination element for the page following the paginated page exists
let newSubsequentPageElement = this.getPaginationElementForPageNo(newSubsequentPageNo)
if (!newSubsequentPageElement.length) {
// If it does not exist then try getting the old next page no element
let oldSubsequentPageElement = this.getPaginationElementForPageNo(this._currentPageNo + 1)
if (oldSubsequentPageElement.length) {
// If it does exist then mutate it for this purpose
oldSubsequentPageElement.attr('href', newSubsequentPageNoUrl).text(newSubsequentPageNo)
} else {
// If even that does not exist, then clone the less desirable alternative; the last page element and mutate it to this use
let lastPageElement = this.getPaginationElementForPageNo(this._lastPageNo)
lastPageElement.clone().insertAfter(currentPageElement).attr('href', newSubsequentPageNoUrl).text(newSubsequentPageNo)
}
}
// Remove any other pagination elements for already paginated pages
currentNextPageElements.each((index, element) => {
let paginationLink = $(element)
if (this.getPageNoFromUrl(paginationLink.attr('href')) <= this._paginatedPageNo) {
paginationLink.remove()
}
})
}
}
Utilities.callEventHandler(this._onAfterPagination, [this])
}
}
/**
* @param {number} threshold
* @param {number} limit
* @private
*/
_loadAndParseNextPage (threshold, limit)
{
let lastPageHasNotBeenReached = this._paginatedPageNo < this._lastPageNo
let paginationLimitHasNotBeenMet = limit > 0 && (this._paginatedPageNo - this._currentPageNo) < limit
let compliantItemsAreLessThanTheThreshold = this._targetElement.find(this._itemClassesSelector + ':not(.noncompliant-item)').length < threshold
if (lastPageHasNotBeenReached && paginationLimitHasNotBeenMet && compliantItemsAreLessThanTheThreshold) {
this._sandbox.load(this.getPageUrlFromPageNo(++this._paginatedPageNo) + ' ' + this._listSelector, '', () => {
this._pageConcatenated = true
this._sandbox.find(this._itemClassesSelector).insertAfter(this._targetElement.find(this._itemClassesSelector + ':last'))
this._sandbox.empty()
})
} else {
this._conformUIToNewPaginatedState()
}
}
/**
* @param {JQuery} paginationWrapper
* @param {JQuery.Selector} listSelector
* @param {JQuery.Selector} itemClassesSelector
* @param {string } lastPageUrl
* @return {BrazenPaginator}
*/
configure (paginationWrapper, listSelector, itemClassesSelector, lastPageUrl)
{
this._lastPageUrl = lastPageUrl
this._listSelector = listSelector
this._itemClassesSelector = itemClassesSelector
this._paginationWrapper = paginationWrapper
return this
}
getCurrentPageNo ()
{
return this._currentPageNo
}
getLastPageNo ()
{
return this._lastPageNo
}
getListSelector ()
{
return this._listSelector
}
/**
* @param {string} pageUrl
* @return {number}
*/
getPageNoFromUrl (pageUrl)
{
return Utilities.callEventHandlerOrFail('onGetPageNoFromUrl', this._onGetPageNoFromUrl, [pageUrl, this])
}
/**
* @param {number} pageNo
* @return {string}
*/
getPageUrlFromPageNo (pageNo)
{
return Utilities.callEventHandlerOrFail('onGetPageUrlFromPageNo', this._onGetPageUrlFromPageNo, [pageNo, this])
}
/**
* @param {number} pageNo
* @return {JQuery}
*/
getPaginationElementForPageNo (pageNo)
{
return Utilities.callEventHandlerOrFail('onGetPaginationElementForPageNo', this._onGetPaginationElementForPageNo, [pageNo, this])
}
getPaginatedPageNo ()
{
return this._paginatedPageNo
}
getPaginationWrapper ()
{
return this._paginationWrapper
}
initialize ()
{
this._currentPageNo = this.getPageNoFromUrl(window.location.href)
this._lastPageNo = this.getPageNoFromUrl(this._lastPageUrl)
this._paginatedPageNo = this._currentPageNo
this._sandbox = $('<div id="brazen-paginator-sandbox" hidden/>').appendTo('body')
this._targetElement = $(this._listSelector + ':first')
return this
}
/**
* @param {PaginatorAfterPaginationEventHandler} handler
* @return {this}
*/
onAfterPagination (handler)
{
this._onAfterPagination = handler
return this
}
/**
* @param {PaginatorGetPageNoFromUrlHandler} handler
* @return {this}
*/
onGetPageNoFromUrl (handler)
{
this._onGetPageNoFromUrl = handler
return this
}
/**
* @param {PaginatorGetPageUrlFromPageNoHandler} handler
* @return {this}
*/
onGetPageUrlFromPageNo (handler)
{
this._onGetPageUrlFromPageNo = handler
return this
}
/**
* @param {PaginatorGetPaginationElementForPageNoHandler} handler
* @return {this}
*/
onGetPaginationElementForPageNo (handler)
{
this._onGetPaginationElementForPageNo = handler
return this
}
run (threshold, limit)
{
if (this._paginationWrapper.length && threshold) {
this._loadAndParseNextPage(threshold, limit)
}
return this
}
}
class BrazenBaseSearchEnhancer
{
/**
* @typedef {{configKey: string, validate: SearchEnhancerFilterValidationCallback, comply: SearchEnhancerFilterComplianceCallback}} ComplianceFilter
*/
/**
* @callback SearchEnhancerFilterValidationCallback
* @param {*} configValues
* @return boolean
*/
/**
* @callback SearchEnhancerFilterComplianceCallback
* @param {JQuery} item
* @param {*} configValues
* @return {*}
*/
/**
* @return BrazenBaseSearchEnhancer
*/
static initialize ()
{
BrazenBaseSearchEnhancer.throwOverrideError()
}
static throwOverrideError ()
{
throw new Error('Method must be overridden.')
}
/**
* @param {string} scriptPrefix
* @param {string|string[]} itemSelectors
*/
constructor (scriptPrefix, itemSelectors)
{
/**
* Array of item compliance filters ordered in intended sequence of execution
* @type {ComplianceFilter[]}
* @protected
*/
this._complianceFilters = []
/**
* @type {string}
* @protected
*/
this._itemClassesSelector = '.' + (Array.isArray(itemSelectors) ? itemSelectors : [itemSelectors]).join(',.')
/**
* Pagination manager
* @type BrazenPaginator|null
* @protected
*/
this._paginator = null
/**
* @type {string}
* @protected
*/
this._scriptPrefix = scriptPrefix
/**
* @type {StatisticsRecorder}
* @protected
*/
this._statistics = new StatisticsRecorder(this._scriptPrefix)
/**
* @type {BrazenUIGenerator}
* @protected
*/
this._uiGen = new BrazenUIGenerator(this._scriptPrefix)
/**
* Local storage store with defaults
* @type {BrazenConfigurationManager}
* @protected
*/
this._configurationManager = BrazenConfigurationManager.create(this._uiGen).
addFlagField(OPTION_DISABLE_COMPLIANCE_VALIDATION, 'Disables all search filters.').
addFlagField(OPTION_ALWAYS_SHOW_SETTINGS_PANE, 'Always show configuration interface.')
// Events
/**
* Operations to perform after script initialization
* @type {Function}
* @protected
*/
this._onAfterInitialization = null
/**
* Operations to perform after UI generation
* @type {Function}
* @protected
*/
this._onAfterUIBuild = null
/**
* Operations to perform before compliance validation. This callback can also be used to skip compliance validation by returning false.
* @type {null}
* @protected
*/
this._onBeforeCompliance = null
/**
* Operations to perform before UI generation
* @type {Function}
* @protected
*/
this._onBeforeUIBuild = null
/**
* Operations to perform after compliance checks, the first time a item is retrieved
* @type {Function}
* @protected
*/
this._onFirstHitAfterCompliance = null
/**
* Operations to perform before compliance checks, the first time a item is retrieved
* @type {Function}
* @protected
*/
this._onFirstHitBeforeCompliance = null
/**
* Get item lists from the page
* @type {Function}
* @protected
*/
this._onGetItemLists = null
/**
* @type {Function}
* @protected
*/
this._onGetItemName = null
/**
* Logic to hide a non-compliant item
* @type {Function}
* @protected
*/
this._onItemHide = (item) => {
item.addClass('noncompliant-item')
item.hide()
}
/**
* Logic to show compliant item
* @type {Function}
* @protected
*/
this._onItemShow = (item) => {
item.removeClass('noncompliant-item')
item.show()
}
/**
* Must return the generated settings section node
* @type {Function}
* @protected
*/
this._onUIBuild = null
/**
* Validate initiating initialization.
* Can be used to stop script init on specific pages or vice versa
* @type {Function}
* @protected
*/
this._onValidateInit = () => true
}
/**
* @param {string} configKey
* @param {SearchEnhancerFilterValidationCallback} validationCallback
* @param {SearchEnhancerFilterComplianceCallback} complianceCallback
* @protected
*/
_addItemComplianceFilter (configKey, validationCallback, complianceCallback)
{
this._complianceFilters.push({
configKey: configKey,
validate: validationCallback,
comply: complianceCallback,
})
}
/**
* @param {string} helpText
* @protected
*/
_addItemBlacklistFilter (helpText)
{
this._configurationManager.addRulesetField(
FILTER_TEXT_BLACKLIST, helpText, null, null, (rules) => Utilities.buildWholeWordMatchingRegex(rules) ?? '')
this._addItemComplianceFilter(
FILTER_TEXT_BLACKLIST, (value) => value !== '', (item, value) => item[0][ITEM_NAME_DOM_KEY].match(value) === null
)
}
/**
* @param {JQuery.Selector} durationNodeSelector
* @param {string|null} helpText
* @protected
*/
_addItemDurationRangeFilter (durationNodeSelector, helpText = null)
{
this._configurationManager.addRangeField(FILTER_DURATION_RANGE, 0, 100000, helpText ?? 'Filter items by duration.')
this._addItemComplianceFilter(FILTER_DURATION_RANGE, (range) => range.minimum > 0 || range.maximum > 0, (item, range) => {
let duration = 0
let durationNode = item.find(durationNodeSelector)
if (durationNode.length) {
duration = durationNode.text().split(':')
duration = (parseInt(duration[0]) * 60) + parseInt(duration[1])
}
return duration === 0 ? !durationNode.length : Validator.isInRange(duration, range.minimum, range.maximum)
})
}
/**
* @param {JQuery.Selector} ratingNodeSelector
* @param {string|null} helpText
* @param {string|null} unratedHelpText
* @protected
*/
_addItemPercentageRatingRangeFilter (ratingNodeSelector, helpText = null, unratedHelpText = null)
{
this._configurationManager.
addRangeField(FILTER_PERCENTAGE_RATING_RANGE, 0, 100000, helpText ?? 'Filter items by percentage rating.').
addFlagField(FILTER_UNRATED, unratedHelpText ?? 'Hide items with zero or no rating.')
this._addItemComplianceFilter(FILTER_PERCENTAGE_RATING_RANGE, (range) => range.minimum > 0 || range.maximum > 0, (item, range) => {
let rating = item.find(ratingNodeSelector)
rating = rating.length === 0 ? 0 : parseInt(rating.text().replace('%', ''))
return rating === 0 ? !this._configurationManager.getValue(FILTER_UNRATED) : Validator.isInRange(rating, range.minimum, range.maximum)
})
}
/**
* @param {string} helpText
* @protected
*/
_addItemTextSanitizationFilter (helpText)
{
this._configurationManager.addRulesetField(FILTER_TEXT_SANITIZATION, helpText, (rules) => {
let sanitizationRules = {}, fragments, validatedTargetWords
for (let sanitizationRule of rules) {
if (sanitizationRule.includes('=')) {
fragments = sanitizationRule.split('=')
if (fragments[0] === '') {
fragments[0] = ' '
}
validatedTargetWords = Utilities.trimAndKeepNonEmptyStrings(fragments[1].split(','))
if (validatedTargetWords.length) {
sanitizationRules[fragments[0]] = validatedTargetWords
}
}
}
return sanitizationRules
}, (rules) => {
let sanitizationRulesText = []
for (let substitute in rules) {
sanitizationRulesText.push(substitute + '=' + rules[substitute].join(','))
}
return sanitizationRulesText
}, (rules) => {
let optimizedRules = {}
for (const substitute in rules) {
optimizedRules[substitute] = Utilities.buildWholeWordMatchingRegex(rules[substitute])
}
return optimizedRules
})
}
/**
* @param {string|null} helpText
* @protected
*/
_addItemTextSearchFilter (helpText = null)
{
this._configurationManager.addTextField(
FILTER_TEXT_SEARCH, helpText ?? 'Show videos with these comma separated words in their names.')
this._addItemComplianceFilter(
FILTER_TEXT_SEARCH, (value) => value.length, (item, value) => item[0][ITEM_NAME_DOM_KEY].includes(value))
}
/**
* @param {string} helpText
* @protected
*/
_addItemWhitelistFilter (helpText)
{
this._configurationManager.addRulesetField(
FILTER_TEXT_WHITELIST, helpText, null, null, (rules) => Utilities.buildWholeWordMatchingRegex(rules))
}
_addPaginationConfiguration ()
{
this._configurationManager.
addNumberField(CONFIG_PAGINATOR_LIMIT, 1, 50, 'Limit paginator to concatenate the specified number of maximum pages.').
addNumberField(CONFIG_PAGINATOR_THRESHOLD, 1, 1000, 'Make paginator ensure the specified number of minimum results.')
}
/**
* Filters items as per settings
* @param {JQuery} itemsList
* @param {boolean} fromObserver
* @protected
*/
_complyItemsList (itemsList, fromObserver = false)
{
let items = fromObserver ? itemsList.filter(this._itemClassesSelector) : itemsList.find(this._itemClassesSelector)
items.each((index, element) => {
let item = $(element)
// Before compliance filtering
if (typeof element[ITEM_PROCESSED_ONCE_DOM_KEY] === 'undefined') {
element[ITEM_PROCESSED_ONCE_DOM_KEY] = false
Utilities.callEventHandler(this._onFirstHitBeforeCompliance, [item])
element[ITEM_NAME_DOM_KEY] = Utilities.callEventHandlerOrFail('getItemName', this._onGetItemName, [item])
}
// Compliance filtering
let itemComplies = true
if (!this._configurationManager.getValue(OPTION_DISABLE_COMPLIANCE_VALIDATION) &&
this._validateItemWhiteList(item) &&
Utilities.callEventHandler(this._onBeforeCompliance, [item], true)
) {
let configField
for (let complianceFilter of this._complianceFilters) {
configField = this._configurationManager.getFieldOrFail(complianceFilter.configKey)
if (complianceFilter.validate(configField.optimized ?? configField.value)) {
itemComplies = complianceFilter.comply(item, configField.optimized ?? configField.value)
this._statistics.record(complianceFilter.configKey, itemComplies)
if (!itemComplies) {
break
}
}
}
}
itemComplies ? Utilities.callEventHandler(this._onItemShow, [item]) : Utilities.callEventHandler(this._onItemHide, [item])
// After compliance filtering
if (!element[ITEM_PROCESSED_ONCE_DOM_KEY]) {
Utilities.callEventHandler(this._onFirstHitAfterCompliance, [item])
element[ITEM_PROCESSED_ONCE_DOM_KEY] = true
}
})
this._statistics.updateUI()
}
/**
* @protected
*/
_createSettingsFormActions ()
{
return this._uiGen.createFormSection().append([
this._uiGen.createFormActions([
this._uiGen.createFormButton('Apply', 'Apply settings.', () => this._onApplyNewSettings()),
this._uiGen.createFormButton('Save', 'Apply and update saved configuration.', () => this._onSaveSettings()),
this._uiGen.createFormButton('Reset', 'Revert to saved configuration.', () => this._onResetSettings()),
]),
])
}
/**
* @protected
*/
_createSettingsBackupRestoreFormActions ()
{
return this._uiGen.createFormSection('Backup & Restore').append([
this._uiGen.createFormActions([
this._uiGen.createFormButton('Backup', 'Backup settings to the clipboard.', () => this._onBackupSettings()),
this._uiGen.createFormGroupInput('text').attr('id', 'restore-settings').attr('placeholder', 'Paste settings...'),
this._uiGen.createFormButton('Restore', 'Restore backup settings.', () => this._onRestoreSettings()),
], 'single-column-layout'),
])
}
/**
* @param {JQuery} UISection
* @private
*/
_embedUI (UISection)
{
UISection.on('mouseleave', (event) => {
if (!this._configurationManager.getValue(OPTION_ALWAYS_SHOW_SETTINGS_PANE)) {
$(event.currentTarget).hide(300)
}
})
if (this._configurationManager.getValue(OPTION_ALWAYS_SHOW_SETTINGS_PANE)) {
UISection.show()
}
this._uiGen.constructor.appendToBody(UISection)
this._uiGen.constructor.appendToBody(this._uiGen.createSettingsShowButton('', UISection))
}
/**
* @private
*/
_onApplyNewSettings ()
{
this._configurationManager.update()
this._validateCompliance()
}
/**
* @private
*/
_onBackupSettings ()
{
navigator.clipboard.writeText(this._configurationManager.backup()).
then(() => this._uiGen.updateStatus('Settings backed up to clipboard!')).
catch(() => this._uiGen.updateStatus('Settings backup failed!'));
}
/**
* @private
*/
_onResetSettings ()
{
this._configurationManager.revertChanges()
this._validateCompliance()
}
/**
* @private
*/
_onRestoreSettings ()
{
let settings = $('#restore-settings').val().trim()
if (!settings) {
this._uiGen.updateStatus('No Settings provided!', true)
Utilities.sleep(3000).then(() => this._uiGen.resetStatus())
} else {
try {
this._configurationManager.restore(settings)
this._uiGen.updateStatus('Settings restored!')
this._validateCompliance()
} catch (e) {
this._uiGen.updateStatus('Settings restoration failed!')
}
}
}
/**
* @private
*/
_onSaveSettings ()
{
this._onApplyNewSettings()
this._configurationManager.save()
}
/**
* @protected
*/
_showNotLoggedInAlert ()
{
alert('You need to be logged in to use this functionality')
}
/**
* @param {boolean} firstRun
* @protected
*/
_validateCompliance (firstRun = false)
{
let itemLists = Utilities.callEventHandler(this._onGetItemLists)
if (!firstRun) {
this._statistics.reset()
itemLists.each((index, itemsList) => {
this._complyItemsList($(itemsList))
})
} else {
itemLists.each((index, itemList) => {
let itemListObject = $(itemList)
if (this._paginator && itemListObject.is(this._paginator.getListSelector())) {
ChildObserver.create().onNodesAdded((itemsAdded) => {
this._complyItemsList($(itemsAdded), true)
this._paginator.run(this._configurationManager.getValue(CONFIG_PAGINATOR_THRESHOLD), this._configurationManager.getValue(CONFIG_PAGINATOR_LIMIT))
}).observe(itemList)
} else {
ChildObserver.create().onNodesAdded((itemsAdded) => this._complyItemsList($(itemsAdded), true)).observe(itemList)
}
this._complyItemsList(itemListObject)
})
}
if (this._paginator) {
this._paginator.run(this._configurationManager.getValue(CONFIG_PAGINATOR_THRESHOLD), this._configurationManager.getValue(CONFIG_PAGINATOR_LIMIT))
}
}
/**
* @param {JQuery} item
* @return {boolean}
* @protected
*/
_validateItemWhiteList (item)
{
let field = this._configurationManager.getField(FILTER_TEXT_WHITELIST)
if (field) {
let validationResult = field.value.length ? Validator.regexMatches(item[0][ITEM_NAME_DOM_KEY], field.optimized) : true
this._statistics.record(FILTER_TEXT_WHITELIST, validationResult)
return validationResult
}
return true
}
/**
* Initialize the script and do basic UI removals
*/
init ()
{
if (Utilities.callEventHandler(this._onValidateInit)) {
this._configurationManager.initialize(this._scriptPrefix)
if (this._paginator) {
this._paginator.initialize()
}
Utilities.callEventHandler(this._onBeforeUIBuild)
this._embedUI(Utilities.callEventHandler(this._onUIBuild))
Utilities.callEventHandler(this._onAfterUIBuild)
this._configurationManager.updateInterface()
this._validateCompliance(true)
this._uiGen.updateStatus('Initial run completed.')
Utilities.callEventHandler(this._onAfterInitialization)
}
}
}