Make "Open Image in New Tab" open raw image (when possible) (#7315)

* tell search engines not to index collections because bots keep spamming them and I can basically guarantee not a single human being is using collections in a way where this will be detrimental to them (besides the advertisement spam)

* Index creator created collections

* Open raw images from open image in new tab option in context menu
This commit is contained in:
chyz
2026-08-26 18:59:15 +00:00
committed by GitHub
parent c88efd48af
commit 3439a43880
17 changed files with 80 additions and 9 deletions
@@ -3,7 +3,12 @@
<template #title> <template #title>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-lg font-extrabold text-contrast">Transfer</span> <span class="text-lg font-extrabold text-contrast">Transfer</span>
<Avatar :src="organization.icon_url" :alt="organization.name" size="xs" /> <Avatar
:src="organization.icon_url"
:raw-src="organization.raw_icon_url"
:alt="organization.name"
size="xs"
/>
<span class="text-lg font-extrabold text-contrast">{{ organization.name }}</span> <span class="text-lg font-extrabold text-contrast">{{ organization.name }}</span>
</div> </div>
</template> </template>
@@ -3,7 +3,12 @@
<template #title> <template #title>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-lg font-extrabold text-contrast">Transfer</span> <span class="text-lg font-extrabold text-contrast">Transfer</span>
<Avatar :src="project.icon_url" :alt="project.name" size="xs" /> <Avatar
:src="project.icon_url"
:raw-src="project.raw_icon_url"
:alt="project.name"
size="xs"
/>
<span class="text-lg font-extrabold text-contrast">{{ project.name }}</span> <span class="text-lg font-extrabold text-contrast">{{ project.name }}</span>
</div> </div>
</template> </template>
@@ -3,6 +3,7 @@
<template #leading> <template #leading>
<Avatar <Avatar
:src="organization.icon_url" :src="organization.icon_url"
:raw-src="organization.raw_icon_url"
:alt="organization.name" :alt="organization.name"
:tint-by="organization.id" :tint-by="organization.id"
size="96px" size="96px"
+6 -1
View File
@@ -18,7 +18,12 @@
> >
<LeftArrowIcon /> <LeftArrowIcon />
</ButtonLink> </ButtonLink>
<Avatar :src="project.icon_url" :tint-by="project.id" size="64px" /> <Avatar
:src="project.icon_url"
:raw-src="project.raw_icon_url"
:tint-by="project.id"
size="64px"
/>
</template> </template>
<template #metadata> <template #metadata>
<PageHeaderMetadata> <PageHeaderMetadata>
@@ -216,6 +216,7 @@
<img <img
:src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'" :src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'"
:alt="item.title ? item.title : 'gallery-image'" :alt="item.title ? item.title : 'gallery-image'"
@contextmenu="onFullImageContextMenu($event, item.raw_url)"
/> />
</a> </a>
<div class="gallery-body"> <div class="gallery-body">
@@ -308,6 +309,7 @@ import {
NewModal as Modal, NewModal as Modal,
Textarea, Textarea,
useFormatDateTime, useFormatDateTime,
useFullImageContextMenu,
} from '@modrinth/ui' } from '@modrinth/ui'
import { useEventListener } from '@vueuse/core' import { useEventListener } from '@vueuse/core'
@@ -320,6 +322,7 @@ const formatDate = useFormatDateTime({
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',
}) })
const onFullImageContextMenu = useFullImageContextMenu()
// Single DI injection // Single DI injection
const { const {
@@ -215,6 +215,7 @@
<img <img
:src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'" :src="item.url ? item.url : 'https://cdn.modrinth.com/placeholder-banner.svg'"
:alt="item.title ? item.title : 'gallery-image'" :alt="item.title ? item.title : 'gallery-image'"
@contextmenu="onFullImageContextMenu($event, item.raw_url)"
/> />
</a> </a>
<div class="gallery-body"> <div class="gallery-body">
@@ -299,6 +300,7 @@ import {
NewModal as Modal, NewModal as Modal,
Textarea, Textarea,
useFormatDateTime, useFormatDateTime,
useFullImageContextMenu,
} from '@modrinth/ui' } from '@modrinth/ui'
import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue' import AiImageWarningModal from '~/components/ui/AiImageWarningModal.vue'
@@ -310,6 +312,7 @@ const formatDate = useFormatDateTime({
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',
}) })
const onFullImageContextMenu = useFullImageContextMenu()
const { const {
projectV2: project, projectV2: project,
+1 -1
View File
@@ -14,7 +14,7 @@
<div v-else-if="app && createdBy && authorizationData" class="flex flex-col gap-6"> <div v-else-if="app && createdBy && authorizationData" class="flex flex-col gap-6">
<div class="mt-4 flex items-center justify-center"> <div class="mt-4 flex items-center justify-center">
<div class="flex w-full flex-row items-center justify-evenly"> <div class="flex w-full flex-row items-center justify-evenly">
<Avatar size="md" :src="app.icon_url" /> <Avatar size="md" :src="app.icon_url" :raw-src="app.raw_icon_url" />
<!-- <img class="profile-pic" :src="app.icon_url" alt="User profile picture" /> --> <!-- <img class="profile-pic" :src="app.icon_url" alt="User profile picture" /> -->
<div class="flex select-none items-center justify-center text-[2rem] text-primary"></div> <div class="flex select-none items-center justify-center text-[2rem] text-primary"></div>
<Avatar size="md" circle :src="auth.user?.avatar_url" /> <Avatar size="md" circle :src="auth.user?.avatar_url" />
@@ -150,7 +150,7 @@
</nuxt-link> </nuxt-link>
</ClientOnly> </ClientOnly>
<div class="grid grid-cols-[auto_1fr] gap-4 sm:grid-cols-[auto_1fr_auto]"> <div class="grid grid-cols-[auto_1fr] gap-4 sm:grid-cols-[auto_1fr_auto]">
<Avatar :src="collection.icon_url" size="64px" /> <Avatar :src="collection.icon_url" :raw-src="collection.raw_icon_url" size="64px" />
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<h1 class="heading-2xl"> <h1 class="heading-2xl">
{{ collection.name }} {{ collection.name }}
@@ -15,7 +15,7 @@
class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4" class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4"
> >
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<Avatar size="sm" :src="organization.icon_url" /> <Avatar size="sm" :src="organization.icon_url" :raw-src="organization.raw_icon_url" />
<div class="flex flex-col justify-center gap-1"> <div class="flex flex-col justify-center gap-1">
<h2 class="m-0 text-base"> <h2 class="m-0 text-base">
<nuxt-link :to="`/organization/${organization.slug}`"> <nuxt-link :to="`/organization/${organization.slug}`">
@@ -181,7 +181,7 @@
<div v-for="app in usersApps" :key="app.id" class="universal-card recessed token mt-4"> <div v-for="app in usersApps" :key="app.id" class="universal-card recessed token mt-4">
<div class="token-info"> <div class="token-info">
<div class="token-icon"> <div class="token-icon">
<Avatar size="sm" :src="app.icon_url" /> <Avatar size="sm" :src="app.icon_url" :raw-src="app.raw_icon_url" />
<div> <div>
<h2 class="token-title">{{ app.name }}</h2> <h2 class="token-title">{{ app.name }}</h2>
<div> <div>
@@ -869,6 +869,7 @@ export namespace Labrinth {
id: string id: string
name: string name: string
icon_url: string | null icon_url: string | null
raw_icon_url: string | null
max_scopes: number max_scopes: number
redirect_uris: OAuthRedirectUri[] redirect_uris: OAuthRedirectUri[]
created_by: string created_by: string
@@ -1002,6 +1003,7 @@ export namespace Labrinth {
loaders: string[] loaders: string[]
versions: string[] versions: string[]
icon_url?: string icon_url?: string
raw_icon_url?: string
issues_url?: string issues_url?: string
source_url?: string source_url?: string
wiki_url?: string wiki_url?: string
@@ -1145,6 +1147,7 @@ export namespace Labrinth {
mrpack_loaders: string[] mrpack_loaders: string[]
versions: string[] versions: string[]
icon_url?: string icon_url?: string
raw_icon_url?: string
link_urls: Record<string, Link> link_urls: Record<string, Link>
gallery: GalleryItem[] gallery: GalleryItem[]
color?: number color?: number
@@ -1264,6 +1267,7 @@ export namespace Labrinth {
team_id: string team_id: string
description: string description: string
icon_url: string | null icon_url: string | null
raw_icon_url: string | null
color: number | null color: number | null
members: TeamMember[] members: TeamMember[]
} }
@@ -1387,6 +1391,7 @@ export namespace Labrinth {
team_id: string team_id: string
description: string description: string
icon_url: string | null icon_url: string | null
raw_icon_url: string | null
color: number | null color: number | null
members: Projects.v3.TeamMember[] members: Projects.v3.TeamMember[]
moderation_notes?: Users.Common.ModerationNote | null moderation_notes?: Users.Common.ModerationNote | null
@@ -2297,6 +2302,7 @@ export namespace Labrinth {
name: string name: string
description: string | null description: string | null
icon_url: string | null icon_url: string | null
raw_icon_url: string | null
color: number | null color: number | null
status: CollectionStatus status: CollectionStatus
created: string created: string
@@ -17,6 +17,7 @@
:loading="loading" :loading="loading"
@load="onLoad" @load="onLoad"
@error="onError" @error="onError"
@contextmenu="onFullImageContextMenu($event, rawSrc)"
/> />
<svg <svg
v-else v-else
@@ -50,6 +51,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
import { useFullImageContextMenu } from '../../composables'
const onFullImageContextMenu = useFullImageContextMenu()
const pixelated = ref(false) const pixelated = ref(false)
const hasTransparentCorners = ref(false) const hasTransparentCorners = ref(false)
const hasDetectedCorners = ref(false) const hasDetectedCorners = ref(false)
@@ -68,6 +73,7 @@ defineExpose({
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
src?: string | null src?: string | null
rawSrc?: string | null
alt?: string alt?: string
size?: string size?: string
circle?: boolean circle?: boolean
@@ -79,6 +85,7 @@ const props = withDefaults(
}>(), }>(),
{ {
src: null, src: null,
rawSrc: null,
alt: '', alt: '',
size: '2rem', size: '2rem',
circle: false, circle: false,
@@ -5,7 +5,13 @@
@contextmenu="emit('contextmenu', $event)" @contextmenu="emit('contextmenu', $event)"
> >
<template #leading> <template #leading>
<Avatar :src="project.icon_url" :alt="project.title" :tint-by="project.id" size="96px" /> <Avatar
:src="project.icon_url"
:raw-src="project.raw_icon_url"
:alt="project.title"
:tint-by="project.id"
size="96px"
/>
</template> </template>
<template v-if="showStatusBadge" #badges> <template v-if="showStatusBadge" #badges>
@@ -5,7 +5,14 @@
:header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)" :header="project.license.name ? project.license.name : formatMessage(messages.licenseTitle)"
> >
<template #title> <template #title>
<Avatar :src="project.icon_url" :alt="project.title" class="icon" size="32px" no-shadow /> <Avatar
:src="project.icon_url"
:raw-src="project.raw_icon_url"
:alt="project.title"
class="icon"
size="32px"
no-shadow
/>
<span class="text-lg font-extrabold text-contrast"> <span class="text-lg font-extrabold text-contrast">
{{ project.license.name ? project.license.name : formatMessage(messages.licenseTitle) }} {{ project.license.name ? project.license.name : formatMessage(messages.licenseTitle) }}
</span> </span>
@@ -0,0 +1,18 @@
export function useFullImageContextMenu() {
return function onFullImageContextMenu(event: MouseEvent, fullUrl: string | null | undefined) {
if (!fullUrl) return
const img = event.currentTarget as HTMLImageElement
const originalSrc = img.src
if (originalSrc === fullUrl) return
img.src = fullUrl
window.addEventListener(
'focus',
() => {
img.src = originalSrc
},
{ once: true },
)
}
}
+1
View File
@@ -5,6 +5,7 @@ export * from './format-bytes'
export * from './format-date-time' export * from './format-date-time'
export * from './format-money' export * from './format-money'
export * from './format-number' export * from './format-number'
export * from './full-image-context-menu'
export * from './hosting-intercom' export * from './hosting-intercom'
export * from './how-ago' export * from './how-ago'
export * from './i18n' export * from './i18n'
+4
View File
@@ -89,6 +89,7 @@ export interface ProjectV3 {
versions: ModrinthId[] versions: ModrinthId[]
icon_url?: string icon_url?: string
raw_icon_url?: string
link_urls: Record< link_urls: Record<
string, string,
@@ -129,6 +130,7 @@ export interface Project {
body: string body: string
icon_url?: string icon_url?: string
raw_icon_url?: string
color?: number color?: number
categories: Category[] categories: Category[]
@@ -230,6 +232,7 @@ export type Organization = {
team_id: ModrinthId team_id: ModrinthId
description: string description: string
icon_url: string icon_url: string
raw_icon_url: string | null
color: number color: number
members: OrganizationMember[] members: OrganizationMember[]
} }
@@ -254,6 +257,7 @@ export type Collection = {
name: string name: string
description: string description: string
icon_url: string icon_url: string
raw_icon_url: string | null
color: number color: number
status: CollectionStatus status: CollectionStatus
created: string created: string