release: v1.2.0 — unified images/ storage and legacy migration
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Consolidate local images under data/images/, add docker migrate CLI, startup index sync with orphan cleanup, and remove upload folder UI/API.
This commit is contained in:
1 parent
3dee0437e6
commit
469d3be735
50 files changed
+1273
-956
No files matched your search
@@ -12,20 +12,13 @@ export function useImageList() {
|
||||
const totalPages = ref(1)
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const folderTotal = ref<number | null>(null)
|
||||
const totalCount = ref<number | null>(null)
|
||||
const loadingTotal = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const activeSearch = ref('')
|
||||
const activeFolder = ref('all')
|
||||
const activeStorageBackend = ref('all')
|
||||
const storageBackendOptions = ref<Array<{ id: string, name: string }>>([])
|
||||
|
||||
function folderQueryParams(): Record<string, string> {
|
||||
const folder = activeFolder.value.trim()
|
||||
if (!folder || folder === 'all') return {}
|
||||
return { folder }
|
||||
}
|
||||
|
||||
function storageQueryParams(): Record<string, string> {
|
||||
const backendId = activeStorageBackend.value.trim()
|
||||
if (!backendId || backendId === 'all') return {}
|
||||
@@ -33,16 +26,7 @@ export function useImageList() {
|
||||
}
|
||||
|
||||
function listQueryParams(): Record<string, string> {
|
||||
return {
|
||||
...folderQueryParams(),
|
||||
...storageQueryParams()
|
||||
}
|
||||
}
|
||||
|
||||
function matchesActiveFolder(key: string) {
|
||||
const folder = activeFolder.value.trim()
|
||||
if (!folder || folder === 'all') return true
|
||||
return key.startsWith(`${folder}/`)
|
||||
return storageQueryParams()
|
||||
}
|
||||
|
||||
function matchesActiveStorage(item: ImageItem) {
|
||||
@@ -74,9 +58,9 @@ export function useImageList() {
|
||||
credentials: 'include',
|
||||
query: listQueryParams()
|
||||
})
|
||||
folderTotal.value = data.total
|
||||
totalCount.value = data.total
|
||||
} catch {
|
||||
folderTotal.value = null
|
||||
totalCount.value = null
|
||||
} finally {
|
||||
loadingTotal.value = false
|
||||
}
|
||||
@@ -138,27 +122,13 @@ export function useImageList() {
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
|
||||
async function initializeList(folder = 'all') {
|
||||
const next = folder.trim() || 'all'
|
||||
activeFolder.value = next
|
||||
async function initializeList() {
|
||||
page.value = 1
|
||||
activeSearch.value = ''
|
||||
searchQuery.value = ''
|
||||
await Promise.all([fetchList(1), fetchTotal(), loadStorageBackendOptions()])
|
||||
}
|
||||
|
||||
async function setActiveFolder(folder: string) {
|
||||
const next = folder.trim() || 'all'
|
||||
if (next === activeFolder.value) return
|
||||
activeFolder.value = next
|
||||
page.value = 1
|
||||
if (activeSearch.value) {
|
||||
await Promise.all([fetchSearch(1), fetchTotal()])
|
||||
} else {
|
||||
await Promise.all([fetchList(1), fetchTotal()])
|
||||
}
|
||||
}
|
||||
|
||||
async function setActiveStorageBackend(backendId: string) {
|
||||
const next = backendId.trim() || 'all'
|
||||
if (next === activeStorageBackend.value) return
|
||||
@@ -192,14 +162,12 @@ export function useImageList() {
|
||||
}
|
||||
|
||||
function prependItems(newItems: ImageItem[]) {
|
||||
const matched = newItems.filter(item =>
|
||||
matchesActiveFolder(item.key) && matchesActiveStorage(item)
|
||||
)
|
||||
const matched = newItems.filter(item => matchesActiveStorage(item))
|
||||
if (!matched.length || page.value !== 1) return
|
||||
items.value = [...matched, ...items.value]
|
||||
total.value += matched.length
|
||||
if (folderTotal.value !== null) {
|
||||
folderTotal.value += matched.length
|
||||
if (totalCount.value !== null) {
|
||||
totalCount.value += matched.length
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +178,8 @@ export function useImageList() {
|
||||
if (removed > 0) {
|
||||
total.value = Math.max(0, total.value - removed)
|
||||
}
|
||||
if (folderTotal.value !== null && removed > 0) {
|
||||
folderTotal.value = Math.max(0, folderTotal.value - removed)
|
||||
if (totalCount.value !== null && removed > 0) {
|
||||
totalCount.value = Math.max(0, totalCount.value - removed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,34 +191,30 @@ export function useImageList() {
|
||||
})
|
||||
|
||||
const listSummary = computed(() => {
|
||||
const folder = activeFolder.value.trim() || 'all'
|
||||
const folderLabel = folder === 'all' ? t('stats.allFolders') : folder
|
||||
const storageLabel = activeStorageLabel.value
|
||||
if (loadingTotal.value && folderTotal.value === null) {
|
||||
if (loadingTotal.value && totalCount.value === null) {
|
||||
return storageLabel
|
||||
? `${folderLabel} · ${storageLabel} · ${t('stats.counting')}`
|
||||
: `${folderLabel} · ${t('stats.counting')}`
|
||||
? `${storageLabel} · ${t('stats.counting')}`
|
||||
: t('stats.counting')
|
||||
}
|
||||
if (activeSearch.value) {
|
||||
if (storageLabel) {
|
||||
return t('stats.summarySearchStorage', {
|
||||
folder: folderLabel,
|
||||
storage: storageLabel,
|
||||
q: activeSearch.value,
|
||||
total: total.value
|
||||
total: total.value,
|
||||
storage: storageLabel
|
||||
})
|
||||
}
|
||||
return t('stats.summarySearch', { folder: folderLabel, q: activeSearch.value, total: total.value })
|
||||
return t('stats.summarySearch', { q: activeSearch.value, total: total.value })
|
||||
}
|
||||
const count = folderTotal.value === null ? total.value : folderTotal.value
|
||||
const count = totalCount.value === null ? total.value : totalCount.value
|
||||
if (storageLabel) {
|
||||
return t('stats.summaryTotalStorage', {
|
||||
folder: folderLabel,
|
||||
storage: storageLabel,
|
||||
total: count
|
||||
total: count,
|
||||
storage: storageLabel
|
||||
})
|
||||
}
|
||||
return t('stats.summaryTotal', { folder: folderLabel, total: count })
|
||||
return t('stats.summaryTotal', { total: count })
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -259,7 +223,6 @@ export function useImageList() {
|
||||
totalPages,
|
||||
total,
|
||||
loading,
|
||||
activeFolder,
|
||||
activeStorageBackend,
|
||||
storageBackendOptions,
|
||||
searchQuery,
|
||||
@@ -270,7 +233,6 @@ export function useImageList() {
|
||||
fetchTotal,
|
||||
refreshList,
|
||||
initializeList,
|
||||
setActiveFolder,
|
||||
setActiveStorageBackend,
|
||||
loadStorageBackendOptions,
|
||||
submitSearch,
|
||||
|
||||
@@ -17,8 +17,7 @@ export function useImageUpload() {
|
||||
|
||||
async function uploadFiles(
|
||||
files: File[],
|
||||
folder = 'images',
|
||||
options?: { notify?: boolean, includeFolder?: boolean }
|
||||
options?: { notify?: boolean }
|
||||
) {
|
||||
if (uploading.value) return
|
||||
|
||||
@@ -55,9 +54,6 @@ export function useImageUpload() {
|
||||
}))
|
||||
|
||||
const formData = new FormData()
|
||||
if (options?.includeFolder !== false) {
|
||||
formData.append('folder', folder.trim() || 'images')
|
||||
}
|
||||
for (const file of prepared) {
|
||||
formData.append('files', file, file.name)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const STORAGE_KEY = 'pichost.upload-preferences'
|
||||
export type CopyFormat = 'url' | 'markdown' | 'html'
|
||||
|
||||
export interface UploadPreferences {
|
||||
defaultFolder: string
|
||||
compressEnabled: boolean
|
||||
autoCopyMarkdown: boolean
|
||||
copyFormat: CopyFormat
|
||||
@@ -11,7 +10,6 @@ export interface UploadPreferences {
|
||||
}
|
||||
|
||||
const DEFAULTS: UploadPreferences = {
|
||||
defaultFolder: 'images',
|
||||
compressEnabled: false,
|
||||
autoCopyMarkdown: false,
|
||||
copyFormat: 'markdown',
|
||||
@@ -36,9 +34,6 @@ function readStored(): UploadPreferences {
|
||||
if (!raw) return { ...DEFAULTS }
|
||||
const parsed = JSON.parse(raw) as Partial<UploadPreferences>
|
||||
return {
|
||||
defaultFolder: typeof parsed.defaultFolder === 'string' && parsed.defaultFolder.trim()
|
||||
? parsed.defaultFolder.trim()
|
||||
: DEFAULTS.defaultFolder,
|
||||
compressEnabled: parsed.compressEnabled === true,
|
||||
autoCopyMarkdown: parsed.autoCopyMarkdown === true,
|
||||
copyFormat: isCopyFormat(parsed.copyFormat) ? parsed.copyFormat : DEFAULTS.copyFormat,
|
||||
@@ -55,7 +50,6 @@ function writeStored(prefs: UploadPreferences) {
|
||||
}
|
||||
|
||||
export function useUploadPreferences() {
|
||||
const defaultFolder = useState('upload-pref-folder', () => DEFAULTS.defaultFolder)
|
||||
const compressEnabled = useState('upload-pref-compress', () => DEFAULTS.compressEnabled)
|
||||
const autoCopyMarkdown = useState('upload-pref-auto-md', () => DEFAULTS.autoCopyMarkdown)
|
||||
const copyFormat = useState<CopyFormat>('upload-pref-copy-format', () => DEFAULTS.copyFormat)
|
||||
@@ -64,7 +58,6 @@ export function useUploadPreferences() {
|
||||
|
||||
function loadPreferences() {
|
||||
const stored = readStored()
|
||||
defaultFolder.value = stored.defaultFolder
|
||||
compressEnabled.value = stored.compressEnabled
|
||||
autoCopyMarkdown.value = stored.autoCopyMarkdown
|
||||
copyFormat.value = stored.copyFormat
|
||||
@@ -74,7 +67,6 @@ export function useUploadPreferences() {
|
||||
|
||||
function savePreferences() {
|
||||
writeStored({
|
||||
defaultFolder: defaultFolder.value.trim() || DEFAULTS.defaultFolder,
|
||||
compressEnabled: compressEnabled.value,
|
||||
autoCopyMarkdown: autoCopyMarkdown.value,
|
||||
copyFormat: copyFormat.value,
|
||||
@@ -82,14 +74,13 @@ export function useUploadPreferences() {
|
||||
})
|
||||
}
|
||||
|
||||
watch([defaultFolder, compressEnabled, autoCopyMarkdown, copyFormat, clientWebpQuality], () => {
|
||||
watch([compressEnabled, autoCopyMarkdown, copyFormat, clientWebpQuality], () => {
|
||||
if (loaded.value) {
|
||||
savePreferences()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
defaultFolder,
|
||||
compressEnabled,
|
||||
autoCopyMarkdown,
|
||||
copyFormat,
|
||||
|
||||
Reference in new issue
Block a user