diff --git a/app/modules/cafe24/routes_products.py b/app/modules/cafe24/routes_products.py index 454ebf9..99892be 100644 --- a/app/modules/cafe24/routes_products.py +++ b/app/modules/cafe24/routes_products.py @@ -107,10 +107,13 @@ def _editor_ctx(st: Any, product_no: int) -> dict[str, Any]: "summary_description": product.get("summary_description") or "", }, "desc": desc, - # 편집기에는 이미지 경로의 %EC%9A%A9… 을 한글로 풀어서 보여준다. - # 저장할 때 다시 인코딩하므로 카페24에 저장되는 값은 그대로다. - "html_pc": store.decode_html_urls(desc.description) if desc else "", - "html_mobile": store.decode_html_urls(desc.mobile_description) if desc else "", + # 편집기에는 (1) 이미지 경로의 %EC%9A%A9… 을 한글로 풀고 + # (2) 태그마다 줄을 나눠 정리해서 보여준다. + # 저장할 때 같은 정리를 거친 값을 카페24에 쓴다(화면과 저장값이 같다). + "html_pc": store.format_html(store.decode_html_urls(desc.description)) if desc else "", + "html_mobile": ( + store.format_html(store.decode_html_urls(desc.mobile_description)) if desc else "" + ), # 지문은 **인코딩된 원본**으로 만든다(적용 직전 카페24 값과 비교하므로). "fingerprint": store.fingerprint(desc.description) if desc else "", "revisions": st.list_revisions(product_no, limit=20), @@ -233,7 +236,9 @@ def product_apply( base = f"/cafe24/?{list_query}" if list_query else f"/cafe24/?selected={product_no}" back = base if f"selected={product_no}" in base else f"{base}&selected={product_no}" - submitted = store.encode_html_urls(html or "") + # 화면에서 보던 그대로(정리된 소스)를 카페24에 반영한다. 한글 이미지 경로는 + # 원래의 퍼센트 인코딩으로 되돌린다. + submitted = store.format_html(store.encode_html_urls(html or "")) if not submitted.strip(): return RedirectResponse( url=f"{back}&err=내용이 비어 있습니다. 상세페이지를 비우려면 카페24 관리자에서 하세요.", diff --git a/app/modules/cafe24/store.py b/app/modules/cafe24/store.py index 93e7099..a12eeec 100644 --- a/app/modules/cafe24/store.py +++ b/app/modules/cafe24/store.py @@ -181,6 +181,174 @@ def encode_html_urls(html: str) -> str: return _map_urls(html, encode_url_value) +# ════════════════════════════════════════════════════════════ +# 소스 정리(포맷) — 태그마다 줄을 나누고 들여쓴다. +# +# ⚠️ 렌더링을 바꾸지 않는 것이 최우선이다. HTML 에서 공백은 의미가 있어서, +# 인라인 요소 사이에 줄바꿈을 넣으면 화면에 공백이 생긴다(이미지 사이가 +# 벌어지는 고전적인 사고). 그래서 **블록 요소 경계에서만** 줄을 나눈다. +# img·br·span·a 같은 인라인 요소와 텍스트는 원래 줄에 그대로 둔다. +# ' + '
안녕하세요 여름 특가
' + '
'
+ "| 1 | 2 |
") for line in lines)
+
+
+def test_format_keeps_inline_elements_together():
+ """이미지 사이에 줄바꿈이 들어가면 화면에 공백이 생긴다 — 붙여둬야 한다."""
+ out = store.format_html(_MESSY)
+ assert '![]() ' in out
+ assert "안녕하세요 여름 특가 " in out + + +def test_format_preserves_style_content_verbatim(): + out = store.format_html(_MESSY) + assert "\t/* 주석 */" in out + assert "\t.v{max-width:100%}" in out + + +def test_format_is_idempotent(): + """편집하지 않고 다시 적용해도 저장값이 계속 바뀌면 안 된다.""" + once = store.format_html(_MESSY) + assert store.format_html(once) == once + assert store.format_html(store.format_html(once)) == once + + +def test_format_collapses_short_blocks(): + assert store.format_html(" | 1 | ") == "1 | " + # 길면 나눈다. + long_text = "가" * 200 + assert "\n" in store.format_html("%s | " % long_text) + + +def test_format_survives_broken_html(): + """닫는 태그 누락·꺾쇠 조각이 있어도 예외 없이 뭔가를 돌려준다.""" + for bad in ("