Something went wrong. Try again.
Two-way sync Firefox bookmarks with an AT Protocol PDS using network.cosmik.* lexicons.
Something went wrong. Try again.
orbitmarks options.html
8.9 kB · 335 lines
HTML
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336<!doctype html><html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>OrbitMarks — Options</title> <style> :root { --form-padding: 0.35rem; --form-font-size: 0.9rem; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1a1a1a; background: #fafafa; } h1 { font-size: 1.5rem; margin-bottom: 1.5rem; } .avatar { max-height: 2rem; border-radius: 50%; aspect-ratio: 1; } .field { margin: 1rem 0; } label { display: block; font-weight: 600; margin-bottom: 0.25rem; font-size: 0.875rem; } .input-group { display: flex; flex-flow: row nowrap; gap: 0.5rem; align-items: center; } .expand { flex-grow: 1; } input[type="text"], input[type="number"], select { width: 100%; padding: var(--form-padding); border: 1px solid #ccc; border-radius: 4px; font-size: var(--form-font-size); box-sizing: border-box; } input[readonly] { background: #f0f0f0; color: #555; } input[type="checkbox"] { margin-right: 0.5rem; } .checkbox-label { display: flex; align-items: center; font-weight: 400; } button { padding: var(--form-padding) 1rem; border: 1px solid #ccc; border-radius: 4px; background: #fff; cursor: pointer; font-size: var(--form-font-size); } button.primary { background: #0060df; color: #fff; border-color: #0060df; } button.primary:hover { background: #003eaa; } button.primary:disabled { opacity: 0.6; cursor: not-allowed; } button.danger { background: #e22850; color: #fff; border-color: #e22850; } button.danger:hover { background: #b81d3d; } .actions { margin-top: 0.8rem; display: flex; gap: 0.5rem; flex-wrap: wrap; } .actions.right { justify-content: flex-end; } .status { margin: 1rem 0; padding: 0.5rem; border-radius: 4px; display: none; } .status.ok { display: block; background: #d4edda; color: #155724; } .status.err { display: block; background: #f8d7da; color: #721c24; } .status.info { display: block; background: #d1ecf1; color: #0c5460; } hr { margin: 1.5rem 0; border: none; border-top: 1px solid #ddd; } .section-title { font-size: 1.125rem; font-weight: 600; margin-bottom: 1rem; } .meta { font-size: 0.75rem; color: #666; margin-top: 0.25rem; } .field-group { border: 1px solid #ddd; border-radius: 6px; padding: 1rem; margin-bottom: 1rem; } .auth-status { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; } .dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } .dot.ok { background: #30a46c; } .dot.err { background: #e22850; } .dot.off { background: #ccc; } .dot.pending { background: #f5a623; } code { background: #eee; padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; word-break: break-all; } .resolve-result { margin-top: 0.75rem; padding: 0.75rem; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; display: none; } .resolve-result .row { font-size: 0.8125rem; margin-bottom: 0.25rem; word-break: break-all; } .resolve-result .row:last-child { margin-bottom: 0; } </style> </head> <body> <h1>OrbitMarks</h1>
<div id="status" class="status"></div>
<div class="field-group"> <div class="section-title">PDS Connection</div>
<div class="field"> <label for="handle">Handle</label> <div class="input-group"> <img class="avatar" id="avatar" alt="Atmosphere Account logo" src="/icons/atmosphere.svg" /> <input type="text" id="handle" placeholder="user.bsky.social" /> <button id="resolveBtn" class="primary">Resolve</button> </div> <div class="meta"> Your AT Protocol handle (domain). The PDS URL and DID are resolved automatically. </div> </div>
<div id="resolveResult" class="resolve-result"> <div class="row"> <strong>DID:</strong> <span id="resolvedDid"></span> </div> <div class="row"> <strong>PDS URL:</strong> <span id="resolvedPdsUrl"></span> </div> </div>
<div id="permissionPrompt" class="resolve-result" style="display: none; border-color: #f5a623" > <div class="row" id="permissionMessage"></div> <div class="actions"> <button id="grantPermissionBtn" class="primary">Grant Access</button> </div> </div>
<div class="actions input-group"> <div id="authStatusArea" class="expand"> <div class="auth-status" id="authStatus"> <span class="dot off" id="authDot"></span> <span id="authLabel">Not authenticated</span> </div> </div>
<button id="customClientId">Change Client ID</button>
<button id="connectBtn" class="primary">Connect to PDS</button> <button id="disconnectBtn" class="danger" style="display: none"> Disconnect </button> </div>
<div id="clientIdField" class="field" hidden> <label for="clientId">OAuth Client ID</label> <input type="text" id="clientId" placeholder="http://localhost" /> <div class="meta"> The special value <code>http://localhost</code> uses a loopback redirect (<code id="redirectUriPlaceholder">(loading…)</code>) per OAuth 2.0 for Native Apps (RFC 8252). You can also use a hosted <code>client-metadata.json</code> URL if needed. </div> </div> </div>
<div class="field-group"> <div class="section-title">Sync Settings</div>
<div class="field"> <label for="rootStatus">Sync Root Folder</label> <input type="text" id="rootStatus" value="No sync root configured." disabled /> </div>
<div class="actions"> <button id="createRoot">Create New Folder</button> <button id="chooseRoot">Choose Existing Folder</button> <span class="expand"></span> <button id="clearRoot" class="danger">Clear Sync Root</button> </div>
<div class="field"> <label for="syncInterval">Sync Interval (minutes)</label> <select id="syncInterval"> <option value="1">1 minute</option> <option value="5" selected>5 minutes</option> <option value="15">15 minutes</option> <option value="30">30 minutes</option> <option value="0">Manual only</option> </select> </div>
<div class="field"> <label class="checkbox-label"> <input type="checkbox" id="syncOnDelete" /> Sync on delete — remove PDS records when bookmarks are deleted </label> <div class="meta"> When enabled, deleting a synced bookmark also deletes the corresponding card and collection link from the PDS. </div> </div>
<div class="actions right"> <button id="syncNow" class="primary">Sync Now</button> </div> </div>
<div class="field-group"> <div class="section-title">Diagnostics</div> <p class="meta"> Session logs captured from the background script. Export and share them when reporting issues. </p> <div class="actions"> <button id="exportLogsBtn">Export Logs</button> <span class="expand"></span> <button id="clearLogsBtn" class="danger">Clear Logs</button> </div> </div>
<script src="options.js"></script> </body></html>