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:
1 parent
527305355c
commit
f465306c1d
21 files changed
+229
-109
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
define('APP_NAME', 'hyprcrd');
|
||||
define('APP_VERSION', '1.3.0');
|
||||
define('APP_VERSION', '1.3.1');
|
||||
|
||||
// Database path — writable directory, outside webroot
|
||||
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hyprcrd",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"description": "hyprcrd — self-hosted card manager",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
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; }
|
||||
.brand { font-family: 'Silkscreen', cursive !important; }
|
||||
body.no-scanlines::after { display: none !important; }
|
||||
|
||||
+36
-36
@@ -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>
|
||||
<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 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>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1256,19 +1256,19 @@ if (str_starts_with($route, '/api/')) {
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</button>
|
||||
<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-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>
|
||||
<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 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">
|
||||
@@ -1693,7 +1693,7 @@ if (str_starts_with($route, '/api/')) {
|
||||
<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>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>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>
|
||||
@@ -1935,7 +1935,7 @@ if (str_starts_with($route, '/api/')) {
|
||||
<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">
|
||||
<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>
|
||||
</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">
|
||||
@@ -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">
|
||||
<i class="ph ph-trash text-sm"></i><span class="font-pixel text-[10px]">Del</span>
|
||||
</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>
|
||||
</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>
|
||||
</button>
|
||||
<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>
|
||||
</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">
|
||||
@@ -2170,10 +2170,10 @@ if (str_starts_with($route, '/api/')) {
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
</button>
|
||||
<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">
|
||||
</div>
|
||||
<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="bold" title="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="strikethrough" title="Strikethrough" class="toolbar-btn"><i class="ph ph-text-strikethrough 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" data-tip="bold" class="toolbar-btn"><i class="ph ph-text-bolder 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" 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>
|
||||
<button data-md="code" title="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="code" data-tip="code" class="toolbar-btn"><i class="ph ph-code 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>
|
||||
<button data-md="ul" title="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="ol" title="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="todo" title="Todo" class="toolbar-btn"><i class="ph ph-check-square 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" data-tip="tree list" class="toolbar-btn"><i class="ph ph-tree-structure 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" data-tip="quote" class="toolbar-btn"><i class="ph ph-quotes 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>
|
||||
<button data-md="link" title="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="hr" title="Divider" class="toolbar-btn"><i class="ph ph-minus 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" data-tip="card ref [[" class="toolbar-btn"><i class="ph ph-arrow-square-out 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>
|
||||
<button data-md="extract" title="Extract selection to new card" 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 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" 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">
|
||||
<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>
|
||||
<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') ?>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
<button id="find-prev" class="toolbar-btn" title="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-close" class="toolbar-btn" title="close"><i class="ph ph-x 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" data-tip="next"><i class="ph ph-caret-down 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 id="replace-row" class="flex items-center gap-2">
|
||||
<i class="ph ph-swap text-xs text-t-dim"></i>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { openModal, closeModal, saveCard, saveOnly, openFindBar } from './module
|
||||
import { openSearch, closeSearch } from './modules/search.js';
|
||||
import { showContextMenu } from './modules/context-menu.js';
|
||||
import { undo, canUndo } from './modules/undo.js';
|
||||
import './modules/tooltip.js';
|
||||
import { registerShortcut, guards, renderShortcutHelp } from './modules/shortcuts.js';
|
||||
import { openGallery, closeGallery } from './modules/gallery.js';
|
||||
import { refreshTasksView, openTasks, closeTasks } from './modules/tasks.js';
|
||||
|
||||
@@ -294,11 +294,11 @@ function renderBar() {
|
||||
// render (e.g. bucket reorder, plain renderBar() call).
|
||||
const allUnread = state.activeBucket !== 'all' && state.unseenCardIds.size > 0;
|
||||
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 === 'current')}" data-bucket="current" title="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 === 'journal')}" data-bucket="journal" title="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 === '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" data-tip="current"><i class="ph ph-lightning 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" data-tip="journal"><i class="ph ph-notebook 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 => {
|
||||
const kids = childrenOf(b.id);
|
||||
const isSuper = kids.length > 0;
|
||||
@@ -400,7 +400,7 @@ function renderBar() {
|
||||
const c = m.color || '#64748b';
|
||||
const active = state.activeBucket == m.id || (!members.some(x => x.id == state.activeBucket) && lastId == m.id);
|
||||
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('');
|
||||
// Append inside #bucket-bar (not document.body) so the collab
|
||||
|
||||
@@ -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('')}
|
||||
</div>
|
||||
<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>`;
|
||||
}).join('');
|
||||
|
||||
@@ -433,7 +433,7 @@ function renderSidebar(card) {
|
||||
<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>` : ''}
|
||||
<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>
|
||||
</button>
|
||||
</div>`;
|
||||
@@ -554,7 +554,7 @@ function applyReadingResizeHandles(root) {
|
||||
const line = document.createElement('button');
|
||||
line.type = 'button';
|
||||
line.className = 'detail-font-dragline';
|
||||
line.title = 'drag to resize text';
|
||||
line.dataset.tip = 'drag to resize text';
|
||||
line.setAttribute('aria-label', 'resize text');
|
||||
el.appendChild(line);
|
||||
|
||||
@@ -615,7 +615,7 @@ export function bindDetailImageFullscreen(root) {
|
||||
// Regular images: double-click, no gallery nav
|
||||
root.querySelectorAll('img').forEach(img => {
|
||||
if (img.closest('.md-gallery')) return;
|
||||
img.title = 'double-click to open fullscreen';
|
||||
img.dataset.tip = 'double-click to fullscreen';
|
||||
img.addEventListener('dblclick', e => {
|
||||
e.preventDefault();
|
||||
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">
|
||||
<i class="ph ph-arrow-left text-sm"></i><span class="font-pixel text-[10px]">${splitLastQuery ? 'results' : 'related cards'}</span>
|
||||
</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>
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
@@ -102,7 +102,7 @@ function startCloseMorph() {
|
||||
|
||||
CARD_COLORS.forEach(c => {
|
||||
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.className = 'w-5 h-5 rounded-sm border-2 border-transparent transition-all hover:scale-125 color-swatch';
|
||||
btn.style.background = c.hex;
|
||||
@@ -1243,13 +1243,13 @@ $('md-asset-picker')?.addEventListener('click', async () => {
|
||||
const type = assetType(f.name);
|
||||
const label = f.name.replace(/\.[^.]+$/, '');
|
||||
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">
|
||||
</div>`;
|
||||
}
|
||||
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)';
|
||||
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>
|
||||
<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>`;
|
||||
|
||||
@@ -170,9 +170,9 @@ export async function openGallery() {
|
||||
<div class="relative">
|
||||
${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">
|
||||
<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-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-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-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}" 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}" data-tip="delete"><i class="ph ph-trash text-sm"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 font-pixel text-[8px] text-t-dim truncate">${esc(f.name)}</div>
|
||||
|
||||
@@ -146,7 +146,7 @@ export function renderMd(src) {
|
||||
const linked = processed.replace(/\[\[([^\]]+)\]\]/g, (_, title) => {
|
||||
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>`;
|
||||
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);
|
||||
|
||||
@@ -73,7 +73,7 @@ export function render() {
|
||||
<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>` : ''}
|
||||
</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>`;
|
||||
|
||||
banner.querySelectorAll('[data-notif-id]').forEach(el => {
|
||||
|
||||
@@ -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);
|
||||
@@ -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;">
|
||||
<!-- 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="eraser" class="toolbar-btn" title="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="rect" class="toolbar-btn" title="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="fill" class="toolbar-btn" title="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="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" data-tip="Eraser (E)"><i class="ph ph-eraser 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" data-tip="Rectangle (R)"><i class="ph ph-rectangle 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" data-tip="Fill (F)"><i class="ph ph-paint-bucket 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>
|
||||
<label class="flex items-center gap-1 text-t-muted text-xs">
|
||||
<span>Char:</span>
|
||||
@@ -24,9 +24,9 @@
|
||||
<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">
|
||||
</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>
|
||||
<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>
|
||||
<span id="ascii-canvas-coords" class="text-t-dim text-xs font-mono">1:1</span>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<div id="chat-flyout-header">
|
||||
<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>
|
||||
<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-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-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" 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>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div id="chat-thread-messages" class="chat-thread-messages"></div>
|
||||
<form id="chat-thread-form">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<i class="ph ph-envelope text-t-accent text-base"></i>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -41,11 +41,11 @@
|
||||
<div class="sticky top-0 z-20 bg-t-bg">
|
||||
<!-- 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">
|
||||
<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-next" class="toolbar-btn" title="Next page"><i class="ph ph-caret-right 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" 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>
|
||||
<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>
|
||||
<span class="font-pixel text-[9px]">select spam (<span id="email-spam-count">0</span>)</span>
|
||||
</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">
|
||||
<span id="email-selection-count" class="font-pixel text-[10px] text-t-accent">0 selected</span>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</button>
|
||||
</div>
|
||||
@@ -91,9 +91,9 @@
|
||||
</div>
|
||||
<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-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-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-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-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" 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" 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -218,9 +218,9 @@ function renderAccountsList() {
|
||||
<div class="email-account-meta">${escapeHtml(a.username || '')} @ ${escapeHtml(a.host)}:${a.port} · ${escapeHtml(a.encryption)}</div>
|
||||
</div>
|
||||
<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-test-btn" title="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-edit-btn" data-tip="edit"><i class="ph ph-pencil-simple 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" data-tip="delete"><i class="ph ph-trash text-sm"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
@@ -452,13 +452,13 @@ function renderMessageList() {
|
||||
const spamScore = m.spam?.score ?? 0;
|
||||
const spamReasons = (m.spam?.reasons || []).join(' · ');
|
||||
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'
|
||||
? `<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}` : '';
|
||||
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">
|
||||
<div class="email-msg-content">
|
||||
<div class="flex items-baseline justify-between gap-2">
|
||||
|
||||
@@ -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>
|
||||
<span id="gitea-status" class="text-[10px] text-t-dim font-pixel"></span>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -40,25 +40,25 @@
|
||||
<div class="sticky top-0 z-20 bg-t-bg border-b border-t-border">
|
||||
<!-- Filters bar -->
|
||||
<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="closed">closed</option>
|
||||
<option value="all">all</option>
|
||||
</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="assigned">assigned to me</option>
|
||||
<option value="created">created by me</option>
|
||||
<option value="mentioned">mentioning me</option>
|
||||
</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">
|
||||
<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-refresh" class="toolbar-btn" title="Refresh"><i class="ph ph-arrows-clockwise text-sm"></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" data-tip="Refresh"><i class="ph ph-arrows-clockwise text-sm"></i></button>
|
||||
</div>
|
||||
<!-- Pagination toolbar -->
|
||||
<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-next" class="toolbar-btn" title="Next page"><i class="ph ph-caret-right 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" 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
<i class="ph ph-plus-square text-t-accent text-lg"></i>
|
||||
<h3 class="text-t-bright text-base font-bold">new issue</h3>
|
||||
<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 class="space-y-2">
|
||||
<label class="block relative">
|
||||
@@ -140,12 +140,12 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<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>
|
||||
<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 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">
|
||||
<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-cancel" class="toolbar-btn" title="Cancel"><i class="ph ph-x text-sm text-t-danger"></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" data-tip="Cancel"><i class="ph ph-x text-sm text-t-danger"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<span id="gitea-detail-number" class="font-pixel text-[10px] text-t-dim mt-1"></span>
|
||||
@@ -158,7 +158,7 @@
|
||||
</div>
|
||||
<div class="flex items-start gap-2 mb-2">
|
||||
<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 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>
|
||||
@@ -171,7 +171,7 @@
|
||||
<!-- Metadata row: assignees + milestone + due date with inline editor -->
|
||||
<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>
|
||||
<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 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">
|
||||
@@ -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>
|
||||
<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">
|
||||
<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>
|
||||
@@ -213,7 +213,7 @@
|
||||
<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-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 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>
|
||||
|
||||
@@ -157,9 +157,9 @@ function renderAccountsList() {
|
||||
<div class="gitea-account-meta">${escapeHtml(a.base_url)}${a.default_repo ? ' · ' + escapeHtml(a.default_repo) : ''}</div>
|
||||
</div>
|
||||
<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-test-btn" title="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-edit-btn" data-tip="Edit"><i class="ph ph-pencil-simple 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" data-tip="Delete"><i class="ph ph-trash text-sm"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
<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;">
|
||||
</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-pick" class="toolbar-btn text-xs" title="Upload new image"><i class="ph ph-upload-simple 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" 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">
|
||||
<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>
|
||||
<span id="slideshow-ed-hint" class="font-pixel text-[9px] text-t-dim">drag on image to draw · click rect to select</span>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +187,7 @@ function renderImageSlide(slide) {
|
||||
const rects = slide.hotspots.map((h, i) => {
|
||||
const style = `left:${h.x}%;top:${h.y}%;width:${h.w}%;height:${h.h}%`;
|
||||
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('');
|
||||
viewerSlide.innerHTML = `
|
||||
<div class="slide-image-wrap">
|
||||
|
||||
Reference in new issue
Block a user