diff --git a/src/api/posts.ts b/src/api/posts.ts index 212c861..9830649 100644 --- a/src/api/posts.ts +++ b/src/api/posts.ts @@ -11,11 +11,12 @@ export interface Attachment { export async function uploadAttachment( authToken: string, base64data: string, + mediaType: string, ): Promise { const url = `${BACKEND_URL}/api/v1/media` const response = await http(url, { method: "POST", - json: { file: base64data }, + json: { file: base64data, media_type: mediaType }, authToken, }) const data = await response.json() diff --git a/src/components/PostEditor.vue b/src/components/PostEditor.vue index b5fa8af..98b3a08 100644 --- a/src/components/PostEditor.vue +++ b/src/components/PostEditor.vue @@ -230,10 +230,11 @@ async function onAttachmentUpload(event: Event) { return } const imageDataUrl = await fileToDataUrl(files[0]) - const imageBase64 = dataUrlToBase64(imageDataUrl) + const imageData = dataUrlToBase64(imageDataUrl) const attachment = await uploadAttachment( ensureAuthToken(), - imageBase64, + imageData.data, + imageData.mediaType, ) attachments.push(attachment) } diff --git a/src/utils/upload.ts b/src/utils/upload.ts index 8ee60a5..0f760b7 100644 --- a/src/utils/upload.ts +++ b/src/utils/upload.ts @@ -1,3 +1,5 @@ +const DATA_URL_REGEXP = /^data:(\w+\/[-+.\w]+);base64,([a-zA-Z0-9+/]+={0,2})$/ + export async function fileToDataUrl(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader() @@ -10,6 +12,10 @@ export async function fileToDataUrl(file: File): Promise { }) } -export function dataUrlToBase64(dataUrl: string): string { - return dataUrl.replace(/^data:.+;base64,/, "") +export function dataUrlToBase64(dataUrl: string): { mediaType: string, data: string } { + const match = dataUrl.trim().match(DATA_URL_REGEXP) + if (!match) { + throw new Error(`invalid data URL ${dataUrl}`) + } + return { mediaType: match[1], data: match[2] } } diff --git a/src/views/ProfileForm.vue b/src/views/ProfileForm.vue index b0789fc..8a0734f 100644 --- a/src/views/ProfileForm.vue +++ b/src/views/ProfileForm.vue @@ -163,7 +163,8 @@ async function onFilePicked(fieldName: "avatar" | "header", event: Event) { } const imageDataUrl = await fileToDataUrl(files[0]) images[fieldName] = imageDataUrl - form[fieldName] = dataUrlToBase64(imageDataUrl) + const imageData = dataUrlToBase64(imageDataUrl) + form[fieldName] = imageData.data } function isValidExtraField(index: number): boolean { diff --git a/tests/unit/upload.spec.ts b/tests/unit/upload.spec.ts new file mode 100644 index 0000000..35b773f --- /dev/null +++ b/tests/unit/upload.spec.ts @@ -0,0 +1,10 @@ +import { expect } from "chai" +import { dataUrlToBase64 } from "@/utils/upload" + +describe("Data URL", () => { + it("Should parse data URL", () => { + const dataUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9ImZlYXRoZXIgZmVhdGhlci1zZWFyY2giPjxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjgiPjwvY2lyY2xlPjxsaW5lIHgxPSIyMSIgeTE9IjIxIiB4Mj0iMTYuNjUiIHkyPSIxNi42NSI+PC9saW5lPjwvc3ZnPg==" + const { data, mediaType } = dataUrlToBase64(dataUrl) + expect(mediaType).to.equal("image/svg+xml") + }) +})