diff --git a/editor.py b/editor.py index 10a0d53..2d8817a 100644 --- a/editor.py +++ b/editor.py @@ -7,6 +7,7 @@ Why stdlib only: no pip installs, one file to read, easy to see how it works. Routes: GET / -> the editor page (HTML) + GET /strings-format.js -> .strings parse/serialize helpers (pure JS) GET /file -> current Localizable.strings + git dirty banner POST /save -> write the file + plutil lint {body: content} POST /save-build -> write the file, then start a background build job @@ -190,7 +191,8 @@ def start_build_job(content: str) -> str: # HTTP layer # -------------------------------------------------------------------------- -PAGE = (Path(__file__).with_name("index.html")).read_text(encoding="utf-8") +PAGE = (Path(__file__).parent / "index.html").read_text(encoding="utf-8") +FORMAT_JS = (Path(__file__).parent / "strings-format.js").read_text(encoding="utf-8") class Handler(BaseHTTPRequestHandler): @@ -217,6 +219,9 @@ class Handler(BaseHTTPRequestHandler): if path in ("/", "/index.html"): self._send(200, PAGE.encode(), "text/html; charset=utf-8") return + if path == "/strings-format.js": + self._send(200, FORMAT_JS.encode(), "text/javascript; charset=utf-8") + return if path == "/file": if not self._authorized(): self._json(401, {"error": "token required"}) diff --git a/index.html b/index.html index aa5db4b..90e5f56 100644 --- a/index.html +++ b/index.html @@ -20,8 +20,15 @@ padding: 8px 10px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; } #dirty.ok { background: #12331c; border-color: #1f7a3d; color: #7ee2a0; } + .toolbar { margin-bottom: 12px; } + .seg { display: flex; gap: 6px; margin-bottom: 10px; } + .seg-btn { + flex: 1; padding: 9px 0; border-radius: 10px; font-size: 14px; font-weight: 600; + background: #0d0f12; color: #8b93a1; border: 1px solid #2a313a; cursor: pointer; + } + .seg-btn.on { background: #12331c; border-color: #1f7a3d; color: #7ee2a0; } select { - width: 100%; margin-bottom: 12px; padding: 10px 12px; font-size: 15px; + width: 100%; margin-bottom: 10px; padding: 10px 12px; font-size: 15px; background: #0d0f12; color: #e6e8eb; border: 1px solid #2a313a; border-radius: 10px; -webkit-appearance: none; appearance: none; } @@ -30,6 +37,36 @@ background: #0d0f12; color: #d7dce2; border: 1px solid #2a313a; border-radius: 10px; padding: 12px; font: 13px/1.5 ui-monospace, Menlo, monospace; } + #notice { + display: none; background: #3a2b10; border: 1px solid #8a6410; color: #ffd479; + padding: 8px 10px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; + } + /* Form view ------------------------------------------------------------ */ + .form { display: flex; flex-direction: column; } + .section { + font-size: 12px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; + color: #5fd68a; padding: 14px 2px 5px; margin-top: 8px; + border-bottom: 1px solid #1f7a3d33; + } + .comment { color: #5a6472; font-size: 12px; padding: 8px 2px; font-style: italic; } + .entry { + display: grid; grid-template-columns: minmax(0, 42%) minmax(0, 58%); + gap: 10px; align-items: start; padding: 7px 0; border-bottom: 1px solid #161a20; + } + .entry .k { + font: 12.5px/1.45 ui-monospace, Menlo, monospace; color: #9aa4b2; + word-break: break-word; padding-top: 8px; user-select: text; + } + .entry .v input { + width: 100%; background: #0d0f12; color: #e6e8eb; border: 1px solid #2a313a; + border-radius: 8px; padding: 7px 9px; font-size: 14px; + } + .entry .v input:focus { border-color: #1f7a3d; outline: none; } + @media (max-width: 640px) { + /* Mobile: key on top, value below */ + .entry { grid-template-columns: 1fr; gap: 3px; } + .entry .k { padding-top: 0; font-size: 11.5px; } + } .row { display: flex; gap: 10px; margin: 12px 0; flex-wrap: wrap; } button { flex: 1; min-width: 140px; padding: 14px 16px; border: 0; border-radius: 12px; @@ -56,10 +93,18 @@