diff --git a/backend/hyperglosae/src/updates/editFlag.js b/backend/hyperglosae/src/updates/editFlag.js new file mode 100644 index 00000000..3ae8e855 --- /dev/null +++ b/backend/hyperglosae/src/updates/editFlag.js @@ -0,0 +1,10 @@ +function(doc, req) { + if (!doc) return [null, { json: { error: 'not_found' } }]; + const body = JSON.parse(req.body); + if (body.beingEditedBy) { + doc.beingEditedBy = body.beingEditedBy; + } else { + delete doc.beingEditedBy; + } + return [doc, { json: { status: 'ok' } }]; +} \ No newline at end of file diff --git a/backend/hyperglosae/src/views/content/map.js b/backend/hyperglosae/src/views/content/map.js index fcec13f7..1fdb95a6 100644 --- a/backend/hyperglosae/src/views/content/map.js +++ b/backend/hyperglosae/src/views/content/map.js @@ -1,9 +1,9 @@ function (doc) { const { getRelatedDocuments, emitPassages, emitIncludedDocuments } = require('views/lib/links'); - let { _id, text = '', isPartOf = _id, links = [] } = doc; + let { _id, text = '', isPartOf = _id, links = [], beingEditedBy } = doc; let related = getRelatedDocuments({isPartOf, links}); - emitPassages({text, isPartOf, related}); + emitPassages({text, isPartOf, related, beingEditedBy}); emitIncludedDocuments({isPartOf, links}); -} +} \ No newline at end of file diff --git a/backend/hyperglosae/src/views/lib/links.js b/backend/hyperglosae/src/views/lib/links.js index 3e01b765..dd38560c 100644 --- a/backend/hyperglosae/src/views/lib/links.js +++ b/backend/hyperglosae/src/views/lib/links.js @@ -31,11 +31,17 @@ const parseText = (text) => { })); } -exports.emitPassages = ({text, isPartOf, related}) => { +exports.emitPassages = ({text, isPartOf, related, beingEditedBy}) => { parseText(text).forEach(({rubric, passage, parsed_rubric}) => related.forEach((x) => { - emit([x, ...parsed_rubric], { text: passage, isPartOf, rubric, _id: null }); - }) + emit([x, ...parsed_rubric], { + text: passage, + isPartOf, + rubric, + _id: null, + ...(beingEditedBy && {beingEditedBy}) + }); + }) ); } diff --git a/frontend/scenarios/co-edit.feature b/frontend/scenarios/co-edit.feature index bcdcb8a9..72c40da3 100644 --- a/frontend/scenarios/co-edit.feature +++ b/frontend/scenarios/co-edit.feature @@ -22,3 +22,13 @@ Scénario: qui modifie les métadonnées """ Alors les métadonnées de la glose en mode édition contiennent "dc_creator: Bill" +Scénario: qui est en train de modifier le contenu + Quand "Bill" est en train d’éditer le passage “1” + Alors la glose en mode édition indique que "Bill" modifie le passage “1” + +Scénario: termine de modifier le contenu + Soit "Bill" est en train d’éditer le passage “1” + Et la glose en mode édition indique que "Bill" modifie le passage “1” + Quand "Bill" quitte le mode édition + Alors la glose n'indique pas de modification en cours sur le passage "1" + diff --git a/frontend/src/components/EditableText.jsx b/frontend/src/components/EditableText.jsx index 19b3452a..6d3e4fac 100644 --- a/frontend/src/components/EditableText.jsx +++ b/frontend/src/components/EditableText.jsx @@ -6,13 +6,15 @@ import DiscreeteDropdown from './DiscreeteDropdown'; import PictureUploadAction from '../menu-items/PictureUploadAction'; import {v4 as uuid} from 'uuid'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; +import { PencilSquare } from 'react-bootstrap-icons'; -function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, setHighlightedText, setSelectedText, rawEditMode, setRawEditMode, backend, setLastUpdate}) { +function EditableText({id, text, rubric, isPartOf, links, beingEditedBy, fragment, setFragment, setHighlightedText, setSelectedText, rawEditMode, setRawEditMode, backend, setLastUpdate, user}) { const [beingEdited, setBeingEdited] = useState(false); const [editedDocument, setEditedDocument] = useState(); const [editedText, setEditedText] = useState(); const [hasBeenChanged, setHasBeenChanged] = useState(false); const PASSAGE = new RegExp(`\\{${rubric}} ?([^{]*)`); + const isEditedByOther = beingEditedBy && beingEditedBy !== user; let parsePassage = (rawText) => (rubric) ? rawText.match(PASSAGE)[1] @@ -33,6 +35,19 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, return x; }), [backend, id, isPartOf, links, rubric]); + // Marque "en édition par " quand on entre, démarque quand on sort + useEffect(() => { + if (!user || !id) return; + if (beingEdited) { + backend.markEditing(id, user).catch(console.error); + } + return () => { + if (beingEdited) { + backend.markEditing(id, null).catch(console.error); + } + }; + }, [beingEdited, id, user, backend]); + useEffect(() => { if (fragment) { updateEditedDocument() @@ -57,6 +72,7 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, }, [rawEditMode, updateEditedDocument]); let handleClick = () => { + if (isEditedByOther) return; setBeingEdited(true); updateEditedDocument() .then((x) => { @@ -102,25 +118,41 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, .catch(console.error); }; + // Vue lecture if (!beingEdited) return ( -
+
+ {isEditedByOther && ( + {beingEditedBy} is currently editing passage {rubric || '0'}} + > + + + )} Edit content...} + overlay={ + + {isEditedByOther ? `Locked by ${beingEditedBy}` : 'Edit content...'} + + } >
- {text || ' '} + {text || '\u00A0'}
- +
); + + // Vue édition return ( -
+ +