diff --git a/app/static/css/admin.css b/app/static/css/admin.css index eda6411..a734317 100644 --- a/app/static/css/admin.css +++ b/app/static/css/admin.css @@ -12,7 +12,7 @@ .admin-subnav-spacer { flex:1; } .admin-subnav-form { margin:0; } .admin-subnav-logout { color:var(--ink-muted); font-weight:400; } -.admin-subnav-logout:hover { color:#8c2828; } +.admin-subnav-logout:hover { color:var(--danger); } /* tabs */ .admin-tabs { display:flex; border-bottom:2px solid var(--border); margin-bottom:20px; } @@ -34,23 +34,23 @@ /* badges */ .task-badge, .status-badge { display:inline-block; padding:2px 8px; border-radius:3px; font-size:.75rem; font-weight:500; white-space:nowrap; } -.task-crawl { background:#e3f2fd; color:#1565c0; } +.task-crawl { background:var(--info-bg); color:#1565c0; } .task-summarize { background:#f3e5f5; color:#7b1fa2; } -.task-cleanup { background:#e8f5e9; color:#2e7d32; } -.task-delete { background:#fce4ec; color:#c62828; } +.task-cleanup { background:var(--success-bg); color:#2e7d32; } +.task-delete { background:var(--danger-bg); color:var(--danger-bright); } .task-scheduler { background:#fff3e0; color:#e65100; } -.status-success { background:#e8f5e9; color:#388e3c; } -.status-running { background:#e3f2fd; color:#1976d2; } -.status-failed { background:#fce4ec; color:#c62828; } +.status-success { background:var(--success-bg); color:#388e3c; } +.status-running { background:var(--info-bg); color:#1976d2; } +.status-failed { background:var(--danger-bg); color:var(--danger-bright); } .time-cell { white-space:nowrap; color:var(--ink-light); } -.error-cell { max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:#c62828; font-size:.8rem; } +.error-cell { max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--danger-bright); font-size:.8rem; } /* action button */ .admin-action-btn { display:inline-flex; align-items:center; gap:6px; padding:8px 18px; background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); font-size:.85rem; font-weight:500; color:var(--ink); cursor:pointer; transition:all .2s; font-family:var(--font-sans); line-height:1.4; } .admin-action-btn:hover { border-color:var(--accent); color:var(--accent); box-shadow:0 2px 8px var(--shadow); } .admin-action-btn:active { transform:translateY(1px); box-shadow:none; } .admin-action-btn-sm { padding:5px 12px; font-size:.8rem; } -.admin-action-btn-danger:hover { border-color:#8c2828; color:#8c2828; } +.admin-action-btn-danger:hover { border-color:var(--danger); color:var(--danger); } /* checkbox */ .admin-check { appearance:none; -webkit-appearance:none; width:18px; height:18px; border:1.5px solid var(--border); border-radius:3px; background:var(--surface); cursor:pointer; vertical-align:middle; position:relative; transition:all .15s; } @@ -70,15 +70,15 @@ .confirm-btn { padding:8px 18px; border-radius:var(--radius); font-size:.85rem; font-weight:500; cursor:pointer; border:1px solid var(--border); font-family:var(--font-sans); transition:all .15s; } .confirm-btn-cancel { background:var(--surface); color:var(--ink-light); } .confirm-btn-cancel:hover { border-color:var(--ink-light); } -.confirm-btn-ok { background:#8c2828; color:#fff; border-color:#8c2828; } -.confirm-btn-ok:hover { background:#a13030; } +.confirm-btn-ok { background:var(--danger); color:#fff; border-color:var(--danger); } +.confirm-btn-ok:hover { background:var(--danger-hover); } /* ── Dashboard ────────────────────────────────────────────────── */ .stats-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:24px; } .stat-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:20px; text-align:center; } .stat-value { font-family:var(--font-body); font-size:2rem; font-weight:500; color:var(--accent); line-height:1.2; } -.stat-warn { color:#7a6430; } -.stat-danger { color:#8c2828; } +.stat-warn { color:var(--warning); } +.stat-danger { color:var(--danger); } .stat-label { font-size:.82rem; color:var(--ink-light); margin-top:4px; } .admin-quick-actions { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:24px; } .admin-info-grid { display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-bottom:24px; } @@ -89,7 +89,7 @@ .info-label { font-size:.85rem; color:var(--ink-light); min-width:72px; flex-shrink:0; } .info-value { font-size:.88rem; color:var(--ink); display:flex; align-items:center; gap:6px; } .status-dot { display:inline-block; width:8px; height:8px; border-radius:50%; } -.status-dot-on { background:#3d6e3d; } +.status-dot-on { background:var(--success); } .status-dot-off { background:var(--ink-muted); } .scheduler-history { margin-top:20px; padding-top:16px; border-top:1px solid var(--border); } .section-subtitle { font-size:.9rem; font-weight:500; color:var(--ink-light); margin-bottom:10px; } @@ -99,10 +99,10 @@ .dist-label { font-size:.8rem; color:var(--ink-light); min-width:60px; text-align:right; } .dist-bar-wrap { flex:1; height:16px; background:var(--bg); border-radius:4px; overflow:hidden; } .dist-bar { height:100%; border-radius:4px; min-width:2px; transition:width .3s; } -.dist-bar-done { background:#3d6e3d; } -.dist-bar-pending { background:#7a6430; } +.dist-bar-done { background:var(--success); } +.dist-bar-pending { background:var(--warning); } .dist-bar-running,.dist-bar-processing { background:var(--accent); } -.dist-bar-failed,.dist-bar-permanent_failure { background:#8c2828; } +.dist-bar-failed,.dist-bar-permanent_failure { background:var(--danger); } .dist-bar-none { background:var(--ink-muted); } .dist-count { font-size:.8rem; color:var(--ink); font-variant-numeric:tabular-nums; min-width:28px; } .admin-section { margin-top:24px; } @@ -111,15 +111,13 @@ /* ── Logs: Summary ────────────────────────────────────────────── */ .summary-filters { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-bottom:12px; } .summary-filter-label { font-size:.85rem; color:var(--ink-light); } -.summary-filters .filter-chip { padding:4px 10px; font-size:.8rem; background:var(--surface); border:1px solid var(--border); border-radius:4px; color:var(--ink-light); cursor:pointer; transition:all .2s; font-family:var(--font-sans); } -.summary-filters .filter-chip:hover { border-color:var(--accent); color:var(--accent); } -.summary-filters .filter-chip.active { background:var(--accent); color:#fff; border-color:var(--accent); } +.summary-filters .filter-chip { cursor:pointer; transition:all .2s; font-family:var(--font-sans); } .summary-stats-row { display:flex; gap:16px; margin-bottom:16px; flex-wrap:wrap; } .summary-stat { font-size:.85rem; color:var(--ink-light); } .summary-stat strong { font-variant-numeric:tabular-nums; } -.summary-stat-pending strong { color:#7a6430; } -.summary-stat-failed strong { color:#8c2828; } -.summary-stat-done strong { color:#3d6e3d; } +.summary-stat-pending strong { color:var(--warning); } +.summary-stat-failed strong { color:var(--danger); } +.summary-stat-done strong { color:var(--success); } .summary-table td.title-cell { max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .retry-btn { padding:3px 10px; font-size:.75rem; background:var(--surface); border:1px solid var(--border); border-radius:4px; color:var(--accent); cursor:pointer; transition:all .2s; font-family:var(--font-sans); } .retry-btn:hover { border-color:var(--accent); background:var(--accent); color:#fff; } @@ -133,9 +131,7 @@ .paper-search-form { margin-bottom:16px; } .paper-search-row { display:flex; gap:8px; flex-wrap:wrap; align-items:center; } .paper-search-input { flex:1; min-width:200px; padding:8px 14px; border:1px solid var(--border); border-radius:var(--radius); font-size:.85rem; font-family:var(--font-sans); background:var(--surface); color:var(--ink); } -.paper-search-input:focus { outline:none; border-color:var(--accent); } .paper-filter-input { padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius); font-size:.82rem; font-family:var(--font-sans); background:var(--surface); color:var(--ink); } -.paper-filter-input:focus { outline:none; border-color:var(--accent); } .paper-search-btn { padding:8px 18px; background:var(--accent); color:#fff; border:none; border-radius:var(--radius); font-size:.85rem; font-weight:500; cursor:pointer; font-family:var(--font-sans); transition:background .2s; } .paper-search-btn:hover { background:var(--accent-hover); } .paper-batch-bar { display:flex; align-items:center; gap:12px; padding:10px 0; margin-bottom:8px; border-bottom:1px solid var(--border); } @@ -148,7 +144,7 @@ .action-cell { white-space:nowrap; } .action-btn-sm { display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px; background:var(--surface); border:1px solid var(--border); border-radius:4px; font-size:.85rem; color:var(--ink-light); cursor:pointer; transition:all .15s; padding:0; vertical-align:middle; } .action-btn-sm:hover { border-color:var(--accent); color:var(--accent); } -.action-btn-danger:hover { border-color:#8c2828; color:#8c2828; } +.action-btn-danger:hover { border-color:var(--danger); color:var(--danger); } /* ── Responsive ────────────────────────────────────────────────── */ @media (max-width:880px) { .stats-grid{grid-template-columns:repeat(2,1fr);} .admin-info-grid{grid-template-columns:1fr;} } diff --git a/app/static/css/style.css b/app/static/css/style.css index 5a5d2e2..49adc1d 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -14,6 +14,18 @@ --border-soft: #e5e3d8; /* soft row separator */ --shadow: rgba(0, 0, 0, 0.05); /* whisper shadow */ --radius: 8px; + --radius-lg: 12px; + + /* 语义色 */ + --success: var(--success); + --success-bg: #e8f5e9; + --danger: var(--danger); + --danger-bright: var(--danger-bright); + --danger-bg: var(--danger-bg); + --danger-hover: #a13030; + --warning: #7a6430; + --tag-bg: var(--tag-bg); + --info-bg: #e3f2fd; /* 字体 — Kami serif-first */ --font-body: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif; @@ -125,7 +137,7 @@ a:hover { text-decoration: none; } -/* ── Date Chips ─────────────────────────────────────────────────── */ +/* ── Date Quick Nav ─────────────────────────────────────────────── */ .date-quick-nav { margin-top: 32px; padding-top: 16px; @@ -138,7 +150,11 @@ a:hover { flex-wrap: wrap; } -.date-chip { +/* ── Chips (shared) ─────────────────────────────────────────────── */ +.date-chip, +.tag-chip, +.filter-chip { + display: inline-block; padding: 4px 10px; background: var(--surface); border: 1px solid var(--border); @@ -146,16 +162,25 @@ a:hover { font-size: 0.8rem; color: var(--ink-light); } -.date-chip:hover { +.date-chip:hover, +.tag-chip:hover, +.filter-chip:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; } -.date-chip.active { +.date-chip.active, +.tag-chip.active, +.filter-chip.active { background: var(--accent); color: #fff; border-color: var(--accent); } +.filter-chip { + padding: 6px 14px; + border-radius: var(--radius); + font-size: 0.85rem; +} /* ── Paper Card ─────────────────────────────────────────────────── */ .paper-list { @@ -227,7 +252,7 @@ a:hover { .tag { display: inline-block; padding: 1px 5px; - background: #EEF2F7; + background: var(--tag-bg); color: var(--accent); border-radius: 2px; font-size: 0.75rem; @@ -262,12 +287,12 @@ a:hover { } .summary-done { background: rgba(27, 54, 93, 0.08); - color: #3d6e3d; + color: var(--success); } .summary-failed, .summary-permanent_failure { background: rgba(140, 40, 40, 0.08); - color: #8c2828; + color: var(--danger); } .btn-detail { @@ -408,7 +433,7 @@ a:hover { } .error-detail { font-size: 0.85rem; - color: #8c2828; + color: var(--danger); margin-top: 8px; } @@ -451,7 +476,11 @@ a:hover { transition: border-color 0.2s; font-family: var(--font-sans); } -.nav-search-input:focus { +.nav-search-input:focus, +.search-input:focus, +.paper-search-input:focus, +.paper-filter-input:focus, +.login-field input:focus { outline: none; border-color: var(--accent); } @@ -474,9 +503,7 @@ a:hover { color: var(--ink); } .search-input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(45, 95, 138, 0.1); + box-shadow: 0 0 0 3px rgba(27, 54, 93, 0.1); } .search-btn { @@ -506,25 +533,7 @@ a:hover { font-size: 0.85rem; color: var(--ink-light); } -.tag-chip { - display: inline-block; - padding: 4px 10px; - background: var(--surface); - border: 1px solid var(--border); - border-radius: 4px; - font-size: 0.8rem; - color: var(--ink-light); -} -.tag-chip:hover { - border-color: var(--accent); - color: var(--accent); - text-decoration: none; -} -.tag-chip.active { - background: var(--accent); - color: #fff; - border-color: var(--accent); -} +/* .tag-chip: shared chip styles (see Chips section) */ /* ── Search Meta & Sort ─────────────────────────────────────────── */ .search-meta { @@ -614,25 +623,7 @@ mark { margin-bottom: 16px; } -.filter-chip { - display: inline-block; - padding: 6px 14px; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius); - font-size: 0.85rem; - color: var(--ink-light); -} -.filter-chip:hover { - border-color: var(--accent); - color: var(--accent); - text-decoration: none; -} -.filter-chip.active { - background: var(--accent); - color: #fff; - border-color: var(--accent); -} +/* .filter-chip: shared chip styles (see Chips section) */ /* ── Paper Card Footer (enhanced) ──────────────────────────────── */ .paper-footer { @@ -688,11 +679,11 @@ mark { } .reading-read_summary { background: rgba(27, 54, 93, 0.06); - color: #3d6e3d; + color: var(--success); } .reading-read_full { background: rgba(27, 54, 93, 0.10); - color: #3d6e3d; + color: var(--success); font-weight: 500; } @@ -786,7 +777,7 @@ mark { color: var(--accent); white-space: nowrap; padding: 2px 8px; - background: #EEF2F7; + background: var(--tag-bg); border-radius: 4px; font-variant-numeric: tabular-nums; } @@ -1019,31 +1010,64 @@ mark { /* ── 图片灯箱 ── */ .lightbox-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 9999; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; + z-index: 99999 !important; background: rgba(0, 0, 0, 0.85); - display: flex; - align-items: center; - justify-content: center; - cursor: zoom-out; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; opacity: 0; - visibility: hidden; - transition: opacity 0.2s, visibility 0.2s; + transition: opacity 0.2s; } .lightbox-overlay.active { opacity: 1; - visibility: visible; } .lightbox-overlay img { - max-width: 95vw; - max-height: 95vh; - object-fit: contain; + position: absolute; + transform-origin: 0 0; border-radius: 4px; box-shadow: 0 0 40px rgba(0, 0, 0, 0.4); + cursor: grab; + user-select: none; + -webkit-user-drag: none; +} +.lightbox-overlay img.dragging { + cursor: grabbing; +} +.lightbox-toolbar { + position: absolute; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + background: rgba(0, 0, 0, 0.6); + padding: 8px 14px; + border-radius: 24px; + z-index: 100000; +} +.lightbox-toolbar button { + background: none; + border: 1px solid rgba(255, 255, 255, 0.3); + color: #fff; + width: 36px; + height: 36px; + border-radius: 50%; + font-size: 1.1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s; +} +.lightbox-toolbar button:hover { + background: rgba(255, 255, 255, 0.15); } /* ── Benchmark 表格 ── */ @@ -1065,7 +1089,7 @@ mark { border-bottom: 1px solid var(--border); } .benchmarks-table .improvement { - color: #3d6e3d; + color: var(--success); font-weight: 500; } @@ -1114,8 +1138,8 @@ mark { } .login-error { - background: #fce4ec; - color: #c62828; + background: var(--danger-bg); + color: var(--danger-bright); padding: 10px 14px; border-radius: var(--radius); font-size: 0.85rem; @@ -1151,8 +1175,6 @@ mark { } .login-field input:focus { - outline: none; - border-color: var(--accent); box-shadow: 0 0 0 3px rgba(27, 54, 93, 0.1); } @@ -1180,3 +1202,179 @@ mark { padding: 28px 20px; } } + +/* ── Kami Date Picker ──────────────────────────────────────────── */ +.kami-date-picker-wrapper { + position: relative; + display: inline-block; +} + +.kami-date-popup { + position: absolute; + top: calc(100% + 6px); + left: 50%; + transform: translateX(-50%) translateY(-4px); + z-index: 200; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 4px 24px var(--shadow); + padding: 16px; + min-width: 280px; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s, transform 0.15s, visibility 0.15s; +} +.kami-date-popup.open { + opacity: 1; + visibility: visible; + transform: translateX(-50%) translateY(0); +} + +.kami-date-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + font-family: var(--font-body); + font-size: 0.95rem; + font-weight: 500; + color: var(--ink); +} +.kami-date-header button { + width: 28px; + height: 28px; + background: none; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + color: var(--ink-light); + font-size: 1rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; + padding: 0; +} +.kami-date-header button:hover { + border-color: var(--accent); + color: var(--accent); +} + +.kami-date-weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; + margin-bottom: 4px; +} +.kami-date-weekdays span { + text-align: center; + font-size: 0.75rem; + color: var(--ink-muted); + font-weight: 500; + padding: 4px 0; +} + +.kami-date-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 2px; +} +.kami-date-day { + position: relative; + text-align: center; + padding: 6px 0; + font-size: 0.82rem; + border-radius: 4px; + cursor: pointer; + color: var(--ink); + transition: background 0.15s, color 0.15s; + user-select: none; +} +.kami-date-day:hover { + background: var(--accent-bg); +} +.kami-date-day.muted { + color: var(--ink-muted); + opacity: 0.35; + cursor: default; +} +.kami-date-day.muted:hover { + background: none; +} +.kami-date-day.today { + font-weight: 600; + box-shadow: inset 0 0 0 1px var(--accent); +} +.kami-date-day.selected { + background: var(--accent); + color: #fff; + font-weight: 500; +} +.kami-date-day.selected:hover { + background: var(--accent-hover); +} +.kami-date-day.disabled { + color: var(--ink-muted); + opacity: 0.3; + cursor: not-allowed; +} +.kami-date-day.disabled:hover { + background: none; +} + +/* 小圆点 — 标记有数据的日期 */ +.kami-date-day.marked::after { + content: ''; + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--accent); +} +.kami-date-day.selected.marked::after { + background: #fff; +} + +.kami-date-footer { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); + text-align: center; +} +.kami-date-footer a { + font-size: 0.8rem; + color: var(--ink-muted); +} +.kami-date-footer a:hover { + color: var(--accent); + text-decoration: none; +} + +/* date-title 上的日历小图标 */ +.date-picker-icon { + font-size: 0.85rem; + margin-left: 4px; + opacity: 0.45; + vertical-align: middle; +} + +/* 管理后台日期输入框样式 */ +.kami-date-input { + cursor: pointer; + width: 110px; +} +.kami-date-input::placeholder { + color: var(--ink-muted); +} + +/* 移动端响应式 */ +@media (max-width: 480px) { + .kami-date-popup { + min-width: 260px; + } +} diff --git a/app/static/js/date-picker.js b/app/static/js/date-picker.js new file mode 100644 index 0000000..97b304e --- /dev/null +++ b/app/static/js/date-picker.js @@ -0,0 +1,291 @@ +/** + * KamiDatePicker — 轻量日历弹窗,kami 暖纸风格。 + * + * 用法: + * new KamiDatePicker(triggerEl, { + * value: '2025-06-09', // 初始日期 + * maxDate: '2025-06-09', // 最晚可选 + * minDate: '2024-01-01', // 最早可选 + * markedDates: ['2025-06-08'],// 有数据的日期(小圆点) + * onChange: function(dateStr) {}, // 选择回调 + * }); + */ +;(function () { + 'use strict'; + + var INSTANCES = []; + + var WEEKDAYS = ['日', '一', '二', '三', '四', '五', '六']; + + function pad(n) { + return n < 10 ? '0' + n : '' + n; + } + function isoDate(y, m, d) { + return y + '-' + pad(m + 1) + '-' + pad(d); + } + function parseDate(s) { + if (!s) return null; + var parts = s.split('-'); + if (parts.length !== 3) return null; + return new Date(+parts[0], +parts[1] - 1, +parts[2]); + } + function sameDay(a, b) { + return a && b && a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); + } + + function KamiDatePicker(triggerEl, opts) { + opts = opts || {}; + this.trigger = triggerEl; + this.value = opts.value || null; + this.maxDate = opts.maxDate ? parseDate(opts.maxDate) : null; + this.minDate = opts.minDate ? parseDate(opts.minDate) : null; + this.markedSet = {}; + if (opts.markedDates) { + for (var i = 0; i < opts.markedDates.length; i++) { + this.markedSet[opts.markedDates[i]] = true; + } + } + this.onChange = opts.onChange || function () {}; + this.opened = false; + + // view state + var initial = parseDate(this.value) || new Date(); + this.viewYear = initial.getFullYear(); + this.viewMonth = initial.getMonth(); + + this._build(); + this._bind(); + INSTANCES.push(this); + } + + KamiDatePicker.prototype._build = function () { + // wrapper + this.wrapper = document.createElement('div'); + this.wrapper.className = 'kami-date-picker-wrapper'; + this.trigger.parentNode.insertBefore(this.wrapper, this.trigger); + this.wrapper.appendChild(this.trigger); + + // popup + this.popup = document.createElement('div'); + this.popup.className = 'kami-date-popup'; + this.popup.innerHTML = this._renderHTML(); + this.wrapper.appendChild(this.popup); + + // make trigger look clickable + this.trigger.style.cursor = 'pointer'; + }; + + KamiDatePicker.prototype._bind = function () { + var self = this; + + // toggle on trigger click + this._triggerHandler = function (e) { + e.preventDefault(); + e.stopPropagation(); + self.toggle(); + }; + this.trigger.addEventListener('click', this._triggerHandler); + + // popup internal clicks + this.popup.addEventListener('click', function (e) { + var target = e.target; + + // prev month + if (target.closest('.kami-date-prev')) { + e.stopPropagation(); + self.viewMonth--; + if (self.viewMonth < 0) { self.viewMonth = 11; self.viewYear--; } + self._update(); + return; + } + // next month + if (target.closest('.kami-date-next')) { + e.stopPropagation(); + self.viewMonth++; + if (self.viewMonth > 11) { self.viewMonth = 0; self.viewYear++; } + self._update(); + return; + } + // today link + if (target.closest('.kami-date-today-link')) { + e.stopPropagation(); + self.select(new Date()); + return; + } + // day cell + var dayEl = target.closest('.kami-date-day'); + if (dayEl && !dayEl.classList.contains('muted')) { + e.stopPropagation(); + var y = +dayEl.dataset.year; + var m = +dayEl.dataset.month; + var d = +dayEl.dataset.day; + self.select(new Date(y, m, d)); + } + }); + }; + + KamiDatePicker.prototype._renderHTML = function () { + var y = this.viewYear; + var m = this.viewMonth; + var today = new Date(); + var selected = parseDate(this.value); + + var html = ''; + + // header + html += '