From 0fd18bfb4899e0a8f801f32e6f430d5fd0746204 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 1 May 2026 18:07:36 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EB=A7=88?= =?UTF-8?q?=ED=81=AC=EB=8B=A4=EC=9A=B4=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminMarkdownPreview.vue | 152 ++++++++++++++++++++++ components/admin/AdminPostForm.vue | 132 +++++++++++++++++-- docs/history.md | 8 ++ docs/map.md | 1 + docs/spec.md | 7 + docs/todo.md | 2 +- docs/update.md | 6 + package-lock.json | 4 +- package.json | 2 +- 9 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 components/admin/AdminMarkdownPreview.vue diff --git a/components/admin/AdminMarkdownPreview.vue b/components/admin/AdminMarkdownPreview.vue new file mode 100644 index 0000000..aa27b6c --- /dev/null +++ b/components/admin/AdminMarkdownPreview.vue @@ -0,0 +1,152 @@ + + + diff --git a/components/admin/AdminPostForm.vue b/components/admin/AdminPostForm.vue index d5332e5..a1f1462 100644 --- a/components/admin/AdminPostForm.vue +++ b/components/admin/AdminPostForm.vue @@ -17,6 +17,8 @@ const props = defineProps({ const emit = defineEmits(['submit']) const slugTouched = ref(Boolean(props.initialPost.slug)) +const editorMode = ref('write') +const contentTextarea = ref(null) const form = reactive({ title: props.initialPost.title || '', @@ -66,6 +68,77 @@ const parseTags = (value) => [...new Set(value .map((tag) => toSlug(tag)) .filter(Boolean))] +/** + * 본문 선택 영역에 마크다운 문법 삽입 + * @param {string} before - 선택 영역 앞에 넣을 문자열 + * @param {string} after - 선택 영역 뒤에 넣을 문자열 + * @param {string} fallback - 선택 영역이 없을 때 넣을 문자열 + * @returns {void} + */ +const insertMarkdown = (before, after = '', fallback = '') => { + const textarea = contentTextarea.value + + if (!textarea) { + form.content += fallback || before + return + } + + const start = textarea.selectionStart + const end = textarea.selectionEnd + const selectedText = form.content.slice(start, end) + const insertText = selectedText + ? `${before}${selectedText}${after}` + : (fallback || `${before}${after}`) + + form.content = `${form.content.slice(0, start)}${insertText}${form.content.slice(end)}` + + nextTick(() => { + textarea.focus() + const cursor = start + insertText.length + textarea.setSelectionRange(cursor, cursor) + }) +} + +/** + * 제목 문법 삽입 + * @returns {void} + */ +const insertHeading = () => { + insertMarkdown('## ', '', '## 제목') +} + +/** + * 굵게 문법 삽입 + * @returns {void} + */ +const insertBold = () => { + insertMarkdown('**', '**', '**강조**') +} + +/** + * 목록 문법 삽입 + * @returns {void} + */ +const insertList = () => { + insertMarkdown('- ', '', '- 목록') +} + +/** + * 인용 문법 삽입 + * @returns {void} + */ +const insertQuote = () => { + insertMarkdown('> ', '', '> 인용') +} + +/** + * 코드 블록 문법 삽입 + * @returns {void} + */ +const insertCodeBlock = () => { + insertMarkdown('```\n', '\n```', '```\n코드\n```') +} + /** * 게시물 입력값 제출 * @returns {void} @@ -102,14 +175,57 @@ const submitPost = () => { > -