<commit_before><template>
	<aside
		ref="userlist"
		class="userlist">
		<div class="count">
			<input
				ref="input"
				:value="userSearchInput"
				:placeholder="channel.users.length + ' user' + (channel.users.length === 1 ? '' : 's')"
				type="search"
				class="search"
				aria-label="Search among the user list"
				tabindex="-1"
				@input="setUserSearchInput"
				@keydown.up="navigateUserList(-1)"
				@keydown.down="navigateUserList(1)"
				@keydown.page-up="navigateUserList(-10)"
				@keydown.page-down="navigateUserList(10)"
				@keydown.enter="selectUser">
		</div>
		<div class="names">
			<div
				v-for="(users, mode) in groupedUsers"
				:key="mode"
				:class="['user-mode', getModeClass(mode)]">
				<template v-if="userSearchInput.length > 0">
					<UsernameFiltered
						v-for="user in users"
						:key="user.original.nick"
						:on-hover="hoverUser"
						:active="user.original === activeUser"
						:user="user" />
				</template>
				<template v-else>
					<Username
						v-for="user in users"
						:key="user.nick"
						:on-hover="hoverUser"
						:active="user === activeUser"
						:user="user" />
				</template>
			</div>
		</div>
	</aside>
</template>

<script>
const fuzzy = require("fuzzy");
import Username from "./Username.vue";
import UsernameFiltered from "./UsernameFiltered.vue";

const modes = {
	"~": "owner",
	"&": "admin",
	"!": "admin",
	"@": "op",
	"%": "half-op",
	"+": "voice",
	"": "normal",
};

export default {
	name: "ChatUserList",
	components: {
		Username,
		UsernameFiltered,
	},
	props: {
		channel: Object,
	},
	data() {
		return {
			userSearchInput: "",
			activeUser: null,
		};
	},
	computed: {
		// filteredUsers is computed, to avoid unnecessary filtering
		// as it is shared between filtering and keybindings.
		filteredUsers() {
			return fuzzy.filter(
				this.userSearchInput,
				this.channel.users,
				{
					pre: "<b>",
					post: "</b>",
					extract: (u) => u.nick,
				}
			);
		},
		groupedUsers() {
			const groups = {};

			if (this.userSearchInput) {
				const result = this.filteredUsers;

				for (const user of result) {
					if (!groups[user.original.mode]) {
						groups[user.original.mode] = [];
					}

					groups[user.original.mode].push(user);
				}
			} else {
				for (const user of this.channel.users) {
					if (!groups[user.mode]) {
						groups[user.mode] = [user];
					} else {
						groups[user.mode].push(user);
					}
				}
			}

			return groups;
		},
	},
	methods: {
		setUserSearchInput(e) {
			this.userSearchInput = e.target.value;
		},
		getModeClass(mode) {
			return modes[mode];
		},
		selectUser() {
			// Simulate a click on the active user to open the context menu.
			// Coordinates are provided to position the menu correctly.
			if (!this.activeUser) {
				return;
			}

			const el = this.$refs.userlist.querySelector(".active");
			const rect = el.getBoundingClientRect();
			const ev = new MouseEvent("click", {
				view: window,
				bubbles: true,
				cancelable: true,
				clientX: rect.x,
				clientY: rect.y + rect.height,
			});
			el.dispatchEvent(ev);
		},
		hoverUser(user) {
			this.activeUser = user;
		},
		navigateUserList(direction) {
			let users = this.channel.users;
<commit_msg>Reset active user when leaving user list with mouse<commit_after><template>
	<aside
		ref="userlist"
		class="userlist"
		@mouseleave="removeHoverUser">
		<div class="count">
			<input
				ref="input"
				:value="userSearchInput"
				:placeholder="channel.users.length + ' user' + (channel.users.length === 1 ? '' : 's')"
				type="search"
				class="search"
				aria-label="Search among the user list"
				tabindex="-1"
				@input="setUserSearchInput"
				@keydown.up="navigateUserList(-1)"
				@keydown.down="navigateUserList(1)"
				@keydown.page-up="navigateUserList(-10)"
				@keydown.page-down="navigateUserList(10)"
				@keydown.enter="selectUser">
		</div>
		<div class="names">
			<div
				v-for="(users, mode) in groupedUsers"
				:key="mode"
				:class="['user-mode', getModeClass(mode)]">
				<template v-if="userSearchInput.length > 0">
					<UsernameFiltered
						v-for="user in users"
						:key="user.original.nick"
						:on-hover="hoverUser"
						:active="user.original === activeUser"
						:user="user" />
				</template>
				<template v-else>
					<Username
						v-for="user in users"
						:key="user.nick"
						:on-hover="hoverUser"
						:active="user === activeUser"
						:user="user" />
				</template>
			</div>
		</div>
	</aside>
</template>

<script>
const fuzzy = require("fuzzy");
import Username from "./Username.vue";
import UsernameFiltered from "./UsernameFiltered.vue";

const modes = {
	"~": "owner",
	"&": "admin",
	"!": "admin",
	"@": "op",
	"%": "half-op",
	"+": "voice",
	"": "normal",
};

export default {
	name: "ChatUserList",
	components: {
		Username,
		UsernameFiltered,
	},
	props: {
		channel: Object,
	},
	data() {
		return {
			userSearchInput: "",
			activeUser: null,
		};
	},
	computed: {
		// filteredUsers is computed, to avoid unnecessary filtering
		// as it is shared between filtering and keybindings.
		filteredUsers() {
			return fuzzy.filter(
				this.userSearchInput,
				this.channel.users,
				{
					pre: "<b>",
					post: "</b>",
					extract: (u) => u.nick,
				}
			);
		},
		groupedUsers() {
			const groups = {};

			if (this.userSearchInput) {
				const result = this.filteredUsers;

				for (const user of result) {
					if (!groups[user.original.mode]) {
						groups[user.original.mode] = [];
					}

					groups[user.original.mode].push(user);
				}
			} else {
				for (const user of this.channel.users) {
					if (!groups[user.mode]) {
						groups[user.mode] = [user];
					} else {
						groups[user.mode].push(user);
					}
				}
			}

			return groups;
		},
	},
	methods: {
		setUserSearchInput(e) {
			this.userSearchInput = e.target.value;
		},
		getModeClass(mode) {
			return modes[mode];
		},
		selectUser() {
			// Simulate a click on the active user to open the context menu.
			// Coordinates are provided to position the menu correctly.
			if (!this.activeUser) {
				return;
			}

			const el = this.$refs.userlist.querySelector(".active");
			const rect = el.getBoundingClientRect();
			const ev = new MouseEvent("click", {
				view: window,
				bubbles: true,
				cancelable: true,
				clientX: rect.x,
				clientY: rect.y + rect.height,
			});
			el.dispatchEvent(ev);
		},
		hoverUser(user) {
			this.activeUser = user;
		},
		removeHoverUser() {
			this.activeUser = null;
		},
		navigateUserList(direction) {
			let users = this.channel.users;

{'fraction_non_alphanumeric': 0.16920267802799757, 'fraction_numerical': 0.002738892270237371, 'mean_word_length': 2.898576512455516, 'pattern_counts': {'":': 14, '<': 43, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '227759', 'n_tokens_mistral': 2881, 'n_tokens_neox': 2255, 'n_words': 458}