User:TheAstorPastor/common.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
Undo revision 60266 by TheAstorPastor (talk) Tags: Replaced Undo Reverted |
No edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
// Only run on Test Wiki:Request for Permissions page |
|||
if (mw.config.get('wgTitle') === 'Request for permissions' && |
|||
mw.config.get('wgNamespaceNumber') === 4) { // NS 4 is "Project" namespace (Test Wiki:) |
|||
$(document).ready(function() { |
|||
//importScript('User:Joepayne/grantBureaucrat.js'); // Backlink: [[User:Joepayne/grantBureaucrat.js]] |
|||
// Initialize the script |
|||
importScript('User:Joepayne/stripRights.js'); // Backlink: [[User:Joepayne/stripRights.js]] |
|||
testWikiRfPManager.init(); |
|||
//importScript('User:MacFan4000/grantAdmin.js'); // Backlink: [[User:MacFan4000/grantAdmin.js]] |
|||
}); |
|||
//importScript('User:Kiteretsu/js/all-in-one.js'); // Backlink: [[User:Kiteretsu/js/all-in-one.js]] |
|||
//importScript('User:DodoMan/chatbot.js'); // Backlink: [[User:DodoMan/chatbot.js]] |
|||
const testWikiRfPManager = { |
|||
importScript('User:JJBullet/findInactiveSysops.js'); // Backlink: [[User:JJBullet/findInactiveSysops.js]] |
|||
// Configuration |
|||
importScript('User:Harvici/find-archived-section.js'); // Backlink: [[User:Harvici/find-archived-section.js]] |
|||
config: { |
|||
importScript('User:Harvici/UTCLiveClock.js'); // Backlink: [[User:Harvici/UTCLiveClock.js]] |
|||
// Rights that can be assigned with corresponding templates |
|||
importScript('User:Harvici/Twinkle-preferences-toolbar.js'); // Backlink: [[User:Harvici/Twinkle-preferences-toolbar.js]] |
|||
rights: { |
|||
importScript('User:Kiteretsu/rollbackSum.js'); // Backlink: [[User:Kiteretsu/rollbackSum.js]] |
|||
'sysop': { |
|||
importScript('User:Kiteretsu/DiscussionCloser.js'); // Backlink: [[User:Kiteretsu/DiscussionCloser.js]] |
|||
displayName: 'Administrator', |
|||
importScript('User:TheAstorPastor/VisualEditorEverywhere.js'); // Backlink: [[User:TheAstorPastor/VisualEditorEverywhere.js]] |
|||
template: '{{administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)' |
|||
}, |
|||
'bureaucrat': { |
|||
displayName: 'Bureaucrat', |
|||
template: '{{bureaucrat granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)' |
|||
}, |
|||
'interface-admin': { |
|||
displayName: 'Interface Administrator', |
|||
template: '{{interface administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)' |
|||
}, |
|||
'non-stewardsuppress': { |
|||
displayName: 'Suppressor', |
|||
template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)' |
|||
}, |
|||
'abusefilter-admin': { |
|||
displayName: 'Abuse Filter Administrator', |
|||
template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)' |
|||
} |
|||
} |
|||
}, |
|||
// Initialize the script |
|||
init: function() { |
|||
// Check if user has the required permissions |
|||
if (!mw.config.get('wgUserGroups').includes('sysop')) { |
|||
console.log('TestWiki RfP Manager: User does not have sysop rights'); |
|||
return; |
|||
} |
|||
// Load required styles |
|||
this.loadStyles(); |
|||
// Process each RfP section |
|||
this.processRfPSections(); |
|||
}, |
|||
// Load CSS styles for the script |
|||
loadStyles: function() { |
|||
mw.loader.addStyleTag(` |
|||
.twrm-container { |
|||
background-color: #f8f9fa; |
|||
border: 1px solid #a2a9b1; |
|||
border-radius: 3px; |
|||
padding: 12px; |
|||
margin: 10px 0; |
|||
box-shadow: 0 1px 2px rgba(0,0,0,0.1); |
|||
} |
|||
.twrm-header { |
|||
font-weight: bold; |
|||
margin-bottom: 8px; |
|||
color: #222; |
|||
font-size: 14px; |
|||
padding-bottom: 5px; |
|||
border-bottom: 1px solid #eaecf0; |
|||
} |
|||
.twrm-controls { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 10px; |
|||
align-items: center; |
|||
margin-bottom: 8px; |
|||
} |
|||
.twrm-select { |
|||
padding: 6px 8px; |
|||
border: 1px solid #a2a9b1; |
|||
border-radius: 2px; |
|||
background-color: white; |
|||
min-width: 200px; |
|||
} |
|||
.twrm-button { |
|||
padding: 6px 12px; |
|||
background-color: #36c; |
|||
color: white; |
|||
border: none; |
|||
border-radius: 2px; |
|||
cursor: pointer; |
|||
font-weight: bold; |
|||
} |
|||
.twrm-button:hover { |
|||
background-color: #447ff5; |
|||
} |
|||
.twrm-button:disabled { |
|||
background-color: #c8ccd1; |
|||
cursor: not-allowed; |
|||
} |
|||
.twrm-result { |
|||
margin-top: 10px; |
|||
padding: 8px; |
|||
background-color: #eaf3ff; |
|||
border: 1px solid #c8ccd1; |
|||
border-radius: 2px; |
|||
display: none; |
|||
} |
|||
.twrm-copy-button { |
|||
padding: 4px 8px; |
|||
background-color: #f8f9fa; |
|||
border: 1px solid #a2a9b1; |
|||
border-radius: 2px; |
|||
margin-top: 8px; |
|||
cursor: pointer; |
|||
} |
|||
.twrm-copy-button:hover { |
|||
background-color: #eaecf0; |
|||
} |
|||
.twrm-user-info { |
|||
margin-bottom: 8px; |
|||
font-style: italic; |
|||
} |
|||
.twrm-success { |
|||
color: #14866d; |
|||
font-weight: bold; |
|||
margin-top: 5px; |
|||
display: none; |
|||
} |
|||
`); |
|||
}, |
|||
// Process all RfP sections on the page |
|||
processRfPSections: function() { |
|||
const self = this; |
|||
// Find all h2 headers which typically denote user request sections |
|||
$('.mw-parser-output > h2').each(function() { |
|||
const $header = $(this); |
|||
const username = $header.find('.mw-headline').text().trim(); |
|||
if (username) { |
|||
// Find the section content |
|||
let $section = $header.nextUntil('h2, h1'); |
|||
// Extract requested right from the section |
|||
const requestedRight = self.extractRequestedRight($section); |
|||
// Create rights manager UI |
|||
self.createRightsManagerUI($header, username, requestedRight); |
|||
} |
|||
}); |
|||
}, |
|||
// Extract the requested right from section content |
|||
extractRequestedRight: function($section) { |
|||
let rightText = ''; |
|||
// Look for the "Requested right" line |
|||
$section.each(function() { |
|||
const text = $(this).text(); |
|||
if (text.includes('Requested right')) { |
|||
// Extract the right from the text |
|||
const match = text.match(/Requested right[:\s]*([^*\n]+)/i); |
|||
if (match && match[1]) { |
|||
rightText = match[1].trim(); |
|||
} |
|||
return false; // Break the loop |
|||
} |
|||
}); |
|||
// Map common variations to our defined rights |
|||
const rightMap = { |
|||
'administrator': 'sysop', |
|||
'admin': 'sysop', |
|||
'sysop': 'sysop', |
|||
'bureaucrat': 'bureaucrat', |
|||
'crat': 'bureaucrat', |
|||
'interface administrator': 'interface-admin', |
|||
'interface admin': 'interface-admin', |
|||
'suppressor': 'non-stewardsuppress', |
|||
'suppress': 'non-stewardsuppress', |
|||
'abuse filter administrator': 'abusefilter-admin', |
|||
'abusefilter admin': 'abusefilter-admin', |
|||
'abuse filter admin': 'abusefilter-admin' |
|||
}; |
|||
// Try to match the requested right |
|||
for (const [key, value] of Object.entries(rightMap)) { |
|||
if (rightText.toLowerCase().includes(key.toLowerCase())) { |
|||
return value; |
|||
} |
|||
} |
|||
return ''; // No match found |
|||
}, |
|||
// Create the UI for managing rights |
|||
createRightsManagerUI: function($header, username, preselectedRight) { |
|||
const self = this; |
|||
// Create container |
|||
const $container = $('<div>') |
|||
.addClass('twrm-container') |
|||
.insertAfter($header); |
|||
// Create header |
|||
$('<div>') |
|||
.addClass('twrm-header') |
|||
.text('Rights Manager') |
|||
.appendTo($container); |
|||
// Add user info |
|||
$('<div>') |
|||
.addClass('twrm-user-info') |
|||
.text(`Request by: ${username}`) |
|||
.appendTo($container); |
|||
// Create controls container |
|||
const $controls = $('<div>') |
|||
.addClass('twrm-controls') |
|||
.appendTo($container); |
|||
// Create right selection dropdown |
|||
const $select = $('<select>') |
|||
.addClass('twrm-select') |
|||
.appendTo($controls); |
|||
// Add default option |
|||
$('<option>') |
|||
.val('') |
|||
.text('Select right to grant...') |
|||
.appendTo($select); |
|||
// Add options for each available right |
|||
for (const [right, config] of Object.entries(this.config.rights)) { |
|||
$('<option>') |
|||
.val(right) |
|||
.text(config.displayName) |
|||
.prop('selected', right === preselectedRight) |
|||
.appendTo($select); |
|||
} |
|||
// Create grant button |
|||
const $grantButton = $('<button>') |
|||
.addClass('twrm-button') |
|||
.text('Grant Rights') |
|||
.prop('disabled', !preselectedRight) |
|||
.appendTo($controls); |
|||
// Create API button |
|||
const $apiButton = $('<button>') |
|||
.addClass('twrm-button') |
|||
.text('Grant via API') |
|||
.prop('disabled', !preselectedRight) |
|||
.appendTo($controls); |
|||
// Create result container |
|||
const $result = $('<div>') |
|||
.addClass('twrm-result') |
|||
.appendTo($container); |
|||
// Create copy button |
|||
const $copyButton = $('<button>') |
|||
.addClass('twrm-copy-button') |
|||
.text('Copy to Clipboard') |
|||
.appendTo($result); |
|||
// Create success message |
|||
const $success = $('<div>') |
|||
.addClass('twrm-success') |
|||
.text('Rights granted successfully!') |
|||
.appendTo($container); |
|||
// Handle select change |
|||
$select.on('change', function() { |
|||
const hasSelection = $(this).val() !== ''; |
|||
$grantButton.prop('disabled', !hasSelection); |
|||
$apiButton.prop('disabled', !hasSelection); |
|||
$result.hide(); |
|||
$success.hide(); |
|||
}); |
|||
// Handle grant button click |
|||
$grantButton.on('click', function() { |
|||
const selectedRight = $select.val(); |
|||
if (selectedRight && self.config.rights[selectedRight]) { |
|||
// Show template response |
|||
$result.show().text(self.config.rights[selectedRight].template); |
|||
} |
|||
}); |
|||
// Handle API button click |
|||
$apiButton.on('click', function() { |
|||
const selectedRight = $select.val(); |
|||
if (selectedRight && self.config.rights[selectedRight]) { |
|||
// Set button to loading state |
|||
const originalText = $apiButton.text(); |
|||
$apiButton.text('Processing...').prop('disabled', true); |
|||
// Grant the right using MediaWiki API |
|||
self.grantRightViaAPI(username, selectedRight) |
|||
.then(function(success) { |
|||
if (success) { |
|||
$success.show(); |
|||
$result.show().text(self.config.rights[selectedRight].template); |
|||
} else { |
|||
$result.show().text('Error granting rights. Please try again or use manual method.'); |
|||
} |
|||
}) |
|||
.catch(function() { |
|||
$result.show().text('Error granting rights. Please try again or use manual method.'); |
|||
}) |
|||
.finally(function() { |
|||
// Reset button state |
|||
$apiButton.text(originalText).prop('disabled', false); |
|||
}); |
|||
} |
|||
}); |
|||
// Handle copy button click |
|||
$copyButton.on('click', function() { |
|||
const textToCopy = $result.text(); |
|||
navigator.clipboard.writeText(textToCopy).then(function() { |
|||
$copyButton.text('Copied!'); |
|||
setTimeout(function() { |
|||
$copyButton.text('Copy to Clipboard'); |
|||
}, 2000); |
|||
}); |
|||
}); |
|||
}, |
|||
// Grant rights via MediaWiki API |
|||
grantRightViaAPI: function(username, right) { |
|||
return new Promise(function(resolve) { |
|||
// Get API token |
|||
mw.user.getToken('csrf').then(function(token) { |
|||
// Make API request to grant right |
|||
new mw.Api().postWithToken('csrf', { |
|||
action: 'userrights', |
|||
user: username, |
|||
add: right, |
|||
reason: 'Granted per request on [[Test Wiki:Request for Permissions]]', |
|||
token: token |
|||
}) |
|||
.done(function() { |
|||
resolve(true); |
|||
}) |
|||
.fail(function() { |
|||
resolve(false); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
}; |
|||
} |
|||
Revision as of 13:57, 11 April 2025
// Only run on Test Wiki:Request for Permissions page
if (mw.config.get('wgTitle') === 'Request for permissions' &&
mw.config.get('wgNamespaceNumber') === 4) { // NS 4 is "Project" namespace (Test Wiki:)
$(document).ready(function() {
// Initialize the script
testWikiRfPManager.init();
});
const testWikiRfPManager = {
// Configuration
config: {
// Rights that can be assigned with corresponding templates
rights: {
'sysop': {
displayName: 'Administrator',
template: '{{administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
},
'bureaucrat': {
displayName: 'Bureaucrat',
template: '{{bureaucrat granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
},
'interface-admin': {
displayName: 'Interface Administrator',
template: '{{interface administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
},
'non-stewardsuppress': {
displayName: 'Suppressor',
template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
},
'abusefilter-admin': {
displayName: 'Abuse Filter Administrator',
template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
}
}
},
// Initialize the script
init: function() {
// Check if user has the required permissions
if (!mw.config.get('wgUserGroups').includes('sysop')) {
console.log('TestWiki RfP Manager: User does not have sysop rights');
return;
}
// Load required styles
this.loadStyles();
// Process each RfP section
this.processRfPSections();
},
// Load CSS styles for the script
loadStyles: function() {
mw.loader.addStyleTag(`
.twrm-container {
background-color: #f8f9fa;
border: 1px solid #a2a9b1;
border-radius: 3px;
padding: 12px;
margin: 10px 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.twrm-header {
font-weight: bold;
margin-bottom: 8px;
color: #222;
font-size: 14px;
padding-bottom: 5px;
border-bottom: 1px solid #eaecf0;
}
.twrm-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
margin-bottom: 8px;
}
.twrm-select {
padding: 6px 8px;
border: 1px solid #a2a9b1;
border-radius: 2px;
background-color: white;
min-width: 200px;
}
.twrm-button {
padding: 6px 12px;
background-color: #36c;
color: white;
border: none;
border-radius: 2px;
cursor: pointer;
font-weight: bold;
}
.twrm-button:hover {
background-color: #447ff5;
}
.twrm-button:disabled {
background-color: #c8ccd1;
cursor: not-allowed;
}
.twrm-result {
margin-top: 10px;
padding: 8px;
background-color: #eaf3ff;
border: 1px solid #c8ccd1;
border-radius: 2px;
display: none;
}
.twrm-copy-button {
padding: 4px 8px;
background-color: #f8f9fa;
border: 1px solid #a2a9b1;
border-radius: 2px;
margin-top: 8px;
cursor: pointer;
}
.twrm-copy-button:hover {
background-color: #eaecf0;
}
.twrm-user-info {
margin-bottom: 8px;
font-style: italic;
}
.twrm-success {
color: #14866d;
font-weight: bold;
margin-top: 5px;
display: none;
}
`);
},
// Process all RfP sections on the page
processRfPSections: function() {
const self = this;
// Find all h2 headers which typically denote user request sections
$('.mw-parser-output > h2').each(function() {
const $header = $(this);
const username = $header.find('.mw-headline').text().trim();
if (username) {
// Find the section content
let $section = $header.nextUntil('h2, h1');
// Extract requested right from the section
const requestedRight = self.extractRequestedRight($section);
// Create rights manager UI
self.createRightsManagerUI($header, username, requestedRight);
}
});
},
// Extract the requested right from section content
extractRequestedRight: function($section) {
let rightText = '';
// Look for the "Requested right" line
$section.each(function() {
const text = $(this).text();
if (text.includes('Requested right')) {
// Extract the right from the text
const match = text.match(/Requested right[:\s]*([^*\n]+)/i);
if (match && match[1]) {
rightText = match[1].trim();
}
return false; // Break the loop
}
});
// Map common variations to our defined rights
const rightMap = {
'administrator': 'sysop',
'admin': 'sysop',
'sysop': 'sysop',
'bureaucrat': 'bureaucrat',
'crat': 'bureaucrat',
'interface administrator': 'interface-admin',
'interface admin': 'interface-admin',
'suppressor': 'non-stewardsuppress',
'suppress': 'non-stewardsuppress',
'abuse filter administrator': 'abusefilter-admin',
'abusefilter admin': 'abusefilter-admin',
'abuse filter admin': 'abusefilter-admin'
};
// Try to match the requested right
for (const [key, value] of Object.entries(rightMap)) {
if (rightText.toLowerCase().includes(key.toLowerCase())) {
return value;
}
}
return ''; // No match found
},
// Create the UI for managing rights
createRightsManagerUI: function($header, username, preselectedRight) {
const self = this;
// Create container
const $container = $('<div>')
.addClass('twrm-container')
.insertAfter($header);
// Create header
$('<div>')
.addClass('twrm-header')
.text('Rights Manager')
.appendTo($container);
// Add user info
$('<div>')
.addClass('twrm-user-info')
.text(`Request by: ${username}`)
.appendTo($container);
// Create controls container
const $controls = $('<div>')
.addClass('twrm-controls')
.appendTo($container);
// Create right selection dropdown
const $select = $('<select>')
.addClass('twrm-select')
.appendTo($controls);
// Add default option
$('<option>')
.val('')
.text('Select right to grant...')
.appendTo($select);
// Add options for each available right
for (const [right, config] of Object.entries(this.config.rights)) {
$('<option>')
.val(right)
.text(config.displayName)
.prop('selected', right === preselectedRight)
.appendTo($select);
}
// Create grant button
const $grantButton = $('<button>')
.addClass('twrm-button')
.text('Grant Rights')
.prop('disabled', !preselectedRight)
.appendTo($controls);
// Create API button
const $apiButton = $('<button>')
.addClass('twrm-button')
.text('Grant via API')
.prop('disabled', !preselectedRight)
.appendTo($controls);
// Create result container
const $result = $('<div>')
.addClass('twrm-result')
.appendTo($container);
// Create copy button
const $copyButton = $('<button>')
.addClass('twrm-copy-button')
.text('Copy to Clipboard')
.appendTo($result);
// Create success message
const $success = $('<div>')
.addClass('twrm-success')
.text('Rights granted successfully!')
.appendTo($container);
// Handle select change
$select.on('change', function() {
const hasSelection = $(this).val() !== '';
$grantButton.prop('disabled', !hasSelection);
$apiButton.prop('disabled', !hasSelection);
$result.hide();
$success.hide();
});
// Handle grant button click
$grantButton.on('click', function() {
const selectedRight = $select.val();
if (selectedRight && self.config.rights[selectedRight]) {
// Show template response
$result.show().text(self.config.rights[selectedRight].template);
}
});
// Handle API button click
$apiButton.on('click', function() {
const selectedRight = $select.val();
if (selectedRight && self.config.rights[selectedRight]) {
// Set button to loading state
const originalText = $apiButton.text();
$apiButton.text('Processing...').prop('disabled', true);
// Grant the right using MediaWiki API
self.grantRightViaAPI(username, selectedRight)
.then(function(success) {
if (success) {
$success.show();
$result.show().text(self.config.rights[selectedRight].template);
} else {
$result.show().text('Error granting rights. Please try again or use manual method.');
}
})
.catch(function() {
$result.show().text('Error granting rights. Please try again or use manual method.');
})
.finally(function() {
// Reset button state
$apiButton.text(originalText).prop('disabled', false);
});
}
});
// Handle copy button click
$copyButton.on('click', function() {
const textToCopy = $result.text();
navigator.clipboard.writeText(textToCopy).then(function() {
$copyButton.text('Copied!');
setTimeout(function() {
$copyButton.text('Copy to Clipboard');
}, 2000);
});
});
},
// Grant rights via MediaWiki API
grantRightViaAPI: function(username, right) {
return new Promise(function(resolve) {
// Get API token
mw.user.getToken('csrf').then(function(token) {
// Make API request to grant right
new mw.Api().postWithToken('csrf', {
action: 'userrights',
user: username,
add: right,
reason: 'Granted per request on [[Test Wiki:Request for Permissions]]',
token: token
})
.done(function() {
resolve(true);
})
.fail(function() {
resolve(false);
});
});
});
}
};
}