55d1c1d6f3
- 안내 문구를 "주석토글[Ctrl+/] · 줄 복사[Alt+Shift+↑↓] · 줄 이동[Alt+↑↓] · 줄 삭제[Shift+Del]" 로 맞춘다(줄 복사가 빠져 있었다). - textarea 의 title 툴팁을 뺀다. 편집 중 마우스 옆에 떠서 소스를 가렸다. 같은 내용이 위 안내줄에 항상 보이므로 잃는 정보가 없다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
212 lines
12 KiB
HTML
212 lines
12 KiB
HTML
{# 오른쪽 편집기 조각.
|
|
전체 페이지(products.html)가 include 하고, JS 가 /products/{no}/pane 으로
|
|
같은 조각만 다시 받아 끼워 넣는다. 그래서 여기에는 <script> 를 두지 않는다
|
|
(innerHTML 로 삽입된 script 는 실행되지 않는다 — JS 는 products.html 에 있고
|
|
삽입 후 cf24BindEditor() 로 다시 연결한다). #}
|
|
|
|
{% if editor_error %}
|
|
<div class="cf24-flash cf24-flash-err">
|
|
카페24 조회에 실패했습니다: {{ editor_error }}<br />
|
|
<a href="/cafe24/system">시스템 화면에서 연결 상태를 확인하세요.</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="cf24-editor-head">
|
|
{# 제목 = 상품명. 연필 버튼을 누르면 입력칸으로 바뀐다(평소에는 읽기 전용 —
|
|
클릭만으로 실수로 고쳐지지 않게). 저장은 JS 가 POST /products/{no}/name.
|
|
카페24 조회에 실패했을 때(desc 없음)는 현재 이름을 믿을 수 없어 버튼을 뺀다. #}
|
|
<div class="cf24-name-box" id="cf24-name" data-product-no="{{ product_no }}">
|
|
<h3 class="cf24-editor-title" id="cf24-name-view">
|
|
<span id="cf24-name-text">{{ info.product_name or '상품' }}</span>
|
|
{% if desc %}
|
|
<button type="button" class="cf24-name-edit" id="cf24-name-edit"
|
|
title="상품명 수정 (카페24에 즉시 반영)" aria-label="상품명 수정">✎</button>
|
|
{% endif %}
|
|
</h3>
|
|
{% if desc %}
|
|
<div class="cf24-name-form is-hidden" id="cf24-name-form">
|
|
<input class="cf24-name-input" id="cf24-name-input" type="text" maxlength="250"
|
|
value="{{ info.product_name }}" aria-label="상품명" />
|
|
<button class="erp-btn erp-btn-primary" type="button" id="cf24-name-save">저장</button>
|
|
<button class="erp-btn erp-btn-outline" type="button" id="cf24-name-cancel">취소</button>
|
|
</div>
|
|
{% endif %}
|
|
<p class="cf24-editor-sub">
|
|
상품번호 {{ product_no }}
|
|
{% if info.product_code %}· <code>{{ info.product_code }}</code>{% endif %}
|
|
{% if info.price %}· {{ info.price }}{% endif %}
|
|
{% if info.updated_date %}· 최근 수정 {{ info.updated_date }}{% endif %}
|
|
</p>
|
|
</div>
|
|
{# 배지 클릭 = 진열/판매 토글. 카페24 조회에 실패했을 때(desc 없음)는 현재 상태를
|
|
믿을 수 없으므로 누를 수 없는 표시로만 둔다. 실제 전환은 products.html 의
|
|
JS 가 POST /products/{no}/status 로 처리하고 응답값으로 다시 그린다. #}
|
|
<div class="cf24-editor-badges" id="cf24-status" data-product-no="{{ product_no }}">
|
|
{% if desc %}
|
|
<button type="button" class="erp-badge cf24-badge-btn {{ 'cf24-badge-ok' if info.display else 'cf24-badge-off' }}"
|
|
data-status-field="display" data-status-on="{{ 1 if info.display else 0 }}"
|
|
title="클릭하면 진열 상태를 바꿉니다 (카페24에 즉시 반영)">{{ '진열' if info.display else '미진열' }}</button>
|
|
<button type="button" class="erp-badge cf24-badge-btn {{ 'cf24-badge-ok' if info.selling else 'cf24-badge-off' }}"
|
|
data-status-field="selling" data-status-on="{{ 1 if info.selling else 0 }}"
|
|
title="클릭하면 판매 상태를 바꿉니다 (카페24에 즉시 반영)">{{ '판매' if info.selling else '중지' }}</button>
|
|
{% else %}
|
|
{% if info.display %}<span class="erp-badge cf24-badge-ok">진열</span>
|
|
{% else %}<span class="erp-badge cf24-badge-off">미진열</span>{% endif %}
|
|
{% if info.selling %}<span class="erp-badge cf24-badge-ok">판매</span>
|
|
{% else %}<span class="erp-badge cf24-badge-off">중지</span>{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{# 고객이 보는 상세페이지 주소. readonly input 이라 기존 복사 버튼(data-copy)이
|
|
그대로 동작한다(값을 box.value 로 읽는다). #}
|
|
{% if product_url %}
|
|
<div class="cf24-url-row">
|
|
<input class="cf24-url" id="cf24-url" type="text" readonly value="{{ product_url }}"
|
|
onclick="this.select();" aria-label="상품 상세페이지 주소" />
|
|
<button class="erp-btn erp-btn-outline" type="button" data-copy="cf24-url">주소 복사</button>
|
|
<a class="erp-btn erp-btn-outline" href="{{ product_url }}"
|
|
target="_blank" rel="noopener noreferrer">쇼핑몰에서 열기</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if desc %}
|
|
<form class="cf24-editor-form" method="post"
|
|
action="/cafe24/products/{{ product_no }}/apply"
|
|
data-confirm="카페24 쇼핑몰에 바로 반영됩니다. 적용할까요? 직전 내용은 자동으로 백업되어 되돌릴 수 있습니다.">
|
|
<input type="hidden" name="base_fingerprint" value="{{ fingerprint }}" />
|
|
<input type="hidden" name="list_query" value="{{ list_query }}" />
|
|
|
|
<div class="cf24-editor-bar">
|
|
<span class="cf24-shortcuts">단축키: 주석토글[Ctrl+/] · 줄 복사[Alt+Shift+↑↓] · 줄 이동[Alt+↑↓] · 줄 삭제[Shift+Del]</span>
|
|
<span class="cf24-editor-bar-right">
|
|
<input class="cf24-memo" type="text" name="memo" maxlength="200"
|
|
placeholder="변경 메모 (버전 이력에 남습니다)" />
|
|
<button class="erp-btn erp-btn-outline" type="button" data-copy="cf24-html-pc">복사</button>
|
|
<button class="erp-btn erp-btn-outline" type="button"
|
|
data-reload="{{ product_no }}"
|
|
title="카페24에서 현재 소스를 다시 읽어옵니다. 카페24 관리자에서 방금 고쳤다면 이걸 누르세요.">다시 읽기</button>
|
|
<button class="erp-btn erp-btn-primary" type="submit">카페24에 적용</button>
|
|
</span>
|
|
</div>
|
|
|
|
{# 색칠된 <pre> 위에 투명한 <textarea> 를 겹쳐 문법 강조를 만든다.
|
|
<pre> 가 크기를 정하고 <textarea> 는 inset:0 으로 그 위를 정확히 덮는다.
|
|
줄바꿈을 하지 않고(wrap=off) 가로로 스크롤하므로 줄 번호가 항상 맞는다.
|
|
두 요소의 폰트·여백이 다르면 글자가 어긋난다 — CSS 에서 함께 관리한다. #}
|
|
<div class="cf24-code" id="cf24-code-pc">
|
|
<div class="cf24-gutter" aria-hidden="true"><div class="cf24-gutter-inner" id="cf24-gutter-pc"></div></div>
|
|
<div class="cf24-code-body">
|
|
<pre class="cf24-code-hl" id="cf24-hl-pc" aria-hidden="true"></pre>
|
|
{# title 툴팁은 달지 않는다 — 편집 중 마우스 옆에 뜨면 소스를 가린다.
|
|
단축키 안내는 위 바(cf24-shortcuts)에 항상 보인다. #}
|
|
<textarea id="cf24-html-pc" class="cf24-code-input" name="html" wrap="off"
|
|
spellcheck="false" autocapitalize="off" autocorrect="off">{{ html_pc }}</textarea>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
{# 예약 — 적용 폼과 형제로 둔다(폼 중첩은 불가). 위 편집기 내용을 JS 가 hidden 에
|
|
복사해 함께 보낸다. 등록 시점 내용이 DRAFT 버전으로 고정되므로, 이후 편집기를
|
|
더 고쳐도 예약된 내용은 바뀌지 않는다. #}
|
|
<details class="cf24-details">
|
|
<summary>예약 적용 — 지정한 시각에 자동 반영</summary>
|
|
<form class="cf24-schedule-form" id="cf24-schedule-form" method="post"
|
|
action="/cafe24/schedules"
|
|
data-confirm="지정한 시각에 자동으로 반영됩니다. 예약을 등록할까요?">
|
|
<input type="hidden" name="product_no" value="{{ product_no }}" />
|
|
<input type="hidden" name="list_query" value="{{ list_query }}" />
|
|
<input type="hidden" name="html" id="cf24-schedule-html" />
|
|
|
|
{# datetime-local 은 로캘·브라우저마다 표시 폭이 달라 잘리는 사고가 있었다(실제
|
|
발생). 날짜/시간을 별도 input 으로 나누면 각각 폭이 고정이라 잘릴 일이 없다.
|
|
제출 직전 JS 가 두 값을 합쳐 hidden scheduled_at 에 넣는다 — 서버(store.
|
|
parse_schedule_at)는 예전과 같은 'YYYY-MM-DDTHH:MM' 형식을 그대로 받으므로
|
|
백엔드는 변경하지 않았다. #}
|
|
<input type="hidden" name="scheduled_at" id="cf24-schedule-at" />
|
|
<div class="cf24-schedule-grid">
|
|
<label class="cf24-schedule-datetime">
|
|
<span>예약 시각</span>
|
|
{# 네이티브 date/time input 은 브라우저가 표시 형식을 강제한다(CSS로 못 바꿈).
|
|
그래서 코드 편집기와 같은 방식을 쓴다 — 투명한 네이티브 input 을 우리가
|
|
그린 형식화된 텍스트 위에 겹친다. 클릭·키보드·달력 팝업은 네이티브 그대로
|
|
동작하고, 보이는 글자만 "2026년 08월 20일"/"오후 07시 30분" 형식이다.
|
|
value 는 그대로 YYYY-MM-DD / HH:MM 이라 제출 시 합치는 로직은 그대로다. #}
|
|
<span class="cf24-schedule-datetime-row">
|
|
<span class="cf24-dt-field cf24-dt-field-date">
|
|
<span class="cf24-dt-display" id="cf24-schedule-date-display" aria-hidden="true">연도.월.일</span>
|
|
<input class="cf24-dt-native" type="date" id="cf24-schedule-date"
|
|
aria-label="예약 날짜" required />
|
|
</span>
|
|
<span class="cf24-dt-field cf24-dt-field-time">
|
|
<span class="cf24-dt-display" id="cf24-schedule-time-display" aria-hidden="true">시:분</span>
|
|
<input class="cf24-dt-native" type="time" id="cf24-schedule-time"
|
|
aria-label="예약 시간" required />
|
|
</span>
|
|
</span>
|
|
</label>
|
|
<label>
|
|
<span>진열</span>
|
|
<select class="cf24-schedule-input" name="set_display">
|
|
<option value="">변경 없음</option>
|
|
<option value="on">진열</option>
|
|
<option value="off">미진열</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>판매</span>
|
|
<select class="cf24-schedule-input" name="set_selling">
|
|
<option value="">변경 없음</option>
|
|
<option value="on">판매</option>
|
|
<option value="off">중지</option>
|
|
</select>
|
|
</label>
|
|
<label class="cf24-schedule-wide">
|
|
<span>메모</span>
|
|
<input class="cf24-schedule-input" type="text" name="memo" maxlength="200"
|
|
placeholder="예: 8월 프로모션 시작" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="cf24-toolbar" style="margin-top:8px;">
|
|
<label class="cf24-check-inline" style="margin-left:0;">
|
|
<input type="checkbox" name="apply_html" value="1" checked />
|
|
위 편집기 내용을 그 시각에 적용
|
|
</label>
|
|
<button class="erp-btn erp-btn-primary" type="submit">예약 등록</button>
|
|
<a class="erp-btn erp-btn-outline" href="/cafe24/schedules">예약 목록</a>
|
|
</div>
|
|
<p class="cf24-muted" style="margin:8px 0 0;">
|
|
진열·판매만 바꾸려면 위 체크를 해제하세요. 셋 중 하나 이상은 선택해야 합니다.
|
|
</p>
|
|
</form>
|
|
</details>
|
|
|
|
<details class="cf24-details">
|
|
<summary>버전 이력 {% if revisions %}({{ revisions | length }}건){% endif %}</summary>
|
|
{% if revisions %}
|
|
<div class="cf24-scroll">
|
|
<table class="erp-table cf24-compact">
|
|
<thead>
|
|
<tr><th>시각</th><th>유형</th><th>길이</th><th>작업자</th><th>메모</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for rev in revisions %}
|
|
<tr>
|
|
<td class="cf24-nowrap">{{ rev.created_at }}</td>
|
|
<td class="cf24-nowrap"><code>{{ rev.revision_type }}</code></td>
|
|
<td class="cf24-nowrap">{{ rev.html_length }}자</td>
|
|
<td class="cf24-nowrap">{{ rev.created_by or '—' }}</td>
|
|
<td>{{ rev.memo or '' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="cf24-muted">버전 선택 복원은 Phase 6 에서 붙습니다. 내용은 모두 보관됩니다.</p>
|
|
{% else %}
|
|
<p class="cf24-muted">아직 이 상품의 변경 이력이 없습니다.</p>
|
|
{% endif %}
|
|
</details>
|
|
{% endif %}
|