diff --git a/components/admin/AdminEditorBlockPanel.vue b/components/admin/AdminEditorBlockPanel.vue
index b141f7d..47f175c 100644
--- a/components/admin/AdminEditorBlockPanel.vue
+++ b/components/admin/AdminEditorBlockPanel.vue
@@ -34,7 +34,8 @@ const emit = defineEmits([
'update-quote-background',
'update-callout-options',
'update-code-options',
- 'update-toggle-options'
+ 'update-toggle-options',
+ 'update-inline-color'
])
const backgroundLabels = {
@@ -50,6 +51,15 @@ const backgroundSwatches = {
...CALLOUT_BACKGROUND_SWATCHES
}
+const inlineColorSwatches = {
+ gray: '#6b7280',
+ blue: '#0055ff',
+ green: '#16ae68',
+ yellow: '#a16207',
+ red: '#e60000',
+ purple: '#8800ff'
+}
+
const languageOptions = ['', 'javascript', 'html', 'css', 'vue', 'json', 'bash', 'markdown', 'sql']
/**
@@ -80,6 +90,13 @@ const getQuoteBackgroundLabel = (background) => QUOTE_BACKGROUND_LABELS[backgrou
*/
const getQuoteBackgroundSwatch = (background) => QUOTE_BACKGROUND_SWATCHES[background] || QUOTE_BACKGROUND_SWATCHES.gray
+/**
+ * 인라인 색상 스와치를 반환한다.
+ * @param {string} color - 색상 키
+ * @returns {string} CSS 색상
+ */
+const getInlineColorSwatch = (color) => inlineColorSwatches[color] || inlineColorSwatches.gray
+
/**
* 블록 종류 라벨
* @returns {string}
@@ -93,6 +110,10 @@ const panelTitle = computed(() => {
return '갤러리'
}
+ if (props.panel.kind === 'inline-color') {
+ return '텍스트 색상'
+ }
+
if (props.panel.kind === 'embed') {
return '임베드'
}
@@ -129,6 +150,10 @@ const panelMeta = computed(() => {
return `${props.panel.images.length}개 이미지`
}
+ if (props.panel.kind === 'inline-color') {
+ return '선택한 텍스트'
+ }
+
if (props.panel.kind === 'embed') {
return 'YouTube·X 등 URL'
}
@@ -198,7 +223,36 @@ const onPanelFocusOut = (event) => {
-
+
+
+
+
+ 글자색
+
+
+ 선택한 텍스트 범위에 인라인 색상을 적용합니다.
+
+
+
+
+
+
+
+
+
diff --git a/components/content/ContentMarkdownEditableInline.vue b/components/content/ContentMarkdownEditableInline.vue
index d5d783c..20313e7 100644
--- a/components/content/ContentMarkdownEditableInline.vue
+++ b/components/content/ContentMarkdownEditableInline.vue
@@ -1540,4 +1540,48 @@ defineExpose({ focusEditor, readEditorValue })
font-size: 0.9em;
color: #fff;
}
+
+.content-markdown-editable-inline :deep([data-inline-color='gray']) {
+ color: var(--site-muted);
+}
+
+.content-markdown-editable-inline :deep([data-inline-color='blue']) {
+ color: #0055ff;
+}
+
+.content-markdown-editable-inline :deep([data-inline-color='green']) {
+ color: #16ae68;
+}
+
+.content-markdown-editable-inline :deep([data-inline-color='yellow']) {
+ color: #a16207;
+}
+
+.content-markdown-editable-inline :deep([data-inline-color='red']) {
+ color: #e60000;
+}
+
+.content-markdown-editable-inline :deep([data-inline-color='purple']) {
+ color: #8800ff;
+}
+
+:global(html[data-theme='dark']) .content-markdown-editable-inline :deep([data-inline-color='blue']) {
+ color: #7aa2ff;
+}
+
+:global(html[data-theme='dark']) .content-markdown-editable-inline :deep([data-inline-color='green']) {
+ color: #5ee0a0;
+}
+
+:global(html[data-theme='dark']) .content-markdown-editable-inline :deep([data-inline-color='yellow']) {
+ color: #facc15;
+}
+
+:global(html[data-theme='dark']) .content-markdown-editable-inline :deep([data-inline-color='red']) {
+ color: #ff6b6b;
+}
+
+:global(html[data-theme='dark']) .content-markdown-editable-inline :deep([data-inline-color='purple']) {
+ color: #c084fc;
+}
diff --git a/components/content/ContentMarkdownInlineSegments.vue b/components/content/ContentMarkdownInlineSegments.vue
new file mode 100644
index 0000000..89abd9d
--- /dev/null
+++ b/components/content/ContentMarkdownInlineSegments.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+ {{ segment.text }}
+ {{ segment.text }}
+ {{ segment.text }}
+ {{ segment.text }}
+ {{ segment.text }}
+ {{ segment.text }}
+
+
+
+ {{ segment.text }}
+
+
+
+
+
diff --git a/components/content/ContentMarkdownRenderer.vue b/components/content/ContentMarkdownRenderer.vue
index e01d0e3..ce1401b 100644
--- a/components/content/ContentMarkdownRenderer.vue
+++ b/components/content/ContentMarkdownRenderer.vue
@@ -5,7 +5,7 @@ import {
isImageUrl,
parseImageMarkdownLine
} from '../../lib/markdown-image.js'
-import { parseInlineSegments, readEditableTextFromElement, setEditableCaretOffset } from '../../lib/markdown-inline.js'
+import { readEditableTextFromElement, setEditableCaretOffset } from '../../lib/markdown-inline.js'
import {
applyLiveSelectionDelete,
collapseLiveSelection,
@@ -2482,15 +2482,6 @@ const onMergeWithPreviousLine = (lineIndex, payload) => {
emit('merge-with-previous-line', { lineIndex, mergedLine })
}
-/**
- * 줄바꿈이 포함된 인라인 마크다운을 줄 단위 세그먼트로 변환한다.
- * @param {string} value - 원본 문자열
- * @returns {Array>} 줄별 인라인 세그먼트
- */
-const parseInlineSegmentLines = (value) => {
- return String(value || '').split('\n').map(parseInlineSegments)
-}
-
/**
* 라이트박스를 연다
* @param {Array