global tooltip system, migrate all title attrs to data-tip/data-help

New tooltip.js module with styled tooltips (data-tip) and multi-line
inline help (data-help). Migrates all ~70 native title attributes
across index.php, JS modules, and all plugins. Adds inline help on
9 strategic UI elements (split, superview, zen, extract, assets, etc).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-12 19:55:17 +02:00
1 parent 527305355c
commit f465306c1d
21 files changed
+229 -109

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
define('APP_NAME', 'hyprcrd'); define('APP_NAME', 'hyprcrd');
define('APP_VERSION', '1.3.0'); define('APP_VERSION', '1.3.1');
// Database path — writable directory, outside webroot // Database path — writable directory, outside webroot
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation // DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hyprcrd", "name": "hyprcrd",
"version": "1.3.0", "version": "1.3.1",
"description": "hyprcrd — self-hosted card manager", "description": "hyprcrd — self-hosted card manager",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
+28
View File
@@ -1,4 +1,32 @@
body { font-family: var(--t-font-content, 'JetBrains Mono', monospace); } body { font-family: var(--t-font-content, 'JetBrains Mono', monospace); }
/* Tooltip system */
#tooltip {
display: none;
position: fixed;
z-index: 9999;
max-width: 280px;
padding: 4px 8px;
font-family: var(--t-font-pixel, 'Silkscreen', cursive);
font-size: 9px;
line-height: 1.5;
color: var(--t-bright);
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
pointer-events: none;
white-space: nowrap;
}
#tooltip.tooltip-help {
white-space: normal;
padding: 8px 12px;
font-size: 10px;
max-width: 320px;
font-family: var(--t-font-content, 'JetBrains Mono', monospace);
color: var(--t-muted);
border-left: 3px solid var(--t-accent);
}
.font-pixel { font-family: var(--t-font-ui, var(--t-font-pixel, 'Silkscreen', cursive)) !important; } .font-pixel { font-family: var(--t-font-ui, var(--t-font-pixel, 'Silkscreen', cursive)) !important; }
.brand { font-family: 'Silkscreen', cursive !important; } .brand { font-family: 'Silkscreen', cursive !important; }
body.no-scanlines::after { display: none !important; } body.no-scanlines::after { display: none !important; }
+36 -36
View File
@@ -1227,7 +1227,7 @@ if (str_starts_with($route, '/api/')) {
<i class="ph ph-magnifying-glass text-sm"></i><span class="font-pixel text-[10px]">Search</span> <i class="ph ph-magnifying-glass text-sm"></i><span class="font-pixel text-[10px]">Search</span>
<kbd class="text-[9px] text-t-dim border border-t-border rounded px-1 py-0.5 ml-1 font-pixel">ctrl+k</kbd> <kbd class="text-[9px] text-t-dim border border-t-border rounded px-1 py-0.5 ml-1 font-pixel">ctrl+k</kbd>
</button> </button>
<button id="header-sync" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="sync now"> <button id="header-sync" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="sync now">
<i class="ph ph-arrows-clockwise text-sm" id="header-sync-icon"></i><span class="font-pixel text-[10px]">Sync</span> <i class="ph ph-arrows-clockwise text-sm" id="header-sync-icon"></i><span class="font-pixel text-[10px]">Sync</span>
</button> </button>
</div> </div>
@@ -1256,19 +1256,19 @@ if (str_starts_with($route, '/api/')) {
</div> </div>
<div id="bucket-row" class="flex items-center px-6 py-1.5 relative"> <div id="bucket-row" class="flex items-center px-6 py-1.5 relative">
<div class="flex items-center gap-1 overflow-x-auto flex-1 relative" id="bucket-bar"></div> <div class="flex items-center gap-1 overflow-x-auto flex-1 relative" id="bucket-bar"></div>
<button id="board-split-toggle" class="toolbar-toggle" title="split preview"> <button id="board-split-toggle" class="toolbar-toggle" data-help="Split preview\nShows a side panel with card preview,\nrelated cards, and backlinks.">
<i class="ph ph-split-horizontal text-sm"></i> <i class="ph ph-split-horizontal text-sm"></i>
</button> </button>
<button id="superview-toggle" class="toolbar-toggle" title="compact list view"> <button id="superview-toggle" class="toolbar-toggle" data-help="Superview\nCompact multi-column list with\nhover cards. Great for overviews.">
<i class="ph ph-list text-sm"></i> <i class="ph ph-list text-sm"></i>
</button> </button>
<button id="hide-done" class="toolbar-toggle" title="toggle done"> <button id="hide-done" class="toolbar-toggle" data-tip="toggle done">
<i class="ph ph-check-circle text-sm"></i> <i class="ph ph-check-circle text-sm"></i>
</button> </button>
<button id="hide-termine" class="toolbar-toggle" title="toggle appointments"> <button id="hide-termine" class="toolbar-toggle" data-tip="toggle appointments">
<i class="ph ph-calendar-x text-sm"></i> <i class="ph ph-calendar-x text-sm"></i>
</button> </button>
<button id="hide-tagged" class="toolbar-toggle" title="toggle tagged cards"> <button id="hide-tagged" class="toolbar-toggle" data-tip="toggle tagged cards">
<i class="ph ph-hash text-sm"></i> <i class="ph ph-hash text-sm"></i>
</button> </button>
<button id="sort-toggle" class="toolbar-toggle"> <button id="sort-toggle" class="toolbar-toggle">
@@ -1650,7 +1650,7 @@ if (str_starts_with($route, '/api/')) {
<div class="design-section-label">presets</div> <div class="design-section-label">presets</div>
<div class="design-row"> <div class="design-row">
<select id="design-preset-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer flex-1"></select> <select id="design-preset-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer flex-1"></select>
<button id="design-preset-delete" class="flex items-center gap-1 px-2 py-1 text-[10px] border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors" title="Delete user preset"><i class="ph ph-trash text-xs"></i></button> <button id="design-preset-delete" class="flex items-center gap-1 px-2 py-1 text-[10px] border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors" data-tip="delete user preset"><i class="ph ph-trash text-xs"></i></button>
</div> </div>
<div class="design-row"> <div class="design-row">
<input id="design-preset-name" type="text" placeholder="new preset name..." class="bg-t-bg border border-t-border text-t-bright text-xs font-mono px-2 py-1 flex-1 focus:border-t-accent focus:outline-none"> <input id="design-preset-name" type="text" placeholder="new preset name..." class="bg-t-bg border border-t-border text-t-bright text-xs font-mono px-2 py-1 flex-1 focus:border-t-accent focus:outline-none">
@@ -1693,7 +1693,7 @@ if (str_starts_with($route, '/api/')) {
<div class="design-section-label">effects</div> <div class="design-section-label">effects</div>
<div class="design-row"><label>Card shadow</label><select id="setting-shadow-card" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div> <div class="design-row"><label>Card shadow</label><select id="setting-shadow-card" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div>
<div class="design-row"><label>Modal shadow</label><select id="setting-shadow-modal" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div> <div class="design-row"><label>Modal shadow</label><select id="setting-shadow-modal" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div>
<div class="design-row"><label>UI shadow</label><select id="setting-shadow-ui" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" title="Header & footer shadow"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div> <div class="design-row"><label>UI shadow</label><select id="setting-shadow-ui" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" data-tip="header & footer shadow"><option value="none">none</option><option value="soft">soft</option><option value="medium">medium</option><option value="heavy">heavy</option></select></div>
<div class="design-row"><label>Card lift</label><input type="range" id="setting-lift-card" min="0" max="8" step="1" class="design-slider"><span class="design-val" id="val-lift-card">0</span></div> <div class="design-row"><label>Card lift</label><input type="range" id="setting-lift-card" min="0" max="8" step="1" class="design-slider"><span class="design-val" id="val-lift-card">0</span></div>
<div class="design-row"><label>Overlay blur</label><input type="range" id="setting-blur-overlay" min="0" max="16" step="1" class="design-slider"><span class="design-val" id="val-blur-overlay">0</span></div> <div class="design-row"><label>Overlay blur</label><input type="range" id="setting-blur-overlay" min="0" max="16" step="1" class="design-slider"><span class="design-val" id="val-blur-overlay">0</span></div>
<div class="design-row"><label>Animation</label><select id="setting-anim-speed" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="off">off</option><option value="fast">fast</option><option value="normal">normal</option><option value="slow">slow</option></select></div> <div class="design-row"><label>Animation</label><select id="setting-anim-speed" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="off">off</option><option value="fast">fast</option><option value="normal">normal</option><option value="slow">slow</option></select></div>
@@ -1935,7 +1935,7 @@ if (str_starts_with($route, '/api/')) {
<i class="ph ph-magnifying-glass text-t-accent text-base"></i> <i class="ph ph-magnifying-glass text-t-accent text-base"></i>
<input type="text" id="search-input" placeholder="search cards..." class="flex-1 bg-transparent text-t-bright text-sm placeholder:text-t-dim font-mono" autocomplete="off" spellcheck="false"> <input type="text" id="search-input" placeholder="search cards..." class="flex-1 bg-transparent text-t-bright text-sm placeholder:text-t-dim font-mono" autocomplete="off" spellcheck="false">
<select id="search-bucket-filter" class="bg-t-bg border border-t-border text-t-muted text-[10px] font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="">all buckets</option></select> <select id="search-bucket-filter" class="bg-t-bg border border-t-border text-t-muted text-[10px] font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer"><option value="">all buckets</option></select>
<button id="search-replace-toggle" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="toggle replace"> <button id="search-replace-toggle" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-help="Replace mode\nSearch and replace text across all\nmatching cards. Ctrl+Shift+H to open.">
<i class="ph ph-swap text-sm"></i><span class="font-pixel text-[10px]">Replace</span> <i class="ph ph-swap text-sm"></i><span class="font-pixel text-[10px]">Replace</span>
</button> </button>
<button id="search-close" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"> <button id="search-close" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors">
@@ -2018,14 +2018,14 @@ if (str_starts_with($route, '/api/')) {
<button id="detail-delete" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"> <button id="detail-delete" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors">
<i class="ph ph-trash text-sm"></i><span class="font-pixel text-[10px]">Del</span> <i class="ph ph-trash text-sm"></i><span class="font-pixel text-[10px]">Del</span>
</button> </button>
<button id="detail-done" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="mark done"> <button id="detail-done" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="mark done">
<i class="ph ph-check-circle text-sm"></i><span class="font-pixel text-[10px]" id="detail-done-label">Done</span> <i class="ph ph-check-circle text-sm"></i><span class="font-pixel text-[10px]" id="detail-done-label">Done</span>
</button> </button>
<button id="detail-zen" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="zen mode"> <button id="detail-zen" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-help="Zen mode\nFull-screen distraction-free reading.\nDrag edges to resize.">
<i class="ph ph-corners-out text-sm"></i> <i class="ph ph-corners-out text-sm"></i>
</button> </button>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="split-right-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="split view"> <button id="split-right-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-help="Split view\nShows cards sharing tags, links,\nor content with this card.">
<i class="ph ph-split-horizontal text-sm"></i><span class="font-pixel text-[10px]">Split</span> <i class="ph ph-split-horizontal text-sm"></i><span class="font-pixel text-[10px]">Split</span>
</button> </button>
<button id="detail-close" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"> <button id="detail-close" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors">
@@ -2170,10 +2170,10 @@ if (str_starts_with($route, '/api/')) {
</div> </div>
<div class="flex items-center gap-3 px-4 py-2.5 border-b border-t-border shrink-0"> <div class="flex items-center gap-3 px-4 py-2.5 border-b border-t-border shrink-0">
<input type="text" id="inp-title" placeholder="> title..." class="flex-1 bg-transparent border-none text-t-bright text-base font-bold focus:outline-none placeholder:text-t-dim font-pixel"> <input type="text" id="inp-title" placeholder="> title..." class="flex-1 bg-transparent border-none text-t-bright text-base font-bold focus:outline-none placeholder:text-t-dim font-pixel">
<button id="inp-due-date" class="flex items-center gap-1 px-2 py-1 text-[10px] font-pixel border border-t-border text-t-dim hover:border-t-accent hover:text-t-accent transition-colors rounded-sm shrink-0" title="set due date"> <button id="inp-due-date" class="flex items-center gap-1 px-2 py-1 text-[10px] font-pixel border border-t-border text-t-dim hover:border-t-accent hover:text-t-accent transition-colors rounded-sm shrink-0" data-tip="set due date">
<i class="ph ph-clock-countdown text-xs"></i><span id="inp-due-label">No due</span> <i class="ph ph-clock-countdown text-xs"></i><span id="inp-due-label">No due</span>
</button> </button>
<button id="inp-done-toggle" class="flex items-center gap-1 px-2 py-1 text-[10px] font-pixel border border-t-border text-t-dim hover:border-t-accent hover:text-t-accent transition-colors rounded-sm shrink-0" title="mark done"> <button id="inp-done-toggle" class="flex items-center gap-1 px-2 py-1 text-[10px] font-pixel border border-t-border text-t-dim hover:border-t-accent hover:text-t-accent transition-colors rounded-sm shrink-0" data-tip="mark done">
<i class="ph ph-check-circle text-xs"></i><span id="inp-done-label">Done</span> <i class="ph ph-check-circle text-xs"></i><span id="inp-done-label">Done</span>
</button> </button>
<input type="hidden" id="inp-due-value" value=""> <input type="hidden" id="inp-due-value" value="">
@@ -2182,33 +2182,33 @@ if (str_starts_with($route, '/api/')) {
<input type="hidden" id="inp-color" value="#38bdf8"> <input type="hidden" id="inp-color" value="#38bdf8">
</div> </div>
<div class="flex items-center gap-1 px-4 py-1.5 border-b border-t-border shrink-0" id="md-toolbar"> <div class="flex items-center gap-1 px-4 py-1.5 border-b border-t-border shrink-0" id="md-toolbar">
<button data-md="heading" title="Heading" class="toolbar-btn"><i class="ph ph-text-h text-sm"></i></button> <button data-md="heading" data-tip="heading" class="toolbar-btn"><i class="ph ph-text-h text-sm"></i></button>
<button data-md="bold" title="Bold" class="toolbar-btn"><i class="ph ph-text-bolder text-sm"></i></button> <button data-md="bold" data-tip="bold" class="toolbar-btn"><i class="ph ph-text-bolder text-sm"></i></button>
<button data-md="italic" title="Italic" class="toolbar-btn"><i class="ph ph-text-italic text-sm"></i></button> <button data-md="italic" data-tip="italic" class="toolbar-btn"><i class="ph ph-text-italic text-sm"></i></button>
<button data-md="strikethrough" title="Strikethrough" class="toolbar-btn"><i class="ph ph-text-strikethrough text-sm"></i></button> <button data-md="strikethrough" data-tip="strikethrough" class="toolbar-btn"><i class="ph ph-text-strikethrough text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button data-md="code" title="Code" class="toolbar-btn"><i class="ph ph-code text-sm"></i></button> <button data-md="code" data-tip="code" class="toolbar-btn"><i class="ph ph-code text-sm"></i></button>
<button data-md="codeblock" title="Code block" class="toolbar-btn"><i class="ph ph-code-block text-sm"></i></button> <button data-md="codeblock" data-tip="code block" class="toolbar-btn"><i class="ph ph-code-block text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button data-md="ul" title="List" class="toolbar-btn"><i class="ph ph-list-bullets text-sm"></i></button> <button data-md="ul" data-tip="list" class="toolbar-btn"><i class="ph ph-list-bullets text-sm"></i></button>
<button data-md="tree" title="Tree list" class="toolbar-btn"><i class="ph ph-tree-structure text-sm"></i></button> <button data-md="tree" data-tip="tree list" class="toolbar-btn"><i class="ph ph-tree-structure text-sm"></i></button>
<button data-md="ol" title="Numbered" class="toolbar-btn"><i class="ph ph-list-numbers text-sm"></i></button> <button data-md="ol" data-tip="numbered" class="toolbar-btn"><i class="ph ph-list-numbers text-sm"></i></button>
<button data-md="quote" title="Quote" class="toolbar-btn"><i class="ph ph-quotes text-sm"></i></button> <button data-md="quote" data-tip="quote" class="toolbar-btn"><i class="ph ph-quotes text-sm"></i></button>
<button data-md="todo" title="Todo" class="toolbar-btn"><i class="ph ph-check-square text-sm"></i></button> <button data-md="todo" data-tip="todo" class="toolbar-btn"><i class="ph ph-check-square text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button data-md="link" title="Link" class="toolbar-btn"><i class="ph ph-link text-sm"></i></button> <button data-md="link" data-tip="link" class="toolbar-btn"><i class="ph ph-link text-sm"></i></button>
<button data-md="cardlink" title="Card ref [[" class="toolbar-btn"><i class="ph ph-arrow-square-out text-sm"></i></button> <button data-md="cardlink" data-tip="card ref [[" class="toolbar-btn"><i class="ph ph-arrow-square-out text-sm"></i></button>
<button data-md="hr" title="Divider" class="toolbar-btn"><i class="ph ph-minus text-sm"></i></button> <button data-md="hr" data-tip="divider" class="toolbar-btn"><i class="ph ph-minus text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button data-md="extract" title="Extract selection to new card" class="toolbar-btn"><i class="ph ph-scissors text-sm"></i></button> <button data-md="extract" data-help="Extract\nMoves selected text into a new card\nand inserts a [[link]] in its place." class="toolbar-btn"><i class="ph ph-scissors text-sm"></i></button>
<button id="md-upload-pdf" title="Upload & link PDF" class="toolbar-btn"><i class="ph ph-file-pdf text-sm"></i></button> <button id="md-upload-pdf" data-help="Upload PDF\nSelect a PDF file to upload.\nInserts a download link into the card." class="toolbar-btn"><i class="ph ph-file-pdf text-sm"></i></button>
<input type="file" id="md-upload-pdf-file" accept=".pdf,application/pdf" class="hidden"> <input type="file" id="md-upload-pdf-file" accept=".pdf,application/pdf" class="hidden">
<button id="md-asset-picker" title="Insert from assets" class="toolbar-btn" style="position:relative;"><i class="ph ph-images text-sm"></i></button> <button id="md-asset-picker" data-help="Insert from assets\nBrowse uploaded images, audio, and PDFs.\nClick a tile to insert into the editor." class="toolbar-btn" style="position:relative;"><i class="ph ph-images text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button id="md-find-replace" title="Find & replace (Ctrl+H)" class="toolbar-btn" onclick="openFindBar(true)"><i class="ph ph-magnifying-glass text-sm"></i></button> <button id="md-find-replace" data-help="Find & replace\nSearch and replace text within this card.\nCtrl+H to open, Ctrl+F for find-only." class="toolbar-btn" onclick="openFindBar(true)"><i class="ph ph-magnifying-glass text-sm"></i></button>
<?= PluginManager::instance()->slot('md-toolbar') ?> <?= PluginManager::instance()->slot('md-toolbar') ?>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="toggle-lines" class="toolbar-btn flex items-center gap-1.5 px-2" title="line numbers"><i class="ph ph-hash text-sm"></i></button> <button id="toggle-lines" class="toolbar-btn flex items-center gap-1.5 px-2" data-tip="line numbers"><i class="ph ph-hash text-sm"></i></button>
<button id="toggle-preview" class="toolbar-btn flex items-center gap-1.5 px-2"><i class="ph ph-eye text-sm"></i><span class="font-pixel text-[9px]">Preview</span></button> <button id="toggle-preview" class="toolbar-btn flex items-center gap-1.5 px-2"><i class="ph ph-eye text-sm"></i><span class="font-pixel text-[9px]">Preview</span></button>
</div> </div>
<div class="flex flex-1 min-h-0 overflow-hidden"> <div class="flex flex-1 min-h-0 overflow-hidden">
@@ -2220,9 +2220,9 @@ if (str_starts_with($route, '/api/')) {
<i class="ph ph-magnifying-glass text-xs text-t-dim"></i> <i class="ph ph-magnifying-glass text-xs text-t-dim"></i>
<input id="find-input" type="text" placeholder="find…" class="flex-1 bg-t-bg border border-t-border text-t-bright text-xs px-2 py-1 font-mono focus:border-t-accent focus:outline-none" style="min-width:0;"> <input id="find-input" type="text" placeholder="find…" class="flex-1 bg-t-bg border border-t-border text-t-bright text-xs px-2 py-1 font-mono focus:border-t-accent focus:outline-none" style="min-width:0;">
<span id="find-count" class="font-pixel text-[9px] text-t-dim" style="min-width:50px;text-align:center;"></span> <span id="find-count" class="font-pixel text-[9px] text-t-dim" style="min-width:50px;text-align:center;"></span>
<button id="find-prev" class="toolbar-btn" title="previous"><i class="ph ph-caret-up text-xs"></i></button> <button id="find-prev" class="toolbar-btn" data-tip="previous"><i class="ph ph-caret-up text-xs"></i></button>
<button id="find-next" class="toolbar-btn" title="next"><i class="ph ph-caret-down text-xs"></i></button> <button id="find-next" class="toolbar-btn" data-tip="next"><i class="ph ph-caret-down text-xs"></i></button>
<button id="find-close" class="toolbar-btn" title="close"><i class="ph ph-x text-xs"></i></button> <button id="find-close" class="toolbar-btn" data-tip="close"><i class="ph ph-x text-xs"></i></button>
</div> </div>
<div id="replace-row" class="flex items-center gap-2"> <div id="replace-row" class="flex items-center gap-2">
<i class="ph ph-swap text-xs text-t-dim"></i> <i class="ph ph-swap text-xs text-t-dim"></i>
+1
View File
@@ -13,6 +13,7 @@ import { openModal, closeModal, saveCard, saveOnly, openFindBar } from './module
import { openSearch, closeSearch } from './modules/search.js'; import { openSearch, closeSearch } from './modules/search.js';
import { showContextMenu } from './modules/context-menu.js'; import { showContextMenu } from './modules/context-menu.js';
import { undo, canUndo } from './modules/undo.js'; import { undo, canUndo } from './modules/undo.js';
import './modules/tooltip.js';
import { registerShortcut, guards, renderShortcutHelp } from './modules/shortcuts.js'; import { registerShortcut, guards, renderShortcutHelp } from './modules/shortcuts.js';
import { openGallery, closeGallery } from './modules/gallery.js'; import { openGallery, closeGallery } from './modules/gallery.js';
import { refreshTasksView, openTasks, closeTasks } from './modules/tasks.js'; import { refreshTasksView, openTasks, closeTasks } from './modules/tasks.js';
+6 -6
View File
@@ -294,11 +294,11 @@ function renderBar() {
// render (e.g. bucket reorder, plain renderBar() call). // render (e.g. bucket reorder, plain renderBar() call).
const allUnread = state.activeBucket !== 'all' && state.unseenCardIds.size > 0; const allUnread = state.activeBucket !== 'all' && state.unseenCardIds.size > 0;
bar.innerHTML = ` bar.innerHTML = `
<button class="${tab(state.activeBucket === 'all')}${allUnread ? ' has-unread' : ''}" data-bucket="all" title="all"><i class="ph ph-squares-four text-sm"></i>${allUnread ? '<span class="bar-unread-dot"></span>' : ''}</button> <button class="${tab(state.activeBucket === 'all')}${allUnread ? ' has-unread' : ''}" data-bucket="all" data-tip="all"><i class="ph ph-squares-four text-sm"></i>${allUnread ? '<span class="bar-unread-dot"></span>' : ''}</button>
<button class="${tab(state.activeBucket === 'current')}" data-bucket="current" title="current"><i class="ph ph-lightning text-sm"></i></button> <button class="${tab(state.activeBucket === 'current')}" data-bucket="current" data-tip="current"><i class="ph ph-lightning text-sm"></i></button>
<button class="${tab(state.activeBucket === 'favorites')}" data-bucket="favorites" title="favorites"><i class="ph ph-star text-sm"></i></button> <button class="${tab(state.activeBucket === 'favorites')}" data-bucket="favorites" data-tip="favorites"><i class="ph ph-star text-sm"></i></button>
<button class="${tab(state.activeBucket === 'journal')}" data-bucket="journal" title="journal"><i class="ph ph-notebook text-sm"></i></button> <button class="${tab(state.activeBucket === 'journal')}" data-bucket="journal" data-tip="journal"><i class="ph ph-notebook text-sm"></i></button>
<button class="${tab(state.activeBucket === 'none')}" data-bucket="none" title="unsorted"><i class="ph ph-tray text-sm"></i></button> <button class="${tab(state.activeBucket === 'none')}" data-bucket="none" data-tip="unsorted"><i class="ph ph-tray text-sm"></i></button>
${topLevel.map(b => { ${topLevel.map(b => {
const kids = childrenOf(b.id); const kids = childrenOf(b.id);
const isSuper = kids.length > 0; const isSuper = kids.length > 0;
@@ -400,7 +400,7 @@ function renderBar() {
const c = m.color || '#64748b'; const c = m.color || '#64748b';
const active = state.activeBucket == m.id || (!members.some(x => x.id == state.activeBucket) && lastId == m.id); const active = state.activeBucket == m.id || (!members.some(x => x.id == state.activeBucket) && lastId == m.id);
const isParent = m.id === parentId; const isParent = m.id === parentId;
return `<button class="bucket-tab ${active ? 'active' : ''} flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors w-full" data-bucket="${m.id}" draggable="true" data-bucket-drag="${m.id}" style="border-left:3px solid ${c};${active ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-folder text-sm"></i><span class="font-pixel text-[10px] flex-1 text-left">${esc(m.name)}</span>${isParent ? '<i class="ph ph-house text-[10px] opacity-60" title="parent"></i>' : ''}</button>`; return `<button class="bucket-tab ${active ? 'active' : ''} flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors w-full" data-bucket="${m.id}" draggable="true" data-bucket-drag="${m.id}" style="border-left:3px solid ${c};${active ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-folder text-sm"></i><span class="font-pixel text-[10px] flex-1 text-left">${esc(m.name)}</span>${isParent ? '<i class="ph ph-house text-[10px] opacity-60" data-tip="parent"></i>' : ''}</button>`;
}; };
pop.innerHTML = members.map(renderEntry).join(''); pop.innerHTML = members.map(renderEntry).join('');
// Append inside #bucket-bar (not document.body) so the collab // Append inside #bucket-bar (not document.body) so the collab
+1 -1
View File
@@ -76,7 +76,7 @@ function renderCalList(calendars) {
${CAL_COLORS.map(c => `<button class="cal-cfg-color" data-color="${c}" style="width:14px;height:14px;border-radius:2px;background:${c};border:2px solid ${c === activeColor ? 'var(--t-bright)' : 'transparent'};cursor:pointer;"></button>`).join('')} ${CAL_COLORS.map(c => `<button class="cal-cfg-color" data-color="${c}" style="width:14px;height:14px;border-radius:2px;background:${c};border:2px solid ${c === activeColor ? 'var(--t-bright)' : 'transparent'};cursor:pointer;"></button>`).join('')}
</div> </div>
<div class="flex gap-1 ml-1"> <div class="flex gap-1 ml-1">
${CAL_ICONS.slice(0, 6).map(ic => `<button class="cal-cfg-icon" data-icon="${ic.id}" title="${ic.label}" style="font-size:12px;cursor:pointer;padding:1px;border-radius:2px;${ic.id === activeIcon ? 'color:var(--t-accent);' : 'color:var(--t-dim);'}"><i class="ph ph-${ic.id}"></i></button>`).join('')} ${CAL_ICONS.slice(0, 6).map(ic => `<button class="cal-cfg-icon" data-icon="${ic.id}" data-tip="${ic.label}" style="font-size:12px;cursor:pointer;padding:1px;border-radius:2px;${ic.id === activeIcon ? 'color:var(--t-accent);' : 'color:var(--t-dim);'}"><i class="ph ph-${ic.id}"></i></button>`).join('')}
</div> </div>
</div>`; </div>`;
}).join(''); }).join('');
+4 -4
View File
@@ -433,7 +433,7 @@ function renderSidebar(card) {
<span class="w-2 h-2 rounded-full shrink-0" style="background:${gc.color}"></span> <span class="w-2 h-2 rounded-full shrink-0" style="background:${gc.color}"></span>
${img ? `<span class="w-6 h-6 rounded-full shrink-0 bg-cover bg-center border border-t-border" style="background-image:url('${img}')"></span>` : ''} ${img ? `<span class="w-6 h-6 rounded-full shrink-0 bg-cover bg-center border border-t-border" style="background-image:url('${img}')"></span>` : ''}
<span class="font-pixel text-[10px] ${active ? 'text-t-bright' : 'text-t-muted'} truncate flex-1">${esc(gc.title)}</span> <span class="font-pixel text-[10px] ${active ? 'text-t-bright' : 'text-t-muted'} truncate flex-1">${esc(gc.title)}</span>
<button onclick="event.stopPropagation();window._ungroupCard(${gc.id})" class="text-t-dim hover:text-t-danger transition-colors shrink-0 ${gc.id == groupCards[0]?.id ? 'invisible' : ''}" title="ungroup"> <button onclick="event.stopPropagation();window._ungroupCard(${gc.id})" class="text-t-dim hover:text-t-danger transition-colors shrink-0 ${gc.id == groupCards[0]?.id ? 'invisible' : ''}" data-tip="ungroup">
<i class="ph ph-x text-[10px]"></i> <i class="ph ph-x text-[10px]"></i>
</button> </button>
</div>`; </div>`;
@@ -554,7 +554,7 @@ function applyReadingResizeHandles(root) {
const line = document.createElement('button'); const line = document.createElement('button');
line.type = 'button'; line.type = 'button';
line.className = 'detail-font-dragline'; line.className = 'detail-font-dragline';
line.title = 'drag to resize text'; line.dataset.tip = 'drag to resize text';
line.setAttribute('aria-label', 'resize text'); line.setAttribute('aria-label', 'resize text');
el.appendChild(line); el.appendChild(line);
@@ -615,7 +615,7 @@ export function bindDetailImageFullscreen(root) {
// Regular images: double-click, no gallery nav // Regular images: double-click, no gallery nav
root.querySelectorAll('img').forEach(img => { root.querySelectorAll('img').forEach(img => {
if (img.closest('.md-gallery')) return; if (img.closest('.md-gallery')) return;
img.title = 'double-click to open fullscreen'; img.dataset.tip = 'double-click to fullscreen';
img.addEventListener('dblclick', e => { img.addEventListener('dblclick', e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -898,7 +898,7 @@ function loadSplitCard(id) {
<button id="split-back" class="flex items-center gap-1.5 flex-1 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors"> <button id="split-back" class="flex items-center gap-1.5 flex-1 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors">
<i class="ph ph-arrow-left text-sm"></i><span class="font-pixel text-[10px]">${splitLastQuery ? 'results' : 'related cards'}</span> <i class="ph ph-arrow-left text-sm"></i><span class="font-pixel text-[10px]">${splitLastQuery ? 'results' : 'related cards'}</span>
</button> </button>
<button id="split-promote" class="flex items-center gap-1.5 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors" title="open as main card"> <button id="split-promote" class="flex items-center gap-1.5 px-3 py-2 text-xs text-t-muted hover:text-t-accent hover:bg-t-raised transition-colors" data-tip="open as main card">
<i class="ph ph-arrow-line-left text-sm"></i><span class="font-pixel text-[10px]">Focus</span> <i class="ph ph-arrow-line-left text-sm"></i><span class="font-pixel text-[10px]">Focus</span>
</button> </button>
</div>`; </div>`;
+3 -3
View File
@@ -102,7 +102,7 @@ function startCloseMorph() {
CARD_COLORS.forEach(c => { CARD_COLORS.forEach(c => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.type = 'button'; btn.title = c.name; btn.type = 'button'; btn.dataset.tip = c.name;
btn.dataset.color = c.hex; btn.dataset.color = c.hex;
btn.className = 'w-5 h-5 rounded-sm border-2 border-transparent transition-all hover:scale-125 color-swatch'; btn.className = 'w-5 h-5 rounded-sm border-2 border-transparent transition-all hover:scale-125 color-swatch';
btn.style.background = c.hex; btn.style.background = c.hex;
@@ -1243,13 +1243,13 @@ $('md-asset-picker')?.addEventListener('click', async () => {
const type = assetType(f.name); const type = assetType(f.name);
const label = f.name.replace(/\.[^.]+$/, ''); const label = f.name.replace(/\.[^.]+$/, '');
if (type === 'image') { if (type === 'image') {
return `<div class="asset-pick-tile" data-url="${esc(f.url)}" data-name="${esc(f.name)}" data-type="image" title="${esc(f.name)}" style="cursor:pointer;border-radius:4px;overflow:hidden;aspect-ratio:1;background:var(--t-bg);border:1px solid var(--t-border);"> return `<div class="asset-pick-tile" data-url="${esc(f.url)}" data-name="${esc(f.name)}" data-type="image" data-tip="${esc(f.name)}" style="cursor:pointer;border-radius:4px;overflow:hidden;aspect-ratio:1;background:var(--t-bg);border:1px solid var(--t-border);">
<img src="${esc(f.url)}" style="width:100%;height:100%;object-fit:cover;" loading="lazy"> <img src="${esc(f.url)}" style="width:100%;height:100%;object-fit:cover;" loading="lazy">
</div>`; </div>`;
} }
const icon = type === 'audio' ? 'ph-music-note' : type === 'pdf' ? 'ph-file-pdf' : 'ph-file'; const icon = type === 'audio' ? 'ph-music-note' : type === 'pdf' ? 'ph-file-pdf' : 'ph-file';
const iconColor = type === 'pdf' ? 'color:var(--t-danger)' : 'color:var(--t-dim)'; const iconColor = type === 'pdf' ? 'color:var(--t-danger)' : 'color:var(--t-dim)';
return `<div class="asset-pick-tile" data-url="${esc(f.url)}" data-name="${esc(f.name)}" data-type="${type}" title="${esc(f.name)}" style="cursor:pointer;border-radius:4px;overflow:hidden;aspect-ratio:1;background:var(--t-bg);border:1px solid var(--t-border);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px;"> return `<div class="asset-pick-tile" data-url="${esc(f.url)}" data-name="${esc(f.name)}" data-type="${type}" data-tip="${esc(f.name)}" style="cursor:pointer;border-radius:4px;overflow:hidden;aspect-ratio:1;background:var(--t-bg);border:1px solid var(--t-border);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px;">
<i class="ph ${icon}" style="font-size:20px;${iconColor}"></i> <i class="ph ${icon}" style="font-size:20px;${iconColor}"></i>
<span style="font-size:7px;color:var(--t-muted);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--t-font-pixel,'Silkscreen',monospace);">${esc(label)}</span> <span style="font-size:7px;color:var(--t-muted);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--t-font-pixel,'Silkscreen',monospace);">${esc(label)}</span>
</div>`; </div>`;
+3 -3
View File
@@ -170,9 +170,9 @@ export async function openGallery() {
<div class="relative"> <div class="relative">
${mediaTile(f.url, 'border-t-danger/30 opacity-60')} ${mediaTile(f.url, 'border-t-danger/30 opacity-60')}
<div style="position:absolute;top:4px;right:4px;display:flex;gap:4px;" class="opacity-0 group-hover:opacity-100 transition-opacity"> <div style="position:absolute;top:4px;right:4px;display:flex;gap:4px;" class="opacity-0 group-hover:opacity-100 transition-opacity">
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-orphan="${f.name}" title="new card"><i class="ph ph-plus text-sm"></i></button> <button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-orphan="${f.name}" data-tip="new card"><i class="ph ph-plus text-sm"></i></button>
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-slide="${f.name}" title="new slide (background)"><i class="ph ph-image-square text-sm"></i></button> <button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-slide="${f.name}" data-tip="new slide"><i class="ph ph-image-square text-sm"></i></button>
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-danger rounded border-0 cursor-pointer hover:bg-t-danger hover:text-t-bg transition-colors" data-del-orphan="${f.name}" title="delete"><i class="ph ph-trash text-sm"></i></button> <button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-danger rounded border-0 cursor-pointer hover:bg-t-danger hover:text-t-bg transition-colors" data-del-orphan="${f.name}" data-tip="delete"><i class="ph ph-trash text-sm"></i></button>
</div> </div>
</div> </div>
<div class="mt-1 font-pixel text-[8px] text-t-dim truncate">${esc(f.name)}</div> <div class="mt-1 font-pixel text-[8px] text-t-dim truncate">${esc(f.name)}</div>
+1 -1
View File
@@ -146,7 +146,7 @@ export function renderMd(src) {
const linked = processed.replace(/\[\[([^\]]+)\]\]/g, (_, title) => { const linked = processed.replace(/\[\[([^\]]+)\]\]/g, (_, title) => {
const card = state.cards.find(c => c.title.toLowerCase() === title.toLowerCase()); const card = state.cards.find(c => c.title.toLowerCase() === title.toLowerCase());
if (card) return `<span class="card-link" data-card-id="${card.id}">${esc(title)}</span>`; if (card) return `<span class="card-link" data-card-id="${card.id}">${esc(title)}</span>`;
return `<span class="card-link broken" title="card not found">${esc(title)}</span>`; return `<span class="card-link broken" data-tip="card not found">${esc(title)}</span>`;
}); });
let html = marked.parse(linked); let html = marked.parse(linked);
+1 -1
View File
@@ -73,7 +73,7 @@ export function render() {
<span>${esc(i.label)}</span> <span>${esc(i.label)}</span>
${i.dismissable !== false ? `<button class="upcoming-dismiss" data-dismiss="${i.id}"><i class="ph ph-x text-xs"></i></button>` : ''} ${i.dismissable !== false ? `<button class="upcoming-dismiss" data-dismiss="${i.id}"><i class="ph ph-x text-xs"></i></button>` : ''}
</div>`).join('')} </div>`).join('')}
<button id="dismiss-all" class="shrink-0 ml-auto text-t-dim hover:text-t-danger transition-colors" title="dismiss all"><i class="ph ph-x-circle text-sm"></i></button> <button id="dismiss-all" class="shrink-0 ml-auto text-t-dim hover:text-t-danger transition-colors" data-tip="dismiss all"><i class="ph ph-x-circle text-sm"></i></button>
</div>`; </div>`;
banner.querySelectorAll('[data-notif-id]').forEach(el => { banner.querySelectorAll('[data-notif-id]').forEach(el => {
+91
View File
@@ -0,0 +1,91 @@
// ── Global tooltip system ──
// Replaces native title="" with styled tooltips. Supports:
// data-tip="short text" — single-line tooltip (replaces title)
// data-help="line1\nline2" — multi-line inline help (shown on hover)
const SHOW_DELAY = 400;
const HIDE_DELAY = 100;
const OFFSET = 8;
let tipEl = null;
let showTimer = null;
let hideTimer = null;
let currentTarget = null;
function create() {
if (tipEl) return;
tipEl = document.createElement('div');
tipEl.id = 'tooltip';
tipEl.setAttribute('role', 'tooltip');
document.body.appendChild(tipEl);
}
function show(target) {
clearTimeout(hideTimer);
if (currentTarget === target) return;
currentTarget = target;
create();
const tip = target.dataset.tip || '';
const help = target.dataset.help || '';
if (!tip && !help) { hide(); return; }
if (help) {
tipEl.classList.add('tooltip-help');
tipEl.innerHTML = help.replace(/\\n/g, '<br>');
} else {
tipEl.classList.remove('tooltip-help');
tipEl.textContent = tip;
}
tipEl.style.visibility = 'hidden';
tipEl.style.display = 'block';
const rect = target.getBoundingClientRect();
const tipRect = tipEl.getBoundingClientRect();
let top, left;
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
if (spaceBelow >= tipRect.height + OFFSET || spaceBelow >= spaceAbove) {
top = rect.bottom + OFFSET;
tipEl.classList.remove('tooltip-above');
} else {
top = rect.top - tipRect.height - OFFSET;
tipEl.classList.add('tooltip-above');
}
left = rect.left + rect.width / 2 - tipRect.width / 2;
left = Math.max(8, Math.min(left, window.innerWidth - tipRect.width - 8));
top = Math.max(4, top);
tipEl.style.top = top + 'px';
tipEl.style.left = left + 'px';
tipEl.style.visibility = '';
}
function hide() {
currentTarget = null;
if (tipEl) tipEl.style.display = 'none';
}
function onEnter(e) {
const target = e.target.closest('[data-tip],[data-help]');
if (!target) return;
clearTimeout(hideTimer);
showTimer = setTimeout(() => show(target), SHOW_DELAY);
}
function onLeave(e) {
const target = e.target.closest('[data-tip],[data-help]');
if (!target) return;
clearTimeout(showTimer);
hideTimer = setTimeout(hide, HIDE_DELAY);
}
document.addEventListener('pointerenter', onEnter, true);
document.addEventListener('pointerleave', onLeave, true);
document.addEventListener('pointerdown', () => { clearTimeout(showTimer); hide(); }, true);
document.addEventListener('keydown', () => { clearTimeout(showTimer); hide(); }, true);
document.addEventListener('scroll', () => { clearTimeout(showTimer); hide(); }, true);
+9 -9
View File
@@ -3,13 +3,13 @@
<div class="ascii-canvas-modal bg-t-surface border border-t-border rounded-lg shadow-xl flex flex-col" style="width:90vw;max-width:960px;height:85vh;max-height:700px;margin:auto;position:absolute;inset:0;"> <div class="ascii-canvas-modal bg-t-surface border border-t-border rounded-lg shadow-xl flex flex-col" style="width:90vw;max-width:960px;height:85vh;max-height:700px;margin:auto;position:absolute;inset:0;">
<!-- Toolbar --> <!-- Toolbar -->
<div class="flex items-center gap-1 px-3 py-2 border-b border-t-border shrink-0 flex-wrap" id="ascii-canvas-toolbar"> <div class="flex items-center gap-1 px-3 py-2 border-b border-t-border shrink-0 flex-wrap" id="ascii-canvas-toolbar">
<button data-tool="brush" class="toolbar-btn active" title="Brush (B)"><i class="ph ph-pencil-simple text-sm"></i></button> <button data-tool="brush" class="toolbar-btn active" data-tip="Brush (B)"><i class="ph ph-pencil-simple text-sm"></i></button>
<button data-tool="eraser" class="toolbar-btn" title="Eraser (E)"><i class="ph ph-eraser text-sm"></i></button> <button data-tool="eraser" class="toolbar-btn" data-tip="Eraser (E)"><i class="ph ph-eraser text-sm"></i></button>
<button data-tool="line" class="toolbar-btn" title="Line (L)"><i class="ph ph-line-segment text-sm"></i></button> <button data-tool="line" class="toolbar-btn" data-tip="Line (L)"><i class="ph ph-line-segment text-sm"></i></button>
<button data-tool="rect" class="toolbar-btn" title="Rectangle (R)"><i class="ph ph-rectangle text-sm"></i></button> <button data-tool="rect" class="toolbar-btn" data-tip="Rectangle (R)"><i class="ph ph-rectangle text-sm"></i></button>
<button data-tool="circle" class="toolbar-btn" title="Ellipse (O)"><i class="ph ph-circle text-sm"></i></button> <button data-tool="circle" class="toolbar-btn" data-tip="Ellipse (O)"><i class="ph ph-circle text-sm"></i></button>
<button data-tool="fill" class="toolbar-btn" title="Fill (F)"><i class="ph ph-paint-bucket text-sm"></i></button> <button data-tool="fill" class="toolbar-btn" data-tip="Fill (F)"><i class="ph ph-paint-bucket text-sm"></i></button>
<button data-tool="text" class="toolbar-btn" title="Text (T)"><i class="ph ph-text-t text-sm"></i></button> <button data-tool="text" class="toolbar-btn" data-tip="Text (T)"><i class="ph ph-text-t text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<label class="flex items-center gap-1 text-t-muted text-xs"> <label class="flex items-center gap-1 text-t-muted text-xs">
<span>Char:</span> <span>Char:</span>
@@ -24,9 +24,9 @@
<span>H:</span> <span>H:</span>
<input id="ascii-canvas-height" type="number" value="20" min="3" max="100" class="w-12 bg-t-bg border border-t-border rounded text-t-text text-xs text-center px-1 py-0"> <input id="ascii-canvas-height" type="number" value="20" min="3" max="100" class="w-12 bg-t-bg border border-t-border rounded text-t-text text-xs text-center px-1 py-0">
</label> </label>
<button id="ascii-canvas-resize" class="toolbar-btn text-xs" title="Resize"><i class="ph ph-arrows-out text-sm"></i></button> <button id="ascii-canvas-resize" class="toolbar-btn text-xs" data-tip="Resize"><i class="ph ph-arrows-out text-sm"></i></button>
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button id="ascii-canvas-clear" class="toolbar-btn text-xs" title="Clear"><i class="ph ph-trash text-sm"></i></button> <button id="ascii-canvas-clear" class="toolbar-btn text-xs" data-tip="Clear"><i class="ph ph-trash text-sm"></i></button>
<div class="flex-1"></div> <div class="flex-1"></div>
<span id="ascii-canvas-coords" class="text-t-dim text-xs font-mono">1:1</span> <span id="ascii-canvas-coords" class="text-t-dim text-xs font-mono">1:1</span>
</div> </div>
+3 -3
View File
@@ -4,8 +4,8 @@
<div id="chat-flyout-header"> <div id="chat-flyout-header">
<i class="ph ph-chats-circle text-t-accent text-sm"></i> <i class="ph ph-chats-circle text-t-accent text-sm"></i>
<span id="chat-flyout-title" class="font-pixel text-[10px] text-t-bright flex-1">chat</span> <span id="chat-flyout-title" class="font-pixel text-[10px] text-t-bright flex-1">chat</span>
<button id="chat-new-btn" class="text-t-muted hover:text-t-accent transition-colors" title="new conversation"><i class="ph ph-plus-square text-sm"></i></button> <button id="chat-new-btn" class="text-t-muted hover:text-t-accent transition-colors" data-tip="new conversation"><i class="ph ph-plus-square text-sm"></i></button>
<button id="chat-refresh-btn" class="text-t-muted hover:text-t-accent transition-colors" title="refresh"><i class="ph ph-arrows-clockwise text-sm" id="chat-refresh-icon"></i></button> <button id="chat-refresh-btn" class="text-t-muted hover:text-t-accent transition-colors" data-tip="refresh"><i class="ph ph-arrows-clockwise text-sm" id="chat-refresh-icon"></i></button>
<button id="chat-close-btn" class="text-t-muted hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i></button> <button id="chat-close-btn" class="text-t-muted hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i></button>
</div> </div>
@@ -29,7 +29,7 @@
<div id="chat-thread-messages" class="chat-thread-messages"></div> <div id="chat-thread-messages" class="chat-thread-messages"></div>
<form id="chat-thread-form"> <form id="chat-thread-form">
<input id="chat-thread-input" type="text" autocomplete="off" placeholder="type a message..." maxlength="2000"> <input id="chat-thread-input" type="text" autocomplete="off" placeholder="type a message..." maxlength="2000">
<button type="submit" id="chat-thread-send" class="text-t-accent hover:brightness-125" title="send"><i class="ph ph-paper-plane-tilt text-sm"></i></button> <button type="submit" id="chat-thread-send" class="text-t-accent hover:brightness-125" data-tip="send"><i class="ph ph-paper-plane-tilt text-sm"></i></button>
</form> </form>
</div> </div>
+12 -12
View File
@@ -7,10 +7,10 @@
<i class="ph ph-envelope text-t-accent text-base"></i> <i class="ph ph-envelope text-t-accent text-base"></i>
<h2 class="font-pixel text-xs text-t-accent">mail</h2> <h2 class="font-pixel text-xs text-t-accent">mail</h2>
<select id="email-account-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer min-w-32"></select> <select id="email-account-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer min-w-32"></select>
<button id="email-refresh" class="toolbar-btn" title="Refresh"><i class="ph ph-arrows-clockwise text-sm"></i></button> <button id="email-refresh" class="toolbar-btn" data-tip="refresh"><i class="ph ph-arrows-clockwise text-sm"></i></button>
<span id="email-status" class="text-[10px] text-t-dim font-pixel"></span> <span id="email-status" class="text-[10px] text-t-dim font-pixel"></span>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="email-show-settings" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="Manage accounts in settings"><i class="ph ph-gear text-sm"></i><span class="font-pixel text-[10px]">Accounts</span></button> <button id="email-show-settings" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="manage accounts"><i class="ph ph-gear text-sm"></i><span class="font-pixel text-[10px]">Accounts</span></button>
<button id="email-close" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Close</span></button> <button id="email-close" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Close</span></button>
</div> </div>
@@ -41,11 +41,11 @@
<div class="sticky top-0 z-20 bg-t-bg"> <div class="sticky top-0 z-20 bg-t-bg">
<!-- List toolbar: pagination + spam quick-select --> <!-- List toolbar: pagination + spam quick-select -->
<div id="email-list-toolbar" class="hidden flex items-center gap-2 px-3 py-1.5 border-b border-t-border"> <div id="email-list-toolbar" class="hidden flex items-center gap-2 px-3 py-1.5 border-b border-t-border">
<button id="email-page-prev" class="toolbar-btn" title="Previous page"><i class="ph ph-caret-left text-sm"></i></button> <button id="email-page-prev" class="toolbar-btn" data-tip="previous page"><i class="ph ph-caret-left text-sm"></i></button>
<button id="email-page-next" class="toolbar-btn" title="Next page"><i class="ph ph-caret-right text-sm"></i></button> <button id="email-page-next" class="toolbar-btn" data-tip="next page"><i class="ph ph-caret-right text-sm"></i></button>
<span id="email-page-info" class="font-pixel text-[9px] text-t-dim">—</span> <span id="email-page-info" class="font-pixel text-[9px] text-t-dim">—</span>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="email-select-spam" class="hidden flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" title="Select all messages flagged as spam"> <button id="email-select-spam" class="hidden flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" data-tip="select spam">
<i class="ph ph-warning-octagon text-sm"></i> <i class="ph ph-warning-octagon text-sm"></i>
<span class="font-pixel text-[9px]">select spam (<span id="email-spam-count">0</span>)</span> <span class="font-pixel text-[9px]">select spam (<span id="email-spam-count">0</span>)</span>
</button> </button>
@@ -54,16 +54,16 @@
<div id="email-selection-bar" class="hidden flex items-center gap-2 px-3 py-2 border-b border-t-accent bg-t-raised"> <div id="email-selection-bar" class="hidden flex items-center gap-2 px-3 py-2 border-b border-t-accent bg-t-raised">
<span id="email-selection-count" class="font-pixel text-[10px] text-t-accent">0 selected</span> <span id="email-selection-count" class="font-pixel text-[10px] text-t-accent">0 selected</span>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="email-selection-mark-spam" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" title="Mark sender as spam (future mail from same sender will be flagged)"> <button id="email-selection-mark-spam" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" data-tip="mark as spam">
<i class="ph ph-warning-octagon text-sm"></i><span class="font-pixel text-[9px]">Spam</span> <i class="ph ph-warning-octagon text-sm"></i><span class="font-pixel text-[9px]">Spam</span>
</button> </button>
<button id="email-selection-mark-ham" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-accent text-t-accent hover:bg-t-accent/10 transition-colors" title="Mark sender as not spam (allowlist)"> <button id="email-selection-mark-ham" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-accent text-t-accent hover:bg-t-accent/10 transition-colors" data-tip="not spam">
<i class="ph ph-check-circle text-sm"></i><span class="font-pixel text-[9px]">Not spam</span> <i class="ph ph-check-circle text-sm"></i><span class="font-pixel text-[9px]">Not spam</span>
</button> </button>
<button id="email-selection-delete" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" title="Delete selected on server"> <button id="email-selection-delete" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-danger text-t-danger hover:bg-t-danger/10 transition-colors" data-tip="Delete selected on server">
<i class="ph ph-trash text-sm"></i><span class="font-pixel text-[9px]">Delete</span> <i class="ph ph-trash text-sm"></i><span class="font-pixel text-[9px]">Delete</span>
</button> </button>
<button id="email-selection-clear" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="Clear selection"> <button id="email-selection-clear" class="flex items-center gap-1 px-2 py-0.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="Clear selection">
<i class="ph ph-x text-sm"></i> <i class="ph ph-x text-sm"></i>
</button> </button>
</div> </div>
@@ -91,9 +91,9 @@
</div> </div>
<div class="mt-3 flex items-center gap-2 flex-wrap"> <div class="mt-3 flex items-center gap-2 flex-wrap">
<button id="email-extract-btn" class="flex items-center gap-1.5 px-3 py-1 text-xs bg-t-accent text-t-bg border border-t-accent hover:brightness-110 transition-all"><i class="ph ph-scissors text-sm"></i><span class="font-pixel text-[10px]">Extract as card</span></button> <button id="email-extract-btn" class="flex items-center gap-1.5 px-3 py-1 text-xs bg-t-accent text-t-bg border border-t-accent hover:brightness-110 transition-all"><i class="ph ph-scissors text-sm"></i><span class="font-pixel text-[10px]">Extract as card</span></button>
<button id="email-view-html" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="Show HTML rendered version (external images blocked by default)"><i class="ph ph-code text-sm"></i><span class="font-pixel text-[10px]">View as html</span></button> <button id="email-view-html" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="Show HTML rendered version (external images blocked by default)"><i class="ph ph-code text-sm"></i><span class="font-pixel text-[10px]">View as html</span></button>
<button id="email-view-text" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="Switch back to plain text"><i class="ph ph-text-aa text-sm"></i><span class="font-pixel text-[10px]">View as text</span></button> <button id="email-view-text" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="Switch back to plain text"><i class="ph ph-text-aa text-sm"></i><span class="font-pixel text-[10px]">View as text</span></button>
<button id="email-load-images" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-accent2 text-t-accent2 hover:bg-t-accent2/10 transition-colors" title="Load external images (privacy: tracking pixels may be loaded)"><i class="ph ph-image text-sm"></i><span class="font-pixel text-[10px]">Load images (<span id="email-blocked-count">0</span>)</span></button> <button id="email-load-images" class="hidden items-center gap-1.5 px-3 py-1 text-xs border border-t-accent2 text-t-accent2 hover:bg-t-accent2/10 transition-colors" data-tip="Load external images (privacy: tracking pixels may be loaded)"><i class="ph ph-image text-sm"></i><span class="font-pixel text-[10px]">Load images (<span id="email-blocked-count">0</span>)</span></button>
<span id="email-extract-status" class="font-pixel text-[9px] text-t-dim"></span> <span id="email-extract-status" class="font-pixel text-[9px] text-t-dim"></span>
</div> </div>
</div> </div>
+6 -6
View File
@@ -218,9 +218,9 @@ function renderAccountsList() {
<div class="email-account-meta">${escapeHtml(a.username || '')} @ ${escapeHtml(a.host)}:${a.port} · ${escapeHtml(a.encryption)}</div> <div class="email-account-meta">${escapeHtml(a.username || '')} @ ${escapeHtml(a.host)}:${a.port} · ${escapeHtml(a.encryption)}</div>
</div> </div>
<div class="email-account-actions"> <div class="email-account-actions">
<button class="toolbar-btn email-edit-btn" title="Edit"><i class="ph ph-pencil-simple text-sm"></i></button> <button class="toolbar-btn email-edit-btn" data-tip="edit"><i class="ph ph-pencil-simple text-sm"></i></button>
<button class="toolbar-btn email-test-btn" title="Test"><i class="ph ph-plug text-sm"></i></button> <button class="toolbar-btn email-test-btn" data-tip="test"><i class="ph ph-plug text-sm"></i></button>
<button class="toolbar-btn email-del-btn" title="Delete"><i class="ph ph-trash text-sm"></i></button> <button class="toolbar-btn email-del-btn" data-tip="delete"><i class="ph ph-trash text-sm"></i></button>
</div> </div>
</div> </div>
`).join(''); `).join('');
@@ -452,13 +452,13 @@ function renderMessageList() {
const spamScore = m.spam?.score ?? 0; const spamScore = m.spam?.score ?? 0;
const spamReasons = (m.spam?.reasons || []).join(' · '); const spamReasons = (m.spam?.reasons || []).join(' · ');
const spamBadge = spamLevel === 'spam' const spamBadge = spamLevel === 'spam'
? `<span class="email-spam-badge spam" title="${escapeHtml(spamReasons)}">⚠ spam</span>` ? `<span class="email-spam-badge spam" data-tip="${escapeHtml(spamReasons)}">⚠ spam</span>`
: spamLevel === 'suspicious' : spamLevel === 'suspicious'
? `<span class="email-spam-badge suspicious" title="${escapeHtml(spamReasons)}">? suspicious</span>` ? `<span class="email-spam-badge suspicious" data-tip="${escapeHtml(spamReasons)}">? suspicious</span>`
: ''; : '';
const rowTitle = spamReasons ? `spam score ${spamScore}: ${spamReasons}` : ''; const rowTitle = spamReasons ? `spam score ${spamScore}: ${spamReasons}` : '';
return ` return `
<div class="email-msg-row ${m.seen ? '' : 'unseen'} ${sel ? 'selected' : ''} spam-${spamLevel}" data-uid="${m.uid}" title="${escapeHtml(rowTitle)}"> <div class="email-msg-row ${m.seen ? '' : 'unseen'} ${sel ? 'selected' : ''} spam-${spamLevel}" data-uid="${m.uid}" data-tip="${escapeHtml(rowTitle)}">
<input type="checkbox" class="email-msg-check" ${sel ? 'checked' : ''} aria-label="select message"> <input type="checkbox" class="email-msg-check" ${sel ? 'checked' : ''} aria-label="select message">
<div class="email-msg-content"> <div class="email-msg-content">
<div class="flex items-baseline justify-between gap-2"> <div class="flex items-baseline justify-between gap-2">
+15 -15
View File
@@ -9,7 +9,7 @@
<select id="gitea-account-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer min-w-32"></select> <select id="gitea-account-select" class="bg-t-bg border border-t-border text-t-bright text-xs font-pixel px-2 py-1 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer min-w-32"></select>
<span id="gitea-status" class="text-[10px] text-t-dim font-pixel"></span> <span id="gitea-status" class="text-[10px] text-t-dim font-pixel"></span>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="gitea-show-settings" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" title="Manage accounts in settings"><i class="ph ph-gear text-sm"></i><span class="font-pixel text-[10px]">Accounts</span></button> <button id="gitea-show-settings" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="Manage accounts in settings"><i class="ph ph-gear text-sm"></i><span class="font-pixel text-[10px]">Accounts</span></button>
<button id="gitea-close" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Close</span></button> <button id="gitea-close" class="flex items-center gap-1.5 px-2 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors"><i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Close</span></button>
</div> </div>
@@ -40,25 +40,25 @@
<div class="sticky top-0 z-20 bg-t-bg border-b border-t-border"> <div class="sticky top-0 z-20 bg-t-bg border-b border-t-border">
<!-- Filters bar --> <!-- Filters bar -->
<div class="flex items-center gap-2 px-3 py-1.5"> <div class="flex items-center gap-2 px-3 py-1.5">
<select id="gitea-filter-state" class="bg-t-bg border border-t-border text-t-bright text-[10px] font-pixel px-1.5 py-0.5 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" title="Issue state"> <select id="gitea-filter-state" class="bg-t-bg border border-t-border text-t-bright text-[10px] font-pixel px-1.5 py-0.5 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" data-tip="Issue state">
<option value="open">open</option> <option value="open">open</option>
<option value="closed">closed</option> <option value="closed">closed</option>
<option value="all">all</option> <option value="all">all</option>
</select> </select>
<select id="gitea-filter-scope" class="bg-t-bg border border-t-border text-t-bright text-[10px] font-pixel px-1.5 py-0.5 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" title="Filter by your involvement"> <select id="gitea-filter-scope" class="bg-t-bg border border-t-border text-t-bright text-[10px] font-pixel px-1.5 py-0.5 rounded-sm focus:border-t-accent focus:outline-none cursor-pointer" data-tip="Filter by your involvement">
<option value="all">everyone</option> <option value="all">everyone</option>
<option value="assigned">assigned to me</option> <option value="assigned">assigned to me</option>
<option value="created">created by me</option> <option value="created">created by me</option>
<option value="mentioned">mentioning me</option> <option value="mentioned">mentioning me</option>
</select> </select>
<input id="gitea-filter-q" type="text" placeholder="search..." autocomplete="off" class="flex-1 min-w-0 bg-t-bg border border-t-border text-t-text px-2 py-0.5 text-[11px] focus:border-t-accent focus:outline-none font-mono"> <input id="gitea-filter-q" type="text" placeholder="search..." autocomplete="off" class="flex-1 min-w-0 bg-t-bg border border-t-border text-t-text px-2 py-0.5 text-[11px] focus:border-t-accent focus:outline-none font-mono">
<button id="gitea-new-issue" class="toolbar-btn" title="New issue"><i class="ph ph-plus-square text-sm text-t-accent"></i></button> <button id="gitea-new-issue" class="toolbar-btn" data-tip="New issue"><i class="ph ph-plus-square text-sm text-t-accent"></i></button>
<button id="gitea-refresh" class="toolbar-btn" title="Refresh"><i class="ph ph-arrows-clockwise text-sm"></i></button> <button id="gitea-refresh" class="toolbar-btn" data-tip="Refresh"><i class="ph ph-arrows-clockwise text-sm"></i></button>
</div> </div>
<!-- Pagination toolbar --> <!-- Pagination toolbar -->
<div class="flex items-center gap-2 px-3 py-1 border-t border-t-border"> <div class="flex items-center gap-2 px-3 py-1 border-t border-t-border">
<button id="gitea-page-prev" class="toolbar-btn" title="Previous page"><i class="ph ph-caret-left text-sm"></i></button> <button id="gitea-page-prev" class="toolbar-btn" data-tip="Previous page"><i class="ph ph-caret-left text-sm"></i></button>
<button id="gitea-page-next" class="toolbar-btn" title="Next page"><i class="ph ph-caret-right text-sm"></i></button> <button id="gitea-page-next" class="toolbar-btn" data-tip="Next page"><i class="ph ph-caret-right text-sm"></i></button>
<span id="gitea-page-info" class="font-pixel text-[9px] text-t-dim">—</span> <span id="gitea-page-info" class="font-pixel text-[9px] text-t-dim">—</span>
</div> </div>
</div> </div>
@@ -83,7 +83,7 @@
<i class="ph ph-plus-square text-t-accent text-lg"></i> <i class="ph ph-plus-square text-t-accent text-lg"></i>
<h3 class="text-t-bright text-base font-bold">new issue</h3> <h3 class="text-t-bright text-base font-bold">new issue</h3>
<div class="flex-1"></div> <div class="flex-1"></div>
<button id="gitea-create-cancel" class="toolbar-btn" title="Cancel"><i class="ph ph-x text-sm"></i></button> <button id="gitea-create-cancel" class="toolbar-btn" data-tip="Cancel"><i class="ph ph-x text-sm"></i></button>
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<label class="block relative"> <label class="block relative">
@@ -140,12 +140,12 @@
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div id="gitea-title-display" class="flex items-center gap-2"> <div id="gitea-title-display" class="flex items-center gap-2">
<h3 id="gitea-detail-title" class="text-t-bright text-base font-bold flex-1 break-words"></h3> <h3 id="gitea-detail-title" class="text-t-bright text-base font-bold flex-1 break-words"></h3>
<button id="gitea-title-edit-btn" class="toolbar-btn shrink-0" title="Edit title"><i class="ph ph-pencil-simple text-sm"></i></button> <button id="gitea-title-edit-btn" class="toolbar-btn shrink-0" data-tip="Edit title"><i class="ph ph-pencil-simple text-sm"></i></button>
</div> </div>
<div id="gitea-title-edit" class="hidden flex items-center gap-2"> <div id="gitea-title-edit" class="hidden flex items-center gap-2">
<input id="gitea-title-input" type="text" class="flex-1 bg-t-bg border border-t-accent text-t-bright px-2 py-1 text-sm focus:outline-none font-mono"> <input id="gitea-title-input" type="text" class="flex-1 bg-t-bg border border-t-accent text-t-bright px-2 py-1 text-sm focus:outline-none font-mono">
<button id="gitea-title-save" class="toolbar-btn" title="Save"><i class="ph ph-check text-sm text-t-accent"></i></button> <button id="gitea-title-save" class="toolbar-btn" data-tip="Save"><i class="ph ph-check text-sm text-t-accent"></i></button>
<button id="gitea-title-cancel" class="toolbar-btn" title="Cancel"><i class="ph ph-x text-sm text-t-danger"></i></button> <button id="gitea-title-cancel" class="toolbar-btn" data-tip="Cancel"><i class="ph ph-x text-sm text-t-danger"></i></button>
</div> </div>
</div> </div>
<span id="gitea-detail-number" class="font-pixel text-[10px] text-t-dim mt-1"></span> <span id="gitea-detail-number" class="font-pixel text-[10px] text-t-dim mt-1"></span>
@@ -158,7 +158,7 @@
</div> </div>
<div class="flex items-start gap-2 mb-2"> <div class="flex items-start gap-2 mb-2">
<div id="gitea-detail-labels" class="flex flex-wrap gap-1 flex-1"></div> <div id="gitea-detail-labels" class="flex flex-wrap gap-1 flex-1"></div>
<button id="gitea-labels-edit-btn" class="toolbar-btn shrink-0" title="Edit labels"><i class="ph ph-tag text-sm"></i></button> <button id="gitea-labels-edit-btn" class="toolbar-btn shrink-0" data-tip="Edit labels"><i class="ph ph-tag text-sm"></i></button>
</div> </div>
<div id="gitea-labels-editor" class="hidden border border-t-border bg-t-surface p-2 mb-2"> <div id="gitea-labels-editor" class="hidden border border-t-border bg-t-surface p-2 mb-2">
<div id="gitea-labels-warning" class="hidden mb-2 px-2 py-1 text-[10px] font-mono break-words border border-amber-500 text-amber-400 bg-t-bg"></div> <div id="gitea-labels-warning" class="hidden mb-2 px-2 py-1 text-[10px] font-mono break-words border border-amber-500 text-amber-400 bg-t-bg"></div>
@@ -171,7 +171,7 @@
<!-- Metadata row: assignees + milestone + due date with inline editor --> <!-- Metadata row: assignees + milestone + due date with inline editor -->
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<div id="gitea-detail-meta" class="flex flex-wrap gap-x-3 gap-y-1 flex-1 text-[10px] font-pixel text-t-dim"></div> <div id="gitea-detail-meta" class="flex flex-wrap gap-x-3 gap-y-1 flex-1 text-[10px] font-pixel text-t-dim"></div>
<button id="gitea-meta-edit-btn" class="toolbar-btn shrink-0" title="Edit assignees, milestone, due date"><i class="ph ph-sliders-horizontal text-sm"></i></button> <button id="gitea-meta-edit-btn" class="toolbar-btn shrink-0" data-tip="Edit assignees, milestone, due date"><i class="ph ph-sliders-horizontal text-sm"></i></button>
</div> </div>
<div id="gitea-meta-editor" class="hidden border border-t-border bg-t-surface p-3 mb-2"> <div id="gitea-meta-editor" class="hidden border border-t-border bg-t-surface p-3 mb-2">
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
@@ -192,7 +192,7 @@
<span class="font-pixel text-[9px] text-t-dim uppercase block mb-1"><i class="ph ph-calendar text-xs"></i> due date</span> <span class="font-pixel text-[9px] text-t-dim uppercase block mb-1"><i class="ph ph-calendar text-xs"></i> due date</span>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<input id="gitea-meta-due" type="date" class="flex-1 bg-t-bg border border-t-border text-t-bright px-2 py-1.5 text-xs focus:border-t-accent focus:outline-none font-mono"> <input id="gitea-meta-due" type="date" class="flex-1 bg-t-bg border border-t-border text-t-bright px-2 py-1.5 text-xs focus:border-t-accent focus:outline-none font-mono">
<button id="gitea-meta-due-clear" class="toolbar-btn" title="Clear due date"><i class="ph ph-x text-sm"></i></button> <button id="gitea-meta-due-clear" class="toolbar-btn" data-tip="Clear due date"><i class="ph ph-x text-sm"></i></button>
</div> </div>
</div> </div>
</div> </div>
@@ -213,7 +213,7 @@
<div class="flex-1 min-h-0 overflow-y-auto px-5 py-4"> <div class="flex-1 min-h-0 overflow-y-auto px-5 py-4">
<div id="gitea-body-display" class="flex items-start gap-2"> <div id="gitea-body-display" class="flex items-start gap-2">
<div id="gitea-detail-body" class="prose text-sm text-t-text flex-1 min-w-0"></div> <div id="gitea-detail-body" class="prose text-sm text-t-text flex-1 min-w-0"></div>
<button id="gitea-body-edit-btn" class="toolbar-btn shrink-0" title="Edit body"><i class="ph ph-pencil-simple text-sm"></i></button> <button id="gitea-body-edit-btn" class="toolbar-btn shrink-0" data-tip="Edit body"><i class="ph ph-pencil-simple text-sm"></i></button>
</div> </div>
<div id="gitea-body-edit" class="hidden"> <div id="gitea-body-edit" class="hidden">
<textarea id="gitea-body-input" class="w-full bg-t-bg border border-t-accent text-t-text px-3 py-2 text-xs focus:outline-none font-mono leading-relaxed" rows="10"></textarea> <textarea id="gitea-body-input" class="w-full bg-t-bg border border-t-accent text-t-text px-3 py-2 text-xs focus:outline-none font-mono leading-relaxed" rows="10"></textarea>
+3 -3
View File
@@ -157,9 +157,9 @@ function renderAccountsList() {
<div class="gitea-account-meta">${escapeHtml(a.base_url)}${a.default_repo ? ' · ' + escapeHtml(a.default_repo) : ''}</div> <div class="gitea-account-meta">${escapeHtml(a.base_url)}${a.default_repo ? ' · ' + escapeHtml(a.default_repo) : ''}</div>
</div> </div>
<div class="gitea-account-actions"> <div class="gitea-account-actions">
<button class="toolbar-btn gitea-edit-btn" title="Edit"><i class="ph ph-pencil-simple text-sm"></i></button> <button class="toolbar-btn gitea-edit-btn" data-tip="Edit"><i class="ph ph-pencil-simple text-sm"></i></button>
<button class="toolbar-btn gitea-test-btn" title="Test"><i class="ph ph-plug text-sm"></i></button> <button class="toolbar-btn gitea-test-btn" data-tip="Test"><i class="ph ph-plug text-sm"></i></button>
<button class="toolbar-btn gitea-del-btn" title="Delete"><i class="ph ph-trash text-sm"></i></button> <button class="toolbar-btn gitea-del-btn" data-tip="Delete"><i class="ph ph-trash text-sm"></i></button>
</div> </div>
</div> </div>
`).join(''); `).join('');
+3 -3
View File
@@ -28,11 +28,11 @@
<span class="font-pixel text-[10px]">image:</span> <span class="font-pixel text-[10px]">image:</span>
<input id="slideshow-ed-src" type="text" placeholder="/uploads/foo.png" class="bg-t-bg border border-t-border text-t-text text-xs font-mono" style="width:300px;padding:2px 8px;border-radius:3px;"> <input id="slideshow-ed-src" type="text" placeholder="/uploads/foo.png" class="bg-t-bg border border-t-border text-t-text text-xs font-mono" style="width:300px;padding:2px 8px;border-radius:3px;">
</label> </label>
<button id="slideshow-ed-gallery" class="toolbar-btn text-xs" title="Pick from gallery"><i class="ph ph-images text-sm"></i></button> <button id="slideshow-ed-gallery" class="toolbar-btn text-xs" data-tip="pick from gallery"><i class="ph ph-images text-sm"></i></button>
<button id="slideshow-ed-pick" class="toolbar-btn text-xs" title="Upload new image"><i class="ph ph-upload-simple text-sm"></i></button> <button id="slideshow-ed-pick" class="toolbar-btn text-xs" data-tip="upload new image"><i class="ph ph-upload-simple text-sm"></i></button>
<input type="file" id="slideshow-ed-file" accept="image/*" class="hidden"> <input type="file" id="slideshow-ed-file" accept="image/*" class="hidden">
<div class="w-px h-4 bg-t-border mx-1"></div> <div class="w-px h-4 bg-t-border mx-1"></div>
<button id="slideshow-ed-delete" class="toolbar-btn text-xs" title="Delete selected hotspot" disabled><i class="ph ph-trash text-sm"></i></button> <button id="slideshow-ed-delete" class="toolbar-btn text-xs" data-tip="delete selected hotspot" disabled><i class="ph ph-trash text-sm"></i></button>
<div class="flex-1"></div> <div class="flex-1"></div>
<span id="slideshow-ed-hint" class="font-pixel text-[9px] text-t-dim">drag on image to draw · click rect to select</span> <span id="slideshow-ed-hint" class="font-pixel text-[9px] text-t-dim">drag on image to draw · click rect to select</span>
</div> </div>
+1 -1
View File
@@ -187,7 +187,7 @@ function renderImageSlide(slide) {
const rects = slide.hotspots.map((h, i) => { const rects = slide.hotspots.map((h, i) => {
const style = `left:${h.x}%;top:${h.y}%;width:${h.w}%;height:${h.h}%`; const style = `left:${h.x}%;top:${h.y}%;width:${h.w}%;height:${h.h}%`;
const label = resolveTargetLabel(h.target) || h.target || ''; const label = resolveTargetLabel(h.target) || h.target || '';
return `<div class="slide-hotspot-live" data-hs="${i}" style="${style}" title="${escHtml(label)}"></div>`; return `<div class="slide-hotspot-live" data-hs="${i}" style="${style}" data-tip="${escHtml(label)}"></div>`;
}).join(''); }).join('');
viewerSlide.innerHTML = ` viewerSlide.innerHTML = `
<div class="slide-image-wrap"> <div class="slide-image-wrap">