mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
feat(metadata): add FB2 cover extraction and fix stale staging cover
- add cover-fb2.ts: extract cover from <coverpage>/<binary> in FB2 XML - add fb2-parser.ts: extract title, authors, series, language, year, genres from FB2 title-info - wire FB2 into cover.ts extractor switch and metadata.service.ts extractMetadata - wire FB2 into staging-metadata.service.ts for staging ingestion - add fb2 to METADATA_FORMATS in scanner.service.ts and upload-processor.service.ts - add MetadataService.saveExtractedCoverBytes() for saving cover bytes directly - fix staging finalize: copy staging cover to book cover dir on finalize, overwriting stale covers - add Regenerate Cover button to CoverEditorPanel with permission gate and cache busting
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onUnmounted } from 'vue'
|
||||
import { ImagePlus, Link, RotateCcw, Upload, X } from 'lucide-vue-next'
|
||||
import { Image, ImagePlus, Link, Loader2, RotateCcw, Upload, X } from 'lucide-vue-next'
|
||||
import type { BookDetail } from '@projectx/types'
|
||||
import { hideOnError } from '../../../lib/metadata-fetch'
|
||||
import { useCoverEditor } from '../../../composables/useCoverEditor'
|
||||
import { useCoverVersions } from '../../../composables/useCoverVersions'
|
||||
import { usePermissions } from '@/features/auth/composables/usePermissions'
|
||||
|
||||
const props = defineProps<{ book: BookDetail }>()
|
||||
const emit = defineEmits<{ coverChanged: ['extracted' | 'custom' | null] }>()
|
||||
@@ -12,7 +13,22 @@ const emit = defineEmits<{ coverChanged: ['extracted' | 'custom' | null] }>()
|
||||
const bookIdRef = computed(() => props.book.id)
|
||||
const { uploading, error, previewSrc, pendingFile, pendingUrl, selectFile, setUrl, clearPending, confirm, revert } = useCoverEditor(bookIdRef)
|
||||
|
||||
const { coverUrl } = useCoverVersions()
|
||||
const { coverUrl, bumpVersion } = useCoverVersions()
|
||||
const { hasPermission } = usePermissions()
|
||||
|
||||
const reExtractingCover = ref(false)
|
||||
|
||||
async function reExtractCover() {
|
||||
if (reExtractingCover.value) return
|
||||
reExtractingCover.value = true
|
||||
try {
|
||||
await fetch(`/api/v1/books/${props.book.id}/re-extract-cover`, { method: 'POST' })
|
||||
bumpVersion(props.book.id)
|
||||
emit('coverChanged', 'extracted')
|
||||
} finally {
|
||||
reExtractingCover.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const mode = ref<'file' | 'url'>('file')
|
||||
const urlInput = ref('')
|
||||
@@ -152,6 +168,16 @@ onUnmounted(() => clearTimeout(debounceTimer))
|
||||
<RotateCcw class="size-3" />
|
||||
Revert to original
|
||||
</button>
|
||||
<button
|
||||
v-if="hasPermission('library_edit_metadata')"
|
||||
class="flex items-center justify-center gap-1.5 w-full h-8 rounded-lg border border-input bg-background text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors disabled:opacity-50"
|
||||
:disabled="reExtractingCover"
|
||||
@click="reExtractCover"
|
||||
>
|
||||
<Loader2 v-if="reExtractingCover" class="size-3 animate-spin" />
|
||||
<Image v-else class="size-3" />
|
||||
Regenerate Cover
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user