Merge branch 'remove-easymde-fix-ime' into main
This commit is contained in:
@@ -954,8 +954,11 @@ label {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 마크다운 에디터 툴바 (EasyMDE는 toolbar:false로 끄고 여기서 자체 버튼으로 대체 —
|
/* 마크다운 에디터 툴바 — 예전엔 EasyMDE(CodeMirror) 위에 얹었던 자체 버튼이었는데, iOS에서
|
||||||
EasyMDE 기본 툴바는 Font Awesome CDN을 전제로 해서 이 앱의 "CDN 금지" 원칙과 안 맞는다) */
|
CodeMirror가 한글 조합(IME) 중에 화면을 다시 그리면서 자소분리를 일으키는 문제 때문에
|
||||||
|
에디터를 순수 <textarea>(.markdown-editor)로 되돌렸다 — app/static/js/journal-editor.js의
|
||||||
|
JournalEditor가 선택 영역에 마크다운 문법을 직접 넣고 빼는 방식으로 같은 버튼들을 지원한다.
|
||||||
|
.markdown-editor 자체는 위 공통 textarea 규칙을 그대로 물려받아 별도 스타일이 필요 없다. */
|
||||||
.markdown-toolbar {
|
.markdown-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -979,34 +982,6 @@ label {
|
|||||||
background: var(--color-bg);
|
background: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EasyMDE(CodeMirror) 컨테이너를 이 앱의 입력 필드 톤에 맞춘다 */
|
|
||||||
.markdown-editor + .EasyMDEContainer .CodeMirror {
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-control);
|
|
||||||
background: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 6px 8px;
|
|
||||||
overflow: hidden; /* 안에서 뭐가 카드 폭보다 커지려 해도 밖으로 안 새어나가게 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor + .EasyMDEContainer .CodeMirror-cursor {
|
|
||||||
border-left-color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-editor + .EasyMDEContainer .editor-statusbar {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 원본 해상도가 큰 이미지를 붙여넣었을 때 에디터/미리보기 폭을 넘어가지 않게 캡핑.
|
|
||||||
.journal-entry-content img가 미리보기(.journal-preview)는 이미 커버하지만, 에디터 쪽
|
|
||||||
(CodeMirror가 마크다운 이미지를 인라인 위젯으로 그리는 경우)도 같은 규칙을 강제로 적용. */
|
|
||||||
.markdown-editor + .EasyMDEContainer .CodeMirror img {
|
|
||||||
max-width: 100% !important;
|
|
||||||
height: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.journal-entry-content > *:first-child {
|
.journal-entry-content > *:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
-7
File diff suppressed because one or more lines are too long
+162
-70
@@ -1,59 +1,158 @@
|
|||||||
(function () {
|
(function () {
|
||||||
// 내장 툴바(toolbar: false)는 안 쓴다 — EasyMDE 기본 툴바 아이콘은 Font Awesome CDN을 전제로
|
// 예전엔 이 파일이 EasyMDE(CodeMirror 5 기반 마크다운 에디터)를 초기화했는데, iOS Safari에서
|
||||||
// 하는데, 이 앱은 CDN을 안 쓰는 게 원칙이라 대신 journal.html/journal_day_detail.html에서
|
// 한글처럼 여러 keystroke를 조합해 한 글자를 완성하는 IME 입력 중에 CodeMirror가 화면을
|
||||||
// 직접 만든 텍스트 버튼이 아래 EDITOR_ACTIONS로 EasyMDE 인스턴스 메서드를 호출한다.
|
// 다시 그리면서 조합 버퍼를 끊어버려 자소가 분리된 채로 남는 문제(자소분리)가 있었다.
|
||||||
function initEditors(root) {
|
// inputStyle을 contenteditable로 강제해도 아이폰에서까지 재현돼서(CodeMirror5 자체의 CJK
|
||||||
if (typeof EasyMDE === "undefined") return;
|
// IME 한계로 보임), 에디터 엔진을 아예 걷어내고 순수 <textarea>(.markdown-editor)로
|
||||||
var scope = root instanceof Element ? root : document;
|
// 되돌렸다 — 브라우저 네이티브 입력 처리를 그대로 쓰면 IME 조합이 깨질 이유가 없다.
|
||||||
var textareas = scope.querySelectorAll("textarea.markdown-editor:not([data-easymde-initialized])");
|
// 아래는 그 대신 굵게/기울임 같은 툴바 버튼이 쓸 수 있게 선택 영역에 마크다운 문법을
|
||||||
|
// 직접 넣고 빼는 최소 구현이다 (journal_editor_toolbar.html에서 호출).
|
||||||
|
|
||||||
textareas.forEach(function (textarea) {
|
function fireInput(textarea) {
|
||||||
textarea.setAttribute("data-easymde-initialized", "true");
|
|
||||||
|
|
||||||
var easymde = new EasyMDE({
|
|
||||||
element: textarea,
|
|
||||||
toolbar: false,
|
|
||||||
spellChecker: false,
|
|
||||||
autoDownloadFontAwesome: false,
|
|
||||||
status: false,
|
|
||||||
placeholder: textarea.getAttribute("placeholder") || "",
|
|
||||||
minHeight: (textarea.getAttribute("rows") || 6) * 24 + "px",
|
|
||||||
// 글머리 목록 버튼/Enter 자동 이어쓰기가 기본 "*" 대신 "-"를 쓰게 한다.
|
|
||||||
// 서버 렌더링(app/markdown_utils.py)은 -/*/+ 전부 동일하게 처리하니 렌더링과는 무관하고
|
|
||||||
// 순수하게 에디터가 새로 만들어주는 글머리 기호에 대한 취향 설정이다.
|
|
||||||
unorderedListStyle: "-",
|
|
||||||
// CodeMirror(EasyMDE 내부 엔진)는 User-Agent로 모바일 여부를 판단해 입력 모드를
|
|
||||||
// textarea/contenteditable 중 하나로 자동으로 고른다. iPadOS 13+는 기본 설정에서
|
|
||||||
// 데스크톱 Safari인 척하는 UA를 보내기 때문에(Mac으로 오인) 이 자동 감지가 실패해서
|
|
||||||
// 더 취약한 textarea 모드로 떨어지고, 한글처럼 여러 keystroke를 조합해 한 글자를
|
|
||||||
// 완성하는 IME 입력 중에 CodeMirror가 화면을 다시 그리면서 조합 버퍼를 끊어버려
|
|
||||||
// 자소가 분리된 채로 남는 현상(자소분리)이 생긴다. contenteditable 모드를 생성 시점에
|
|
||||||
// 명시적으로 강제해 이 오감지를 우회한다 — inputStyle은 CodeMirror 생성 이후에는
|
|
||||||
// 바꿀 수 없는 옵션이라(런타임에 setOption을 호출하면 에러) 반드시 여기서 지정해야
|
|
||||||
// 한다. iPhone은 원래도 정상 감지되지만 동일하게 명시해서 일관되게 둔다.
|
|
||||||
inputStyle: "contenteditable",
|
|
||||||
});
|
|
||||||
|
|
||||||
// CodeMirror는 원본 textarea와 실시간으로 값이 동기화되지 않는다(.save()를 명시적으로
|
|
||||||
// 불러야 함) — 매 변경마다 저장하고, htmx 미리보기(hx-trigger="input ...")와
|
|
||||||
// Alpine x-model이 둘 다 반응하도록 input 이벤트를 합성해서 던진다.
|
|
||||||
easymde.codemirror.on("change", function () {
|
|
||||||
easymde.codemirror.save();
|
|
||||||
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
});
|
|
||||||
|
|
||||||
easymde.codemirror.on("paste", function (cm, event) {
|
|
||||||
handleImagePaste(cm, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
textarea._easymde = easymde;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function insertAtCursor(textarea, text) {
|
||||||
|
var start = textarea.selectionStart;
|
||||||
|
var end = textarea.selectionEnd;
|
||||||
|
var value = textarea.value;
|
||||||
|
textarea.value = value.slice(0, start) + text + value.slice(end);
|
||||||
|
var pos = start + text.length;
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = pos;
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 클립보드 이미지 붙여넣기용: 업로드 중 표식을 값에서 찾아 최종 마크다운 링크로 바꾼다.
|
||||||
|
// 고정된 커서 좌표 대신 텍스트 표식으로 위치를 추적하므로, 업로드가 끝나기 전에 사용자가
|
||||||
|
// 다른 곳을 계속 타이핑해도 자리를 잃지 않는다. 표식이 이미 지워졌으면(사용자가 직접
|
||||||
|
// 지운 경우) 조용히 넘어간다.
|
||||||
|
function replaceMarker(textarea, marker, replacement) {
|
||||||
|
var idx = textarea.value.indexOf(marker);
|
||||||
|
if (idx === -1) return;
|
||||||
|
var before = textarea.value.slice(0, idx);
|
||||||
|
var after = textarea.value.slice(idx + marker.length);
|
||||||
|
textarea.value = before + replacement + after;
|
||||||
|
var pos = before.length + replacement.length;
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = pos;
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapSelection(textarea, prefix, suffix) {
|
||||||
|
if (suffix === undefined) suffix = prefix;
|
||||||
|
var start = textarea.selectionStart;
|
||||||
|
var end = textarea.selectionEnd;
|
||||||
|
var value = textarea.value;
|
||||||
|
var selected = value.slice(start, end);
|
||||||
|
textarea.value = value.slice(0, start) + prefix + selected + suffix + value.slice(end);
|
||||||
|
textarea.selectionStart = start + prefix.length;
|
||||||
|
textarea.selectionEnd = start + prefix.length + selected.length;
|
||||||
|
textarea.focus();
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커서(또는 선택 영역)가 걸쳐 있는 줄들의 범위를 구한다 — 글머리 기호/인용 등 줄 단위
|
||||||
|
// 토글에 쓰인다.
|
||||||
|
function currentLineRange(textarea) {
|
||||||
|
var start = textarea.selectionStart;
|
||||||
|
var end = textarea.selectionEnd;
|
||||||
|
var value = textarea.value;
|
||||||
|
var lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
||||||
|
var lineEnd = value.indexOf("\n", end);
|
||||||
|
if (lineEnd === -1) lineEnd = value.length;
|
||||||
|
return { lineStart: lineStart, lineEnd: lineEnd, block: value.slice(lineStart, lineEnd) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceBlock(textarea, range, newBlock) {
|
||||||
|
var value = textarea.value;
|
||||||
|
textarea.value = value.slice(0, range.lineStart) + newBlock + value.slice(range.lineEnd);
|
||||||
|
textarea.selectionStart = range.lineStart;
|
||||||
|
textarea.selectionEnd = range.lineStart + newBlock.length;
|
||||||
|
textarea.focus();
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleLinePrefix(textarea, prefix) {
|
||||||
|
var range = currentLineRange(textarea);
|
||||||
|
var lines = range.block.split("\n");
|
||||||
|
var allPrefixed = lines.every(function (line) { return line.indexOf(prefix) === 0; });
|
||||||
|
var newLines = lines.map(function (line) {
|
||||||
|
if (allPrefixed) return line.slice(prefix.length);
|
||||||
|
return line.indexOf(prefix) === 0 ? line : prefix + line;
|
||||||
|
});
|
||||||
|
replaceBlock(textarea, range, newLines.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOrderedList(textarea) {
|
||||||
|
var range = currentLineRange(textarea);
|
||||||
|
var lines = range.block.split("\n");
|
||||||
|
var re = /^\d+\.\s/;
|
||||||
|
var allNumbered = lines.every(function (line) { return re.test(line); });
|
||||||
|
var newLines = lines.map(function (line, i) {
|
||||||
|
return allNumbered ? line.replace(re, "") : (i + 1) + ". " + line.replace(re, "");
|
||||||
|
});
|
||||||
|
replaceBlock(textarea, range, newLines.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHeading(textarea) {
|
||||||
|
var range = currentLineRange(textarea);
|
||||||
|
var re = /^#{1,6}\s/;
|
||||||
|
var newBlock = re.test(range.block) ? range.block.replace(re, "") : "### " + range.block;
|
||||||
|
replaceBlock(textarea, range, newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCode(textarea) {
|
||||||
|
var start = textarea.selectionStart;
|
||||||
|
var end = textarea.selectionEnd;
|
||||||
|
var selected = textarea.value.slice(start, end);
|
||||||
|
if (selected.indexOf("\n") === -1) {
|
||||||
|
wrapSelection(textarea, "`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var value = textarea.value;
|
||||||
|
textarea.value = value.slice(0, start) + "```\n" + selected + "\n```" + value.slice(end);
|
||||||
|
textarea.selectionStart = start + 4;
|
||||||
|
textarea.selectionEnd = start + 4 + selected.length;
|
||||||
|
textarea.focus();
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertLink(textarea) {
|
||||||
|
var start = textarea.selectionStart;
|
||||||
|
var end = textarea.selectionEnd;
|
||||||
|
var selected = textarea.value.slice(start, end) || "링크 텍스트";
|
||||||
|
var url = window.prompt("링크 주소를 입력하세요", "https://");
|
||||||
|
if (!url) return;
|
||||||
|
var value = textarea.value;
|
||||||
|
var markdown = "[" + selected + "](" + url + ")";
|
||||||
|
textarea.value = value.slice(0, start) + markdown + value.slice(end);
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = start + markdown.length;
|
||||||
|
textarea.focus();
|
||||||
|
fireInput(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
// journal_editor_toolbar.html의 버튼들과 journal.html의 카테고리 템플릿 자동 채우기가
|
||||||
|
// 호출하는 공개 API.
|
||||||
|
window.JournalEditor = {
|
||||||
|
bold: function (t) { if (t) wrapSelection(t, "**"); },
|
||||||
|
italic: function (t) { if (t) wrapSelection(t, "*"); },
|
||||||
|
strike: function (t) { if (t) wrapSelection(t, "~~"); },
|
||||||
|
heading: function (t) { if (t) toggleHeading(t); },
|
||||||
|
quote: function (t) { if (t) toggleLinePrefix(t, "> "); },
|
||||||
|
ul: function (t) { if (t) toggleLinePrefix(t, "- "); },
|
||||||
|
ol: function (t) { if (t) toggleOrderedList(t); },
|
||||||
|
code: function (t) { if (t) toggleCode(t); },
|
||||||
|
link: function (t) { if (t) insertLink(t); },
|
||||||
|
setValue: function (t, value) {
|
||||||
|
if (!t) return;
|
||||||
|
t.value = value;
|
||||||
|
fireInput(t);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// 클립보드에 이미지가 있으면(스크린샷/사진 복사 등) 그대로 붙여넣기 대신 서버에 업로드하고
|
// 클립보드에 이미지가 있으면(스크린샷/사진 복사 등) 그대로 붙여넣기 대신 서버에 업로드하고
|
||||||
// 그 자리에 마크다운 이미지 문법()을 끼워넣는다. 이미지가 아니면 그냥 통과시켜서
|
// 그 자리에 마크다운 이미지 문법()을 끼워넣는다. 이미지가 아니면 그냥 통과시켜서
|
||||||
// CodeMirror 기본 텍스트 붙여넣기가 그대로 동작하게 둔다.
|
// 기본 텍스트 붙여넣기가 그대로 동작하게 둔다.
|
||||||
function handleImagePaste(cm, event) {
|
function handleImagePaste(textarea, event) {
|
||||||
var items = event.clipboardData && event.clipboardData.items;
|
var items = event.clipboardData && event.clipboardData.items;
|
||||||
if (!items) return;
|
if (!items) return;
|
||||||
|
|
||||||
@@ -70,15 +169,8 @@
|
|||||||
var file = imageItem.getAsFile();
|
var file = imageItem.getAsFile();
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
var doc = cm.getDoc();
|
var marker = "![업로드 중… #" + Math.random().toString(36).slice(2, 8) + "]()";
|
||||||
var from = doc.getCursor();
|
insertAtCursor(textarea, marker);
|
||||||
var placeholder = "![업로드 중...]()";
|
|
||||||
doc.replaceRange(placeholder, from);
|
|
||||||
var to = { line: from.line, ch: from.ch + placeholder.length };
|
|
||||||
// 업로드가 끝나기 전에 사용자가 다른 곳에서 계속 타이핑해도(줄 추가 등) 자리를 잃지
|
|
||||||
// 않도록 고정 좌표 대신 CodeMirror 북마크로 추적한다.
|
|
||||||
var startMark = doc.setBookmark(from);
|
|
||||||
var endMark = doc.setBookmark(to);
|
|
||||||
|
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append("file", file, file.name || "pasted-image.png");
|
formData.append("file", file, file.name || "pasted-image.png");
|
||||||
@@ -89,20 +181,20 @@
|
|||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
var start = startMark.find();
|
replaceMarker(textarea, marker, "");
|
||||||
var end = endMark.find();
|
|
||||||
if (start && end) { doc.replaceRange("", start, end); }
|
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
var start = startMark.find();
|
replaceMarker(textarea, marker, "(이미지 붙여넣기 실패: " + err.message + ")");
|
||||||
var end = endMark.find();
|
});
|
||||||
if (start && end) { doc.replaceRange("(이미지 붙여넣기 실패: " + err.message + ")", start, end); }
|
}
|
||||||
})
|
|
||||||
.finally(function () {
|
function initEditors(root) {
|
||||||
startMark.clear();
|
var scope = root instanceof Element ? root : document;
|
||||||
endMark.clear();
|
var textareas = scope.querySelectorAll("textarea.markdown-editor:not([data-journal-editor-initialized])");
|
||||||
cm.save();
|
|
||||||
cm.getTextArea().dispatchEvent(new Event("input", { bubbles: true }));
|
textareas.forEach(function (textarea) {
|
||||||
|
textarea.setAttribute("data-journal-editor-initialized", "true");
|
||||||
|
textarea.addEventListener("paste", function (event) { handleImagePaste(textarea, event); });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
-7
File diff suppressed because one or more lines are too long
@@ -1,7 +1,6 @@
|
|||||||
const CACHE_NAME = "habit-tracker-v6";
|
const CACHE_NAME = "habit-tracker-v7";
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
"/static/css/style.css",
|
"/static/css/style.css",
|
||||||
"/static/css/vendor/easymde.min.css",
|
|
||||||
"/static/js/app.js",
|
"/static/js/app.js",
|
||||||
"/static/js/push-register.js",
|
"/static/js/push-register.js",
|
||||||
"/static/js/habit-reorder.js",
|
"/static/js/habit-reorder.js",
|
||||||
@@ -10,7 +9,6 @@ const APP_SHELL = [
|
|||||||
"/static/js/vendor/htmx.min.js",
|
"/static/js/vendor/htmx.min.js",
|
||||||
"/static/js/vendor/alpine.min.js",
|
"/static/js/vendor/alpine.min.js",
|
||||||
"/static/js/vendor/sortable.min.js",
|
"/static/js/vendor/sortable.min.js",
|
||||||
"/static/js/vendor/easymde.min.js",
|
|
||||||
"/static/icons/icon-192.png",
|
"/static/icons/icon-192.png",
|
||||||
"/static/icons/icon-512.png",
|
"/static/icons/icon-512.png",
|
||||||
"/static/icons/icon-apple-180.png",
|
"/static/icons/icon-apple-180.png",
|
||||||
|
|||||||
@@ -18,13 +18,11 @@
|
|||||||
<link rel="icon" href="/static/icons/icon-192.png" />
|
<link rel="icon" href="/static/icons/icon-192.png" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/style.css" />
|
<link rel="stylesheet" href="/static/css/style.css" />
|
||||||
<link rel="stylesheet" href="/static/css/vendor/easymde.min.css" />
|
|
||||||
<script src="/static/js/vendor/htmx.min.js" defer></script>
|
<script src="/static/js/vendor/htmx.min.js" defer></script>
|
||||||
<script src="/static/js/push-register.js" defer></script>
|
<script src="/static/js/push-register.js" defer></script>
|
||||||
<script src="/static/js/vendor/sortable.min.js" defer></script>
|
<script src="/static/js/vendor/sortable.min.js" defer></script>
|
||||||
<script src="/static/js/habit-reorder.js" defer></script>
|
<script src="/static/js/habit-reorder.js" defer></script>
|
||||||
<script src="/static/js/journal-category-reorder.js" defer></script>
|
<script src="/static/js/journal-category-reorder.js" defer></script>
|
||||||
<script src="/static/js/vendor/easymde.min.js" defer></script>
|
|
||||||
<script src="/static/js/journal-editor.js" defer></script>
|
<script src="/static/js/journal-editor.js" defer></script>
|
||||||
<script src="/static/js/vendor/alpine.min.js" defer></script>
|
<script src="/static/js/vendor/alpine.min.js" defer></script>
|
||||||
<script src="/static/js/app.js" defer></script>
|
<script src="/static/js/app.js" defer></script>
|
||||||
|
|||||||
@@ -134,10 +134,7 @@
|
|||||||
if (this.content === this.lastAutoFilled) {
|
if (this.content === this.lastAutoFilled) {
|
||||||
this.content = tmpl;
|
this.content = tmpl;
|
||||||
this.lastAutoFilled = tmpl;
|
this.lastAutoFilled = tmpl;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => { this.$refs.contentField.dispatchEvent(new Event('input')); });
|
||||||
if (this.$refs.contentField._easymde) { this.$refs.contentField._easymde.value(tmpl); }
|
|
||||||
this.$refs.contentField.dispatchEvent(new Event('input'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() { this.onCategoryChange(this.$refs.categorySelect.value); },
|
init() { this.onCategoryChange(this.$refs.categorySelect.value); },
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<div class="markdown-toolbar">
|
<div class="markdown-toolbar">
|
||||||
<span x-show="!previewMode" style="display:flex; gap:4px; flex-wrap:wrap;">
|
<span x-show="!previewMode" style="display:flex; gap:4px; flex-wrap:wrap;">
|
||||||
<button type="button" class="md-tool-btn" title="굵게" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleBold()"><strong>B</strong></button>
|
<button type="button" class="md-tool-btn" title="굵게" @click="JournalEditor.bold($el.closest('.field').querySelector('textarea'))"><strong>B</strong></button>
|
||||||
<button type="button" class="md-tool-btn" title="기울임" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleItalic()"><em>I</em></button>
|
<button type="button" class="md-tool-btn" title="기울임" @click="JournalEditor.italic($el.closest('.field').querySelector('textarea'))"><em>I</em></button>
|
||||||
<button type="button" class="md-tool-btn" title="취소선" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleStrikethrough()"><s>S</s></button>
|
<button type="button" class="md-tool-btn" title="취소선" @click="JournalEditor.strike($el.closest('.field').querySelector('textarea'))"><s>S</s></button>
|
||||||
<button type="button" class="md-tool-btn" title="제목" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleHeadingSmaller()">H</button>
|
<button type="button" class="md-tool-btn" title="제목" @click="JournalEditor.heading($el.closest('.field').querySelector('textarea'))">H</button>
|
||||||
<button type="button" class="md-tool-btn" title="인용" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleBlockquote()">❝</button>
|
<button type="button" class="md-tool-btn" title="인용" @click="JournalEditor.quote($el.closest('.field').querySelector('textarea'))">❝</button>
|
||||||
<button type="button" class="md-tool-btn" title="글머리 목록" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleUnorderedList()">•</button>
|
<button type="button" class="md-tool-btn" title="글머리 목록" @click="JournalEditor.ul($el.closest('.field').querySelector('textarea'))">•</button>
|
||||||
<button type="button" class="md-tool-btn" title="번호 목록" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleOrderedList()">1.</button>
|
<button type="button" class="md-tool-btn" title="번호 목록" @click="JournalEditor.ol($el.closest('.field').querySelector('textarea'))">1.</button>
|
||||||
<button type="button" class="md-tool-btn" title="코드" @click="$el.closest('.field').querySelector('textarea')._easymde?.toggleCodeBlock()"></></button>
|
<button type="button" class="md-tool-btn" title="코드" @click="JournalEditor.code($el.closest('.field').querySelector('textarea'))"></></button>
|
||||||
<button type="button" class="md-tool-btn" title="링크" @click="$el.closest('.field').querySelector('textarea')._easymde?.drawLink()">🔗</button>
|
<button type="button" class="md-tool-btn" title="링크" @click="JournalEditor.link($el.closest('.field').querySelector('textarea'))">🔗</button>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user