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 -5Showing whitespace changes
public/index.html+1 -1
@@ -6733,7 +6733,7 @@
6733 <small class="chat_file_size select_chat_block_filename_item"></small>6733 <small class="chat_file_size select_chat_block_filename_item"></small>
6734 <small class="chat_messages_num select_chat_block_filename_item"></small>6734 <small class="chat_messages_num select_chat_block_filename_item"></small>
6735 </div>6735 </div>
6736 <div class="flex-container gap10px">6736 <div class="select_chat_actions flex-container gap10px">
6737 <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>6737 <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>
6738 <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>6738 <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>
6739 <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>6739 <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';
4import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';4import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
5import { power_user } from './power-user.js';5import { power_user } from './power-user.js';
6import { getTokenCountAsync } from './tokenizers.js';6import { getTokenCountAsync } from './tokenizers.js';
7import { clamp, timestampToMoment } from './utils.js';7import { clamp, copyText, timestampToMoment } from './utils.js';
8import { chat, deleteSwipe, ensureSwipes, isMessageSwipeable, isSwipingAllowed, swipe, syncMesToSwipe } from '/script.js';8import { chat, deleteSwipe, ensureSwipes, isMessageSwipeable, isSwipingAllowed, swipe, syncMesToSwipe } from '/script.js';
99
10/**10/**
@@ -132,6 +132,7 @@ async function openSwipePicker(messageId) {
132 const template = $('#past_chat_template .select_chat_block_wrapper').clone();132 const template = $('#past_chat_template .select_chat_block_wrapper').clone();
133 const block = template.find('.select_chat_block');133 const block = template.find('.select_chat_block');
134 block.removeClass('select_chat_block').addClass('swipe_picker_block');134 block.removeClass('select_chat_block').addClass('swipe_picker_block');
135 block.find('.select_chat_actions').removeClass('gap10px');
135 const branchButton = template.find('.exportRawChatButton');136 const branchButton = template.find('.exportRawChatButton');
136 const deleteButton = template.find('.PastChat_cross');137 const deleteButton = template.find('.PastChat_cross');
137 const swipeInfo = Array.isArray(message.swipe_info) ? message.swipe_info[index] : null;138 const swipeInfo = Array.isArray(message.swipe_info) ? message.swipe_info[index] : null;
@@ -162,7 +163,7 @@ async function openSwipePicker(messageId) {
162 'data-i18n': '[title]Create Branch',163 'data-i18n': '[title]Create Branch',
163 })164 })
164 .removeClass('exportRawChatButton fa-solid fa-file-export')165 .removeClass('exportRawChatButton fa-solid fa-file-export')
165 .addClass('swipe_picker_branch mes_button fa-regular fa-code-branch')166 .addClass('swipe_picker_branch mes_button fa-fw fa-regular fa-code-branch')
166 .on('click', async (event) => {167 .on('click', async (event) => {
167 event.preventDefault();168 event.preventDefault();
168 event.stopPropagation();169 event.stopPropagation();
@@ -174,7 +175,7 @@ async function openSwipePicker(messageId) {
174 .removeAttr('file_name')175 .removeAttr('file_name')
175 .attr('aria-disabled', String(!canDeleteSwipe))176 .attr('aria-disabled', String(!canDeleteSwipe))
176 .removeClass('fa-skull')177 .removeClass('fa-skull')
177 .addClass('swipe_picker_delete fa-trash-can')178 .addClass('swipe_picker_delete fa-fw fa-trash-can')
178 .toggleClass('hoverglow', canDeleteSwipe)179 .toggleClass('hoverglow', canDeleteSwipe)
179 .toggleClass('disabled', !canDeleteSwipe)180 .toggleClass('disabled', !canDeleteSwipe)
180 .each(function () {181 .each(function () {
@@ -229,11 +230,42 @@ async function openSwipePicker(messageId) {
229230
230 await renderSwipeList();231 await renderSwipeList();
231 });232 });
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
232 template.find('.select_chat_block_filename').text(`#${index + 1}${index === Number(message.swipe_id ?? 0) ? ` ${t`[Current]`}` : ''}`);264 template.find('.select_chat_block_filename').text(`#${index + 1}${index === Number(message.swipe_id ?? 0) ? ` ${t`[Current]`}` : ''}`);
233 template.find('.chat_messages_date').text(sendDate);265 template.find('.chat_messages_date').text(sendDate);
234 template.find('.chat_file_size').text(swipeDetails.length ? `(${swipeDetails[0]}${swipeDetails.length > 1 ? ',' : ')'}` : '');266 template.find('.chat_file_size').text(swipeDetails.length ? `(${swipeDetails[0]}${swipeDetails.length > 1 ? ',' : ')'}` : '');
235 template.find('.chat_messages_num').text(swipeDetails.length > 1 ? `${swipeDetails.slice(1).join(', ')})` : '');267 template.find('.chat_messages_num').text(swipeDetails.length > 1 ? `${swipeDetails.slice(1).join(', ')})` : '');
236 template.find('.select_chat_block_mes').text(previewText || t`(empty swipe)`);268 template.find('.select_chat_block_mes').text(previewText ? swipeText : t`(empty swipe)`);
237269
238 block.on('click', () => setSelectedSwipe(index));270 block.on('click', () => setSelectedSwipe(index));
239 block.on('dblclick', async () => {271 block.on('dblclick', async () => {
@@ -269,6 +301,7 @@ async function openSwipePicker(messageId) {
269 defaultState: String(selectedSwipeId + 1),301 defaultState: String(selectedSwipeId + 1),
270 tooltip: `1-${message.swipes.length}`,302 tooltip: `1-${message.swipes.length}`,
271 }],303 }],
304 large: true,
272 wider: true,305 wider: true,
273 allowVerticalScrolling: true,306 allowVerticalScrolling: true,
274 onOpen: function () {307 onOpen: function () {
public/style.css+42 -0
@@ -4788,6 +4788,48 @@ h5 {
4788 user-select: none;4788 user-select: none;
4789}4789}
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
4791.select_chat_block .avatar {4833.select_chat_block .avatar {
4792 grid-row: span 2;4834 grid-row: span 2;
4793}4835}