Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG-nightly.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### 3.1.17.1000

- Added priority system for custom highlights
### 3.1.16.2000

- Updated Firefox extension URL in onboarding
Expand Down
168 changes: 136 additions & 32 deletions src/app/settings/SettingsConfigHighlights.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,35 @@
<main class="seventv-settings-custom-highlights">
<div class="tabs"></div>
<div class="list">
<u><h5>Phrases & Words</h5></u>
<!--Phrases & Words highlights-->
<div class="category-header">
<u><h5>Phrases & Words</h5></u>
</div>
<div class="phrase-item item heading">
<div class="centered">Priority</div>
<div>Pattern</div>
<div>Label</div>
<div class="centered">Flash Title</div>
<div class="centered">Case Sensitive</div>
<div class="centered">RegExp</div>
<div>Case Sensitive</div>
<div class="centered">Flash Title</div>
<div>Color</div>
</div>

<UiScrollable>
<template v-for="(h, _, index) of highlights.getAllPhraseHighlights()" :key="h.id">
<template v-for="(h, index) of phraseHighlightsSorted" :key="h.id">
<div class="phrase-item item">
<!-- Priority input -->
<div name="priority" class="centered">
<input
type="number"
class="priority-input"
:value="h.priority"
min="1"
@blur="onPriorityChange(h, $event)"
@keydown.enter="($event.target as HTMLInputElement).blur()"
/>
</div>

<!-- Pattern -->
<div name="pattern" class="use-virtual-input" tabindex="0" @click="onInputFocus(h, 'pattern')">
<span>{{ h.pattern }}</span>
Expand All @@ -35,11 +51,6 @@
/>
</div>

<!-- Checkbox: Flash Title -->
<div name="flash-title" class="centered">
<FormCheckbox :checked="!!h.flashTitle" @update:checked="onFlashTitleChange(h, $event)" />
</div>

<!-- Checkbox: RegExp -->
<div name="is-regexp" class="centered">
<FormCheckbox
Expand All @@ -58,6 +69,11 @@
/>
</div>

<!-- Checkbox: Flash Title -->
<div name="flash-title" class="centered">
<FormCheckbox :checked="!!h.flashTitle" @update:checked="onFlashTitleChange(h, $event)" />
</div>

<div name="color">
<input v-model="h.color" type="color" @input="onColorChange(h, $event as InputEvent)" />
</div>
Expand Down Expand Up @@ -109,19 +125,34 @@
<hr class="solid" />

<!--Badge highlights-->
<u><h5>Badges</h5></u>
<div class="category-header">
<u><h5>Badges</h5></u>
</div>
<div class="badge-item item heading">
<div class="centered">Priority</div>
<div>Badge ID</div>
<div>Version</div>
<div>Label</div>
<div>Badge</div>
<div>Lable</div>
<div class="centered">Version</div>
<div class="centered">Badge</div>
<div class="centered">Flash Title</div>
<div>Color</div>
</div>

<UiScrollable>
<template v-for="(h, _, index) of highlights.getAllBadgeHighlights()" :key="h.id">
<template v-for="(h, index) of badgeHighlightsSorted" :key="h.id">
<div class="badge-item item">
<!-- Priority input -->
<div name="priority" class="centered">
<input
type="number"
class="priority-input"
:value="h.priority"
min="1"
@blur="onPriorityChange(h, $event)"
@keydown.enter="($event.target as HTMLInputElement).blur()"
/>
</div>

<!-- Badge ID -->
<div
name="pattern"
Expand All @@ -138,16 +169,6 @@
/>
</div>

<!-- Version -->
<div name="version" class="use-virtual-input" tabindex="0" @click="onInputFocus(h, 'version')">
<span>{{ h.version }}</span>
<FormInput
:ref="(c) => inputs.version.set(h, c as InstanceType<typeof FormInput>)"
v-model="h.version"
@blur="onInputBlur(h, 'version')"
/>
</div>

<!-- Label -->
<div name="label" class="use-virtual-input" tabindex="0" @click="onInputFocus(h, 'label')">
<span>{{ h.label }}</span>
Expand All @@ -158,7 +179,22 @@
/>
</div>

<div name="badgeURL">
<!-- Version -->
<div
name="version"
class="use-virtual-input centered"
tabindex="0"
@click="onInputFocus(h, 'version')"
>
<span>{{ h.version }}</span>
<FormInput
:ref="(c) => inputs.version.set(h, c as InstanceType<typeof FormInput>)"
v-model="h.version"
@blur="onInputBlur(h, 'version')"
/>
</div>

<div name="badgeURL" class="centered">
<input v-model="h.badgeURL" type="image" :src="h.badgeURL" />
</div>

Expand Down Expand Up @@ -223,17 +259,32 @@
<hr class="solid" />

