diff --git a/frontend/src/components/SourceCodeEditor/SourceCodeEditor.tsx b/frontend/src/components/SourceCodeEditor/SourceCodeEditor.tsx index 51a131b74..0f0e61348 100644 --- a/frontend/src/components/SourceCodeEditor/SourceCodeEditor.tsx +++ b/frontend/src/components/SourceCodeEditor/SourceCodeEditor.tsx @@ -14,6 +14,7 @@ interface Props { content: string; onChangeFilename: (newFileName: string) => void; onChangeContent: (newContent: string) => void; + onBlurFilename: (newFileName: string) => void; isValidContentChange?: (newContent: string) => boolean; handleDeleteSourceCode: () => void; sourceCodeRef?: React.Ref | null; @@ -24,6 +25,7 @@ const SourceCodeEditor = ({ filenameAutoFocus = false, content, onChangeFilename, + onBlurFilename, onChangeContent, isValidContentChange = () => true, handleDeleteSourceCode, @@ -53,11 +55,16 @@ const SourceCodeEditor = ({ previousContentRef.current = value; }; + const handleFilenameBlur = (e: React.FocusEvent) => { + onBlurFilename(e.target.value); + }; + return ( diff --git a/frontend/src/components/TagInput/TagInput.tsx b/frontend/src/components/TagInput/TagInput.tsx index ce131eaa5..b31e59ead 100644 --- a/frontend/src/components/TagInput/TagInput.tsx +++ b/frontend/src/components/TagInput/TagInput.tsx @@ -1,8 +1,8 @@ -import { ChangeEvent, Dispatch, KeyboardEvent, SetStateAction } from 'react'; +import { ChangeEvent, KeyboardEvent } from 'react'; import { Flex, Input, TagButton, Text } from '@/components'; import { ToastContext } from '@/contexts'; -import { useCustomContext, useScreenReader } from '@/hooks'; +import { useCustomContext } from '@/hooks'; import { validateTagLength } from '@/service/validates'; import { theme } from '@/style/theme'; @@ -11,30 +11,21 @@ interface Props { handleValue: (e: ChangeEvent) => void; resetValue: () => void; tags: string[]; - setTags: Dispatch>; + addTag: (newTag: string) => void; + deleteTag: (tag: string) => void; } -const TagInput = ({ value, handleValue, resetValue, tags, setTags }: Props) => { +const TagInput = ({ value, handleValue, resetValue, tags, addTag, deleteTag }: Props) => { const { failAlert } = useCustomContext(ToastContext); - const { updateScreenReaderMessage } = useScreenReader(); const handleSpaceBarAndEnterKeydown = (e: KeyboardEvent) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); - addTag(); + addTag(value); resetValue(); } }; - const addTag = () => { - if (value === '' || tags.includes(value)) { - return; - } - - setTags((prev) => [...prev, value]); - updateScreenReaderMessage(`${value} 태그 등록`); - }; - const handleTagInput = (e: ChangeEvent) => { const errorMessage = validateTagLength(e.target.value); @@ -66,16 +57,7 @@ const TagInput = ({ value, handleValue, resetValue, tags, setTags }: Props) => { )} - {tags?.map((tag, idx) => ( - { - setTags((prev) => prev.filter((el) => el !== tag)); - }} - /> - ))} + {tags?.map((tag, idx) => deleteTag(tag)} />)} { onChange={handleTagInput} onKeyUp={handleSpaceBarAndEnterKeydown} onBlur={() => { - addTag(); + addTag(value); resetValue(); }} /> diff --git a/frontend/src/hooks/template/useTag.ts b/frontend/src/hooks/template/useTag.ts index 5bf9f1dc9..01e18abfa 100644 --- a/frontend/src/hooks/template/useTag.ts +++ b/frontend/src/hooks/template/useTag.ts @@ -1,14 +1,29 @@ import { useState } from 'react'; -import { useNoSpaceInput } from '@/hooks'; +import { useNoSpaceInput, useScreenReader } from '@/hooks'; export const useTag = (initTags: string[]) => { const [tags, setTags] = useState(initTags); const [value, handleValue, resetValue] = useNoSpaceInput(''); + const { updateScreenReaderMessage } = useScreenReader(); + + const addTag = (newTag: string) => { + if (newTag === '' || tags.includes(newTag)) { + return; + } + + setTags((prev) => [...prev, newTag]); + updateScreenReaderMessage(`${newTag} 태그 등록`); + }; + + const deleteTag = (tag: string) => { + setTags((prev) => prev.filter((el) => el !== tag)); + }; return { tags, - setTags, + addTag, + deleteTag, value, handleValue, resetValue, diff --git a/frontend/src/pages/TemplateEditPage/TemplateEditPage.tsx b/frontend/src/pages/TemplateEditPage/TemplateEditPage.tsx index 64d84781c..70f442a0a 100644 --- a/frontend/src/pages/TemplateEditPage/TemplateEditPage.tsx +++ b/frontend/src/pages/TemplateEditPage/TemplateEditPage.tsx @@ -12,6 +12,7 @@ import { ICON_SIZE } from '@/style/styleConstants'; import { theme } from '@/style/theme'; import type { Template, TemplateEditRequest } from '@/types'; import { TemplateVisibility } from '@/types/template'; +import { getLanguageForAutoTag } from '@/utils'; import * as S from './TemplateEditPage.style'; @@ -140,6 +141,7 @@ const TemplateEditPage = ({ template, toggleEditButton }: Props) => { isValidContentChange={isValidContentChange} onChangeContent={(newContent) => handleContentChange(newContent, index)} onChangeFilename={(newFilename) => handleFilenameChange(newFilename, index)} + onBlurFilename={(newFilename) => tagProps.addTag(getLanguageForAutoTag(newFilename))} handleDeleteSourceCode={() => handleDeleteSourceCode(index)} filenameAutoFocus={index !== 0} /> diff --git a/frontend/src/pages/TemplateUploadPage/TemplateUploadPage.tsx b/frontend/src/pages/TemplateUploadPage/TemplateUploadPage.tsx index 6e81b2efc..1dc2a705a 100644 --- a/frontend/src/pages/TemplateUploadPage/TemplateUploadPage.tsx +++ b/frontend/src/pages/TemplateUploadPage/TemplateUploadPage.tsx @@ -12,6 +12,7 @@ import { ICON_SIZE } from '@/style/styleConstants'; import { theme } from '@/style/theme'; import { TemplateUploadRequest } from '@/types'; import { TemplateVisibility } from '@/types/template'; +import { getLanguageForAutoTag } from '@/utils'; import * as S from './TemplateUploadPage.style'; @@ -129,6 +130,7 @@ const TemplateUploadPage = () => { isValidContentChange={isValidContentChange} onChangeContent={(newContent) => handleContentChange(newContent, index)} onChangeFilename={(newFilename) => handleFilenameChange(newFilename, index)} + onBlurFilename={(newFilename) => tagProps.addTag(getLanguageForAutoTag(newFilename))} handleDeleteSourceCode={() => handleDeleteSourceCode(index)} filenameAutoFocus={index !== 0} /> diff --git a/frontend/src/utils/getLanguageByFileName.ts b/frontend/src/utils/getLanguageByFileName.ts index 836dbe254..04dce7fcf 100644 --- a/frontend/src/utils/getLanguageByFileName.ts +++ b/frontend/src/utils/getLanguageByFileName.ts @@ -5,6 +5,21 @@ export const getLanguageByFilename = (filename: string) => { return language; }; +export const getLanguageForAutoTag = (filename: string) => { + const extension = getFileExtension(filename); + const language = getLanguageByExtension(extension); + + if (extension === 'jsx' || extension === 'tsx') { + return 'react'; + } + + if (language === 'plaintext') { + return ''; + } + + return language; +}; + const getFileExtension = (filename: string) => { if (filename.includes('.')) { const parts = filename.split('.'); diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 3af87010a..302a327fd 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -1,7 +1,7 @@ export { formatRelativeTime } from './formatRelativeTime'; export { formatWithK } from './formatWithK'; export { getByteSize } from './getByteSize'; -export { getLanguageByFilename } from './getLanguageByFileName'; +export { getLanguageByFilename, getLanguageForAutoTag } from './getLanguageByFileName'; export { remToPx } from './remToPx'; export { scroll } from './scroll'; export { getChildOfType, getChildrenWithoutTypes } from './reactChildrenHelpers';