feat(cafe24): HTML 편집기 줄 이동·복사 단축키
Alt+↑/↓ 로 커서가 있는 줄(선택이 있으면 그 줄들)을 위/아래로 옮기고,
Alt+Shift+↑/↓ 로 같은 줄을 하나 더 만든다. 상세페이지는 <img> 한 줄이 곧
한 구획이라 순서 바꾸기·반복이 잦다.
- 이동은 윗줄/아랫줄과 통째로 자리를 바꾸는 방식이라 줄 수가 변하지 않는다.
첫 줄 위·마지막 줄 아래에서는 아무 것도 하지 않는다.
- 복사는 위로 하면 커서가 원래 자리(위쪽 사본)에, 아래로 하면 새로 생긴
아래쪽 사본으로 간다.
- 주석 토글과 같은 줄 범위 규칙을 쓰도록 lineRange()/applyEdit() 로 묶었다.
선택이 개행에서 끝나면 다음 줄은 대상이 아니다.
- execCommand("insertText") 라 Ctrl+Z 로 되돌아간다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,7 @@
|
|||||||
<input type="hidden" name="list_query" value="{{ list_query }}" />
|
<input type="hidden" name="list_query" value="{{ list_query }}" />
|
||||||
|
|
||||||
<div class="cf24-editor-bar">
|
<div class="cf24-editor-bar">
|
||||||
<span class="cf24-muted">상세설명 HTML · {{ desc.description | length }}자 · PC/모바일 공통 · Ctrl+/ 주석</span>
|
<span class="cf24-muted">상세설명 HTML · {{ desc.description | length }}자 · PC/모바일 공통 · Ctrl+/ 주석 · Alt+↑↓ 줄 이동</span>
|
||||||
<span class="cf24-editor-bar-right">
|
<span class="cf24-editor-bar-right">
|
||||||
<input class="cf24-memo" type="text" name="memo" maxlength="200"
|
<input class="cf24-memo" type="text" name="memo" maxlength="200"
|
||||||
placeholder="변경 메모 (버전 이력에 남습니다)" />
|
placeholder="변경 메모 (버전 이력에 남습니다)" />
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
<pre class="cf24-code-hl" id="cf24-hl-pc" aria-hidden="true"></pre>
|
<pre class="cf24-code-hl" id="cf24-hl-pc" aria-hidden="true"></pre>
|
||||||
<textarea id="cf24-html-pc" class="cf24-code-input" name="html" wrap="off"
|
<textarea id="cf24-html-pc" class="cf24-code-input" name="html" wrap="off"
|
||||||
spellcheck="false" autocapitalize="off" autocorrect="off"
|
spellcheck="false" autocapitalize="off" autocorrect="off"
|
||||||
title="Tab 들여쓰기 · Ctrl+/ 주석 토글(선택한 줄 전체, 선택이 없으면 커서 줄)">{{ html_pc }}</textarea>
|
title="Tab 들여쓰기 · Ctrl+/ 주석 토글 · Alt+↑/↓ 줄 이동 · Alt+Shift+↑/↓ 줄 복사 (선택이 없으면 커서가 있는 줄)">{{ html_pc }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -186,6 +186,37 @@
|
|||||||
timer = setTimeout(repaint, 80);
|
timer = setTimeout(repaint, 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 편집 단축키 공용 헬퍼 ──
|
||||||
|
lineRange() : 지금 선택(없으면 커서)이 걸친 **줄 전체** 범위.
|
||||||
|
applyEdit() : 그 범위를 새 문자열로 바꾸고 선택을 다시 잡는다. */
|
||||||
|
|
||||||
|
function lineRange() {
|
||||||
|
var value = ta.value;
|
||||||
|
var start = ta.selectionStart, end = ta.selectionEnd;
|
||||||
|
// 선택이 개행에서 끝나면 그 다음 줄은 대상이 아니다(드래그로 줄 끝까지
|
||||||
|
// 끌었을 때 아래 줄까지 딸려오는 것을 막는다).
|
||||||
|
if (end > start && value.charAt(end - 1) === "\n") end -= 1;
|
||||||
|
var from = value.lastIndexOf("\n", start - 1) + 1;
|
||||||
|
var to = value.indexOf("\n", end);
|
||||||
|
if (to === -1) to = value.length;
|
||||||
|
return { value: value, start: start, end: end, from: from, to: to };
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEdit(from, to, text, selStart, selEnd) {
|
||||||
|
// execCommand 로 넣어야 브라우저의 실행취소(Ctrl+Z) 이력에 남는다.
|
||||||
|
// 지원하지 않으면 value 를 직접 바꾼다(되돌리기만 안 될 뿐 동작은 같다).
|
||||||
|
var value = ta.value;
|
||||||
|
ta.selectionStart = from;
|
||||||
|
ta.selectionEnd = to;
|
||||||
|
var inserted = false;
|
||||||
|
try { inserted = document.execCommand("insertText", false, text); } catch (err) { inserted = false; }
|
||||||
|
if (!inserted) ta.value = value.slice(0, from) + text + value.slice(to);
|
||||||
|
ta.selectionStart = selStart;
|
||||||
|
ta.selectionEnd = selEnd;
|
||||||
|
dirty = true;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
/* Ctrl+/ (Mac ⌘+/) 로 HTML 주석 토글.
|
/* Ctrl+/ (Mac ⌘+/) 로 HTML 주석 토글.
|
||||||
- 선택이 있으면 그 선택이 걸친 **줄 전체**가 대상이다(반쯤 걸친 줄이
|
- 선택이 있으면 그 선택이 걸친 **줄 전체**가 대상이다(반쯤 걸친 줄이
|
||||||
잘려 태그가 깨지지 않게).
|
잘려 태그가 깨지지 않게).
|
||||||
@@ -196,20 +227,11 @@
|
|||||||
var COMMENT_MARK = /<!--[ \t]?|[ \t]?-->/g;
|
var COMMENT_MARK = /<!--[ \t]?|[ \t]?-->/g;
|
||||||
|
|
||||||
function toggleComment() {
|
function toggleComment() {
|
||||||
var value = ta.value;
|
var r = lineRange();
|
||||||
var start = ta.selectionStart, end = ta.selectionEnd;
|
var block = r.value.slice(r.from, r.to);
|
||||||
// 선택이 개행에서 끝나면 그 다음 줄은 대상이 아니다(드래그로 줄 끝까지
|
|
||||||
// 끌었을 때 아래 줄까지 주석되는 것을 막는다).
|
|
||||||
if (end > start && value.charAt(end - 1) === "\n") end -= 1;
|
|
||||||
|
|
||||||
var lineStart = value.lastIndexOf("\n", start - 1) + 1;
|
|
||||||
var lineEnd = value.indexOf("\n", end);
|
|
||||||
if (lineEnd === -1) lineEnd = value.length;
|
|
||||||
|
|
||||||
var block = value.slice(lineStart, lineEnd);
|
|
||||||
if (!block.trim()) return; // 빈 줄에서는 아무 것도 하지 않는다
|
if (!block.trim()) return; // 빈 줄에서는 아무 것도 하지 않는다
|
||||||
|
|
||||||
var caretIn = start - lineStart; // 블록 안에서의 커서 위치
|
var caretIn = r.start - r.from; // 블록 안에서의 커서 위치
|
||||||
var shift = 0, out;
|
var shift = 0, out;
|
||||||
|
|
||||||
COMMENT_MARK.lastIndex = 0;
|
COMMENT_MARK.lastIndex = 0;
|
||||||
@@ -225,23 +247,45 @@
|
|||||||
shift = caretIn >= indent.length ? 5 : 0; // "<!-- " 길이
|
shift = caretIn >= indent.length ? 5 : 0; // "<!-- " 길이
|
||||||
}
|
}
|
||||||
|
|
||||||
// execCommand 로 넣어야 브라우저의 실행취소(Ctrl+Z) 이력에 남는다.
|
if (r.start === r.end) {
|
||||||
// 지원하지 않으면 value 를 직접 바꾼다(그때는 되돌리기가 안 될 뿐 동작은 같다).
|
var pos = r.from + Math.min(out.length, Math.max(0, caretIn + shift));
|
||||||
ta.selectionStart = lineStart;
|
applyEdit(r.from, r.to, out, pos, pos);
|
||||||
ta.selectionEnd = lineEnd;
|
|
||||||
var inserted = false;
|
|
||||||
try { inserted = document.execCommand("insertText", false, out); } catch (err) { inserted = false; }
|
|
||||||
if (!inserted) ta.value = value.slice(0, lineStart) + out + value.slice(lineEnd);
|
|
||||||
|
|
||||||
if (start === end) {
|
|
||||||
var pos = lineStart + Math.min(out.length, Math.max(0, caretIn + shift));
|
|
||||||
ta.selectionStart = ta.selectionEnd = pos;
|
|
||||||
} else {
|
} else {
|
||||||
ta.selectionStart = lineStart; // 바꾼 범위를 계속 선택해 둔다
|
// 바꾼 범위를 계속 선택해 둔다(연달아 다시 누르면 그대로 되돌아온다).
|
||||||
ta.selectionEnd = lineStart + out.length;
|
applyEdit(r.from, r.to, out, r.from, r.from + out.length);
|
||||||
}
|
}
|
||||||
dirty = true;
|
}
|
||||||
refresh();
|
|
||||||
|
/* Alt+↑/↓ — 커서가 있는 줄(선택이 있으면 그 줄들)을 위/아래로 옮긴다.
|
||||||
|
윗줄/아랫줄과 통째로 자리를 바꾸는 방식이라 줄 수는 그대로다. */
|
||||||
|
function moveLines(down) {
|
||||||
|
var r = lineRange();
|
||||||
|
var block = r.value.slice(r.from, r.to);
|
||||||
|
if (!down) {
|
||||||
|
if (r.from === 0) return; // 첫 줄 위로는 못 간다
|
||||||
|
var prevFrom = r.value.lastIndexOf("\n", r.from - 2) + 1;
|
||||||
|
var prev = r.value.slice(prevFrom, r.from - 1);
|
||||||
|
var up = -(prev.length + 1);
|
||||||
|
applyEdit(prevFrom, r.to, block + "\n" + prev, r.start + up, r.end + up);
|
||||||
|
} else {
|
||||||
|
if (r.to >= r.value.length) return; // 마지막 줄 아래로는 못 간다
|
||||||
|
var nextTo = r.value.indexOf("\n", r.to + 1);
|
||||||
|
if (nextTo === -1) nextTo = r.value.length;
|
||||||
|
var next = r.value.slice(r.to + 1, nextTo);
|
||||||
|
var dn = next.length + 1;
|
||||||
|
applyEdit(r.from, nextTo, next + "\n" + block, r.start + dn, r.end + dn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alt+Shift+↑/↓ — 같은 줄을 하나 더 만든다.
|
||||||
|
위로 복사하면 커서는 원래 자리(위쪽 사본)에, 아래로 복사하면 새로 생긴
|
||||||
|
아래쪽 사본으로 옮긴다(VS Code 와 같은 느낌). */
|
||||||
|
function duplicateLines(down) {
|
||||||
|
var r = lineRange();
|
||||||
|
var block = r.value.slice(r.from, r.to);
|
||||||
|
var text = block + "\n" + block;
|
||||||
|
var shift = down ? block.length + 1 : 0;
|
||||||
|
applyEdit(r.from, r.to, text, r.start + shift, r.end + shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
ta.addEventListener("scroll", sync);
|
ta.addEventListener("scroll", sync);
|
||||||
@@ -254,6 +298,15 @@
|
|||||||
toggleComment();
|
toggleComment();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Alt+↑/↓ 줄 이동, Alt+Shift+↑/↓ 줄 복사.
|
||||||
|
var isUp = e.key === "ArrowUp" || e.code === "ArrowUp";
|
||||||
|
var isDown = e.key === "ArrowDown" || e.code === "ArrowDown";
|
||||||
|
if (e.altKey && !e.ctrlKey && !e.metaKey && (isUp || isDown)) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.shiftKey) duplicateLines(isDown);
|
||||||
|
else moveLines(isDown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Tab 은 포커스 이동이 아니라 들여쓰기로 쓴다.
|
// Tab 은 포커스 이동이 아니라 들여쓰기로 쓴다.
|
||||||
if (e.key !== "Tab") return;
|
if (e.key !== "Tab") return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
Reference in New Issue
Block a user