<!--Username highlights-->
<u><h5>Usernames</h5></u>
<div class="category-header">
<u><h5>Usernames</h5></u>
</div>
<div class="username-item item heading">
<div class="centered">Priority</div>
<div>Username</div>
<div>Label</div>
<div class="centered">Flash Title</div>
<div>Color</div>
</div>

<UiScrollable>
<template v-for="(h, _, index) of highlights.getAllUsernameHighlights()" :key="h.id">
<template v-for="(h, index) of usernameHighlightsSorted" :key="h.id">
<div class="username-item item">
<!-- Priority input -->
<div name="priority" class="centered">
<input
type="number"
class="priority-input"
:value="h.priority"
min="1"
@blur="onPriorityChange(h, $event)"
@keydown.enter="($event.target as HTMLInputElement).blur()"
/>
</div>

<!-- Username -->
<div name="pattern" class="use-virtual-input" tabindex="0" @click="onInputFocus(h, 'pattern')">
<span>{{ h.pattern }}</span>
Expand Down Expand Up @@ -314,7 +365,7 @@
</template>

<script setup lang="ts">
import { nextTick, reactive, ref, toRef, watch } from "vue";
import { computed, nextTick, reactive, ref, toRef, watch } from "vue";
import { useChannelContext } from "@/composable/channel/useChannelContext";
import { HighlightDef, useChatHighlights } from "@/composable/chat/useChatHighlights";
import { useChatProperties } from "@/composable/chat/useChatProperties";
Expand All @@ -330,6 +381,11 @@ const ctx = useChannelContext(); // this will be an empty context, as config is
const highlights = useChatHighlights(ctx);
const properties = useChatProperties(ctx);

// Computed sorted lists for each category (reactive to priority changes)
const phraseHighlightsSorted = computed(() => highlights.getAllPhraseHighlightsSorted());
const usernameHighlightsSorted = computed(() => highlights.getAllUsernameHighlightsSorted());
const badgeHighlightsSorted = computed(() => highlights.getAllBadgeHighlightsSorted());

const newPhraseInput = ref("");
const newUsernameInput = ref("");
const newBadgeInput = ref("");
Expand Down Expand Up @@ -480,6 +536,18 @@ function setupWatcher(inputRef: any, patternKey: string) {
setupWatcher(newPhraseInput, "phrase");
setupWatcher(newUsernameInput, "username");
setupWatcher(newBadgeInput, "badge");

// Priority change handler - updates priority and triggers re-numbering
function onPriorityChange(h: HighlightDef, ev: Event): void {
if (!(ev.target instanceof HTMLInputElement)) return;
const newPriority = parseInt(ev.target.value, 10);
if (isNaN(newPriority) || newPriority < 1) {
// Reset input to current priority if invalid
ev.target.value = String(h.priority ?? 1);
return;
}
highlights.setPriority(h.id, newPriority);
}
</script>

<style scoped lang="scss">
Expand All @@ -492,6 +560,17 @@ main.seventv-settings-custom-highlights {
"list";
overflow-x: auto;

.category-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 0.5rem;

u {
flex: 1;
}
}

hr.solid {
border-top: 2px solid #fff;
opacity: 0.2;
Expand All @@ -511,21 +590,21 @@ main.seventv-settings-custom-highlights {
max-height: 100rem;

.phrase-item {
grid-template-columns: 20% 9rem 1fr 1fr 1fr 1fr 1fr;
grid-template-columns: 3rem 20% 9rem 1fr 1fr 1fr 1fr 1fr;
}

.username-item {
grid-template-columns: 20% 9rem 1fr 1fr 1fr;
grid-template-columns: 3rem 20% 9rem 1fr 1fr 1fr;
}

.badge-item {
grid-template-columns: 20% 9rem 1fr 1fr 1fr 1fr 1fr;
grid-template-columns: 3rem 20% 9rem 1fr 1fr 1fr 1fr 1fr;
}

.item {
display: grid;
grid-auto-flow: row dense;
column-gap: 3rem;
column-gap: 2rem;
padding: 1rem;

> div {
Expand Down Expand Up @@ -568,6 +647,31 @@ main.seventv-settings-custom-highlights {
}
}

.priority-input {
width: 2.5rem;
padding: 0.3rem;
text-align: center;
font-size: 0.875rem;
font-weight: 500;
background-color: var(--seventv-input-background);
border: 0.1rem solid var(--seventv-input-border);
border-radius: 0.25rem;
color: var(--seventv-text-color-normal);
appearance: textfield;

&:focus {
border-color: var(--seventv-primary);
outline: none;
}

/* Hide spin buttons */
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
}

&:not(.create-new) > .use-virtual-input {
cursor: text;
padding: 0.5rem;
Expand Down
Loading