feat: add image management functionality
- Implemented image folder repository with CRUD operations. - Added image reference repository for managing image references to articles. - Created image repository for handling image assets, including listing, inserting, updating, and deleting images. - Introduced image usage repository to track storage usage and quotas for tenants. - Added SQL queries for image assets, folders, references, and usage. - Developed image handler for HTTP endpoints to manage images and folders. - Created database migration scripts for image-related tables and structures.
This commit is contained in:
@@ -36,8 +36,10 @@ const publishPlatforms = ref<string[]>([]);
|
||||
const initialPublishPlatforms = ref<string[]>([]);
|
||||
const coverEnabled = ref(false);
|
||||
const coverAssetUrl = ref("");
|
||||
const coverImageAssetId = ref<number | null>(null);
|
||||
const coverFileName = ref("");
|
||||
const initialCoverAssetUrl = ref("");
|
||||
const initialCoverImageAssetId = ref<number | null>(null);
|
||||
const coverPickerOpen = ref(false);
|
||||
const leaveModalOpen = ref(false);
|
||||
const publishModalOpen = ref(false);
|
||||
@@ -83,8 +85,10 @@ watch(
|
||||
publishPlatforms.value = normalizePublishPlatformIds(detail.platforms ?? []);
|
||||
initialPublishPlatforms.value = [...publishPlatforms.value];
|
||||
coverAssetUrl.value = resolveApiURL(detail.cover_asset_url);
|
||||
coverImageAssetId.value = detail.cover_image_asset_id ?? null;
|
||||
coverFileName.value = deriveCoverFileName(coverAssetUrl.value);
|
||||
initialCoverAssetUrl.value = coverAssetUrl.value;
|
||||
initialCoverImageAssetId.value = coverImageAssetId.value;
|
||||
coverEnabled.value = Boolean(coverAssetUrl.value);
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -99,12 +103,14 @@ const normalizedCoverValue = computed(() => {
|
||||
}
|
||||
return coverAssetUrl.value.trim();
|
||||
});
|
||||
const referencedImageAssetIds = computed(() => extractReferencedImageAssetIds(markdown.value));
|
||||
const hasChanges = computed(
|
||||
() =>
|
||||
title.value.trim() !== initialTitle.value.trim() ||
|
||||
markdown.value !== initialMarkdown.value ||
|
||||
serializePlatformSelection(publishPlatforms.value) !== serializePlatformSelection(initialPublishPlatforms.value) ||
|
||||
normalizedCoverValue.value !== initialCoverAssetUrl.value.trim(),
|
||||
normalizedCoverValue.value !== initialCoverAssetUrl.value.trim() ||
|
||||
coverImageAssetId.value !== initialCoverImageAssetId.value,
|
||||
);
|
||||
|
||||
watch(
|
||||
@@ -124,6 +130,8 @@ const saveMutation = useMutation({
|
||||
markdown_content: markdown.value,
|
||||
platforms: normalizePublishPlatformIds(publishPlatforms.value),
|
||||
cover_asset_url: normalizedCoverValue.value,
|
||||
referenced_image_asset_ids: referencedImageAssetIds.value,
|
||||
cover_image_asset_id: coverImageAssetId.value,
|
||||
}),
|
||||
onSuccess: async (data) => {
|
||||
message.success(t("article.editor.messages.saved"));
|
||||
@@ -134,8 +142,10 @@ const saveMutation = useMutation({
|
||||
publishPlatforms.value = normalizePublishPlatformIds(data.platforms ?? []);
|
||||
initialPublishPlatforms.value = [...publishPlatforms.value];
|
||||
coverAssetUrl.value = resolveApiURL(data.cover_asset_url);
|
||||
coverImageAssetId.value = data.cover_image_asset_id ?? null;
|
||||
coverFileName.value = deriveCoverFileName(coverAssetUrl.value);
|
||||
initialCoverAssetUrl.value = coverAssetUrl.value;
|
||||
initialCoverImageAssetId.value = coverImageAssetId.value;
|
||||
coverEnabled.value = coverRequired.value || Boolean(coverAssetUrl.value);
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["articles"] }),
|
||||
@@ -199,14 +209,16 @@ async function uploadEditorImage(file: File): Promise<string> {
|
||||
return result.url;
|
||||
}
|
||||
|
||||
function handleCoverPicked(payload: { url: string; fileName: string }): void {
|
||||
function handleCoverPicked(payload: { url: string; fileName: string; assetId?: number | null }): void {
|
||||
coverAssetUrl.value = payload.url;
|
||||
coverImageAssetId.value = payload.assetId ?? null;
|
||||
coverFileName.value = payload.fileName;
|
||||
coverEnabled.value = true;
|
||||
}
|
||||
|
||||
function handleRemoveCover(): void {
|
||||
coverAssetUrl.value = "";
|
||||
coverImageAssetId.value = null;
|
||||
coverFileName.value = "";
|
||||
}
|
||||
|
||||
@@ -314,6 +326,20 @@ function stripLeadingTitleHeading(titleValue: string, markdownValue: string): st
|
||||
return normalizedMarkdown.slice(match[0].length).replace(/^\s*\r?\n/, "");
|
||||
}
|
||||
|
||||
function extractReferencedImageAssetIds(markdownValue: string): number[] {
|
||||
const ids = new Set<number>();
|
||||
const matcher = /data-asset-id=(?:"|')(\d+)(?:"|')/g;
|
||||
|
||||
for (const match of markdownValue.matchAll(matcher)) {
|
||||
const id = Number.parseInt(match[1] ?? "", 10);
|
||||
if (Number.isInteger(id) && id > 0) {
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return [...ids];
|
||||
}
|
||||
|
||||
function serializePlatformSelection(platformIds: string[]): string {
|
||||
return normalizePublishPlatformIds(platformIds)
|
||||
.sort()
|
||||
@@ -467,6 +493,7 @@ function serializePlatformSelection(platformIds: string[]): string {
|
||||
:platform-ids="publishPlatforms"
|
||||
:current-url="coverAssetUrl"
|
||||
:current-file-name="coverFileName"
|
||||
:current-asset-id="coverImageAssetId"
|
||||
@confirmed="handleCoverPicked"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user