From 69888785b72f516c75805617e48516b6e7b14479 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 <97682967+Tarunmeena0901@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:48:58 +0530 Subject: [PATCH 01/20] other(editor): Introduced an identifier editing section and alias editing section --- .../alias-editor/alias-editor.js | 40 +++++++++--------- src/client/entity-editor/entity-editor.tsx | 4 +- .../identifier-editor/identifier-editor.js | 42 ++++++++----------- .../identifier-editor/identifier-row.tsx | 2 +- 4 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/client/entity-editor/alias-editor/alias-editor.js b/src/client/entity-editor/alias-editor/alias-editor.js index 3edf74c36f..5fd6d87b6d 100644 --- a/src/client/entity-editor/alias-editor/alias-editor.js +++ b/src/client/entity-editor/alias-editor/alias-editor.js @@ -16,8 +16,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {hideAliasEditor, removeEmptyAliases} from './actions'; +import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import { removeEmptyAliases} from './actions'; import AliasModalBody from './alias-modal-body'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import PropTypes from 'prop-types'; @@ -37,7 +37,7 @@ import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; * @param {Array} props.languageOptions - The list of possible languages for an * alias. * @param {Function} props.onClose - A function to be called when the button to - * close the editor is clicked. + * add new alias is clicked. * @param {boolean} props.show - Whether or not the editor modal should be * visible. * @returns {ReactElement} React element containing the rendered AliasEditor. @@ -63,37 +63,37 @@ const AliasEditor = ({ ); return ( - - - - Alias Editor {helpIconElement} - - +
+
+
+

Add new indentifiers

+
+ {helpIconElement} +
+
+
- +
- +
- - - - +
+ +
+
); }; AliasEditor.displayName = 'AliasEditor'; AliasEditor.propTypes = { languageOptions: PropTypes.array.isRequired, onClose: PropTypes.func.isRequired, - show: PropTypes.bool -}; -AliasEditor.defaultProps = { - show: false + }; function mapDispatchToProps(dispatch) { return { onClose: () => { - dispatch(hideAliasEditor()); + dispatch(removeEmptyAliases()); } }; diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index 82ca154209..4fa50b4d3f 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -87,7 +87,6 @@ const EntityEditor = (props: Props) => { {heading} - { @@ -97,7 +96,8 @@ const EntityEditor = (props: Props) => { ) } - + + diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-editor/identifier-editor.js index 9295e0ae51..1db7b17d21 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-editor/identifier-editor.js @@ -16,8 +16,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {hideIdentifierEditor, removeEmptyIdentifiers} from './actions'; +import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import { removeEmptyIdentifiers} from './actions'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import IdentifierModalBody from './identifier-modal-body'; import PropTypes from 'prop-types'; @@ -39,16 +39,13 @@ import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; * @param {Function} props.onAddIdentifier - A function to be called when the * button to add an identifier is clicked. * @param {Function} props.onClose - A function to be called when the button to - * close the editor is clicked. - * @param {boolean} props.show - Whether or not the editor modal should be - * visible. + * add the identifier is clicked. * @returns {ReactElement} React element containing the rendered * IdentifierEditor. */ const IdentifierEditor = ({ identifierTypes, onClose, - show }) => { const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; @@ -67,37 +64,34 @@ const IdentifierEditor = ({ ); return ( - - - - Identifier Editor {helpIconElement} - - +
+
+
+

Add new indentifiers

+
+ {helpIconElement} +
+
+
- +
- +
- - - - +
+ +
+
); }; IdentifierEditor.displayName = 'IdentifierEditor'; IdentifierEditor.propTypes = { identifierTypes: PropTypes.array.isRequired, onClose: PropTypes.func.isRequired, - show: PropTypes.bool }; -IdentifierEditor.defaultProps = { - show: false -}; - function mapDispatchToProps(dispatch) { return { onClose: () => { - dispatch(hideIdentifierEditor()); dispatch(removeEmptyIdentifiers()); } }; diff --git a/src/client/entity-editor/identifier-editor/identifier-row.tsx b/src/client/entity-editor/identifier-editor/identifier-row.tsx index 16cabe1eed..253d7e9d31 100644 --- a/src/client/entity-editor/identifier-editor/identifier-row.tsx +++ b/src/client/entity-editor/identifier-editor/identifier-row.tsx @@ -119,7 +119,7 @@ function IdentifierRow({ - ); + ); }; AliasEditor.displayName = 'AliasEditor'; AliasEditor.propTypes = { languageOptions: PropTypes.array.isRequired, - onClose: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired }; function mapDispatchToProps(dispatch) { diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-editor/identifier-editor.js index c26379575d..2077522255 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-editor/identifier-editor.js @@ -16,15 +16,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'; -import { removeEmptyIdentifiers } from './actions'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import IdentifierModalBody from './identifier-modal-body'; -import PropTypes from 'prop-types'; import React from 'react'; -import { connect } from 'react-redux'; -import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; - +import PropTypes from 'prop-types'; +import {connect} from 'react-redux'; +import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; +import IdentifierModalBody from './identifier-modal-body'; +import {removeEmptyIdentifiers} from './actions'; /** * Container component. The IdentifierEditor component contains a number of @@ -45,7 +44,7 @@ import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; */ const IdentifierEditor = ({ identifierTypes, - onClose, + onClose }) => { const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; @@ -66,7 +65,7 @@ const IdentifierEditor = ({ return (
-
+

Add new indentifiers

{helpIconElement} @@ -75,7 +74,7 @@ const IdentifierEditor = ({
- +
@@ -87,7 +86,7 @@ const IdentifierEditor = ({ IdentifierEditor.displayName = 'IdentifierEditor'; IdentifierEditor.propTypes = { identifierTypes: PropTypes.array.isRequired, - onClose: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired }; function mapDispatchToProps(dispatch) { return { From 0ed84f868dcbe40d36e6eb92d6b41fb6c954d3c7 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 <97682967+Tarunmeena0901@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:57:50 +0530 Subject: [PATCH 08/20] fixed lint error --- src/client/entity-editor/alias-editor/alias-editor.js | 10 +++++----- .../identifier-editor/identifier-editor.js | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/client/entity-editor/alias-editor/alias-editor.js b/src/client/entity-editor/alias-editor/alias-editor.js index efb506e26a..bedfe762d4 100644 --- a/src/client/entity-editor/alias-editor/alias-editor.js +++ b/src/client/entity-editor/alias-editor/alias-editor.js @@ -15,14 +15,14 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import React from 'react'; +import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {hideAliasEditor, removeEmptyAliases} from './actions'; +import AliasModalBody from './alias-modal-body'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import PropTypes from 'prop-types'; +import React from 'react'; import {connect} from 'react-redux'; -import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; -import AliasModalBody from './alias-modal-body'; -import {removeEmptyAliases} from './actions'; /** * Container component. The AliasEditor component contains a number of AliasRow diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-editor/identifier-editor.js index 2077522255..a9d4f2351e 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-editor/identifier-editor.js @@ -16,14 +16,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import React from 'react'; +import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {hideIdentifierEditor, removeEmptyIdentifiers} from './actions'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import IdentifierModalBody from './identifier-modal-body'; import PropTypes from 'prop-types'; +import React from 'react'; import {connect} from 'react-redux'; -import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; -import IdentifierModalBody from './identifier-modal-body'; -import {removeEmptyIdentifiers} from './actions'; /** * Container component. The IdentifierEditor component contains a number of From 4b02088520ae43f341ceadee4ef90366905884b1 Mon Sep 17 00:00:00 2001 From: "Ta.run" <97682967+Tarunmeena0901@users.noreply.github.com> Date: Sat, 13 Jan 2024 02:35:03 +0530 Subject: [PATCH 09/20] identifier editing section heading change Co-authored-by: Monkey Do --- src/client/entity-editor/identifier-editor/identifier-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-editor/identifier-editor.js index a9d4f2351e..580ceb5b6b 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-editor/identifier-editor.js @@ -66,7 +66,7 @@ const IdentifierEditor = ({
-

Add new indentifiers

+

Add identifiers

{helpIconElement}
From 8d6ad13ed5a28b541339454a7f8d5cf93d163678 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 <97682967+Tarunmeena0901@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:11:19 +0530 Subject: [PATCH 10/20] other(entity-editor): made UI changes and removed unecessary imports --- .../alias-editor/alias-editor.js | 30 +++---------- .../entity-editor/alias-editor/alias-row.js | 45 +++++++------------ src/client/entity-editor/entity-editor.tsx | 21 ++------- .../identifier-editor/identifier-editor.js | 25 ++--------- .../identifier-editor/identifier-row.tsx | 25 +++++------ 5 files changed, 40 insertions(+), 106 deletions(-) diff --git a/src/client/entity-editor/alias-editor/alias-editor.js b/src/client/entity-editor/alias-editor/alias-editor.js index bedfe762d4..e5f46159ac 100644 --- a/src/client/entity-editor/alias-editor/alias-editor.js +++ b/src/client/entity-editor/alias-editor/alias-editor.js @@ -15,8 +15,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {hideAliasEditor, removeEmptyAliases} from './actions'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import AliasModalBody from './alias-modal-body'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import PropTypes from 'prop-types'; @@ -34,15 +33,10 @@ import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; * editor. * @param {Array} props.languageOptions - The list of possible languages for an * alias. - * @param {Function} props.onClose - A function to be called when the button to - * add new alias is clicked. - * @param {boolean} props.show - Whether or not the editor modal should be - * visible. * @returns {ReactElement} React element containing the rendered AliasEditor. */ const AliasEditor = ({ - languageOptions, - onClose + languageOptions }) => { const helpText = `Variant names for an entity such as alternate spelling, different script, stylistic representation, acronyms, etc. Refer to the help page for more details and examples.`; @@ -62,9 +56,9 @@ const AliasEditor = ({ return (
-
+

Add new alias

-
+
{helpIconElement}
@@ -72,24 +66,12 @@ const AliasEditor = ({
-
- -
); }; AliasEditor.displayName = 'AliasEditor'; AliasEditor.propTypes = { - languageOptions: PropTypes.array.isRequired, - onClose: PropTypes.func.isRequired + languageOptions: PropTypes.array.isRequired }; -function mapDispatchToProps(dispatch) { - return { - onClose: () => { - dispatch(removeEmptyAliases()); - } - }; -} - -export default connect(null, mapDispatchToProps)(AliasEditor); +export default connect(null, null)(AliasEditor); diff --git a/src/client/entity-editor/alias-editor/alias-row.js b/src/client/entity-editor/alias-editor/alias-row.js index e744f0c277..9067650168 100644 --- a/src/client/entity-editor/alias-editor/alias-row.js +++ b/src/client/entity-editor/alias-editor/alias-row.js @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Col, Form, Row} from 'react-bootstrap'; +import {Col, Form, Row} from 'react-bootstrap'; import { debouncedUpdateAliasName, debouncedUpdateAliasSortName, removeAliasRow, updateAliasLanguage, updateAliasPrimary @@ -30,8 +30,8 @@ import NameField from '../common/name-field'; import PropTypes from 'prop-types'; import React from 'react'; import SortNameField from '../common/sort-name-field'; +import {faTrash} from '@fortawesome/free-solid-svg-icons'; import {connect} from 'react-redux'; -import {faTimes} from '@fortawesome/free-solid-svg-icons'; import {isAliasEmpty} from '../helpers'; @@ -75,25 +75,21 @@ const AliasRow = ({ onRemoveButtonClick, onPrimaryClick }) => ( -
+
- + - + - - + + + +  Remove + + - - - -
); AliasRow.displayName = 'AliasEditor.AliasRow'; diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index 4fa50b4d3f..14ea4349dd 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -37,8 +37,7 @@ type OwnProps = { }; type StateProps = { - aliasEditorVisible: boolean, - identifierEditorVisible: boolean, + }; type DispatchProps = { @@ -51,10 +50,6 @@ type Props = StateProps & DispatchProps & OwnProps; * Container component. Renders all of the sections of the entity editing form. * * @param {Object} props - The properties passed to the component. - * @param {boolean} props.aliasEditorVisible - Whether the alias editor modal - * should be made visible. - * @param {boolean} props.identifierEditorVisible - Whether the identifier - * editor modal should be made visible. * @param {React.Node} props.children - The child content to wrap with this * entity editor form. * @param {Function} props.onSubmit - A function to be called when the @@ -63,10 +58,8 @@ type Props = StateProps & DispatchProps & OwnProps; */ const EntityEditor = (props: Props) => { const { - aliasEditorVisible, children, heading, - identifierEditorVisible, onSubmit } = props; const currentState = (useSelector((state) => state) as any).toJS(); @@ -89,6 +82,7 @@ const EntityEditor = (props: Props) => { + { React.cloneElement( React.Children.only(children), @@ -97,7 +91,6 @@ const EntityEditor = (props: Props) => { } - @@ -109,14 +102,6 @@ const EntityEditor = (props: Props) => { }; EntityEditor.displayName = 'EntityEditor'; -function mapStateToProps(rootState): StateProps { - const state = rootState.get('buttonBar'); - return { - aliasEditorVisible: state.get('aliasEditorVisible'), - identifierEditorVisible: state.get('identifierEditorVisible') - }; -} - function mapDispatchToProps(dispatch, {submissionUrl}) { return { onSubmit: (event:React.FormEvent) => { @@ -126,4 +111,4 @@ function mapDispatchToProps(dispatch, {submissionUrl}) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(EntityEditor); +export default connect(null, mapDispatchToProps)(EntityEditor); diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-editor/identifier-editor.js index 580ceb5b6b..ac0f4e4225 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-editor/identifier-editor.js @@ -16,8 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Modal, OverlayTrigger, Tooltip} from 'react-bootstrap'; -import {hideIdentifierEditor, removeEmptyIdentifiers} from './actions'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import IdentifierModalBody from './identifier-modal-body'; import PropTypes from 'prop-types'; @@ -37,14 +36,11 @@ import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; * identifier. * @param {Function} props.onAddIdentifier - A function to be called when the * button to add an identifier is clicked. - * @param {Function} props.onClose - A function to be called when the button to - * add the identifier is clicked. * @returns {ReactElement} React element containing the rendered * IdentifierEditor. */ const IdentifierEditor = ({ - identifierTypes, - onClose + identifierTypes }) => { const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; @@ -72,28 +68,15 @@ const IdentifierEditor = ({
-
- -
- -
); }; IdentifierEditor.displayName = 'IdentifierEditor'; IdentifierEditor.propTypes = { - identifierTypes: PropTypes.array.isRequired, - onClose: PropTypes.func.isRequired + identifierTypes: PropTypes.array.isRequired }; -function mapDispatchToProps(dispatch) { - return { - onClose: () => { - dispatch(removeEmptyIdentifiers()); - } - }; -} -export default connect(null, mapDispatchToProps)(IdentifierEditor); +export default connect(null, null)(IdentifierEditor); diff --git a/src/client/entity-editor/identifier-editor/identifier-row.tsx b/src/client/entity-editor/identifier-editor/identifier-row.tsx index 253d7e9d31..5b48ae073b 100644 --- a/src/client/entity-editor/identifier-editor/identifier-row.tsx +++ b/src/client/entity-editor/identifier-editor/identifier-row.tsx @@ -24,7 +24,7 @@ import { Action, debouncedUpdateIdentifierValue, removeIdentifierRow, updateIdentifierType } from './actions'; -import {Button, Col, Form, Row} from 'react-bootstrap'; +import {Col, Form, Row} from 'react-bootstrap'; import { IdentifierType, validateIdentifierValue @@ -35,7 +35,7 @@ import Select from 'react-select'; import ValueField from './value-field'; import {collapseWhiteSpaces} from '../../../common/helpers/utils'; import {connect} from 'react-redux'; -import {faTimes} from '@fortawesome/free-solid-svg-icons'; +import {faTrash} from '@fortawesome/free-solid-svg-icons'; type OwnProps = { @@ -92,9 +92,9 @@ function IdentifierRow({ })); const identifierValue = identifierTypesForDisplay.filter((el) => el.value === typeValue); return ( -
+
- + - + Type + + + + + + + + + + ); +}; +identifierSection.displayName = 'identifierSection'; +identifierSection.propTypes = { + identifierTypes: PropTypes.array.isRequired, + show: PropTypes.bool +}; +identifierSection.defaultProps = { + show: false +}; + +function mapDispatchToProps(dispatch) { + return { + onRemoveEmptyIdentifiers: () => { + dispatch(removeEmptyIdentifiers()); + } + }; +} + +export default connect(null, mapDispatchToProps)(identifierSection); diff --git a/src/client/entity-editor/identifier-section/identifier-fields.tsx b/src/client/entity-editor/identifier-section/identifier-fields.tsx new file mode 100644 index 0000000000..f0c9408cc5 --- /dev/null +++ b/src/client/entity-editor/identifier-section/identifier-fields.tsx @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2016 Ben Ockmore + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +import * as React from 'react'; +import * as data from '../../helpers/data'; + +import { + Action, addIdentifier +} from './actions'; +import {Button, Col, Container, Form, Row} from 'react-bootstrap'; +import { + IdentifierType, + validateIdentifierValue +} from '../validators/common'; +import type {Dispatch} from 'redux'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import Select from 'react-select'; +import ValueField from './value-field'; +import {collapseWhiteSpaces} from '../../../common/helpers/utils'; +import {connect} from 'react-redux'; +import {faPlus} from '@fortawesome/free-solid-svg-icons'; + + +type OwnProps = { + index: number, + typeOptions: Array, + isUnifiedForm: boolean +}; + +type DispatchProps = { + onAddNewIdentifier: (value, type) => unknown +}; + +type Props = DispatchProps & OwnProps; + +type TypeObj = { + value: number, + label: string +}; + +/** + * The IdentifierFields component renders a single Row + * containing several input fields, allowing the user to set the value and type + * for an identifier in the identifierSection. A button is also included to + * remove the identifier from the editor. + * + * @param {Object} props - The properties passed to the component. + * @param {number} props.index - The index of the row in the parent editor. + * @param {Array} props.typeOptions - The list of possible types for an + * identifier. + * @param {Function} props.onAddNewIdentifier - A function to be called when a new + * identifier is added + * @returns {ReactElement} React element containing the rendered IdentifierRow. + */ +function IdentifierFields({ + index, + typeOptions, + onAddNewIdentifier, + isUnifiedForm +}: Props) { + const identifierTypesForDisplay = typeOptions.map((type) => ({ + label: type.label, + value: type.id + })); + + const [value, setValue] = React.useState(''); + const [type, setType] = React.useState(null); + + const lgCol = {offset: 3, span: 2}; + if (isUnifiedForm) { + lgCol.offset = 0; + } + + const handleIdentifierValueChange = React.useCallback((event: React.ChangeEvent) => { + setValue(event.target.value); + }, []); + + const handleAddIdentifier = React.useCallback(() => { + onAddNewIdentifier(value, type); + }, [value, type]); + + return ( + // + + + + + + + + Type + - - - + + -  Remove - +
); } -IdentifierRow.displayName = 'IdentifierEditor.Identifier'; +IdentifierRow.displayName = 'IdentifierRow'; function handleValueChange( @@ -162,16 +169,14 @@ function handleValueChange( return dispatch(debouncedUpdateIdentifierValue(index, value, guessedType)); } - function mapStateToProps(rootState, {index}: OwnProps): StateProps { - const state = rootState.get('identifierEditor'); + const state = rootState.get('identifierSection'); return { typeValue: state.getIn([index, 'type']), valueValue: state.getIn([index, 'value']) }; } - function mapDispatchToProps( dispatch: Dispatch, {index, typeOptions}: OwnProps diff --git a/src/client/entity-editor/identifier-section/identifier-section-body.tsx b/src/client/entity-editor/identifier-section/identifier-section-body.tsx new file mode 100644 index 0000000000..56f4e34084 --- /dev/null +++ b/src/client/entity-editor/identifier-section/identifier-section-body.tsx @@ -0,0 +1,62 @@ +import IdentifierFields from './identifier-fields'; +import IdentifierRow from './identifier-row'; +import Immutable from 'immutable'; +import React from 'react'; +import classNames from 'classnames'; +import {connect} from 'react-redux'; + + +type IdentifierSectionBodyStateProps = { + identifiers: Immutable.Map; +}; +type IdentifierSectionBodyOwnProps = { + identifierTypes: Array, + isUnifiedForm: boolean +}; +type IdentifierSectionBodyProps = IdentifierSectionBodyOwnProps & IdentifierSectionBodyStateProps; + + +export const IdentifierSectionBody = ({identifiers, identifierTypes, isUnifiedForm}:IdentifierSectionBodyProps) => { + let textAlign = 'text-center'; + if (isUnifiedForm) { + textAlign = 'text-start'; + } + const noIdentifiersTextClass = + classNames({textAlign}, {'d-none': identifiers.size}); + return ( + <> +
+

This entity has no identifiers

+
+
+ { + identifiers.map((identifier, rowId) => ( + + )).toArray() + } +
+
+ +
+ ); +}; + +function mapStateToProps(state) { + return { + identifiers: state.get('identifierSection') + }; +} + +export default connect(mapStateToProps, null)(IdentifierSectionBody); diff --git a/src/client/entity-editor/identifier-editor/identifier-editor.js b/src/client/entity-editor/identifier-section/identifier-section.js similarity index 72% rename from src/client/entity-editor/identifier-editor/identifier-editor.js rename to src/client/entity-editor/identifier-section/identifier-section.js index ac0f4e4225..eb103f106d 100644 --- a/src/client/entity-editor/identifier-editor/identifier-editor.js +++ b/src/client/entity-editor/identifier-section/identifier-section.js @@ -18,33 +18,30 @@ import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import IdentifierModalBody from './identifier-modal-body'; +import IdentifierSectionBody from './identifier-section-body'; import PropTypes from 'prop-types'; import React from 'react'; import {connect} from 'react-redux'; import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; /** - * Container component. The IdentifierEditor component contains a number of - * IdentifierRow elements, and renders these inside a modal, which appears when - * the show property of the component is set. + * Container component. The identifierSection component contains + * IdentifierSectionBody elements, and renders it in the entity editor page * * @param {Object} props - The properties passed to the component. * @param {Array} props.identifiers - The list of identifiers to be rendered in * the editor. * @param {Array} props.identifierTypes - The list of possible types for an * identifier. - * @param {Function} props.onAddIdentifier - A function to be called when the - * button to add an identifier is clicked. * @returns {ReactElement} React element containing the rendered - * IdentifierEditor. + * identifierSection. */ -const IdentifierEditor = ({ - identifierTypes +const IdentifierSection = ({ + identifierTypes, + isUnifiedForm }) => { const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; - const helpIconElement = ( ); - return (
-
+

Add identifiers

-
+
{helpIconElement}
- +
); }; -IdentifierEditor.displayName = 'IdentifierEditor'; -IdentifierEditor.propTypes = { - identifierTypes: PropTypes.array.isRequired +IdentifierSection.displayName = 'IdentifierSection'; +IdentifierSection.propTypes = { + identifierTypes: PropTypes.array.isRequired, + isUnifiedForm: PropTypes.bool +}; +IdentifierSection.defaultProps = { + isUnifiedForm: false }; -export default connect(null, null)(IdentifierEditor); +export default connect(null, null)(IdentifierSection); diff --git a/src/client/entity-editor/identifier-editor/reducer.ts b/src/client/entity-editor/identifier-section/reducer.ts similarity index 80% rename from src/client/entity-editor/identifier-editor/reducer.ts rename to src/client/entity-editor/identifier-section/reducer.ts index 9e5749ac98..3e74fcb83b 100644 --- a/src/client/entity-editor/identifier-editor/reducer.ts +++ b/src/client/entity-editor/identifier-section/reducer.ts @@ -18,16 +18,12 @@ import * as Immutable from 'immutable'; import { - ADD_IDENTIFIER_ROW, ADD_OTHER_ISBN, REMOVE_EMPTY_IDENTIFIERS, REMOVE_IDENTIFIER_ROW, + ADD_IDENTIFIER, + ADD_OTHER_ISBN, REMOVE_EMPTY_IDENTIFIERS, REMOVE_IDENTIFIER_ROW, UPDATE_IDENTIFIER_TYPE, UPDATE_IDENTIFIER_VALUE } from './actions'; -const EMPTY_IDENTIFIER = Immutable.Map({ - type: null, - value: '' -}); - type State = Immutable.OrderedMap; function reducer( @@ -36,8 +32,19 @@ function reducer( ) { const {type, payload} = action; switch (type) { - case ADD_IDENTIFIER_ROW: - return state.set(payload, EMPTY_IDENTIFIER); + case ADD_IDENTIFIER: { + const IDENTIFIER_DATA = Immutable.Map(payload.data); + const newIdentifier = state.set(payload.rowId, IDENTIFIER_DATA); + + // don't switch type if user already selected it + if (payload.suggestedType && !state.getIn([payload.rowId, 'type'])) { + return newIdentifier.setIn( + [payload.rowId, 'type'], payload.suggestedType.id + ); + } + + return newIdentifier; + } case UPDATE_IDENTIFIER_VALUE: { const updatedValue = state.setIn( @@ -58,7 +65,7 @@ function reducer( return state.delete(payload); case REMOVE_EMPTY_IDENTIFIERS: return state.filterNot(identifier => - identifier.get('value') === '' && identifier.get('type') === null); + identifier.value === '' && identifier.type === null); case ADD_OTHER_ISBN: { const {rowId, value, type: typeId} = payload; // search if given identifier already exists diff --git a/src/client/entity-editor/identifier-editor/value-field.js b/src/client/entity-editor/identifier-section/value-field.js similarity index 100% rename from src/client/entity-editor/identifier-editor/value-field.js rename to src/client/entity-editor/identifier-section/value-field.js diff --git a/src/client/entity-editor/submission-section/actions.ts b/src/client/entity-editor/submission-section/actions.ts index 974f9add5c..d8154b6157 100644 --- a/src/client/entity-editor/submission-section/actions.ts +++ b/src/client/entity-editor/submission-section/actions.ts @@ -183,7 +183,7 @@ function transformFormData(data:Record):Record { }); // add edition at last if (data.ISBN.type) { - data.identifierEditor.m0 = data.ISBN; + data.identifierSection.m0 = data.ISBN; } data.relationshipSection.relationships = _.mapValues(data.Works, (work, key) => { const relationship = { diff --git a/src/client/entity-editor/validators/author.ts b/src/client/entity-editor/validators/author.ts index 0bba8ab215..2b301a6ce1 100644 --- a/src/client/entity-editor/validators/author.ts +++ b/src/client/entity-editor/validators/author.ts @@ -98,9 +98,9 @@ export function validateForm( formData: any, identifierTypes?: Array<_IdentifierType> | null | undefined ): boolean { const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validateAuthorSection(get(formData, 'authorSection', {})), diff --git a/src/client/entity-editor/validators/edition-group.ts b/src/client/entity-editor/validators/edition-group.ts index 2132fa5a9e..9b06279d68 100644 --- a/src/client/entity-editor/validators/edition-group.ts +++ b/src/client/entity-editor/validators/edition-group.ts @@ -57,9 +57,9 @@ export function validateForm( validAuthorCredit = validateAuthorCreditSection(get(formData, 'authorCreditEditor', {}), authorCreditEnable); } const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validateEditionGroupSection(get(formData, 'editionGroupSection', {})), diff --git a/src/client/entity-editor/validators/edition.ts b/src/client/entity-editor/validators/edition.ts index 91e45af41d..93b8070b0a 100644 --- a/src/client/entity-editor/validators/edition.ts +++ b/src/client/entity-editor/validators/edition.ts @@ -147,9 +147,9 @@ export function validateForm( validAuthorCredit = validateAuthorCreditSection(get(formData, 'authorCreditEditor', {}), authorCreditEnable); } const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validateEditionSection(get(formData, 'editionSection', {})), diff --git a/src/client/entity-editor/validators/publisher.ts b/src/client/entity-editor/validators/publisher.ts index d09a1210c9..7011960f31 100644 --- a/src/client/entity-editor/validators/publisher.ts +++ b/src/client/entity-editor/validators/publisher.ts @@ -80,9 +80,9 @@ export function validateForm( formData: any, identifierTypes?: Array<_IdentifierType> | null | undefined ): boolean { const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validatePublisherSection(get(formData, 'publisherSection', {})), diff --git a/src/client/entity-editor/validators/series.ts b/src/client/entity-editor/validators/series.ts index 996952eb55..490e60c7f5 100644 --- a/src/client/entity-editor/validators/series.ts +++ b/src/client/entity-editor/validators/series.ts @@ -51,9 +51,9 @@ export function validateForm( formData: any, identifierTypes?: Array<_IdentifierType> | null | undefined ): boolean { const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validateSeriesSection(get(formData, 'seriesSection', {})), diff --git a/src/client/entity-editor/validators/work.ts b/src/client/entity-editor/validators/work.ts index b77d565e23..6fcc73495f 100644 --- a/src/client/entity-editor/validators/work.ts +++ b/src/client/entity-editor/validators/work.ts @@ -49,9 +49,9 @@ export function validateForm( formData: any, identifierTypes?: Array<_IdentifierType> | null | undefined ): boolean { const conditions = [ - validateAliases(get(formData, 'aliasEditor', {})), + validateAliases(get(formData, 'aliasSection', {})), validateIdentifiers( - get(formData, 'identifierEditor', {}), identifierTypes + get(formData, 'identifierSection', {}), identifierTypes ), validateNameSection(get(formData, 'nameSection', {})), validateWorkSection(get(formData, 'workSection', {})), diff --git a/src/client/unified-form/common/entity-modal-body.tsx b/src/client/unified-form/common/entity-modal-body.tsx index dd43e31f13..f72d20bfe7 100644 --- a/src/client/unified-form/common/entity-modal-body.tsx +++ b/src/client/unified-form/common/entity-modal-body.tsx @@ -1,8 +1,8 @@ import {EntityModalBodyProps, EntityModalDispatchProps} from '../interface/type'; -import AliasModalBody from '../../entity-editor/alias-editor/alias-modal-body'; +import AliasModalBody from '../../entity-editor/alias-section/alias-section-body'; // import {validateAliases, validateIdentifiers, validateNameSection} from '../../entity-editor/validators/common'; import AnnotationSection from '../../entity-editor/annotation-section/annotation-section'; -import IdentifierModalBody from '../../entity-editor/identifier-editor/identifier-modal-body'; +import IdentifierSectionBody from '../../entity-editor/identifier-section/identifier-section-body'; import NameSection from '../../entity-editor/name-section/name-section'; import React from 'react'; import RelationshipSection from '../../entity-editor/relationship-editor/relationship-section'; @@ -10,8 +10,8 @@ import SingleAccordion from './single-accordion'; import SubmissionSection from '../../entity-editor/submission-section/submission-section'; import {connect} from 'react-redux'; import {omit} from 'lodash'; -import {removeEmptyAliases} from '../../entity-editor/alias-editor/actions'; -import {removeEmptyIdentifiers} from '../../entity-editor/identifier-editor/actions'; +import {removeEmptyAliases} from '../../entity-editor/alias-section/actions'; +import {removeEmptyIdentifiers} from '../../entity-editor/identifier-section/actions'; function EntityModalBody({onModalSubmit, children, validate, onAliasClose, onIdentifierClose, ...rest} @@ -34,7 +34,7 @@ function EntityModalBody({onModalSubmit, children, validate, onAliasClose, onIde - + diff --git a/src/client/unified-form/cover-tab/cover-tab.tsx b/src/client/unified-form/cover-tab/cover-tab.tsx index e3d7907938..b9ac6b6a9e 100644 --- a/src/client/unified-form/cover-tab/cover-tab.tsx +++ b/src/client/unified-form/cover-tab/cover-tab.tsx @@ -2,9 +2,8 @@ import {Col, Row} from 'react-bootstrap'; import {CoverDispatchProps, CoverProps, CoverStateProps, EntitySelect, State} from '../interface/type'; import {clearPublisher, clearPublishers} from './action'; import AuthorCreditSection from '../../entity-editor/author-credit-editor/author-credit-section'; -import ButtonBar from '../../entity-editor/button-bar/button-bar'; import ISBNField from './isbn-field'; -import IdentifierEditor from '../../entity-editor/identifier-editor/identifier-editor'; +import IdentifierSection from '../../entity-editor/identifier-section/identifier-section'; import NameSection from '../../entity-editor/name-section/name-section'; import React from 'react'; import SearchEntityCreate from '../common/search-entity-create-select'; @@ -14,7 +13,7 @@ import {updatePublisher} from '../../entity-editor/edition-section/actions'; export function CoverTab(props:CoverProps) { - const {publisherValue: publishers, onPublisherChange, identifierEditorVisible, + const {publisherValue: publishers, onPublisherChange, onClearPublisher, handleClearPublishers, modalIsOpen, ...rest} = props; const publisherValue:EntitySelect[] = Object.values(convertMapToObject(publishers ?? {})); const onChangeHandler = React.useCallback((value:EntitySelect[], action) => { @@ -50,9 +49,7 @@ export function CoverTab(props:CoverProps) { - - - +
); @@ -60,7 +57,6 @@ export function CoverTab(props:CoverProps) { function mapStateToProps(rootState:State):CoverStateProps { return { - identifierEditorVisible: rootState.getIn(['buttonBar', 'identifierEditorVisible']), modalIsOpen: rootState.get('entityModalIsOpen', false), publisherValue: rootState.getIn(['editionSection', 'publisher'], {}) }; diff --git a/src/client/unified-form/cover-tab/isbn-field.tsx b/src/client/unified-form/cover-tab/isbn-field.tsx index cfe4d4dfb3..c286a974ac 100644 --- a/src/client/unified-form/cover-tab/isbn-field.tsx +++ b/src/client/unified-form/cover-tab/isbn-field.tsx @@ -4,7 +4,7 @@ import {isbn10To13, isbn13To10} from '../../../common/helpers/utils'; import {FormCheck} from 'react-bootstrap'; import NameField from '../../entity-editor/common/name-field'; import React from 'react'; -import {addOtherISBN} from '../../entity-editor/identifier-editor/actions'; +import {addOtherISBN} from '../../entity-editor/identifier-section/actions'; import {connect} from 'react-redux'; diff --git a/src/client/unified-form/helpers.ts b/src/client/unified-form/helpers.ts index 053564a0e3..b51ac763e1 100644 --- a/src/client/unified-form/helpers.ts +++ b/src/client/unified-form/helpers.ts @@ -6,18 +6,17 @@ import {seriesReducer, worksReducer} from './content-tab/reducer'; import {ADD_EDITION_GROUP} from './detail-tab/action'; import {DUPLICATE_WORK} from './content-tab/action'; import Immutable from 'immutable'; -import aliasEditorReducer from '../entity-editor/alias-editor/reducer'; +import aliasSectionReducer from '../entity-editor/alias-section/reducer'; import annotationSectionReducer from '../entity-editor/annotation-section/reducer'; import authorCreditEditorReducer from '../entity-editor/author-credit-editor/reducer'; import authorSectionReducer from '../entity-editor/author-section/reducer'; -import buttonBarReducer from '../entity-editor/button-bar/reducer'; import {camelCase} from 'lodash'; import {combineReducers} from 'redux-immutable'; import {convertMapToObject} from '../helpers/utils'; import editionGroupSectionReducer from '../entity-editor/edition-group-section/reducer'; import editionGroupsReducer from './detail-tab/reducer'; import editionSectionReducer from '../entity-editor/edition-section/reducer'; -import identifierEditorReducer from '../entity-editor/identifier-editor/reducer'; +import identifierSectionReducer from '../entity-editor/identifier-section/reducer'; import nameSectionReducer from '../entity-editor/name-section/reducer'; import publisherSectionReducer from '../entity-editor/publisher-section/reducer'; import relationshipSectionReducer from '../entity-editor/relationship-editor/reducer'; @@ -67,12 +66,8 @@ const initialACState = Immutable.fromJS( } ); const initialState = Immutable.Map({ - aliasEditor: Immutable.Map({}), + aliasSection: Immutable.Map({}), annotationSection: Immutable.Map({content: ''}), - buttonBar: Immutable.Map({ - aliasEditorVisible: false, - identifierEditorVisible: false - }), editionSection: Immutable.Map({ authorCreditEditorVisible: false, authorCreditEnable: true, @@ -85,7 +80,7 @@ const initialState = Immutable.Map({ releaseDate: '', status: null }), - identifierEditor: Immutable.OrderedMap(), + identifierSection: Immutable.OrderedMap(), nameSection: Immutable.Map({ disambiguation: '', exactMatches: [], @@ -117,10 +112,9 @@ function crossSliceReducer(state:State, action:Action) { const {type} = action; let intermediateState = state; const activeEntityState = { - aliasEditor: state.get('aliasEditor'), + aliasSection: state.get('aliasSection'), annotationSection: state.get('annotationSection'), - buttonBar: state.get('buttonBar'), - identifierEditor: state.get('identifierEditor'), + identifierSection: state.get('identifierSection'), nameSection: state.get('nameSection'), relationshipSection: state.get('relationshipSection'), submissionSection: state.get('submissionSection') @@ -182,9 +176,9 @@ function crossSliceReducer(state:State, action:Action) { const identifiers = fromWork.getIn(['identifierSet', 'identifiers'], null); const other:any = {}; if (identifiers) { - const identifierEditor = identifiers.map((identifier) => Immutable.Map({type: identifier.get('typeId'), + const identifierSection = identifiers.map((identifier) => Immutable.Map({type: identifier.get('typeId'), value: identifier.get('value')})); - other.identifierEditor = identifierEditor; + other.identifierSection = identifierSection; } if (relationships) { const rels = relationships.map((rel, key) => { @@ -260,16 +254,15 @@ export function createRootReducer() { Publishers: publishersReducer, Series: seriesReducer, Works: worksReducer, - aliasEditor: aliasEditorReducer, + aliasSection: aliasSectionReducer, annotationSection: annotationSectionReducer, authorCreditEditor: authorCreditEditorReducer, authorSection: authorSectionReducer, autoISBN: autoISBNReducer, - buttonBar: buttonBarReducer, editionGroupSection: editionGroupSectionReducer, editionSection: editionSectionReducer, entityModalIsOpen: entityModalIsOpenReducer, - identifierEditor: identifierEditorReducer, + identifierSection: identifierSectionReducer, nameSection: nameSectionReducer, publisherSection: publisherSectionReducer, relationshipSection: relationshipSectionReducer, diff --git a/src/client/unified-form/interface/type.ts b/src/client/unified-form/interface/type.ts index 539200b22c..75de0760d6 100644 --- a/src/client/unified-form/interface/type.ts +++ b/src/client/unified-form/interface/type.ts @@ -74,7 +74,6 @@ export type CoverOwnProps = { export type CoverStateProps = { publisherValue:any[], modalIsOpen:boolean, - identifierEditorVisible:boolean }; export type CoverDispatchProps = { onPublisherChange: (arg:any)=>unknown, @@ -151,10 +150,10 @@ export type EntityModalStateProps = { isNameSectionValid:boolean, isNameSectionEmpty:boolean, isAliasEditorValid:boolean, - isIdentifierEditorValid:boolean, + isIdentifierSeactionValid:boolean, isEntitySectionValid:boolean, isAliasEditorEmpty:boolean, - isIdentifierEditorEmpty:boolean + isIdentifierSectionEmpty:boolean }; export type EntityModalBodyOwnProps = { diff --git a/src/client/unified-form/validators/cover-tab.ts b/src/client/unified-form/validators/cover-tab.ts index 6f636ae67e..a5a6673bd4 100644 --- a/src/client/unified-form/validators/cover-tab.ts +++ b/src/client/unified-form/validators/cover-tab.ts @@ -25,7 +25,7 @@ export function validateISBN(isbn) { */ export function validateCoverTab(data:any, identifierTypes:any[]) { return validateNameSection(get(data, 'nameSection')) && - validateIdentifiers(get(data, 'identifierEditor', {}), identifierTypes) && + validateIdentifiers(get(data, 'identifierSection', {}), identifierTypes) && validateAuthorCreditSection(get(data, 'authorCreditEditor')) && validateISBN(get(data, 'ISBN')); } @@ -40,13 +40,13 @@ export function isCoverTabEmpty(data:any) { const nameSection = get(data, 'nameSection', {}); const authorCreditEditor = get(data, 'authorCreditEditor', {}); const ISBN = get(data, 'ISBN', {}); - const identifierEditor = get(data, 'identifierEditor', {}); + const identifierSection = get(data, 'identifierSection', {}); return nameSection.name?.length === 0 && nameSection.sortName?.length === 0 && !nameSection.language && nameSection.disambiguation?.length === 0 && size(authorCreditEditor) === 1 && !authorCreditEditor.n0?.author && - size(identifierEditor) === 0 && + size(identifierSection) === 0 && !ISBN.type; } diff --git a/src/server/helpers/utils.ts b/src/server/helpers/utils.ts index a9659a9d37..90cf0ebcd3 100644 --- a/src/server/helpers/utils.ts +++ b/src/server/helpers/utils.ts @@ -223,7 +223,7 @@ export async function getIdByField( * Generate Identifier state from req body * * @param {object} sourceIdentifierState - source state in format of t{typeId}:value - * @returns {object} - correctly formatted identifierEditor state + * @returns {object} - correctly formatted identifierSection state */ export function generateIdenfierState(sourceIdentifierState:Record):Record { let index = 0; @@ -307,7 +307,7 @@ export async function searchOption(orm, type:string, query:string, idKey = 'id', } /** - * Parse NameSection, IdentifierEditor, AnnotationSection state from request body + * Parse NameSection, IdentifierSection, AnnotationSection state from request body * * @param {object} req - Request object * @param {string} type - entity type @@ -337,9 +337,9 @@ export async function parseInitialState(req, type):Promise> if (initialState.nameSection.language) { initialState.nameSection.language = await getIdByField(Language, 'name', initialState.nameSection.language); } - // IdentifierEditor State - if (initialState.identifierEditor) { - initialState.identifierEditor = generateIdenfierState(initialState.identifierEditor); + // IdentifierSection State + if (initialState.identifierSection) { + initialState.identifierSection = generateIdenfierState(initialState.identifierSection); } // AnnotationSection State if (initialState.annotationSection) { diff --git a/src/server/routes/entity/author.ts b/src/server/routes/entity/author.ts index 3992613ddb..c14cdbeb65 100644 --- a/src/server/routes/entity/author.ts +++ b/src/server/routes/entity/author.ts @@ -53,11 +53,11 @@ const additionalAuthorProps = [ export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const relationships = entityRoutes.constructRelationships( @@ -258,13 +258,8 @@ export function authorToFormState(author) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(author.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -280,9 +275,9 @@ export function authorToFormState(author) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const authorSection = { @@ -320,10 +315,9 @@ export function authorToFormState(author) { } return { - aliasEditor, + aliasSection, authorSection, - buttonBar, - identifierEditor, + identifierSection, nameSection, relationshipSection, ...optionalSections diff --git a/src/server/routes/entity/edition-group.ts b/src/server/routes/entity/edition-group.ts index 59b4b2ab4a..e25a6e1931 100644 --- a/src/server/routes/entity/edition-group.ts +++ b/src/server/routes/entity/edition-group.ts @@ -52,11 +52,11 @@ type AuthorCreditEditorT = { export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const relationships = entityRoutes.constructRelationships( @@ -251,13 +251,8 @@ export function editionGroupToFormState(editionGroup) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(editionGroup.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -273,9 +268,9 @@ export function editionGroupToFormState(editionGroup) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const editionGroupSection = { @@ -326,11 +321,10 @@ export function editionGroupToFormState(editionGroup) { } return { - aliasEditor, + aliasSection, authorCreditEditor, - buttonBar, editionGroupSection, - identifierEditor, + identifierSection, nameSection, relationshipSection, ...optionalSections diff --git a/src/server/routes/entity/edition.ts b/src/server/routes/entity/edition.ts index 3239810f48..e60d468760 100644 --- a/src/server/routes/entity/edition.ts +++ b/src/server/routes/entity/edition.ts @@ -64,11 +64,11 @@ type PassportRequest = express.Request & { export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const relationships = entityRoutes.constructRelationships( @@ -414,13 +414,8 @@ export function editionToFormState(edition) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(edition.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -454,9 +449,9 @@ export function editionToFormState(edition) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const physicalEnable = !( @@ -521,11 +516,10 @@ export function editionToFormState(edition) { } return { - aliasEditor, + aliasSection, authorCreditEditor, - buttonBar, editionSection, - identifierEditor, + identifierSection, nameSection, relationshipSection, ...optionalSections diff --git a/src/server/routes/entity/entity.tsx b/src/server/routes/entity/entity.tsx index 445418dab9..9411f09ed9 100644 --- a/src/server/routes/entity/entity.tsx +++ b/src/server/routes/entity/entity.tsx @@ -1229,10 +1229,10 @@ type NameSectionT = { }; export function constructAliases( - aliasEditor: {[propName: string]: AliasEditorT}, nameSection: NameSectionT + aliasSection: {[propName: string]: AliasEditorT}, nameSection: NameSectionT ) { const aliases = _.map( - aliasEditor, + aliasSection, ( {language: languageId, name, primary, sortName}: AliasEditorT, id @@ -1256,17 +1256,17 @@ export function constructAliases( }, ...aliases]; } -type IdentifierEditorT = { +type IdentifierSectionT = { type: number, value: string }; export function constructIdentifiers( - identifierEditor: {[propName: string]: IdentifierEditorT} + identifierSection: {[propName: string]: IdentifierSectionT} ) { return _.map( - identifierEditor, - ({type: typeId, value}: IdentifierEditorT, id: string) => + identifierSection, + ({type: typeId, value}: IdentifierSectionT, id: string) => ({id, typeId, value}) ); } diff --git a/src/server/routes/entity/publisher.ts b/src/server/routes/entity/publisher.ts index e874ce06bf..5c5e167066 100644 --- a/src/server/routes/entity/publisher.ts +++ b/src/server/routes/entity/publisher.ts @@ -52,11 +52,11 @@ const additionalPublisherProps = [ export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const relationships = entityRoutes.constructRelationships( @@ -264,13 +264,8 @@ export function publisherToFormState(publisher) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(publisher.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -286,9 +281,9 @@ export function publisherToFormState(publisher) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const publisherSection = { @@ -323,9 +318,8 @@ export function publisherToFormState(publisher) { } return { - aliasEditor, - buttonBar, - identifierEditor, + aliasSection, + identifierSection, nameSection, publisherSection, relationshipSection, diff --git a/src/server/routes/entity/series.ts b/src/server/routes/entity/series.ts index 836450b4b0..2860765e23 100644 --- a/src/server/routes/entity/series.ts +++ b/src/server/routes/entity/series.ts @@ -51,11 +51,11 @@ const additionalSeriesProps = [ export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const seriesItems = entityRoutes.constructRelationships( data.seriesSection, 'seriesItems' @@ -251,13 +251,8 @@ export function seriesToFormState(series) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(series.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -273,9 +268,9 @@ export function seriesToFormState(series) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const seriesSection = { orderType: series.seriesOrderingType && series.seriesOrderingType.id, @@ -315,9 +310,8 @@ export function seriesToFormState(series) { } return { - aliasEditor, - buttonBar, - identifierEditor, + aliasSection, + identifierSection, nameSection, relationshipSection, seriesSection, diff --git a/src/server/routes/entity/work.ts b/src/server/routes/entity/work.ts index 9a0890a49f..ccc2f7c00b 100644 --- a/src/server/routes/entity/work.ts +++ b/src/server/routes/entity/work.ts @@ -50,11 +50,11 @@ type OptionalSectionsT = { export function transformNewForm(data) { const aliases = entityRoutes.constructAliases( - data.aliasEditor, data.nameSection + data.aliasSection, data.nameSection ); const identifiers = entityRoutes.constructIdentifiers( - data.identifierEditor + data.identifierSection ); const relationships = entityRoutes.constructRelationships( @@ -276,13 +276,8 @@ export function workToFormState(work) { const defaultAliasIndex = entityRoutes.getDefaultAliasIndex(work.aliasSet); const defaultAliasList = aliases.splice(defaultAliasIndex, 1); - const aliasEditor = {}; - aliases.forEach((alias) => { aliasEditor[alias.id] = alias; }); - - const buttonBar = { - aliasEditorVisible: false, - identifierEditorVisible: false - }; + const aliasSection = {}; + aliases.forEach((alias) => { aliasSection[alias.id] = alias; }); const nameSection = _.isEmpty(defaultAliasList) ? { language: null, @@ -298,9 +293,9 @@ export function workToFormState(work) { ...rest })) : []; - const identifierEditor = {}; + const identifierSection = {}; identifiers.forEach( - (identifier) => { identifierEditor[identifier.id] = identifier; } + (identifier) => { identifierSection[identifier.id] = identifier; } ); const workSection = { @@ -335,9 +330,8 @@ export function workToFormState(work) { } return { - aliasEditor, - buttonBar, - identifierEditor, + aliasSection, + identifierSection, nameSection, relationshipSection, workSection, diff --git a/src/server/routes/merge.ts b/src/server/routes/merge.ts index e975aaa3ba..b300d475f1 100644 --- a/src/server/routes/merge.ts +++ b/src/server/routes/merge.ts @@ -63,9 +63,9 @@ function entitiesToFormState(entities: any[]) { defaultAliasIndex = entityRoutes.getDefaultAliasIndex(aliases); } - const aliasEditor = {}; + const aliasSection = {}; aliases.forEach((alias) => { - aliasEditor[alias.id] = alias; + aliasSection[alias.id] = alias; }); const nameSection = aliases[defaultAliasIndex] || @@ -93,11 +93,11 @@ function entitiesToFormState(entities: any[]) { return returnValue; }, []); - const identifierEditor = {}; + const identifierSection = {}; const uniqueIdentifiers = _.uniqWith(identifiers, (identifierA, identifierB) => identifierA.type === identifierB.type && identifierA.value === identifierB.value); uniqueIdentifiers.forEach((identifier) => { - identifierEditor[identifier.id] = identifier; + identifierSection[identifier.id] = identifier; }); const type = _.camelCase(targetEntity.type); @@ -159,10 +159,10 @@ function entitiesToFormState(entities: any[]) { const authorCredit = authorCredits.length ? authorCredits[0] : null; const props = { - aliasEditor, + aliasSection, annotationSection, authorCredit, - identifierEditor, + identifierSection, nameSection, relationshipSection }; diff --git a/test/src/client/entity-editor/validators/test-author.js b/test/src/client/entity-editor/validators/test-author.js index 4382db39e1..a0b7d829bc 100644 --- a/test/src/client/entity-editor/validators/test-author.js +++ b/test/src/client/entity-editor/validators/test-author.js @@ -185,9 +185,9 @@ function describeValidateAuthorSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, + aliasSection: VALID_ALIASES, authorSection: VALID_AUTHOR_SECTION, - identifierEditor: VALID_IDENTIFIERS, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, submissionSection: VALID_SUBMISSION_SECTION }; @@ -209,7 +209,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -220,7 +220,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/entity-editor/validators/test-edition.js b/test/src/client/entity-editor/validators/test-edition.js index f3d0102dc5..34b3c506e4 100644 --- a/test/src/client/entity-editor/validators/test-edition.js +++ b/test/src/client/entity-editor/validators/test-edition.js @@ -420,10 +420,10 @@ function describeValidateEditionSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, + aliasSection: VALID_ALIASES, authorCreditEditor: VALID_AUTHOR_CREDIT_EDITOR, editionSection: VALID_EDITION_SECTION, - identifierEditor: VALID_IDENTIFIERS, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, submissionSection: VALID_SUBMISSION_SECTION }; @@ -460,7 +460,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -508,7 +508,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/entity-editor/validators/test-publication.js b/test/src/client/entity-editor/validators/test-publication.js index f0060c81ed..938f74f69f 100644 --- a/test/src/client/entity-editor/validators/test-publication.js +++ b/test/src/client/entity-editor/validators/test-publication.js @@ -107,10 +107,10 @@ function describeValidateEditionGroupSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, + aliasSection: VALID_ALIASES, authorCreditEditor: VALID_AUTHOR_CREDIT_EDITOR, editionGroupSection: VALID_EDITION_GROUP_SECTION, - identifierEditor: VALID_IDENTIFIERS, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, submissionSection: VALID_SUBMISSION_SECTION }; @@ -132,7 +132,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -143,7 +143,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/entity-editor/validators/test-publisher.js b/test/src/client/entity-editor/validators/test-publisher.js index 3c98854b84..9041be04cc 100644 --- a/test/src/client/entity-editor/validators/test-publisher.js +++ b/test/src/client/entity-editor/validators/test-publisher.js @@ -157,8 +157,8 @@ function describeValidatePublisherSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, - identifierEditor: VALID_IDENTIFIERS, + aliasSection: VALID_ALIASES, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, publisherSection: VALID_PUBLISHER_SECTION, submissionSection: VALID_SUBMISSION_SECTION @@ -181,7 +181,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -192,7 +192,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/entity-editor/validators/test-series.js b/test/src/client/entity-editor/validators/test-series.js index 37deda1407..2faf518318 100644 --- a/test/src/client/entity-editor/validators/test-series.js +++ b/test/src/client/entity-editor/validators/test-series.js @@ -118,8 +118,8 @@ function describeValidateSeriesSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, - identifierEditor: VALID_IDENTIFIERS, + aliasSection: VALID_ALIASES, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, seriesSection: VALID_SERIES_SECTION, submissionSection: VALID_SUBMISSION_SECTION @@ -142,7 +142,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -153,7 +153,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/entity-editor/validators/test-work.js b/test/src/client/entity-editor/validators/test-work.js index c87afa9ac0..c980261c06 100644 --- a/test/src/client/entity-editor/validators/test-work.js +++ b/test/src/client/entity-editor/validators/test-work.js @@ -146,8 +146,8 @@ function describeValidateWorkSection() { function describeValidateForm() { const validForm = { - aliasEditor: VALID_ALIASES, - identifierEditor: VALID_IDENTIFIERS, + aliasSection: VALID_ALIASES, + identifierSection: VALID_IDENTIFIERS, nameSection: VALID_NAME_SECTION, submissionSection: VALID_SUBMISSION_SECTION, workSection: VALID_WORK_SECTION @@ -170,7 +170,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - aliasEditor: INVALID_ALIASES + aliasSection: INVALID_ALIASES }, IDENTIFIER_TYPES ); @@ -181,7 +181,7 @@ function describeValidateForm() { const result = validateForm( { ...validForm, - identifierEditor: INVALID_IDENTIFIERS + identifierSection: INVALID_IDENTIFIERS }, IDENTIFIER_TYPES ); expect(result).to.be.false; diff --git a/test/src/client/unified-form/helpers.ts b/test/src/client/unified-form/helpers.ts index 9d1f1804d3..7567927b64 100644 --- a/test/src/client/unified-form/helpers.ts +++ b/test/src/client/unified-form/helpers.ts @@ -12,7 +12,7 @@ export const emptyCoverTabState = { author: null } }, - identifierEditor: {}, + identifierSection: {}, nameSection: { disambiguation: '', language: null, diff --git a/test/src/server/routes/entity/author.js b/test/src/server/routes/entity/author.js index ecd59561e3..e86ef7ee2e 100644 --- a/test/src/server/routes/entity/author.js +++ b/test/src/server/routes/entity/author.js @@ -55,7 +55,7 @@ describe('Author routes with entity editing priv', () => { 'authorSection.endDate': '', 'authorSection.gender': '', 'authorSection.type': '', - 'identifierEditor.t23': 'openlibid' + 'identifierSection.t23': 'openlibid' }; const res = await agent.post('/author/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); expect(res.ok).to.be.true; diff --git a/test/src/server/routes/entity/edition-group.js b/test/src/server/routes/entity/edition-group.js index 759ef9e757..6e1fedbc36 100644 --- a/test/src/server/routes/entity/edition-group.js +++ b/test/src/server/routes/entity/edition-group.js @@ -51,7 +51,7 @@ describe('Edition Group routes with entity editing priv', () => { const data = { ...seedInitialState, 'editionGroupSection.type': '', - 'identifierEditor.t19': 'wikidataid' + 'identifierSection.t19': 'wikidataid' }; const res = await agent.post('/edition-group/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); diff --git a/test/src/server/routes/entity/edition.js b/test/src/server/routes/entity/edition.js index 2436e0de8c..d3fb47cc9d 100644 --- a/test/src/server/routes/entity/edition.js +++ b/test/src/server/routes/entity/edition.js @@ -57,7 +57,7 @@ describe('Edition routes with entity editing priv', () => { 'editionSection.releaseDate': 'invalid', 'editionSection.weight': '453', 'editionSection.width': '', - 'identifierEditor.t10': '0374533555' + 'identifierSection.t10': '0374533555' }; const res = await agent.post('/edition/create').set('Origin', `http://127.0.0.1:${agent.app.address().port}`).send(data); expect(res.ok).to.be.true; diff --git a/test/src/server/routes/entity/publisher.js b/test/src/server/routes/entity/publisher.js index 0656930f38..92fc64e086 100644 --- a/test/src/server/routes/entity/publisher.js +++ b/test/src/server/routes/entity/publisher.js @@ -50,7 +50,7 @@ describe('Publisher routes with entity editing priv', () => { it('should not throw error while seeding publisher', async () => { const data = { ...seedInitialState, - 'identifierEditor.t20': 'wikidataid', + 'identifierSection.t20': 'wikidataid', 'publisherSection.area': '', 'publisherSection.beginDate': 'invalid', 'publisherSection.endDate': '', diff --git a/test/src/server/routes/entity/series.js b/test/src/server/routes/entity/series.js index 246db746f0..656070837a 100644 --- a/test/src/server/routes/entity/series.js +++ b/test/src/server/routes/entity/series.js @@ -69,7 +69,7 @@ describe('Series routes with entity editing priv', () => { it('should not throw error while seeding series', async () => { const data = { ...seedInitialState, - 'identifierEditor.t30': 'wikidataid', + 'identifierSection.t30': 'wikidataid', orderType: '', seriesType: '' diff --git a/test/src/server/routes/entity/work.js b/test/src/server/routes/entity/work.js index 15a9f3a886..70c319551a 100644 --- a/test/src/server/routes/entity/work.js +++ b/test/src/server/routes/entity/work.js @@ -51,7 +51,7 @@ describe('Work routes with entity editing priv', () => { it('should not throw error while seeding work', async () => { const data = { ...seedInitialState, - 'identifierEditor.t8': 'wikidataid', + 'identifierSection.t8': 'wikidataid', 'workSection.languages0': 'English', 'workSection.languages1': 'Japenglish', 'workSection.type': '' diff --git a/test/test-helpers/create-entities.js b/test/test-helpers/create-entities.js index 83c1caf96d..c814e0e900 100644 --- a/test/test-helpers/create-entities.js +++ b/test/test-helpers/create-entities.js @@ -37,7 +37,7 @@ const {updateLanguageSet} = orm.func.language; export const seedInitialState = { annotationSection: 'annotation', - 'identifierEditor.t0': '1234567', + 'identifierSection.t0': '1234567', 'nameSection.disambiguation': 'disambiguation', 'nameSection.language': 'English', 'nameSection.name': 'name', @@ -46,12 +46,12 @@ export const seedInitialState = { }; export const baseState = { - aliasEditor: {}, + aliasSection: {}, annotationSection: { content: '' }, authorCreditEditor: {}, - identifierEditor: {}, + identifierSection: {}, nameSection: { disambiguation: '', language: 42, From 77ff0ffd35ad373f1bda94ddb03ec579261c25f7 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 04:37:04 +0530 Subject: [PATCH 13/20] fix error occur due to merge --- src/client/entity-editor/entity-editor.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index a6a541b54e..68deb77138 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -38,7 +38,8 @@ type OwnProps = { }; type StateProps = { - + aliasEditorVisible: boolean, + identifierEditorVisible: boolean, }; type DispatchProps = { @@ -51,6 +52,10 @@ type Props = StateProps & DispatchProps & OwnProps; * Container component. Renders all of the sections of the entity editing form. * * @param {Object} props - The properties passed to the component. + * @param {boolean} props.aliasEditorVisible - Whether the alias editor modal + * should be made visible. + * @param {boolean} props.identifierEditorVisible - Whether the identifier + * editor modal should be made visible. * @param {React.Node} props.children - The child content to wrap with this * entity editor form. * @param {Function} props.onSubmit - A function to be called when the @@ -59,9 +64,12 @@ type Props = StateProps & DispatchProps & OwnProps; */ const EntityEditor = (props: Props) => { const { + aliasEditorVisible, children, heading, - onSubmit + identifierEditorVisible, + onSubmit, + entity } = props; const currentState = (useSelector((state) => state) as any).toJS(); let entityURL; @@ -109,6 +117,14 @@ const EntityEditor = (props: Props) => { }; EntityEditor.displayName = 'EntityEditor'; +function mapStateToProps(rootState): StateProps { + const state = rootState.get('buttonBar'); + return { + aliasEditorVisible: state.get('aliasEditorVisible'), + identifierEditorVisible: state.get('identifierEditorVisible') + }; +} + function mapDispatchToProps(dispatch, {submissionUrl}) { return { onSubmit: (event:React.FormEvent) => { @@ -118,4 +134,4 @@ function mapDispatchToProps(dispatch, {submissionUrl}) { }; } -export default connect(null, mapDispatchToProps)(EntityEditor); +export default connect(mapStateToProps, mapDispatchToProps)(EntityEditor); \ No newline at end of file From 3ef5b5491c932726cb4c2b4227eb27310f0a2722 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 11:50:18 +0530 Subject: [PATCH 14/20] minor fix --- src/client/entity-editor/entity-editor.tsx | 23 ++-------------------- src/client/unified-form/interface/type.ts | 6 +++--- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index 68deb77138..ad6e1ad6fd 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -37,25 +37,16 @@ type OwnProps = { entity: any }; -type StateProps = { - aliasEditorVisible: boolean, - identifierEditorVisible: boolean, -}; - type DispatchProps = { onSubmit: (event:React.FormEvent) => unknown }; -type Props = StateProps & DispatchProps & OwnProps; +type Props = DispatchProps & OwnProps; /** * Container component. Renders all of the sections of the entity editing form. * * @param {Object} props - The properties passed to the component. - * @param {boolean} props.aliasEditorVisible - Whether the alias editor modal - * should be made visible. - * @param {boolean} props.identifierEditorVisible - Whether the identifier - * editor modal should be made visible. * @param {React.Node} props.children - The child content to wrap with this * entity editor form. * @param {Function} props.onSubmit - A function to be called when the @@ -64,10 +55,8 @@ type Props = StateProps & DispatchProps & OwnProps; */ const EntityEditor = (props: Props) => { const { - aliasEditorVisible, children, heading, - identifierEditorVisible, onSubmit, entity } = props; @@ -117,14 +106,6 @@ const EntityEditor = (props: Props) => { }; EntityEditor.displayName = 'EntityEditor'; -function mapStateToProps(rootState): StateProps { - const state = rootState.get('buttonBar'); - return { - aliasEditorVisible: state.get('aliasEditorVisible'), - identifierEditorVisible: state.get('identifierEditorVisible') - }; -} - function mapDispatchToProps(dispatch, {submissionUrl}) { return { onSubmit: (event:React.FormEvent) => { @@ -134,4 +115,4 @@ function mapDispatchToProps(dispatch, {submissionUrl}) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(EntityEditor); \ No newline at end of file +export default connect(null, mapDispatchToProps)(EntityEditor); \ No newline at end of file diff --git a/src/client/unified-form/interface/type.ts b/src/client/unified-form/interface/type.ts index 75de0760d6..c4cf70914c 100644 --- a/src/client/unified-form/interface/type.ts +++ b/src/client/unified-form/interface/type.ts @@ -149,10 +149,10 @@ export type SearchEntityCreateProps = SearchEntityCreateDispatchProps & SearchEn export type EntityModalStateProps = { isNameSectionValid:boolean, isNameSectionEmpty:boolean, - isAliasEditorValid:boolean, - isIdentifierSeactionValid:boolean, + isAliasSectionValid:boolean, + isIdentifierSectionValid:boolean, isEntitySectionValid:boolean, - isAliasEditorEmpty:boolean, + isAliasSectionEmpty:boolean, isIdentifierSectionEmpty:boolean }; From 7905072350c696e604b8428d4e0c62cf449740a7 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 12:12:50 +0530 Subject: [PATCH 15/20] build test fix --- src/client/entity-editor/identifier-section/identifier-row.tsx | 2 +- .../identifier-section/identifier-section-body.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/entity-editor/identifier-section/identifier-row.tsx b/src/client/entity-editor/identifier-section/identifier-row.tsx index a569a01ed5..f1e0dac0dc 100644 --- a/src/client/entity-editor/identifier-section/identifier-row.tsx +++ b/src/client/entity-editor/identifier-section/identifier-row.tsx @@ -117,7 +117,7 @@ function IdentifierRow({ /> - {identifierValue[0].label} : {valueValue} + {identifierValue[0]?.label} : {valueValue}
From 5f759c50dbfb4d4db6c1f4eba63e7e7a8c5dba6c Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 20:02:41 +0530 Subject: [PATCH 16/20] merging --- package.json | 15 +- .../parts/{achievement.js => achievement.tsx} | 55 ++-- .../components/input/drag-and-drop-image.tsx | 7 - src/client/components/input/drag-and-drop.tsx | 44 ++-- .../pages/parts/add-to-collection-modal.js | 2 +- .../pages/parts/editor-achievements.js | 8 +- .../components/pages/parts/search-results.js | 21 +- .../entity-editor/alias-section/reducer.js | 2 +- src/client/entity-editor/entity-editor.tsx | 20 +- .../identifier-section/identifier-row.tsx | 2 +- .../identifier-section-body.tsx | 2 +- src/client/unified-form/interface/type.ts | 6 +- src/common/helpers/{search.js => search.ts} | 182 +++++++------ src/common/helpers/utils.ts | 9 +- src/server/helpers/collectionRouteUtils.js | 22 +- src/server/routes/editor.tsx | 35 ++- src/server/routes/entity/entity.tsx | 60 +++-- .../routes/entity/process-unified-form.ts | 4 +- src/server/routes/register.js | 105 ++++---- yarn.lock | 249 ++++++++++-------- 20 files changed, 446 insertions(+), 404 deletions(-) rename src/client/components/forms/parts/{achievement.js => achievement.tsx} (63%) rename src/common/helpers/{search.js => search.ts} (73%) diff --git a/package.json b/package.json index 2dd4322310..b11c0f2fb5 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,11 @@ "node": ">= 18" }, "browserslist": "> 0.25%, not dead", + "overrides": { + "react-select-fast-filter-options":{ + "react-select": "$react-select" + } + }, "dependencies": { "@babel/runtime": "^7.17.7", "@cospired/i18n-iso-languages": "^4.0.0", @@ -46,7 +51,7 @@ "cross-env": "^7.0.3", "date-fns": "^2.15.0", "debug": "^4.3.2", - "express": "^4.18.2", + "express": "^4.19.2", "express-session": "^1.17.3", "express-slow-down": "^1.3.1", "http-status": "^1.6.2", @@ -60,7 +65,7 @@ "log-node": "^8.0.3", "mocha-chai-jest-snapshot": "^1.1.4", "morgan": "^1.10.0", - "nodemailer": "^6.5.0", + "nodemailer": "^6.9.9", "passport": "^0.6.0", "passport-musicbrainz-oauth2": "git+https://git@github.com/LordSputnik/passport-musicbrainz-oauth2.git", "prop-types": "^15.8.1", @@ -70,7 +75,7 @@ "react-datepicker": "^4.7.0", "react-dom": "^16.13.1", "react-hot-loader": "^4.13.0", - "react-redux": "^8.1.3", + "react-redux": "^7.2.9", "react-select": "^4.3.1", "react-select-fast-filter-options": "^0.2.3", "react-simple-star-rating": "^4.0.5", @@ -118,7 +123,7 @@ "chai-sorted": "^0.2.0", "clean-webpack-plugin": "^4.0.0", "compression-webpack-plugin": "^9.2.0", - "css-loader": "^6.7.1", + "css-loader": "^6.9.1", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.6", "eslint": "^7.30.0", @@ -143,7 +148,7 @@ "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.3.0", "webpack-cli": "^4.10.0", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "webpack-hot-middleware": "^2.25.0" } } diff --git a/src/client/components/forms/parts/achievement.js b/src/client/components/forms/parts/achievement.tsx similarity index 63% rename from src/client/components/forms/parts/achievement.js rename to src/client/components/forms/parts/achievement.tsx index 472f74eca5..7ca03dc6e2 100644 --- a/src/client/components/forms/parts/achievement.js +++ b/src/client/components/forms/parts/achievement.tsx @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 Max Prettyjohns + * 2023 Meziyum * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,12 +18,13 @@ */ import * as bootstrap from 'react-bootstrap'; +import type {Achievement} from '../../input/drag-and-drop'; import DragAndDropImage from '../../input/drag-and-drop-image'; -import PropTypes from 'prop-types'; import React from 'react'; const {Card, Col, Container, Row} = bootstrap; +/* eslint-disable sort-keys */ const maxAchievementProgress = { 1: 1, 2: 50, @@ -33,7 +35,6 @@ const maxAchievementProgress = { 7: 1, 8: 10, 9: 100, - // eslint-disable-next-line sort-keys 10: 10, 11: 7, 12: 30, @@ -56,28 +57,41 @@ const maxAchievementProgress = { 29: 10, 30: 100 }; +/* eslint-enable sort-keys */ -function Achievement(props) { - const {achievement, counter, unlocked} = props; - const {id, name, description, badgeUrl} = achievement; +interface AchievementComponentProps { + achievement: Achievement +} + +/** + * Achievement Component + * + * A React component that displays an achievement card with its details, including name, + * description, badge image, and progress if the achievement is locked. + * + * @component + * + * @param {Object} props - The props for the Achievement component. + * @param {Achievement} props.achievement - The achievement object containing details. + * @param {number} props.counter - The current progress or counter for the achievement. + * @param {boolean} props.unlocked - A boolean indicating whether the achievement is unlocked. + * + * @returns {JSX.Element} The rendered Achievement card component. + */ +function AchievementComponent({achievement}: AchievementComponentProps): JSX.Element { + const {id, name, description, badgeUrl, counter, unlocked} = achievement; const imgElement = unlocked ? ( ) : ( {name} ); @@ -107,21 +121,6 @@ function Achievement(props) { ); } -Achievement.displayName = 'achievement'; - -Achievement.propTypes = { - achievement: PropTypes.shape({ - badgeUrl: PropTypes.string, - description: PropTypes.string, - id: PropTypes.number, - name: PropTypes.string - }).isRequired, - counter: PropTypes.number, - unlocked: PropTypes.bool -}; -Achievement.defaultProps = { - counter: 0, - unlocked: false -}; +AchievementComponent.displayName = 'achievement'; -export default Achievement; +export default AchievementComponent; diff --git a/src/client/components/input/drag-and-drop-image.tsx b/src/client/components/input/drag-and-drop-image.tsx index 82caa7b788..33564578b8 100644 --- a/src/client/components/input/drag-and-drop-image.tsx +++ b/src/client/components/input/drag-and-drop-image.tsx @@ -16,7 +16,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import PropTypes from 'prop-types'; import React from 'react'; @@ -71,11 +70,5 @@ function DragAndDropImage({achievementId, achievementName, height, src}: Props): } DragAndDropImage.displayName = 'DragAndDropImage'; -DragAndDropImage.propTypes = { - achievementId: PropTypes.number.isRequired, - achievementName: PropTypes.string.isRequired, - height: PropTypes.string.isRequired, - src: PropTypes.string.isRequired -}; export default DragAndDropImage; diff --git a/src/client/components/input/drag-and-drop.tsx b/src/client/components/input/drag-and-drop.tsx index 306bcad22c..fed54f788b 100644 --- a/src/client/components/input/drag-and-drop.tsx +++ b/src/client/components/input/drag-and-drop.tsx @@ -17,7 +17,6 @@ */ import * as bootstrap from 'react-bootstrap'; -import PropTypes from 'prop-types'; import React from 'react'; @@ -31,10 +30,20 @@ const {useState, useCallback} = React; * @property {string} badgeUrl - The source URL of the achievement's badge image. * @property {number} id - The ID of the achievement. */ -type Achievement = { - name: string; +export type Achievement = { badgeUrl: string | null; + counter:number; + description: string; id: number; + name: string; + unlocked: boolean; +}; +type AchievementForDisplay = Pick; + +const blankBadge:AchievementForDisplay = { + badgeUrl: '/images/blankbadge.svg', + id: null, + name: 'drag badge to set' }; /** @@ -56,18 +65,14 @@ type Props = { * @returns {JSX.Element} A React component that displays a drag-and-drop card for an achievement. */ function DragAndDrop({name, initialAchievement}: Props): JSX.Element { - const [achievement, setAchievement] = useState(initialAchievement); + const [achievement, setAchievement] = useState(initialAchievement); const handleClick = useCallback((event: React.MouseEvent) => { event.preventDefault(); - setAchievement({ - badgeUrl: '/images/blankbadge.svg', - id: null, - name: 'drag badge to set' - }); - }); + setAchievement(blankBadge); + }, []); const handleDragOver = useCallback((event: React.DragEvent) => { event.preventDefault(); - }); + }, []); const handleDrop = useCallback((event: React.DragEvent) => { event.preventDefault(); let data; @@ -83,7 +88,7 @@ function DragAndDrop({name, initialAchievement}: Props): JSX.Element { id: data.id, name: data.name }); - }); + }, [setAchievement]); return ( this.privacy = ref} - /> diff --git a/src/client/components/pages/parts/editor-achievements.js b/src/client/components/pages/parts/editor-achievements.js index 903d0d9399..8af15d072c 100644 --- a/src/client/components/pages/parts/editor-achievements.js +++ b/src/client/components/pages/parts/editor-achievements.js @@ -59,9 +59,7 @@ class EditorAchievementTab extends React.Component { const achievementHTML = ( ); if (achievement.unlocked) { @@ -93,15 +91,15 @@ class EditorAchievementTab extends React.Component { > diff --git a/src/client/components/pages/parts/search-results.js b/src/client/components/pages/parts/search-results.js index c38291be08..da5d7c4705 100644 --- a/src/client/components/pages/parts/search-results.js +++ b/src/client/components/pages/parts/search-results.js @@ -29,6 +29,12 @@ import {genEntityIconHTMLElement} from '../../../helpers/entity'; const {Alert, Badge, Button, ButtonGroup, Table} = bootstrap; +// Main entities have a BBID but some other indexed types +// have an ID field instead (collections, editors, areas) +function getId(entity) { + return entity.bbid ?? entity.id; +} + /** * Renders the document and displays the 'SearchResults' page. * @returns {ReactElement} a HTML document which displays the SearchResults. @@ -89,8 +95,8 @@ class SearchResults extends React.Component { // eslint-disable-next-line react/no-access-state-in-setstate const oldSelected = this.state.selected; let newSelected; - if (oldSelected.find(selected => selected.bbid === entity.bbid)) { - newSelected = oldSelected.filter(selected => selected.bbid !== entity.bbid); + if (oldSelected.find(selected => getId(selected) === getId(entity))) { + newSelected = oldSelected.filter(selected => getId(selected) !== getId(entity)); } else { newSelected = [...oldSelected, entity]; @@ -148,6 +154,7 @@ class SearchResults extends React.Component { if (!result) { return null; } + const id = getId(result); const name = result.defaultAlias ? result.defaultAlias.name : '(unnamed)'; @@ -160,19 +167,19 @@ class SearchResults extends React.Component { const disambiguation = result.disambiguation ? ({result.disambiguation.comment}) : ''; // No redirect link for Area entity results const link = result.type === 'Area' ? - `//musicbrainz.org/area/${result.bbid}` : - `/${_kebabCase(result.type)}/${result.bbid}`; + `//musicbrainz.org/area/${id}` : + `/${_kebabCase(result.type)}/${id}`; /* eslint-disable react/jsx-no-bind */ return ( - + { !this.props.condensed && { this.props.user ? selected.bbid === result.bbid)} + checked={this.state.selected.find(selected => getId(selected) === id)} className="checkboxes" type="checkbox" onChange={() => this.toggleRow(result)} @@ -212,7 +219,7 @@ class SearchResults extends React.Component { this.props.user ?
selected.bbid)} + bbids={this.state.selected.map(getId)} closeModalAndShowMessage={this.closeModalAndShowMessage} entityType={this.state.selected[0]?.type} handleCloseModal={this.onCloseModal} diff --git a/src/client/entity-editor/alias-section/reducer.js b/src/client/entity-editor/alias-section/reducer.js index cf43c90895..a5fe279281 100644 --- a/src/client/entity-editor/alias-section/reducer.js +++ b/src/client/entity-editor/alias-section/reducer.js @@ -46,7 +46,7 @@ function reducer( return state.delete(payload); case REMOVE_EMPTY_ALIASES: return state.filterNot(alias => - alias.get('name') === '' && alias.get('language') === null && alias.get('sortName') === ''); + alias.get('name') === ''); // no default } return state; diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index fe4a31aac3..03266d9902 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -26,6 +26,7 @@ import NameSection from './name-section/name-section'; import RelationshipSection from './relationship-editor/relationship-section'; import SubmissionSection from './submission-section/submission-section'; import _ from 'lodash'; +import {getEntityUrl} from '../helpers/entity'; import {submit} from './submission-section/actions'; @@ -33,17 +34,14 @@ type OwnProps = { children: React.ReactElement, heading: string, intitialState:Record, -}; - -type StateProps = { - + entity: any }; type DispatchProps = { onSubmit: (event:React.FormEvent) => unknown }; -type Props = StateProps & DispatchProps & OwnProps; +type Props = DispatchProps & OwnProps; /** * Container component. Renders all of the sections of the entity editing form. @@ -59,9 +57,11 @@ const EntityEditor = (props: Props) => { const { children, heading, - onSubmit + onSubmit, + entity } = props; const currentState = (useSelector((state) => state) as any).toJS(); + let entityURL; // eslint-disable-next-line consistent-return const handleUrlChange = React.useCallback(() => { if (!_.isEqual(currentState, props.intitialState) && !currentState.submissionSection.submitted) { @@ -72,11 +72,17 @@ const EntityEditor = (props: Props) => { window.onbeforeunload = handleUrlChange; }, [handleUrlChange]); + if (entity) { + entityURL = getEntityUrl(entity); + } + return (
- {heading} +
+ {entityURL ? {heading} : heading} +
diff --git a/src/client/entity-editor/identifier-section/identifier-row.tsx b/src/client/entity-editor/identifier-section/identifier-row.tsx index a569a01ed5..f1e0dac0dc 100644 --- a/src/client/entity-editor/identifier-section/identifier-row.tsx +++ b/src/client/entity-editor/identifier-section/identifier-row.tsx @@ -117,7 +117,7 @@ function IdentifierRow({ /> - {identifierValue[0].label} : {valueValue} + {identifierValue[0]?.label} : {valueValue}
diff --git a/src/client/unified-form/interface/type.ts b/src/client/unified-form/interface/type.ts index 75de0760d6..c4cf70914c 100644 --- a/src/client/unified-form/interface/type.ts +++ b/src/client/unified-form/interface/type.ts @@ -149,10 +149,10 @@ export type SearchEntityCreateProps = SearchEntityCreateDispatchProps & SearchEn export type EntityModalStateProps = { isNameSectionValid:boolean, isNameSectionEmpty:boolean, - isAliasEditorValid:boolean, - isIdentifierSeactionValid:boolean, + isAliasSectionValid:boolean, + isIdentifierSectionValid:boolean, isEntitySectionValid:boolean, - isAliasEditorEmpty:boolean, + isAliasSectionEmpty:boolean, isIdentifierSectionEmpty:boolean }; diff --git a/src/common/helpers/search.js b/src/common/helpers/search.ts similarity index 73% rename from src/common/helpers/search.js rename to src/common/helpers/search.ts index 813c189034..e1092311eb 100644 --- a/src/common/helpers/search.js +++ b/src/common/helpers/search.ts @@ -17,10 +17,11 @@ */ /* eslint-disable camelcase */ -import * as commonUtils from '../../common/helpers/utils'; +import * as commonUtils from './utils'; import {camelCase, isString, snakeCase, upperFirst} from 'lodash'; import ElasticSearch from '@elastic/elasticsearch'; +import type {EntityTypeString} from 'bookbrainz-data/lib/types/entity'; import httpStatus from 'http-status'; import log from 'log'; @@ -48,6 +49,37 @@ function sanitizeEntityType(type) { return snakeCase(type); } +type IndexableEntities = EntityTypeString | 'Editor' | 'Collection' | 'Area'; +const commonProperties = ['bbid', 'id', 'name', 'type', 'disambiguation']; + +/* We don't currently want to index the entire Model in ElasticSearch, + which contains a lot of fields we don't use as well as some internal props (_pivot props) + This utility function prepares the Model into a minimal object that will be indexed +*/ +export function getDocumentToIndex(entity:any, entityType: IndexableEntities) { + const additionalProperties = []; + switch (entityType) { + case 'Work': + additionalProperties.push('authors'); + break; + default: + break; + } + let aliases = entity.related('aliasSet')?.related('aliases')?.toJSON({ignorePivot: true, visible: 'name'}); + if (!aliases) { + // Some models don't have the same aliasSet structure, i.e. Collection, Editor, Area, … + const name = entity.get('name'); + aliases = {name}; + } + const identifiers = entity.related('identifierSet')?.related('identifiers')?.toJSON({ignorePivot: true, visible: 'value'}); + + return { + ...entity.toJSON({ignorePivot: true, visible: commonProperties.concat(additionalProperties)}), + aliases, + identifiers: identifiers ?? null + }; +} + async function _fetchEntityModelsForESResults(orm, results) { const {Area, Editor, UserCollection} = orm; @@ -60,10 +92,10 @@ async function _fetchEntityModelsForESResults(orm, results) { // Special cases first if (entityStub.type === 'Area') { - const area = await Area.forge({gid: entityStub.bbid}) + const area = await Area.forge({gid: entityStub.id}) .fetch({withRelated: ['areaType']}); - const areaJSON = area.toJSON(); + const areaJSON = area.toJSON({omitPivot: true}); const areaParents = await area.parents(); areaJSON.defaultAlias = { name: areaJSON.name @@ -75,27 +107,27 @@ async function _fetchEntityModelsForESResults(orm, results) { return areaJSON; } if (entityStub.type === 'Editor') { - const editor = await Editor.forge({id: entityStub.bbid}) + const editor = await Editor.forge({id: entityStub.id}) .fetch(); - const editorJSON = editor.toJSON(); + const editorJSON = editor.toJSON({omitPivot: true}); editorJSON.defaultAlias = { name: editorJSON.name }; editorJSON.type = 'Editor'; - editorJSON.bbid = entityStub.bbid; + editorJSON.id = entityStub.id; return editorJSON; } if (entityStub.type === 'Collection') { - const collection = await UserCollection.forge({id: entityStub.bbid}) + const collection = await UserCollection.forge({id: entityStub.id}) .fetch(); - const collectionJSON = collection.toJSON(); + const collectionJSON = collection.toJSON({omitPivot: true}); collectionJSON.defaultAlias = { name: collectionJSON.name }; collectionJSON.type = 'Collection'; - collectionJSON.bbid = entityStub.bbid; + collectionJSON.id = entityStub.id; return collectionJSON; } // Regular entity @@ -103,11 +135,11 @@ async function _fetchEntityModelsForESResults(orm, results) { const entity = await model.forge({bbid: entityStub.bbid}) .fetch({require: false, withRelated: ['defaultAlias.language', 'disambiguation', 'aliasSet.aliases', 'identifierSet.identifiers', 'relationshipSet.relationships.source', 'relationshipSet.relationships.target', 'relationshipSet.relationships.type', 'annotation']}); - const entityJSON = entity?.toJSON(); + const entityJSON = entity?.toJSON({omitPivot: true}); if (entityJSON && entityJSON.relationshipSet) { entityJSON.relationshipSet.relationships = await Promise.all(entityJSON.relationshipSet.relationships.map(async (rel) => { - rel.source = await commonUtils.getEntityAlias(orm, rel.source.bbid, rel.source.type); - rel.target = await commonUtils.getEntityAlias(orm, rel.target.bbid, rel.target.type); + rel.source = await commonUtils.getEntity(orm, rel.source.bbid, rel.source.type); + rel.target = await commonUtils.getEntity(orm, rel.target.bbid, rel.target.type); return rel; })); } @@ -145,7 +177,7 @@ export async function _bulkIndexEntities(entities) { const bulkOperations = entitiesToIndex.reduce((accumulator, entity) => { accumulator.push({ index: { - _id: entity.bbid, + _id: entity.bbid ?? entity.id, _index, _type: snakeCase(entity.type) } @@ -160,19 +192,19 @@ export async function _bulkIndexEntities(entities) { // eslint-disable-next-line no-await-in-loop const response = await _client.bulk({ body: bulkOperations - }); + }).catch(error => { log.error('error bulk indexing entities for search:', error); }); /* * In case of failed index operations, the promise won't be rejected; * instead, we have to inspect the response and respond to any failures * individually. */ - if (response.errors === true) { + if (response?.errors === true) { entitiesToIndex = response.items.reduce((accumulator, item) => { // We currently only handle queue overrun if (item.index.status === httpStatus.TOO_MANY_REQUESTS) { const failedEntity = entities.find( - (element) => element.bbid === item.index._id + (element) => (element.bbid ?? element.id) === item.index._id ); accumulator.push(failedEntity); @@ -223,7 +255,7 @@ export async function autocomplete(orm, query, type, size = 42) { else { queryBody = { match: { - 'aliasSet.aliases.name.autocomplete': { + 'aliases.name.autocomplete': { minimum_should_match: '80%', query } @@ -248,26 +280,28 @@ export async function autocomplete(orm, query, type, size = 42) { // eslint-disable-next-line consistent-return export function indexEntity(entity) { + const entityType = entity.get('type'); + const document = getDocumentToIndex(entity, entityType); if (entity) { return _client.index({ - body: entity, - id: entity.bbid, + body: document, + id: entity.get('bbid') || entity.get('id'), index: _index, - type: snakeCase(entity.type) - }); + type: snakeCase(entityType) + }).catch(error => { log.error('error indexing entity for search:', error); }); } } export function deleteEntity(entity) { return _client.delete({ - id: entity.bbid, + id: entity.bbid ?? entity.id, index: _index, type: snakeCase(entity.type) - }); + }).catch(error => { log.error('error deleting entity from index:', error); }); } export function refreshIndex() { - return _client.indices.refresh({index: _index}); + return _client.indices.refresh({index: _index}).catch(error => { log.error('error refreshing search index:', error); }); } export async function generateIndex(orm) { @@ -276,7 +310,7 @@ export async function generateIndex(orm) { mappings: { _default_: { properties: { - 'aliasSet.aliases': { + aliases: { properties: { name: { fields: { @@ -291,8 +325,7 @@ export async function generateIndex(orm) { }, type: 'text' } - }, - type: 'object' + } }, authors: { analyzer: 'trigrams', @@ -341,7 +374,8 @@ export async function generateIndex(orm) { type: 'ngram' } } - } + }, + 'index.mapping.ignore_malformed': true } }; @@ -384,7 +418,7 @@ export async function generateIndex(orm) { {model: EditionGroup, relations: []}, {model: Publisher, relations: ['area']}, {model: Series, relations: ['seriesOrderingType']}, - {model: Work, relations: ['workType', 'relationshipSet.relationships.type']} + {model: Work, relations: ['relationshipSet.relationships.type']} ]; // Update the indexed entries for each entity type @@ -400,6 +434,7 @@ export async function generateIndex(orm) { ); const entityLists = await Promise.all(behaviorPromise); /* eslint-disable @typescript-eslint/no-unused-vars */ + const entityFetchOrder:EntityTypeString[] = ['Author', 'Edition', 'EditionGroup', 'Publisher', 'Series', 'Work']; const [authorsCollection, editionCollection, editionGroupCollection, @@ -413,40 +448,40 @@ export async function generateIndex(orm) { const relationshipSet = workEntity.related('relationshipSet'); if (relationshipSet) { const authorWroteWorkRels = relationshipSet.related('relationships')?.filter(relationshipModel => relationshipModel.get('typeId') === 8); - const authorNames = authorWroteWorkRels.map(relationshipModel => { + const authorNames = []; + authorWroteWorkRels.forEach(relationshipModel => { // Search for the Author in the already fetched BookshelfJS Collection const source = authorsCollection.get(relationshipModel.get('sourceBbid')); - if (!source) { - // This shouldn't happen, but just in case - return null; + const name = source?.related('defaultAlias')?.get('name'); + if (name) { + authorNames.push(name); } - return source.toJSON().defaultAlias.name; }); workEntity.set('authors', authorNames); } }); // Index all the entities - for (const entityList of entityLists) { - const listArray = entityList.toJSON(); + entityLists.forEach((entityList, idx) => { + const entityType:EntityTypeString = entityFetchOrder[idx]; + const listArray = entityList.map(entity => getDocumentToIndex(entity, entityType)); listIndexes.push(_processEntityListForBulk(listArray)); - } + }); + await Promise.all(listIndexes); const areaCollection = await Area.forge() .fetchAll(); - const areas = areaCollection.toJSON(); + const areas = areaCollection.toJSON({omitPivot: true}); - /** To index names, we use aliasSet.aliases.name and bbid, which Areas don't have. - * We massage the area to return a similar format as BB entities - */ - const processedAreas = areas.map((area) => new Object({ - aliasSet: { - aliases: [ - {name: area.name} - ] - }, - bbid: area.gid, + /** To index names, we use aliases.name and type, which Areas don't have. + * We massage the area to return a similar format as BB entities + */ + const processedAreas = areas.map((area) => ({ + aliases: [ + {name: area.name} + ], + id: area.gid, type: 'Area' })); await _processEntityListForBulk(processedAreas); @@ -455,36 +490,31 @@ export async function generateIndex(orm) { // no bots .where('type_id', 1) .fetchAll(); - const editors = editorCollection.toJSON(); - - /** To index names, we use aliasSet.aliases.name and bbid, which Editors don't have. - * We massage the editor to return a similar format as BB entities - */ - const processedEditors = editors.map((editor) => new Object({ - aliasSet: { - aliases: [ - {name: editor.name} - ] - }, - bbid: editor.id, + const editors = editorCollection.toJSON({omitPivot: true}); + + /** To index names, we use aliases.name and type, which Editors don't have. + * We massage the editor to return a similar format as BB entities + */ + const processedEditors = editors.map((editor) => ({ + aliases: [ + {name: editor.name} + ], + id: editor.id, type: 'Editor' })); await _processEntityListForBulk(processedEditors); const userCollections = await UserCollection.forge().where({public: true}) .fetchAll(); - const userCollectionsJSON = userCollections.toJSON(); - - /** To index names, we use aliasSet.aliases.name and bbid, which UserCollections don't have. - * We massage the editor to return a similar format as BB entities - */ - const processedCollections = userCollectionsJSON.map((collection) => new Object({ - aliasSet: { - aliases: [ - {name: collection.name} - ] - }, - bbid: collection.id, + const userCollectionsJSON = userCollections.toJSON({omitPivot: true}); + + /** To index names, we use aliases.name and type, which UserCollections don't have. + * We massage the editor to return a similar format as BB entities + */ + const processedCollections = userCollectionsJSON.map((collection) => ({ + aliases: [ + {name: collection.name} + ], id: collection.id, type: 'Collection' })); @@ -495,7 +525,7 @@ export async function generateIndex(orm) { export async function checkIfExists(orm, name, type) { const {bookshelf} = orm; - const bbids = await new Promise((resolve, reject) => { + const bbids:string[] = await new Promise((resolve, reject) => { bookshelf.transaction(async (transacting) => { try { const result = await orm.func.alias.getBBIDsWithMatchingAlias( @@ -535,10 +565,10 @@ export function searchByName(orm, name, type, size, from) { query: { multi_match: { fields: [ - 'aliasSet.aliases.name^3', - 'aliasSet.aliases.name.search', + 'aliases.name^3', + 'aliases.name.search', 'disambiguation', - 'identifierSet.identifiers.value' + 'identifiers.value' ], minimum_should_match: '80%', query: name, diff --git a/src/common/helpers/utils.ts b/src/common/helpers/utils.ts index 0e718ec8a6..2b778f1a22 100644 --- a/src/common/helpers/utils.ts +++ b/src/common/helpers/utils.ts @@ -310,12 +310,15 @@ export async function getEntityByBBID(orm, bbid:string, otherRelations:Array { +export async function getEntity(orm, bbid:string, type:EntityType, fetchOptions?:Record):Promise { if (!isValidBBID(bbid)) { return null; } - const entityData = await orm.func.entity.getEntity(orm, upperFirst(type), bbid, []); - return entityData; + const finalBBID = await orm.func.entity.recursivelyGetRedirectBBID(orm, bbid); + const Model = getEntityModelByType(orm, upperFirst(type)); + const entity = await new Model({bbid: finalBBID}) + .fetch({require: true, ...fetchOptions}); + return entity && entity.toJSON(); } export function getAliasLanguageCodes(entity: LazyLoadedEntityT) { diff --git a/src/server/helpers/collectionRouteUtils.js b/src/server/helpers/collectionRouteUtils.js index bd018365b8..24b8c534c4 100644 --- a/src/server/helpers/collectionRouteUtils.js +++ b/src/server/helpers/collectionRouteUtils.js @@ -16,11 +16,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import * as handler from '../helpers/handler'; import * as search from '../../common/helpers/search'; import {camelCase, differenceWith, isEqual, toLower, upperFirst} from 'lodash'; import {BadRequestError} from '../../common/helpers/error'; - +import log from 'log'; /** * A handler for create or edit actions on collections. @@ -89,22 +88,11 @@ export async function collectionCreateOrEditHandler(req, res, next) { // if it's new or the name is changed, // we need to update this collection in ElasticSearch index if (isNew || res.locals.collection.name !== newCollection.get('name')) { - const collectionPromiseForES = new Promise((resolve) => { - const collectionForES = { - aliasSet: { - aliases: [ - {name: newCollection.get('name')} - ] - }, - bbid: newCollection.get('id'), - id: newCollection.get('id'), - type: 'Collection' - }; - resolve(collectionForES); - }); - return handler.sendPromiseResult(res, collectionPromiseForES, search.indexEntity); + // Type needed for indexing + newCollection.set('type', 'Collection'); + await search.indexEntity(newCollection); } - return res.send(newCollection.toJSON()); + return res.status(200).send(newCollection.toJSON()); } catch (err) { return next(err); diff --git a/src/server/routes/editor.tsx b/src/server/routes/editor.tsx index dd6aa81457..e0ffbf3350 100644 --- a/src/server/routes/editor.tsx +++ b/src/server/routes/editor.tsx @@ -20,7 +20,6 @@ import * as auth from '../helpers/auth'; import * as commonUtils from '../../common/helpers/utils'; import * as error from '../../common/helpers/error'; -import * as handler from '../helpers/handler'; import * as propHelpers from '../../client/helpers/props'; import * as search from '../../common/helpers/search'; import {AdminActionType, PrivilegeType} from '../../common/helpers/privileges-utils'; @@ -42,6 +41,7 @@ import _ from 'lodash'; import express from 'express'; import {getOrderedCollectionsForEditorPage} from '../helpers/collections'; import {getOrderedRevisionForEditorPage} from '../helpers/revisions'; +import log from 'log'; import target from '../templates/target'; @@ -122,8 +122,8 @@ function isCurrentUser(reqUserID, sessionUser) { return reqUserID === sessionUser.id; } -router.post('/edit/handler', auth.isAuthenticatedForHandler, (req, res) => { - async function runAsync() { +router.post('/edit/handler', auth.isAuthenticatedForHandler, async (req, res) => { + try { const {Editor} = req.app.locals.orm; if (!isCurrentUser(req.body.id, req.user)) { @@ -140,10 +140,10 @@ router.post('/edit/handler', auth.isAuthenticatedForHandler, (req, res) => { throw new error.NotFoundError('Editor not found', req); }); if (editor.get('areaId') === req.body.areaId && - editor.get('bio') === req.body.bio && - editor.get('name') === req.body.name && - editor.get('titleUnlockId') === req.body.title && - editor.get('genderId') === req.body.genderId + editor.get('bio') === req.body.bio && + editor.get('name') === req.body.name && + editor.get('titleUnlockId') === req.body.title && + editor.get('genderId') === req.body.genderId ) { throw new error.FormSubmissionError('No change to the profile'); } @@ -164,19 +164,18 @@ router.post('/edit/handler', auth.isAuthenticatedForHandler, (req, res) => { }); res.locals.user.name = req.body.name; + const editorJSON = modifiedEditor.toJSON(); - return { - aliasSet: { - aliases: [ - {name: editorJSON.name} - ] - }, - bbid: editorJSON.id, - type: 'Editor' - }; - } - handler.sendPromiseResult(res, runAsync(), search.indexEntity); + // Type needed for indexing + modifiedEditor.set('type', 'Editor'); + await search.indexEntity(modifiedEditor); + + return res.status(200).send(editorJSON); + } + catch (err) { + return error.sendErrorAsJSON(res, err); + } }); router.post('/privs/edit/handler', auth.isAuthenticatedForHandler, auth.isAuthorized(ADMIN), async (req, res, next) => { diff --git a/src/server/routes/entity/entity.tsx b/src/server/routes/entity/entity.tsx index 9411f09ed9..b7064ebcd1 100644 --- a/src/server/routes/entity/entity.tsx +++ b/src/server/routes/entity/entity.tsx @@ -478,7 +478,7 @@ export function handleDelete( editorJSON.id, body.note ); - return savedMainEntity.toJSON(); + return savedMainEntity.toJSON({omitPivot: true}); }); return handler.sendPromiseResult(res, entityDeletePromise, search.deleteEntity); @@ -1047,7 +1047,7 @@ export async function indexAutoCreatedEditionGroup(orm, newEdition, transacting) transacting, withRelated: 'aliasSet.aliases' }); - await search.indexEntity(editionGroup.toJSON()); + await search.indexEntity(editionGroup.toJSON({omitPivot: true})); } catch (err) { log.error('Could not reindex edition group after edition creation:', err); @@ -1156,34 +1156,37 @@ export async function processSingleEntity(formBody, JSONEntity, reqSession, editorJSON.id, body.note ); - /* We need to load the aliases for search reindexing and refresh it*/ - await savedMainEntity.load(['aliasSet.aliases', 'defaultAlias.language', 'relationshipSet.relationships.source', - 'relationshipSet.relationships.target', 'relationshipSet.relationships.type', 'annotation'], {transacting}); - - /* New entities will lack some attributes like 'type' required for search indexing */ - if (isNew) { - await savedMainEntity.refresh({transacting}); + /* We need to load the aliases for search reindexing and refresh it (otherwise 'type' is missing for new entities)*/ + await savedMainEntity.refresh({transacting, withRelated: ['aliasSet.aliases', 'defaultAlias.language', + 'relationshipSet.relationships.source', 'relationshipSet.relationships.target', 'relationshipSet.relationships.type', 'annotation']}); + if (isNew && savedMainEntity.get('type') === 'Edition') { /* fetch and reindex EditionGroups that may have been created automatically by the ORM and not indexed */ - if (savedMainEntity.get('type') === 'Edition') { - await indexAutoCreatedEditionGroup(orm, savedMainEntity, transacting); - } + await indexAutoCreatedEditionGroup(orm, savedMainEntity, transacting); } - const entityJSON = savedMainEntity.toJSON(); - if (entityJSON && entityJSON.relationshipSet) { - entityJSON.relationshipSet.relationships = await Promise.all(entityJSON.relationshipSet.relationships.map(async (rel) => { + const entityRelationships = savedMainEntity.related('relationshipSet')?.related('relationships'); + if (savedMainEntity.get('type') === 'Work' && entityRelationships?.length) { + const authorsOfWork = await Promise.all(entityRelationships.toJSON().filter( + // "Author wrote Work" relationship + (relation) => relation.typeId === 8 + ).map(async (relationshipJSON) => { try { - rel.source = await commonUtils.getEntityAlias(orm, rel.source.bbid, rel.source.type); - rel.target = await commonUtils.getEntityAlias(orm, rel.target.bbid, rel.target.type); + const {source} = relationshipJSON; + const sourceEntity = await commonUtils.getEntity( + orm, source.bbid, source.type, {require: false, transacting} + ); + return sourceEntity.name; } catch (err) { log.error(err); } - return rel; + return null; })); + // Attach a work's authors for search indexing + savedMainEntity.set('authors', authorsOfWork.filter(Boolean)); } - return entityJSON; + return savedMainEntity; } catch (err) { log.error(err); @@ -1191,7 +1194,7 @@ export async function processSingleEntity(formBody, JSONEntity, reqSession, } } -export function handleCreateOrEditEntity( +export async function handleCreateOrEditEntity( req: PassportRequest, res: $Response, entityType: EntityTypeString, @@ -1201,16 +1204,15 @@ export function handleCreateOrEditEntity( const {orm}: {orm?: any} = req.app.locals; const editorJSON = req.user; const {bookshelf} = orm; - const entityEditPromise = bookshelf.transaction((transacting) => + const savedEntityModel = await bookshelf.transaction((transacting) => processSingleEntity(req.body, res.locals.entity, req.session, entityType, orm, editorJSON, derivedProps, isMergeOperation, transacting)); - const achievementPromise = entityEditPromise.then( - (entityJSON) => processAchievement(orm, editorJSON.id, entityJSON) - ); - return handler.sendPromiseResult( - res, - achievementPromise, - search.indexEntity - ); + const entityJSON = savedEntityModel.toJSON(); + + await processAchievement(orm, editorJSON.id, entityJSON); + + await search.indexEntity(savedEntityModel); + + return res.status(200).send(entityJSON); } type AliasEditorT = { diff --git a/src/server/routes/entity/process-unified-form.ts b/src/server/routes/entity/process-unified-form.ts index 7b3726904c..9a1fdf5812 100644 --- a/src/server/routes/entity/process-unified-form.ts +++ b/src/server/routes/entity/process-unified-form.ts @@ -2,6 +2,7 @@ import * as achievement from '../../helpers/achievement'; import type {Request as $Request, Response as $Response} from 'express'; import {FormSubmissionError, sendErrorAsJSON} from '../../../common/helpers/error'; +import {_bulkIndexEntities, getDocumentToIndex} from '../../../common/helpers/search'; import {authorToFormState, transformNewForm as authorTransform} from './author'; import {editionGroupToFormState, transformNewForm as editionGroupTransform} from './edition-group'; import {editionToFormState, transformNewForm as editionTransform} from './edition'; @@ -13,7 +14,6 @@ import type { EntityTypeString } from 'bookbrainz-data/lib/types/entity'; import _ from 'lodash'; -import {_bulkIndexEntities} from '../../../common/helpers/search'; import log from 'log'; import {processSingleEntity} from './entity'; @@ -206,7 +206,7 @@ export async function handleCreateMultipleEntities( } // log indexing error if any try { - _bulkIndexEntities(processedAchievements); + _bulkIndexEntities(processedEntities.map(en => getDocumentToIndex(en, en.get('type')))); } catch (err) { log.error(err); diff --git a/src/server/routes/register.js b/src/server/routes/register.js index 4a88d7ffa3..56936a3eb5 100644 --- a/src/server/routes/register.js +++ b/src/server/routes/register.js @@ -93,65 +93,58 @@ router.get('/details', middleware.loadGenders, (req, res) => { })); }); -router.post('/handler', (req, res) => { - const {Editor, EditorType} = req.app.locals.orm; +router.post('/handler', async (req, res) => { + try { + const {Editor, EditorType} = req.app.locals.orm; + + // Check whether the user is logged in - if so, redirect to profile page + if (req.user) { + return res.redirect(`/editor/${req.user.id}`); + } + + if (!req.session.mbProfile) { + return res.redirect('/auth'); + } + + // Fetch the default EditorType from the database + const editorType = await EditorType.forge({label: 'Editor'}) + .fetch({require: true}); + + // Create a new Editor and add to the database + + const editor = await new Editor({ + cachedMetabrainzName: req.session.mbProfile.sub, + genderId: req.body.gender, + metabrainzUserId: req.session.mbProfile.metabrainz_user_id, + name: req.body.displayName, + typeId: editorType.id + }) + .save(); - // Check whether the user is logged in - if so, redirect to profile page - if (req.user) { - return res.redirect(`/editor/${req.user.id}`); - } + req.session.mbProfile = null; - if (!req.session.mbProfile) { - return res.redirect('/auth'); - } + const editorJSON = editor.toJSON(); - // Fetch the default EditorType from the database - const registerPromise = EditorType.forge({label: 'Editor'}) - .fetch({require: true}) - .then( - // Create a new Editor and add to the database - (editorType) => - new Editor({ - cachedMetabrainzName: req.session.mbProfile.sub, - genderId: req.body.gender, - metabrainzUserId: req.session.mbProfile.metabrainz_user_id, - name: req.body.displayName, - typeId: editorType.id - }) - .save() - ) - .then((editor) => { - req.session.mbProfile = null; - const editorJSON = editor.toJSON(); - - // in ES index, we're storing the editor as entity - const editorForES = {}; - editorForES.bbid = editorJSON.id; - editorForES.aliasSet = { - aliases: [ - {name: editorJSON.name} - ] - }; - editorForES.type = 'Editor'; - return editorForES; - }) - .catch((err) => { - log.debug(err); - - if (_.isMatch(err, {constraint: 'editor_name_key'})) { - throw new error.FormSubmissionError( - 'That username already exists - please try using another,' + - ' or contact us to have your existing BookBrainz account' + - ' linked to a MusicBrainz account.' - ); - } - - throw new error.FormSubmissionError( - 'Something went wrong when registering, please try again!' - ); - }); - - return handler.sendPromiseResult(res, registerPromise, search.indexEntity); + // Type needed for indexing + editor.set('type', 'Editor'); + await search.indexEntity(editor); + + return res.status(200).send(editorJSON); + } + catch (err) { + if (_.isMatch(err, {constraint: 'editor_name_key'})) { + return error.sendErrorAsJSON(res, new error.FormSubmissionError( + 'That username already exists - please try using another,' + + ' or contact us to have your existing BookBrainz account' + + ' linked to a MusicBrainz account.' + )); + } + log.error(err); + + return error.sendErrorAsJSON(res, new error.FormSubmissionError( + 'Something went wrong when registering, please try again!' + )); + } }); export default router; diff --git a/yarn.lock b/yarn.lock index 1e024195af..5f4ef0ac51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1210,13 +1210,20 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.0", "@babel/runtime@^7.17.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.0", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.0", "@babel/runtime@^7.17.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.15.4": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.16.0", "@babel/template@^7.16.7", "@babel/template@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -1901,10 +1908,10 @@ dependencies: "@types/node" "*" -"@types/hoist-non-react-statics@^3.3.1": - version "3.3.3" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.3.tgz#8bb41d9a88164f82dd2745ff05e637e655f34d19" - integrity sha512-Wny3a2UXn5FEA1l7gc6BbpoV5mD1XijZqgkp4TRgDCDL5r3B5ieOFGUX5h3n78Tr1MEG7BfvoM8qeztdvNU0fw== +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" @@ -1995,6 +2002,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.20": + version "7.1.33" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15" + integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-select@^4.0.18": version "4.0.18" resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-4.0.18.tgz#f907f406411afa862217a9d86c54a301367a35c1" @@ -2061,11 +2078,6 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== -"@types/use-sync-external-store@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" - integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== - "@types/warning@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" @@ -2730,13 +2742,13 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" - content-type "~1.0.4" + content-type "~1.0.5" debug "2.6.9" depd "2.0.0" destroy "1.2.0" @@ -2744,7 +2756,7 @@ body-parser@1.20.1: iconv-lite "0.4.24" on-finished "2.4.1" qs "6.11.0" - raw-body "2.5.1" + raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -3166,16 +3178,11 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@2.0.19: +colorette@2.0.19, colorette@^2.0.10, colorette@^2.0.14: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3280,10 +3287,10 @@ content-disposition@0.5.4: dependencies: safe-buffer "5.2.1" -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.8.0" @@ -3302,10 +3309,10 @@ cookie@0.4.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== cookiejar@^2.1.0, cookiejar@^2.1.1, cookiejar@^2.1.4: version "2.1.4" @@ -3375,19 +3382,19 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-loader@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" - integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== +css-loader@^6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.9.1.tgz#9ec9a434368f2bdfeffbf8f6901a1ce773586c6b" + integrity sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ== dependencies: icss-utils "^5.1.0" - postcss "^8.4.7" + postcss "^8.4.33" postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" + postcss-modules-local-by-default "^4.0.4" + postcss-modules-scope "^3.1.1" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.3.5" + semver "^7.5.4" css-select@^5.1.0: version "5.1.0" @@ -3954,16 +3961,17 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.62, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.63" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.63.tgz#9c222a63b6a332ac80b1e373b426af723b895bd6" + integrity sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ== dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" -es6-iterator@^2.0.3, es6-iterator@~2.0.3: +es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -3972,7 +3980,7 @@ es6-iterator@^2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-symbol@^3.1.1, es6-symbol@~3.1.3: +es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== @@ -4269,6 +4277,16 @@ esm@^3.2.25: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -4378,17 +4396,17 @@ express-slow-down@^1.3.1: dependencies: defaults "^1.0.3" -express@^4.18.2: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -4648,10 +4666,10 @@ from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-monkey@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-monkey@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788" + integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew== fs-readdir-recursive@^1.1.0: version "1.1.0" @@ -6086,12 +6104,12 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" - integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - fs-monkey "1.0.3" + fs-monkey "^1.0.4" memoize-one@^5.0.0: version "5.2.1" @@ -6304,10 +6322,10 @@ nanoid@3.1.25: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare@^1.4.0: version "1.4.0" @@ -6339,11 +6357,6 @@ next-tick@1, next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - nise@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.1.tgz#ac4237e0d785ecfcb83e20f389185975da5c31f3" @@ -6373,10 +6386,10 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== -nodemailer@^6.5.0: - version "6.7.2" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" - integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== +nodemailer@^6.9.9: + version "6.9.9" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.9.tgz#4549bfbf710cc6addec5064dd0f19874d24248d9" + integrity sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA== nodemon@^2.0.2: version "2.0.15" @@ -6901,19 +6914,19 @@ postcss-modules-extract-imports@^3.0.0: resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== +postcss-modules-local-by-default@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" + integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== +postcss-modules-scope@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" + integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== dependencies: postcss-selector-parser "^6.0.4" @@ -6937,12 +6950,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.2.14, postcss@^8.4.7: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== +postcss@^8.2.14, postcss@^8.4.33: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -7136,10 +7149,10 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== dependencies: bytes "3.1.2" http-errors "2.0.0" @@ -7240,6 +7253,11 @@ react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" @@ -7277,17 +7295,17 @@ react-popper@^2.2.5: react-fast-compare "^3.0.1" warning "^4.0.2" -react-redux@^8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.3.tgz#4fdc0462d0acb59af29a13c27ffef6f49ab4df46" - integrity sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw== +react-redux@^7.2.9: + version "7.2.9" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" + integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== dependencies: - "@babel/runtime" "^7.12.1" - "@types/hoist-non-react-statics" "^3.3.1" - "@types/use-sync-external-store" "^0.0.3" + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" hoist-non-react-statics "^3.3.2" - react-is "^18.0.0" - use-sync-external-store "^1.0.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" react-select-fast-filter-options@^0.2.3: version "0.2.3" @@ -7449,6 +7467,13 @@ redux-thunk@^2.2.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== +redux@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + redux@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" @@ -7792,7 +7817,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8: +semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -8397,6 +8422,11 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -8553,11 +8583,6 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -use-sync-external-store@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -8686,13 +8711,13 @@ webpack-cli@^4.10.0: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" - integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" - memfs "^3.4.1" + memfs "^3.4.3" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" From f645aa5d72b2f66a7899b2f58d9dff120dec21da Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 20:09:25 +0530 Subject: [PATCH 17/20] minor fix --- src/client/entity-editor/entity-editor.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index 60fb248e9e..ee3e139a51 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -27,7 +27,6 @@ import RelationshipSection from './relationship-editor/relationship-section'; import SubmissionSection from './submission-section/submission-section'; import _ from 'lodash'; import {getEntityUrl} from '../helpers/entity'; -import {getEntityUrl} from '../helpers/entity'; import {submit} from './submission-section/actions'; @@ -36,14 +35,12 @@ type OwnProps = { heading: string, intitialState:Record, entity: any - entity: any }; type DispatchProps = { onSubmit: (event:React.FormEvent) => unknown }; -type Props = DispatchProps & OwnProps; type Props = DispatchProps & OwnProps; /** @@ -62,12 +59,9 @@ const EntityEditor = (props: Props) => { heading, onSubmit, entity - onSubmit, - entity } = props; const currentState = (useSelector((state) => state) as any).toJS(); let entityURL; - let entityURL; // eslint-disable-next-line consistent-return const handleUrlChange = React.useCallback(() => { if (!_.isEqual(currentState, props.intitialState) && !currentState.submissionSection.submitted) { From eeaf8a5a596ab6e04a859e36f375667795c47d6d Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Wed, 24 Apr 2024 20:10:08 +0530 Subject: [PATCH 18/20] minor fix --- src/client/entity-editor/entity-editor.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index ee3e139a51..d6d5b045dd 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -83,9 +83,6 @@ const EntityEditor = (props: Props) => {
{entityURL ? {heading} : heading}
-
- {entityURL ? {heading} : heading} -
From 40a406609854ed6c16db01b30ae719df25be54e7 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Thu, 25 Apr 2024 03:31:29 +0530 Subject: [PATCH 19/20] done some remaining renaming of for alias merge --- .../entity-editor/alias-section/alias-editor-merge.js | 2 +- src/client/entity-editor/alias-section/alias-row-merge.js | 2 +- .../entity-editor/identifier-section/identifier-section.js | 1 + src/server/routes/entity/entity.tsx | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/client/entity-editor/alias-section/alias-editor-merge.js b/src/client/entity-editor/alias-section/alias-editor-merge.js index 81d4ab7ad4..7873f5a7e5 100644 --- a/src/client/entity-editor/alias-section/alias-editor-merge.js +++ b/src/client/entity-editor/alias-section/alias-editor-merge.js @@ -71,7 +71,7 @@ AliasEditorMerge.propTypes = { function mapStateToProps(rootState) { const nameSection = rootState.get('nameSection'); - const aliases = rootState.get('aliasEditor'); + const aliases = rootState.get('aliasSection'); /** Dynamically filter out aliases that are different * from the attributes selected in the nameSection diff --git a/src/client/entity-editor/alias-section/alias-row-merge.js b/src/client/entity-editor/alias-section/alias-row-merge.js index 1961630e7c..585b466326 100644 --- a/src/client/entity-editor/alias-section/alias-row-merge.js +++ b/src/client/entity-editor/alias-section/alias-row-merge.js @@ -75,7 +75,7 @@ function mapDispatchToProps(dispatch, {index}) { } function mapStateToProps(rootState, {index}) { - const state = rootState.get('aliasEditor'); + const state = rootState.get('aliasSection'); return { languageValue: state.getIn([index, 'language']), nameValue: state.getIn([index, 'name']), diff --git a/src/client/entity-editor/identifier-section/identifier-section.js b/src/client/entity-editor/identifier-section/identifier-section.js index eb103f106d..e037d28c4c 100644 --- a/src/client/entity-editor/identifier-section/identifier-section.js +++ b/src/client/entity-editor/identifier-section/identifier-section.js @@ -40,6 +40,7 @@ const IdentifierSection = ({ identifierTypes, isUnifiedForm }) => { + console.log("IT :", identifierTypes); const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; const helpIconElement = ( diff --git a/src/server/routes/entity/entity.tsx b/src/server/routes/entity/entity.tsx index b7064ebcd1..e124a795dc 100644 --- a/src/server/routes/entity/entity.tsx +++ b/src/server/routes/entity/entity.tsx @@ -1215,7 +1215,7 @@ export async function handleCreateOrEditEntity( return res.status(200).send(entityJSON); } -type AliasEditorT = { +type aliasSectionT = { language: number | null | undefined, name: string, primary: boolean, @@ -1231,12 +1231,12 @@ type NameSectionT = { }; export function constructAliases( - aliasSection: {[propName: string]: AliasEditorT}, nameSection: NameSectionT + aliasSection: {[propName: string]: aliasSectionT}, nameSection: NameSectionT ) { const aliases = _.map( aliasSection, ( - {language: languageId, name, primary, sortName}: AliasEditorT, + {language: languageId, name, primary, sortName}: aliasSectionT, id ) => ({ default: false, From 6bc94a35f9f32e485f2eb14ed254843067e712e9 Mon Sep 17 00:00:00 2001 From: Tarunmeena0901 Date: Thu, 25 Apr 2024 03:39:54 +0530 Subject: [PATCH 20/20] lint error fix --- src/client/entity-editor/entity-editor.tsx | 2 +- .../entity-editor/identifier-section/identifier-section.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/entity-editor/entity-editor.tsx b/src/client/entity-editor/entity-editor.tsx index d6d5b045dd..03266d9902 100644 --- a/src/client/entity-editor/entity-editor.tsx +++ b/src/client/entity-editor/entity-editor.tsx @@ -115,4 +115,4 @@ function mapDispatchToProps(dispatch, {submissionUrl}) { }; } -export default connect(null, mapDispatchToProps)(EntityEditor); \ No newline at end of file +export default connect(null, mapDispatchToProps)(EntityEditor); diff --git a/src/client/entity-editor/identifier-section/identifier-section.js b/src/client/entity-editor/identifier-section/identifier-section.js index e037d28c4c..eb103f106d 100644 --- a/src/client/entity-editor/identifier-section/identifier-section.js +++ b/src/client/entity-editor/identifier-section/identifier-section.js @@ -40,7 +40,6 @@ const IdentifierSection = ({ identifierTypes, isUnifiedForm }) => { - console.log("IT :", identifierTypes); const helpText = `identity of the entity in other databases and services, such as ISBN, barcode, MusicBrainz ID, WikiData ID, OpenLibrary ID, etc. You can enter either the identifier only (Q2517049) or a full link (https://www.wikidata.org/wiki/Q2517049).`; const helpIconElement = (