Skip to content

Commit

Permalink
Simultaneously Displaying Multilingual Metadata on the Article Landin…
Browse files Browse the repository at this point in the history
…g Page
  • Loading branch information
jyhein committed Oct 23, 2024
1 parent 94f8793 commit 1a8db07
Show file tree
Hide file tree
Showing 6 changed files with 506 additions and 16 deletions.
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

0 comments on commit 1a8db07

Please sign in to comment.