revert(cafe24): 상단 공통 홍보 숨기기 기능 제거
사용자 결정에 따라 없던 기능으로 되돌린다. 카페24 API 로 스킨 detail.html 의
상품번호 목록을 고칠 수 없다는 것이 확인된 뒤, 대안(상세설명 CSS 주입 / 스크립트
태그)을 모두 채택하지 않기로 정리했다.
제거 대상:
store.py HIDE_PROMO_* 상수, has_hidden_promo, set_promo_hidden
routes_products 편집기 컨텍스트의 promo 상태, hide_promo 폼 필드,
적용 시 블록 주입, 분리 상품 모바일 블록 동기화, 감사로그 표기
_editor.html 「상단 공통 홍보 숨기기」 체크박스
cafe24.css .cf24-check-inline
tests promo 관련 4건
docs 2-3 절
적용 시 "변경 없음" 판정에 모바일 비교를 포함시킨 것은 남겼다. 공통 홍보와 무관하게
맞는 동작이다(PC 는 그대로여도 모바일이 PC 와 다르면 맞춰줘야 한다).
문서 정리도 함께 했다. 앞선 편집에서 3-1·2-2 절이 중복 삽입되고 절 순서가
뒤섞여 있던 것을 2 → 2-1 → 2-2 → 3 → 3-1 → 3-2 로 바로잡았다. 편집기 절에는
아직 이전 방식(크기 맞추기·sticky 줄번호)이 적혀 있었는데 실제 구현인 스크롤
동기화로 갱신하고, 크기 계산이 두 번 실패한 이유를 근거 수치와 함께 남겼다.
⚠️ 이미 이 기능으로 적용한 상품이 있다면 그 상품 상세설명에
<style id="cf24-hide-common-promo"> 블록이 남아 있다. 편집기에서 그 줄을 지우고
적용하면 된다.
검증: 유닛테스트 45개 통과, 라우트 10개 등록, 템플릿 컴파일 확인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,8 @@ ORDER_SCOPES: tuple[str, ...] = ("mall.read_order", "mall.write_order")
|
||||
# GET /admin/themes/pages → No API found. (버전 2026-03-01 에 존재하지 않음)
|
||||
# 스킨 HTML 파일(product/detail.html 등)을 읽거나 쓰는 엔드포인트는 없다.
|
||||
#
|
||||
# 즉 스킨의 `const numbers = [...]`(상단 공통 홍보 제외 목록)을 API 로 고치는 방법은
|
||||
# 없다. 상단 공통 홍보 숨김은 상품 상세설명 안 CSS 로 처리한다
|
||||
# (app/modules/cafe24/store.py 의 set_promo_hidden).
|
||||
# 즉 스킨에 박힌 값(예: 상단 공통 홍보를 제외할 상품번호 목록)을 API 로 고치는
|
||||
# 방법은 없다. 스킨은 카페24 관리자에서 직접 관리해야 한다.
|
||||
#
|
||||
# 페이지에 코드를 주입하는 유일한 수단은 스크립트 태그(/admin/scripttags)이며,
|
||||
# 디자인이 아니라 **mall.write_store(상점)** 권한이 필요하고 인라인 코드가 아닌
|
||||
|
||||
@@ -116,9 +116,6 @@ def _editor_ctx(st: Any, product_no: int) -> dict[str, Any]:
|
||||
),
|
||||
# 지문은 **인코딩된 원본**으로 만든다(적용 직전 카페24 값과 비교하므로).
|
||||
"fingerprint": store.fingerprint(desc.description) if desc else "",
|
||||
# 상단 공통 홍보 숨김 상태(상세설명 안 CSS 블록으로 판단)
|
||||
"promo_hidden": store.has_hidden_promo(desc.description) if desc else False,
|
||||
"promo_hidden_mobile": store.has_hidden_promo(desc.mobile_description) if desc else False,
|
||||
"revisions": st.list_revisions(product_no, limit=20),
|
||||
"editor_error": error,
|
||||
}
|
||||
@@ -217,7 +214,6 @@ def product_apply(
|
||||
base_fingerprint: str = Form(""),
|
||||
memo: str = Form(""),
|
||||
list_query: str = Form(""),
|
||||
hide_promo: str = Form(""),
|
||||
):
|
||||
"""편집한 HTML 을 카페24에 즉시 적용한다.
|
||||
|
||||
@@ -242,10 +238,7 @@ def product_apply(
|
||||
|
||||
# 화면에서 보던 그대로(정리된 소스)를 카페24에 반영한다. 한글 이미지 경로는
|
||||
# 원래의 퍼센트 인코딩으로 되돌린다.
|
||||
want_hide_promo = bool(hide_promo)
|
||||
submitted = store.format_html(
|
||||
store.set_promo_hidden(store.encode_html_urls(html or ""), want_hide_promo)
|
||||
)
|
||||
submitted = store.format_html(store.encode_html_urls(html or ""))
|
||||
if not submitted.strip():
|
||||
return RedirectResponse(
|
||||
url=f"{back}&err=내용이 비어 있습니다. 상세페이지를 비우려면 카페24 관리자에서 하세요.",
|
||||
@@ -280,14 +273,9 @@ def product_apply(
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
if current.separated_mobile:
|
||||
# 분리 상품은 모바일 본문을 건드리지 않는다. 다만 공통 홍보 숨김은 PC/모바일
|
||||
# 양쪽에 걸어야 효과가 있으므로 그 블록만 모바일에도 맞춰준다.
|
||||
mobile_target = store.set_promo_hidden(current.mobile_description, want_hide_promo)
|
||||
mobile_html = mobile_target if mobile_target != current.mobile_description else None
|
||||
else:
|
||||
# 미분리 상품은 모바일도 함께 맞춘다(PC 만 바꾸면 모바일이 어긋난다).
|
||||
mobile_html = submitted
|
||||
# 미분리 상품은 모바일도 함께 맞춘다(PC 만 바꾸면 모바일이 어긋난다).
|
||||
# 분리 상품은 모바일을 건드리지 않는다(화면에 별도 반영 안내를 띄운다).
|
||||
mobile_html = None if current.separated_mobile else submitted
|
||||
|
||||
if submitted == current.description and (
|
||||
mobile_html is None or mobile_html == current.mobile_description
|
||||
@@ -319,9 +307,7 @@ def product_apply(
|
||||
actor=actor, action="apply_description", product_no=product_no,
|
||||
revision_id=revision_id, result="SUCCESS",
|
||||
detail=f"{len(submitted)}자 적용 (백업 {backup_id}"
|
||||
+ (", 모바일 동시 반영" if mobile_html is not None else "")
|
||||
+ (", 공통홍보 숨김" if want_hide_promo else "")
|
||||
+ ")",
|
||||
+ (", 모바일 동시 반영)" if mobile_html is not None else ")"),
|
||||
)
|
||||
logger.info("카페24 상품 %s 상세설명 적용 (%s)", product_no, actor)
|
||||
return RedirectResponse(
|
||||
|
||||
@@ -368,47 +368,6 @@ def _collapse_short_blocks(lines: list[str]) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 상단 공통 홍보 숨기기
|
||||
#
|
||||
# 스킨(detail.html)에는 공통 홍보를 지울 상품번호 목록이 박혀 있다.
|
||||
# const numbers = [12,31,32, ...]; // .edb-img-tag-w 를 remove()
|
||||
# 그런데 카페24 Admin API 는 **스킨 파일을 읽거나 쓸 수 없다**(테마는 조회만).
|
||||
# 그래서 같은 결과를 상품 상세설명 안의 CSS 로 낸다 — 상세설명은 우리가 쓸 수 있고,
|
||||
# 상품별로 켜고 끌 수 있으며, 상태가 그 상품 소스에 그대로 보인다.
|
||||
#
|
||||
# id 를 붙여 우리가 넣은 블록임을 표시한다. 사람이 쓴 <style> 은 건드리지 않는다.
|
||||
# ════════════════════════════════════════════════════════════
|
||||
HIDE_PROMO_ID = "cf24-hide-common-promo"
|
||||
HIDE_PROMO_BLOCK = (
|
||||
f'<style id="{HIDE_PROMO_ID}">/* DBX ERP: 상단 공통 홍보 숨김 */\n'
|
||||
".edb-img-tag-w{display:none !important}\n"
|
||||
"</style>"
|
||||
)
|
||||
_HIDE_PROMO_RE = re.compile(
|
||||
r"[ \t]*<style[^>]*\bid\s*=\s*[\"']%s[\"'][^>]*>.*?</style>\s*" % re.escape(HIDE_PROMO_ID),
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
def has_hidden_promo(html: str) -> bool:
|
||||
"""이 상품의 상세설명에 공통 홍보 숨김 블록이 들어 있는가."""
|
||||
return bool(_HIDE_PROMO_RE.search(html or ""))
|
||||
|
||||
|
||||
def set_promo_hidden(html: str, hidden: bool) -> str:
|
||||
"""숨김 블록을 넣거나 뺀다. 여러 번 호출해도 결과가 같다(멱등).
|
||||
|
||||
넣을 때는 맨 앞에 둔다 — 찾기 쉽고, 상세설명 어디에 있어도 CSS 효과는 같다.
|
||||
"""
|
||||
stripped = _HIDE_PROMO_RE.sub("", html or "")
|
||||
if not hidden:
|
||||
return stripped
|
||||
if not stripped.strip():
|
||||
return HIDE_PROMO_BLOCK
|
||||
return HIDE_PROMO_BLOCK + "\n" + stripped.lstrip("\n")
|
||||
|
||||
|
||||
def fingerprint(html: str) -> str:
|
||||
"""편집 시작 시점의 카페24 값 지문. 적용 직전 값과 비교해 충돌을 잡는다.
|
||||
|
||||
|
||||
@@ -55,17 +55,7 @@
|
||||
<input type="hidden" name="list_query" value="{{ list_query }}" />
|
||||
|
||||
<div class="cf24-editor-bar">
|
||||
<span class="cf24-muted">
|
||||
PC 상세설명 HTML · {{ desc.description | length }}자
|
||||
<label class="cf24-check-inline" title="상세페이지 상단의 공통 홍보 영역(.edb-img-tag-w)을 이 상품에서만 숨깁니다. PC·모바일 모두 적용됩니다.">
|
||||
<input type="checkbox" name="hide_promo" value="1"
|
||||
{% if promo_hidden %}checked{% endif %} />
|
||||
상단 공통 홍보 숨기기
|
||||
{% if promo_hidden != promo_hidden_mobile %}
|
||||
<span class="cf24-warn">(PC/모바일 상태 불일치 — 적용하면 맞춰집니다)</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
</span>
|
||||
<span class="cf24-muted">PC 상세설명 HTML · {{ desc.description | length }}자</span>
|
||||
<span class="cf24-editor-bar-right">
|
||||
<input class="cf24-memo" type="text" name="memo" maxlength="200"
|
||||
placeholder="변경 메모 (버전 이력에 남습니다)" />
|
||||
|
||||
@@ -539,45 +539,6 @@ def test_format_then_encode_roundtrip():
|
||||
assert saved == raw
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 상단 공통 홍보 숨기기 (스킨 파일을 못 고치므로 상세설명 CSS 로 처리)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
def test_promo_hidden_add_and_remove():
|
||||
body = "<div><img src=\"a.gif\"></div>"
|
||||
on = store.set_promo_hidden(body, True)
|
||||
assert store.has_hidden_promo(on) is True
|
||||
assert ".edb-img-tag-w{display:none !important}" in on
|
||||
assert body in on # 원래 내용은 그대로 남는다
|
||||
|
||||
off = store.set_promo_hidden(on, False)
|
||||
assert store.has_hidden_promo(off) is False
|
||||
assert off.strip() == body
|
||||
|
||||
|
||||
def test_promo_hidden_is_idempotent():
|
||||
body = "<div>x</div>"
|
||||
once = store.set_promo_hidden(body, True)
|
||||
assert store.set_promo_hidden(once, True) == once
|
||||
twice_off = store.set_promo_hidden(store.set_promo_hidden(once, False), False)
|
||||
assert twice_off.strip() == body
|
||||
|
||||
|
||||
def test_promo_hidden_survives_formatting():
|
||||
"""정리(포맷)를 거쳐도 숨김 상태가 유지되고 인식된다."""
|
||||
formatted = store.format_html(store.set_promo_hidden("<div><p>내용</p></div>", True))
|
||||
assert store.has_hidden_promo(formatted) is True
|
||||
assert store.has_hidden_promo(store.format_html(store.set_promo_hidden(formatted, False))) is False
|
||||
|
||||
|
||||
def test_promo_hidden_keeps_other_style_blocks():
|
||||
"""사람이 쓴 <style> 은 건드리지 않는다 — 우리 블록만 id 로 찾는다."""
|
||||
body = "<style>.v{max-width:100%}</style><div>x</div>"
|
||||
on = store.set_promo_hidden(body, True)
|
||||
off = store.set_promo_hidden(on, False)
|
||||
assert ".v{max-width:100%}" in off
|
||||
assert off.strip() == body
|
||||
|
||||
|
||||
def test_fingerprint_detects_change():
|
||||
a = store.fingerprint("<p>A</p>")
|
||||
assert a == store.fingerprint("<p>A</p>")
|
||||
|
||||
@@ -240,15 +240,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cf24-check-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: var(--sp-12, 12px);
|
||||
cursor: pointer;
|
||||
color: var(--color-rich-black, #0a0a0a);
|
||||
}
|
||||
|
||||
.cf24-memo {
|
||||
width: 260px;
|
||||
padding: 6px var(--sp-10, 10px);
|
||||
|
||||
Reference in New Issue
Block a user