게시물 라이브 편집 블록 동작 개선
This commit is contained in:
@@ -316,6 +316,117 @@ const getLineIndexAtOffset = (offset) => {
|
|||||||
return Math.max(0, value.slice(0, safeOffset).split('\n').length - 1)
|
return Math.max(0, value.slice(0, safeOffset).split('\n').length - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 닫는 fenced 줄에서 대응하는 여는 줄을 찾는다.
|
||||||
|
* @param {string[]} lines - 마크다운 줄
|
||||||
|
* @param {number} closingLine - 닫는 줄 번호
|
||||||
|
* @param {(line: string) => boolean} isOpeningLine - 여는 줄 판별
|
||||||
|
* @returns {number|null} 여는 줄 번호
|
||||||
|
*/
|
||||||
|
const findPreviousFencedOpeningLine = (lines, closingLine, isOpeningLine) => {
|
||||||
|
for (let index = closingLine - 1; index >= 0; index -= 1) {
|
||||||
|
const trimmed = String(lines[index] ?? '').trim()
|
||||||
|
|
||||||
|
if (isOpeningLine(trimmed)) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 여는 fenced 줄에서 닫는 줄을 찾는다.
|
||||||
|
* @param {string[]} lines - 마크다운 줄
|
||||||
|
* @param {number} openingLine - 여는 줄 번호
|
||||||
|
* @param {string} closingMarker - 닫는 표식
|
||||||
|
* @returns {number|null} 닫는 줄 번호
|
||||||
|
*/
|
||||||
|
const findNextFencedClosingLine = (lines, openingLine, closingMarker) => {
|
||||||
|
for (let index = openingLine + 1; index < lines.length; index += 1) {
|
||||||
|
if (String(lines[index] ?? '').trim() === closingMarker) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정 줄이 속한 코드 펜스 범위를 찾는다.
|
||||||
|
* @param {string[]} lines - 마크다운 줄
|
||||||
|
* @param {number} targetLine - 확인할 줄 번호
|
||||||
|
* @returns {{ openingLine: number, closingLine: number|null }|null} 코드 펜스 범위
|
||||||
|
*/
|
||||||
|
const findCodeFenceRangeAtLine = (lines, targetLine) => {
|
||||||
|
for (let index = 0; index < lines.length; index += 1) {
|
||||||
|
if (!String(lines[index] ?? '').trim().startsWith('```')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const closingLine = findNextFencedClosingLine(lines, index, '```')
|
||||||
|
|
||||||
|
if (targetLine === index || (closingLine !== null && targetLine >= index && targetLine <= closingLine)) {
|
||||||
|
return { openingLine: index, closingLine }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closingLine !== null) {
|
||||||
|
index = closingLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 라이브 모드에 실제로 존재하는 편집 줄로 포커스 대상을 보정한다.
|
||||||
|
* @param {number} line - 원본 줄 번호
|
||||||
|
* @param {number} offset - 원본 줄 내부 오프셋
|
||||||
|
* @returns {{ line: number, offset: number }} 라이브 포커스 대상
|
||||||
|
*/
|
||||||
|
const resolvePreviewFocusPosition = (line, offset) => {
|
||||||
|
const lines = (markdownValue.value ?? '').split('\n')
|
||||||
|
const safeLine = Math.min(Math.max(0, line), Math.max(0, lines.length - 1))
|
||||||
|
const trimmed = String(lines[safeLine] ?? '').trim()
|
||||||
|
|
||||||
|
if (trimmed.startsWith('```')) {
|
||||||
|
const codeFenceRange = findCodeFenceRangeAtLine(lines, safeLine)
|
||||||
|
|
||||||
|
if (!codeFenceRange || codeFenceRange.closingLine === null) {
|
||||||
|
return { line: safeLine, offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusLine = Math.min(
|
||||||
|
Math.max(codeFenceRange.openingLine + 1, safeLine),
|
||||||
|
Math.max(codeFenceRange.openingLine + 1, codeFenceRange.closingLine - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return { line: focusLine, offset: safeLine === codeFenceRange.openingLine ? 0 : offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith(':::callout') || trimmed.startsWith(':::toggle')) {
|
||||||
|
return { line: safeLine + 1, offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed === ':::') {
|
||||||
|
const previousFencedOpening = findPreviousFencedOpeningLine(
|
||||||
|
lines,
|
||||||
|
safeLine,
|
||||||
|
(candidate) => candidate.startsWith(':::callout') || candidate.startsWith(':::toggle')
|
||||||
|
)
|
||||||
|
|
||||||
|
if (previousFencedOpening !== null) {
|
||||||
|
return { line: Math.max(previousFencedOpening + 1, safeLine - 1), offset }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^>\s*(?:\[!bg=|\{bg=)/.test(trimmed) && String(lines[safeLine + 1] ?? '').trim().startsWith('>')) {
|
||||||
|
return { line: safeLine + 1, offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { line: safeLine, offset }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 현재 textarea 선택 위치를 라이브 모드 포커스 대상으로 저장한다.
|
* 현재 textarea 선택 위치를 라이브 모드 포커스 대상으로 저장한다.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
@@ -327,10 +438,12 @@ const rememberWritePositionForPreview = () => {
|
|||||||
? Math.min(textarea.selectionStart, value.length)
|
? Math.min(textarea.selectionStart, value.length)
|
||||||
: Math.min(lastSelectionState.value.start, value.length)
|
: Math.min(lastSelectionState.value.start, value.length)
|
||||||
const line = getLineIndexAtOffset(start)
|
const line = getLineIndexAtOffset(start)
|
||||||
|
const offset = Math.max(0, start - getLineStartOffset(line))
|
||||||
|
const focusPosition = resolvePreviewFocusPosition(line, offset)
|
||||||
|
|
||||||
pendingPreviewFocus.value = {
|
pendingPreviewFocus.value = {
|
||||||
line,
|
line: focusPosition.line,
|
||||||
offset: Math.max(0, start - getLineStartOffset(line))
|
offset: focusPosition.offset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,6 +1219,20 @@ const wrapInline = (prefix, suffix, placeholder) => {
|
|||||||
setTextareaSelection(selectionStart, selectionStart + inner.length)
|
setTextareaSelection(selectionStart, selectionStart + inner.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 선택 텍스트를 링크 마크다운으로 바꾼다.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const insertInlineLink = () => {
|
||||||
|
const { start, end, value } = getSelectionState()
|
||||||
|
const selected = value.slice(start, end) || '링크 텍스트'
|
||||||
|
const replacement = `[${selected}](https://)`
|
||||||
|
|
||||||
|
markdownValue.value = `${value.slice(0, start)}${replacement}${value.slice(end)}`
|
||||||
|
const urlStart = start + selected.length + 3
|
||||||
|
setTextareaSelection(urlStart, urlStart + 'https://'.length)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 선택된 줄을 변환한다.
|
* 선택된 줄을 변환한다.
|
||||||
* @param {(line: string) => string} transformLine - 줄 변환 함수
|
* @param {(line: string) => string} transformLine - 줄 변환 함수
|
||||||
@@ -2819,6 +2946,10 @@ const handleKeydown = (event) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
deleteSelectedSourceLines()
|
deleteSelectedSourceLines()
|
||||||
|
} else if (key === 'k') {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
insertInlineLink()
|
||||||
} else if (key === 'b') {
|
} else if (key === 'b') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
wrapInline('**', '**', '굵은 글씨')
|
wrapInline('**', '**', '굵은 글씨')
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ const onBodyCommit = (payload) => {
|
|||||||
block-class="content-markdown-callout-editor__body min-w-0 text-[15px] leading-8 text-[var(--site-text)]"
|
block-class="content-markdown-callout-editor__body min-w-0 text-[15px] leading-8 text-[var(--site-text)]"
|
||||||
enter-mode="multiline"
|
enter-mode="multiline"
|
||||||
plain-text
|
plain-text
|
||||||
|
arrow-exit-creates-line
|
||||||
:source-line="bodySourceLine"
|
:source-line="bodySourceLine"
|
||||||
:source-line-count="bodyLines.length"
|
:source-line-count="bodyLines.length"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ const toggleLineNumbers = () => {
|
|||||||
block-class="content-markdown-code-block-editor__editor m-0 min-w-0 border-0 bg-transparent p-0 font-mono text-sm leading-6 text-white outline-none"
|
block-class="content-markdown-code-block-editor__editor m-0 min-w-0 border-0 bg-transparent p-0 font-mono text-sm leading-6 text-white outline-none"
|
||||||
enter-mode="multiline"
|
enter-mode="multiline"
|
||||||
plain-text
|
plain-text
|
||||||
|
arrow-exit-creates-line
|
||||||
:source-line="bodySourceLine"
|
:source-line="bodySourceLine"
|
||||||
|
:source-line-count="bodyLines.length"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@input="onBodyInput"
|
@input="onBodyInput"
|
||||||
@commit="onBodyCommit"
|
@commit="onBodyCommit"
|
||||||
|
|||||||
@@ -653,6 +653,43 @@ const buildInsertBelowPayload = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 편집 영역의 현재 선택 텍스트를 링크 마크다운으로 바꾼다.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const insertMarkdownLinkAtSelection = () => {
|
||||||
|
if (!import.meta.client || !rootRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection()
|
||||||
|
|
||||||
|
if (!selection || selection.rangeCount === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
|
||||||
|
if (!rootRef.value.contains(range.commonAncestorContainer)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedText = selection.toString() || '링크 텍스트'
|
||||||
|
const markdown = `[${selectedText}](https://)`
|
||||||
|
|
||||||
|
document.execCommand('insertText', false, markdown)
|
||||||
|
syncSlashState()
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (!rootRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('input', readEditorValue())
|
||||||
|
emit('commit', readEditorValue())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 커서 위치에서 문단을 분리한다.
|
* 커서 위치에서 문단을 분리한다.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
@@ -792,6 +829,13 @@ const onKeydown = (event) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key.toLowerCase() === 'k') {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
insertMarkdownLinkAtSelection()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 'k') {
|
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 'k') {
|
||||||
if (props.sourceLine !== null) {
|
if (props.sourceLine !== null) {
|
||||||
const lineContext = getCaretLineContext()
|
const lineContext = getCaretLineContext()
|
||||||
|
|||||||
@@ -17,9 +17,14 @@ import {
|
|||||||
stripListMarker,
|
stripListMarker,
|
||||||
stripQuoteMarker
|
stripQuoteMarker
|
||||||
} from '../../lib/markdown-live-edit.js'
|
} from '../../lib/markdown-live-edit.js'
|
||||||
import { buildCodeBlockLines, parseCodeFenceLine } from '../../lib/markdown-code-block.js'
|
import { buildCodeBlockLines, buildCodeFenceOpener, parseCodeFenceLine } from '../../lib/markdown-code-block.js'
|
||||||
import { buildToggleBlockLines, parseToggleOpenerLine } from '../../lib/markdown-toggle.js'
|
import { buildToggleBlockLines, parseToggleOpenerLine } from '../../lib/markdown-toggle.js'
|
||||||
import { CALLOUT_BACKGROUND_OPTIONS, QUOTE_BACKGROUND_OPTIONS, parseCalloutOptions } from '../../lib/markdown-callout.js'
|
import {
|
||||||
|
buildCalloutOpenerLine,
|
||||||
|
CALLOUT_BACKGROUND_OPTIONS,
|
||||||
|
QUOTE_BACKGROUND_OPTIONS,
|
||||||
|
parseCalloutOptions
|
||||||
|
} from '../../lib/markdown-callout.js'
|
||||||
import { createHeadingIdFactory } from '../../lib/markdown-toc.js'
|
import { createHeadingIdFactory } from '../../lib/markdown-toc.js'
|
||||||
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
|
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
|
||||||
import ProseCodeBlock from './ProseCodeBlock.vue'
|
import ProseCodeBlock from './ProseCodeBlock.vue'
|
||||||
@@ -631,21 +636,29 @@ const parseMarkdownBlocks = (markdown) => {
|
|||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.push(attachSourceRange(
|
if (index >= lines.length) {
|
||||||
createBlock('code', codeLines.join('\n'), null, `block-${blocks.length}`, {
|
index = startLine
|
||||||
codeLanguage: fenceOptions.language,
|
} else {
|
||||||
codeShowLineNumbers: fenceOptions.showLineNumbers
|
blocks.push(attachSourceRange(
|
||||||
}),
|
createBlock('code', codeLines.join('\n'), null, `block-${blocks.length}`, {
|
||||||
startLine,
|
codeLanguage: fenceOptions.language,
|
||||||
index
|
codeShowLineNumbers: fenceOptions.showLineNumbers
|
||||||
))
|
}),
|
||||||
index += 1
|
startLine,
|
||||||
continue
|
index
|
||||||
|
))
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trimmedLine === '---') {
|
if (trimmedLine === '---') {
|
||||||
const startLine = index
|
const startLine = index
|
||||||
blocks.push(attachSourceRange(createBlock('divider', '', null, `block-${blocks.length}`), startLine, startLine))
|
blocks.push(attachSourceRange(
|
||||||
|
createBlock('divider', '', null, `block-${blocks.length}`),
|
||||||
|
startLine,
|
||||||
|
startLine
|
||||||
|
))
|
||||||
index += 1
|
index += 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -907,6 +920,19 @@ const focusEditableAtLine = (lineIndex, attempt = 0, cursorPosition = 'auto', ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof caretOffset === 'number' && caretOffset >= 0) {
|
if (typeof caretOffset === 'number' && caretOffset >= 0) {
|
||||||
|
if (isRangedLine && Number.isInteger(elementSourceLine)) {
|
||||||
|
const text = readEditableTextFromElement(/** @type {HTMLElement} */ (element))
|
||||||
|
const textLines = text.length ? text.split('\n') : ['']
|
||||||
|
const targetLineIndex = Math.max(0, Math.min(lineIndex - elementSourceLine, textLines.length - 1))
|
||||||
|
const lineOffset = textLines
|
||||||
|
.slice(0, targetLineIndex)
|
||||||
|
.reduce((sum, textLine) => sum + textLine.length + 1, 0)
|
||||||
|
|
||||||
|
setEditableCaretOffset(/** @type {HTMLElement} */ (element), lineOffset + caretOffset)
|
||||||
|
element.scrollIntoView({ block: scrollBlock, inline: 'nearest' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setEditableCaretOffset(/** @type {HTMLElement} */ (element), caretOffset)
|
setEditableCaretOffset(/** @type {HTMLElement} */ (element), caretOffset)
|
||||||
element.scrollIntoView({ block: scrollBlock, inline: 'nearest' })
|
element.scrollIntoView({ block: scrollBlock, inline: 'nearest' })
|
||||||
return
|
return
|
||||||
@@ -1415,6 +1441,49 @@ const buildParagraphSplitLines = (head, tail) => {
|
|||||||
return [h, t]
|
return [h, t]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문단 Enter 입력을 블록 단축 입력으로 변환할지 확인한다.
|
||||||
|
* @param {Object} block - 문단 블록
|
||||||
|
* @param {string} before - 커서 앞 텍스트
|
||||||
|
* @param {string} after - 커서 뒤 텍스트
|
||||||
|
* @returns {boolean} 변환 처리 여부
|
||||||
|
*/
|
||||||
|
const applyParagraphShortcutSplit = (block, before, after) => {
|
||||||
|
const head = String(before ?? '').trim()
|
||||||
|
const tail = String(after ?? '')
|
||||||
|
|
||||||
|
if (tail.trim()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^```[A-Za-z0-9_-]*$/.test(head)) {
|
||||||
|
const opener = head === '```' ? buildCodeFenceOpener({ language: '', showLineNumbers: true }) : head
|
||||||
|
|
||||||
|
pendingFocusLine.value = block.meta.startLine + 1
|
||||||
|
pendingFocusPosition.value = 'start'
|
||||||
|
commitInlineBlockLines(block, [opener, '', '```'])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head === '!!!' || head === ':::callout') {
|
||||||
|
pendingFocusLine.value = block.meta.startLine + 1
|
||||||
|
pendingFocusPosition.value = 'start'
|
||||||
|
commitInlineBlockLines(block, [
|
||||||
|
buildCalloutOpenerLine({
|
||||||
|
calloutEmojiEnabled: false,
|
||||||
|
calloutEmoji: '💡',
|
||||||
|
calloutBackground: 'blue',
|
||||||
|
title: ''
|
||||||
|
}),
|
||||||
|
'',
|
||||||
|
':::'
|
||||||
|
])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 문단 Enter 분리 — 마크다운에 빈 줄을 넣어 다음 문단을 만든다.
|
* 문단 Enter 분리 — 마크다운에 빈 줄을 넣어 다음 문단을 만든다.
|
||||||
* @param {Object} block - 블록
|
* @param {Object} block - 블록
|
||||||
@@ -1430,6 +1499,10 @@ const onParagraphSplit = (block, { before, after }) => {
|
|||||||
|
|
||||||
lastParagraphSplitAt = now
|
lastParagraphSplitAt = now
|
||||||
|
|
||||||
|
if (applyParagraphShortcutSplit(block, before, after)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const replacementLines = buildParagraphSplitLines(before, after)
|
const replacementLines = buildParagraphSplitLines(before, after)
|
||||||
const focusLine = block.meta.startLine + Math.max(replacementLines.length - 1, 1)
|
const focusLine = block.meta.startLine + Math.max(replacementLines.length - 1, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,9 @@ const onExitBelow = (payload) => {
|
|||||||
enter-mode="multiline"
|
enter-mode="multiline"
|
||||||
navigation-scope="parent"
|
navigation-scope="parent"
|
||||||
plain-text
|
plain-text
|
||||||
|
arrow-exit-creates-line
|
||||||
:source-line="bodySourceLine"
|
:source-line="bodySourceLine"
|
||||||
|
:source-line-count="String(modelValue ?? '').split('\n').length"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@commit="onBodyCommit"
|
@commit="onBodyCommit"
|
||||||
@insert-below="onExitBelow"
|
@insert-below="onExitBelow"
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ const backgroundClass = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<blockquote
|
<blockquote
|
||||||
class="prose-blockquote mb-2.5 text-[15px] leading-8"
|
class="prose-blockquote mb-5 text-[15px] leading-8"
|
||||||
:class="variant === 'alt'
|
:class="variant === 'alt'
|
||||||
? 'rounded-[14px] border border-[var(--site-line)] bg-[var(--site-panel)] px-6 py-5 italic text-[var(--site-text)]'
|
? 'rounded-[14px] border border-[var(--site-line)] bg-[var(--site-panel)] px-6 py-5 italic text-[var(--site-text)]'
|
||||||
: ['rounded-[10px] border-l-2 px-5 py-4 font-medium text-[#15171a]', backgroundClass]"
|
: ['border-l-[3px] bg-transparent py-1 pl-5 pr-0 font-normal text-[#15171a]', backgroundClass]"
|
||||||
>
|
>
|
||||||
<span class="whitespace-pre-line">
|
<span class="block whitespace-pre-line">
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
@@ -55,31 +55,25 @@ const backgroundClass = computed(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.prose-blockquote--gray {
|
.prose-blockquote--gray {
|
||||||
border-color: #050505;
|
border-color: #050505;
|
||||||
background: color-mix(in srgb, #050505 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-blockquote--blue {
|
.prose-blockquote--blue {
|
||||||
border-color: #0055ff;
|
border-color: #0055ff;
|
||||||
background: color-mix(in srgb, #0055ff 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-blockquote--green {
|
.prose-blockquote--green {
|
||||||
border-color: #16ae68;
|
border-color: #16ae68;
|
||||||
background: color-mix(in srgb, #16ae68 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-blockquote--yellow {
|
.prose-blockquote--yellow {
|
||||||
border-color: #ffff00;
|
border-color: #ffff00;
|
||||||
background: color-mix(in srgb, #ffff00 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-blockquote--red {
|
.prose-blockquote--red {
|
||||||
border-color: #ff0000;
|
border-color: #ff0000;
|
||||||
background: color-mix(in srgb, #ff0000 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-blockquote--purple {
|
.prose-blockquote--purple {
|
||||||
border-color: #8800ff;
|
border-color: #8800ff;
|
||||||
background: color-mix(in srgb, #8800ff 10%, #ffffff);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -63,25 +63,31 @@ const backgroundClass = computed(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.prose-callout--gray {
|
.prose-callout--gray {
|
||||||
background: color-mix(in srgb, #050505 10%, #ffffff);
|
background: color-mix(in srgb, #050505 10%, #ffffff);
|
||||||
|
border: 1px solid #050505;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-callout--blue {
|
.prose-callout--blue {
|
||||||
background: color-mix(in srgb, #0055ff 10%, #ffffff);
|
background: color-mix(in srgb, #0055ff 10%, #ffffff);
|
||||||
|
border: 1px solid #0055ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-callout--green {
|
.prose-callout--green {
|
||||||
background: color-mix(in srgb, #16ae68 10%, #ffffff);
|
background: color-mix(in srgb, #16ae68 10%, #ffffff);
|
||||||
|
border: 1px solid #16ae68;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-callout--yellow {
|
.prose-callout--yellow {
|
||||||
background: color-mix(in srgb, #ffff00 10%, #ffffff);
|
background: color-mix(in srgb, #ffff00 10%, #ffffff);
|
||||||
|
border: 1px solid #ffff00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-callout--red {
|
.prose-callout--red {
|
||||||
background: color-mix(in srgb, #ff0000 10%, #ffffff);
|
background: color-mix(in srgb, #ff0000 10%, #ffffff);
|
||||||
|
border: 1px solid #ff0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose-callout--purple {
|
.prose-callout--purple {
|
||||||
background: color-mix(in srgb, #8800ff 10%, #ffffff);
|
background: color-mix(in srgb, #8800ff 10%, #ffffff);
|
||||||
|
border: 1px solid #8800ff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree`, 관리자 상단 네비 평면 표용 `flattenNavigationEditorWrappers` |
|
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree`, 관리자 상단 네비 평면 표용 `flattenNavigationEditorWrappers` |
|
||||||
| lib/markdown-content-normalizer.js | 관리자 Markdown-first 전환 후 레거시 블록 배열·객체 본문 값을 저장용 마크다운 문자열로 변환 |
|
| lib/markdown-content-normalizer.js | 관리자 Markdown-first 전환 후 레거시 블록 배열·객체 본문 값을 저장용 마크다운 문자열로 변환 |
|
||||||
| lib/markdown-block-context.js | 관리자 Markdown textarea·라이브 편집 포커스 위치 기준 이미지·갤러리·임베드·인용·콜아웃·코드·토글 블록 설정 패널 대상 판별 |
|
| lib/markdown-block-context.js | 관리자 Markdown textarea·라이브 편집 포커스 위치 기준 이미지·갤러리·임베드·인용·콜아웃·코드·토글 블록 설정 패널 대상 판별 |
|
||||||
|
| lib/markdown-callout.js | 인용 막대 색상·콜아웃 배경색 옵션, 선언 줄 파싱·직렬화 |
|
||||||
| lib/brand-color.js | 사이트 브랜드 컬러 기본값·hex 검증·정규화 |
|
| lib/brand-color.js | 사이트 브랜드 컬러 기본값·hex 검증·정규화 |
|
||||||
| lib/markdown-image.js | 이미지 마크다운 직렬화·파싱, 단독 이미지 URL 판별 |
|
| lib/markdown-image.js | 이미지 마크다운 직렬화·파싱, 단독 이미지 URL 판별 |
|
||||||
| lib/markdown-toc.js | 공개 게시글 TOC용 H1~H3 제목 추출과 앵커 ID 생성 |
|
| lib/markdown-toc.js | 공개 게시글 TOC용 H1~H3 제목 추출과 앵커 ID 생성 |
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
| components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 |
|
| components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 |
|
||||||
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(왼쪽 상태 텍스트·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·신규 임시 슬러그·발행·예약·멤버십·비공개 상태 저장, 발행 모달(중앙 배치), 좌우 설정 패널(작은 화면은 오른쪽 고정 오버레이), 오른쪽 `View Post` 링크, 미리보기 emit·미저장 이탈 가드, 추천 글 토글, 태그 색상 배지 다중 입력·메인 태그 드롭다운·부분 검색 추천 |
|
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(왼쪽 상태 텍스트·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·신규 임시 슬러그·발행·예약·멤버십·비공개 상태 저장, 발행 모달(중앙 배치), 좌우 설정 패널(작은 화면은 오른쪽 고정 오버레이), 오른쪽 `View Post` 링크, 미리보기 emit·미저장 이탈 가드, 추천 글 토글, 태그 색상 배지 다중 입력·메인 태그 드롭다운·부분 검색 추천 |
|
||||||
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 페이지 공개 상태 선택, HTML 문서 기본 모드, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입 |
|
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 페이지 공개 상태 선택, HTML 문서 기본 모드, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입 |
|
||||||
| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 소스·라이브 `Cmd+Shift+K` 줄 삭제, 코드·콜아웃·토글 내부 줄 삭제, 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원, 라이브 슬래시 명령 후 포커스 복원 지연 |
|
| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 소스·라이브 `Cmd+Shift+K` 줄 삭제, 소스·라이브 `Cmd/Ctrl+K` 링크 삽입, 코드·콜아웃·토글 내부 줄 삭제, 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원(코드·콜아웃·토글 선언 줄은 본문 줄로 보정), 라이브 슬래시 명령 후 포커스 복원 지연 |
|
||||||
| components/admin/AdminEditorBlockPanel.vue | 게시물 설정 사이드바 오버레이 블록 설정(이미지·갤러리·임베드·인용 배경색·콜아웃 제목·아이콘·배경색·코드·토글), 갤러리 선택 이미지 강조 |
|
| components/admin/AdminEditorBlockPanel.vue | 게시물 설정 사이드바 오버레이 블록 설정(이미지·갤러리·임베드·인용 배경색·콜아웃 제목·아이콘·배경색·코드·토글), 갤러리 선택 이미지 강조 |
|
||||||
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·인용과 같은 배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·인용과 같은 배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
||||||
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) |
|
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) |
|
||||||
@@ -111,11 +112,11 @@
|
|||||||
| 파일 | 화면 위치 |
|
| 파일 | 화면 위치 |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
| components/content/ContentRenderer.vue | 게시물/페이지 본문 |
|
| components/content/ContentRenderer.vue | 게시물/페이지 본문 |
|
||||||
| components/content/ContentMarkdownRenderer.vue | 마크다운 문자열 기반 본문 렌더링, 문단 text-base(16px), 빈 줄 spacer 보존·hard break `<br>` 처리, 확장 블록 파싱, `:::` fenced 블록 원본 범위 보정, 인용 배경 옵션(`> [!bg=...]`), 라이브 콜아웃·인용 포커스 기반 오른쪽 설정 패널 연결, 라이브 인용 Enter 줄 추가·마지막 줄 아래 방향키 이탈, 콜아웃 아래 방향키 새 줄 생성 차단, 라이브 멀티라인 콜아웃 편집 줄 범위 포커스, 라이브 방향키 이동 시 편집 가능한 줄·카드형 블록 탐색, 라이브 코드·콜아웃·토글 내부 줄 삭제와 마지막 줄 블록 삭제, 라이브 이미지·갤러리 드래그 병합·추가·분리 UI, 갤러리 비율 기반 행 레이아웃, 라이브 갤러리 개별 이미지 편집·삭제, 리스트 마커 파란 계열 통일 |
|
| components/content/ContentMarkdownRenderer.vue | 마크다운 문자열 기반 본문 렌더링, 문단 text-base(16px), 빈 줄 spacer 보존·hard break `<br>` 처리, 확장 블록 파싱, `:::` fenced 블록 원본 범위 보정, 닫히지 않은 코드 펜스 하위 콘텐츠 보호, 인용 막대 색상 옵션(`> [!bg=...]`), 라이브 문단 ` ``` `·`!!!` Enter 코드 블록·콜아웃 단축 생성, 라이브 콜아웃·인용 포커스 기반 오른쪽 설정 패널 연결, 라이브 인용 Enter 줄 추가·마지막 줄 아래 방향키 이탈, 콜아웃 아래 방향키 새 줄 생성 차단, 라이브 멀티라인 콜아웃 편집 줄 범위 포커스, 라이브 방향키 이동 시 편집 가능한 줄·카드형 블록 탐색, 라이브 코드·콜아웃·토글 내부 줄 삭제와 마지막 줄 블록 삭제, 라이브 이미지·갤러리 드래그 병합·추가·분리 UI, 갤러리 비율 기반 행 레이아웃, 라이브 갤러리 개별 이미지 편집·삭제, 리스트 마커 파란 계열 통일 |
|
||||||
| components/content/ProseHeading.vue | h1~h6 제목, 기본 mt-12 제거 |
|
| components/content/ProseHeading.vue | h1~h6 제목, 기본 mt-12 제거 |
|
||||||
| components/content/ProseImage.vue | 본문 내 이미지, 로드 실패·빈 URL placeholder |
|
| components/content/ProseImage.vue | 본문 내 이미지, 로드 실패·빈 URL placeholder |
|
||||||
| components/content/ProseList.vue | 목록 |
|
| components/content/ProseList.vue | 목록 |
|
||||||
| components/content/ProseBlockquote.vue | 인용구, 회색 기본값과 인용 전용 배경 프리셋, 다크모드 기본 인용 텍스트 가독성 보정 |
|
| components/content/ProseBlockquote.vue | 인용구, 왼쪽 세로 막대형 기본 스타일과 색상 프리셋, 다크모드 기본 인용 텍스트 가독성 보정 |
|
||||||
| components/content/ProseCodeBlock.vue | 코드 블록 공통 셸(다크 배경, 줄번호 gutter, 공개 복사 버튼) |
|
| components/content/ProseCodeBlock.vue | 코드 블록 공통 셸(다크 배경, 줄번호 gutter, 공개 복사 버튼) |
|
||||||
| components/content/ContentMarkdownCodeBlockEditor.vue | 라이브 모드 코드 블록 인라인 편집(Language·줄번호 토글) |
|
| components/content/ContentMarkdownCodeBlockEditor.vue | 라이브 모드 코드 블록 인라인 편집(Language·줄번호 토글) |
|
||||||
| components/content/ProseButton.vue | 버튼 |
|
| components/content/ProseButton.vue | 버튼 |
|
||||||
|
|||||||
@@ -77,8 +77,8 @@
|
|||||||
- 댓글 정렬은 `인기순`(좋아요 우선), `최신순`, `오래된순`을 제공한다.
|
- 댓글 정렬은 `인기순`(좋아요 우선), `최신순`, `오래된순`을 제공한다.
|
||||||
- 댓글 아바타 이미지 로드 실패 시 이니셜 아바타로 즉시 대체한다.
|
- 댓글 아바타 이미지 로드 실패 시 이니셜 아바타로 즉시 대체한다.
|
||||||
- 공개 게시물 본문은 콘텐츠 타입별 컴포넌트로 분리해 추후 스타일 변경이 쉽도록 구성
|
- 공개 게시물 본문은 콘텐츠 타입별 컴포넌트로 분리해 추후 스타일 변경이 쉽도록 구성
|
||||||
- 인용문(`>`)은 첫 줄 옵션 `> [!bg=blue]` 또는 `> {bg=blue}`로 배경색을 지정할 수 있으며, 지원 값은 콜아웃과 같은 `gray`, `blue`, `green`, `yellow`, `red`, `purple`이다.
|
- 인용문(`>`)은 왼쪽 세로 막대형 기본 스타일로 표시한다. 첫 줄 옵션 `> [!bg=blue]` 또는 `> {bg=blue}`는 인용 막대 색상으로 반영하며, 지원 값은 콜아웃과 같은 `gray`, `blue`, `green`, `yellow`, `red`, `purple`이다.
|
||||||
- 관리자 Markdown-first 글쓰기의 오른쪽 블록 설정 패널은 인용·콜아웃·코드 블록·토글 설정을 지원한다. 콜아웃은 제목·아이콘 표시 여부·아이콘·배경색, 코드 블록은 언어·줄번호 표시 여부, 토글은 기본 펼침·닫힘 상태를 선언 줄에 저장한다. 콜아웃 아이콘은 라이브·공개 화면 모두 왼쪽 상단에 배치하고, 아이콘·제목 헤더 아래에 본문을 줄바꿈해 표시한다. 아이콘 미사용 시 자리 표시자를 남기지 않는다. 한글 등 IME 조합 입력 중에는 줄바꿈 직후 블록 판별이 일시적으로 비어도 마지막 블록 컨텍스트를 유지해 설정 패널이 닫히지 않게 한다.
|
- 관리자 Markdown-first 글쓰기의 오른쪽 블록 설정 패널은 인용·콜아웃·코드 블록·토글 설정을 지원한다. 콜아웃은 제목·아이콘 표시 여부·아이콘·배경색, 코드 블록은 언어·줄번호 표시 여부, 토글은 기본 펼침·닫힘 상태를 선언 줄에 저장한다. 콜아웃 아이콘은 라이브·공개 화면 모두 왼쪽 상단에 배치하고, 아이콘·제목 헤더 아래에 본문을 줄바꿈해 표시한다. 아이콘 미사용 시 자리 표시자를 남기지 않는다. 라이브 문단에서는 ``` Enter로 코드 블록, `!!!` Enter로 콜아웃을 만들 수 있고, 소스·라이브 모드 모두 `Cmd/Ctrl+K`로 링크 마크다운을 삽입한다. 한글 등 IME 조합 입력 중에는 줄바꿈 직후 블록 판별이 일시적으로 비어도 마지막 블록 컨텍스트를 유지해 설정 패널이 닫히지 않게 한다.
|
||||||
- 게시물 상세의 오른쪽 사이드바는 데스크톱에서 추천 사이트 대신 본문 H1~H3 제목 기반 TOC를 표시한다. TOC 링크는 본문 제목에 부여된 앵커 ID로 부드럽게 이동하며, 고정 상단 헤더 높이와 여백을 반영해 제목이 화면 밖에 걸리지 않게 한다. 본문 스크롤 중에는 현재 제목에 해당하는 TOC 항목을 강조하고, 목차 항목이 많으면 TOC 내부 스크롤이 활성 항목을 따라간다. 본문 제목이 없으면 목차 없음 문구를 표시한다. 오른쪽 사이드바가 본문 아래로 내려가는 모바일 폭에서는 TOC를 숨긴다.
|
- 게시물 상세의 오른쪽 사이드바는 데스크톱에서 추천 사이트 대신 본문 H1~H3 제목 기반 TOC를 표시한다. TOC 링크는 본문 제목에 부여된 앵커 ID로 부드럽게 이동하며, 고정 상단 헤더 높이와 여백을 반영해 제목이 화면 밖에 걸리지 않게 한다. 본문 스크롤 중에는 현재 제목에 해당하는 TOC 항목을 강조하고, 목차 항목이 많으면 TOC 내부 스크롤이 활성 항목을 따라간다. 본문 제목이 없으면 목차 없음 문구를 표시한다. 오른쪽 사이드바가 본문 아래로 내려가는 모바일 폭에서는 TOC를 숨긴다.
|
||||||
- 제목 우측 공유 버튼을 누르면 게시물 공유 모달을 연다.
|
- 제목 우측 공유 버튼을 누르면 게시물 공유 모달을 연다.
|
||||||
- 로그인 회원 ID가 게시물 `author_id`와 같으면 제목 우측 공유 버튼 옆에 수정 아이콘을 표시하며, 클릭 시 `/admin/posts/:id` 편집 화면을 새 탭으로 연다.
|
- 로그인 회원 ID가 게시물 `author_id`와 같으면 제목 우측 공유 버튼 옆에 수정 아이콘을 표시하며, 클릭 시 `/admin/posts/:id` 편집 화면을 새 탭으로 연다.
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.5.56
|
||||||
|
|
||||||
|
- 게시물 글쓰기: 소스 모드 코드·콜아웃·토글 선언 줄에서 라이브 모드로 전환해도 실제 본문 줄로 포커스가 복원되도록 수정.
|
||||||
|
- 게시물 글쓰기: 라이브 모드 마지막 코드·콜아웃·토글 본문에서 아래 방향키로 외부 기본 문단을 만들며 빠져나오도록 수정.
|
||||||
|
- 게시물 글쓰기: 닫히지 않은 코드 펜스가 아래 콘텐츠를 코드 블록으로 삼키지 않도록 수정.
|
||||||
|
- 게시물 글쓰기: 라이브 모드 문단에서 ``` 또는 `!!!` 입력 후 Enter로 코드 블록·콜아웃을 빠르게 만들 수 있도록 추가.
|
||||||
|
- 게시물 글쓰기: 소스·라이브 모드 `Cmd/Ctrl+K` 링크 삽입 단축키 추가.
|
||||||
|
- 게시물 본문: 인용문을 배경 카드형 대신 왼쪽 세로 막대형 기본 스타일로 변경.
|
||||||
|
- 게시물 본문: 콜아웃 색상별 보더 표시 추가.
|
||||||
|
|
||||||
## v1.5.55
|
## v1.5.55
|
||||||
|
|
||||||
- 게시물 글쓰기: 소스 모드 `Cmd+Shift+K` 현재 줄 삭제 단축키 복구.
|
- 게시물 글쓰기: 소스 모드 `Cmd+Shift+K` 현재 줄 삭제 단축키 복구.
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ export const QUOTE_BACKGROUND_LABELS = {
|
|||||||
|
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
export const QUOTE_BACKGROUND_SWATCHES = {
|
export const QUOTE_BACKGROUND_SWATCHES = {
|
||||||
|
gray: '#050505',
|
||||||
|
blue: '#0055ff',
|
||||||
|
green: '#16ae68',
|
||||||
|
yellow: '#ffff00',
|
||||||
|
red: '#ff0000',
|
||||||
|
purple: '#8800ff'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Record<string, string>} */
|
||||||
|
export const CALLOUT_BACKGROUND_SWATCHES = {
|
||||||
gray: 'color-mix(in srgb, #050505 10%, #ffffff)',
|
gray: 'color-mix(in srgb, #050505 10%, #ffffff)',
|
||||||
blue: 'color-mix(in srgb, #0055ff 10%, #ffffff)',
|
blue: 'color-mix(in srgb, #0055ff 10%, #ffffff)',
|
||||||
green: 'color-mix(in srgb, #16ae68 10%, #ffffff)',
|
green: 'color-mix(in srgb, #16ae68 10%, #ffffff)',
|
||||||
@@ -24,9 +34,6 @@ export const QUOTE_BACKGROUND_SWATCHES = {
|
|||||||
purple: 'color-mix(in srgb, #8800ff 10%, #ffffff)'
|
purple: 'color-mix(in srgb, #8800ff 10%, #ffffff)'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Record<string, string>} */
|
|
||||||
export const CALLOUT_BACKGROUND_SWATCHES = QUOTE_BACKGROUND_SWATCHES
|
|
||||||
|
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
export const CALLOUT_EMOJI_OPTIONS = ['💡', '⚠️', '❗', '✅', '📌', '🔥', '💬']
|
export const CALLOUT_EMOJI_OPTIONS = ['💡', '⚠️', '❗', '✅', '📌', '🔥', '💬']
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.5.55",
|
"version": "1.5.56",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.5.55",
|
"version": "1.5.56",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.5.55",
|
"version": "1.5.56",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
Reference in New Issue
Block a user