diff --git a/app/modules/cafe24/routes_products.py b/app/modules/cafe24/routes_products.py index 99892be..9dad151 100644 --- a/app/modules/cafe24/routes_products.py +++ b/app/modules/cafe24/routes_products.py @@ -116,6 +116,9 @@ 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, } @@ -214,6 +217,7 @@ def product_apply( base_fingerprint: str = Form(""), memo: str = Form(""), list_query: str = Form(""), + hide_promo: str = Form(""), ): """편집한 HTML 을 카페24에 즉시 적용한다. @@ -238,7 +242,10 @@ def product_apply( # 화면에서 보던 그대로(정리된 소스)를 카페24에 반영한다. 한글 이미지 경로는 # 원래의 퍼센트 인코딩으로 되돌린다. - submitted = store.format_html(store.encode_html_urls(html or "")) + want_hide_promo = bool(hide_promo) + submitted = store.format_html( + store.set_promo_hidden(store.encode_html_urls(html or ""), want_hide_promo) + ) if not submitted.strip(): return RedirectResponse( url=f"{back}&err=내용이 비어 있습니다. 상세페이지를 비우려면 카페24 관리자에서 하세요.", @@ -273,11 +280,19 @@ def product_apply( status_code=303, ) - if submitted == current.description: - return RedirectResponse(url=f"{back}&msg=변경된 내용이 없어 적용하지 않았습니다.", 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 + ): + return RedirectResponse(url=f"{back}&msg=변경된 내용이 없어 적용하지 않았습니다.", status_code=303) try: products.update_descriptions( api.client, product_no, description=submitted, mobile_description=mobile_html @@ -304,7 +319,9 @@ 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 mobile_html is not None else "") + + (", 공통홍보 숨김" if want_hide_promo else "") + + ")", ) logger.info("카페24 상품 %s 상세설명 적용 (%s)", product_no, actor) return RedirectResponse( diff --git a/app/modules/cafe24/store.py b/app/modules/cafe24/store.py index e4fdc80..6ebea05 100644 --- a/app/modules/cafe24/store.py +++ b/app/modules/cafe24/store.py @@ -368,6 +368,47 @@ 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 를 붙여 우리가 넣은 블록임을 표시한다. 사람이 쓴 " +) +_HIDE_PROMO_RE = re.compile( + r"[ \t]*]*\bid\s*=\s*[\"']%s[\"'][^>]*>.*?\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 값 지문. 적용 직전 값과 비교해 충돌을 잡는다. diff --git a/app/modules/cafe24/templates/cafe24/_editor.html b/app/modules/cafe24/templates/cafe24/_editor.html index 5e71d05..91be241 100644 --- a/app/modules/cafe24/templates/cafe24/_editor.html +++ b/app/modules/cafe24/templates/cafe24/_editor.html @@ -55,7 +55,17 @@
- PC 상세설명 HTML · {{ desc.description | length }}자 + + PC 상세설명 HTML · {{ desc.description | length }}자 + + diff --git a/app/modules/cafe24/templates/cafe24/products.html b/app/modules/cafe24/templates/cafe24/products.html index 7eaccbd..03b30b9 100644 --- a/app/modules/cafe24/templates/cafe24/products.html +++ b/app/modules/cafe24/templates/cafe24/products.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} {% block content %} diff --git a/app/modules/cafe24/templates/cafe24/schedules.html b/app/modules/cafe24/templates/cafe24/schedules.html index e62501a..31df825 100644 --- a/app/modules/cafe24/templates/cafe24/schedules.html +++ b/app/modules/cafe24/templates/cafe24/schedules.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} {% block content %} diff --git a/app/modules/cafe24/templates/cafe24/system.html b/app/modules/cafe24/templates/cafe24/system.html index 33a7c4d..a40c35b 100644 --- a/app/modules/cafe24/templates/cafe24/system.html +++ b/app/modules/cafe24/templates/cafe24/system.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} {% block content %} diff --git a/app/modules/cafe24/tests/test_cafe24.py b/app/modules/cafe24/tests/test_cafe24.py index 2fb4a80..ff73bfb 100644 --- a/app/modules/cafe24/tests/test_cafe24.py +++ b/app/modules/cafe24/tests/test_cafe24.py @@ -535,6 +535,45 @@ def test_format_then_encode_roundtrip(): assert saved == raw +# ════════════════════════════════════════════════════════════ +# 상단 공통 홍보 숨기기 (스킨 파일을 못 고치므로 상세설명 CSS 로 처리) +# ════════════════════════════════════════════════════════════ +def test_promo_hidden_add_and_remove(): + body = "
" + 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 = "
x
" + 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("

내용

", 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(): + """사람이 쓴
x
" + 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("

A

") assert a == store.fingerprint("

A

") diff --git a/app/static/cafe24.css b/app/static/cafe24.css index fdf3649..c89fa42 100644 --- a/app/static/cafe24.css +++ b/app/static/cafe24.css @@ -240,6 +240,15 @@ 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); diff --git a/docs/CAFE24_MODULE.md b/docs/CAFE24_MODULE.md index b035724..4edf19f 100644 --- a/docs/CAFE24_MODULE.md +++ b/docs/CAFE24_MODULE.md @@ -196,6 +196,40 @@ cafe24_oauth_tokens 저장 - 닫는 태그가 빠진 HTML 이 흔하므로 들여쓰기 깊이에 상한(12)을 둔다. 어떤 이유로든 실패하면 **원본을 그대로** 돌려준다(정리보다 안 깨지는 게 중요). +### 2-3. 상단 공통 홍보 숨기기 + +스킨(`detail.html`)에는 공통 홍보를 지울 상품번호가 박혀 있다. + +```js +const numbers = [12,31,32, ...]; // .edb-img-tag-w 를 remove() +``` + +**카페24 Admin API 로는 이 파일을 고칠 수 없다.** 확인 결과 스킨/테마는 조회만 +가능하고(`GET /admin/themes`), 스킨 HTML 파일을 읽거나 쓰는 엔드포인트가 없다. +쓸 수 있는 것은 테마 페이지와 스크립트 태그(`/admin/scripttags`)뿐이다. + +그래서 같은 결과를 **상품 상세설명 안의 CSS** 로 낸다. 상세설명은 우리가 쓸 수 있고, +상품별로 켜고 끌 수 있으며, 상태가 그 상품 소스에 그대로 보인다. + +```html + +``` + +- 편집기 상단 **「상단 공통 홍보 숨기기」** 체크박스로 켜고 끈다. 적용할 때 반영된다. +- `id` 로 우리 블록만 찾는다 — 사람이 쓴 `