Skip to content

Commit

Permalink
Merge branch 'dev' into feat/highlight-by-username
Browse files Browse the repository at this point in the history
  • Loading branch information
Excellify authored Sep 30, 2024
2 parents 9026c31 + 27d14f3 commit 494d868
Show file tree
Hide file tree
Showing 52 changed files with 1,563 additions and 754 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG-nightly.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
### 3.1.1 4000
### 3.1.2.1000

- Implement support for the new Kick site
- Added option to settings to hide Stories from the sidebar
- Fixed settings to hide recommended channels and viewers also watch channels
- Fixed an issue where history navigation is accidentally triggered during IME composition
- Added option to highlight messages of specific usernames
- Added option to highlight messages based on badges
- Added button to user card to toggle highlighting for a users messages

### 3.1.1 3000
### 3.1.1.3000

- Added support for animated FFZ emotes
- Added option to hide whispers
Expand Down
14 changes: 11 additions & 3 deletions src/app/chat/Badge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:alt="alt"
:style="{
backgroundColor,
borderRadius,
}"
@mouseenter="show(imgRef)"
@mouseleave="hide()"
Expand All @@ -20,16 +21,19 @@ import BadgeTooltip from "./BadgeTooltip.vue";
const props = defineProps<{
alt: string;
type: "twitch" | "app";
badge: Twitch.ChatBadge | SevenTV.Cosmetic<"BADGE">;
type: "twitch" | "picture" | "app";
badge: Twitch.ChatBadge | Twitch.SharedChat | SevenTV.Cosmetic<"BADGE">;
}>();
const backgroundColor = ref("");
const borderRadius = ref("");
const srcset = {
twitch: (badge: Twitch.ChatBadge) => `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
picture: (badge: Twitch.SharedChat) =>
`${badge.profileImageURL.slice(0, -9)}28x28.png 1.6x, ${badge.profileImageURL.slice(0, -9)}70x70.png 3.8x`,
app: (badge: SevenTV.Cosmetic<"BADGE">) =>
badge.data.host.files.map((fi, i) => `https:${badge.data.host.url}/${fi.name} ${i + 1}x`).join(", "),
}[props.type](props.badge as SevenTV.Cosmetic<"BADGE"> & Twitch.ChatBadge);
}[props.type](props.badge as SevenTV.Cosmetic<"BADGE"> & Twitch.SharedChat & Twitch.ChatBadge);
const imgRef = ref<HTMLElement>();
Expand All @@ -44,6 +48,10 @@ function isApp(badge: typeof props.badge): badge is SevenTV.Cosmetic<"BADGE"> {
if (isApp(props.badge)) {
backgroundColor.value = props.badge.data.backgroundColor ?? "";
}
if (typeof props.badge == "string") {
borderRadius.value = "25%";
}
</script>

<style scoped lang="scss">
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/UserMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<UserTag
v-if="msg.author && !hideAuthor"
:user="msg.author"
:source-data="msg.sourceData"
:badges="msg.badges"
:msg-id="msg.sym"
@open-native-card="openViewerCard($event, msg.author.username, msg.id)"
Expand Down
17 changes: 15 additions & 2 deletions src/app/chat/UserTag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
<div v-if="user && user.displayName" ref="tagRef" class="seventv-chat-user" :style="{ color: user.color }">
<!--Badge List -->
<span
v-if="!hideBadges && ((twitchBadges.length && twitchBadgeSets?.count) || cosmetics.badges.size)"
v-if="
!hideBadges && ((twitchBadges.length && twitchBadgeSets?.count) || cosmetics.badges.size || sourceData)
"
class="seventv-chat-user-badge-list"
>
<Badge
v-if="sourceData"
:key="sourceData.login"
:badge="sourceData"
:alt="sourceData.displayName"
type="picture"
/>
<Badge
v-for="badge of twitchBadges"
:key="badge.id"
Expand Down Expand Up @@ -60,6 +69,7 @@ import { autoPlacement, shift } from "@floating-ui/dom";
const props = withDefaults(
defineProps<{
user: ChatUser;
sourceData?: Twitch.SharedChat;
msgId?: symbol;
asMention?: boolean;
hideBadges?: boolean;
Expand Down Expand Up @@ -106,7 +116,10 @@ watchEffect(() => {
const setID = key;
const badgeID = value;
for (const setGroup of [twitchBadgeSets.value.channelsBySet, twitchBadgeSets.value.globalsBySet]) {
for (const setGroup of [
props.sourceData?.badges.channelsBySet ?? twitchBadgeSets.value.channelsBySet,
twitchBadgeSets.value.globalsBySet,
]) {
if (!setGroup) continue;
const set = setGroup.get(setID);
Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/14.SubscriptionMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
</span>
<span class="bold">Subscribed</span>
with
{{ plan }}.
{{ plan }}
<template v-if="msgData.sourceData"> to {{ msgData.sourceData.displayName }} </template>
<span>.</span>
</div>
</div>

Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/15.Resubscription.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
</span>
<span class="bold">Subscribed</span>
with
{{ plan }}. They've subscribed for
{{ plan }}
<template v-if="msgData.sourceData"> to {{ msgData.sourceData.displayName }} </template>
<span>. They've subscribed for </span>
<span class="bold"> {{ msgData.cumulativeMonths }} months</span>
<template v-if="msgData.shouldShareStreakTenure">
, {{ msgData.streakMonths }} month{{ (msgData.streakMonths ?? 0) > 1 ? "s" : "" }} in a row.
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/msg/21.SubGift.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<span class="bold"> Tier {{ msgData.methods?.plan.charAt(0) }} </span>
Sub to
<span class="bold"> {{ msgData.recipientDisplayName }} </span>
<template v-if="msgData.sourceData"> in {{ msgData.sourceData.displayName }}'s channel! </template>
</div>
</div>
</span>
Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/26.Raid.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<template>
<span class="seventv-raid-message-container seventv-highlight">
<span class="bold">{{ msgData.params.displayName }}</span>
raided with a viewer count of
raided
<template v-if="msgData.sourceData"> {{ msgData.sourceData.displayName }}'s channel </template>
with a viewer count of
<span class="bold"> {{ msgData.params.viewerCount }}</span>
<span>.</span>
</span>
Expand Down
5 changes: 4 additions & 1 deletion src/app/chat/msg/35.SubMysteryGift.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
is gifting {{ msgData.massGiftCount }} Tier {{ msgData.plan.charAt(0) }} Sub{{
msgData.massGiftCount > 1 ? "s" : ""
}}
to {{ msgData.channel }}'s community.
<template v-if="msgData.sourceData">
<span>to {{ msgData.sourceData.displayName }}'s community</span>
</template>
<span>.</span>
<template v-if="msgData.senderCount == msgData.massGiftCount">
It's their first Gift Sub in the channel!
</template>
Expand Down
6 changes: 5 additions & 1 deletion src/app/chat/msg/41.BitsBadgeTier.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
{{ msgData.user.displayName }}
</span>
just earned a new
<span class="bold">{{ msgData.threshold }} Bits badge!</span>
<span class="bold">{{ msgData.threshold }} Bits badge</span>
<template v-if="msgData.sourceData">
<span> in {{ msgData.sourceData.displayName }}'s channel</span>
</template>
<span>!</span>
</div>
</div>
<div v-if="msg.body" class="bits-badge-message">
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/msg/43.PointsReward.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<span class="reward-name bold">
{{ msgData.reward.name }}
</span>
<template v-if="msgData.sourceData"> in {{ msgData.sourceData.displayName }}'s channel </template>
</div>
<span class="reward-cost bold">
<TwChannelPoints />
Expand Down
5 changes: 4 additions & 1 deletion src/app/chat/msg/49.AnnouncementMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
<div class="announce-icon">
<TwAnnounce />
</div>
<div class="announce-title">Announcement</div>
<div class="announce-title">
<template v-if="msgData.sourceData"> {{ msgData.sourceData.displayName }}'s </template>
Announcement
</div>
</div>
<div class="announce-message">
<slot />
Expand Down
6 changes: 5 additions & 1 deletion src/app/chat/msg/52.ViewerMilestone.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
</div>
<span class="bold">Watch Streak Reached!: </span>
<span v-if="msg.author">{{ msg.author.displayName }}</span>
is currently on a {{ msgData.watchStreak }}-stream streak!
is currently on a {{ msgData.watchStreak }}-stream streak
<template v-if="msgData.sourceData">
<span>in {{ msgData.sourceData.displayName }}'s channel</span>
</template>
<span>!</span>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/app/emote-menu/EmoteMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ onKeyStroke("e", (ev) => {
// Up/Down Arrow iterates providers
useEventListener("keydown", (ev) => {
if (ev.isComposing) return;
if (!["ArrowUp", "ArrowDown"].includes(ev.key)) return;
const cur = Object.keys(visibleProviders).indexOf(activeProvider.value ?? "7TV");
Expand Down
2 changes: 1 addition & 1 deletion src/common/Image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function imageHostToSrcset(
multiplier /= targetSize;

if (srcset) srcset += ", ";
srcset += `https:${host.url}/${size.name} ${multiplier}x`;
srcset += `${host.url}/${size.name} ${multiplier}x`;
}

if (targetSize === 1) known[host.url] = srcset;
Expand Down
26 changes: 26 additions & 0 deletions src/common/Input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function getSearchRange(text: string, position: number): [number, number] {
let start = 0;
let end = 0;

for (let i = position; ; i--) {
if (i < 1 || (text.charAt(i - 1) === " " && i !== position)) {
start = i;
break;
}
}

for (let i = position + 1; ; i++) {
if (i > text.length || text.charAt(i - 1) === " ") {
end = i - 1;
break;
}
}

return [start, end];
}

export interface TabToken {
token: string;
priority: number;
item?: SevenTV.ActiveEmote;
}
76 changes: 76 additions & 0 deletions src/common/ReactHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ export function getRootVNode(): ReactExtended.ReactVNode | undefined {
return undefined;
}

export function getReactProps<T>(element: Element): T | undefined {
for (const k in element) {
if (k.startsWith("__reactProps")) {
const props = Reflect.get(element, k);
return props;
}
}
return undefined;
}
/**
* Searches the React VDOM tree for a component, starting at the defined node, searching upwards.
* @param node React VDOM node to start at
Expand Down Expand Up @@ -427,6 +436,73 @@ export function useComponentHook<C extends ReactExtended.WritableComponent>(
return hook;
}

export function getElementFiberStatic<P>(
elem: Element,
offset: number,
): ReactExtended.ReactFunctionalFiber<P> | undefined {
const node = getVNodeFromDOM(elem);
if (!node) return;
let current = node;
for (let i = 0; i < offset; i++) {
if (!current.return) return;
current = current.return;
}
if (!current.elementType) return;

return current as unknown as ReactExtended.ReactFunctionalFiber<P>;
}

export async function useStaticRenderFunctionHook<P extends object>(
selector: string,
offset: number,
func: HookedElementFunction<P>,
) {
let elem = document.querySelector(selector);

if (!elem || !getElementFiberStatic(elem, offset)) {
elem = await new Promise<Element>((resolve) => {
const observer = new MutationObserver((records) => {
for (const record of records) {
if (!record.addedNodes) continue;
record.addedNodes.forEach((node) => {
if (node instanceof Element && node.querySelector(selector)) {
if (getElementFiberStatic(node, offset)) {
resolve(node);
observer.disconnect();
return;
}
}
});
}
});
observer.observe(document, { childList: true, subtree: true });
});
}

if (!elem) return;

const fiber = getElementFiberStatic<P>(elem, offset)!;

defineFunctionHook(fiber.elementType, "render", func);
func.call(fiber.elementType, null, fiber.pendingProps);

onUnmounted(() => {
unsetPropertyHook(fiber.elementType, "render");
});
}

type RenderFunction<P extends object> = (props: P, ref?: React.RefObject<Element>) => ReactExtended.ReactRuntimeElement;

type HookedElementFunction<
P extends object,
E extends ReactExtended.ReactFunctionalFiber<P> = ReactExtended.ReactFunctionalFiber<P>,
> = (
this: E["elementType"],
old: RenderFunction<P> | null,
props: P,
ref?: React.RefObject<Element>,
) => ReactExtended.ReactRuntimeElement | null;

interface ComponentCriteria {
parentSelector?: string;
childSelector?: string;
Expand Down
Loading

0 comments on commit 494d868

Please sign in to comment.