에디터 미디어 업로드 검증 로직 분리
This commit is contained in:
@@ -23,12 +23,14 @@ import {
|
||||
} from '../../lib/markdown-editor-history.js'
|
||||
import {
|
||||
createMediaBlockMarkdown,
|
||||
findUploadSizeLimitError,
|
||||
getGallerySelectionOrder as getGallerySelectionOrderFromSelection,
|
||||
getMediaItemKind,
|
||||
getMediaPickerPreviewUrl,
|
||||
isMediaItemSelectableForTarget,
|
||||
isUploadFileAllowedForMediaTarget,
|
||||
resolveSelectedMediaItems,
|
||||
resolveUploadFetchErrorMessage,
|
||||
toggleMediaSelectionState
|
||||
} from '../../lib/markdown-editor-media.js'
|
||||
import {
|
||||
@@ -49,9 +51,6 @@ import {
|
||||
DEFAULT_UPLOAD_MAX_FILE_COUNT,
|
||||
DEFAULT_UPLOAD_MAX_TOTAL_SIZE,
|
||||
formatUploadSizeLimit,
|
||||
getUploadFilesTotalSize,
|
||||
getMaxUploadBytesForKind,
|
||||
getUploadKind,
|
||||
normalizeUploadMaxFileCount,
|
||||
normalizeUploadMaxTotalSize
|
||||
} from '../../lib/upload-size-limit.js'
|
||||
@@ -3268,66 +3267,18 @@ const applyMediaSelection = () => {
|
||||
closeMediaPicker()
|
||||
}
|
||||
|
||||
/**
|
||||
* 업로드 API 오류 메시지를 사용자용 문장으로 변환한다.
|
||||
* @param {unknown} error - fetch 오류
|
||||
* @returns {string} 오류 메시지
|
||||
*/
|
||||
const resolveUploadFetchErrorMessage = (error) => {
|
||||
const responseMessage = error?.data?.message || error?.response?._data?.message
|
||||
|
||||
if (typeof responseMessage === 'string' && responseMessage.trim()) {
|
||||
return responseMessage.trim()
|
||||
}
|
||||
|
||||
if (error?.statusCode === 413 || error?.status === 413) {
|
||||
return '업로드 가능한 파일 크기를 초과했습니다.'
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
return '파일 업로드에 실패했습니다.'
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 파일이 업로드 한도를 넘지 않는지 검사한다.
|
||||
* @param {FileList|Array<File>} files - 업로드 파일 목록
|
||||
* @returns {string|null} 초과 시 오류 메시지, 아니면 null
|
||||
*/
|
||||
const findUploadSizeLimitError = (files) => {
|
||||
const fileList = Array.from(files || [])
|
||||
|
||||
if (fileList.length > uploadMaxFileCount.value) {
|
||||
return `한 번에 업로드할 수 있는 파일은 최대 ${uploadMaxFileCount.value}개입니다.`
|
||||
}
|
||||
|
||||
const uploadTotalSize = getUploadFilesTotalSize(fileList)
|
||||
|
||||
if (uploadTotalSize > uploadMaxTotalSize.value) {
|
||||
return `한 번에 업로드 가능한 전체 용량을 초과했습니다. (최대 ${formatUploadSizeLimit(uploadMaxTotalSize.value)})`
|
||||
}
|
||||
|
||||
for (const file of fileList) {
|
||||
const uploadKind = getUploadKind(file.type, file.name)
|
||||
const maxBytes = getMaxUploadBytesForKind(uploadKind, uploadSizeLimits.value)
|
||||
|
||||
if (file.size > maxBytes) {
|
||||
return `파일 크기가 너무 큽니다. (최대 ${formatUploadSizeLimit(maxBytes)})`
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 미디어 파일을 업로드한다.
|
||||
* @param {FileList|Array<File>} files - 업로드 파일 목록
|
||||
* @returns {Promise<Array<Object>>} 업로드된 파일 목록
|
||||
*/
|
||||
const uploadMediaFiles = async (files) => {
|
||||
const sizeLimitError = findUploadSizeLimitError(files)
|
||||
const sizeLimitError = findUploadSizeLimitError({
|
||||
files,
|
||||
maxFileCount: uploadMaxFileCount.value,
|
||||
maxTotalSize: uploadMaxTotalSize.value,
|
||||
uploadSizeLimits: uploadSizeLimits.value
|
||||
})
|
||||
|
||||
if (sizeLimitError) {
|
||||
throw new Error(sizeLimitError)
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree`, 관리자 상단 네비 평면 표용 `flattenNavigationEditorWrappers` |
|
||||
| lib/markdown-content-normalizer.js | 관리자 Markdown-first 전환 후 레거시 블록 배열·객체 본문 값을 저장용 마크다운 문자열로 변환 |
|
||||
| lib/markdown-editor-history.js | 관리자 Markdown-first 에디터 undo/redo 히스토리 항목 생성, 레거시 문자열 항목 본문 추출, 선택 범위 보정 |
|
||||
| lib/markdown-editor-media.js | 관리자 Markdown-first 에디터 미디어 선택 모달의 미디어 종류 판별, 대상별 선택 가능 여부, 미리보기 URL, 업로드 MIME 허용, 선택 순서·토글·항목 해석, fenced 미디어 블록 생성 |
|
||||
| lib/markdown-editor-media.js | 관리자 Markdown-first 에디터 미디어 선택 모달의 미디어 종류 판별, 대상별 선택 가능 여부, 미리보기 URL, 업로드 MIME 허용, 선택 순서·토글·항목 해석, 업로드 오류·한도 검증, fenced 미디어 블록 생성 |
|
||||
| lib/markdown-block-parser.js | 공개 Markdown 본문 문자열을 제목·문단·이미지·갤러리·fenced 블록·인용·목록·코드·표 렌더링 블록으로 변환 |
|
||||
| lib/markdown-block-context.js | 관리자 Markdown textarea·라이브 편집 포커스 위치 기준 이미지·갤러리·링크·iframe·HTML·인용·콜아웃·코드·토글 블록 설정 패널 대상 판별, 인용 옵션 공통 파서 연결, 코드 fenced 블록 여닫는 펜스 쌍 판별 |
|
||||
| lib/markdown-iframe-embed.js | 게시물 본문 iframe 블록의 코드 판별과 안전 속성 추출, `allowfullscreen`을 `allow=fullscreen` 권한으로 정규화 |
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 업데이트 이력
|
||||
|
||||
## v1.11.71
|
||||
|
||||
- 관리자 Markdown 에디터 미디어 업로드 API 오류 메시지 변환과 파일 개수·총량·개별 크기 한도 검증 로직을 공통 helper로 분리.
|
||||
- 미디어 업로드 오류 메시지와 업로드 한도 검증 단위 테스트 추가.
|
||||
|
||||
## v1.11.70
|
||||
|
||||
- 관리자 Markdown 에디터 미디어 선택 모달의 선택 순서·단일/갤러리 선택 토글·선택 URL 기반 항목 해석 로직을 공통 helper로 분리.
|
||||
|
||||
@@ -5,6 +5,12 @@ import {
|
||||
isPostCardThumbnailUrl,
|
||||
withGeneratedImageCacheVersion
|
||||
} from './post-card-thumbnail-url.js'
|
||||
import {
|
||||
formatUploadSizeLimit,
|
||||
getMaxUploadBytesForKind,
|
||||
getUploadFilesTotalSize,
|
||||
getUploadKind
|
||||
} from './upload-size-limit.js'
|
||||
|
||||
/**
|
||||
* 파일 크기를 표시용 문자열로 변환한다.
|
||||
@@ -233,3 +239,65 @@ export const resolveSelectedMediaItems = (selectedUrls, selectedItemMap, mediaIt
|
||||
.map((url) => itemMap[url] || items.find((item) => item?.url === url))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* 업로드 API 오류 메시지를 사용자용 문장으로 변환한다.
|
||||
* @param {unknown} error - fetch 오류
|
||||
* @returns {string} 오류 메시지
|
||||
*/
|
||||
export const resolveUploadFetchErrorMessage = (error) => {
|
||||
const responseMessage = error?.data?.message || error?.response?._data?.message
|
||||
|
||||
if (typeof responseMessage === 'string' && responseMessage.trim()) {
|
||||
return responseMessage.trim()
|
||||
}
|
||||
|
||||
if (error?.statusCode === 413 || error?.status === 413) {
|
||||
return '업로드 가능한 파일 크기를 초과했습니다.'
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
return '파일 업로드에 실패했습니다.'
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 파일이 업로드 한도를 넘지 않는지 검사한다.
|
||||
* @param {Object} options - 업로드 한도 검사 옵션
|
||||
* @param {FileList|Array<File>} options.files - 업로드 파일 목록
|
||||
* @param {number} options.maxFileCount - 최대 파일 수
|
||||
* @param {number} options.maxTotalSize - 최대 전체 용량
|
||||
* @param {{ image: number, video: number, audio: number, document: number }} options.uploadSizeLimits - 종류별 업로드 한도
|
||||
* @returns {string|null} 초과 시 오류 메시지, 아니면 null
|
||||
*/
|
||||
export const findUploadSizeLimitError = ({
|
||||
files,
|
||||
maxFileCount,
|
||||
maxTotalSize,
|
||||
uploadSizeLimits
|
||||
}) => {
|
||||
const fileList = Array.from(files || [])
|
||||
|
||||
if (fileList.length > maxFileCount) {
|
||||
return `한 번에 업로드할 수 있는 파일은 최대 ${maxFileCount}개입니다.`
|
||||
}
|
||||
|
||||
const uploadTotalSize = getUploadFilesTotalSize(fileList)
|
||||
|
||||
if (uploadTotalSize > maxTotalSize) {
|
||||
return `한 번에 업로드 가능한 전체 용량을 초과했습니다. (최대 ${formatUploadSizeLimit(maxTotalSize)})`
|
||||
}
|
||||
|
||||
for (const file of fileList) {
|
||||
const uploadKind = getUploadKind(file.type, file.name)
|
||||
const maxBytes = getMaxUploadBytesForKind(uploadKind, uploadSizeLimits)
|
||||
|
||||
if (file.size > maxBytes) {
|
||||
return `파일 크기가 너무 큽니다. (최대 ${formatUploadSizeLimit(maxBytes)})`
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.70",
|
||||
"version": "1.11.71",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.70",
|
||||
"version": "1.11.71",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sori.studio",
|
||||
"version": "1.11.70",
|
||||
"version": "1.11.71",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
createMediaBlockMarkdown,
|
||||
findUploadSizeLimitError,
|
||||
formatMediaFileSize,
|
||||
getGallerySelectionOrder,
|
||||
getMediaItemKind,
|
||||
@@ -9,8 +10,10 @@ import {
|
||||
isMediaItemSelectableForTarget,
|
||||
isUploadFileAllowedForMediaTarget,
|
||||
resolveSelectedMediaItems,
|
||||
resolveUploadFetchErrorMessage,
|
||||
toggleMediaSelectionState
|
||||
} from '../lib/markdown-editor-media.js'
|
||||
import { buildDefaultUploadSizeLimits } from '../lib/upload-size-limit.js'
|
||||
|
||||
test('미디어 파일 크기를 표시 문자열로 변환한다', () => {
|
||||
assert.equal(formatMediaFileSize(512), '512 B')
|
||||
@@ -127,3 +130,58 @@ test('선택된 URL 순서대로 미디어 항목을 해석한다', () => {
|
||||
{ url: '/b.png', source: 'list' }
|
||||
])
|
||||
})
|
||||
|
||||
test('업로드 API 오류를 사용자 메시지로 변환한다', () => {
|
||||
assert.equal(resolveUploadFetchErrorMessage({
|
||||
data: {
|
||||
message: ' 서버 오류 '
|
||||
}
|
||||
}), '서버 오류')
|
||||
assert.equal(resolveUploadFetchErrorMessage({ statusCode: 413 }), '업로드 가능한 파일 크기를 초과했습니다.')
|
||||
assert.equal(resolveUploadFetchErrorMessage(new Error('네트워크 오류')), '네트워크 오류')
|
||||
assert.equal(resolveUploadFetchErrorMessage({}), '파일 업로드에 실패했습니다.')
|
||||
})
|
||||
|
||||
test('업로드 파일 한도 초과 메시지를 계산한다', () => {
|
||||
const uploadSizeLimits = buildDefaultUploadSizeLimits({
|
||||
image: 100
|
||||
})
|
||||
|
||||
assert.equal(findUploadSizeLimitError({
|
||||
files: [
|
||||
{ name: 'a.png', type: 'image/png', size: 10 },
|
||||
{ name: 'b.png', type: 'image/png', size: 10 }
|
||||
],
|
||||
maxFileCount: 1,
|
||||
maxTotalSize: 1000,
|
||||
uploadSizeLimits
|
||||
}), '한 번에 업로드할 수 있는 파일은 최대 1개입니다.')
|
||||
|
||||
assert.equal(findUploadSizeLimitError({
|
||||
files: [
|
||||
{ name: 'a.png', type: 'image/png', size: 60 },
|
||||
{ name: 'b.png', type: 'image/png', size: 60 }
|
||||
],
|
||||
maxFileCount: 5,
|
||||
maxTotalSize: 100,
|
||||
uploadSizeLimits
|
||||
}), '한 번에 업로드 가능한 전체 용량을 초과했습니다. (최대 100B)')
|
||||
|
||||
assert.equal(findUploadSizeLimitError({
|
||||
files: [
|
||||
{ name: 'a.png', type: 'image/png', size: 101 }
|
||||
],
|
||||
maxFileCount: 5,
|
||||
maxTotalSize: 1000,
|
||||
uploadSizeLimits
|
||||
}), '파일 크기가 너무 큽니다. (최대 100B)')
|
||||
|
||||
assert.equal(findUploadSizeLimitError({
|
||||
files: [
|
||||
{ name: 'a.png', type: 'image/png', size: 100 }
|
||||
],
|
||||
maxFileCount: 5,
|
||||
maxTotalSize: 1000,
|
||||
uploadSizeLimits
|
||||
}), null)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user