Task #347: EM edit dialog — strip HTML in inputs + reorder/rename buttons
- safe-html.ts: add htmlToPlainText (DOMParser, br/block→\n, decode, collapse)
and wrapAsParagraph (HTML-escape + <p>…</p>) helpers.
- executive-meetings.tsx:
- openEdit() runs htmlToPlainText on titleAr, titleEn, and each attendee.name
so the dialog's plain <Input> shows clean text instead of raw markup.
- save() wraps titleAr, titleEn, and each attendee.name with wrapAsParagraph
so the on-disk format stays byte-compatible with what the inline Tiptap
EditableCell produces. Empty-title validation still fires off the plain
string before wrapping.
- Swapped MeetingFormDialog buttons: addSubheading now renders before the
attendee-add button.
- ar.json: executiveMeetings.manage.attendees.add "إضافة حاضر" → "إضافة عضو".
- en.json: same key "Add attendee" → "Add member".
- Inline schedule cell chips (executiveMeetings.schedule.*) untouched.
This commit is contained in:
@@ -102,7 +102,7 @@ import {
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { GripVertical } from "lucide-react";
|
||||
import { EditableCell } from "@/components/editable-cell";
|
||||
import { safeHtml } from "@/lib/safe-html";
|
||||
import { safeHtml, htmlToPlainText, wrapAsParagraph } from "@/lib/safe-html";
|
||||
import {
|
||||
ALERT_COLOR_PRESETS,
|
||||
DEFAULT_ALERT_PREFS,
|
||||
@@ -5462,8 +5462,8 @@ function ManageSection({
|
||||
function openEdit(m: Meeting) {
|
||||
setEditing({
|
||||
id: m.id,
|
||||
titleAr: m.titleAr,
|
||||
titleEn: m.titleEn ?? "",
|
||||
titleAr: htmlToPlainText(m.titleAr),
|
||||
titleEn: htmlToPlainText(m.titleEn ?? ""),
|
||||
meetingDate: m.meetingDate,
|
||||
dailyNumber: String(m.dailyNumber),
|
||||
startTime: m.startTime ? m.startTime.slice(0, 5) : "",
|
||||
@@ -5480,7 +5480,11 @@ function ManageSection({
|
||||
// the dialog don't have one until after save, so we always assign
|
||||
// `_sid` here for uniformity. The save projection enumerates wire
|
||||
// fields and naturally drops `_sid`.
|
||||
attendees: m.attendees.map((a) => ({ ...a, _sid: genAttendeeSid() })),
|
||||
attendees: m.attendees.map((a) => ({
|
||||
...a,
|
||||
name: htmlToPlainText(a.name),
|
||||
_sid: genAttendeeSid(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5499,7 +5503,7 @@ function ManageSection({
|
||||
// for new meetings, while preserving any pre-existing English
|
||||
// title on edits (the dialog hides the field but openEdit still
|
||||
// loads it into MeetingFormState).
|
||||
const titleEn = editing.titleEn.trim() || editing.titleAr.trim();
|
||||
const titleEnPlain = editing.titleEn.trim() || editing.titleAr.trim();
|
||||
// Prefer the dialog-supplied canonical values from the time
|
||||
// picker's commit() result over the controlled `editing.*`
|
||||
// state — the picker's onChange suppresses ambiguous/invalid
|
||||
@@ -5514,8 +5518,8 @@ function ManageSection({
|
||||
: editing.endTime || null;
|
||||
setSaving(true);
|
||||
const body: Record<string, unknown> = {
|
||||
titleAr: editing.titleAr.trim(),
|
||||
titleEn,
|
||||
titleAr: wrapAsParagraph(editing.titleAr),
|
||||
titleEn: wrapAsParagraph(titleEnPlain),
|
||||
meetingDate: editing.meetingDate,
|
||||
startTime,
|
||||
endTime,
|
||||
@@ -5526,7 +5530,7 @@ function ManageSection({
|
||||
isHighlighted: editing.isHighlighted ? 1 : 0,
|
||||
notes: editing.notes.trim() || null,
|
||||
attendees: editing.attendees.map((a, idx) => ({
|
||||
name: a.name,
|
||||
name: wrapAsParagraph(a.name),
|
||||
title: a.title,
|
||||
attendanceType: a.attendanceType,
|
||||
sortOrder: idx,
|
||||
@@ -6404,16 +6408,6 @@ function MeetingFormDialog({
|
||||
{t("executiveMeetings.manage.attendees.removeAll")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={addAttendee}
|
||||
className="gap-1"
|
||||
data-testid="em-attendee-add"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
{t("executiveMeetings.manage.attendees.add")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -6424,6 +6418,16 @@ function MeetingFormDialog({
|
||||
<Plus className="w-4 h-4" />
|
||||
{t("executiveMeetings.manage.attendees.addSubheading")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={addAttendee}
|
||||
className="gap-1"
|
||||
data-testid="em-attendee-add"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
{t("executiveMeetings.manage.attendees.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user