Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkp/pkp-lib#7272 Simultaneously Displaying Multilingual Metadata on the Article Landing Page #4050

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/pkp
29 changes: 29 additions & 0 deletions pages/article/ArticleHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use APP\observers\events\UsageEvent;
use APP\payment\ojs\OJSCompletedPaymentDAO;
use APP\payment\ojs\OJSPaymentManager;
use APP\publication\Publication;
use APP\security\authorization\OjsJournalMustPublishPolicy;
use APP\submission\Submission;
use APP\template\TemplateManager;
Expand Down Expand Up @@ -357,6 +358,12 @@ public function view($args, $request)
$templateMgr->assign('purchaseArticleEnabled', true);
}

$templateMgr->assign('pubLocaleData', $this->getMultilingualMetadataOpts(
$publication,
$templateMgr->getTemplateVars('currentLocale'),
$templateMgr->getTemplateVars('activeTheme')->getOption('showMultilingualMetadata') ?: [],
));

if (!Hook::call('ArticleHandler::view', [&$request, &$issue, &$article, $publication])) {
$templateMgr->display('frontend/pages/article.tpl');
event(new UsageEvent(Application::ASSOC_TYPE_SUBMISSION, $context, $article, null, null, $this->issue));
Expand Down Expand Up @@ -616,4 +623,26 @@ public function userCanViewGalley($request, $articleId, $galleyId = null)
}
return true;
}

/**
* Multilingual publication metadata for template:
* showMultilingualMetadataOpts - Show metadata in other languages: title (+ subtitle), keywords, abstract, etc.
*/
protected function getMultilingualMetadataOpts(Publication $publication, string $currentUILocale, array $showMultilingualMetadataOpts): array
{
$langNames = collect($publication->getLanguageNames())
->sortKeys();
$langs = $langNames->keys();
return [
'opts' => array_flip($showMultilingualMetadataOpts),
'localeNames' => $langNames,
'langTags' => $langNames->map(fn ($_, $l) => preg_replace(['/@.+$/', '/_/'], ['', '-'], $l))->toArray() /* remove @ and text after */,
'localeOrder' => collect($publication->getLocalePrecedence())
->intersect($langs) /* remove locales not in publication's languages */
->concat($langs)
->unique()
->values()
->toArray(),
];
}
}
24 changes: 24 additions & 0 deletions plugins/themes/default/DefaultThemePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,30 @@ public function init()
'default' => 'none',
]);

$this->addOption('showMultilingualMetadata', 'FieldOptions', [
'label' => __('plugins.themes.default.option.metadata.label'),
'description' => __('plugins.themes.default.option.metadata.description'),
'options' => [
[
'value' => 'title',
'label' => __('submission.title'),
],
[
'value' => 'keywords',
'label' => __('common.keywords'),
],
[
'value' => 'abstract',
'label' => __('common.abstract'),
],
[
'value' => 'author',
'label' => __('default.groups.name.author'),
],
],
'default' => [],
]);


// Load primary stylesheet
$this->addStyle('stylesheet', 'styles/index.less');
Expand Down
194 changes: 194 additions & 0 deletions plugins/themes/default/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,197 @@
});

})(jQuery);

