You've already forked AstralRinth
forked from didirus/AstralRinth
App redesign (#2946)
* Start of app redesign * format * continue progress * Content page nearly done * Fix recursion issues with content page * Fix update all alignment * Discover page progress * Settings progress * Removed unlocked-size hack that breaks web * Revamp project page, refactor web project page to share code with app, fixed loading bar, misc UI/UX enhancements, update ko-fi logo, update arrow icons, fix web issues caused by floating-vue migration, fix tooltip issues, update web tooltips, clean up web hydration issues * Ads + run prettier * Begin auth refactor, move common messages to ui lib, add i18n extraction to all apps, begin Library refactor * fix ads not hiding when plus log in * rev lockfile changes/conflicts * Fix sign in page * Add generated * (mostly) Data driven search * Fix search mobile issue * profile fixes * Project versions page, fix typescript on UI lib and misc fixes * Remove unused gallery component * Fix linkfunction err * Search filter controls at top, localization for locked filters * Fix provided filter names * Fix navigating from instance browse to main browse * Friends frontend (#2995) * Friends system frontend * (almost) finish frontend * finish friends, fix lint * Fix lint --------- Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com> * Refresh macOS app icon * Update web search UI more * Fix link opens * Fix frontend build --------- Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<ButtonStyled v-if="!!slots.title" :type="type">
|
||||
<button class="!w-full" @click="() => (isOpen ? close() : open())">
|
||||
<slot name="title" /><DropdownIcon
|
||||
class="ml-auto size-5 transition-transform duration-300"
|
||||
class="ml-auto size-5 text-contrast transition-transform duration-300"
|
||||
:class="{ 'rotate-180': isOpen }"
|
||||
/>
|
||||
</button>
|
||||
@@ -62,6 +62,9 @@ defineOptions({
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.3s ease-in-out;
|
||||
animation: height-animate 500ms ease-in-out both;
|
||||
content-visibility: auto;
|
||||
animation-composition: replace;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
@@ -77,4 +80,13 @@ defineOptions({
|
||||
.accordion-content > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes height-animate {
|
||||
from {
|
||||
block-size: initial;
|
||||
}
|
||||
to {
|
||||
block-size: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
'badge flex items-center gap-1 font-semibold text-secondary ' + color + ' type--' + type
|
||||
"
|
||||
>
|
||||
<template v-if="color"> <span class="circle" /> {{ $capitalizeString(type) }}</template>
|
||||
<template v-if="color"> <span class="circle" /> {{ capitalizeString(type) }}</template>
|
||||
|
||||
<!-- User roles -->
|
||||
<template v-else-if="type === 'admin'"> <ModrinthIcon /> Modrinth Team</template>
|
||||
@@ -36,25 +36,28 @@
|
||||
<template v-else-if="type === 'closed'"> <CloseIcon /> Closed</template>
|
||||
|
||||
<!-- Other -->
|
||||
<template v-else> <span class="circle" /> {{ $capitalizeString(type) }} </template>
|
||||
<template v-else> <span class="circle" /> {{ capitalizeString(type) }} </template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { GlobeIcon, LinkIcon } from "@modrinth/assets";
|
||||
|
||||
import ModrinthIcon from "~/assets/images/logo.svg?component";
|
||||
import PlusIcon from "~/assets/images/utils/plus.svg?component";
|
||||
import ModeratorIcon from "~/assets/images/sidebar/admin.svg?component";
|
||||
import CreatorIcon from "~/assets/images/utils/box.svg?component";
|
||||
import DraftIcon from "~/assets/images/utils/file-text.svg?component";
|
||||
import CrossIcon from "~/assets/images/utils/x.svg?component";
|
||||
import ArchiveIcon from "~/assets/images/utils/archive.svg?component";
|
||||
import ProcessingIcon from "~/assets/images/utils/updated.svg?component";
|
||||
import CheckIcon from "~/assets/images/utils/check.svg?component";
|
||||
import LockIcon from "~/assets/images/utils/lock.svg?component";
|
||||
import CalendarIcon from "~/assets/images/utils/calendar.svg?component";
|
||||
import CloseIcon from "~/assets/images/utils/check-circle.svg?component";
|
||||
import {
|
||||
GlobeIcon,
|
||||
LinkIcon,
|
||||
ModrinthIcon,
|
||||
PlusIcon,
|
||||
ScaleIcon as ModeratorIcon,
|
||||
BoxIcon as CreatorIcon,
|
||||
FileTextIcon as DraftIcon,
|
||||
XIcon as CrossIcon,
|
||||
ArchiveIcon,
|
||||
UpdatedIcon as ProcessingIcon,
|
||||
CheckIcon,
|
||||
LockIcon,
|
||||
CalendarIcon,
|
||||
XCircleIcon as CloseIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { capitalizeString } from "@modrinth/utils";
|
||||
|
||||
defineProps({
|
||||
type: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<nav
|
||||
ref="scrollContainer"
|
||||
class="experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
>
|
||||
<NuxtLink
|
||||
v-for="(link, index) in filteredLinks"
|
||||
@@ -11,7 +11,7 @@
|
||||
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
|
||||
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
|
||||
:class="{
|
||||
'text-brand': activeIndex === index && !subpageSelected,
|
||||
'text-button-textSelected': activeIndex === index && !subpageSelected,
|
||||
'text-contrast': activeIndex === index && subpageSelected,
|
||||
}"
|
||||
>
|
||||
@@ -20,7 +20,7 @@
|
||||
</NuxtLink>
|
||||
<div
|
||||
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${
|
||||
subpageSelected ? 'bg-button-bg' : 'bg-brand-highlight'
|
||||
subpageSelected ? 'bg-button-bg' : 'bg-button-bgSelected'
|
||||
}`"
|
||||
:style="{
|
||||
left: sliderLeftPx,
|
||||
@@ -161,4 +161,8 @@ watch(
|
||||
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
<template>
|
||||
<div class="experimental-styles-within flex flex-col gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<ManySelect
|
||||
v-model="selectedPlatforms"
|
||||
:options="filterOptions.platform"
|
||||
@change="updateFilters"
|
||||
>
|
||||
<FilterIcon class="h-5 w-5 text-secondary" />
|
||||
Platform
|
||||
<template #option="{ option }">
|
||||
{{ formatCategory(option) }}
|
||||
</template>
|
||||
</ManySelect>
|
||||
<ManySelect
|
||||
v-model="selectedGameVersions"
|
||||
:options="filterOptions.gameVersion"
|
||||
search
|
||||
@change="updateFilters"
|
||||
>
|
||||
<FilterIcon class="h-5 w-5 text-secondary" />
|
||||
Game versions
|
||||
<template #footer>
|
||||
<Checkbox v-model="showSnapshots" class="mx-1" :label="`Show all versions`" />
|
||||
</template>
|
||||
</ManySelect>
|
||||
<ManySelect
|
||||
v-model="selectedChannels"
|
||||
:options="filterOptions.channel"
|
||||
@change="updateFilters"
|
||||
>
|
||||
<FilterIcon class="h-5 w-5 text-secondary" />
|
||||
Channels
|
||||
<template #option="{ option }">
|
||||
{{ option === "release" ? "Release" : option === "beta" ? "Beta" : "Alpha" }}
|
||||
</template>
|
||||
</ManySelect>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1 empty:hidden">
|
||||
<button
|
||||
v-if="selectedChannels.length + selectedGameVersions.length + selectedPlatforms.length > 1"
|
||||
class="tag-list__item text-contrast transition-transform active:scale-[0.95]"
|
||||
@click="clearFilters"
|
||||
>
|
||||
<XCircleIcon />
|
||||
Clear all filters
|
||||
</button>
|
||||
<button
|
||||
v-for="channel in selectedChannels"
|
||||
:key="`remove-filter-${channel}`"
|
||||
class="tag-list__item transition-transform active:scale-[0.95]"
|
||||
:style="`--_color: var(--color-${channel === 'alpha' ? 'red' : channel === 'beta' ? 'orange' : 'green'});--_bg-color: var(--color-${channel === 'alpha' ? 'red' : channel === 'beta' ? 'orange' : 'green'}-highlight)`"
|
||||
@click="toggleFilter('channel', channel)"
|
||||
>
|
||||
<XIcon />
|
||||
{{ channel.slice(0, 1).toUpperCase() + channel.slice(1) }}
|
||||
</button>
|
||||
<button
|
||||
v-for="version in selectedGameVersions"
|
||||
:key="`remove-filter-${version}`"
|
||||
class="tag-list__item transition-transform active:scale-[0.95]"
|
||||
@click="toggleFilter('gameVersion', version)"
|
||||
>
|
||||
<XIcon />
|
||||
{{ version }}
|
||||
</button>
|
||||
<button
|
||||
v-for="platform in selectedPlatforms"
|
||||
:key="`remove-filter-${platform}`"
|
||||
class="tag-list__item transition-transform active:scale-[0.95]"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
@click="toggleFilter('platform', platform)"
|
||||
>
|
||||
<XIcon />
|
||||
{{ formatCategory(platform) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FilterIcon, XCircleIcon, XIcon } from "@modrinth/assets";
|
||||
import { ManySelect, Checkbox } from "@modrinth/ui";
|
||||
import { formatCategory } from "@modrinth/utils";
|
||||
import type { ModrinthVersion } from "@modrinth/utils";
|
||||
|
||||
const props = defineProps<{ versions: ModrinthVersion[] }>();
|
||||
|
||||
const emit = defineEmits(["switch-page"]);
|
||||
|
||||
const allChannels = ref(["release", "beta", "alpha"]);
|
||||
|
||||
const route = useNativeRoute();
|
||||
const router = useNativeRouter();
|
||||
|
||||
const tags = useTags();
|
||||
|
||||
const showSnapshots = ref(false);
|
||||
|
||||
type FilterType = "channel" | "gameVersion" | "platform";
|
||||
type Filter = string;
|
||||
|
||||
const filterOptions = computed(() => {
|
||||
const filters: Record<FilterType, Filter[]> = {
|
||||
channel: [],
|
||||
gameVersion: [],
|
||||
platform: [],
|
||||
};
|
||||
|
||||
const platformSet = new Set();
|
||||
const gameVersionSet = new Set();
|
||||
const channelSet = new Set();
|
||||
|
||||
for (const version of props.versions) {
|
||||
for (const loader of version.loaders) {
|
||||
platformSet.add(loader);
|
||||
}
|
||||
for (const gameVersion of version.game_versions) {
|
||||
gameVersionSet.add(gameVersion);
|
||||
}
|
||||
channelSet.add(version.version_type);
|
||||
}
|
||||
|
||||
if (channelSet.size > 0) {
|
||||
filters.channel = Array.from(channelSet) as Filter[];
|
||||
filters.channel.sort((a, b) => allChannels.value.indexOf(a) - allChannels.value.indexOf(b));
|
||||
}
|
||||
if (gameVersionSet.size > 0) {
|
||||
const gameVersions = tags.value.gameVersions.filter((x) => gameVersionSet.has(x.version));
|
||||
|
||||
filters.gameVersion = gameVersions
|
||||
.filter((x) => (showSnapshots.value ? true : x.version_type === "release"))
|
||||
.map((x) => x.version);
|
||||
}
|
||||
if (platformSet.size > 0) {
|
||||
filters.platform = Array.from(platformSet) as Filter[];
|
||||
}
|
||||
|
||||
return filters;
|
||||
});
|
||||
|
||||
const selectedChannels = ref<string[]>([]);
|
||||
const selectedGameVersions = ref<string[]>([]);
|
||||
const selectedPlatforms = ref<string[]>([]);
|
||||
|
||||
selectedChannels.value = route.query.c ? getArrayOrString(route.query.c) : [];
|
||||
selectedGameVersions.value = route.query.g ? getArrayOrString(route.query.g) : [];
|
||||
selectedPlatforms.value = route.query.l ? getArrayOrString(route.query.l) : [];
|
||||
|
||||
async function toggleFilters(type: FilterType, filters: Filter[]) {
|
||||
for (const filter of filters) {
|
||||
await toggleFilter(type, filter);
|
||||
}
|
||||
|
||||
await router.replace({
|
||||
query: {
|
||||
...route.query,
|
||||
c: selectedChannels.value,
|
||||
g: selectedGameVersions.value,
|
||||
l: selectedPlatforms.value,
|
||||
},
|
||||
});
|
||||
|
||||
emit("switch-page", 1);
|
||||
}
|
||||
|
||||
async function toggleFilter(type: FilterType, filter: Filter, skipRouter = false) {
|
||||
if (type === "channel") {
|
||||
selectedChannels.value = selectedChannels.value.includes(filter)
|
||||
? selectedChannels.value.filter((x) => x !== filter)
|
||||
: [...selectedChannels.value, filter];
|
||||
} else if (type === "gameVersion") {
|
||||
selectedGameVersions.value = selectedGameVersions.value.includes(filter)
|
||||
? selectedGameVersions.value.filter((x) => x !== filter)
|
||||
: [...selectedGameVersions.value, filter];
|
||||
} else if (type === "platform") {
|
||||
selectedPlatforms.value = selectedPlatforms.value.includes(filter)
|
||||
? selectedPlatforms.value.filter((x) => x !== filter)
|
||||
: [...selectedPlatforms.value, filter];
|
||||
}
|
||||
if (!skipRouter) {
|
||||
await updateFilters();
|
||||
}
|
||||
}
|
||||
|
||||
async function updateFilters() {
|
||||
await router.replace({
|
||||
query: {
|
||||
...route.query,
|
||||
c: selectedChannels.value,
|
||||
g: selectedGameVersions.value,
|
||||
l: selectedPlatforms.value,
|
||||
},
|
||||
});
|
||||
|
||||
emit("switch-page", 1);
|
||||
}
|
||||
|
||||
async function clearFilters() {
|
||||
selectedChannels.value = [];
|
||||
selectedGameVersions.value = [];
|
||||
selectedPlatforms.value = [];
|
||||
|
||||
await router.replace({
|
||||
query: {
|
||||
...route.query,
|
||||
c: undefined,
|
||||
g: undefined,
|
||||
l: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
emit("switch-page", 1);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
toggleFilter,
|
||||
toggleFilters,
|
||||
});
|
||||
</script>
|
||||
@@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="grid grid-cols-[min-content_auto_min-content_min-content] items-center gap-2 rounded-2xl border-[1px] border-button-bg bg-bg p-2"
|
||||
>
|
||||
<VersionChannelIndicator :channel="version.version_type" />
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<h1 class="my-0 truncate text-nowrap text-base font-extrabold leading-none text-contrast">
|
||||
{{ version.version_number }}
|
||||
</h1>
|
||||
<p class="m-0 truncate text-nowrap text-xs font-semibold text-secondary">
|
||||
{{ version.name }}
|
||||
</p>
|
||||
</div>
|
||||
<ButtonStyled color="brand">
|
||||
<a :href="downloadUrl" class="min-w-0" @click="emit('onDownload')">
|
||||
<DownloadIcon aria-hidden="true" /> Download
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<nuxt-link
|
||||
:to="`/project/${props.version.project_id}/version/${props.version.id}`"
|
||||
class="min-w-0"
|
||||
aria-label="Open project page"
|
||||
@click="emit('onNavigate')"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" />
|
||||
</nuxt-link>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled, VersionChannelIndicator } from "@modrinth/ui";
|
||||
import { DownloadIcon, ExternalIcon } from "@modrinth/assets";
|
||||
|
||||
const props = defineProps<{
|
||||
version: Version;
|
||||
}>();
|
||||
|
||||
const downloadUrl = computed(() => {
|
||||
const primary: VersionFile = props.version.files.find((x) => x.primary) || props.version.files[0];
|
||||
return primary.url;
|
||||
});
|
||||
|
||||
const emit = defineEmits(["onDownload", "onNavigate"]);
|
||||
</script>
|
||||
@@ -108,7 +108,7 @@
|
||||
type="search"
|
||||
name="search"
|
||||
autocomplete="off"
|
||||
class="h-8 min-h-[unset] w-full border-[1px] border-solid border-button-bg bg-transparent py-2 pl-9"
|
||||
class="h-8 min-h-[unset] w-full border-[1px] border-solid border-divider bg-transparent py-2 pl-9"
|
||||
placeholder="Search..."
|
||||
@input="$emit('update:searchQuery', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="border-0 border-b border-solid"
|
||||
:class="danger ? 'border-[#cb2245] dark:border-[#612d38]' : 'border-button-bg'"
|
||||
:class="danger ? 'border-[#cb2245] dark:border-[#612d38]' : 'border-divider'"
|
||||
></div>
|
||||
<div class="mt-2 h-full w-full overflow-auto px-6">
|
||||
<slot />
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
v-if="isOpen"
|
||||
ref="menuRef"
|
||||
data-pyro-telepopover-root
|
||||
class="experimental-styles-within fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-2xl border-[1px] border-solid border-button-bg bg-bg-raised p-2 shadow-lg"
|
||||
class="experimental-styles-within fixed isolate z-[9999] flex w-fit flex-col gap-2 overflow-hidden rounded-2xl border-[1px] border-solid border-divider bg-bg-raised p-2 shadow-lg"
|
||||
:style="menuStyle"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
@@ -272,7 +272,7 @@ const handleItemClick = (option: Option, index: number) => {
|
||||
|
||||
const handleMouseOver = (index: number) => {
|
||||
selectedIndex.value = index;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
};
|
||||
|
||||
// Scrolling is disabled for keyboard navigation
|
||||
@@ -295,7 +295,7 @@ const enableBodyScroll = () => {
|
||||
|
||||
const focusFirstMenuItem = () => {
|
||||
if (menuItemsRef.value.length > 0) {
|
||||
menuItemsRef.value[0].focus();
|
||||
menuItemsRef.value[0].focus?.();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -312,26 +312,26 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
case "ArrowDown":
|
||||
event.preventDefault();
|
||||
selectedIndex.value = (selectedIndex.value + 1) % filteredOptions.value.length;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
break;
|
||||
case "ArrowUp":
|
||||
event.preventDefault();
|
||||
selectedIndex.value =
|
||||
(selectedIndex.value - 1 + filteredOptions.value.length) % filteredOptions.value.length;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
break;
|
||||
case "Home":
|
||||
event.preventDefault();
|
||||
if (menuItemsRef.value.length > 0) {
|
||||
selectedIndex.value = 0;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
}
|
||||
break;
|
||||
case "End":
|
||||
event.preventDefault();
|
||||
if (menuItemsRef.value.length > 0) {
|
||||
selectedIndex.value = filteredOptions.value.length - 1;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
}
|
||||
break;
|
||||
case "Enter":
|
||||
@@ -344,7 +344,7 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
closeMenu();
|
||||
triggerRef.value?.focus();
|
||||
triggerRef.value?.focus?.();
|
||||
break;
|
||||
case "Tab":
|
||||
event.preventDefault();
|
||||
@@ -355,7 +355,7 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
} else {
|
||||
selectedIndex.value = (selectedIndex.value + 1) % filteredOptions.value.length;
|
||||
}
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -366,7 +366,7 @@ const handleKeydown = (event: KeyboardEvent) => {
|
||||
);
|
||||
if (matchIndex !== -1) {
|
||||
selectedIndex.value = matchIndex;
|
||||
menuItemsRef.value[selectedIndex.value].focus();
|
||||
menuItemsRef.value[selectedIndex.value].focus?.();
|
||||
}
|
||||
if (typeAheadTimeout.value) {
|
||||
clearTimeout(typeAheadTimeout.value);
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
}"
|
||||
>
|
||||
<template v-if="members[message.author_id]">
|
||||
<ConditionalNuxtLink
|
||||
<AutoLink
|
||||
class="message__icon"
|
||||
:is-link="!noLinks"
|
||||
:to="`/user/${members[message.author_id].username}`"
|
||||
:to="noLinks ? '' : `/user/${members[message.author_id].username}`"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -21,19 +20,16 @@
|
||||
circle
|
||||
:raised="raised"
|
||||
/>
|
||||
</ConditionalNuxtLink>
|
||||
</AutoLink>
|
||||
<span :class="`message__author role-${members[message.author_id].role}`">
|
||||
<LockIcon
|
||||
v-if="message.body.private"
|
||||
v-tooltip="'Only visible to moderators'"
|
||||
class="private-icon"
|
||||
/>
|
||||
<ConditionalNuxtLink
|
||||
:is-link="!noLinks"
|
||||
:to="`/user/${members[message.author_id].username}`"
|
||||
>
|
||||
<AutoLink :to="noLinks ? '' : `/user/${members[message.author_id].username}`">
|
||||
{{ members[message.author_id].username }}
|
||||
</ConditionalNuxtLink>
|
||||
</AutoLink>
|
||||
<ScaleIcon v-if="members[message.author_id].role === 'moderator'" v-tooltip="'Moderator'" />
|
||||
<ModrinthIcon
|
||||
v-else-if="members[message.author_id].role === 'admin'"
|
||||
@@ -107,7 +103,7 @@ import {
|
||||
ModrinthIcon,
|
||||
ScaleIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { OverflowMenu, ConditionalNuxtLink } from "@modrinth/ui";
|
||||
import { AutoLink, OverflowMenu } from "@modrinth/ui";
|
||||
import { renderString } from "@modrinth/utils";
|
||||
import Avatar from "~/components/ui/Avatar.vue";
|
||||
import Badge from "~/components/ui/Badge.vue";
|
||||
@@ -287,7 +283,7 @@ a:active + .message__author a,
|
||||
}
|
||||
|
||||
.moderation-color,
|
||||
role-moderator {
|
||||
.role-moderator {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user