Skip to content

Conversation

@Sidqui-Youssef
Copy link
Contributor

@Sidqui-Youssef Sidqui-Youssef commented Apr 29, 2025

We, @Sidqui-Youssef @lucas-rubagotti @Amine-jabote @FredWantou @IlanPin @nasschml , hereby grant to Hyperglosae maintainers the right to publish our contribution under the terms of any licenses the Free Software Foundation classifies as Free Software Licenses.

@benel
Copy link
Member

benel commented Apr 30, 2025

Thank you @Sidqui-Youssef and @Amine-jabote for your scenario.

Please note that your scenario should be in the frontend/scenario folder instead of the feature folder (the latter is deprecated, it was to be used with Capybara instead of Cypress test framework).

@benel benel marked this pull request as ready for review May 26, 2025 14:50
@benel benel self-requested a review as a code owner May 26, 2025 14:50
@benel
Copy link
Member

benel commented May 26, 2025

Thank you for your contribution @lucas-rubagotti @IlanPin @nasschml.
Any idea why the tests are failing?
Do you need help?

@FredWantou FredWantou force-pushed the feat-187 branch 4 times, most recently from 34cc072 to 274098c Compare May 27, 2025 13:57
@Sidqui-Youssef
Copy link
Contributor Author

On remercie @lunatiique et @floporsch911 qui nous ont aidé pour la gestion de l'historique des commits .

Copy link
Member

@benel benel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution @IlanPin and @nasschml (and the others for scenarios and tests).

However the implementation cannot be integrated for now as it still does not comply with the quality expectations of the project.

The related skill to be acquired in this course is named "Développer une fonctionnalité en minimisant l'impact des changements sur le code existant".

For now the last commit impacts 319 lines. This is HUGE for a single feature. If you look at others you will see it ranges from 20 to 80 lines. Please amend the code to comply with my first review and I will start a seconde one.

Regards.

@@ -1,103 +1,309 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't move the line from line 3 to line 1. This will make the diff more understandable (and hence easier to rebase and to understand blame view)

import DiscreeteDropdown from './DiscreeteDropdown';
import PictureUploadAction from '../menu-items/PictureUploadAction';
import {v4 as uuid} from 'uuid';
import { v4 as uuid } from 'uuid';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't include changes unrelated with your contribution. This "pollutes" blame view and more lines means more conflicts when integrating your work.

setSelectedText,
backend,
setLastUpdate
}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't include changes unrelated with your contribution. This "pollutes" blame view and more lines means more conflicts when integrating your work.

Moreover, in this case, it makes it very difficult to locate if you added a parameter or not.

const [editedDocument, setEditedDocument] = useState();
const [editedText, setEditedText] = useState();
const PASSAGE = new RegExp(`\\{${rubric}} ?([^{]*)`);
const [editedDocument, setEditedDocument] = useState(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't include changes unrelated with your contribution. This "pollutes" blame view and more lines means more conflicts when integrating your work.

In this cas, this means exactly the same (no parameter means a null parameter).

const [editedText, setEditedText] = useState();
const PASSAGE = new RegExp(`\\{${rubric}} ?([^{]*)`);
const [editedDocument, setEditedDocument] = useState(null);
const [editedText, setEditedText] = useState('');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't include changes unrelated with your contribution. This "pollutes" blame view and more lines means more conflicts when integrating your work.
Moreover null is different from "". This could have unexpected effects.

});

this.deleteDocument = ({_id, _rev}) =>
this.deleteDocument = ({ _id, _rev }) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated with the contribution.

.then(x => x.userCtx?.name);

this.postSession = ({name, password}) =>
this.postSession = ({ name, password }) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated with the contribution.