/**
* Create language buttons to show multilingual metadata
* [data-pkp-locales]: Publication's locales in order
* [data-pkp-switcher-text]: Texts for the switchers to control
* [data-pkp-switcher-target]: Switchers' containers
*/
(() => {
function createButtonSwitcher(textsObj, originalLocaleOrder, metadataFieldName, selectedLocale) {
// Get all locales for the switcher from the texts
const textsElsLocales = textsObj.els.reduce((locales, textEls) => {
textEls.forEach((el) => {
locales[el.getAttribute('data-pkp-locale')] = el.getAttribute('data-pkp-locale-name');
});
return locales;
}, {});

// Create containers
const spanContainer = document.createElement('span');
[
['class', `switcher-buttons-${metadataFieldName}`],
].forEach((attr) => spanContainer.setAttribute(...attr));

const spanButtons = document.createElement('span');
const spanButtonsId = `switcher-buttons-${metadataFieldName}`;
[
['id', spanButtonsId],
].forEach((attr) => spanButtons.setAttribute(...attr));

// Create, sort to alphabetical order, and append buttons
originalLocaleOrder
.map((elLocale) => {
if (!textsElsLocales[elLocale]) {
return null;
}
if (!selectedLocale.value) {
selectedLocale.value = elLocale;
}

const isSelectedLocale = elLocale === selectedLocale.value;
const button = document.createElement('button');
[
['data-pkp-locale', elLocale],
['data-pkp-switcher-button', metadataFieldName],
['class', `pkpBadge pkpBadge--button collapse-button${isSelectedLocale ? ' selected-button show-button' : ''}`],
['type', 'button'],
['aria-controls', isSelectedLocale ? spanButtonsId : textsObj.ids.join(' ')],
...isSelectedLocale
? [
['aria-expanded', false],
]
: [],
].forEach((attr) => button.setAttribute(...attr));
button.textContent = textsElsLocales[elLocale];

return button;
})
.filter((btn) => btn)
.sort((a, b) => a.getAttribute('data-pkp-locale').localeCompare(b.getAttribute('data-pkp-locale')))
.forEach((btn) => spanButtons.appendChild(btn));

// If only one button, set it disabled
if (spanButtons.children.length === 1) {
spanButtons.children[0].disabled = true;
}

spanContainer.appendChild(spanButtons);

return spanContainer;
}

/**
* Show or hide switcher's target texts
* If selected locale doesn't match any, all texts are hidden
*/
function showText(selectedLocale, textsEls) {
textsEls.forEach((textsEl) => {
textsEl.forEach((textEl) => {
const elLocale = textEl.getAttribute('data-pkp-locale');
if (elLocale === selectedLocale.value) {
textEl.classList.add('show-text');
} else {
textEl.classList.remove('show-text');
}
});
});
}

/**
* Change/update buttons' aria-attributes
*/
function switchButtonAria(btnTarget, buttons) {
let btnTargetOldAriaControls = btnTarget.getAttribute('aria-controls');
let btnPrevSelectedLangAriaControls = null;
buttons.forEach((btn) => {
// Previously selected langauge button
if (btn.getAttribute('aria-expanded')) {
btnPrevSelectedLangAriaControls = btn.getAttribute('aria-controls');
btn.removeAttribute('aria-expanded');
btn.setAttribute('aria-controls', btnTargetOldAriaControls);
}
});
btnTarget.setAttribute('aria-expanded', true);
btnTarget.setAttribute('aria-controls', btnPrevSelectedLangAriaControls);
}

function setButtonSwitcher(textsObj, switcherTargetEl, metadataFieldName, originalLocaleOrder) {
// Currently selected language for buttons and texts
const selectedLocale = {value: null};
const buttonSwitcherEl = createButtonSwitcher(textsObj, originalLocaleOrder, metadataFieldName, selectedLocale);

// Sync buttons and shown texts
showText(selectedLocale, textsObj.els);

const buttons = buttonSwitcherEl.querySelectorAll('button');

// Add listeners if more than one button
if (buttons.length > 1) {
// Selected language shows/hides other switcher buttons, and otherwise switches language and shows text
buttonSwitcherEl.addEventListener('click', (evt) => {
const btnTarget = evt.target;
if (btnTarget.type === 'button') {
if (btnTarget.getAttribute('data-pkp-locale') === selectedLocale.value) {
buttons.forEach((btn) => {
if (btn.getAttribute('data-pkp-locale') !== selectedLocale.value) {
btn.classList.toggle('show-button');
}
});
btnTarget.setAttribute('aria-expanded', true);
} else {
selectedLocale.value = btnTarget.getAttribute('data-pkp-locale');
switchButtonAria(btnTarget, buttons);
buttons.forEach((btn) => {
if (btn.getAttribute('data-pkp-locale') === selectedLocale.value) {
btn.classList.add('selected-button');
} else {
btn.classList.remove('selected-button');
}
});
showText(selectedLocale, textsObj.els);
}
}
});
// Hide switcher (except selected language) buttons when it loses focus
buttonSwitcherEl.addEventListener('focusout', (evt) => {
if (!evt.relatedTarget || evt.relatedTarget.getAttribute('data-pkp-switcher-button') !== metadataFieldName) {
buttons.forEach((btn) => {
if (btn.getAttribute('data-pkp-locale') !== selectedLocale.value) {
btn.classList.remove('show-button');
} else {
btn.setAttribute('aria-expanded', false);
}
});
}
});
}

// Append and show switcher
switcherTargetEl.append(buttonSwitcherEl);
switcherTargetEl.classList.remove('collapse-switcher');
}

/**
* Get all multilingual texts and ids for the switchers
*/
function getSwitcherTexts() {
const textsObj = {};
document.querySelectorAll('[data-pkp-switcher-text]').forEach((textsEl) => {
const key = textsEl.getAttribute('data-pkp-switcher-text');
if (!textsObj[key]) {
textsObj[key] = {ids: [], els: []};
}
textsObj[key].ids.push(textsEl.id);
textsObj[key].els.push([...textsEl.querySelectorAll('[data-pkp-locale]')]);
});
return textsObj;
}

(() => {
const originalLocaleOrder = document.querySelector('[data-pkp-locales]')?.getAttribute('data-pkp-locales').split(',');
const switcherTargetEls = document.querySelectorAll('[data-pkp-switcher-target]');
if (!originalLocaleOrder || !switcherTargetEls.length) {
return;
}
const switcherTextsObj = getSwitcherTexts();
// Get target elements for switchers and create them
switcherTargetEls.forEach((switcherTargetEl) => {
const metadataFieldName = switcherTargetEl.getAttribute('data-pkp-switcher-target');
if (switcherTextsObj[metadataFieldName]) {
setButtonSwitcher(switcherTextsObj[metadataFieldName], switcherTargetEl, metadataFieldName, originalLocaleOrder);
}
});
})();
})();
9 changes: 9 additions & 0 deletions plugins/themes/default/locale/en/locale.po
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,12 @@ msgstr "Next slide"

msgid "plugins.themes.default.prevSlide"
msgstr "Previous slide"

msgid "plugins.themes.default.option.metadata.label"
msgstr "Show article metadata on the article landing page"

msgid "plugins.themes.default.option.metadata.description"
msgstr "Select the article metadata to show in other languages."

msgid "plugins.themes.default.ariaDescription.languageSwitcher"
msgstr "Selects a language to show the metadata in."
Loading