From fdec3ef11e58299586690612f3990f2d179a115d Mon Sep 17 00:00:00 2001 From: GaziYucel Date: Mon, 27 Jan 2025 21:41:34 +0100 Subject: [PATCH] Multiple author affiliations (Ror) --- public/globals.js | 17 + .../DropdownActions/DropdownActions.vue | 4 +- src/components/Form/FormGroup.vue | 2 + .../Form/fields/FieldAffiliations.mdx | 23 + .../Form/fields/FieldAffiliations.stories.js | 33 + .../Form/fields/FieldAffiliations.vue | 607 ++++++++++++++++++ ...FieldAffiliationsRorAutoSuggest.stories.js | 33 + ...ue => FieldAffiliationsRorAutoSuggest.vue} | 41 +- .../fields/FieldRorAutosuggest.stories.js | 40 -- .../Form/mocks/field-affiliations.js | 425 ++++++++++++ .../contributors/ContributorsListPanel.vue | 12 + 11 files changed, 1184 insertions(+), 53 deletions(-) create mode 100644 src/components/Form/fields/FieldAffiliations.mdx create mode 100644 src/components/Form/fields/FieldAffiliations.stories.js create mode 100644 src/components/Form/fields/FieldAffiliations.vue create mode 100644 src/components/Form/fields/FieldAffiliationsRorAutoSuggest.stories.js rename src/components/Form/fields/{FieldRorAutosuggest.vue => FieldAffiliationsRorAutoSuggest.vue} (82%) delete mode 100644 src/components/Form/fields/FieldRorAutosuggest.stories.js create mode 100644 src/components/Form/mocks/field-affiliations.js diff --git a/public/globals.js b/public/globals.js index 3d90f042c..7b0b23921 100644 --- a/public/globals.js +++ b/public/globals.js @@ -158,6 +158,7 @@ window.pkp = { 'article.metadata': 'Metadata', 'author.users.contributor.principalContact': 'Primary Contact', 'author.users.contributor.setPrincipalContact': 'Set Primary Contact', + 'common.add': 'Add', 'common.addCCBCC': 'Add CC/BCC', 'common.assign': 'Assign', 'common.attachFiles': 'Attach Files', @@ -804,6 +805,22 @@ window.pkp = { 'The submission has been advanced to the next round of review', 'workflow.submissionNextReviewRoundInFutureStage': 'The submission advanced to the next review round, was accepted, and is currently in the {$stage} stage.', + 'user.affiliations': 'Affiliations', + 'user.affiliations.description': 'Enter the full name of the institution below, avoiding any acronyms. Select the name from the dropdown and click "Add" to include the affiliation in your profile. (e.g. "Simon Fraser University")', + 'user.affiliations.institution': 'Institution', + 'user.affiliations.translation': 'More information', + 'user.affiliations.translationEditActionLabel': 'Edit institution name', + 'user.affiliations.translationDeleteActionLabel': 'Remove institution', + 'user.affiliations.translationActionsAriaLabel': 'Click to edit or delete', + 'user.affiliations.translationsAllAvailable': 'All translations available', + 'user.affiliations.translationsSomeAvailable': '{$count} of {$total} languages completed', + 'user.affiliations.typeTranslationNameInLanguageLabel': 'Type the institute name in {$language}', + 'user.affiliations.translationNameInLanguage': 'Institute name in {$language}', + 'user.affiliations.deleteModal.title': 'Are you sure?', + 'user.affiliations.deleteModal.message': 'The affiliation {$affiliation} will be deleted.', + 'user.affiliations.searchPhraseLabel': 'Type the institute name in {$language}', + 'user.affiliations.searchPhraseNothingFound': 'Your search phrase could not be found', + 'user.affiliations.primaryLocaleRequired': 'The primary language {$primaryLocale} is required', }, tinyMCE: { skinUrl: '/styles/tinymce', diff --git a/src/components/DropdownActions/DropdownActions.vue b/src/components/DropdownActions/DropdownActions.vue index 2b1737629..ba02eaed8 100644 --- a/src/components/DropdownActions/DropdownActions.vue +++ b/src/components/DropdownActions/DropdownActions.vue @@ -127,7 +127,9 @@ const emit = defineEmits([ ]); const emitAction = (action) => { - if (action.name) { + if (action.name && (action.id || action.id === 0)) { + emit('action', [action.name, action.id]); + } else if (action.name) { emit('action', action.name); } }; diff --git a/src/components/Form/FormGroup.vue b/src/components/Form/FormGroup.vue index ce7b6087c..843506640 100644 --- a/src/components/Form/FormGroup.vue +++ b/src/components/Form/FormGroup.vue @@ -53,6 +53,7 @@ + + diff --git a/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.stories.js b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.stories.js new file mode 100644 index 000000000..106dc0337 --- /dev/null +++ b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.stories.js @@ -0,0 +1,33 @@ +import {http, HttpResponse} from 'msw'; +import FieldAffiliationsRorAutoSuggest from './FieldAffiliationsRorAutoSuggest.vue'; +import FieldAffiliationsMock from '@/components/Form/mocks/field-affiliations'; + +export default { + title: 'Forms/FieldAffiliationsRorAutoSuggest', + component: FieldAffiliationsRorAutoSuggest, + render: (args) => ({ + components: {FieldAffiliationsRorAutoSuggest}, + setup() { + return {args}; + }, + template: '', + }), + parameters: { + msw: { + handlers: [ + http.get('https://api.ror.org/v2/organizations', async () => { + return HttpResponse.json(FieldAffiliationsMock.organizations); + }), + ], + }, + docs: { + story: { + height: '500px', + }, + }, + }, +}; + +export const Default = { + args: {...FieldAffiliationsMock}, +}; diff --git a/src/components/Form/fields/FieldRorAutosuggest.vue b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue similarity index 82% rename from src/components/Form/fields/FieldRorAutosuggest.vue rename to src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue index 356f3cfae..b3ab66220 100644 --- a/src/components/Form/fields/FieldRorAutosuggest.vue +++ b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue @@ -61,7 +61,7 @@ import {useFetch} from '@/composables/useFetch'; import {useId} from '@/composables/useId'; const {generateId} = useId(); - +const autosuggestContainerId = generateId(); const currentSelected = ref([]); const inputValue = ref(''); const isFocused = ref(false); @@ -70,8 +70,6 @@ const queryParams = computed(() => ({ query: inputValue.value, })); -const autosuggestContainerId = generateId(); - const { data: suggestions, isLoading, @@ -97,14 +95,27 @@ const autoSuggestProps = computed(() => ({ const mappedSuggestions = computed(() => { return suggestions.value?.items.map((item) => { - // get the name from "ror_display" type - const name = item.names?.find((i) => + const displayLocale = item.names?.find((i) => i.types.includes('ror_display'), - )?.value; + )?.lang; + + let name = {}; + item.names?.forEach((item) => { + if (item.types.includes('label') || item.types.includes('ror_display')) { + name[item.lang] = item.value; + } + }); return { - value: item.id, - label: name, + value: { + id: null, + ror: item.id, + displayLocale: displayLocale, + isActive: item.status === 'active' ? 1 : 0, + name: name, + _href: null, + }, + label: name[displayLocale], hasSlot: true, href: item.id, }; @@ -112,7 +123,9 @@ const mappedSuggestions = computed(() => { }); watch(queryParams, () => { - getSuggestions(); + if (inputValue.value.length > 3) { + getSuggestions(); + } }); async function getSuggestions() { @@ -129,7 +142,7 @@ function changeFocus(focused) { function handleSelect(suggestion) { if (!suggestion) { - if (!inputValue.value || !mappedSuggestions.value.length) { + if (!inputValue.value) { return; } @@ -139,8 +152,8 @@ function handleSelect(suggestion) { }; } - if (!currentSelected.value.some((item) => item.value === suggestion.value)) { - currentSelected.value.push(suggestion); + if (currentSelected.value !== suggestion.value) { + currentSelected.value = [suggestion]; } } @@ -149,4 +162,8 @@ function handleDeselect(item) { (selected) => selected.value !== item.value, ); } + +defineExpose({ + currentSelected, +}); diff --git a/src/components/Form/fields/FieldRorAutosuggest.stories.js b/src/components/Form/fields/FieldRorAutosuggest.stories.js deleted file mode 100644 index 68a75679f..000000000 --- a/src/components/Form/fields/FieldRorAutosuggest.stories.js +++ /dev/null @@ -1,40 +0,0 @@ -import {http, HttpResponse} from 'msw'; -import FieldRorAutosuggest from './FieldRorAutosuggest.vue'; -import InstitutionsMock from '@/mocks/institutions.json'; - -export default { - title: 'Components/FieldRorAutosuggest', - component: FieldRorAutosuggest, - render: (args) => ({ - components: {FieldRorAutosuggest}, - setup() { - return {args}; - }, - template: '', - }), - parameters: { - msw: { - handlers: [ - http.get('https://api.ror.org/v2/organizations', async () => { - return HttpResponse.json(InstitutionsMock); - }), - ], - }, - docs: { - story: { - height: '300px', - }, - }, - }, -}; - -export const Default = { - render: (args) => ({ - components: {FieldRorAutosuggest}, - setup() { - return {args}; - }, - template: '', - }), - args: {}, -}; diff --git a/src/components/Form/mocks/field-affiliations.js b/src/components/Form/mocks/field-affiliations.js new file mode 100644 index 000000000..48c4eed12 --- /dev/null +++ b/src/components/Form/mocks/field-affiliations.js @@ -0,0 +1,425 @@ +export default { + name: 'author-affiliations', + component: 'author-affiliations', + authorId: 1, + primaryLocale: 'en', + locales: [ + {key: 'en', label: 'English'}, + {key: 'fr_CA', label: 'French (Canada)'}, + {key: 'de', label: 'German'}, + ], + value: [ + { + id: 12, + authorId: 1, + ror: 'https://ror.org/0213rcc28', + name: { + en: 'Simon Fraser University', + fr_CA: 'Simon Fraser University', + de: 'Simon Fraser University', + tr: 'Simon Fraser Üniversitesi', + }, + }, + { + id: 13, + authorId: 1, + ror: 'https://ror.org/02e2c7k09', + name: { + en: 'Delft University of Technology', + fr_CA: '', + de: 'Technische Universität Delft', + tr: 'Delft Teknik Üniversitesi', + }, + }, + { + id: 14, + authorId: 1, + ror: '', + name: { + en: 'German National Library of Science and Technology', + fr_CA: '', + de: 'Technische Informationsbibliothek (TIB)', + tr: 'Teknik Bilgi Kütübanesi', + }, + }, + ], + organizations: { + number_of_results: 100, + time_taken: 15, + items: [ + { + id: 'https://ror.org/0213rcc28', + names: [ + { + lang: null, + types: ['acronym'], + value: 'SFU', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Simon Fraser University', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/02njbw696', + names: [ + { + lang: 'en', + types: ['label'], + value: 'Simón Bolívar University', + }, + { + lang: 'es', + types: ['ror_display', 'label'], + value: 'Universidad Simón Bolívar', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/01ak5cj98', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Simón Bolívar University', + }, + { + lang: null, + types: ['acronym'], + value: 'USB', + }, + { + lang: 'es', + types: ['label'], + value: 'Universidad Simón Bolívar', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/014579w63', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Fraser Health', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/03455j977', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Fraser Institute', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/00jtqvs23', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Fraser Research', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/04h6w7946', + names: [ + { + lang: 'en', + types: ['alias'], + value: 'Fraser Valley College', + }, + { + lang: null, + types: ['acronym'], + value: 'UFV', + }, + { + lang: 'en', + types: ['alias'], + value: 'University College of the Fraser Valley', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'University of the Fraser Valley', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/03z27es23', + names: [ + { + lang: null, + types: ['acronym'], + value: 'UMSS', + }, + { + lang: 'es', + types: ['label'], + value: 'Universidad Mayor de San Simón', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'University of San Simón', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/00wmrqg59', + names: [ + { + lang: null, + types: ['ror_display', 'label'], + value: 'West Fraser (Canada)', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/02bnf4x85', + names: [ + { + lang: null, + types: ['acronym'], + value: 'USK', + }, + { + lang: 'fr', + types: ['ror_display', 'label'], + value: 'Université Simon Kimbangu', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/04m9zwa74', + names: [ + { + lang: 'de', + types: ['ror_display', 'label'], + value: 'Claussen Simon Stiftung', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/03jrrzr69', + names: [ + { + lang: null, + types: ['acronym'], + value: 'UASB', + }, + { + lang: 'es', + types: ['ror_display', 'label'], + value: 'Universidad Andina Simón Bolívar', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/0208tyb13', + names: [ + { + lang: 'es', + types: ['ror_display', 'label'], + value: 'Universidad Peruana Simón Bolívar', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/02haar591', + names: [ + { + lang: null, + types: ['acronym'], + value: 'IPSL', + }, + { + lang: 'fr', + types: ['ror_display', 'label'], + value: 'Institut Pierre-Simon Laplace', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/00g1d7b60', + names: [ + { + lang: 'en', + types: ['alias'], + value: 'IU Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'IU Melvin and Bren Simon Comprehensive Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'IU Simon Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'IU Simon Comprehensive Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'Indiana University Cancer Center', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: + 'Indiana University Melvin and Bren Simon Comprehensive Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'Melvin and Bren Simon Comprehensive Cancer Center', + }, + { + lang: 'en', + types: ['alias'], + value: 'Simon Comprehensive Cancer Center', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/02anx2442', + names: [ + { + lang: null, + types: ['acronym'], + value: 'KhNUE', + }, + { + lang: 'en', + types: ['alias'], + value: 'Kharkiv National University of Economics', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Simon Kuznets Kharkiv National University of Economics', + }, + { + lang: null, + types: ['acronym'], + value: 'ХНЕУ', + }, + { + lang: 'uk', + types: ['label'], + value: 'Харківський національний економічний університет', + }, + { + lang: 'ru', + types: ['label'], + value: 'Харьковский национальный экономический университет', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/00nggaz43', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'Georg Simon Ohm University of Applied Sciences Nuremberg', + }, + { + lang: 'de', + types: ['alias'], + value: 'TH Nürnberg', + }, + { + lang: 'de', + types: ['label'], + value: 'Technische Hochschule Nürnberg Georg Simon Ohm', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/03951zg45', + names: [ + { + lang: null, + types: ['acronym'], + value: 'UNESR', + }, + { + lang: 'es', + types: ['ror_display', 'label'], + value: 'Universidad Nacional Experimental Simón Rodríguez', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/0407tnq23', + names: [ + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'John Simon Guggenheim Memorial Foundation', + }, + ], + status: 'active', + }, + { + id: 'https://ror.org/01a8ynn13', + names: [ + { + lang: 'en', + types: ['alias'], + value: 'Simon Langton Grammar School for Boys', + }, + { + lang: 'en', + types: ['alias'], + value: 'The Langton', + }, + { + lang: 'en', + types: ['ror_display', 'label'], + value: 'The Langton Grammar School for Boys', + }, + ], + status: 'active', + }, + ], + }, + newAffiliation: { + id: null, + authorId: 1, + ror: null, + name: {}, + rorObject: {}, + }, +}; diff --git a/src/components/ListPanel/contributors/ContributorsListPanel.vue b/src/components/ListPanel/contributors/ContributorsListPanel.vue index ec832a321..ab901f5f0 100644 --- a/src/components/ListPanel/contributors/ContributorsListPanel.vue +++ b/src/components/ListPanel/contributors/ContributorsListPanel.vue @@ -294,6 +294,13 @@ export default { let activeForm = cloneDeep(this.form); activeForm.action = this.contributorsApiUrl; activeForm.method = 'POST'; + activeForm.fields = activeForm.fields.map((field) => { + if (field.name === 'affiliations') { + field.primaryLocale = activeForm.primaryLocale; + field.locales = activeForm.supportedFormLocales; + } + return field; + }); this.activeForm = activeForm; this.activeFormTitle = this.t('grid.action.addContributor'); const {openSideModal} = useModal(); @@ -395,6 +402,11 @@ export default { field.isVerified = author['orcidIsVerified'] ?? false; field.orcidVerificationRequested = author['orcidVerificationRequested']; + } else if (field.name === 'affiliations') { + field.authorId = author['id']; + field.primaryLocale = activeForm.primaryLocale; + field.locales = activeForm.supportedFormLocales; + field.value = author[field.name]; } else if (Object.keys(author).includes(field.name)) { field.value = author[field.name]; }