Ansico-SoMe-plugin/ansico-some-plugin/admin-script.js

601 lines
25 KiB
JavaScript
Raw Normal View History

2026-04-13 18:55:00 +00:00
jQuery(document).ready(function($){
2026-04-16 16:59:23 +00:00
var frame;
function openAvatarFrame(e){
if (e) {
e.preventDefault();
}
if(frame){ frame.open(); return; }
frame = wp.media({ title: 'Profilbillede', button: { text: 'Brug' }, multiple: false });
frame.on('select', function(){
var att = frame.state().get('selection').first().toJSON();
$('#ansico-avatar-url').val(att.url);
$('#ansico-avatar-preview').attr('src', att.url).show();
$('.ansico-avatar-trigger').addClass('has-image');
$('.ansico-avatar-trigger .ansico-avatar-placeholder').remove();
2026-04-13 18:55:00 +00:00
});
2026-04-16 16:59:23 +00:00
frame.open();
}
$('#ansico-upload-button').on('click', openAvatarFrame);
$('#ansico-remove-button').on('click', function(e){
2026-04-13 18:55:00 +00:00
e.preventDefault();
$('#ansico-avatar-url').val('');
$('#ansico-avatar-preview').hide().attr('src', '');
2026-04-16 16:59:23 +00:00
$('.ansico-avatar-trigger').removeClass('has-image');
if (!$('.ansico-avatar-trigger .ansico-avatar-placeholder').length) {
$('.ansico-avatar-trigger').append('<span class="ansico-avatar-placeholder">Upload</span>');
}
});
function normalizeText(text) {
return $.trim(String(text || '').replace(/\s+/g, ' ')).toLowerCase();
}
function textMatches(text, patterns) {
var normalized = normalizeText(text);
for (var i = 0; i < patterns.length; i++) {
if (normalized.indexOf(patterns[i]) !== -1) {
return true;
}
}
return false;
}
function enhanceAvatarUi() {
var $preview = $('#ansico-avatar-preview');
var $upload = $('#ansico-upload-button');
var $remove = $('#ansico-remove-button');
if (!$preview.length || !$upload.length) {
return;
}
var $cell = $preview.closest('td');
$cell.addClass('ansico-avatar-cell');
$upload.addClass('ansico-avatar-hidden-button');
if (!$cell.find('.ansico-avatar-trigger').length) {
var $trigger = $('<button type="button" class="ansico-avatar-trigger" aria-label="Upload profile picture"></button>');
$preview.before($trigger);
$trigger.append($preview);
if (!$preview.attr('src')) {
$preview.hide();
$trigger.append('<span class="ansico-avatar-placeholder">Upload</span>');
} else {
$trigger.addClass('has-image');
}
}
$cell.on('click', '.ansico-avatar-trigger', openAvatarFrame);
$remove.addClass('ansico-avatar-remove-button');
}
enhanceAvatarUi();
if ( ! window.ansicoAdminSettings || ! window.ansicoAdminSettings.cleanUpProfileSettings ) {
return;
}
var $form = $('#your-profile');
if ( ! $form.length ) {
return;
}
var labels = window.ansicoAdminSettings.labels || {};
var initialSerializedState = '';
var panelLabels = {
profile: labels.profile || 'Profile',
federation: labels.federation || 'Federation',
other: labels.otherSettings || 'Other settings'
};
var profileHeadingPatterns = ['name', 'navn', 'contact info', 'kontaktinformation', 'account management', 'kontohåndtering', 'social profiles', 'profilbillede', 'ansico avatar'];
var federationHeadingPatterns = ['activitypub', 'moderation', 'moderering', 'federation', 'føderation'];
var otherHeadingPatterns = ['application passwords', 'applikationsadgangskoder'];
function bucketForHeading(text) {
if ( textMatches(text, otherHeadingPatterns) ) {
return 'other';
}
if ( textMatches(text, profileHeadingPatterns) ) {
return 'profile';
}
if ( textMatches(text, federationHeadingPatterns) ) {
return 'federation';
}
return 'other';
}
function getSectionNodes($heading) {
var $nodes = $heading;
var $next = $heading.next();
while ( $next.length && ! $next.is('h2, h3') ) {
$nodes = $nodes.add($next);
$next = $next.next();
}
return $nodes;
}
function createSectionBucket($nodes) {
return {
headingText: $nodes.first().text() || '',
nodes: $nodes
};
}
function collectSections($container) {
var results = [];
$container.children('h2, h3').each(function(){
results.push(createSectionBucket(getSectionNodes($(this))));
});
return results;
}
function extractSectionByMatcher(sections, matcherFn) {
var extracted = [];
for (var i = sections.length - 1; i >= 0; i--) {
if (matcherFn(sections[i])) {
extracted.unshift(sections.splice(i, 1)[0]);
}
}
return extracted;
}
function appendSections($panel, sections) {
$.each(sections, function(_, section){
$panel.append(section.nodes);
});
}
// Rename heading in-place for the Ansico avatar section.
$form.find('h2, h3').each(function(){
var $heading = $(this);
if ( normalizeText($heading.text()) === 'ansico avatar' ) {
$heading.text('Profilbillede');
}
});
// Separate biography into its own section.
var $bioRow = $();
$form.find('tr').each(function(){
var headingText = normalizeText($(this).find('th').first().text());
if ( headingText.indexOf('biographical info') !== -1 || headingText.indexOf('biografisk info') !== -1 ) {
$bioRow = $(this);
return false;
}
});
var $bioSection = $();
if ( $bioRow.length ) {
var bioTitle = $.trim($bioRow.find('th').first().text()) || 'Biografisk info';
$bioSection = $('<div class="ansico-profile-section ansico-profile-bio-section"></div>');
$bioSection.append($('<h2></h2>').text(bioTitle));
var $bioTable = $('<table class="form-table" role="presentation"></table>');
$bioTable.append($bioRow);
$bioSection.append($bioTable);
var $sourceTable = $bioRow.closest('table');
if ( $sourceTable.find('tr').length === 0 ) {
var $prevHeading = $sourceTable.prevAll('h2, h3').first();
$sourceTable.remove();
if ( $prevHeading.length ) {
$prevHeading.remove();
}
}
}
var $submits = $form.find('p.submit, .submit').filter(function(){ return $(this).find(':submit').length || $(this).is('p.submit'); }).detach();
var sections = collectSections($form);
if ( ! sections.length && ! $bioSection.length ) {
if ( $submits.length ) {
$form.append($submits);
}
return;
}
var $tabsNav = $('<div class="ansico-profile-tabs-nav" role="tablist" aria-label="Profile settings tabs"></div>');
var $panelsWrap = $('<div class="ansico-profile-tabs-wrap"></div>');
var panels = {
profile: $('<div class="ansico-profile-tab-panel" id="ansico-profile-tab-profile" role="tabpanel"></div>'),
federation: $('<div class="ansico-profile-tab-panel" id="ansico-profile-tab-federation" role="tabpanel"></div>'),
other: $('<div class="ansico-profile-tab-panel" id="ansico-profile-tab-other" role="tabpanel"></div>')
};
$.each(panelLabels, function(key, label){
var target = 'ansico-profile-tab-' + (key === 'other' ? 'other' : key);
var $button = $('<button type="button" class="ansico-profile-tab-button" role="tab" aria-selected="false"></button>');
$button.text(label).attr('data-panel', target);
$tabsNav.append($button);
});
var profileSections = extractSectionByMatcher(sections, function(section){
return bucketForHeading(section.headingText) === 'profile';
});
var federationSections = extractSectionByMatcher(sections, function(section){
return bucketForHeading(section.headingText) === 'federation';
});
var otherSections = sections;
// Pull sections with ActivityPub-ish field names into Federation even if heading text is unexpected.
var fallbackFromProfile = extractSectionByMatcher(profileSections, function(section){
return section.nodes.find('[name*="activitypub"], [id*="activitypub"], [class*="activitypub"], [name*="moderation"], [id*="moderation"], [class*="moderation"]').length > 0 || textMatches(section.headingText, federationHeadingPatterns);
});
federationSections = federationSections.concat(fallbackFromProfile);
var socialFromOther = extractSectionByMatcher(otherSections, function(section){
return textMatches(section.headingText, ['social profiles']) || section.nodes.find('[name^="ansico_social_"], [id^="ansico_social_"]').length > 0;
});
profileSections = profileSections.concat(socialFromOther);
var applicationPasswordSections = extractSectionByMatcher(profileSections, function(section){
return textMatches(section.headingText, otherHeadingPatterns) || section.nodes.find('[name*="application_password"], [id*="application-password"], .application-passwords').length > 0;
});
otherSections = applicationPasswordSections.concat(otherSections);
function findAndExtractRows($sectionNodes, patterns) {
var rows = $();
$sectionNodes.filter('table').add($sectionNodes.find('table')).each(function(){
$(this).find('tr').each(function(){
var rowLabel = normalizeText($(this).find('th').first().text());
if ( textMatches(rowLabel, patterns) ) {
rows = rows.add($(this));
}
});
});
return rows;
}
function buildSingleTableSection(title, $rows) {
if ( !$rows.length ) {
return null;
}
var $wrap = $('<div class="ansico-profile-section"></div>');
$wrap.append($('<h2></h2>').text(title));
var $table = $('<table class="form-table" role="presentation"></table>');
$table.append($rows);
$wrap.append($table);
return createSectionBucket($wrap);
}
function findAllRows($sectionNodes) {
var rows = $();
$sectionNodes.filter('table').add($sectionNodes.find('table')).each(function(){
rows = rows.add($(this).find('tr'));
});
return rows;
}
function pruneEmptySectionList(sectionList) {
return $.grep(sectionList, function(section){
var $tables = section.nodes.filter('table').add(section.nodes.find('table'));
if ( ! $tables.length ) {
return true;
}
return $tables.find('tr').length > 0;
});
}
// Reorder profile sections into a more social-profile-like flow.
var orderedProfile = [];
var avatarSections = extractSectionByMatcher(profileSections, function(section){ return textMatches(section.headingText, ['profilbillede', 'ansico avatar']); });
var nameSections = extractSectionByMatcher(profileSections, function(section){ return textMatches(section.headingText, ['name', 'navn']); });
var contactSections = extractSectionByMatcher(profileSections, function(section){ return textMatches(section.headingText, ['contact info', 'kontaktinformation']); });
var socialSections = extractSectionByMatcher(profileSections, function(section){ return textMatches(section.headingText, ['social profiles']); });
var accountSections = extractSectionByMatcher(profileSections, function(section){ return textMatches(section.headingText, ['account management', 'kontohåndtering']); });
var socialRows = $();
$.each(socialSections, function(_, section){
socialRows = socialRows.add(findAllRows(section.nodes));
2026-04-13 18:55:00 +00:00
});
2026-04-16 16:59:23 +00:00
var websiteRows = $();
$.each(contactSections, function(_, section){
var $rows = findAndExtractRows(section.nodes, ['website', 'websted', 'site url']);
if ( $rows.length ) {
websiteRows = websiteRows.add($rows);
}
});
if ( contactSections.length ) {
var $contactTable = contactSections[0].nodes.filter('table').add(contactSections[0].nodes.find('table')).first();
if ( !$contactTable.length ) {
$contactTable = $('<table class="form-table" role="presentation"></table>');
contactSections[0].nodes = contactSections[0].nodes.add($contactTable);
}
if ( socialRows.length ) {
$contactTable.append('<tr class="ansico-profile-subheading-row"><th colspan="2">Social profiles</th></tr>');
$contactTable.append(socialRows);
}
if ( websiteRows.length ) {
$contactTable.append('<tr class="ansico-profile-subheading-row"><th colspan="2">Website</th></tr>');
$contactTable.append(websiteRows);
}
} else if ( socialRows.length || websiteRows.length ) {
var $contactWrap = $('<div class="ansico-profile-section"></div>');
$contactWrap.append($('<h2></h2>').text('Kontaktinformationer'));
var $contactTableOnly = $('<table class="form-table" role="presentation"></table>');
if ( socialRows.length ) {
$contactTableOnly.append('<tr class="ansico-profile-subheading-row"><th colspan="2">Social profiles</th></tr>');
$contactTableOnly.append(socialRows);
}
if ( websiteRows.length ) {
$contactTableOnly.append('<tr class="ansico-profile-subheading-row"><th colspan="2">Website</th></tr>');
$contactTableOnly.append(websiteRows);
}
$contactWrap.append($contactTableOnly);
contactSections.push(createSectionBucket($contactWrap));
}
// Move ActivityPub header image into the profile hero card instead of Federation.
var headerRows = $();
$.each(federationSections, function(_, section){
section.nodes.filter('table').add(section.nodes.find('table')).each(function(){
$(this).find('tr').each(function(){
var $row = $(this);
var rowLabel = normalizeText($row.find('th').first().text());
var hasHeaderFields = $row.find('[name*="header"], [id*="header"], [class*="header"], [name*="banner"], [id*="banner"], [class*="banner"]').length > 0;
if ( textMatches(rowLabel, ['header image', 'headerbillede', 'cover image', 'banner image']) || hasHeaderFields ) {
headerRows = headerRows.add($row);
}
});
});
});
profileSections = pruneEmptySectionList(profileSections);
federationSections = pruneEmptySectionList(federationSections);
orderedProfile = orderedProfile.concat(avatarSections, nameSections, contactSections);
if ( $bioSection.length ) {
orderedProfile.push(createSectionBucket($bioSection));
$bioSection = $();
}
orderedProfile = orderedProfile.concat(accountSections, profileSections);
appendSections(panels.profile, orderedProfile);
appendSections(panels.federation, federationSections);
appendSections(panels.other, otherSections);
// If any ActivityPub/Moderation heading still ended up elsewhere, move it explicitly.
function moveMatchingHeadings($panel, patterns, $targetPanel) {
$panel.children('h2, h3').each(function(){
var $heading = $(this);
if ( textMatches($heading.text(), patterns) ) {
$targetPanel.append(getSectionNodes($heading));
}
});
}
moveMatchingHeadings(panels.profile, federationHeadingPatterns, panels.federation);
moveMatchingHeadings(panels.other, federationHeadingPatterns, panels.federation);
moveMatchingHeadings(panels.profile, otherHeadingPatterns, panels.other);
moveMatchingHeadings(panels.federation, otherHeadingPatterns, panels.other);
moveMatchingHeadings(panels.other, ['social profiles'], panels.profile);
// Decorate section cards.
panels.profile.children('h2, h3').each(function(){
var $heading = $(this);
var $section = $('<div class="ansico-profile-card"></div>');
var $nodes = getSectionNodes($heading);
$nodes.wrapAll($section);
});
panels.federation.children('h2, h3').each(function(){
var $heading = $(this);
var $section = $('<div class="ansico-profile-card"></div>');
var $nodes = getSectionNodes($heading);
$nodes.wrapAll($section);
});
panels.other.children('h2, h3').each(function(){
var $heading = $(this);
var $section = $('<div class="ansico-profile-card"></div>');
var $nodes = getSectionNodes($heading);
$nodes.wrapAll($section);
});
panels.profile.find('.ansico-profile-card').first().addClass('ansico-profile-card-avatar');
function compactNameFields() {
panels.profile.find('.ansico-profile-card').each(function(){
var $card = $(this);
var headingText = $card.children('h2, h3').first().text();
if ( ! textMatches(headingText, ['name', 'navn']) ) {
return;
}
var $table = $card.find('table.form-table').first();
if ( ! $table.length ) {
return;
}
var $firstRow = $();
var $lastRow = $();
$table.find('tr').each(function(){
var rowLabel = normalizeText($(this).find('th').first().text());
if ( !$firstRow.length && textMatches(rowLabel, ['first name', 'fornavn']) ) {
$firstRow = $(this);
} else if ( !$lastRow.length && textMatches(rowLabel, ['last name', 'efternavn']) ) {
$lastRow = $(this);
}
});
if ( $firstRow.length && $lastRow.length && ! $card.find('.ansico-profile-inline-grid').length ) {
var $grid = $('<div class="ansico-profile-inline-grid"></div>');
$.each([$firstRow, $lastRow], function(_, $row){
var $field = $('<div class="ansico-profile-inline-field"></div>');
$field.append($('<label class="ansico-profile-inline-label"></label>').html($row.find('th').first().html()));
var $control = $('<div class="ansico-profile-inline-control"></div>');
$control.append($row.find('td').contents());
$field.append($control);
$grid.append($field);
$row.remove();
});
$table.before($grid);
if ( !$table.find('tr').length ) {
$table.remove();
}
}
});
}
function integrateHeroHeaderRow() {
var $heroCard = panels.profile.find('.ansico-profile-card').filter(function(){
return textMatches($(this).children('h2, h3').first().text(), ['profilbillede', 'profile picture', 'ansico avatar']);
}).first();
if ( !$heroCard.length ) {
return;
}
$heroCard.addClass('ansico-profile-hero-card');
var $heroTable = $heroCard.find('table.form-table').first();
if ( !$heroTable.length ) {
$heroTable = $('<table class="form-table" role="presentation"></table>');
$heroCard.append($heroTable);
}
if ( headerRows.length ) {
var $coverTable = $('<table class="form-table ansico-profile-cover-table" role="presentation"></table>');
headerRows.each(function(){
$(this).addClass('ansico-profile-cover-row');
});
$coverTable.append(headerRows);
$heroCard.find('h2, h3').first().after($coverTable);
$heroCard.addClass('has-cover');
}
$heroTable.find('tr').each(function(){
var rowLabel = normalizeText($(this).find('th').first().text());
if ( textMatches(rowLabel, ['profilbillede', 'profile picture', 'avatar']) ) {
$(this).addClass('ansico-profile-avatar-row');
}
});
panels.federation.find('.ansico-profile-card').each(function(){
if ( ! $(this).find('tr').length ) {
$(this).remove();
}
});
}
function enhanceHeaderImageUi() {
var $coverRow = panels.profile.find('.ansico-profile-cover-row').first();
if ( !$coverRow.length || $coverRow.hasClass('ansico-cover-ui-ready') ) {
return;
}
$coverRow.addClass('ansico-cover-ui-ready');
var $cell = $coverRow.find('td').first();
if ( !$cell.length ) {
return;
}
var $buttons = $cell.find('button, .button, input[type="button"], input[type="submit"]');
var $selectButton = $buttons.filter(function(){
return !/remove|delete|fjerne|slet/.test(normalizeText($(this).text() || $(this).val()));
}).first();
var $removeButton = $buttons.filter(function(){
return /remove|delete|fjerne|slet/.test(normalizeText($(this).text() || $(this).val()));
}).first();
var $img = $cell.find('img').first();
var $trigger = $('<button type="button" class="ansico-header-trigger" aria-label="Choose header image"></button>');
if ( $img.length ) {
$trigger.append($img);
$trigger.addClass('has-image');
} else {
$trigger.append('<span class="ansico-header-placeholder">Choose a header image</span>');
}
$cell.prepend($trigger);
if ( $selectButton.length ) {
$selectButton.addClass('ansico-header-hidden-button');
$trigger.on('click', function(e){
e.preventDefault();
$selectButton.trigger('click');
});
}
if ( $removeButton.length ) {
$removeButton.addClass('ansico-header-remove-button');
}
var $actions = $('<div class="ansico-header-actions"></div>');
if ( $selectButton.length ) {
$actions.append($selectButton);
}
if ( $removeButton.length ) {
$actions.append($removeButton);
}
if ( $actions.children().length ) {
$cell.append($actions);
}
var observer = new MutationObserver(function(){
var $latestImg = $cell.find('img').not($trigger.find('img')).first();
if ( $latestImg.length ) {
$trigger.empty().append($latestImg).addClass('has-image');
} else if ( !$trigger.find('img').length ) {
$trigger.removeClass('has-image').html('<span class="ansico-header-placeholder">Choose a header image</span>');
}
});
observer.observe($cell.get(0), { childList: true, subtree: true });
if ( $removeButton.length ) {
$removeButton.on('click', function(){
setTimeout(function(){
if ( !$trigger.find('img').length ) {
$trigger.removeClass('has-image').html('<span class="ansico-header-placeholder">Choose a header image</span>');
}
}, 50);
});
}
}
$panelsWrap.append(panels.profile, panels.federation, panels.other);
var $firstHeading = $form.children('h2, h3').first();
if ( $firstHeading.length ) {
$firstHeading.before($tabsNav, $panelsWrap);
} else {
$form.prepend($tabsNav, $panelsWrap);
}
if ( $submits.length ) {
var $submitWrap = $('<div class="ansico-profile-submit-wrap"></div>');
$submitWrap.append($submits);
$form.append($submitWrap);
}
function activateTab(panelId) {
$tabsNav.find('.ansico-profile-tab-button').each(function(){
var active = $(this).attr('data-panel') === panelId;
$(this).toggleClass('is-active', active).attr('aria-selected', active ? 'true' : 'false');
});
$panelsWrap.find('.ansico-profile-tab-panel').each(function(){
$(this).toggleClass('is-active', this.id === panelId);
});
}
$tabsNav.on('click', '.ansico-profile-tab-button', function(){
activateTab($(this).attr('data-panel'));
});
function refreshInitialState() {
initialSerializedState = $form.serialize();
}
function setupUnsavedChangesGuard() {
$(window).off('beforeunload.edit-profile');
$(window).off('beforeunload.ansicoProfileCleanup');
refreshInitialState();
$(window).on('beforeunload.ansicoProfileCleanup', function(){
if ( $form.serialize() !== initialSerializedState ) {
return labels.leaveWarning || 'Vil du forlade webstedet? Ændringer, du har foretaget, gemmes muligvis ikke.';
}
});
$form.on('submit', function(){
refreshInitialState();
$(window).off('beforeunload.ansicoProfileCleanup');
});
}
compactNameFields();
integrateHeroHeaderRow();
enhanceHeaderImageUi();
activateTab('ansico-profile-tab-profile');
enhanceAvatarUi();
setupUnsavedChangesGuard();
});