ChatItem.svelte 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import { goto } from '$app/navigation';
  4. import { onMount, getContext, createEventDispatcher } from 'svelte';
  5. const i18n = getContext('i18n');
  6. const dispatch = createEventDispatcher();
  7. import {
  8. archiveChatById,
  9. cloneChatById,
  10. deleteChatById,
  11. getChatList,
  12. updateChatById
  13. } from '$lib/apis/chats';
  14. import { chatId, chats, mobile, showSidebar } from '$lib/stores';
  15. import ChatMenu from './ChatMenu.svelte';
  16. import ShareChatModal from '$lib/components/chat/ShareChatModal.svelte';
  17. export let chat;
  18. let showShareChatModal = false;
  19. export let selected = false;
  20. let confirmEdit = false;
  21. let confirmDelete = false;
  22. let chatTitle = '';
  23. const editChatTitle = async (id, _title) => {
  24. if (_title === '') {
  25. toast.error($i18n.t('Title cannot be an empty string.'));
  26. } else {
  27. await updateChatById(localStorage.token, id, {
  28. title: _title
  29. });
  30. await chats.set(await getChatList(localStorage.token));
  31. }
  32. };
  33. const deleteChat = async (id) => {
  34. const res = await deleteChatById(localStorage.token, id).catch((error) => {
  35. toast.error(error);
  36. confirmDelete = false;
  37. return null;
  38. });
  39. if (res) {
  40. if ($chatId === id) {
  41. goto('/');
  42. }
  43. await chats.set(await getChatList(localStorage.token));
  44. }
  45. };
  46. const cloneChatHandler = async (id) => {
  47. const res = await cloneChatById(localStorage.token, id).catch((error) => {
  48. toast.error(error);
  49. return null;
  50. });
  51. if (res) {
  52. goto(`/c/${res.id}`);
  53. await chats.set(await getChatList(localStorage.token));
  54. }
  55. };
  56. const archiveChatHandler = async (id) => {
  57. await archiveChatById(localStorage.token, id);
  58. await chats.set(await getChatList(localStorage.token));
  59. };
  60. const focusEdit = async (node: HTMLInputElement) => {
  61. node.focus();
  62. };
  63. </script>
  64. <ShareChatModal bind:show={showShareChatModal} chatId={chat.id} />
  65. <div class=" w-full pr-2 relative group">
  66. {#if confirmEdit}
  67. <div
  68. class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
  69. confirmEdit ||
  70. confirmDelete
  71. ? 'bg-gray-200 dark:bg-gray-900'
  72. : selected
  73. ? 'bg-gray-100 dark:bg-gray-950'
  74. : 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
  75. >
  76. <input
  77. use:focusEdit
  78. bind:value={chatTitle}
  79. class=" bg-transparent w-full outline-none mr-10"
  80. />
  81. </div>
  82. {:else}
  83. <a
  84. class=" w-full flex justify-between rounded-xl px-3 py-2 {chat.id === $chatId ||
  85. confirmEdit ||
  86. confirmDelete
  87. ? 'bg-gray-200 dark:bg-gray-900'
  88. : selected
  89. ? 'bg-gray-100 dark:bg-gray-950'
  90. : ' group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis"
  91. href="/c/{chat.id}"
  92. on:click={() => {
  93. dispatch('select');
  94. if ($mobile) {
  95. showSidebar.set(false);
  96. }
  97. }}
  98. on:dblclick={() => {
  99. chatTitle = chat.title;
  100. confirmEdit = true;
  101. }}
  102. on:mouseover={(e) => {
  103. if (e.shiftKey) {
  104. // Your code here
  105. console.log('hi');
  106. }
  107. }}
  108. on:focus={(e) => {}}
  109. draggable="false"
  110. >
  111. <div class=" flex self-center flex-1 w-full">
  112. <div class=" text-left self-center overflow-hidden w-full h-[20px]">
  113. {chat.title}
  114. </div>
  115. </div>
  116. </a>
  117. {/if}
  118. <div
  119. class="
  120. {chat.id === $chatId || confirmEdit || confirmDelete
  121. ? 'from-gray-200 dark:from-gray-900'
  122. : selected
  123. ? 'from-gray-100 dark:from-gray-950'
  124. : 'invisible group-hover:visible from-gray-100 dark:from-gray-950'}
  125. absolute right-[10px] top-[10px] pr-2 pl-5 bg-gradient-to-l from-80%
  126. to-transparent"
  127. >
  128. {#if confirmEdit}
  129. <div class="flex self-center space-x-1.5 z-10">
  130. <button
  131. class=" self-center dark:hover:text-white transition"
  132. on:click={() => {
  133. editChatTitle(chat.id, chatTitle);
  134. confirmEdit = false;
  135. chatTitle = '';
  136. }}
  137. >
  138. <svg
  139. xmlns="http://www.w3.org/2000/svg"
  140. viewBox="0 0 20 20"
  141. fill="currentColor"
  142. class="w-4 h-4"
  143. >
  144. <path
  145. fill-rule="evenodd"
  146. d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
  147. clip-rule="evenodd"
  148. />
  149. </svg>
  150. </button>
  151. <button
  152. class=" self-center dark:hover:text-white transition"
  153. on:click={() => {
  154. confirmEdit = false;
  155. chatTitle = '';
  156. }}
  157. >
  158. <svg
  159. xmlns="http://www.w3.org/2000/svg"
  160. viewBox="0 0 20 20"
  161. fill="currentColor"
  162. class="w-4 h-4"
  163. >
  164. <path
  165. d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
  166. />
  167. </svg>
  168. </button>
  169. </div>
  170. {:else if confirmDelete}
  171. <div class="flex self-center space-x-1.5 z-10">
  172. <button
  173. class=" self-center dark:hover:text-white transition"
  174. on:click={() => {
  175. deleteChat(chat.id);
  176. }}
  177. >
  178. <svg
  179. xmlns="http://www.w3.org/2000/svg"
  180. viewBox="0 0 20 20"
  181. fill="currentColor"
  182. class="w-4 h-4"
  183. >
  184. <path
  185. fill-rule="evenodd"
  186. d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
  187. clip-rule="evenodd"
  188. />
  189. </svg>
  190. </button>
  191. <button
  192. class=" self-center dark:hover:text-white transition"
  193. on:click={() => {
  194. confirmDelete = false;
  195. }}
  196. >
  197. <svg
  198. xmlns="http://www.w3.org/2000/svg"
  199. viewBox="0 0 20 20"
  200. fill="currentColor"
  201. class="w-4 h-4"
  202. >
  203. <path
  204. d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
  205. />
  206. </svg>
  207. </button>
  208. </div>
  209. {:else}
  210. <div class="flex self-center space-x-1 z-10">
  211. <ChatMenu
  212. chatId={chat.id}
  213. cloneChatHandler={() => {
  214. cloneChatHandler(chat.id);
  215. }}
  216. shareHandler={() => {
  217. showShareChatModal = true;
  218. }}
  219. archiveChatHandler={() => {
  220. archiveChatHandler(chat.id);
  221. }}
  222. renameHandler={() => {
  223. confirmEdit = true;
  224. }}
  225. deleteHandler={() => {
  226. confirmDelete = true;
  227. }}
  228. onClose={() => {
  229. selected = false;
  230. }}
  231. >
  232. <button
  233. aria-label="Chat Menu"
  234. class=" self-center dark:hover:text-white transition"
  235. on:click={() => {
  236. dispatch('select');
  237. }}
  238. >
  239. <svg
  240. xmlns="http://www.w3.org/2000/svg"
  241. viewBox="0 0 16 16"
  242. fill="currentColor"
  243. class="w-4 h-4"
  244. >
  245. <path
  246. d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
  247. />
  248. </svg>
  249. </button>
  250. </ChatMenu>
  251. {#if chat.id === $chatId}
  252. <!-- Shortcut support using "delete-chat-button" id -->
  253. <button
  254. id="delete-chat-button"
  255. class="hidden"
  256. on:click={() => {
  257. confirmDelete = true;
  258. }}
  259. >
  260. <svg
  261. xmlns="http://www.w3.org/2000/svg"
  262. viewBox="0 0 16 16"
  263. fill="currentColor"
  264. class="w-4 h-4"
  265. >
  266. <path
  267. d="M2 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM6.5 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0ZM12.5 6.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
  268. />
  269. </svg>
  270. </button>
  271. {/if}
  272. </div>
  273. {/if}
  274. </div>
  275. </div>