relay UI overhaul, favicon, settings, admin last-seen, fixes
Relay:
- Inbox/shared/sent split into own view-inbox (nav button + poll).
Compose is now single-column, centered.
- Header logo links to compose. Inbox badge stays fresh on all
views via a quiet loadInbox on every view switch.
- Mobile menu: full-height sheet below header, larger buttons
(15px font, 14px padding), full width.
- Login form wrapped in <form> with name attributes + for/id on
labels so password managers can autofill.
- Admin user list shows "last seen <relative time>" per user.
last_seen_at column on tenants, updated on every authenticated
request (session + client API).
- Cache-busting on style.css + app.js via filemtime.
- View persistence in localStorage (survives reload).
- Shared-bucket cards show title only (preview line removed).
Favicon:
- favicon.svg added to project root + copied to public/, relay/,
website/. Linked via <link rel="icon" type="image/svg+xml"> in
all three <head> sections.
Settings:
- New toggle: "Show shopping list items in task collector"
(default off). collectTasks skips #einkauf/#shopping cards when
disabled.
Fixes:
- card-spinner gets display:inline-block so it rotates outside
flex containers (shopping save spinner).
- Tim tab test opens the plugin before checking active state.
- Compose view centered with margin:auto on desktop.
Bumps to 1.9.29.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8e738ed9d4
commit
d50f9fe789
19 files changed
+209
-101
No files matched your search
@@ -395,6 +395,7 @@ body.settings-peeking #settings-overlay > div { pointer-events: auto; }
|
||||
.settings-tab.active { color: var(--t-accent); border-left-color: var(--t-accent); background: color-mix(in srgb, var(--t-accent) 8%, transparent); }
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
@keyframes pulse-glow { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
|
||||
.card-bar-pulse { animation: pulse-glow 2s ease-in-out infinite; }
|
||||
.board-card:has(.card-bar-pulse) { border-color: var(--t-danger); }
|
||||
@@ -402,7 +403,7 @@ body.settings-peeking #settings-overlay > div { pointer-events: auto; }
|
||||
.card-done-badge { position: absolute; bottom: 0; right: 0; width: 0; height: 0; border-style: solid; border-width: 0 0 24px 24px; border-color: transparent transparent #4ade80 transparent; z-index: 1; }
|
||||
.card-done-badge i { position: absolute; bottom: -22px; right: 1px; font-size: 10px; color: #000; }
|
||||
.board-card:has(.card-bar-pulse) .card-bar-pulse ~ * .text-t-danger { animation: pulse-glow 2s ease-in-out infinite; }
|
||||
.card-spinner { animation: spin 0.8s linear infinite; }
|
||||
.card-spinner { animation: spin 0.8s linear infinite; display: inline-block; }
|
||||
|
||||
/* Cards */
|
||||
.card-bar { image-rendering: pixelated; height: var(--ui-bar-height, 3px); }
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="60.122112mm"
|
||||
height="60.122108mm"
|
||||
viewBox="0 0 60.122112 60.122108"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"
|
||||
transform="translate(-8.2147047,-118.8156)"><rect
|
||||
style="fill:#06080f;fill-opacity:1;stroke-width:1.5875"
|
||||
id="rect4"
|
||||
width="60.122112"
|
||||
height="60.122112"
|
||||
x="8.2147045"
|
||||
y="118.81558" /><path
|
||||
id="rect1"
|
||||
style="fill:#15bff6;fill-opacity:1;stroke-width:1.5875"
|
||||
d="m 41.162784,130.18519 v 37.02565 h 20.35844 v -37.02565 z m -25.209835,4.34547 v 23.45335 h 5.000729 v -9.22631 h 8.928654 v 9.22631 h 5.000212 v -23.45335 h -5.000212 v 9.22683 h -8.928654 v -9.22683 z" /></g></svg>
|
||||
|
After Width: | Height: | Size: 926 B |
@@ -1159,6 +1159,7 @@ if (str_starts_with($route, '/api/')) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>hyprcrd</title>
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<link rel="stylesheet" href="css/fonts.css">
|
||||
<link rel="stylesheet" href="css/vendor/phosphor-regular.css">
|
||||
<link rel="stylesheet" href="css/vendor/phosphor-fill.css">
|
||||
@@ -1927,6 +1928,7 @@ if (str_starts_with($route, '/api/')) {
|
||||
<div class="toggle-row" data-toggle="setting-confirm-delete"><span class="toggle-label">Confirm before delete</span><div class="toggle-switch" id="setting-confirm-delete"></div></div>
|
||||
<div class="toggle-row" data-toggle="setting-auto-save"><span class="toggle-label">Auto-save on close (editor)</span><div class="toggle-switch" id="setting-auto-save"></div></div>
|
||||
<div class="toggle-row" data-toggle="setting-open-switches-bucket"><span class="toggle-label">Switch to card's bucket on open (search / link)</span><div class="toggle-switch" id="setting-open-switches-bucket"></div></div>
|
||||
<div class="toggle-row" data-toggle="setting-tasks-show-shopping"><span class="toggle-label">Show shopping list items in task collector</span><div class="toggle-switch" id="setting-tasks-show-shopping"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,13 @@ const summary = $('tasks-summary');
|
||||
const countBadge = $('tasks-open-count');
|
||||
|
||||
function collectTasks() {
|
||||
const showShopping = localStorage.getItem('hyprcrd-tasks-show-shopping') === '1';
|
||||
const all = [];
|
||||
for (const card of state.cards) {
|
||||
if (!showShopping) {
|
||||
const tags = (card.tags || '').split(',').map(s => s.trim());
|
||||
if (tags.includes('einkauf') || tags.includes('shopping')) continue;
|
||||
}
|
||||
const re = /- \[([ xX])\][ \t]*([^\n\r]*)/g;
|
||||
let m, idx = 0;
|
||||
while ((m = re.exec(card.body || '')) !== null) {
|
||||
|
||||
@@ -451,6 +451,7 @@ function initBehaviorSettings() {
|
||||
'setting-confirm-delete': { key: 'hyprcrd-confirm-delete', def: '1' },
|
||||
'setting-auto-save': { key: 'hyprcrd-auto-save', def: '0' },
|
||||
'setting-open-switches-bucket': { key: 'hyprcrd-open-switches-bucket', def: '0' },
|
||||
'setting-tasks-show-shopping': { key: 'hyprcrd-tasks-show-shopping', def: '0' },
|
||||
};
|
||||
for (const [id, cfg] of Object.entries(selects)) {
|
||||
const el = $(id);
|
||||
|
||||
@@ -241,7 +241,7 @@ function openDialog() {
|
||||
const prev = getActiveIds();
|
||||
const next = [...selected];
|
||||
setActiveIds(next);
|
||||
status.textContent = 'saving…';
|
||||
status.innerHTML = '<i class="ph ph-spinner card-spinner" style="font-size:14px;vertical-align:middle;margin-right:4px;"></i> saving — please wait!';
|
||||
try {
|
||||
// Remove deactivated lists from relay.
|
||||
for (const id of prev) {
|
||||
|
||||
@@ -127,7 +127,9 @@ describe('Tim Plugin: Open & Close', () => {
|
||||
describe('Tim Plugin: Tabs', () => {
|
||||
afterEach(async () => { closeTim(); await closeAll(); });
|
||||
|
||||
it('duration tab is active on load', () => {
|
||||
it('duration tab is active on load', async () => {
|
||||
openTim();
|
||||
await sleep(30);
|
||||
const tab = document.querySelector('.tim-tab[data-tim-tab="duration"]');
|
||||
expect(tab.classList.contains('active')).toBe(true);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user