attachment: fix over reliance on fileType()

Signed-off-by: Yonle <yonle@proton.me>
This commit is contained in:
Yonle 2026-02-27 04:12:47 +07:00
commit 38444f3165
No known key found for this signature in database
GPG key ID: 11889F326F523287
10 changed files with 41 additions and 63 deletions

View file

@ -1,7 +1,6 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import nsfwImage from '../../assets/nsfw.png' import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
import Flash from '../flash/flash.vue' import Flash from '../flash/flash.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue' import VideoAttachment from '../video_attachment/video_attachment.vue'
@ -61,9 +60,7 @@ const Attachment = {
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw, hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
preloadImage: this.$store.getters.mergedConfig.preloadImage, preloadImage: this.$store.getters.mergedConfig.preloadImage,
loading: false, loading: false,
img: img: this.attachment.type == "image" && document.createElement('img'),
fileTypeService.fileType(this.attachment.mimetype) === 'image' &&
document.createElement('img'),
modalOpen: false, modalOpen: false,
showHidden: false, showHidden: false,
flashLoaded: false, flashLoaded: false,
@ -84,7 +81,7 @@ const Attachment = {
'-editable': this.edit !== undefined, '-editable': this.edit !== undefined,
'-compact': this.compact, '-compact': this.compact,
}, },
'-type-' + this.type, '-type-' + this.attachment.type,
this.size && '-size-' + this.size, this.size && '-size-' + this.size,
`-${this.useContainFit ? 'contain' : 'cover'}-fit`, `-${this.useContainFit ? 'contain' : 'cover'}-fit`,
] ]
@ -97,14 +94,14 @@ const Attachment = {
}, },
placeholderName() { placeholderName() {
if (this.attachment.description === '' || !this.attachment.description) { if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase() return this.attachment.type.toUpperCase()
} }
return this.attachment.description return this.attachment.description
}, },
placeholderIconClass() { placeholderIconClass() {
if (this.type === 'image') return 'image' if (this.attachment.type === 'image') return 'image'
if (this.type === 'video') return 'video' if (this.attachment.type === 'video') return 'video'
if (this.type === 'audio') return 'music' if (this.attachment.type === 'audio') return 'music'
return 'file' return 'file'
}, },
referrerpolicy() { referrerpolicy() {
@ -112,14 +109,11 @@ const Attachment = {
? '' ? ''
: 'no-referrer' : 'no-referrer'
}, },
type() {
return fileTypeService.fileType(this.attachment.mimetype)
},
hidden() { hidden() {
return this.nsfw && this.hideNsfwLocal && !this.showHidden return this.nsfw && this.hideNsfwLocal && !this.showHidden
}, },
isEmpty() { isEmpty() {
return this.type === 'html' && !this.attachment.oembed return this.attachment.type === 'html' && !this.attachment.oembed
}, },
useModal() { useModal() {
let modalTypes = [] let modalTypes = []
@ -134,7 +128,7 @@ const Attachment = {
: ['image'] : ['image']
break break
} }
return modalTypes.includes(this.type) return modalTypes.includes(this.attachment.type)
}, },
videoTag() { videoTag() {
return this.useModal ? 'button' : 'span' return this.useModal ? 'button' : 'span'
@ -159,7 +153,7 @@ const Attachment = {
if (this.useModal) { if (this.useModal) {
this.$emit('setMedia') this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment) useMediaViewerStore().setCurrentMedia(this.attachment)
} else if (this.type === 'unknown') { } else if (this.attachment.type === 'unknown') {
window.open(this.attachment.url) window.open(this.attachment.url)
} }
}, },
@ -192,7 +186,7 @@ const Attachment = {
if ( if (
this.mergedConfig.useOneClickNsfw && this.mergedConfig.useOneClickNsfw &&
!this.showHidden && !this.showHidden &&
(this.type !== 'video' || this.mergedConfig.playVideosInModal) (this.attachment.type !== 'video' || this.mergedConfig.playVideosInModal)
) { ) {
this.openModal(event) this.openModal(event)
return return

View file

@ -6,7 +6,7 @@
@click="openModal" @click="openModal"
> >
<a <a
v-if="type !== 'html'" v-if="attachment.type !== 'html'"
class="placeholder" class="placeholder"
target="_blank" target="_blank"
:href="attachment.url" :href="attachment.url"
@ -70,7 +70,7 @@
:src="nsfwImage" :src="nsfwImage"
> >
<FAIcon <FAIcon
v-if="type === 'video'" v-if="attachment.type === 'video'"
class="play-icon" class="play-icon"
icon="play-circle" icon="play-circle"
/> />
@ -80,7 +80,7 @@
class="attachment-buttons" class="attachment-buttons"
> >
<button <button
v-if="type === 'flash' && flashLoaded" v-if="attachment.type === 'flash' && flashLoaded"
class="button-default attachment-button -transparent" class="button-default attachment-button -transparent"
:title="$t('status.attachment_stop_flash')" :title="$t('status.attachment_stop_flash')"
@click.prevent="stopFlash" @click.prevent="stopFlash"
@ -88,7 +88,7 @@
<FAIcon icon="stop" /> <FAIcon icon="stop" />
</button> </button>
<button <button
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'" v-if="attachment.description && size !== 'small' && !edit && attachment.type !== 'unknown'"
class="button-default attachment-button -transparent" class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_description')" :title="$t('status.show_attachment_description')"
@click.prevent="toggleDescription" @click.prevent="toggleDescription"
@ -96,7 +96,7 @@
<FAIcon icon="align-right" /> <FAIcon icon="align-right" />
</button> </button>
<button <button
v-if="!useModal && type !== 'unknown'" v-if="!useModal && attachment.type !== 'unknown'"
class="button-default attachment-button -transparent" class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_in_modal')" :title="$t('status.show_attachment_in_modal')"
@click.prevent="openModalForce" @click.prevent="openModalForce"
@ -138,7 +138,7 @@
</div> </div>
<a <a
v-if="type === 'image' && (!hidden || preloadImage)" v-if="attachment.type === 'image' && (!hidden || preloadImage)"
class="image-container" class="image-container"
:class="{'-hidden': hidden && preloadImage }" :class="{'-hidden': hidden && preloadImage }"
:href="attachment.url" :href="attachment.url"
@ -156,7 +156,7 @@
</a> </a>
<a <a
v-if="type === 'unknown' && !hidden" v-if="attachment.type === 'unknown' && !hidden"
class="placeholder-container" class="placeholder-container"
:href="attachment.url" :href="attachment.url"
target="_blank" target="_blank"
@ -173,7 +173,7 @@
<component <component
:is="videoTag" :is="videoTag"
v-if="type === 'video' && !hidden" v-if="attachment.type === 'video' && !hidden"
class="video-container" class="video-container"
:href="attachment.url" :href="attachment.url"
@click.stop.prevent="openModal" @click.stop.prevent="openModal"
@ -193,13 +193,13 @@
</component> </component>
<span <span
v-if="type === 'audio' && !hidden" v-if="attachment.type === 'audio' && !hidden"
class="audio-container" class="audio-container"
:href="attachment.url" :href="attachment.url"
@click.stop.prevent="openModal" @click.stop.prevent="openModal"
> >
<audio <audio
v-if="type === 'audio'" v-if="attachment.type === 'audio'"
:src="attachment.url" :src="attachment.url"
:alt="attachment.description" :alt="attachment.description"
:title="attachment.description" :title="attachment.description"
@ -210,7 +210,7 @@
</span> </span>
<div <div
v-if="type === 'html' && attachment.oembed" v-if="attachment.type === 'html' && attachment.oembed"
class="oembed-container" class="oembed-container"
@click.prevent="linkClicked" @click.prevent="linkClicked"
> >
@ -229,7 +229,7 @@
</div> </div>
<span <span
v-if="type === 'flash' && !hidden" v-if="attachment.type === 'flash' && !hidden"
class="flash-container" class="flash-container"
:href="attachment.url" :href="attachment.url"
@click.stop.prevent="openModal" @click.stop.prevent="openModal"

View file

@ -6,8 +6,6 @@ import StatusBody from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import fileType from 'src/services/file_type/file_type.service'
const ChatListItem = { const ChatListItem = {
name: 'ChatListItem', name: 'ChatListItem',
props: ['chat'], props: ['chat'],
@ -28,7 +26,7 @@ const ChatListItem = {
} }
const types = this.chat.lastMessage.attachments.map((file) => const types = this.chat.lastMessage.attachments.map((file) =>
fileType.fileType(file.mimetype), file.type,
) )
if (types.includes('video')) { if (types.includes('video')) {
return this.$t('file_type.video') return this.$t('file_type.video')

View file

@ -4,6 +4,8 @@ import Attachment from '../attachment/attachment.vue'
import { useMediaViewerStore } from 'src/stores/media_viewer.js' import { useMediaViewerStore } from 'src/stores/media_viewer.js'
const displayTypes = ["image", "video", "flash"]
const Gallery = { const Gallery = {
props: [ props: [
'attachments', 'attachments',
@ -45,20 +47,14 @@ const Gallery = {
: attachments : attachments
.reduce( .reduce(
(acc, attachment, i) => { (acc, attachment, i) => {
if (attachment.mimetype.includes('audio')) { if (attachment.type == 'audio') {
return [ return [
...acc, ...acc,
{ audio: true, items: [attachment] }, { audio: true, items: [attachment] },
{ items: [] }, { items: [] },
] ]
} }
if ( if (!displayTypes.includes(attachment.type)) {
!(
attachment.mimetype.includes('image') ||
attachment.mimetype.includes('video') ||
attachment.mimetype.includes('flash')
)
) {
return [ return [
...acc, ...acc,
{ minimal: true, items: [attachment] }, { minimal: true, items: [attachment] },

View file

@ -1,5 +1,4 @@
import Flash from 'src/components/flash/flash.vue' import Flash from 'src/components/flash/flash.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import PinchZoom from '../pinch_zoom/pinch_zoom.vue' import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
@ -59,9 +58,6 @@ const MediaModal = {
canNavigate() { canNavigate() {
return this.media.length > 1 return this.media.length > 1
}, },
type() {
return this.currentMedia ? this.getType(this.currentMedia) : null
},
swipeDisableClickThreshold() { swipeDisableClickThreshold() {
// If there is only one media, allow more mouse movements to close the modal // If there is only one media, allow more mouse movements to close the modal
// because there is less chance that the user wants to switch to another image // because there is less chance that the user wants to switch to another image
@ -69,9 +65,6 @@ const MediaModal = {
}, },
}, },
methods: { methods: {
getType(media) {
return fileTypeService.fileType(media.mimetype)
},
hide() { hide() {
// HACK: Closing immediately via a touch will cause the click // HACK: Closing immediately via a touch will cause the click
// to be processed on the content below the overlay // to be processed on the content below the overlay
@ -96,7 +89,7 @@ const MediaModal = {
? this.media.length - 1 ? this.media.length - 1
: this.currentIndex - 1 : this.currentIndex - 1
const newMedia = this.media[prevIndex] const newMedia = this.media[prevIndex]
if (this.getType(newMedia) === 'image') { if (newMedia.type === 'image') {
this.loading = true this.loading = true
} }
useMediaViewerStore().setCurrentMedia(newMedia) useMediaViewerStore().setCurrentMedia(newMedia)
@ -109,7 +102,7 @@ const MediaModal = {
? 0 ? 0
: this.currentIndex + 1 : this.currentIndex + 1
const newMedia = this.media[nextIndex] const newMedia = this.media[nextIndex]
if (this.getType(newMedia) === 'image') { if (newMedia.type === 'image') {
this.loading = true this.loading = true
} }
useMediaViewerStore().setCurrentMedia(newMedia) useMediaViewerStore().setCurrentMedia(newMedia)

View file

@ -5,7 +5,7 @@
@backdrop-clicked="hideIfNotSwiped" @backdrop-clicked="hideIfNotSwiped"
> >
<SwipeClick <SwipeClick
v-if="type === 'image'" v-if="currentMedia.type === 'image'"
ref="swipeClick" ref="swipeClick"
class="modal-image-container" class="modal-image-container"
:direction="swipeDirection" :direction="swipeDirection"
@ -36,13 +36,13 @@
</PinchZoom> </PinchZoom>
</SwipeClick> </SwipeClick>
<VideoAttachment <VideoAttachment
v-if="type === 'video'" v-if="currentMedia.type === 'video'"
class="modal-image" class="modal-image"
:attachment="currentMedia" :attachment="currentMedia"
:controls="true" :controls="true"
/> />
<audio <audio
v-if="type === 'audio'" v-if="currentMedia.type === 'audio'"
class="modal-image" class="modal-image"
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description" :alt="currentMedia.description"
@ -50,7 +50,7 @@
controls controls
/> />
<Flash <Flash
v-if="type === 'flash'" v-if="currentMedia.type === 'flash'"
class="modal-image" class="modal-image"
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description" :alt="currentMedia.description"

View file

@ -6,7 +6,6 @@ import DraftCloser from 'src/components/draft_closer/draft_closer.vue'
import Gallery from 'src/components/gallery/gallery.vue' import Gallery from 'src/components/gallery/gallery.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import genRandomSeed from '../../services/random_seed/random_seed.service.js' import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import statusPoster from '../../services/status_poster/status_poster.service.js' import statusPoster from '../../services/status_poster/status_poster.service.js'
@ -649,9 +648,6 @@ const PostStatusForm = {
this.$emit('resize') this.$emit('resize')
this.uploadingFiles = false this.uploadingFiles = false
}, },
type(fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
},
paste(e) { paste(e) {
this.autoPreview() this.autoPreview()
this.resize(e) this.resize(e)

View file

@ -2,8 +2,6 @@ import { mapGetters } from 'vuex'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import fileType from 'src/services/file_type/file_type.service'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faFile, faFile,
@ -104,7 +102,7 @@ const StatusBody = {
}, },
attachmentTypes() { attachmentTypes() {
return this.status.attachments.map((file) => return this.status.attachments.map((file) =>
fileType.fileType(file.mimetype), file.type,
) )
}, },
collapsedStatus() { collapsedStatus() {

View file

@ -1,5 +1,6 @@
import { parseLinkHeader } from '@web3-storage/parse-link-header' import { parseLinkHeader } from '@web3-storage/parse-link-header'
import escapeHtml from 'escape-html' import escapeHtml from 'escape-html'
import fileTypeService from '../file_type/file_type.service.js'
import punycode from 'punycode.js' import punycode from 'punycode.js'
import { isStatusNotification } from '../notification_utils/notification_utils.js' import { isStatusNotification } from '../notification_utils/notification_utils.js'
@ -297,6 +298,11 @@ export const parseAttachment = (data) => {
// output.meta = ??? missing // output.meta = ??? missing
} }
if (data.type !== 'unknown') {
output.type = data.type
} else {
output.type = fileTypeService.fileType(output.mimetype)
}
output.url = data.url output.url = data.url
output.large_thumb_url = data.preview_url output.large_thumb_url = data.preview_url
output.description = data.description output.description = data.description

View file

@ -1,7 +1,5 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import fileTypeService from '../services/file_type/file_type.service.js'
const supportedTypes = new Set(['image', 'video', 'audio', 'flash']) const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
export const useMediaViewerStore = defineStore('mediaViewer', { export const useMediaViewerStore = defineStore('mediaViewer', {
@ -13,8 +11,7 @@ export const useMediaViewerStore = defineStore('mediaViewer', {
actions: { actions: {
setMedia(attachments) { setMedia(attachments) {
const media = attachments.filter((attachment) => { const media = attachments.filter((attachment) => {
const type = fileTypeService.fileType(attachment.mimetype) return supportedTypes.has(attachment.type)
return supportedTypes.has(type)
}) })
this.media = media this.media = media