feat: swipe picker expand/collapse, copy button, and left-align text (#5380) * Initial plan * feat: add expand/collapse, copy button, and left-align text in swipe picker Addresses user feedback for swipe picker (added in 1.17.0): 1. Left-align swipe text (was inheriting centered alignment) 2. Add expand/collapse toggle using hidden checkbox + CSS :has 3. Add copy-to-clipboard button using shared copyText utility Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/90892f1c-3c75-404b-a93b-2abe672cc0e3 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * revert unintended package-lock.json changes from npm install Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/90892f1c-3c75-404b-a93b-2abe672cc0e3 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * fix: address review feedback - hide checkbox, use chevron icons, align buttons with fa-fw - Fix checkbox visibility by using !important to override input[type=checkbox] grid display - Change expand icon to fa-chevron-down and collapse to fa-chevron-up - Add fa-fw class to all action buttons (copy, expand/collapse, branch, delete) for even sizing - Add align-items: baseline to the actions container Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/c8ca349f-3579-442a-baa1-fc138ba1d7a6 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * revert unintended package-lock.json changes Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/c8ca349f-3579-442a-baa1-fc138ba1d7a6 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * fix: update class names for chat action elements in swipe picker * fix: prevent chevron layout shift and swap button order - Use single label element as icon (fa-chevron-down) instead of two child <i> elements toggling visibility, preventing layout shift - Override ::before content to chevron-up when expanded via CSS - Swap chevron and copy button order (chevron first, then copy) Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/284283b1-6c7f-4371-bc4a-94d841513879 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * style: add comment for FA unicode value in CSS Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/284283b1-6c7f-4371-bc4a-94d841513879 Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Rotate the chevron instead of changing content * Make the ui full height --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

63feac9423ba30d9bd3e8736fc56051c8f28397c

Copilot <198982749+Copilot@users.noreply.github.com>

Signed
3 files changed, +80 -5Ignore whitespace
public/index.html+1 -1
@@ -6733,7 +6733,7 @@
67336733 <small class="chat_file_size select_chat_block_filename_item"></small>
67346734 <small class="chat_messages_num select_chat_block_filename_item"></small>
67356735 </div>
67366736 <div class="select_chat_actions flex-container gap10px">
67376737 <div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton opacity50p hoverglow fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div>
67386738 <div title="Download chat as plain text document" data-format="txt" class="exportChatButton opacity50p hoverglow fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div>
67396739 <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>
public/scripts/swipe-picker.js+37 -4
@@ -4,7 +4,7 @@ import { t } from './i18n.js';
44import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
55import { power_user } from './power-user.js';
66import { getTokenCountAsync } from './tokenizers.js';
77import { clamp, copyText, timestampToMoment } from './utils.js';
88import { chat, deleteSwipe, ensureSwipes, isMessageSwipeable, isSwipingAllowed, swipe, syncMesToSwipe } from '/script.js';
99
1010/**
@@ -132,6 +132,7 @@ async function openSwipePicker(messageId) {
132132 const template = $('#past_chat_template .select_chat_block_wrapper').clone();
133133 const block = template.find('.select_chat_block');
134134 block.removeClass('select_chat_block').addClass('swipe_picker_block');
135+ block.find('.select_chat_actions').removeClass('gap10px');
135136 const branchButton = template.find('.exportRawChatButton');
136137 const deleteButton = template.find('.PastChat_cross');
137138 const swipeInfo = Array.isArray(message.swipe_info) ? message.swipe_info[index] : null;
@@ -162,7 +163,7 @@ async function openSwipePicker(messageId) {
162163 'data-i18n': '[title]Create Branch',
163164 })
164165 .removeClass('exportRawChatButton fa-solid fa-file-export')
165166 .addClass('swipe_picker_branch mes_button fa-fw fa-regular fa-code-branch')
166167 .on('click', async (event) => {
167168 event.preventDefault();
168169 event.stopPropagation();
@@ -174,7 +175,7 @@ async function openSwipePicker(messageId) {
174175 .removeAttr('file_name')
175176 .attr('aria-disabled', String(!canDeleteSwipe))
176177 .removeClass('fa-skull')
177178 .addClass('swipe_picker_delete fa-fw fa-trash-can')
178179 .toggleClass('hoverglow', canDeleteSwipe)
179180 .toggleClass('disabled', !canDeleteSwipe)
180181 .each(function () {
@@ -229,11 +230,42 @@ async function openSwipePicker(messageId) {
229230
230231 await renderSwipeList();
231232 });
233+
234+ // Add expand/collapse toggle
235+ const expandCheckboxId = `swipe_picker_expand_${messageId}_${index}`;
236+ const expandCheckbox = document.createElement('input');
237+ expandCheckbox.type = 'checkbox';
238+ expandCheckbox.id = expandCheckboxId;
239+ expandCheckbox.classList.add('swipe_picker_expand_toggle');
240+ block[0].prepend(expandCheckbox);
241+
242+ const expandLabel = document.createElement('label');
243+ expandLabel.htmlFor = expandCheckboxId;
244+ expandLabel.classList.add('swipe_picker_expand_label', 'fa-solid', 'fa-fw', 'fa-chevron-down');
245+ expandLabel.title = t`Expand/Collapse`;
246+ expandLabel.setAttribute('data-i18n', '[title]Expand/Collapse');
247+ expandLabel.addEventListener('click', (event) => event.stopPropagation());
248+
249+ // Add copy button
250+ const copyButton = document.createElement('div');
251+ copyButton.classList.add('swipe_picker_copy', 'fa-solid', 'fa-fw', 'fa-copy');
252+ copyButton.title = t`Copy`;
253+ copyButton.setAttribute('data-i18n', '[title]Copy');
254+ copyButton.addEventListener('click', async (event) => {
255+ event.preventDefault();
256+ event.stopPropagation();
257+ await copyText(swipeText);
258+ toastr.info(t`Copied!`, '', { timeOut: 2000 });
259+ });
260+
261+ // Insert new buttons before the branch button
262+ branchButton.before(expandLabel, copyButton);
263+
232264 template.find('.select_chat_block_filename').text(`#${index + 1}${index === Number(message.swipe_id ?? 0) ? ` ${t`[Current]`}` : ''}`);
233265 template.find('.chat_messages_date').text(sendDate);
234266 template.find('.chat_file_size').text(swipeDetails.length ? `(${swipeDetails[0]}${swipeDetails.length > 1 ? ',' : ')'}` : '');
235267 template.find('.chat_messages_num').text(swipeDetails.length > 1 ? `${swipeDetails.slice(1).join(', ')})` : '');
236268 template.find('.select_chat_block_mes').text(previewText ||? swipeText : t`(empty swipe)`);
237269
238270 block.on('click', () => setSelectedSwipe(index));
239271 block.on('dblclick', async () => {
@@ -269,6 +301,7 @@ async function openSwipePicker(messageId) {
269301 defaultState: String(selectedSwipeId + 1),
270302 tooltip: `1-${message.swipes.length}`,
271303 }],
304+ large: true,
272305 wider: true,
273306 allowVerticalScrolling: true,
274307 onOpen: function () {
public/style.css+42 -0
@@ -4788,6 +4788,48 @@ h5 {
47884788 user-select: none;
47894789}
47904790
4791+.swipe_picker_block .select_chat_block_mes {
4792+ text-align: left;
4793+}
4794+
4795+.swipe_picker_block .select_chat_actions {
4796+ align-items: baseline;
4797+}
4798+
4799+.swipe_picker_expand_toggle {
4800+ display: none !important;
4801+}
4802+
4803+.swipe_picker_block:has(.swipe_picker_expand_toggle:checked) .select_chat_block_mes {
4804+ display: block;
4805+ -webkit-line-clamp: unset;
4806+ line-clamp: unset;
4807+ white-space: pre-wrap;
4808+ overflow-wrap: anywhere;
4809+}
4810+
4811+.swipe_picker_expand_label {
4812+ cursor: pointer;
4813+ opacity: 0.5;
4814+}
4815+
4816+.swipe_picker_expand_label:hover {
4817+ opacity: 1;
4818+}
4819+
4820+.swipe_picker_copy {
4821+ cursor: pointer;
4822+ opacity: 0.5;
4823+}
4824+
4825+.swipe_picker_copy:hover {
4826+ opacity: 1;
4827+}
4828+
4829+.swipe_picker_block:has(.swipe_picker_expand_toggle:checked) .swipe_picker_expand_label {
4830+ transform: rotate(180deg);
4831+}
4832+
47914833.select_chat_block .avatar {
47924834 grid-row: span 2;
47934835}