this.refreshMetadata = (id, callback) => {
this.getView({view: 'metadata', id, options: ['include_docs']})
this.getView({ view: 'metadata', id, options: ['include_docs'] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated with the contribution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?


this.refreshContent = (id, callback) => {
this.getView({view: 'content', id, options: ['include_docs']})
this.getView({ view: 'content', id, options: ['include_docs'] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated with the contribution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?


this.getAllDocuments = (user) =>
this.getView({view: 'all_documents', id: user || 'PUBLIC', options: ['include_docs']})
this.getView({ view: 'all_documents', id: user || 'PUBLIC', options: ['include_docs'] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated with the contribution.

@nasschml nasschml force-pushed the feat-187 branch 2 times, most recently from bc0fdaf to ba21230 Compare June 15, 2025 11:53
Copy link
Member

@benel benel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IlanPin @nasschml
Please refactor the code as requested if you want it to be integrated and your badges to be validated.

? rawText.match(PASSAGE)[1]
: rawText;
// Memoized regex to extract our passage
const PASSAGE = useMemo(() => new RegExp(`\\{${rubric}} ?([^\\{]*)`), [rubric]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, you added useMemo although it had nothing to do with your contribution.

<svg viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1z"/>
</svg>`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?

fig.appendChild(trash);
fig.addEventListener('mouseenter', () => (trash.style.opacity = '1'));
fig.addEventListener('mouseleave', () => (trash.style.opacity = '0'));
trash.addEventListener('click', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?

});
};

const obs = new MutationObserver(attachTrash);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?


this.refreshMetadata = (id, callback) => {
this.getView({view: 'metadata', id, options: ['include_docs']})
this.getView({ view: 'metadata', id, options: ['include_docs'] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?


this.refreshContent = (id, callback) => {
this.getView({view: 'content', id, options: ['include_docs']})
this.getView({ view: 'content', id, options: ['include_docs'] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you fixed this?

@nasschml
Copy link
Contributor

nasschml commented Jun 16, 2025

We have decided to keep the use of MutationObserver in the EditableText component. After further review, we realized that removing it would have required major refactoring of related logic. We have now made sure that everything not related to our contribution remains untouched.

To integrate the React Bootstrap Icons trash component cleanly into the DOM, we imported both react-dom (for createRoot) and react-bootstrap-icons. We refactored the relevant useEffect to use the component instead of injecting raw SVG code. The associated CSS was updated to style the new button as required (bottom-right, with a dark background).

In addition, we updated the event test (event.js). The workaround with manual click propagation is no longer needed, since our click handling is now fully compatible with Cypress.

All changes are now limited strictly to our contribution, and the commit history is clean.

@benel
Copy link
Member

benel commented Jun 16, 2025

@IlanPin @nasschml Here is a prompt you can type into ChatGPT:

Pourquoi le responsable du code d'un projet en React refuse d'intégrer mon code parce qu'il contiendrait des appels aux méthodes querySelectorAll, querySelector, document.createElement, appendChild, observe, closest, getAttribute ?

It will both explain you why these are bad practices and give you the rules you can apply to correct your code.

@nasschml nasschml force-pushed the feat-187 branch 2 times, most recently from 5688511 to 2c55c78 Compare June 16, 2025 18:04
@nasschml
Copy link
Contributor

@benel

Thank you for your feedback regarding the use of DOM methods like querySelectorAll, querySelector, document.createElement, etc.

We now fully understand the importance of maintainability and React best practices:
Direct DOM access can break the React rendering flow, It makes the code less predictable and harder to maintain, Pure React logic ensures better state management and component reusability.

With the latest commit, all logic for displaying and deleting images is now handled 100% in a React-friendly way:
Images are extracted from the markdown and rendered using .map() in the JSX,Trash icons are attached to each image purely through React,No more direct DOM manipulation everything relies on React state and callbacks.

Everything works as expected on my local setup (display, image deletion, etc.), but some tests still fail in the CI pipeline. I will continue investigating this on my side. In the meantime, I am open to any feedback or suggestions you may have to further improve the React implementation.

Copy link
Member

@benel benel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your edits @nasschml and @IlanPin.

Please:

  • remove unrelated line edits from your commit,
  • don't use a name with an uppercase for a variable,
  • refactor your contribution into one (or two) distinct components.

import {v4 as uuid} from 'uuid';

function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, setHighlightedText, setSelectedText, backend, setLastUpdate}) {
function EditableText({ id, text, rubric, isPartOf, links, fragment, setFragment, setHighlightedText, setSelectedText, backend, setLastUpdate }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This edited line is not related with your contribution.

</div>
<DiscreeteDropdown>
<PictureUploadAction {... {id, backend, handleImageUrl}}/>
<PictureUploadAction {...{id, backend, handleImageUrl}} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This edited line is not related with your contribution.

)}
</div>
);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This edited line is not related with your contribution.


const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
let images = [];
let Text = text || '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upper case names are usually used for classes and not for variables.

while ((match = imageRegex.exec(Text)) !== null) {
images.push({ alt: match[1], src: match[2] });
}
Text = Text.replace(imageRegex, '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upper case names are usually used for classes and not for variables.

const clean = t => (t || '').replace(mdRx, '').replace(/\n{2,}/g, '\n\n').trim();
if (internal) {
backend.deleteAttachment(id, name, res => {
if (!res.ok) return alert('Error deleting attachment.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't show notifications to users if you don't provide solutions for them to fix the problem.

<div className="formatted-text" onClick={handleClick}>
<FormattedText {...{setHighlightedText, setSelectedText}}>
{text || '&nbsp;'}
<FormattedText {...{ setHighlightedText, setSelectedText }}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is not related with your contribution.

@nasschml nasschml force-pushed the feat-187 branch 7 times, most recently from caddc1a to 201e3d2 Compare June 16, 2025 19:57
@nasschml
Copy link
Contributor

@benel

About the conditional FormattedText renderingWe had to adjust the prop passed to to avoid image duplication, and this change is not unrelated to our contribution.

Here’s why:The markdown string we receive in text may contain image tags (e.g. alt).If we simply map and display the images below, but keep passing the original text (with images inside) to , those images are rendered twice: once by the markdown parser inside , and once as our mapped images (with the trash icon for deletion).This is a direct UX regression (duplication of images), and also prevents correct linking of the trash/delete functionality to each image, since the markdown renderer doesn't expose the React events we need.

To maintain a clean UI and the expected behavior (edit/delete for each image), we must strip images from the string we send to whenever we also display them in our mapped list.
If we don’t conditionally clean the string, we get either missing images or duplicates, both are blockers for the feature and for Cypress/Capybara tests.So, this line is part of the contribution and necessary for a maintainable and predictable UI.

It is not possible to implement the feature properly without this small conditional, unless we modify the markdown renderer itself (which would be a much heavier change).

Also, I have committed this feature several times, and it works correctly when I run the tests on my local machine. However, the tests keep failing on the Git pipeline. I am still checking what could be causing this difference between environments, but wanted to highlight that everything works locally for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants