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