diff --git a/html/css/app.css b/html/css/app.css index a72f7cb..f920023 100644 --- a/html/css/app.css +++ b/html/css/app.css @@ -334,6 +334,16 @@ a:hover { color: var(--accent-hover); text-decoration: underline; } color: #b91c1c; } +/* Right-aligned row of buttons directly below a picker (e.g. the "Save + selection to favorites" affordance under the Additional groups + picker). Sits between the picker and its hint. */ +.fieldActions { + display: flex; + justify-content: flex-end; + gap: 0.5em; + margin-top: 0.4em; +} + /* Chip strip: compact list of removable pills used by the "Manage favorites" affordance below the favorite-groups picker. Chips are click-target friendly on touch (min 32px tall) and truncate long diff --git a/html/sendMessage.html b/html/sendMessage.html index e087d66..d61162a 100644 --- a/html/sendMessage.html +++ b/html/sendMessage.html @@ -60,13 +60,17 @@ - Selecting a group under Additional groups saves it to your favorites. Click the × on a chip to remove that favorite. + Pick the favorites you want for this message. Click the × on a chip to remove a favorite you no longer use.
+
+ +
+ Pick any additional org groups for this message. They stay one-off unless you save them.
diff --git a/html/sendMessage.js b/html/sendMessage.js index b798bd2..d318c94 100644 --- a/html/sendMessage.js +++ b/html/sendMessage.js @@ -159,45 +159,110 @@ function onRemoveFavorite(event) { }); } -// Fires when the "Additional groups" multi-select changes. For each newly -// selected id that isn't already a favorite we POST /user/groups/add. The -// picker itself keeps the selection (so this send still uses the group); -// the group starts appearing in the Favorite Groups picker on the next -// page load, and immediately in the chip strip below. -function onNewGroupsChange(newlySelectedIds) { - if (!newlySelectedIds || !newlySelectedIds.length) return; +// Explicit "Save selection to favorites" action. Reads whatever is +// currently selected in the "Additional groups" picker, POSTs +// /user/groups/add for each id that isn't already a favorite, and on +// success rewires the widget state so the newly-saved groups move from +// the Additional picker into the Favorite picker without losing this +// message's recipient list. +// +// Kept explicit (button-driven) rather than firing on every change so a +// one-off pick — the common case, since every message tends to target a +// different set of groups — doesn't pollute favorites forever. +function saveSelectionToFavorites() { var newGroupsEl = document.getElementById('newGroups'); - var pending = 0; - var successes = 0; + var favGroupsEl = document.getElementById('favGroups'); + var btn = document.getElementById('saveFavoritesBtn'); + if (!newGroupsEl) return; - newlySelectedIds.forEach(function (id) { - if (favoriteGroups.some(function (g) { return g.id === id; })) return; - pending++; + var selectedOptions = newGroupsEl.getSelectedOptions() || []; + var candidates = []; + for (var i = 0; i < selectedOptions.length; i++) { + var id = selectedOptions[i].value; + if (favoriteGroups.some(function (g) { return g.id === id; })) continue; + candidates.push({ id: id, name: selectedOptions[i].label || selectedOptions[i].text }); + } + if (!candidates.length) { + flashFavoritesStatus('Nothing new to save.'); + return; + } + + if (btn) btn.disabled = true; + var latestFavorites = favoriteGroups; + var successIds = []; + var pending = candidates.length; + + candidates.forEach(function (c) { fetch('/CollabCentral/' + CollabCentral.appName + '/user/groups/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id: id }) + body: JSON.stringify({ id: c.id }) }) .then(function (res) { if (!res.ok) throw new Error('HTTP ' + res.status); return res.json(); }) .then(function (updatedFavorites) { - favoriteGroups = updatedFavorites || favoriteGroups; - successes++; - renderFavoritesManager(); + if (Array.isArray(updatedFavorites)) latestFavorites = updatedFavorites; + successIds.push(c.id); }) .catch(function (err) { - console.error('Failed to add favorite ' + id + ':', err); + console.error('Failed to add favorite ' + c.id + ':', err); flashFavoritesStatus('Could not save one or more favorites.', true); }) .finally(function () { pending--; - if (pending === 0 && successes > 0) { - flashFavoritesStatus(successes === 1 - ? 'Added to favorites.' - : 'Added ' + successes + ' to favorites.'); + if (pending > 0) return; + + if (btn) btn.disabled = false; + if (!successIds.length) return; + + // Capture the current favorite selection BEFORE + // rebuilding the options list — setOptions can clear + // the selection depending on the VirtualSelect + // internals, and we want this message's existing + // favorite picks to survive. + var currentFavIds = []; + if (favGroupsEl && typeof favGroupsEl.getSelectedOptions === 'function') { + var favSel = favGroupsEl.getSelectedOptions() || []; + for (var j = 0; j < favSel.length; j++) currentFavIds.push(favSel[j].value); } + + // Adopt the freshest server view of favorites, then + // rebuild the favorites VirtualSelect options list so + // the just-saved groups become pickable there. + favoriteGroups = latestFavorites; + renderFavoritesManager(); + if (favGroupsEl && typeof favGroupsEl.setOptions === 'function') { + favGroupsEl.setOptions(favoriteGroups.map(function (g) { + return { label: g.name, value: g.id }; + })); + } + + // Preserve this message's recipient set: move the + // saved ids from the Additional picker into the + // Favorite picker. Users can still deselect either + // way manually, but the default is "you saved these, + // they're still going to those groups". + if (favGroupsEl && typeof favGroupsEl.setValue === 'function') { + for (var k = 0; k < successIds.length; k++) { + if (currentFavIds.indexOf(successIds[k]) === -1) currentFavIds.push(successIds[k]); + } + favGroupsEl.setValue(currentFavIds); + } + if (typeof newGroupsEl.setValue === 'function') { + var remaining = []; + var addSel = newGroupsEl.getSelectedOptions() || []; + for (var m = 0; m < addSel.length; m++) { + var mid = addSel[m].value; + if (successIds.indexOf(mid) === -1) remaining.push(mid); + } + newGroupsEl.setValue(remaining); + } + + flashFavoritesStatus(successIds.length === 1 + ? 'Added to favorites.' + : 'Added ' + successIds.length + ' to favorites.'); }); }); } @@ -226,20 +291,9 @@ function loadNewGroups(appName) { el.add(option); } VirtualSelect.init({ ele: '#newGroups' }); - - // VirtualSelect emits a standard 'change' event on the original - //