|
| 1 | +<template> |
| 2 | + <img |
| 3 | + :src="currentSrc" |
| 4 | + :alt="alt" |
| 5 | + :class="imageClass" |
| 6 | + @load="handleLoad" |
| 7 | + @error="handleError" |
| 8 | + v-bind="$attrs" |
| 9 | + /> |
| 10 | +</template> |
| 11 | + |
| 12 | +<script setup> |
| 13 | +import { ref, watch, computed } from 'vue' |
| 14 | +
|
| 15 | +const props = defineProps({ |
| 16 | + image: { |
| 17 | + type: [Object, String], |
| 18 | + required: true |
| 19 | + }, |
| 20 | + width: { |
| 21 | + type: Number, |
| 22 | + default: 1280, |
| 23 | + // Use Commons standard thumbnail sizes for best performance (pre-cached): |
| 24 | + // 320, 640, 800, 1024, 1280, 2560 |
| 25 | + // These sizes are already generated and cached by Wikimedia Commons |
| 26 | + }, |
| 27 | + alt: { |
| 28 | + type: String, |
| 29 | + default: '' |
| 30 | + }, |
| 31 | + imageClass: { |
| 32 | + type: String, |
| 33 | + default: '' |
| 34 | + } |
| 35 | +}) |
| 36 | +
|
| 37 | +const emit = defineEmits(['load', 'error']) |
| 38 | +
|
| 39 | +const attemptIndex = ref(0) |
| 40 | +const hasLoaded = ref(false) |
| 41 | +
|
| 42 | +const imageName = computed(() => { |
| 43 | + return props.image?.entry?.name || props.image?.name || props.image |
| 44 | +}) |
| 45 | +
|
| 46 | +const encodedName = computed(() => { |
| 47 | + return encodeURIComponent(imageName.value) |
| 48 | +}) |
| 49 | +
|
| 50 | +// Use Special:Redirect for maximum compatibility |
| 51 | +// This works for all file types including TIFF, handles redirects, and performs format conversion |
| 52 | +const urlStrategies = computed(() => { |
| 53 | + const name = encodedName.value |
| 54 | + const width = props.width |
| 55 | + |
| 56 | + return [ |
| 57 | + // Strategy 1: Special:Redirect/file with width (works universally, handles TIFF → JPG conversion) |
| 58 | + `//commons.wikimedia.org/w/index.php?title=Special:Redirect/file/${name}&width=${width}`, |
| 59 | + |
| 60 | + // Strategy 2: Special:Redirect/file without width (full size fallback) |
| 61 | + `//commons.wikimedia.org/w/index.php?title=Special:Redirect/file/${name}` |
| 62 | + ] |
| 63 | +}) |
| 64 | +
|
| 65 | +const currentSrc = computed(() => { |
| 66 | + return urlStrategies.value[attemptIndex.value] |
| 67 | +}) |
| 68 | +
|
| 69 | +const handleLoad = () => { |
| 70 | + hasLoaded.value = true |
| 71 | + emit('load') |
| 72 | +} |
| 73 | +
|
| 74 | +const handleError = () => { |
| 75 | + // Try next strategy if available |
| 76 | + if (attemptIndex.value < urlStrategies.value.length - 1) { |
| 77 | + attemptIndex.value++ |
| 78 | + } else { |
| 79 | + // All strategies failed |
| 80 | + emit('error') |
| 81 | + } |
| 82 | +} |
| 83 | +
|
| 84 | +// Reset attempt index when image changes |
| 85 | +watch(imageName, () => { |
| 86 | + attemptIndex.value = 0 |
| 87 | + hasLoaded.value = false |
| 88 | +}) |
| 89 | +</script> |
| 90 | +
|
0 commit comments