Skip to content

Commit

Permalink
kick/chat(feat): iterable tab-completion (#733)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnatoleAM authored Jun 27, 2023
1 parent 05d7035 commit 9ec884f
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-nightly.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added an option to show stream stats such as latency, resolution, bitrate, etc.
- Added an option to set click actions on the video player (pause/unpause and mute/unmute)
- Added an option to hide player extensions
- Added iterable tab-completion on Kick
- Fixed a user card crash
- Fixed an issue with the EventAPI connection closing on the first initialization
- Fixed an issue that prevented new chatters from appearing in autocompletion
Expand Down
39 changes: 29 additions & 10 deletions src/site/kick.com/modules/chat/ChatAutocomplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

<script setup lang="ts">
import { inject, reactive, ref, toRef, watch, watchEffect } from "vue";
import { useEventListener, useMutationObserver } from "@vueuse/core";
import { useEventListener, useMagicKeys, useMutationObserver } from "@vueuse/core";
import { useStore } from "@/store/main";
import { useChannelContext } from "@/composable/channel/useChannelContext";
import { useChatEmotes } from "@/composable/chat/useChatEmotes";
Expand Down Expand Up @@ -127,32 +127,50 @@ function insertAtEnd(value: string): void {
colon.active = false;
}
function handleTab(n: Text, sel: Selection): void {
const tab = reactive({
matches: [] as SevenTV.ActiveEmote[],
index: -1,
cursorLocation: -1,
});
function handleTab(n: Text, sel: Selection, back = false): void {
const { anchorOffset, focusOffset } = sel;
const start = Math.min(anchorOffset, focusOffset);
const end = Math.max(anchorOffset, focusOffset);
const text = n.textContent ?? "";
const tokenStart = text.substring(0, focusOffset).lastIndexOf(" ", focusOffset);
if (sel.anchorOffset != tab.cursorLocation) {
tab.matches = [];
tab.index = -1;
tab.cursorLocation = sel.anchorOffset;
}
const searchWord = text.substring(tokenStart + 1, start);
if (!searchWord) return;
if (tab.matches.length === 0) {
tab.matches = [...Object.values(emotes.active), ...Object.values(cosmetics.emotes)].filter(
(ae) => ae.name.toLowerCase().startsWith(searchWord.toLowerCase()) && ae.provider !== "EMOJI",
);
if (tab.matches.length === 0 || tab.matches[0].provider === "EMOJI") return;
}
const emote = [...Object.values(emotes.active), ...Object.values(cosmetics.emotes)].find((ae) =>
ae.name.toLowerCase().startsWith(searchWord.toLowerCase()),
);
if (!emote || emote.provider === "EMOJI") return;
tab.index = (back ? tab.index - 1 : tab.index + 1) % tab.matches.length;
if (tab.index < 0) tab.index = tab.matches.length - 1;
const textNode = document.createTextNode(`${emote.name} `);
const selectedToken = tab.matches[tab.index];
const spaceAtEnd = end === n.length;
const textNode = document.createTextNode(`${selectedToken.name}${spaceAtEnd ? "" : " "}`);
const range = document.createRange();
range.setStart(n, start - searchWord.length);
range.setEnd(n, end);
range.deleteContents();
range.insertNode(textNode);
sel.collapse(textNode, emote.name.length + 1);
sel.collapse(textNode, selectedToken.name.length + (spaceAtEnd ? 0 : 1));
tab.cursorLocation = sel.focusOffset;
}
// add message to history using unshift
Expand All @@ -168,6 +186,7 @@ function handleMessageSend(text: string) {
watch(currentMessage, handleInputChange);
const { shift: isShiftPressed } = useMagicKeys();
useEventListener(
inputEl,
"keydown",
Expand All @@ -182,7 +201,7 @@ useEventListener(
case "Enter":
if (ev.key === "Tab" && n && n.nodeName === "#text") {
ev.preventDefault();
handleTab(n, sel);
handleTab(n, sel, isShiftPressed.value);
}
if (!colon.active) break;
Expand Down

0 comments on commit 9ec884f

Please sign in to comment.