mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0269b97efd | ||
|
|
cd6dfc6955 | ||
|
|
f595af581a | ||
|
|
b803f6291d |
No files matched your search
@@ -2,6 +2,20 @@
|
||||
|
||||
Notable changes to Parenthèse. Dates are in YYYY-MM-DD format.
|
||||
|
||||
## 1.5.9 (2026-10-29)
|
||||
|
||||
### Tree
|
||||
|
||||
- A child added to someone who has a single partner becomes the couple's child, next to their brothers and sisters, instead of hanging under one parent and an "Unknown" one. With no partner, several partners, or an adoption, the child stays with the parent chosen (#200).
|
||||
|
||||
### Phone
|
||||
|
||||
- No more zoom slider: it opened by itself and covered a corner of the tree. Pinch to zoom, the magnifier zooms in one step. On a computer the slider still shows on hover (#199).
|
||||
|
||||
### Profile
|
||||
|
||||
- "– 1995" no longer breaks after the dash at the end of a line. The magnifier button is labelled "Zoom in" for screen readers.
|
||||
|
||||
## 1.5.8 (2026-10-29)
|
||||
|
||||
### Family tabs
|
||||
|
||||
+16
-3
@@ -73,7 +73,7 @@ import { readGedcomBundle, zipErrorMessage } from './utils/gedcomBundle.js'
|
||||
import { materializeFile } from './utils/zipReader.js'
|
||||
import { loadMediaRefs, loadPhotoRefs, pickFirstFaces, prepareImageForUpload, saveMediaRefs, savePhotoRefs, sizeBucket } from './utils/photoImport'
|
||||
import { fileToBase64, getAccountErrorMessage } from './utils/errorMessages'
|
||||
import { getSiblingParentLinks } from './utils/familyLinks'
|
||||
import { getCoupleParentLinks, getSiblingParentLinks } from './utils/familyLinks'
|
||||
import { getUploadMimeType, inferMediaTypeFromMimeType, validateMediaFile } from './utils/mediaUpload'
|
||||
import {
|
||||
getSource,
|
||||
@@ -1225,6 +1225,19 @@ function App() {
|
||||
siblingParentLinks.push(...links)
|
||||
}
|
||||
}
|
||||
// Un enfant ajouté à une personne qui n'a qu'un conjoint est l'enfant du couple : il passe par les liens
|
||||
// du couple, comme un frère ou une sœur. Une adoption reste au seul parent choisi.
|
||||
const relationCount = (formData.relations || []).length
|
||||
formData = {
|
||||
...formData,
|
||||
relations: (formData.relations || []).filter((rel) => {
|
||||
if (rel.type !== 'child' || rel.adopted) return true
|
||||
const coupleLinks = getCoupleParentLinks(rel.personId, unions)
|
||||
if (!coupleLinks.length) return true
|
||||
siblingParentLinks.push(...coupleLinks)
|
||||
return false
|
||||
}),
|
||||
}
|
||||
|
||||
if (!tree.canEditCurrentTree) {
|
||||
if (isDemoMode) {
|
||||
@@ -1331,7 +1344,7 @@ function App() {
|
||||
},
|
||||
]
|
||||
editMode.updateDraft({ addedPersons })
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: (formData.relations || []).length, tree_id: opaqueTreeId })
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'family', relations: relationCount, tree_id: opaqueTreeId })
|
||||
// La personne apparaît en fantôme « Brouillon » : la famille peut en ajouter d'autres, puis envoyer une
|
||||
// fois avec le bouton « Envoyer » de la barre (Patch, 25/10). Avant, la fenêtre d'envoi s'ouvrait d'office,
|
||||
// seule façon de montrer qu'un ajout invisible comptait.
|
||||
@@ -1409,7 +1422,7 @@ function App() {
|
||||
})
|
||||
|
||||
await tree.refreshCurrentTreeGraph()
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: (formData.relations || []).length, tree_id: opaqueTreeId })
|
||||
trackAppEvent('person_added', { via: addPersonViaRef.current, role: 'manager', relations: relationCount, tree_id: opaqueTreeId })
|
||||
return newPerson
|
||||
} catch (err) {
|
||||
setAddPersonError(err.message || t("Erreur lors de l'ajout de la personne"))
|
||||
|
||||
@@ -631,7 +631,9 @@ const PersonCard = ({
|
||||
// <bdi> : un lieu en arabe ou en hébreu ne retourne pas l'ordre de la ligne (« 1980, الدار البيضاء · 46 ans »)
|
||||
if (born) parts.push(person.birthPlace ? <>{born}, <bdi>{formatPlace(person.birthPlace)}</bdi></> : born)
|
||||
if (!person.isAlive) parts.push(died || '?')
|
||||
return parts.length ? parts.reduce((line, part) => <>{line} – {part}</>) : null
|
||||
// « – 1995 » passe entier à la ligne : Chrome coupe après un tiret même suivi d'une espace insécable,
|
||||
// le gluon U+2060 l'en empêche
|
||||
return parts.length ? parts.reduce((line, part) => <>{line} –{'\u2060\u00a0'}{part}</>) : null
|
||||
})()
|
||||
const ageLabel = age === null ? null : person.isAlive ? t('{age} ans', { age }) : t('{age} ans au décès', { age })
|
||||
// Une seule ligne sous le nom : surnom, repères de vie, âge, séparés par un point médian
|
||||
|
||||
@@ -293,11 +293,6 @@
|
||||
.undo-toast-btn:hover { background: color-mix(in srgb, var(--pz-bg) 26%, transparent); }
|
||||
.undo-toast-btn:focus-visible { outline: 2px solid var(--pz-bg); outline-offset: 2px; }
|
||||
@media (max-width: 768px) { .contrib-success-toast { bottom: 78px; padding: 11px 16px; border-radius: 16px; font-size: 13px; } }
|
||||
/* Sous 768 px, la réglette de zoom reste ouverte en bas à gauche (ZoomControl.css, de 8 à 60 px du bord) :
|
||||
tant qu'elle se voit (ni panneau ni fiche par-dessus), la notification commence après elle au lieu de passer dessus */
|
||||
@media (max-width: 768px) {
|
||||
body:not(:has(.pz-overlay, .person-card)) .contrib-success-toast { left: 72px; right: 12px; width: auto; max-width: none; transform: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.contrib-invite-link { flex-wrap: wrap; border-radius: var(--pz-r-m); padding: 10px; }
|
||||
|
||||
@@ -185,16 +185,4 @@
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
/* Slider toujours visible sur mobile */
|
||||
.zoom-slider-panel.visible {
|
||||
max-height: 240px;
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0) scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.zoom-slider-track {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,8 @@ export default function ZoomControl({ zoomApiRef }) {
|
||||
document.addEventListener('pointerup', onUp)
|
||||
}, [zoomApiRef])
|
||||
|
||||
const panelVisible = isHovered || isMobile
|
||||
// Sur téléphone, on zoome à deux doigts : la réglette n'apparaît jamais, la loupe seule reste
|
||||
const panelVisible = isHovered && !isMobile
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -112,7 +113,7 @@ export default function ZoomControl({ zoomApiRef }) {
|
||||
onMouseEnter={showSlider}
|
||||
onMouseLeave={hideSlider}
|
||||
>
|
||||
{/* Panel slider — hover sur desktop, toujours visible sur mobile */}
|
||||
{/* Panel slider — au survol sur ordinateur, jamais sur téléphone */}
|
||||
<div
|
||||
className={`zoom-slider-panel ${panelVisible ? 'visible' : ''}`}
|
||||
onMouseEnter={showSlider}
|
||||
@@ -139,7 +140,7 @@ export default function ZoomControl({ zoomApiRef }) {
|
||||
<button
|
||||
className="zoom-button"
|
||||
onClick={stepZoom}
|
||||
aria-label={t('Contrôle du zoom')}
|
||||
aria-label={t('Zoomer')}
|
||||
>
|
||||
<ZoomIn size={20} strokeWidth={1.5} className="zoom-btn-icon" />
|
||||
</button>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
'Lien': 'Link',
|
||||
|
||||
// Contrôles
|
||||
'Contrôle du zoom': 'Zoom control',
|
||||
'Zoomer': 'Zoom in',
|
||||
'En cours': 'Loading',
|
||||
|
||||
// Annotations
|
||||
|
||||
@@ -36,3 +36,20 @@ export function getSiblingParentLinks(personId, filiations, unions) {
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
/**
|
||||
* Parents à rattacher à un nouvel enfant de `parentId` : quand cette personne n'a qu'une union,
|
||||
* l'enfant est celui du couple (les deux partenaires, avec l'identifiant de l'union).
|
||||
* Aucune union, ou plusieurs (on ne devine pas l'autre parent) : liste vide, l'enfant reste au seul parent.
|
||||
*
|
||||
* @param {string} parentId - Personne à qui l'on ajoute un enfant
|
||||
* @param {Array} unions - Unions du graphe courant ({ id, partner1Id, partner2Id })
|
||||
* @returns {Array<{ parentId: string, unionId: string }>}
|
||||
*/
|
||||
export function getCoupleParentLinks(parentId, unions) {
|
||||
const key = String(parentId)
|
||||
const own = (unions || []).filter((union) => String(union.partner1Id) === key || String(union.partner2Id) === key)
|
||||
if (own.length !== 1 || !own[0].partner1Id || !own[0].partner2Id) return []
|
||||
const unionId = String(own[0].id)
|
||||
return [{ parentId: String(own[0].partner1Id), unionId }, { parentId: String(own[0].partner2Id), unionId }]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getSiblingParentLinks } from './familyLinks'
|
||||
import { getCoupleParentLinks, getSiblingParentLinks } from './familyLinks'
|
||||
|
||||
describe('getSiblingParentLinks', () => {
|
||||
const unions = [{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' }]
|
||||
@@ -31,3 +31,23 @@ describe('getSiblingParentLinks', () => {
|
||||
expect(getSiblingParentLinks('c1', undefined, undefined)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCoupleParentLinks', () => {
|
||||
it('gives both partners when the parent has a single union', () => {
|
||||
const unions = [{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' }]
|
||||
expect(getCoupleParentLinks('p2', unions)).toEqual([
|
||||
{ parentId: 'p1', unionId: 'u1' },
|
||||
{ parentId: 'p2', unionId: 'u1' },
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps the child with the parent alone when there is no union or several', () => {
|
||||
expect(getCoupleParentLinks('p1', [])).toEqual([])
|
||||
expect(getCoupleParentLinks('p1', undefined)).toEqual([])
|
||||
const unions = [
|
||||
{ id: 'u1', partner1Id: 'p1', partner2Id: 'p2' },
|
||||
{ id: 'u2', partner1Id: 'p3', partner2Id: 'p1' },
|
||||
]
|
||||
expect(getCoupleParentLinks('p1', unions)).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user