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 1dadfd1 commit 3d2f408
Show file tree
Hide file tree
Showing 7 changed files with 501 additions and 35 deletions.
28 changes: 28 additions & 0 deletions pages/catalog/CatalogBookHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ public function book($args, $request)
$templateMgr->addHeader('canonical', '<link rel="canonical" href="' . $url . '">');
}

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

// Display
if (!Hook::call('CatalogBookHandler::book', [&$request, &$submission, &$this->publication, &$this->chapter])) {
$templateMgr->display('frontend/pages/book.tpl');
Expand Down Expand Up @@ -616,4 +622,26 @@ protected function getChaptersFirstPublishedDate(Submission $submission, Chapter

return null;
}

/**
* 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 @@ -126,6 +126,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' => __('submission.synopsis'),
],
[
'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 @@ -100,3 +100,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 @@ -101,3 +101,12 @@ msgstr "Next slide"

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

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

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

msgid "plugins.themes.default.ariaDescription.languageSwitcher"
msgstr "Selects a language to show the metadata in."
110 changes: 110 additions & 0 deletions plugins/themes/default/styles/objects/monograph_full.less
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,116 @@
}
}

.authors .name,
.abstract h2,
.keywords h2 {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}

[data-pkp-switcher-target],
[class^=switcher-buttons],
[class^=switcher-buttons] > span {
display: inline-flex;
}

[data-pkp-switcher-target=title] {
& + .row {
margin-top: calc(@triple / 2);
}
}

[data-pkp-locale] p:first-of-type {
margin-top: 0;
}

[class^=switcher-buttons] {
& > span {
flex-wrap: wrap;
align-content: flex-start;
gap: 0.2rem;
}
}

.pkpBadge {
display: inline-flex;
padding: 0.25em 1em;
font-size: @font-tiny;
font-weight: @normal;
line-height: 1.5em;
border: 1px solid @bg-border-color-light;
border-radius: 1.2em;
color: @text;
}

.pkpBadge--button {
background: inherit;
text-decoration: none;
cursor: pointer;

&:hover {
border-color: @text;
outline: 0;
}
&:disabled,
&:disabled:hover {
color: #fff;
background: @bg-dark;
border-color: @bg-dark;
cursor: not-allowed;
}
}

.collapse-switcher {
display: none;
}

.collapse-button {
&:not(.show-button) {
display: none;
}
}

.collapse-text {
&:not(.show-text) {
display: none;
}
}

.selected-button {
font-weight: @bold;
}

.show-button,
.show-text {
display: inline-flex;
animation: fadeIn 0.7s ease-in-out;

@keyframes fadeIn {
0% {
display: none;
opacity: 0;
}

1% {
display: inline-flex;
opacity: 0;
}

100% {
opacity: 1;
}
}
}

#publication-authors {
& .show-text {
display: inline;
}
}

@media(min-width: @screen-phone) {

.entry_details {
Expand Down
Loading

0 comments on commit 3d2f408

Please sign in to comment.