diff --git a/frontend/scenarios/edit_metadata.feature b/frontend/scenarios/edit_metadata.feature index 63bbfc3b..f6d1c620 100644 --- a/frontend/scenarios/edit_metadata.feature +++ b/frontend/scenarios/edit_metadata.feature @@ -43,3 +43,43 @@ Scénario: sans être connecté dc_issued: 1932 """ Alors je peux lire "Before editing this document, please log in first" + + + +Scénario: avec un formulaire dont on est l'auteur + Soit un document dont je suis l'auteur affiché comme glose + Et une session active avec mon compte + Quand j'ouvre le formulaire de modification des métadonnées + Et je remplis "creator" avec "Alice Liddell" + Et je remplis "title" avec "Chapitre 1: Contexte historique" + Et je remplis "issued" avec "1932" + Et j'ajoute le champ "Language" au formulaire + Et je remplis "language" avec "french" + Et j'ajoute le champ "Translator" au formulaire + Et je remplis "translator" avec "Charles Beaudelaire" + Et j'ajoute le champ "Part of" au formulaire + Et je remplis "isPartOf" avec "Philosophie Moderne : Une première approche" + Et je valide le formulaire + Alors "Chapitre 1: Contexte historique" est la glose ouverte + Et le créateur est "Alice Liddell" + Et l'année de publication est "1932" + Et la langue est "French" + Et le titre de l'ouvrage est "Philosophie Moderne : Une première approche" + + +Scénario: avec un formulaire dont on n'est pas l'auteur + + Soit un document dont je ne suis pas l'auteur affiché comme glose + Et une session active avec mon compte + Quand j'ouvre le formulaire de modification des métadonnées + Alors je peux lire "Before editing this document, please request authorization to its editors first" + + + +Scénario: avec un formulaire sans être connecté + + Soit un document dont je suis l'auteur affiché comme glose + Quand j'ouvre le formulaire de modification des métadonnées + Alors je peux lire "Before editing this document, please log in first" + + diff --git a/frontend/src/components/Metadata.jsx b/frontend/src/components/Metadata.jsx index 7cf39430..f0cbbe2f 100644 --- a/frontend/src/components/Metadata.jsx +++ b/frontend/src/components/Metadata.jsx @@ -1,13 +1,28 @@ import '../styles/Metadata.css'; import { useEffect, useState } from 'react'; -import { parse, stringify } from 'yaml'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; -function Metadata({metadata = {}, editable, backend, setLastUpdate}) { +const CORE_FIELDS = [ + {key: 'dc_creator', label: 'Creator', type: 'text'}, + {key: 'dc_title', label: 'Title', type: 'text'}, + {key: 'dc_issued', label: 'Date', type: 'date'}, +]; + +const OPTIONAL_FIELDS = [ + {key: 'dc_translator', label: 'Translator', type: 'text'}, + {key: 'dc_language', label: 'Language', type: 'text'}, + {key: 'dc_isPartOf', label: 'Part of', type: 'text'}, + {key: 'dc_publisher', label: 'Publisher', type: 'text'}, + {key: 'dc_spatial', label: 'Spatial', type: 'text'}, +]; + +function Metadata({metadata = {}, editable, user, backend, setLastUpdate}) { const [beingEdited, setBeingEdited] = useState(false); const [editedDocument, setEditedDocument] = useState(metadata); - const [editedText, setEditedText] = useState(); + const [editedMetadata, setEditedMetadata] = useState({}); + const [shownOptional, setShownOptional] = useState([]); + const [showAddMenu, setShowAddMenu] = useState(false); useEffect(() => { setEditedDocument(metadata); @@ -18,33 +33,78 @@ function Metadata({metadata = {}, editable, backend, setLastUpdate}) { Object.entries(doc).filter(([key, _]) => key.startsWith('dc_')) ); + let toFieldValue = (value) => + Array.isArray(value) ? value.join(' & ') : (value ?? ''); + + let toDateInputValue = (value) => { + if (!value) return ''; + let date = new Date(value.toString()); + return isNaN(date) ? '' : date.toISOString().slice(0, 10); + }; + + let fieldValue = (key, type) => + type === 'date' + ? toDateInputValue(editedMetadata[key]) + : toFieldValue(editedMetadata[key]); + + let isAuthorized = (doc) => + user && (!doc.editors || doc.editors.includes(user)); + let handleClick = () => { backend.getDocument(metadata._id) .then((x) => { + let md = getMetadata(x); setEditedDocument(x); - setEditedText(stringify(getMetadata(x))); + setEditedMetadata(md); + // Reveal optional fields that already hold a value. + setShownOptional( + OPTIONAL_FIELDS.filter(({key}) => md[key]).map(({key}) => key) + ); + setShowAddMenu(false); setBeingEdited(true); + // Surface the canonical authorization warning right away; the backend + // rejects this no-op write so no revision is created. + if (!isAuthorized(x)) backend.putDocument(x).catch(() => {}); }); }; - let handleChange = (event) => { - setEditedText(event.target.value); + let handleFieldChange = (key) => (event) => { + setEditedMetadata((current) => ({...current, [key]: event.target.value})); }; - let handleBlur = () => { - setBeingEdited(false); + let handleAddField = (key) => { + setShownOptional((current) => [...current, key]); + setShowAddMenu(false); + }; + + let save = () => { let updatedDocument = { ...Object.fromEntries( Object.entries(editedDocument).filter(([key, _]) => !key.startsWith('dc_')) ), - ...parse(editedText.replaceAll(/(dc_isPartOf|dc_title):\s*"?([^"\n]+)"?$/gm, '$1: "$2"')) + ...Object.fromEntries( + Object.entries(editedMetadata).filter(([_, value]) => value !== '') + ), }; setEditedDocument(updatedDocument); + setBeingEdited(false); + setShowAddMenu(false); backend.putDocument(updatedDocument) .then(x => setLastUpdate(x.rev)) .catch(console.error); }; + let handleSubmit = (event) => { + event.preventDefault(); + save(); + }; + + let handleBlur = (event) => { + // Only save once focus leaves the whole form, not when moving between fields. + if (event.currentTarget.contains(event.relatedTarget)) return; + save(); + }; + let format = (actors, prefix = '', suffix = '') => actors && (prefix + [actors].flat().join(' & ') + suffix); @@ -69,7 +129,7 @@ function Metadata({metadata = {}, editable, backend, setLastUpdate}) { {formatTranslation(dc_translator, dc_language)} {dc_isPartOf ? {dc_isPartOf}, : ''} - {dc_issued ? `${new Date(dc_issued.toString()).getFullYear()}` : ''} + {dc_issued ? `${dc_issued.toString().slice(0, 4)}` : ''} ); @@ -86,11 +146,62 @@ function Metadata({metadata = {}, editable, backend, setLastUpdate}) { ); } + let visibleFields = [ + ...CORE_FIELDS, + ...OPTIONAL_FIELDS.filter(({key}) => shownOptional.includes(key)), + ]; + let addableFields = OPTIONAL_FIELDS.filter(({key}) => !shownOptional.includes(key)); + return ( -
-