Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Remove extension from title if it's the same as filetype #1526

Merged
merged 4 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/components/VErrorSection/VErrorImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default defineComponent({
let image = errorItem.image
const errorImage: ErrorImage = {
...image,
originalTitle: image.title,
obulat marked this conversation as resolved.
Show resolved Hide resolved
src: require(`~/assets/error_images/${image.file}.jpg`),
alt: `error-images.${image.id}`,
license: image.license as License,
Expand Down
1 change: 1 addition & 0 deletions src/models/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Tag {
export interface Media {
id: string
title: string
originalTitle: string

creator?: string
creator_url?: string
Expand Down
6 changes: 3 additions & 3 deletions src/utils/attribution-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const extLink = (href: string, text: string) =>
*/
export type AttributableMedia = Pick<
Media,
| 'title'
| 'originalTitle'
| 'foreign_landing_url'
| 'creator'
| 'creator_url'
Expand Down Expand Up @@ -162,10 +162,10 @@ export const getAttribution = (

/* Title */

let title = mediaItem.title || tFn('generic-title')
let title = mediaItem.originalTitle || tFn('generic-title')
if (!isPlaintext && mediaItem.foreign_landing_url)
title = extLink(mediaItem.foreign_landing_url, title)
if (mediaItem.title) title = tFn('actual-title', { title })
if (mediaItem.originalTitle) title = tFn('actual-title', { title })

/* License */

Expand Down
76 changes: 73 additions & 3 deletions src/utils/decode-media-data.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,84 @@
import { title } from 'case'
import { title as titleCase } from 'case'

import { decodeData as decodeString } from '~/utils/decode-data'
import type { Media, Tag } from '~/models/media'
import type { MediaType } from '~/constants/media'
import { AUDIO, IMAGE, MODEL_3D, VIDEO } from '~/constants/media'

/**
* This interface is a subset of `Media` that types dictionaries sent by the API
* being decoded in the `decodeMediaData` function.
*/
interface ApiMedia extends Omit<Media, 'frontendMediaType' | 'title'> {
interface ApiMedia
extends Omit<Media, 'frontendMediaType' | 'title' | 'originalTitle'> {
title?: string
originalTitle?: string
}

const mediaTypeExtensions: Record<MediaType, string[]> = {
[IMAGE]: ['jpg', 'jpeg', 'png', 'gif', 'svg'],
[AUDIO]: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'aiff'],
[VIDEO]: ['mp4', 'webm', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'mpg', 'mpeg'],
[MODEL_3D]: ['fbx', 'obj', 'stl', 'dae', '3ds', 'blend', 'max', 'obj', 'ply'],
}

/**
* Compares the filetypes, taking into account different versions of the same
* filetype. For example, `.jpg` and `.jpeg` are considered the same filetype.
* @param extension - the extension of the file.
* @param filetype - the type of the file.
*/
const isFiletypeMatching = (extension: string, filetype?: string) => {
if (filetype === extension) {
return true
}
return (
filetype &&
['jpg', 'jpeg'].includes(filetype) &&
['jpg', 'jpeg'].includes(extension)
obulat marked this conversation as resolved.
Show resolved Hide resolved
)
}
const extractPartAfterLastDot = (str?: string) => {
if (!str) {
return ''
}
const parts = str.split('.')
return parts.length ? parts[parts.length - 1].toLowerCase() : ''
}

/**
* Strip the extension from title if it matches the filetype of the media.
* Since not all media records return filetype, we also try to guess the filetype
* from the url extension.
*/
const stripExtension = (
title: string,
mediaType: MediaType,
media: ApiMedia
) => {
const filetype = media.filetype ?? extractPartAfterLastDot(media.url)
const titleParts = title.split('.')
if (
mediaTypeExtensions[mediaType].includes(filetype) &&
isFiletypeMatching(extractPartAfterLastDot(title), filetype)
) {
titleParts.pop()
}
return titleParts.join('.')
}
/**
* Corrects the encoding of the media title, or uses the media type as the title.
* If the title has a file extension that matches media filetype, it will be stripped.
*/
const mediaTitle = (
media: ApiMedia,
mediaType: MediaType
): { title: string; originalTitle: string } => {
const originalTitle = decodeString(media.title) || titleCase(mediaType)
return {
originalTitle,
title: stripExtension(originalTitle, mediaType, media),
}
}

/**
Expand All @@ -25,8 +95,8 @@ export const decodeMediaData = <T extends Media>(
): T =>
({
...media,
...mediaTitle(media, mediaType),
frontendMediaType: mediaType,
title: decodeString(media.title) || title(mediaType),
creator: decodeString(media.creator),
// TODO: remove `?? []`
tags: (media.tags ?? ([] as Tag[])).map((tag) => ({
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions test/unit/specs/utils/attribution-html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const i18n = new Vuei18n({
})

const mediaItem: AttributableMedia = {
title: 'Title',
originalTitle: 'Title',
foreign_landing_url: 'https://foreign.landing/url',
creator: 'Creator',
creator_url: 'https://creator/url',
Expand Down Expand Up @@ -44,7 +44,7 @@ describe('getAttribution', () => {
)

it('uses generic title if not known', () => {
const mediaItemNoTitle = { ...mediaItem, title: '' }
const mediaItemNoTitle = { ...mediaItem, originalTitle: '' }
const attrText = getAttribution(mediaItemNoTitle, i18n, {
isPlaintext: true,
})
Expand Down
81 changes: 80 additions & 1 deletion test/unit/specs/utils/decode-image-data.spec.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,100 @@
import { decodeMediaData } from '~/utils/decode-media-data'
import { IMAGE } from '~/constants/media'

const requiredFields = {
id: 'id',
url: 'https://example.com/image.jpg',
foreign_landing_url: 'https://example.com',
license: 'by',
license_version: '4.0',
attribution: 'Attribution',

category: null,
provider: 'provider',

detail_url: 'url',
related_url: 'url',

tags: [],
}

describe('decodeImageData', () => {
it('returns empty string for empty string', () => {
it('decodes symbols correctly', () => {
const data = {
...requiredFields,
creator: 'S\\xe3',
title: 'S\\xe9',
tags: [{ name: 'ma\\xdf' }],
}

const expected = {
...requiredFields,
title: 'Sé',
originalTitle: 'Sé',
creator: 'Sã',
tags: [{ name: 'maß' }],
frontendMediaType: IMAGE,
}

expect(decodeMediaData(data, IMAGE)).toEqual(expected)
})

it('strips the extension if the same as media filetype', () => {
const data = {
...requiredFields,
creator: 'Creator',
title: 'Image.JPEG',
filetype: 'jpg',
}

const expected = {
...requiredFields,
title: 'Image',
originalTitle: 'Image.JPEG',
creator: 'Creator',
filetype: 'jpg',
frontendMediaType: IMAGE,
}

expect(decodeMediaData(data, IMAGE)).toEqual(expected)
})

it('strips the extension if the same as url extension', () => {
const data = {
...requiredFields,
url: 'https://example.com/image.jpg',
creator: 'Creator',
title: 'Image.JPG',
}

const expected = {
...requiredFields,
title: 'Image',
originalTitle: 'Image.JPG',
creator: 'Creator',
frontendMediaType: IMAGE,
}

expect(decodeMediaData(data, IMAGE)).toEqual(expected)
})

it('does not strip the extension if different from filetype in url extension', () => {
const data = {
...requiredFields,
url: 'https://example.com/image.png',
creator: 'Creator',
title: 'Image.JPG',
}

const expected = {
...requiredFields,
url: 'https://example.com/image.png',
title: 'Image.JPG',
originalTitle: 'Image.JPG',
creator: 'Creator',
frontendMediaType: IMAGE,
}

expect(decodeMediaData(data, IMAGE)).toEqual(expected)
})
})