diff --git a/public/globals.js b/public/globals.js index 3d90f042c..7f039209c 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': 'Translation', + '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..5ec2e8aa2 --- /dev/null +++ b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.stories.js @@ -0,0 +1,36 @@ +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://mock/index.php/publicknowledge/api/v1/rors/?searchPhrase=Simon+Fraser+University', + async () => { + return HttpResponse.json(FieldAffiliationsMock.searchResults); + }, + ), + ], + }, + docs: { + story: { + height: '300px', + }, + }, + }, +}; + +export const Default = { + args: {...FieldAffiliationsMock}, +}; diff --git a/src/components/Form/fields/FieldRorAutosuggest.vue b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue similarity index 84% rename from src/components/Form/fields/FieldRorAutosuggest.vue rename to src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue index 356f3cfae..ce76c9e35 100644 --- a/src/components/Form/fields/FieldRorAutosuggest.vue +++ b/src/components/Form/fields/FieldAffiliationsRorAutoSuggest.vue @@ -59,15 +59,16 @@ import Autosuggest from './Autosuggest.vue'; import Icon from '@/components/Icon/Icon.vue'; import {useFetch} from '@/composables/useFetch'; import {useId} from '@/composables/useId'; +import {useApiUrl} from '@/composables/useApiUrl'; const {generateId} = useId(); -const currentSelected = ref([]); +const currentSelected = ref({}); const inputValue = ref(''); const isFocused = ref(false); -const url = 'https://api.ror.org/v2/organizations'; +const {apiUrl} = useApiUrl(`rors/`); const queryParams = computed(() => ({ - query: inputValue.value, + searchPhrase: inputValue.value, })); const autosuggestContainerId = generateId(); @@ -76,7 +77,7 @@ const { data: suggestions, isLoading, fetch: fetchSuggestions, -} = useFetch(url, { +} = useFetch(apiUrl.value, { query: queryParams, }); @@ -97,22 +98,19 @@ 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) => - i.types.includes('ror_display'), - )?.value; - return { - value: item.id, - label: name, + value: item, + label: item['name'][item['displayLocale']], hasSlot: true, - href: item.id, + href: item.ror, }; }); }); watch(queryParams, () => { - getSuggestions(); + if (inputValue.value.length > 3) { + getSuggestions(); + } }); async function getSuggestions() { @@ -129,7 +127,7 @@ function changeFocus(focused) { function handleSelect(suggestion) { if (!suggestion) { - if (!inputValue.value || !mappedSuggestions.value.length) { + if (!inputValue.value) { return; } @@ -139,8 +137,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 +147,8 @@ function handleDeselect(item) { (selected) => selected.value !== item.value, ); } + +defineExpose({ + currentSelected, +}); diff --git a/src/components/Form/fields/FieldAffiliationsTest.stories.js b/src/components/Form/fields/FieldAffiliationsTest.stories.js new file mode 100644 index 000000000..db29d04a0 --- /dev/null +++ b/src/components/Form/fields/FieldAffiliationsTest.stories.js @@ -0,0 +1,13 @@ +import FieldAffiliationsTest from './FieldAffiliationsTest.vue'; + +export default { + title: 'Forms/FieldAffiliationsTest', +}; + +export const Default = { + component: FieldAffiliationsTest, + render: () => ({ + components: {FieldAffiliationsTest}, + template: '', + }), +}; diff --git a/src/components/Form/fields/FieldAffiliationsTest.vue b/src/components/Form/fields/FieldAffiliationsTest.vue new file mode 100644 index 000000000..498e03028 --- /dev/null +++ b/src/components/Form/fields/FieldAffiliationsTest.vue @@ -0,0 +1,31 @@ + + + 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..df48b006b --- /dev/null +++ b/src/components/Form/mocks/field-affiliations.js @@ -0,0 +1,355 @@ +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: 'TR', + }, + }, + { + id: 13, + authorId: 1, + ror: 'https://ror.org/02e2c7k09', + name: { + en: 'Delft University of Technology', + fr_CA: '', + de: 'Technische Universität Delft', + }, + }, + { + id: 14, + authorId: 1, + ror: '', + name: { + en: 'German National Library of Science and Technology', + fr_CA: '', + de: 'Technische Informationsbibliothek (TIB)', + }, + }, + ], + searchResults: { + itemsMax: 110723, + items: [ + { + id: 1, + isActive: true, + displayLocale: 'en', + name: { + en: 'RMIT University', + fr_CA: '', + }, + ror: 'https://ror.org/04ttjf776', + }, + { + id: 2, + isActive: true, + displayLocale: 'en', + name: { + en: 'La Trobe University', + fr_CA: '', + }, + ror: 'https://ror.org/01rxfrp27', + }, + { + id: 3, + isActive: true, + displayLocale: 'en', + name: { + en: 'Victoria University', + fr_CA: '', + }, + ror: 'https://ror.org/04j757h98', + }, + { + id: 4, + isActive: true, + displayLocale: 'en', + name: { + en: 'University of New England', + fr_CA: '', + }, + ror: 'https://ror.org/04r659a56', + }, + { + id: 5, + isActive: true, + displayLocale: 'en', + name: { + en: 'Griffith University', + fr_CA: '', + }, + ror: 'https://ror.org/02sc3r913', + }, + { + id: 6, + isActive: true, + displayLocale: 'en', + name: { + en: 'Central Queensland University', + fr_CA: '', + }, + ror: 'https://ror.org/023q4bk22', + }, + { + id: 7, + isActive: true, + displayLocale: 'en', + name: { + en: 'University of South Australia', + fr_CA: '', + }, + ror: 'https://ror.org/01p93h210', + }, + { + id: 8, + isActive: true, + displayLocale: 'en', + name: { + en: 'Bond University', + fr_CA: '', + }, + ror: 'https://ror.org/006jxzx88', + }, + { + id: 9, + isActive: true, + displayLocale: 'en', + name: { + en: 'Charles Sturt University', + fr_CA: '', + }, + ror: 'https://ror.org/00wfvh315', + }, + { + id: 10, + isActive: true, + displayLocale: 'en', + name: { + en: 'Federation University', + fr_CA: '', + }, + ror: 'https://ror.org/05qbzwv83', + }, + { + id: 11, + isActive: true, + displayLocale: 'en', + name: { + en: 'Burnet Institute', + fr_CA: '', + }, + ror: 'https://ror.org/05ktbsm52', + }, + { + id: 12, + isActive: true, + displayLocale: 'en', + name: { + en: 'Mater Research', + fr_CA: '', + }, + ror: 'https://ror.org/00nx6aa03', + }, + { + id: 13, + isActive: true, + displayLocale: 'en', + name: { + en: 'St Vincents Institute of Medical Research', + fr_CA: '', + }, + ror: 'https://ror.org/02k3cxs74', + }, + { + id: 14, + isActive: true, + displayLocale: 'en', + name: { + en: 'The Heart Research Institute', + fr_CA: '', + }, + ror: 'https://ror.org/046fa4y88', + }, + { + id: 15, + isActive: true, + displayLocale: 'en', + name: { + en: 'University Mental Health Research Institute', + fr_CA: '', + }, + ror: 'https://ror.org/02d439m40', + }, + { + id: 16, + isActive: true, + displayLocale: 'en', + name: { + en: 'Rolls-Royce (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/04h08p482', + }, + { + id: 17, + isActive: true, + displayLocale: 'en', + name: { + en: 'BP (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/01zctcs90', + }, + { + id: 18, + isActive: true, + displayLocale: 'en', + name: { + en: 'Rio Tinto (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/05m7zw681', + }, + { + id: 19, + isActive: true, + displayLocale: 'en', + name: { + en: 'Arup Group (United States)', + fr_CA: '', + }, + ror: 'https://ror.org/03awtex73', + }, + { + id: 20, + isActive: true, + displayLocale: 'en', + name: { + en: 'BT Group (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/00kv9pj15', + }, + { + id: 21, + isActive: true, + displayLocale: 'en', + name: { + en: 'Mater Health Services', + fr_CA: '', + }, + ror: 'https://ror.org/03mjtdk61', + }, + { + id: 22, + isActive: true, + displayLocale: 'en', + name: { + en: 'Pilkington (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/04yyp8h20', + }, + { + id: 23, + isActive: true, + displayLocale: 'en', + name: { + en: 'Trojan Technologies (Canada)', + fr_CA: '', + }, + ror: 'https://ror.org/022rkxt86', + }, + { + id: 24, + isActive: true, + displayLocale: 'en', + name: { + en: 'The Alfred Hospital', + fr_CA: '', + }, + ror: 'https://ror.org/01wddqe20', + }, + { + id: 25, + isActive: true, + displayLocale: 'en', + name: { + en: 'Washington State Department of Health', + fr_CA: '', + }, + ror: 'https://ror.org/02x2akc96', + }, + { + id: 26, + isActive: true, + displayLocale: 'en', + name: { + en: "Women's and Children's Hospital", + fr_CA: '', + }, + ror: 'https://ror.org/03kwrfk72', + }, + { + id: 27, + isActive: true, + displayLocale: 'en', + name: { + en: 'Xstrata (United Kingdom)', + fr_CA: '', + }, + ror: 'https://ror.org/037mpqg03', + }, + { + id: 28, + isActive: true, + displayLocale: 'en', + name: { + en: 'Teck (Canada)', + fr_CA: '', + }, + ror: 'https://ror.org/05cggb038', + }, + { + id: 29, + isActive: true, + displayLocale: 'en', + name: { + en: 'Hunter New England Local Health District', + fr_CA: '', + }, + ror: 'https://ror.org/050b31k83', + }, + { + id: 30, + isActive: true, + displayLocale: 'en', + name: { + en: 'Cancer Council Victoria', + fr_CA: '', + }, + ror: 'https://ror.org/023m51b03', + }, + ], + }, + newAffiliation: { + id: null, + authorId: 1, + ror: null, + name: {}, + }, +}; diff --git a/src/components/Icon/Icon.stories.js b/src/components/Icon/Icon.stories.js index 1dae02bac..595a82cde 100644 --- a/src/components/Icon/Icon.stories.js +++ b/src/components/Icon/Icon.stories.js @@ -49,7 +49,7 @@ export const IconGallery = { template: `
- +
{{icon}}
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]; }