collabcentral/html/sendMessage.html
Joseph B. McQueen 524b443dce Make favorite groups editable from the compose page
Selecting a group in "Additional groups" now auto-saves it to the
caller's favorites, and each favorite gets a × affordance for one-click
removal. Favorites still live where they always have —
config.webex.bot[app].authorized[personId].groups — so nothing changes
for existing installations.

Backend
- New POST /CollabCentral/:app/user/groups/add. Body { id }. Validates
  the caller is authorized for :app, validates :id resolves to a real
  Webex group by looking it up in the cached org-wide group list
  (blocks arbitrary strings from being stuffed into config.json),
  dedups against the existing favorites array, writes config.json via
  saveConfig, and returns the updated array.
- New POST /CollabCentral/:app/user/groups/remove. Body { id }.
  Filters that id out of the caller's favorites, writes only when
  something actually changed (a remove of an unknown id no-ops instead
  of rewriting config.json), and returns the updated array.
- Both endpoints are safe against concurrent writes: index.js is
  single-process and node is single-threaded, so read/mutate/write
  runs atomically per request.
- Both log a compact audit line (last-8 of personId + group name) so
  operators can see who is curating what.

Frontend (sendMessage.html + .js + app.css)
- New "Manage favorites" chip strip renders directly under the
  Favorite Groups picker. Each favorite becomes a pill (uses .alias
  when set, otherwise .name; long labels truncate with ellipsis).
  Clicking × on a pill removes that favorite server-side and updates
  the strip locally.
- Additional Groups picker wires a 'change' listener that diffs the
  current selection against the previous one and only fires
  /user/groups/add for newly-picked ids (never re-fires on the
  reselect side of a deselect+reselect, never spams the API with the
  full selection on every keystroke).
- Client keeps favoriteGroups mirrored to every API response so the
  "already a favorite?" dedup check is a pure in-memory lookup — no
  wasted round trips when the user re-picks a group they already
  favorited.
- Transient status message ("Added to favorites." / "Removed from
  favorites." / error variants) fades under the field label; sticks
  around ~4s.
- New shared styles: .fieldLabelRow (label + inline status),
  .fieldStatus (with --error variant), .chipStrip, .chip,
  .chip__label, .chip__close (with hover/focus states).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 20:25:15 -04:00

105 lines
4.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CollabCentral</title>
<link id="appFavicon" rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
<link rel="stylesheet" href="js/virtual-select.min.css">
<script src="js/virtual-select.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
</head>
<body data-page="send" data-title-suffix="Send Message">
<header class="appHeader">
<div class="appHeader__inner">
<div class="appHeader__brand">
<div id="appAvatar" class="appHeader__avatar"></div>
<div id="appLabel" class="appHeader__label">Loading…</div>
</div>
<nav class="appHeader__nav">
<a href="./sendMessage.html" data-nav="send">Send Message</a>
<a href="./monitorJobs.html" data-nav="monitor">Monitor Jobs</a>
</nav>
<div class="appHeader__spacer"></div>
<div id="appUser" class="appHeader__user"></div>
</div>
</header>
<div id="unauthorizedPanel" class="panel hidden">
<h2>You don't have access to this bot</h2>
<p>Your account isn't on the authorized list for this bot. If you believe this is a mistake, contact the bot's administrator.</p>
</div>
<div id="formPanel" class="container hidden">
<div class="card">
<h1 class="pageTitle">Compose a message</h1>
<p class="pageSubtitle">Pick your recipients, write the message, and send now or schedule it for later.</p>
<form id="sendMessage" enctype="multipart/form-data">
<div class="field">
<label for="my-text-area">Message</label>
<textarea id="my-text-area"></textarea>
</div>
<div class="field">
<label for="uploadImage">Attach image (optional)</label>
<input type="file" id="uploadImage" accept="image/*" name="uploadImage" onchange="showImagePreview(event);">
<img id="file-ip-1-preview" alt="">
</div>
<div class="field">
<div class="fieldLabelRow">
<label for="favGroups">Favorite groups</label>
<span id="favGroupsStatus" class="fieldStatus" role="status" aria-live="polite"></span>
</div>
<select id="favGroups" multiple name="favGroups" placeholder="Favorite groups"
data-silent-initial-value-set="false"></select>
<div id="favGroupsManager" class="chipStrip hidden" aria-label="Manage favorite groups"></div>
<span class="hint">Selecting a group under <em>Additional groups</em> saves it to your favorites. Click the × on a chip to remove that favorite.</span>
</div>
<div class="field">
<label for="newGroups">Additional groups</label>
<select id="newGroups" multiple name="newGroups" placeholder="Search all groups"
data-silent-initial-value-set="false"></select>
</div>
<div class="field">
<label for="uploadCSV">Upload CSV of recipients (optional)</label>
<input type="file" id="uploadCSV" name="uploadCSV" accept="text/csv">
<span class="hint">CSV of Webex person IDs or emails to add to the recipient list.</span>
</div>
<div class="field">
<label for="scheduledFor">Schedule for (leave blank to send immediately)</label>
<input type="datetime-local" id="scheduledFor" name="scheduledFor">
</div>
<div class="formActions">
<button id="submit" type="submit" class="btn btn-primary">Review &amp; send</button>
</div>
</form>
</div>
</div>
<section class="modal hidden" aria-modal="true" role="dialog">
<button class="modal__close" onclick="continueEditing()" aria-label="Close">×</button>
<h3>Ready to send?</h3>
<p id="modalMessage">This message will be sent.</p>
<div class="modal__actions">
<button class="btn btn-secondary" onclick="continueEditing()">Keep editing</button>
<button class="btn btn-primary" onclick="submit()">Send</button>
</div>
</section>
<div class="overlay hidden"></div>
<script src="js/app.js"></script>
<script src="sendMessage.js"></script>
</body>
</html>