feat: [rd] 클코 → 슬랙 변환기 추가
- Claude Code CLI 출력을 슬랙 mrkdwn 형식으로 자동 변환 - 마크다운 → 슬랙 문법 변환 (볼드, 코드블록, 링크 등) - 슬랙 스타일 실시간 미리보기 - 클립보드 복사/붙여넣기 지원
This commit is contained in:
@@ -613,4 +613,16 @@ public function fireShutterDrawing(Request $request): View|\Illuminate\Http\Resp
|
||||
|
||||
return view('rd.fire-shutter-drawing.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 클코 → 슬랙 변환기
|
||||
*/
|
||||
public function ccToSlack(Request $request): View|\Illuminate\Http\Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('rd.cc-to-slack'));
|
||||
}
|
||||
|
||||
return view('rd.cc-to-slack.index');
|
||||
}
|
||||
}
|
||||
|
||||
537
resources/views/rd/cc-to-slack/index.blade.php
Normal file
537
resources/views/rd/cc-to-slack/index.blade.php
Normal file
@@ -0,0 +1,537 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '클코 → 슬랙 변환기')
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
:root {
|
||||
--cs-bg: #0f172a;
|
||||
--cs-card: #1e293b;
|
||||
--cs-card2: #334155;
|
||||
--cs-border: #475569;
|
||||
--cs-text: #f1f5f9;
|
||||
--cs-text2: #94a3b8;
|
||||
--cs-purple: #8b5cf6;
|
||||
--cs-green: #10b981;
|
||||
--cs-blue: #3b82f6;
|
||||
--cs-amber: #f59e0b;
|
||||
--cs-red: #ef4444;
|
||||
--cs-radius: 10px;
|
||||
}
|
||||
.cs-wrap {
|
||||
display: flex; flex-direction: column; height: calc(100vh - 64px);
|
||||
background: var(--cs-bg); overflow: hidden;
|
||||
font-family: 'Pretendard', -apple-system, sans-serif; color: var(--cs-text);
|
||||
margin: -24px;
|
||||
}
|
||||
.cs-toolbar {
|
||||
display: flex; align-items: center; height: 48px; padding: 0 16px;
|
||||
background: var(--cs-card); border-bottom: 1px solid var(--cs-border);
|
||||
gap: 12px; flex-shrink: 0;
|
||||
}
|
||||
.cs-toolbar h1 { font-size: 14px; font-weight: 700; margin: 0; display: flex; align-items: center; gap: 6px; }
|
||||
.cs-toolbar h1 i { color: var(--cs-purple); }
|
||||
.cs-btn {
|
||||
display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px;
|
||||
border-radius: 6px; font-size: 12px; font-weight: 500; cursor: pointer;
|
||||
border: 1px solid var(--cs-border); background: var(--cs-card2); color: var(--cs-text);
|
||||
transition: all .15s;
|
||||
}
|
||||
.cs-btn:hover { background: #475569; }
|
||||
.cs-btn.primary { background: var(--cs-purple); border-color: var(--cs-purple); color: #fff; }
|
||||
.cs-btn.primary:hover { background: #7c3aed; }
|
||||
.cs-btn.success { background: var(--cs-green); border-color: var(--cs-green); color: #fff; }
|
||||
.cs-btn.success:hover { background: #059669; }
|
||||
.cs-btn.danger { background: var(--cs-red); border-color: var(--cs-red); color: #fff; }
|
||||
.cs-btn.blue { background: var(--cs-blue); border-color: var(--cs-blue); color: #fff; }
|
||||
.cs-btn.blue:hover { background: #2563eb; }
|
||||
|
||||
.cs-body {
|
||||
display: flex; flex: 1; overflow: hidden;
|
||||
}
|
||||
.cs-panel {
|
||||
flex: 1; display: flex; flex-direction: column; overflow: hidden;
|
||||
border-right: 1px solid var(--cs-border);
|
||||
}
|
||||
.cs-panel:last-child { border-right: none; }
|
||||
.cs-panel-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 8px 12px; background: var(--cs-card);
|
||||
border-bottom: 1px solid var(--cs-border); flex-shrink: 0;
|
||||
}
|
||||
.cs-panel-header .title {
|
||||
font-size: 12px; font-weight: 600; color: var(--cs-text2);
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.cs-panel-header .actions { display: flex; gap: 6px; }
|
||||
|
||||
.cs-editor {
|
||||
flex: 1; width: 100%; background: transparent; color: var(--cs-text);
|
||||
border: none; outline: none; resize: none; padding: 12px;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||
font-size: 13px; line-height: 1.6;
|
||||
}
|
||||
.cs-editor::placeholder { color: var(--cs-text2); }
|
||||
|
||||
.cs-preview {
|
||||
flex: 1; overflow-y: auto; padding: 12px;
|
||||
font-family: 'Pretendard', -apple-system, sans-serif;
|
||||
font-size: 13px; line-height: 1.6;
|
||||
background: #1a1d21; /* 슬랙 다크 테마 배경 */
|
||||
}
|
||||
/* 슬랙 스타일 미리보기 */
|
||||
.cs-preview .slack-bold { font-weight: 700; }
|
||||
.cs-preview .slack-italic { font-style: italic; }
|
||||
.cs-preview .slack-strike { text-decoration: line-through; }
|
||||
.cs-preview .slack-code {
|
||||
background: #2d2f33; color: #e01e5a; padding: 1px 4px;
|
||||
border-radius: 3px; font-family: 'JetBrains Mono', monospace; font-size: 12px;
|
||||
border: 1px solid #3c3f44;
|
||||
}
|
||||
.cs-preview .slack-codeblock {
|
||||
background: #2d2f33; border: 1px solid #3c3f44; border-radius: 4px;
|
||||
padding: 8px 12px; margin: 4px 0; font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto;
|
||||
}
|
||||
.cs-preview .slack-blockquote {
|
||||
border-left: 4px solid #5c5e63; padding-left: 12px; margin: 4px 0;
|
||||
color: var(--cs-text2);
|
||||
}
|
||||
.cs-preview .slack-link { color: #1d9bd1; text-decoration: none; }
|
||||
.cs-preview .slack-link:hover { text-decoration: underline; }
|
||||
.cs-preview .slack-hr { border: none; border-top: 1px solid #3c3f44; margin: 8px 0; }
|
||||
.cs-preview .slack-heading { font-weight: 700; font-size: 15px; }
|
||||
.cs-preview .slack-list-item { padding-left: 16px; }
|
||||
.cs-preview .slack-list-item::before { content: '• '; color: var(--cs-text2); }
|
||||
.cs-preview .slack-numbered-item { padding-left: 16px; }
|
||||
|
||||
.cs-status {
|
||||
display: flex; align-items: center; padding: 6px 16px;
|
||||
background: var(--cs-card); border-top: 1px solid var(--cs-border);
|
||||
font-size: 11px; color: var(--cs-text2); gap: 16px; flex-shrink: 0;
|
||||
}
|
||||
.cs-status .stat { display: flex; align-items: center; gap: 4px; }
|
||||
.cs-toast {
|
||||
position: fixed; top: 16px; right: 16px; padding: 10px 16px;
|
||||
background: var(--cs-green); color: #fff; border-radius: 8px;
|
||||
font-size: 13px; font-weight: 500; z-index: 9999;
|
||||
transform: translateY(-20px); opacity: 0; transition: all .3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cs-toast.show { transform: translateY(0); opacity: 1; }
|
||||
|
||||
.cs-help {
|
||||
padding: 16px; color: var(--cs-text2); font-size: 12px; line-height: 1.8;
|
||||
}
|
||||
.cs-help kbd {
|
||||
background: var(--cs-card2); border: 1px solid var(--cs-border);
|
||||
padding: 1px 5px; border-radius: 3px; font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cs-wrap">
|
||||
<!-- Toolbar -->
|
||||
<div class="cs-toolbar">
|
||||
<h1><i class="fas fa-exchange-alt"></i> 클코 → 슬랙 변환기</h1>
|
||||
<div style="flex:1"></div>
|
||||
<button class="cs-btn" onclick="showHelp()" title="도움말"><i class="fas fa-question-circle"></i></button>
|
||||
<button class="cs-btn danger" onclick="clearAll()"><i class="fas fa-trash-alt"></i> 초기화</button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="cs-body">
|
||||
<!-- 입력 패널 -->
|
||||
<div class="cs-panel">
|
||||
<div class="cs-panel-header">
|
||||
<span class="title"><i class="fas fa-terminal"></i> Claude Code 출력 (붙여넣기)</span>
|
||||
<div class="actions">
|
||||
<button class="cs-btn" onclick="pasteFromClipboard()" title="클립보드에서 붙여넣기"><i class="fas fa-paste"></i> 붙여넣기</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="inputArea" class="cs-editor"
|
||||
placeholder="Claude Code CLI 출력을 여기에 붙여넣으세요... 예시: ## 분석 결과 - 항목 1 - 항목 2 ```bash echo hello ```"
|
||||
oninput="convert()" spellcheck="false"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 변환 결과 (슬랙 mrkdwn) -->
|
||||
<div class="cs-panel">
|
||||
<div class="cs-panel-header">
|
||||
<span class="title"><i class="fab fa-slack"></i> 슬랙 전송용 텍스트</span>
|
||||
<div class="actions">
|
||||
<button class="cs-btn success" onclick="copyResult()" title="변환된 텍스트 복사"><i class="fas fa-copy"></i> 복사</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="outputArea" class="cs-editor" readonly
|
||||
placeholder="변환된 슬랙용 텍스트가 여기에 표시됩니다..."
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 슬랙 미리보기 -->
|
||||
<div class="cs-panel">
|
||||
<div class="cs-panel-header">
|
||||
<span class="title"><i class="fas fa-eye"></i> 슬랙 미리보기</span>
|
||||
<div class="actions">
|
||||
<span style="font-size:11px; color:var(--cs-text2);">미리보기 전용</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="previewArea" class="cs-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="cs-status">
|
||||
<span class="stat"><i class="fas fa-file-alt"></i> 입력: <span id="statInput">0</span>자</span>
|
||||
<span class="stat"><i class="fab fa-slack"></i> 출력: <span id="statOutput">0</span>자</span>
|
||||
<span class="stat"><i class="fas fa-exchange-alt"></i> 변환율: <span id="statRatio">-</span></span>
|
||||
<div style="flex:1"></div>
|
||||
<span style="color:var(--cs-text2)">Ctrl+V로 붙여넣기 → 자동 변환 → 복사 버튼 클릭 → 슬랙에 붙여넣기</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div id="toast" class="cs-toast"></div>
|
||||
|
||||
<!-- Help Modal -->
|
||||
<div id="helpModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:9998; display:none; align-items:center; justify-content:center;">
|
||||
<div style="background:var(--cs-card); border:1px solid var(--cs-border); border-radius:12px; max-width:520px; width:90%; padding:24px;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
|
||||
<h3 style="margin:0; font-size:15px; font-weight:700;"><i class="fas fa-info-circle" style="color:var(--cs-blue)"></i> 사용법</h3>
|
||||
<button class="cs-btn" onclick="closeHelp()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="cs-help">
|
||||
<p><strong>1단계:</strong> Claude Code CLI에서 메시지를 드래그하여 복사 <kbd>Ctrl+C</kbd></p>
|
||||
<p><strong>2단계:</strong> 왼쪽 입력란에 붙여넣기 <kbd>Ctrl+V</kbd></p>
|
||||
<p><strong>3단계:</strong> 자동 변환된 중앙의 텍스트를 <span style="color:var(--cs-green)">복사</span> 버튼으로 복사</p>
|
||||
<p><strong>4단계:</strong> 슬랙 채팅창에 붙여넣기 <kbd>Ctrl+V</kbd></p>
|
||||
<hr style="border-color:var(--cs-border); margin:12px 0;">
|
||||
<p><strong>변환 규칙:</strong></p>
|
||||
<p>• <code>**bold**</code> → <code>*bold*</code> (슬랙 볼드)</p>
|
||||
<p>• <code>## 제목</code> → <code>*제목*</code> (볼드 처리)</p>
|
||||
<p>• <code>` `` `code` `` `</code> → <code>`code`</code> (인라인 코드 유지)</p>
|
||||
<p>• <code>` `` `` `` `bash ... ` `` `` `` `</code> → <code>` `` `` `` ` ... ` `` `` `` `</code> (코드 블록 유지)</p>
|
||||
<p>• <code>---</code> → <code>———</code> (구분선 대체)</p>
|
||||
<p>• 불필요한 공백/빈줄 정리</p>
|
||||
<p>• 줄바꿈 정상화</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
(function() {
|
||||
const inputArea = document.getElementById('inputArea');
|
||||
const outputArea = document.getElementById('outputArea');
|
||||
const previewArea = document.getElementById('previewArea');
|
||||
|
||||
/**
|
||||
* Claude Code CLI 출력 → 슬랙 mrkdwn 변환
|
||||
*/
|
||||
function convertToSlack(text) {
|
||||
if (!text.trim()) return '';
|
||||
|
||||
let lines = text.split('\n');
|
||||
let result = [];
|
||||
let inCodeBlock = false;
|
||||
let codeBlockLang = '';
|
||||
let codeLines = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
|
||||
// 코드블록 시작/끝 감지
|
||||
if (line.trimStart().match(/^```/)) {
|
||||
if (!inCodeBlock) {
|
||||
inCodeBlock = true;
|
||||
codeBlockLang = line.trimStart().replace(/^```/, '').trim();
|
||||
codeLines = [];
|
||||
continue;
|
||||
} else {
|
||||
// 코드블록 종료
|
||||
inCodeBlock = false;
|
||||
result.push('```');
|
||||
result.push(...codeLines);
|
||||
result.push('```');
|
||||
codeLines = [];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
codeLines.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 빈 줄 처리 (연속 빈줄은 1개로)
|
||||
if (line.trim() === '') {
|
||||
if (result.length > 0 && result[result.length - 1] !== '') {
|
||||
result.push('');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 수평선: --- → ———
|
||||
if (line.trim().match(/^[-]{3,}$/)) {
|
||||
result.push('———');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 헤딩: # ## ### → 슬랙 *볼드*
|
||||
let headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
||||
if (headingMatch) {
|
||||
result.push('*' + headingMatch[2].trim() + '*');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 테이블 구분선 건너뛰기 (|---|---|)
|
||||
if (line.trim().match(/^\|[\s\-:|]+\|$/)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 테이블 행: | a | b | → 슬랙 형식
|
||||
if (line.trim().match(/^\|.*\|$/)) {
|
||||
let cells = line.split('|').filter(c => c.trim() !== '');
|
||||
let formatted = cells.map(c => c.trim()).join(' | ');
|
||||
result.push(formatted);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 일반 줄에서 마크다운 인라인 변환
|
||||
line = convertInlineMarkdown(line);
|
||||
|
||||
result.push(line);
|
||||
}
|
||||
|
||||
// 코드블록이 닫히지 않은 경우
|
||||
if (inCodeBlock && codeLines.length > 0) {
|
||||
result.push('```');
|
||||
result.push(...codeLines);
|
||||
result.push('```');
|
||||
}
|
||||
|
||||
// 마지막 빈줄 제거
|
||||
while (result.length > 0 && result[result.length - 1] === '') {
|
||||
result.pop();
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 인라인 마크다운 → 슬랙 mrkdwn 변환
|
||||
*/
|
||||
function convertInlineMarkdown(line) {
|
||||
// 인라인 코드 보호 (변환 대상에서 제외)
|
||||
let codeSegments = [];
|
||||
line = line.replace(/`([^`]+)`/g, function(match, code) {
|
||||
codeSegments.push(code);
|
||||
return '\x00CODE' + (codeSegments.length - 1) + '\x00';
|
||||
});
|
||||
|
||||
// **bold** 또는 __bold__ → *bold* (슬랙)
|
||||
line = line.replace(/\*\*(.+?)\*\*/g, '*$1*');
|
||||
line = line.replace(/__(.+?)__/g, '*$1*');
|
||||
|
||||
// *italic* (단일 *) → _italic_ (슬랙)
|
||||
// 주의: 이미 *bold*로 변환된 것과 구분 필요
|
||||
// 단일 *만 italic로 변환 (앞뒤로 *가 아닌 경우)
|
||||
line = line.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '_$1_');
|
||||
|
||||
// ~~strike~~ → ~strike~ (슬랙)
|
||||
line = line.replace(/~~(.+?)~~/g, '~$1~');
|
||||
|
||||
// [text](url) → <url|text> (슬랙 링크)
|
||||
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>');
|
||||
|
||||
// > blockquote → > blockquote (슬랙도 > 지원)
|
||||
// 이미 동일하므로 변환 불필요
|
||||
|
||||
// 인라인 코드 복원
|
||||
line = line.replace(/\x00CODE(\d+)\x00/g, function(match, idx) {
|
||||
return '`' + codeSegments[parseInt(idx)] + '`';
|
||||
});
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 슬랙 mrkdwn → HTML 미리보기 렌더링
|
||||
*/
|
||||
function renderSlackPreview(text) {
|
||||
if (!text.trim()) return '<div style="color:var(--cs-text2); padding:20px; text-align:center;">변환된 텍스트가 여기에 미리보기로 표시됩니다</div>';
|
||||
|
||||
let lines = text.split('\n');
|
||||
let html = [];
|
||||
let inCodeBlock = false;
|
||||
let codeLines = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
|
||||
if (line.trim() === '```') {
|
||||
if (!inCodeBlock) {
|
||||
inCodeBlock = true;
|
||||
codeLines = [];
|
||||
continue;
|
||||
} else {
|
||||
inCodeBlock = false;
|
||||
html.push('<div class="slack-codeblock">' + escapeHtml(codeLines.join('\n')) + '</div>');
|
||||
codeLines = [];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
codeLines.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.trim() === '') {
|
||||
html.push('<div style="height:8px"></div>');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.trim() === '———') {
|
||||
html.push('<hr class="slack-hr">');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 인라인 렌더링
|
||||
let rendered = renderInlineSlack(escapeHtml(line));
|
||||
html.push('<div>' + rendered + '</div>');
|
||||
}
|
||||
|
||||
if (inCodeBlock && codeLines.length > 0) {
|
||||
html.push('<div class="slack-codeblock">' + escapeHtml(codeLines.join('\n')) + '</div>');
|
||||
}
|
||||
|
||||
return html.join('\n');
|
||||
}
|
||||
|
||||
function renderInlineSlack(line) {
|
||||
// `code` → <span class="slack-code">
|
||||
line = line.replace(/`([^`]+)`/g, '<span class="slack-code">$1</span>');
|
||||
// *bold* → <span class="slack-bold">
|
||||
line = line.replace(/(?<!\w)\*([^*]+)\*(?!\w)/g, '<span class="slack-bold">$1</span>');
|
||||
// _italic_ → <span class="slack-italic">
|
||||
line = line.replace(/(?<!\w)_([^_]+)_(?!\w)/g, '<span class="slack-italic">$1</span>');
|
||||
// ~strike~ → <span class="slack-strike">
|
||||
line = line.replace(/(?<!\w)~([^~]+)~(?!\w)/g, '<span class="slack-strike">$1</span>');
|
||||
// <url|text> → <a>
|
||||
line = line.replace(/<(https?:\/\/[^|]+)\|([^&]+)>/g, '<a class="slack-link" href="#">$2</a>');
|
||||
return line;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* 변환 실행 (입력 → 변환 → 미리보기)
|
||||
*/
|
||||
window.convert = function() {
|
||||
const input = inputArea.value;
|
||||
const output = convertToSlack(input);
|
||||
|
||||
outputArea.value = output;
|
||||
previewArea.innerHTML = renderSlackPreview(output);
|
||||
|
||||
// 상태바 업데이트
|
||||
document.getElementById('statInput').textContent = input.length.toLocaleString();
|
||||
document.getElementById('statOutput').textContent = output.length.toLocaleString();
|
||||
if (input.length > 0) {
|
||||
let ratio = Math.round((output.length / input.length) * 100);
|
||||
document.getElementById('statRatio').textContent = ratio + '%';
|
||||
} else {
|
||||
document.getElementById('statRatio').textContent = '-';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 결과 복사
|
||||
*/
|
||||
window.copyResult = function() {
|
||||
const output = outputArea.value;
|
||||
if (!output.trim()) {
|
||||
showToast('변환할 텍스트가 없습니다', 'warning');
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(output).then(() => {
|
||||
showToast('슬랙용 텍스트가 복사되었습니다!');
|
||||
}).catch(() => {
|
||||
// fallback
|
||||
outputArea.select();
|
||||
document.execCommand('copy');
|
||||
showToast('복사되었습니다!');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 클립보드에서 붙여넣기
|
||||
*/
|
||||
window.pasteFromClipboard = function() {
|
||||
navigator.clipboard.readText().then(text => {
|
||||
inputArea.value = text;
|
||||
convert();
|
||||
showToast('붙여넣기 완료!');
|
||||
}).catch(() => {
|
||||
inputArea.focus();
|
||||
showToast('Ctrl+V로 직접 붙여넣어주세요', 'warning');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 초기화
|
||||
*/
|
||||
window.clearAll = function() {
|
||||
inputArea.value = '';
|
||||
outputArea.value = '';
|
||||
previewArea.innerHTML = '';
|
||||
document.getElementById('statInput').textContent = '0';
|
||||
document.getElementById('statOutput').textContent = '0';
|
||||
document.getElementById('statRatio').textContent = '-';
|
||||
inputArea.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* 도움말
|
||||
*/
|
||||
window.showHelp = function() {
|
||||
document.getElementById('helpModal').style.display = 'flex';
|
||||
};
|
||||
window.closeHelp = function() {
|
||||
document.getElementById('helpModal').style.display = 'none';
|
||||
};
|
||||
document.getElementById('helpModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeHelp();
|
||||
});
|
||||
|
||||
/**
|
||||
* 토스트 메시지
|
||||
*/
|
||||
function showToast(msg, type) {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.textContent = msg;
|
||||
toast.style.background = type === 'warning' ? 'var(--cs-amber)' : 'var(--cs-green)';
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 2000);
|
||||
}
|
||||
|
||||
// 입력란에 붙여넣기 시 자동 변환
|
||||
inputArea.addEventListener('paste', function() {
|
||||
setTimeout(() => convert(), 50);
|
||||
});
|
||||
|
||||
// 키보드 단축키
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === 'Enter') {
|
||||
copyResult();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
closeHelp();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
@@ -16,9 +16,6 @@
|
||||
use App\Http\Controllers\CategorySyncController;
|
||||
use App\Http\Controllers\ChinaTech\BigTechController;
|
||||
use App\Http\Controllers\ChinaTech\ChinaAiController;
|
||||
use App\Http\Controllers\Help\AccountingGuideController;
|
||||
use App\Http\Controllers\Help\AttendanceGuideController;
|
||||
use App\Http\Controllers\Help\BarobillGuideController;
|
||||
use App\Http\Controllers\ClaudeCode\CoworkController as ClaudeCodeCoworkController;
|
||||
use App\Http\Controllers\ClaudeCode\HistoryController as ClaudeCodeHistoryController;
|
||||
use App\Http\Controllers\ClaudeCode\NewsController as ClaudeCodeNewsController;
|
||||
@@ -41,6 +38,9 @@
|
||||
use App\Http\Controllers\GoogleCloud\CloudApiPricingController as GoogleCloudCloudApiPricingController;
|
||||
use App\Http\Controllers\GoogleCloud\WorkspacePolicyController as GoogleCloudWorkspacePolicyController;
|
||||
use App\Http\Controllers\GoogleCloud\WorkspacePricingController as GoogleCloudWorkspacePricingController;
|
||||
use App\Http\Controllers\Help\AccountingGuideController;
|
||||
use App\Http\Controllers\Help\AttendanceGuideController;
|
||||
use App\Http\Controllers\Help\BarobillGuideController;
|
||||
use App\Http\Controllers\ItemFieldController;
|
||||
use App\Http\Controllers\ItemManagementController;
|
||||
use App\Http\Controllers\Juil\ConstructionSitePhotoController;
|
||||
@@ -432,6 +432,9 @@
|
||||
|
||||
// 방화셔터 도면생성
|
||||
Route::get('/fire-shutter-drawing', [RdController::class, 'fireShutterDrawing'])->name('fire-shutter-drawing');
|
||||
|
||||
// 클코 → 슬랙 변환기
|
||||
Route::get('/cc-to-slack', [RdController::class, 'ccToSlack'])->name('cc-to-slack');
|
||||
});
|
||||
|
||||
// 일일 스크럼 (Blade 화면만)
|
||||
|
||||
Reference in New Issue
Block a user