435cc1aca9
Security:
- Eliminate DOM-based XSS: renderMacros/renderTabs rebuilt with
createElement/textContent/setAttribute + addEventListener. No macro name,
id, image_path, or category ever reaches innerHTML.
- Remove all inline on*= handlers; add a strict Content-Security-Policy meta
(script-src 'self', object-src 'none', base-uri 'none', frame-ancestors
'none').
- Guard JSON.parse on WebSocket frames.
Robustness:
- Service worker: network-first for the app shell + JS/CSS so fixes actually
ship; synthetic 503 for offline API calls; drop no-op /ws intercept; bump
cache version.
- WebSocket reconnect: exponential backoff (1s→30s), tear down old socket
before reconnect, stop on pagehide/beforeunload.
- Tab-switch race fixed with a request-id guard; executing state cleared in
finally.
Accessibility:
- Macro cards and tabs are real buttons with aria-labels/roles; toast +
connection status are aria-live; removed user-scalable=no.
Feature — web-based macro creation/editing:
- Floating "+" button + modal command-sequence builder (text/key/hotkey/
wait/app) with reorder/remove; create via POST, edit via PUT (prefilled
from GET /api/macro/{id}), delete with a named confirm. Works in both local
and relay auth modes. Empty state now offers "Create your first macro".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
4.7 KiB
HTML
99 lines
4.7 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'">
|
||
<meta name="theme-color" content="#007acc">
|
||
<meta name="description" content="Remote macro control for your desktop">
|
||
|
||
<!-- PWA / iOS specific -->
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="apple-mobile-web-app-title" content="MacroPad">
|
||
<meta name="application-name" content="MacroPad">
|
||
<meta name="msapplication-TileColor" content="#007acc">
|
||
<meta name="msapplication-tap-highlight" content="no">
|
||
|
||
<title>MacroPad</title>
|
||
|
||
<link rel="manifest" href="/manifest.json">
|
||
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192.png">
|
||
<link rel="icon" type="image/png" sizes="512x512" href="/static/icons/icon-512.png">
|
||
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
|
||
<link rel="apple-touch-icon" sizes="512x512" href="/static/icons/icon-512.png">
|
||
<link rel="stylesheet" href="/static/css/styles.css">
|
||
</head>
|
||
<body>
|
||
<!-- Header -->
|
||
<header class="header">
|
||
<h1>MacroPad</h1>
|
||
<div class="header-actions">
|
||
<div class="connection-status" id="connection-status" role="status" aria-live="polite">
|
||
<div class="status-dot"></div>
|
||
<span>Disconnected</span>
|
||
</div>
|
||
<button type="button" class="wake-lock-status" id="wake-lock-status" title="Screen wake lock" aria-label="Toggle screen wake lock">
|
||
<span class="wake-icon" aria-hidden="true">☀</span>
|
||
</button>
|
||
<button type="button" class="header-btn icon-btn" id="fullscreen-btn" title="Toggle fullscreen" aria-label="Toggle fullscreen">⛶</button>
|
||
<button type="button" class="header-btn secondary" id="refresh-btn">Refresh</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Tabs -->
|
||
<nav class="tabs" id="tabs-container" role="tablist" aria-label="Macro categories">
|
||
<!-- Tabs rendered dynamically -->
|
||
</nav>
|
||
|
||
<!-- Macro Grid -->
|
||
<main class="macro-grid" id="macro-grid">
|
||
<div class="loading">
|
||
<div class="spinner"></div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Floating action button: create new macro -->
|
||
<button type="button" class="fab" id="new-macro-btn" aria-label="New macro">+</button>
|
||
|
||
<!-- Macro create/edit modal -->
|
||
<div class="modal" id="macro-modal" role="dialog" aria-modal="true" aria-labelledby="macro-modal-title" hidden>
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 id="macro-modal-title">New Macro</h2>
|
||
<button type="button" class="modal-close" id="macro-modal-close" aria-label="Close dialog">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label for="macro-name-input">Name</label>
|
||
<input type="text" id="macro-name-input" autocomplete="off" placeholder="Macro name">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="macro-category-input">Category</label>
|
||
<input type="text" id="macro-category-input" autocomplete="off" list="macro-category-list" placeholder="Category (optional)">
|
||
<datalist id="macro-category-list"></datalist>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Commands</label>
|
||
<div class="command-list" id="command-list"></div>
|
||
<div class="add-command-btns">
|
||
<button type="button" class="add-command-btn" id="add-command-btn">+ Add step</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-danger" id="delete-macro-btn" hidden>Delete</button>
|
||
<button type="button" class="btn btn-secondary" id="cancel-macro-btn">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="save-macro-btn">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast Container -->
|
||
<div class="toast-container" id="toast-container" role="status" aria-live="polite"></div>
|
||
|
||
<script src="/static/js/app.js"></script>
|
||
</body>
|
||
</html>
|