feat: add import summary filtering and improve search in HypaV3
- Add important summary filtering button - Improve search navigation with focus-based results - Add mobile-friendly previous/next search buttons
This commit is contained in:
@@ -796,7 +796,7 @@ export const languageChinese = {
|
||||
"convertSuccessMessage": "成功将 HypaV2 数据转换为 V3",
|
||||
"convertErrorMessage": "将 HypaV2 数据转换为 V3 失败:{0}",
|
||||
"noSummariesLabel": "尚无总结",
|
||||
"searchPlaceholder": "输入 #N、ID 或搜索查询",
|
||||
"searchPlaceholder": "输入 #N、ID 或搜索关键词",
|
||||
"summaryNumberLabel": "总结 #{0}",
|
||||
"deleteAfterConfirmMessage": "删除此后的所有总结?",
|
||||
"deleteAfterConfirmSecondMessage": "此操作无法撤销。您确定吗?",
|
||||
|
||||
@@ -867,7 +867,7 @@ export const languageEnglish = {
|
||||
convertSuccessMessage: "Successfully converted HypaV2 data to V3",
|
||||
convertErrorMessage: "Failed to convert HypaV2 data to V3: {0}",
|
||||
noSummariesLabel: "No summaries yet",
|
||||
searchPlaceholder: "Enter #N, ID, or search query",
|
||||
searchPlaceholder: "Enter #N, ID, or query",
|
||||
summaryNumberLabel: "Summary #{0}",
|
||||
deleteAfterConfirmMessage: "Delete all summaries after this one?",
|
||||
deleteAfterConfirmSecondMessage: "This action cannot be undone. Are you really sure?",
|
||||
|
||||
@@ -705,7 +705,7 @@ export const languageSpanish = {
|
||||
"convertSuccessMessage": "Datos de HypaV2 convertidos exitosamente a V3",
|
||||
"convertErrorMessage": "Error al convertir datos de HypaV2 a V3: {0}",
|
||||
"noSummariesLabel": "Aún no hay resúmenes",
|
||||
"searchPlaceholder": "Ingrese #N, ID o consulta de búsqueda",
|
||||
"searchPlaceholder": "Ingrese #N, ID o búsqueda",
|
||||
"summaryNumberLabel": "Resumen #{0}",
|
||||
"deleteAfterConfirmMessage": "¿Eliminar todos los resúmenes después de este?",
|
||||
"deleteAfterConfirmSecondMessage": "Esta acción no se puede deshacer. ¿Está realmente seguro?",
|
||||
|
||||
@@ -792,7 +792,7 @@ export const languageKorean = {
|
||||
"convertSuccessMessage": "HypaV2 데이터를 V3로 성공적으로 변환했습니다",
|
||||
"convertErrorMessage": "HypaV2 데이터를 V3로 변환하는데 실패했습니다: {0}",
|
||||
"noSummariesLabel": "아직 요약이 없습니다",
|
||||
"searchPlaceholder": "#N, ID 또는 검색어를 입력하세요",
|
||||
"searchPlaceholder": "#N, ID 또는 검색어 입력",
|
||||
"summaryNumberLabel": "요약 #{0}",
|
||||
"deleteAfterConfirmMessage": "이 요약 이후의 모든 요약을 삭제하시겠습니까?",
|
||||
"deleteAfterConfirmSecondMessage": "이 작업은 되돌릴 수 없습니다. 정말 삭제하시겠습니까?",
|
||||
|
||||
@@ -434,7 +434,7 @@ export const LanguageVietnamese = {
|
||||
"convertSuccessMessage": "Đã chuyển đổi thành công dữ liệu HypaV2 sang V3",
|
||||
"convertErrorMessage": "Chuyển đổi dữ liệu HypaV2 sang V3 thất bại: {0}",
|
||||
"noSummariesLabel": "Chưa có tóm tắt nào",
|
||||
"searchPlaceholder": "Nhập #N, ID, hoặc từ khóa tìm kiếm",
|
||||
"searchPlaceholder": "Nhập #N, ID hoặc từ khóa",
|
||||
"summaryNumberLabel": "Tóm tắt #{0}",
|
||||
"deleteAfterConfirmMessage": "Xóa tất cả các tóm tắt sau tóm tắt này?",
|
||||
"deleteAfterConfirmSecondMessage": "Hành động này không thể hoàn tác. Bạn có chắc chắn không?",
|
||||
|
||||
@@ -829,7 +829,7 @@ export const languageChineseTraditional = {
|
||||
"convertSuccessMessage": "成功將 HypaV2 數據轉換為 V3",
|
||||
"convertErrorMessage": "無法將 HypaV2 數據轉換為 V3:{0}",
|
||||
"noSummariesLabel": "尚無摘要",
|
||||
"searchPlaceholder": "輸入 #N、ID 或搜索查詢",
|
||||
"searchPlaceholder": "輸入 #N、ID 或搜尋關鍵字",
|
||||
"summaryNumberLabel": "摘要 #{0}",
|
||||
"deleteAfterConfirmMessage": "刪除此摘要之後的所有摘要?",
|
||||
"deleteAfterConfirmSecondMessage": "此操作無法撤銷。您確定要這樣做嗎?",
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
SettingsIcon,
|
||||
Trash2Icon,
|
||||
XIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
LanguagesIcon,
|
||||
StarIcon,
|
||||
RefreshCw,
|
||||
@@ -51,20 +53,30 @@
|
||||
translationRef: HTMLTextAreaElement;
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
element: HTMLElement;
|
||||
matchType: "chatMemo" | "summary";
|
||||
summaryPosition?: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
class SummarySearchResult {
|
||||
constructor(
|
||||
public summaryIndex: number,
|
||||
public start: number,
|
||||
public end: number
|
||||
) {}
|
||||
}
|
||||
|
||||
class ChatMemoSearchResult {
|
||||
constructor(
|
||||
public summaryIndex: number,
|
||||
public memoIndex: number
|
||||
) {}
|
||||
}
|
||||
|
||||
type SearchResult = SummarySearchResult | ChatMemoSearchResult;
|
||||
|
||||
interface SearchUI {
|
||||
ref: HTMLInputElement;
|
||||
query: string;
|
||||
currentIndex: number;
|
||||
results: SearchResult[];
|
||||
currentResultIndex: number;
|
||||
requestedSearchFromIndex: number;
|
||||
isNavigating: boolean;
|
||||
}
|
||||
|
||||
const hypaV3DataState = $derived(
|
||||
@@ -76,6 +88,7 @@
|
||||
let summaryUIStates = $state<SummaryUI[]>([]);
|
||||
let expandedMessageUIState = $state<ExpandedMessageUI>(null);
|
||||
let searchUIState = $state<SearchUI>(null);
|
||||
let showImportantOnly = $state(false);
|
||||
|
||||
$effect.pre(() => {
|
||||
summaryUIStates = hypaV3DataState.summaries.map((summary) => ({
|
||||
@@ -111,8 +124,10 @@
|
||||
searchUIState = {
|
||||
ref: null,
|
||||
query: "",
|
||||
currentIndex: -1,
|
||||
results: [],
|
||||
currentResultIndex: -1,
|
||||
requestedSearchFromIndex: -1,
|
||||
isNavigating: false,
|
||||
};
|
||||
|
||||
// Focus on search element after it's rendered
|
||||
@@ -135,122 +150,187 @@
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault(); // Prevent event default action
|
||||
e.preventDefault?.(); // Prevent event default action
|
||||
|
||||
const query = searchUIState.query.trim();
|
||||
|
||||
if (!query) return;
|
||||
|
||||
// When received a new query
|
||||
if (searchUIState.currentResultIndex === -1) {
|
||||
const results = generateSearchResults(query);
|
||||
|
||||
if (results.length === 0) return;
|
||||
|
||||
searchUIState.results = results;
|
||||
}
|
||||
|
||||
const nextResult = getNextSearchResult(e.shiftKey);
|
||||
|
||||
if (nextResult) {
|
||||
navigateToSearchResult(nextResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateSearchResults(query: string): SearchResult[] {
|
||||
const results: SearchResult[] = [];
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
|
||||
// Search summary index
|
||||
if (query.match(/^#\d+$/)) {
|
||||
const summaryNumber = parseInt(query.substring(1)) - 1;
|
||||
|
||||
if (
|
||||
summaryNumber >= 0 &&
|
||||
summaryNumber < hypaV3DataState.summaries.length
|
||||
summaryNumber < hypaV3DataState.summaries.length &&
|
||||
(!showImportantOnly ||
|
||||
hypaV3DataState.summaries[summaryNumber].isImportant)
|
||||
) {
|
||||
summaryUIStates[summaryNumber].originalRef.scrollIntoView({
|
||||
behavior: "instant",
|
||||
block: "center",
|
||||
});
|
||||
results.push(new SummarySearchResult(summaryNumber, 0, 0));
|
||||
}
|
||||
|
||||
return;
|
||||
return results;
|
||||
}
|
||||
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (searchUIState.currentIndex === -1) {
|
||||
const results: SearchResult[] = [];
|
||||
|
||||
if (isGuidLike(query)) {
|
||||
// Search chatMemo
|
||||
summaryUIStates.forEach((summaryUI) => {
|
||||
summaryUI.chatMemoRefs.forEach((buttonRef) => {
|
||||
summaryUIStates.forEach((summaryUI, summaryIndex) => {
|
||||
if (
|
||||
!showImportantOnly ||
|
||||
hypaV3DataState.summaries[summaryIndex].isImportant
|
||||
) {
|
||||
summaryUI.chatMemoRefs.forEach((buttonRef, memoIndex) => {
|
||||
const buttonText = buttonRef.textContent?.toLowerCase() || "";
|
||||
|
||||
if (buttonText.includes(normalizedQuery)) {
|
||||
results.push({
|
||||
element: buttonRef as HTMLButtonElement,
|
||||
matchType: "chatMemo",
|
||||
});
|
||||
results.push(new ChatMemoSearchResult(summaryIndex, memoIndex));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Search summary
|
||||
summaryUIStates.forEach((summaryUI) => {
|
||||
summaryUIStates.forEach((summaryUI, summaryIndex) => {
|
||||
if (
|
||||
!showImportantOnly ||
|
||||
hypaV3DataState.summaries[summaryIndex].isImportant
|
||||
) {
|
||||
const textAreaText = summaryUI.originalRef.value?.toLowerCase();
|
||||
|
||||
let pos = -1;
|
||||
|
||||
while (
|
||||
(pos = textAreaText.indexOf(normalizedQuery, pos + 1)) !== -1
|
||||
) {
|
||||
results.push({
|
||||
element: summaryUI.originalRef as HTMLTextAreaElement,
|
||||
matchType: "summary",
|
||||
summaryPosition: {
|
||||
start: pos,
|
||||
end: pos + normalizedQuery.length,
|
||||
},
|
||||
});
|
||||
results.push(
|
||||
new SummarySearchResult(
|
||||
summaryIndex,
|
||||
pos,
|
||||
pos + normalizedQuery.length
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
searchUIState.results = results;
|
||||
return results;
|
||||
}
|
||||
|
||||
if (searchUIState.results.length === 0) return;
|
||||
function isGuidLike(str: string): boolean {
|
||||
const strTrimed = str.trim();
|
||||
|
||||
// Move to next result
|
||||
searchUIState.currentIndex =
|
||||
(searchUIState.currentIndex + 1) % searchUIState.results.length;
|
||||
// Exclude too short inputs
|
||||
if (strTrimed.length < 4) return false;
|
||||
|
||||
const result = searchUIState.results[searchUIState.currentIndex];
|
||||
return /^[0-9a-f]{4,12}(-[0-9a-f]{4,12}){0,4}-?$/i.test(strTrimed);
|
||||
}
|
||||
|
||||
function getNextSearchResult(backward: boolean): SearchResult | null {
|
||||
if (!searchUIState || searchUIState.results.length === 0) return null;
|
||||
|
||||
let nextIndex: number;
|
||||
|
||||
if (searchUIState.requestedSearchFromIndex !== -1) {
|
||||
const fromSummaryIndex = searchUIState.requestedSearchFromIndex;
|
||||
|
||||
nextIndex = backward
|
||||
? searchUIState.results.findLastIndex(
|
||||
(r) => r.summaryIndex <= fromSummaryIndex
|
||||
)
|
||||
: searchUIState.results.findIndex(
|
||||
(r) => r.summaryIndex >= fromSummaryIndex
|
||||
);
|
||||
|
||||
if (nextIndex === -1) {
|
||||
nextIndex = backward ? searchUIState.results.length - 1 : 0;
|
||||
}
|
||||
|
||||
searchUIState.requestedSearchFromIndex = -1;
|
||||
} else {
|
||||
const delta = backward ? -1 : 1;
|
||||
|
||||
nextIndex =
|
||||
(searchUIState.currentResultIndex +
|
||||
delta +
|
||||
searchUIState.results.length) %
|
||||
searchUIState.results.length;
|
||||
}
|
||||
|
||||
searchUIState.currentResultIndex = nextIndex;
|
||||
return searchUIState.results[nextIndex];
|
||||
}
|
||||
|
||||
function navigateToSearchResult(result: SearchResult) {
|
||||
searchUIState.isNavigating = true;
|
||||
|
||||
if (result instanceof SummarySearchResult) {
|
||||
const textarea = summaryUIStates[result.summaryIndex].originalRef;
|
||||
|
||||
// Scroll to element
|
||||
result.element.scrollIntoView({
|
||||
textarea.scrollIntoView({
|
||||
behavior: "instant",
|
||||
block: "center",
|
||||
});
|
||||
|
||||
if (result.matchType === "chatMemo") {
|
||||
// Highlight chatMemo result
|
||||
result.element.classList.add("ring-2", "ring-zinc-500");
|
||||
if (result.start === result.end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove highlight after a short delay
|
||||
window.setTimeout(() => {
|
||||
result.element.classList.remove("ring-2", "ring-zinc-500");
|
||||
}, 1000);
|
||||
} else {
|
||||
// Handle summary text selection
|
||||
const textarea = result.element as HTMLTextAreaElement;
|
||||
|
||||
// Make readonly temporarily
|
||||
textarea.readOnly = true;
|
||||
|
||||
// Select query
|
||||
textarea.setSelectionRange(
|
||||
result.summaryPosition.start,
|
||||
result.summaryPosition.end
|
||||
);
|
||||
|
||||
textarea.focus();
|
||||
// Scroll to query
|
||||
textarea.setSelectionRange(result.start, result.end);
|
||||
scrollToSelection(textarea);
|
||||
|
||||
// This only works on firefox
|
||||
//textarea.scrollTop = textarea.scrollHeight; // Scroll to the bottom
|
||||
//textarea.blur(); // Collapse selection
|
||||
//textarea.focus(); // This scrolls the textarea
|
||||
|
||||
// Highlight textarea
|
||||
// Highlight query on desktop environment
|
||||
if (!("ontouchend" in window)) {
|
||||
// Make readonly temporarily
|
||||
textarea.readOnly = true;
|
||||
textarea.focus();
|
||||
window.setTimeout(() => {
|
||||
searchUIState.ref.focus(); // Restore focus to search bar
|
||||
textarea.readOnly = false; // Remove readonly after focus moved
|
||||
}, 300);
|
||||
}
|
||||
} else {
|
||||
const button =
|
||||
summaryUIStates[result.summaryIndex].chatMemoRefs[result.memoIndex];
|
||||
|
||||
// Scroll to element
|
||||
button.scrollIntoView({
|
||||
behavior: "instant",
|
||||
block: "center",
|
||||
});
|
||||
|
||||
// Highlight chatMemo
|
||||
button.classList.add("ring-2", "ring-zinc-500");
|
||||
|
||||
// Remove highlight after a short delay
|
||||
window.setTimeout(() => {
|
||||
button.classList.remove("ring-2", "ring-zinc-500");
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
searchUIState.isNavigating = false;
|
||||
}
|
||||
|
||||
function scrollToSelection(textarea: HTMLTextAreaElement) {
|
||||
@@ -287,15 +367,6 @@
|
||||
textarea.scrollTop = selectionTop - textarea.clientHeight / 2;
|
||||
}
|
||||
|
||||
function isGuidLike(str: string): boolean {
|
||||
const strTrimed = str.trim();
|
||||
|
||||
// Exclude too short inputs
|
||||
if (strTrimed.length < 4) return false;
|
||||
|
||||
return /^[0-9a-f]{4,12}(-[0-9a-f]{4,12}){0,4}-?$/i.test(strTrimed);
|
||||
}
|
||||
|
||||
async function toggleTranslate(
|
||||
summaryIndex: number,
|
||||
regenerate?: boolean
|
||||
@@ -767,14 +838,35 @@
|
||||
<!-- Search Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={async () => toggleSearch()}
|
||||
>
|
||||
<SearchIcon class="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<!-- Filter Important Summary Button -->
|
||||
<button
|
||||
class="p-2 transition-colors {showImportantOnly
|
||||
? 'text-yellow-400 hover:text-yellow-300'
|
||||
: 'text-zinc-400 hover:text-zinc-200'}"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
if (searchUIState) {
|
||||
searchUIState.query = "";
|
||||
searchUIState.results = [];
|
||||
searchUIState.currentResultIndex = -1;
|
||||
}
|
||||
|
||||
showImportantOnly = !showImportantOnly;
|
||||
}}
|
||||
>
|
||||
<StarIcon class="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<!-- Settings Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
alertStore.set({
|
||||
type: "none",
|
||||
@@ -791,6 +883,7 @@
|
||||
<!-- Reset Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={async () => {
|
||||
if (
|
||||
await alertConfirmTwice(
|
||||
@@ -815,6 +908,7 @@
|
||||
<!-- Close Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
alertStore.set({
|
||||
type: "none",
|
||||
@@ -828,7 +922,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Container -->
|
||||
<div class="flex flex-col gap-2 sm:gap-4 overflow-y-auto">
|
||||
<div class="flex flex-col gap-2 sm:gap-4 overflow-y-auto" tabindex="-1">
|
||||
{#if hypaV3DataState.summaries.length === 0}
|
||||
<!-- Conversion Section -->
|
||||
{#if isHypaV2ConversionPossible()}
|
||||
@@ -841,6 +935,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="my-1 sm:my-2 px-4 py-2 rounded-md text-zinc-300 font-semibold bg-zinc-700 hover:bg-zinc-500 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={async () => {
|
||||
const conversionResult = convertHypaV2ToV3();
|
||||
|
||||
@@ -889,8 +984,8 @@
|
||||
bind:value={searchUIState.query}
|
||||
oninput={() => {
|
||||
if (searchUIState) {
|
||||
searchUIState.currentIndex = -1;
|
||||
searchUIState.results = [];
|
||||
searchUIState.currentResultIndex = -1;
|
||||
}
|
||||
}}
|
||||
onkeydown={(e) => onSearch(e)}
|
||||
@@ -901,16 +996,32 @@
|
||||
<span
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 px-1.5 sm:px-3 py-1 sm:py-2 rounded text-sm font-semibold text-zinc-100 bg-zinc-700/65"
|
||||
>
|
||||
{searchUIState.currentIndex + 1}/{searchUIState.results
|
||||
.length}
|
||||
{searchUIState.currentResultIndex + 1}/{searchUIState
|
||||
.results.length}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Previous Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
onclick={async () => toggleSearch()}
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
onSearch({ shiftKey: true, key: "Enter" } as KeyboardEvent);
|
||||
}}
|
||||
>
|
||||
<XIcon class="w-6 h-6" />
|
||||
<ChevronUpIcon class="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<!-- Next Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
onSearch({ key: "Enter" } as KeyboardEvent);
|
||||
}}
|
||||
>
|
||||
<ChevronDownIcon class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -918,6 +1029,7 @@
|
||||
|
||||
<!-- Summaries List -->
|
||||
{#each hypaV3DataState.summaries as summary, i}
|
||||
{#if !showImportantOnly || summary.isImportant}
|
||||
{#if summaryUIStates[i]}
|
||||
<!-- Summary Item -->
|
||||
<div
|
||||
@@ -936,6 +1048,7 @@
|
||||
<!-- Translate Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
use:handleDualAction={{
|
||||
onMainAction: () => toggleTranslate(i, false),
|
||||
onAlternativeAction: () => toggleTranslate(i, true),
|
||||
@@ -946,9 +1059,10 @@
|
||||
|
||||
<!-- Important Button -->
|
||||
<button
|
||||
class="p-2 hover:text-zinc-200 transition-colors {summary.isImportant
|
||||
? 'text-yellow-400'
|
||||
: 'text-zinc-400'}"
|
||||
class="p-2 transition-colors {summary.isImportant
|
||||
? 'text-yellow-400 hover:text-yellow-300'
|
||||
: 'text-zinc-400 hover:text-zinc-200'}"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
summary.isImportant = !summary.isImportant;
|
||||
}}
|
||||
@@ -959,8 +1073,9 @@
|
||||
<!-- Reroll Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
onclick={async () => await toggleReroll(i)}
|
||||
tabindex="-1"
|
||||
disabled={!isRerollable(i)}
|
||||
onclick={async () => await toggleReroll(i)}
|
||||
>
|
||||
<RefreshCw class="w-4 h-4" />
|
||||
</button>
|
||||
@@ -968,6 +1083,7 @@
|
||||
<!-- Delete After Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={async () => {
|
||||
if (
|
||||
await alertConfirmTwice(
|
||||
@@ -992,6 +1108,11 @@
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900"
|
||||
bind:this={summaryUIStates[i].originalRef}
|
||||
bind:value={summary.text}
|
||||
onfocus={() => {
|
||||
if (searchUIState && !searchUIState.isNavigating) {
|
||||
searchUIState.requestedSearchFromIndex = i;
|
||||
}
|
||||
}}
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
@@ -1004,10 +1125,10 @@
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
readonly
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
|
||||
bind:this={summaryUIStates[i].translationRef}
|
||||
readonly
|
||||
tabindex="-1"
|
||||
bind:this={summaryUIStates[i].translationRef}
|
||||
value={summaryUIStates[i].translation}
|
||||
></textarea>
|
||||
</div>
|
||||
@@ -1024,8 +1145,10 @@
|
||||
<!-- Translate Rerolled Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
use:handleDualAction={{
|
||||
onMainAction: () => toggleTranslateRerolled(i, false),
|
||||
onMainAction: () =>
|
||||
toggleTranslateRerolled(i, false),
|
||||
onAlternativeAction: () =>
|
||||
toggleTranslateRerolled(i, true),
|
||||
}}
|
||||
@@ -1036,6 +1159,7 @@
|
||||
<!-- Cancel Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
summaryUIStates[i].rerolledText = null;
|
||||
summaryUIStates[i].rerolledTranslation = null;
|
||||
@@ -1047,6 +1171,7 @@
|
||||
<!-- Apply Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-rose-300 transition-colors"
|
||||
tabindex="-1"
|
||||
onclick={() => {
|
||||
summary.text = summaryUIStates[i].rerolledText!;
|
||||
summaryUIStates[i].translation = null;
|
||||
@@ -1064,6 +1189,7 @@
|
||||
<div class="mt-2 sm:mt-4">
|
||||
<textarea
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none focus:ring-2 focus:ring-zinc-500 transition-colors text-zinc-200 bg-zinc-900"
|
||||
tabindex="-1"
|
||||
bind:value={summaryUIStates[i].rerolledText}
|
||||
>
|
||||
</textarea>
|
||||
@@ -1077,10 +1203,10 @@
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
readonly
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
|
||||
bind:this={summaryUIStates[i].rerolledTranslationRef}
|
||||
readonly
|
||||
tabindex="-1"
|
||||
bind:this={summaryUIStates[i].rerolledTranslationRef}
|
||||
value={summaryUIStates[i].rerolledTranslation}
|
||||
></textarea>
|
||||
</div>
|
||||
@@ -1101,6 +1227,7 @@
|
||||
<!-- Translate Message Button -->
|
||||
<button
|
||||
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
|
||||
tabindex="-1"
|
||||
use:handleDualAction={{
|
||||
onMainAction: () =>
|
||||
toggleTranslateExpandedMessage(false),
|
||||
@@ -1124,6 +1251,7 @@
|
||||
)
|
||||
? 'ring-2 ring-zinc-500'
|
||||
: ''}"
|
||||
tabindex="-1"
|
||||
bind:this={summaryUIStates[i].chatMemoRefs[memoIndex]}
|
||||
onclick={() => toggleExpandMessage(i, chatMemo)}
|
||||
>
|
||||
@@ -1150,8 +1278,9 @@
|
||||
|
||||
<!-- Content -->
|
||||
<textarea
|
||||
readonly
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
|
||||
readonly
|
||||
tabindex="-1"
|
||||
value={expandedMessage.data}
|
||||
></textarea>
|
||||
{:else}
|
||||
@@ -1178,10 +1307,10 @@
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
readonly
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-vertical rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
|
||||
bind:this={expandedMessageUIState.translationRef}
|
||||
readonly
|
||||
tabindex="-1"
|
||||
bind:this={expandedMessageUIState.translationRef}
|
||||
value={expandedMessageUIState.translation}
|
||||
></textarea>
|
||||
</div>
|
||||
@@ -1189,6 +1318,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<!-- Next Summarization Target -->
|
||||
@@ -1209,8 +1339,8 @@
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
readonly
|
||||
class="p-2 sm:p-4 w-full min-h-40 sm:min-h-56 resize-none overflow-y-auto rounded border border-zinc-700 focus:outline-none transition-colors text-zinc-200 bg-zinc-900"
|
||||
readonly
|
||||
value={nextMessage.data}
|
||||
></textarea>
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user