- 新增多语言国际化文案支持笔记仓库模块 - 配置Universal Links与App Links支持ctc.s2ss.com域名跳转 - 实现CTS会话入口与会话时间更新逻辑 - 新增CTC笔记完整服务栈:API客户端、本地存储、同步服务 - 新增笔记编辑、预览、冲突解决、版本对比组件 - 新增二维码扫码/分享功能与路由配置 - 修复UrlAnalyzerService调用参数冗余问题 - 修复ProfileHeader组件样式问题 - 统一macOS部署目标版本为13.0 - 抑制liquid_glass_widgets高频调试日志
1270 lines
83 KiB
HTML
1270 lines
83 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>笔记仓库 - 预览方案 v2</title>
|
||
<style>
|
||
/* ========== Design System Tokens ========== */
|
||
:root {
|
||
--primary: #6C5CE7;
|
||
--primary-light: #A29BFE;
|
||
--primary-dark: #5A4BD1;
|
||
--secondary: #00CEC9;
|
||
--accent: #6C5CE7;
|
||
--accent-light: rgba(108, 92, 231, 0.12);
|
||
--bg-primary: #F2F2F7;
|
||
--bg-secondary: #FFFFFF;
|
||
--bg-tertiary: #F2F2F7;
|
||
--bg-grouped: #F2F2F7;
|
||
--bg-card: #FFFFFF;
|
||
--text-primary: #1C1C1E;
|
||
--text-secondary: #8E8E93;
|
||
--text-tertiary: #AEAEB2;
|
||
--text-link: #6C5CE7;
|
||
--separator: rgba(60, 60, 67, 0.12);
|
||
--border: rgba(60, 60, 67, 0.12);
|
||
--destructive: #FF3B30;
|
||
--success: #34C759;
|
||
--warning: #FF9500;
|
||
--overlay: rgba(0, 0, 0, 0.4);
|
||
--glass-bg: rgba(255, 255, 255, 0.72);
|
||
--glass-blur: 20px;
|
||
--space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
|
||
--space-5: 20px; --space-6: 24px; --space-8: 32px;
|
||
--radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; --radius-full: 9999px;
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
|
||
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
|
||
--shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
|
||
--shadow-float: 0 12px 40px rgba(0,0,0,0.15);
|
||
--font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif;
|
||
--font-title: 600 17px var(--font-family);
|
||
--font-large: 500 22px var(--font-family);
|
||
--font-body: 400 15px var(--font-family);
|
||
--font-caption: 400 13px var(--font-family);
|
||
--font-micro: 400 11px var(--font-family);
|
||
--transition-fast: 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||
--transition-normal: 0.35s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||
--transition-spring: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
[data-theme="dark"] {
|
||
--primary: #A29BFE; --primary-light: rgba(162,155,254,0.15); --primary-dark: #6C5CE7;
|
||
--bg-primary: #000000; --bg-secondary: #1C1C1E; --bg-tertiary: #2C2C2E;
|
||
--bg-grouped: #000000; --bg-card: #1C1C1E;
|
||
--text-primary: #FFFFFF; --text-secondary: #98989D; --text-tertiary: #636366; --text-link: #A29BFE;
|
||
--separator: rgba(84,84,88,0.65); --border: rgba(84,84,88,0.65);
|
||
--overlay: rgba(0,0,0,0.6); --glass-bg: rgba(28,28,30,0.78);
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3); --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
||
--shadow-lg: 0 8px 30px rgba(0,0,0,0.5); --shadow-float: 0 12px 40px rgba(0,0,0,0.6);
|
||
}
|
||
[data-theme="amoled"] {
|
||
--primary: #A29BFE; --primary-light: rgba(162,155,254,0.12); --primary-dark: #6C5CE7;
|
||
--bg-primary: #000000; --bg-secondary: #0A0A0A; --bg-tertiary: #141414;
|
||
--bg-grouped: #000000; --bg-card: #0A0A0A;
|
||
--text-primary: #F5F5F5; --text-secondary: #8A8A8A; --text-tertiary: #555555; --text-link: #A29BFE;
|
||
--separator: rgba(255,255,255,0.06); --border: rgba(255,255,255,0.06);
|
||
--overlay: rgba(0,0,0,0.7); --glass-bg: rgba(10,10,10,0.85);
|
||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.5); --shadow-md: 0 4px 10px rgba(0,0,0,0.6);
|
||
--shadow-lg: 0 8px 25px rgba(0,0,0,0.7); --shadow-float: 0 12px 35px rgba(0,0,0,0.8);
|
||
}
|
||
[data-accent="coral"] { --primary: #FF6B6B; --primary-light: rgba(255,107,107,0.12); --primary-dark: #E55555; --text-link: #FF6B6B; }
|
||
[data-accent="teal"] { --primary: #00B894; --primary-light: rgba(0,184,148,0.12); --primary-dark: #00A383; --text-link: #00B894; }
|
||
[data-accent="blue"] { --primary: #0984E3; --primary-light: rgba(9,132,227,0.12); --primary-dark: #0770C4; --text-link: #0984E3; }
|
||
[data-accent="orange"] { --primary: #E17055; --primary-light: rgba(225,112,85,0.12); --primary-dark: #C95A3F; --text-link: #E17055; }
|
||
[data-accent="pink"] { --primary: #FD79A8; --primary-light: rgba(253,121,168,0.12); --primary-dark: #E4608F; --text-link: #FD79A8; }
|
||
[data-accent="green"] { --primary: #00B894; --primary-light: rgba(0,184,148,0.12); --primary-dark: #009B7D; --text-link: #00B894; }
|
||
|
||
*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
|
||
html, body { height:100%; font-family:var(--font-family); background:var(--bg-primary); color:var(--text-primary); overflow:hidden; transition: background var(--transition-normal), color var(--transition-normal); }
|
||
|
||
/* ========== App Shell ========== */
|
||
.app-shell { display:flex; flex-direction:column; height:100vh; height:100dvh; max-width:430px; margin:0 auto; position:relative; overflow:hidden; background:var(--bg-primary); }
|
||
@media (min-width:768px) { .app-shell { max-width:430px; border-left:1px solid var(--separator); border-right:1px solid var(--separator); } }
|
||
|
||
/* ========== Nav Bar ========== */
|
||
.nav-bar { position:sticky; top:0; z-index:100; background:var(--glass-bg); backdrop-filter:blur(var(--glass-blur)); -webkit-backdrop-filter:blur(var(--glass-blur)); border-bottom:0.5px solid var(--separator); transition:background var(--transition-normal); }
|
||
.nav-bar-content { display:flex; align-items:center; justify-content:space-between; padding:var(--space-2) var(--space-4); min-height:44px; }
|
||
.nav-bar-left { display:flex; align-items:center; gap:var(--space-2); }
|
||
.nav-bar-title { font:var(--font-title); color:var(--text-primary); letter-spacing:-0.2px; }
|
||
.nav-bar-right { display:flex; align-items:center; gap:var(--space-1); }
|
||
.nav-btn { width:36px; height:36px; border-radius:var(--radius-full); border:none; background:var(--accent-light); color:var(--primary); display:flex; align-items:center; justify-content:center; cursor:pointer; transition:all var(--transition-fast); font-size:18px; }
|
||
.nav-btn:active { transform:scale(0.9); background:var(--primary); color:white; }
|
||
|
||
/* ========== Main Content ========== */
|
||
.main-content { flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; padding-bottom:34px; position:relative; }
|
||
|
||
/* ========== Pull to Refresh ========== */
|
||
.pull-indicator { display:flex; align-items:center; justify-content:center; padding:12px 0; gap:6px; font:var(--font-caption); color:var(--text-tertiary); transition: all 0.3s; }
|
||
.pull-indicator .pull-icon { transition: transform 0.3s; }
|
||
.pull-indicator.pulling .pull-icon { transform: rotate(180deg); }
|
||
.pull-indicator.refreshing .pull-icon { animation: spin 0.8s linear infinite; }
|
||
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||
|
||
/* ========== Red Dot (变更指示) ========== */
|
||
.red-dot { width:8px; height:8px; border-radius:50%; background:var(--destructive); position:absolute; top:8px; right:8px; animation: redPulse 2s infinite; }
|
||
.red-dot-sm { width:6px; height:6px; border-radius:50%; background:var(--destructive); display:inline-block; animation: redPulse 2s infinite; }
|
||
@keyframes redPulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:0.7; transform:scale(0.8); } }
|
||
|
||
/* ========== Search Bar ========== */
|
||
.search-bar { padding:var(--space-2) var(--space-4); }
|
||
.search-input-wrapper { display:flex; align-items:center; gap:var(--space-2); background:var(--bg-tertiary); border-radius:var(--radius-sm); padding:var(--space-2) var(--space-3); transition:background var(--transition-normal); }
|
||
.search-icon { color:var(--text-tertiary); font-size:15px; }
|
||
.search-input { flex:1; border:none; background:transparent; font:var(--font-body); color:var(--text-primary); outline:none; }
|
||
.search-input::placeholder { color:var(--text-tertiary); }
|
||
|
||
/* ========== Section Header ========== */
|
||
.section-header { display:flex; align-items:center; justify-content:space-between; padding:var(--space-4) var(--space-4) var(--space-2); }
|
||
.section-title { font:var(--font-caption); color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.5px; display:flex; align-items:center; gap:6px; }
|
||
.section-count { font:var(--font-micro); color:var(--text-tertiary); background:var(--bg-tertiary); padding:2px 8px; border-radius:var(--radius-full); }
|
||
|
||
/* ========== Swipeable Container (列表模式) ========== */
|
||
.swipe-container { position:relative; overflow:hidden; }
|
||
.swipe-actions { position:absolute; top:0; bottom:0; display:flex; align-items:stretch; z-index:1; }
|
||
.swipe-actions.left { left:0; }
|
||
.swipe-actions.right { right:0; }
|
||
.swipe-action { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:0 14px; gap:4px; cursor:pointer; border:none; color:white; font:var(--font-micro); transition: opacity 0.2s; min-width:64px; }
|
||
.swipe-action:active { opacity:0.7; }
|
||
.swipe-action .action-icon { font-size:20px; }
|
||
.swipe-action.star { background:var(--warning); }
|
||
.swipe-action.share { background:var(--primary); }
|
||
.swipe-action.edit { background:#0984E3; }
|
||
.swipe-action.more { background:var(--text-secondary); }
|
||
.swipe-action.delete { background:var(--destructive); }
|
||
.swipe-action.pin { background:var(--success); }
|
||
|
||
.swipe-content { position:relative; z-index:2; background:var(--bg-card); transition:transform 0.3s cubic-bezier(0.25,0.1,0.25,1); }
|
||
|
||
/* ========== Note Card (Grid Mode) ========== */
|
||
.notes-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:var(--space-3); padding:0 var(--space-4); }
|
||
.note-card { background:var(--bg-card); border-radius:var(--radius-md); padding:var(--space-4); box-shadow:var(--shadow-sm); cursor:pointer; transition:all var(--transition-fast); border:1px solid var(--separator); position:relative; overflow:hidden; }
|
||
.note-card:active { transform:scale(0.97); box-shadow:var(--shadow-md); }
|
||
.note-card-key { font:600 15px var(--font-family); color:var(--primary); margin-bottom:var(--space-2); display:flex; align-items:center; gap:var(--space-2); }
|
||
.note-card-key .emoji { font-size:18px; }
|
||
.note-card-preview { font:var(--font-caption); color:var(--text-secondary); line-height:1.5; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; word-break:break-all; }
|
||
.note-card-meta { display:flex; align-items:center; justify-content:space-between; margin-top:var(--space-3); padding-top:var(--space-2); border-top:0.5px solid var(--separator); }
|
||
.note-card-time { font:var(--font-micro); color:var(--text-tertiary); }
|
||
.note-card-badge { font:var(--font-micro); padding:2px 6px; border-radius:var(--radius-full); background:var(--accent-light); color:var(--primary); }
|
||
.note-card-star { position:absolute; top:var(--space-2); right:var(--space-2); font-size:14px; opacity:0.6; }
|
||
|
||
/* ========== Note List Item ========== */
|
||
.notes-list { padding:0 var(--space-4); }
|
||
.note-list-item { display:flex; align-items:center; gap:var(--space-3); padding:var(--space-3) 0; border-bottom:0.5px solid var(--separator); cursor:pointer; transition:background var(--transition-fast); position:relative; }
|
||
.note-list-item:active { background:var(--accent-light); }
|
||
.note-list-icon { width:44px; height:44px; border-radius:var(--radius-md); background:var(--accent-light); display:flex; align-items:center; justify-content:center; font-size:22px; flex-shrink:0; position:relative; }
|
||
.note-list-content { flex:1; min-width:0; }
|
||
.note-list-title { font:500 15px var(--font-family); color:var(--text-primary); margin-bottom:2px; display:flex; align-items:center; gap:6px; }
|
||
.note-list-subtitle { font:var(--font-caption); color:var(--text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||
.note-list-right { display:flex; flex-direction:column; align-items:flex-end; gap:2px; flex-shrink:0; }
|
||
.note-list-time { font:var(--font-micro); color:var(--text-tertiary); }
|
||
.note-list-badge { font:var(--font-micro); padding:1px 6px; border-radius:var(--radius-full); background:var(--accent-light); color:var(--primary); }
|
||
|
||
/* ========== Timeline Mode ========== */
|
||
.notes-timeline { padding:0 var(--space-4); }
|
||
.timeline-date { font:600 13px var(--font-family); color:var(--primary); padding:var(--space-3) 0 var(--space-2); position:relative; display:flex; align-items:center; gap:var(--space-2); }
|
||
.timeline-date::before { content:''; width:8px; height:8px; border-radius:50%; background:var(--primary); flex-shrink:0; }
|
||
.timeline-line { position:relative; padding-left:20px; margin-left:3.5px; border-left:2px solid var(--separator); }
|
||
.timeline-item { background:var(--bg-card); border-radius:var(--radius-md); padding:var(--space-3); margin-bottom:var(--space-3); box-shadow:var(--shadow-sm); border:1px solid var(--separator); cursor:pointer; transition:all var(--transition-fast); position:relative; }
|
||
.timeline-item:active { transform:scale(0.98); }
|
||
.timeline-item-title { font:500 15px var(--font-family); color:var(--text-primary); margin-bottom:var(--space-1); }
|
||
.timeline-item-preview { font:var(--font-caption); color:var(--text-secondary); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
||
.timeline-item-time { font:var(--font-micro); color:var(--text-tertiary); margin-top:var(--space-2); }
|
||
|
||
/* ========== Modal Overlay ========== */
|
||
.modal-overlay { position:fixed; inset:0; background:var(--overlay); z-index:200; display:flex; align-items:flex-end; justify-content:center; opacity:0; pointer-events:none; transition:opacity var(--transition-normal); }
|
||
.modal-overlay.active { opacity:1; pointer-events:auto; }
|
||
.modal-sheet { width:100%; max-width:430px; background:var(--bg-card); border-radius:var(--radius-xl) var(--radius-xl) 0 0; padding:var(--space-2) var(--space-4) var(--space-8); transform:translateY(100%); transition:transform var(--transition-spring); }
|
||
.modal-overlay.active .modal-sheet { transform:translateY(0); }
|
||
.modal-handle { width:36px; height:5px; border-radius:var(--radius-full); background:var(--text-tertiary); margin:0 auto var(--space-4); }
|
||
.modal-title { font:var(--font-large); color:var(--text-primary); text-align:center; margin-bottom:var(--space-5); }
|
||
.url-preview { background:var(--bg-tertiary); border-radius:var(--radius-md); padding:var(--space-3) var(--space-4); margin-bottom:var(--space-4); display:flex; align-items:center; gap:var(--space-2); border:1px solid var(--separator); }
|
||
.url-preview-label { font:var(--font-caption); color:var(--text-tertiary); flex-shrink:0; }
|
||
.url-preview-base { font:500 15px var(--font-family); color:var(--text-secondary); flex-shrink:0; }
|
||
.url-preview-key { font:600 15px var(--font-family); color:var(--primary); min-width:40px; border-bottom:2px solid var(--primary); padding-bottom:1px; }
|
||
.url-preview-key.empty { color:var(--text-tertiary); border-bottom-color:var(--text-tertiary); }
|
||
.input-group { margin-bottom:var(--space-4); }
|
||
.input-label { font:var(--font-caption); color:var(--text-secondary); margin-bottom:var(--space-2); display:block; }
|
||
.input-field { width:100%; padding:var(--space-3) var(--space-4); background:var(--bg-tertiary); border:1.5px solid var(--separator); border-radius:var(--radius-md); font:var(--font-body); color:var(--text-primary); outline:none; transition:border-color var(--transition-fast); }
|
||
.input-field:focus { border-color:var(--primary); }
|
||
.input-field.error { border-color:var(--destructive); }
|
||
.input-hint { font:var(--font-micro); color:var(--text-tertiary); margin-top:var(--space-1); }
|
||
.input-hint.error { color:var(--destructive); }
|
||
.input-counter { font:var(--font-micro); color:var(--text-tertiary); text-align:right; margin-top:var(--space-1); }
|
||
.modal-actions { display:flex; gap:var(--space-3); margin-top:var(--space-4); }
|
||
.btn { flex:1; padding:var(--space-3) var(--space-4); border-radius:var(--radius-md); font:500 16px var(--font-family); border:none; cursor:pointer; transition:all var(--transition-fast); }
|
||
.btn:active { transform:scale(0.97); }
|
||
.btn-secondary { background:var(--bg-tertiary); color:var(--text-primary); }
|
||
.btn-primary { background:var(--primary); color:white; }
|
||
.btn-primary:disabled { opacity:0.4; cursor:not-allowed; }
|
||
|
||
/* ========== Page Overlay (Settings / Editor) ========== */
|
||
.page-overlay { position:fixed; inset:0; z-index:150; background:var(--bg-primary); transform:translateX(100%); transition:transform var(--transition-normal); max-width:430px; margin:0 auto; display:flex; flex-direction:column; }
|
||
.page-overlay.active { transform:translateX(0); }
|
||
.settings-scroll { flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; padding-bottom:34px; }
|
||
.settings-group { margin:var(--space-4); background:var(--bg-card); border-radius:var(--radius-md); overflow:hidden; box-shadow:var(--shadow-sm); }
|
||
.settings-group-title { font:var(--font-caption); color:var(--text-secondary); padding:var(--space-4) var(--space-4) var(--space-2); text-transform:uppercase; letter-spacing:0.5px; }
|
||
.settings-item { display:flex; align-items:center; justify-content:space-between; padding:var(--space-3) var(--space-4); min-height:44px; border-bottom:0.5px solid var(--separator); }
|
||
.settings-item:last-child { border-bottom:none; }
|
||
.settings-item-left { display:flex; align-items:center; gap:var(--space-3); }
|
||
.settings-item-icon { width:30px; height:30px; border-radius:var(--radius-sm); display:flex; align-items:center; justify-content:center; font-size:16px; flex-shrink:0; }
|
||
.settings-item-icon.blue { background:#0984E3; color:white; }
|
||
.settings-item-icon.green { background:#00B894; color:white; }
|
||
.settings-item-icon.orange { background:#E17055; color:white; }
|
||
.settings-item-icon.purple { background:#6C5CE7; color:white; }
|
||
.settings-item-icon.red { background:#FF3B30; color:white; }
|
||
.settings-item-icon.pink { background:#FD79A8; color:white; }
|
||
.settings-item-icon.teal { background:#00CEC9; color:white; }
|
||
.settings-item-icon.gray { background:#8E8E93; color:white; }
|
||
.settings-item-text { font:var(--font-body); color:var(--text-primary); }
|
||
.settings-item-desc { font:var(--font-micro); color:var(--text-tertiary); margin-top:1px; }
|
||
.settings-item-right { display:flex; align-items:center; gap:var(--space-2); flex-shrink:0; }
|
||
.settings-item-value { font:var(--font-caption); color:var(--text-tertiary); }
|
||
.toggle { width:51px; height:31px; border-radius:var(--radius-full); background:var(--bg-tertiary); position:relative; cursor:pointer; transition:background var(--transition-fast); border:none; flex-shrink:0; }
|
||
.toggle.active { background:var(--success); }
|
||
.toggle::after { content:''; position:absolute; width:27px; height:27px; border-radius:50%; background:white; top:2px; left:2px; box-shadow:0 1px 3px rgba(0,0,0,0.2); transition:transform var(--transition-fast); }
|
||
.toggle.active::after { transform:translateX(20px); }
|
||
.segmented { display:flex; background:var(--bg-tertiary); border-radius:var(--radius-sm); padding:2px; gap:2px; }
|
||
.segmented-btn { flex:1; padding:var(--space-1) var(--space-2); border:none; border-radius:6px; background:transparent; font:500 13px var(--font-family); color:var(--text-secondary); cursor:pointer; transition:all var(--transition-fast); white-space:nowrap; }
|
||
.segmented-btn.active { background:var(--bg-card); color:var(--text-primary); box-shadow:var(--shadow-sm); }
|
||
.quota-bar { margin:var(--space-2) var(--space-4) var(--space-1); background:var(--bg-tertiary); border-radius:var(--radius-full); height:6px; overflow:hidden; }
|
||
.quota-bar-fill { height:100%; border-radius:var(--radius-full); background:var(--success); transition:width var(--transition-normal); }
|
||
.quota-bar-fill.warning { background:var(--warning); }
|
||
.quota-bar-fill.danger { background:var(--destructive); }
|
||
.quota-text { font:var(--font-micro); color:var(--text-tertiary); text-align:right; padding:0 var(--space-4); }
|
||
.notice-section { margin:var(--space-4); padding:var(--space-4); background:var(--accent-light); border-radius:var(--radius-md); border-left:3px solid var(--primary); }
|
||
.notice-title { font:500 14px var(--font-family); color:var(--primary); margin-bottom:var(--space-2); display:flex; align-items:center; gap:var(--space-2); }
|
||
.notice-list { list-style:none; }
|
||
.notice-list li { font:var(--font-caption); color:var(--text-secondary); line-height:1.6; padding:var(--space-1) 0; position:relative; padding-left:14px; }
|
||
.notice-list li::before { content:'•'; position:absolute; left:0; color:var(--primary); }
|
||
.locked-item { opacity:0.5; pointer-events:none; }
|
||
.locked-badge { font:var(--font-micro); color:var(--text-tertiary); background:var(--bg-tertiary); padding:2px 8px; border-radius:var(--radius-full); }
|
||
|
||
/* ========== Sync Indicator ========== */
|
||
.sync-indicator { display:flex; align-items:center; gap:var(--space-2); padding:var(--space-2) var(--space-4); font:var(--font-micro); color:var(--text-tertiary); }
|
||
.sync-dot { width:6px; height:6px; border-radius:50%; background:var(--success); }
|
||
.sync-dot.syncing { animation:pulse 1.5s infinite; }
|
||
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
|
||
|
||
/* ========== Editor Page ========== */
|
||
.editor-page { position:fixed; inset:0; z-index:160; background:var(--bg-primary); transform:translateX(100%); transition:transform var(--transition-normal); max-width:430px; margin:0 auto; display:flex; flex-direction:column; }
|
||
.editor-page.active { transform:translateX(0); }
|
||
.editor-toolbar { display:flex; align-items:center; gap:var(--space-2); padding:var(--space-2) var(--space-4); border-bottom:0.5px solid var(--separator); background:var(--glass-bg); backdrop-filter:blur(var(--glass-blur)); -webkit-backdrop-filter:blur(var(--glass-blur)); }
|
||
.editor-toolbar-btn { width:32px; height:32px; border-radius:var(--radius-sm); border:none; background:transparent; color:var(--text-secondary); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:16px; transition:all var(--transition-fast); }
|
||
.editor-toolbar-btn:active { background:var(--accent-light); color:var(--primary); }
|
||
.editor-toolbar-btn.active { background:var(--accent-light); color:var(--primary); }
|
||
.editor-toolbar-divider { width:1px; height:20px; background:var(--separator); margin:0 2px; }
|
||
.editor-status { display:flex; align-items:center; justify-content:space-between; padding:var(--space-1) var(--space-4); font:var(--font-micro); color:var(--text-tertiary); border-bottom:0.5px solid var(--separator); background:var(--bg-card); }
|
||
.editor-status-left { display:flex; align-items:center; gap:var(--space-2); }
|
||
.editor-url-bar { display:flex; align-items:center; gap:var(--space-2); padding:var(--space-2) var(--space-4); background:var(--bg-tertiary); margin:var(--space-2) var(--space-4); border-radius:var(--radius-sm); font:var(--font-caption); }
|
||
.editor-url-base { color:var(--text-tertiary); }
|
||
.editor-url-key { color:var(--primary); font-weight:600; }
|
||
.editor-textarea { flex:1; width:100%; border:none; background:var(--bg-card); color:var(--text-primary); font:400 15px/1.7 var(--font-family); padding:var(--space-4); outline:none; resize:none; -webkit-overflow-scrolling:touch; }
|
||
.editor-textarea::placeholder { color:var(--text-tertiary); }
|
||
.editor-footer { display:flex; align-items:center; justify-content:space-between; padding:var(--space-2) var(--space-4); border-top:0.5px solid var(--separator); background:var(--glass-bg); backdrop-filter:blur(var(--glass-blur)); -webkit-backdrop-filter:blur(var(--glass-blur)); font:var(--font-micro); color:var(--text-tertiary); }
|
||
.editor-footer-left { display:flex; align-items:center; gap:var(--space-3); }
|
||
.editor-footer-right { display:flex; align-items:center; gap:var(--space-3); }
|
||
|
||
/* ========== Theme / Layout Switcher ========== */
|
||
.theme-switcher { position:fixed; top:50%; right:8px; transform:translateY(-50%); z-index:300; display:flex; flex-direction:column; gap:6px; }
|
||
.theme-dot { width:28px; height:28px; border-radius:50%; border:2px solid rgba(255,255,255,0.3); cursor:pointer; transition:all var(--transition-fast); box-shadow:var(--shadow-md); }
|
||
.theme-dot:active { transform:scale(0.9); }
|
||
.theme-dot.active { border-color:var(--primary); transform:scale(1.15); }
|
||
.layout-switcher { position:fixed; bottom:20px; right:8px; z-index:300; display:flex; flex-direction:column; gap:6px; }
|
||
.layout-btn { width:36px; height:36px; border-radius:var(--radius-sm); border:1px solid var(--separator); background:var(--glass-bg); backdrop-filter:blur(10px); color:var(--text-primary); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:16px; transition:all var(--transition-fast); }
|
||
.layout-btn:active { transform:scale(0.9); }
|
||
.layout-btn.active { background:var(--primary); color:white; border-color:var(--primary); }
|
||
|
||
/* ========== Action Sheet ========== */
|
||
.action-sheet-overlay { position:fixed; inset:0; background:var(--overlay); z-index:250; display:flex; align-items:flex-end; justify-content:center; opacity:0; pointer-events:none; transition:opacity var(--transition-normal); }
|
||
.action-sheet-overlay.active { opacity:1; pointer-events:auto; }
|
||
.action-sheet { width:100%; max-width:430px; padding:0 var(--space-4) var(--space-8); transform:translateY(100%); transition:transform var(--transition-spring); }
|
||
.action-sheet-overlay.active .action-sheet { transform:translateY(0); }
|
||
.action-sheet-group { background:var(--bg-card); border-radius:var(--radius-md); overflow:hidden; margin-bottom:var(--space-2); }
|
||
.action-sheet-item { display:flex; align-items:center; justify-content:center; gap:var(--space-2); padding:var(--space-4); font:var(--font-body); color:var(--primary); cursor:pointer; border-bottom:0.5px solid var(--separator); transition:background var(--transition-fast); }
|
||
.action-sheet-item:last-child { border-bottom:none; }
|
||
.action-sheet-item:active { background:var(--accent-light); }
|
||
.action-sheet-item.destructive { color:var(--destructive); }
|
||
.action-sheet-cancel { background:var(--bg-card); border-radius:var(--radius-md); padding:var(--space-4); font:600 17px var(--font-family); color:var(--primary); text-align:center; cursor:pointer; }
|
||
.action-sheet-cancel:active { background:var(--accent-light); }
|
||
|
||
/* ========== Animations ========== */
|
||
@keyframes fadeInUp { from { opacity:0; transform:translateY(20px); } to { opacity:1; transform:translateY(0); } }
|
||
.animate-in { animation:fadeInUp 0.4s ease-out both; }
|
||
.animate-in:nth-child(1) { animation-delay:0.05s; }
|
||
.animate-in:nth-child(2) { animation-delay:0.1s; }
|
||
.animate-in:nth-child(3) { animation-delay:0.15s; }
|
||
.animate-in:nth-child(4) { animation-delay:0.2s; }
|
||
.animate-in:nth-child(5) { animation-delay:0.25s; }
|
||
.animate-in:nth-child(6) { animation-delay:0.3s; }
|
||
|
||
/* ========== Markdown Preview ========== */
|
||
.editor-preview { flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:var(--space-4); background:var(--bg-card); display:none; }
|
||
.editor-preview.active { display:block; }
|
||
.editor-preview h1 { font:700 24px var(--font-family); color:var(--text-primary); margin:var(--space-4) 0 var(--space-2); }
|
||
.editor-preview h2 { font:600 20px var(--font-family); color:var(--text-primary); margin:var(--space-4) 0 var(--space-2); }
|
||
.editor-preview h3 { font:600 17px var(--font-family); color:var(--text-primary); margin:var(--space-3) 0 var(--space-2); }
|
||
.editor-preview p { font:var(--font-body); color:var(--text-primary); line-height:1.7; margin:var(--space-2) 0; }
|
||
.editor-preview ul, .editor-preview ol { padding-left:var(--space-5); margin:var(--space-2) 0; }
|
||
.editor-preview li { font:var(--font-body); color:var(--text-primary); line-height:1.7; }
|
||
.editor-preview code { font-family:'SF Mono', Menlo, monospace; background:var(--bg-tertiary); padding:2px 6px; border-radius:4px; font-size:13px; color:var(--primary); }
|
||
.editor-preview pre { background:var(--bg-tertiary); padding:var(--space-3); border-radius:var(--radius-md); overflow-x:auto; margin:var(--space-3) 0; }
|
||
.editor-preview pre code { background:transparent; padding:0; }
|
||
.editor-preview blockquote { border-left:3px solid var(--primary); padding-left:var(--space-3); margin:var(--space-3) 0; color:var(--text-secondary); }
|
||
.editor-preview strong { font-weight:600; }
|
||
.editor-preview em { font-style:italic; }
|
||
.editor-preview hr { border:none; border-top:1px solid var(--separator); margin:var(--space-4) 0; }
|
||
.nav-btn.preview-active { background:var(--primary); color:white; }
|
||
|
||
/* ========== Random Key Button ========== */
|
||
.key-input-row { display:flex; gap:var(--space-2); align-items:flex-start; }
|
||
.key-input-row .input-field { flex:1; }
|
||
.btn-random { width:44px; height:44px; border-radius:var(--radius-md); border:1.5px solid var(--separator); background:var(--bg-tertiary); color:var(--text-primary); font-size:20px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all var(--transition-fast); flex-shrink:0; margin-top:0; }
|
||
.btn-random:active { transform:scale(0.92); background:var(--accent-light); border-color:var(--primary); }
|
||
|
||
/* ========== Empty State ========== */
|
||
.empty-state { display:none; flex-direction:column; align-items:center; justify-content:center; padding:var(--space-8) var(--space-4); text-align:center; }
|
||
.empty-state.active { display:flex; }
|
||
.empty-state-icon { font-size:64px; margin-bottom:var(--space-5); opacity:0.8; }
|
||
.empty-state-title { font:600 20px var(--font-family); color:var(--text-primary); margin-bottom:var(--space-2); }
|
||
.empty-state-subtitle { font:var(--font-caption); color:var(--text-secondary); line-height:1.5; }
|
||
|
||
/* ========== Tag Badges ========== */
|
||
.note-card-tags { display:flex; flex-wrap:wrap; gap:4px; margin-top:var(--space-2); }
|
||
.tag-badge { font:500 10px var(--font-family); padding:2px 8px; border-radius:var(--radius-full); white-space:nowrap; }
|
||
.tag-badge.work { background:rgba(9,132,227,0.12); color:#0984E3; }
|
||
.tag-badge.diary { background:rgba(253,121,168,0.12); color:#FD79A8; }
|
||
.tag-badge.code { background:rgba(108,92,231,0.12); color:#6C5CE7; }
|
||
.tag-badge.life { background:rgba(0,184,148,0.12); color:#00B894; }
|
||
.tag-badge.idea { background:rgba(225,112,85,0.12); color:#E17055; }
|
||
.tag-badge.food { background:rgba(255,150,0,0.12); color:#FF9500; }
|
||
|
||
/* ========== Skeleton Loading ========== */
|
||
.skeleton-container { padding:0 var(--space-4); }
|
||
.skeleton-card { background:var(--bg-card); border-radius:var(--radius-md); padding:var(--space-4); border:1px solid var(--separator); margin-bottom:var(--space-3); position:relative; overflow:hidden; }
|
||
.skeleton-line { height:14px; border-radius:7px; background:var(--bg-tertiary); margin-bottom:var(--space-2); }
|
||
.skeleton-line.short { width:40%; }
|
||
.skeleton-line.medium { width:70%; }
|
||
.skeleton-line.long { width:90%; }
|
||
.skeleton-line.title { height:18px; width:50%; border-radius:9px; margin-bottom:var(--space-3); }
|
||
.skeleton-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:var(--space-3); }
|
||
.skeleton-card::after { content:''; position:absolute; inset:0; background:linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.4) 50%, transparent 100%); animation:shimmer 1.5s infinite; }
|
||
[data-theme="dark"] .skeleton-card::after, [data-theme="amoled"] .skeleton-card::after { background:linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.06) 50%, transparent 100%); }
|
||
@keyframes shimmer { 0% { transform:translateX(-100%); } 100% { transform:translateX(100%); } }
|
||
|
||
/* ========== Enhanced Card Animations ========== */
|
||
.note-card.animate-in { animation:cardBounceIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; animation-delay:calc(var(--card-index, 0) * 0.08s); }
|
||
@keyframes cardBounceIn { 0% { opacity:0; transform:scale(0.85) translateY(16px); } 60% { opacity:1; transform:scale(1.03) translateY(-2px); } 100% { opacity:1; transform:scale(1) translateY(0); } }
|
||
|
||
/* ========== Page Transition Enhancement ========== */
|
||
.app-shell.editor-open { transform:scale(0.96); filter:brightness(0.95); transition:transform 0.4s cubic-bezier(0.25,0.1,0.25,1), filter 0.4s cubic-bezier(0.25,0.1,0.25,1); }
|
||
.editor-page.active { box-shadow:-8px 0 30px rgba(0,0,0,0.15); }
|
||
|
||
/* ========== Button Haptic Feedback ========== */
|
||
.nav-btn, .btn, .btn-random, .editor-toolbar-btn, .layout-btn, .action-sheet-item, .action-sheet-cancel { transition:transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), background var(--transition-fast), color var(--transition-fast), opacity var(--transition-fast); }
|
||
.nav-btn:active, .btn:active, .btn-random:active, .editor-toolbar-btn:active, .layout-btn:active, .action-sheet-item:active, .action-sheet-cancel:active { transform:scale(0.92); }
|
||
|
||
/* ========== Pull-to-Refresh Circular Progress ========== */
|
||
.pull-progress { width:24px; height:24px; border-radius:50%; border:2.5px solid var(--separator); border-top-color:var(--primary); display:none; }
|
||
.pull-indicator.refreshing .pull-progress { display:block; animation:spin 0.8s linear infinite; }
|
||
.pull-indicator.refreshing .pull-icon { display:none; }
|
||
|
||
/* ========== Red Dot Appear Animation ========== */
|
||
.red-dot, .red-dot-sm { animation:dotAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both, redPulse 2s 0.4s infinite; }
|
||
@keyframes dotAppear { from { transform:scale(0); } to { transform:scale(1); } }
|
||
</style>
|
||
</head>
|
||
<body data-theme="light" data-accent="purple">
|
||
|
||
<!-- ========== 主页面:笔记仓库列表 ========== -->
|
||
<div class="app-shell" id="mainPage">
|
||
<div class="nav-bar">
|
||
<div class="nav-bar-content">
|
||
<div class="nav-bar-left">
|
||
<button class="nav-btn" onclick="showToast('返回')">‹</button>
|
||
<span class="nav-bar-title">📝 笔记仓库</span>
|
||
</div>
|
||
<div class="nav-bar-right">
|
||
<button class="nav-btn" onclick="openSettings()" title="设置">⚙</button>
|
||
<button class="nav-btn" onclick="openAddSheet()" title="添加">+</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="sync-indicator">
|
||
<div class="sync-dot syncing"></div>
|
||
<span>已同步 · 刚刚</span>
|
||
</div>
|
||
|
||
<div class="search-bar">
|
||
<div class="search-input-wrapper">
|
||
<span class="search-icon">🔍</span>
|
||
<input class="search-input" type="text" placeholder="搜索笔记仓库...">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main-content" id="mainContent">
|
||
<!-- Pull to Refresh Indicator -->
|
||
<div class="pull-indicator" id="pullIndicator">
|
||
<span class="pull-icon">🔄</span>
|
||
<span id="pullText">下拉刷新</span>
|
||
</div>
|
||
|
||
<!-- Grid View (Default) -->
|
||
<div id="gridView">
|
||
<div class="section-header">
|
||
<span class="section-title">⭐ 主要 <span class="red-dot-sm"></span></span>
|
||
<span class="section-count">2</span>
|
||
</div>
|
||
<div class="notes-grid">
|
||
<div class="note-card animate-in" onclick="openEditor('my-diary')">
|
||
<div class="red-dot"></div>
|
||
<span class="note-card-star">⭐</span>
|
||
<div class="note-card-key"><span class="emoji">📓</span> my-diary</div>
|
||
<div class="note-card-preview">今天天气很好,适合出去散步。下午去了公园,看到了很多花...</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">3分钟前</span>
|
||
<span class="note-card-badge">1.2KB</span>
|
||
</div>
|
||
</div>
|
||
<div class="note-card animate-in" onclick="openEditor('work-notes')">
|
||
<span class="note-card-star">⭐</span>
|
||
<div class="note-card-key"><span class="emoji">💼</span> work-notes</div>
|
||
<div class="note-card-preview">项目进度:前端开发完成80%,后端接口联调中...</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">1小时前</span>
|
||
<span class="note-card-badge">856B</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="section-header" style="margin-top:8px;">
|
||
<span class="section-title">📋 其他</span>
|
||
<span class="section-count">4</span>
|
||
</div>
|
||
<div class="notes-grid">
|
||
<div class="note-card animate-in" onclick="openEditor('todo-list')">
|
||
<div class="red-dot"></div>
|
||
<div class="note-card-key"><span class="emoji">✅</span> todo-list</div>
|
||
<div class="note-card-preview">- 完成周报\n- 提交代码review\n- 准备会议材料</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">昨天</span>
|
||
<span class="note-card-badge">320B</span>
|
||
</div>
|
||
</div>
|
||
<div class="note-card animate-in" onclick="openEditor('recipes')">
|
||
<div class="note-card-key"><span class="emoji">🍳</span> recipes</div>
|
||
<div class="note-card-preview">番茄炒蛋:鸡蛋3个,番茄2个,盐适量...</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">3天前</span>
|
||
<span class="note-card-badge">512B</span>
|
||
</div>
|
||
</div>
|
||
<div class="note-card animate-in" onclick="openEditor('code-snippets')">
|
||
<div class="note-card-key"><span class="emoji">💻</span> code-snippets</div>
|
||
<div class="note-card-preview">flutter pub run build_runner build --delete-conflicting-outputs</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">上周</span>
|
||
<span class="note-card-badge">2.1KB</span>
|
||
</div>
|
||
</div>
|
||
<div class="note-card animate-in" onclick="openEditor('shared-ideas')">
|
||
<div class="note-card-key"><span class="emoji">💡</span> shared-ideas</div>
|
||
<div class="note-card-preview">新功能想法:支持Markdown渲染,添加标签系统...</div>
|
||
<div class="note-card-meta">
|
||
<span class="note-card-time">2周前</span>
|
||
<span class="note-card-badge">1.5KB</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- List View (with swipe) -->
|
||
<div id="listView" style="display:none;">
|
||
<div class="section-header">
|
||
<span class="section-title">⭐ 主要 <span class="red-dot-sm"></span></span>
|
||
<span class="section-count">2</span>
|
||
</div>
|
||
<div class="notes-list">
|
||
<!-- Swipeable Item 1 -->
|
||
<div class="swipe-container" data-key="my-diary">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action more" onclick="openActionSheet('my-diary')"><span class="action-icon">•••</span>更多</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('my-diary')">
|
||
<div class="note-list-icon">📓<div class="red-dot" style="top:-2px;right:-2px;"></div></div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">my-diary <span class="red-dot-sm"></span></div>
|
||
<div class="note-list-subtitle">今天天气很好,适合出去散步。下午去了公园...</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">3分钟前</span>
|
||
<span class="note-list-badge">⭐ 主要</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Swipeable Item 2 -->
|
||
<div class="swipe-container" data-key="work-notes">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action more" onclick="openActionSheet('work-notes')"><span class="action-icon">•••</span>更多</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('work-notes')">
|
||
<div class="note-list-icon">💼</div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">work-notes</div>
|
||
<div class="note-list-subtitle">项目进度:前端开发完成80%,后端接口联调中...</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">1小时前</span>
|
||
<span class="note-list-badge">⭐ 主要</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="section-header">
|
||
<span class="section-title">📋 其他</span>
|
||
<span class="section-count">4</span>
|
||
</div>
|
||
<div class="notes-list">
|
||
<div class="swipe-container" data-key="todo-list">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action delete" onclick="showToast('已删除')"><span class="action-icon">🗑</span>删除</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('todo-list')">
|
||
<div class="note-list-icon">✅<div class="red-dot" style="top:-2px;right:-2px;"></div></div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">todo-list <span class="red-dot-sm"></span></div>
|
||
<div class="note-list-subtitle">- 完成周报 - 提交代码review - 准备会议材料</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">昨天</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="swipe-container" data-key="recipes">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action delete" onclick="showToast('已删除')"><span class="action-icon">🗑</span>删除</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('recipes')">
|
||
<div class="note-list-icon">🍳</div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">recipes</div>
|
||
<div class="note-list-subtitle">番茄炒蛋:鸡蛋3个,番茄2个,盐适量...</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">3天前</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="swipe-container" data-key="code-snippets">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action delete" onclick="showToast('已删除')"><span class="action-icon">🗑</span>删除</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('code-snippets')">
|
||
<div class="note-list-icon">💻</div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">code-snippets</div>
|
||
<div class="note-list-subtitle">flutter pub run build_runner build...</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">上周</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="swipe-container" data-key="shared-ideas">
|
||
<div class="swipe-actions right">
|
||
<button class="swipe-action pin" onclick="showToast('已置顶')"><span class="action-icon">📌</span>置顶</button>
|
||
<button class="swipe-action star" onclick="showToast('已标星')"><span class="action-icon">⭐</span>主要</button>
|
||
<button class="swipe-action share" onclick="showToast('已复制链接')"><span class="action-icon">🔗</span>分享</button>
|
||
<button class="swipe-action delete" onclick="showToast('已删除')"><span class="action-icon">🗑</span>删除</button>
|
||
</div>
|
||
<div class="swipe-content" ontouchstart="handleTouchStart(event)" ontouchmove="handleTouchMove(event)" ontouchend="handleTouchEnd(event)">
|
||
<div class="note-list-item" onclick="openEditor('shared-ideas')">
|
||
<div class="note-list-icon">💡</div>
|
||
<div class="note-list-content">
|
||
<div class="note-list-title">shared-ideas</div>
|
||
<div class="note-list-subtitle">新功能想法:支持Markdown渲染...</div>
|
||
</div>
|
||
<div class="note-list-right">
|
||
<span class="note-list-time">2周前</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Timeline View -->
|
||
<div id="timelineView" style="display:none;">
|
||
<div class="notes-timeline">
|
||
<div class="timeline-date">今天</div>
|
||
<div class="timeline-line">
|
||
<div class="timeline-item" onclick="openEditor('my-diary')">
|
||
<div class="red-dot" style="top:8px;right:8px;"></div>
|
||
<div class="timeline-item-title">📓 my-diary</div>
|
||
<div class="timeline-item-preview">今天天气很好,适合出去散步。下午去了公园,看到了很多花...</div>
|
||
<div class="timeline-item-time">3分钟前 · 1.2KB · 有变更</div>
|
||
</div>
|
||
<div class="timeline-item" onclick="openEditor('work-notes')">
|
||
<div class="timeline-item-title">💼 work-notes</div>
|
||
<div class="timeline-item-preview">项目进度:前端开发完成80%,后端接口联调中...</div>
|
||
<div class="timeline-item-time">1小时前 · 856B</div>
|
||
</div>
|
||
</div>
|
||
<div class="timeline-date">昨天</div>
|
||
<div class="timeline-line">
|
||
<div class="timeline-item" onclick="openEditor('todo-list')">
|
||
<div class="red-dot" style="top:8px;right:8px;"></div>
|
||
<div class="timeline-item-title">✅ todo-list</div>
|
||
<div class="timeline-item-preview">- 完成周报 - 提交代码review - 准备会议材料</div>
|
||
<div class="timeline-item-time">昨天 · 320B · 有变更</div>
|
||
</div>
|
||
</div>
|
||
<div class="timeline-date">更早</div>
|
||
<div class="timeline-line">
|
||
<div class="timeline-item" onclick="openEditor('recipes')">
|
||
<div class="timeline-item-title">🍳 recipes</div>
|
||
<div class="timeline-item-preview">番茄炒蛋:鸡蛋3个,番茄2个,盐适量...</div>
|
||
<div class="timeline-item-time">3天前 · 512B</div>
|
||
</div>
|
||
<div class="timeline-item" onclick="openEditor('code-snippets')">
|
||
<div class="timeline-item-title">💻 code-snippets</div>
|
||
<div class="timeline-item-preview">flutter pub run build_runner build --delete-conflicting-outputs</div>
|
||
<div class="timeline-item-time">上周 · 2.1KB</div>
|
||
</div>
|
||
<div class="timeline-item" onclick="openEditor('shared-ideas')">
|
||
<div class="timeline-item-title">💡 shared-ideas</div>
|
||
<div class="timeline-item-preview">新功能想法:支持Markdown渲染,添加标签系统...</div>
|
||
<div class="timeline-item-time">2周前 · 1.5KB</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 添加笔记 ========== -->
|
||
<div class="modal-overlay" id="addSheet">
|
||
<div class="modal-sheet">
|
||
<div class="modal-handle"></div>
|
||
<div class="modal-title">📝 新建笔记</div>
|
||
<div class="url-preview">
|
||
<span class="url-preview-label">预览</span>
|
||
<span class="url-preview-base">ctc.s2ss.com/</span>
|
||
<span class="url-preview-key empty" id="urlKeyPreview">钥匙</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<label class="input-label">🔑 钥匙(唯一标识)</label>
|
||
<input class="input-field" id="keyInput" type="text" placeholder="输入2-16位数字或字符" maxlength="16" autocomplete="off" oninput="validateKey()">
|
||
<div class="input-hint" id="keyHint">仅支持数字和字母,2-16位</div>
|
||
<div class="input-counter"><span id="keyCount">0</span>/16</div>
|
||
</div>
|
||
<div class="input-group">
|
||
<label class="input-label">📄 初始内容(可选)</label>
|
||
<textarea class="input-field" id="contentInput" rows="3" placeholder="输入笔记内容..." style="resize:none; min-height:80px;" oninput="updateContentCount()"></textarea>
|
||
<div class="input-counter"><span id="contentCount">0</span>/10000</div>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-secondary" onclick="closeAddSheet()">取消</button>
|
||
<button class="btn btn-primary" id="createBtn" disabled onclick="createNote()">创建</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== Action Sheet (更多操作) ========== -->
|
||
<div class="action-sheet-overlay" id="actionSheetOverlay" onclick="closeActionSheet()">
|
||
<div class="action-sheet" id="actionSheet">
|
||
<div class="action-sheet-group">
|
||
<div class="action-sheet-item" onclick="showToast('编辑笔记'); closeActionSheet();">✏️ 编辑内容</div>
|
||
<div class="action-sheet-item" onclick="showToast('重命名钥匙'); closeActionSheet();">🔑 重命名</div>
|
||
<div class="action-sheet-item" onclick="showToast('查看历史'); closeActionSheet();">📜 查看历史</div>
|
||
<div class="action-sheet-item" onclick="showToast('导出笔记'); closeActionSheet();">📤 导出</div>
|
||
<div class="action-sheet-item" onclick="showToast('显示二维码'); closeActionSheet();">📱 二维码</div>
|
||
</div>
|
||
<div class="action-sheet-group">
|
||
<div class="action-sheet-item destructive" onclick="showToast('已删除'); closeActionSheet();">🗑 删除笔记</div>
|
||
</div>
|
||
<div class="action-sheet-cancel" onclick="closeActionSheet()">取消</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 设置页面 ========== -->
|
||
<div class="page-overlay" id="settingsPage">
|
||
<div class="nav-bar">
|
||
<div class="nav-bar-content">
|
||
<div class="nav-bar-left">
|
||
<button class="nav-btn" onclick="closeSettings()">‹</button>
|
||
<span class="nav-bar-title">⚙️ 笔记仓库设置</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-scroll">
|
||
<div class="settings-group-title" style="padding-left:16px;">🔄 同步设置</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left">
|
||
<div class="settings-item-icon blue">🔆</div>
|
||
<div><div class="settings-item-text">屏幕常亮</div><div class="settings-item-desc">编辑笔记时保持屏幕不熄灭</div></div>
|
||
</div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left">
|
||
<div class="settings-item-icon green">🔄</div>
|
||
<div><div class="settings-item-text">自动同步</div><div class="settings-item-desc">每天限制 5000 次(含上传/下载/同步)</div></div>
|
||
</div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
</div>
|
||
<div class="quota-bar"><div class="quota-bar-fill" style="width:12%;"></div></div>
|
||
<div class="quota-text">今日剩余 4,380 次</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">⚡ 同步选项</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon teal">⬇️</div><div class="settings-item-text">拉取同步</div></div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon orange">⬆️</div><div class="settings-item-text">推送同步</div></div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon purple">🔀</div><div><div class="settings-item-text">合并策略</div><div class="settings-item-desc">冲突时自动合并,超过1万字符截断</div></div></div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon blue">⏱</div><div class="settings-item-text">同步频率</div></div>
|
||
<div class="settings-item-right"><div class="segmented"><button class="segmented-btn active" onclick="selectSegment(this)">5秒</button><button class="segmented-btn" onclick="selectSegment(this)">10秒</button></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">📜 历史记录</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon green">📋</div><div class="settings-item-text">开启历史记录</div></div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item" onclick="showToast('查看历史记录')">
|
||
<div class="settings-item-left"><div class="settings-item-icon gray">🕐</div><div class="settings-item-text">显示历史记录</div></div>
|
||
<div class="settings-item-right"><span class="settings-item-value">3 条 ›</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">🎨 排版样式</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon purple">🖼</div><div class="settings-item-text">列表样式</div></div>
|
||
<div class="settings-item-right"><div class="segmented"><button class="segmented-btn active" onclick="switchLayout('grid',this)">网格</button><button class="segmented-btn" onclick="switchLayout('list',this)">列表</button><button class="segmented-btn" onclick="switchLayout('timeline',this)">时间线</button></div></div>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon pink">⭐</div><div><div class="settings-item-text">设为主要</div><div class="settings-item-desc">主要笔记长存在顶部</div></div></div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">📎 元数据</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon orange">🕐</div><div class="settings-item-text">添加时间</div></div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon green">📍</div><div class="settings-item-text">添加地点</div></div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon blue">📱</div><div class="settings-item-text">添加机型</div></div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">🔒 安全</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item locked-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon red">🔐</div><div><div class="settings-item-text">加密锁定</div><div class="settings-item-desc">后续开发</div></div></div>
|
||
<span class="locked-badge">即将推出</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-group-title" style="padding-left:16px;">👁 显示</div>
|
||
<div class="settings-group">
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon teal">#️⃣</div><div class="settings-item-text">显示行列</div></div>
|
||
<button class="toggle active" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon purple">📱</div><div class="settings-item-text">显示二维码</div></div>
|
||
<button class="toggle" onclick="this.classList.toggle('active')"></button>
|
||
</div>
|
||
<div class="settings-item locked-item">
|
||
<div class="settings-item-left"><div class="settings-item-icon gray">🔍</div><div><div class="settings-item-text">搜索仓库内容</div><div class="settings-item-desc">所在用户组无权限</div></div></div>
|
||
<span class="locked-badge">🔒 无权限</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 使用须知 - 10条 -->
|
||
<div class="notice-section">
|
||
<div class="notice-title">⚠️ 使用须知</div>
|
||
<ul class="notice-list">
|
||
<li>简单钥匙(如 123、abc)可能被他人修改,建议使用复杂钥匙</li>
|
||
<li>部分违规内容会被系统自动清空</li>
|
||
<li>超过1年未修改的笔记将被自动清空</li>
|
||
<li>单条笔记大小限制 1MB</li>
|
||
<li>请勿存储敏感信息(密码、密钥等)</li>
|
||
<li>数据通过 HTTPS 传输,但存储无加密</li>
|
||
<li>同一钥匙可被任何人访问和修改</li>
|
||
<li>每日API调用限额 5000 次,超出后暂停同步</li>
|
||
<li>笔记内容不做备份,删除后无法恢复</li>
|
||
<li>服务仅供临时记录,不保证数据持久性和完整性</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 笔记编辑页面 ========== -->
|
||
<div class="editor-page" id="editorPage">
|
||
<div class="nav-bar">
|
||
<div class="nav-bar-content">
|
||
<div class="nav-bar-left">
|
||
<button class="nav-btn" onclick="closeEditor()">‹</button>
|
||
<span class="nav-bar-title" id="editorTitle">📓 my-diary</span>
|
||
</div>
|
||
<div class="nav-bar-right">
|
||
<button class="nav-btn" onclick="showToast('更多操作')" title="更多">•••</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- URL Bar -->
|
||
<div class="editor-url-bar">
|
||
<span class="editor-url-base">ctc.s2ss.com/</span>
|
||
<span class="editor-url-key" id="editorUrlKey">my-diary</span>
|
||
</div>
|
||
|
||
<!-- Toolbar -->
|
||
<div class="editor-toolbar">
|
||
<button class="editor-toolbar-btn" onclick="showToast('撤销')" title="撤销">↩️</button>
|
||
<button class="editor-toolbar-btn" onclick="showToast('重做')" title="重做">↪️</button>
|
||
<div class="editor-toolbar-divider"></div>
|
||
<button class="editor-toolbar-btn" onclick="showToast('剪切')" title="剪切">✂️</button>
|
||
<button class="editor-toolbar-btn" onclick="showToast('复制')" title="复制">📋</button>
|
||
<button class="editor-toolbar-btn" onclick="showToast('粘贴')" title="粘贴">📄</button>
|
||
<div class="editor-toolbar-divider"></div>
|
||
<button class="editor-toolbar-btn" onclick="showToast('分享')" title="分享">🔗</button>
|
||
<button class="editor-toolbar-btn" onclick="showToast('二维码')" title="二维码">📱</button>
|
||
<div class="editor-toolbar-divider"></div>
|
||
<button class="editor-toolbar-btn" onclick="showToast('全屏')" title="全屏">⛶</button>
|
||
</div>
|
||
|
||
<!-- Status Bar -->
|
||
<div class="editor-status">
|
||
<div class="editor-status-left">
|
||
<span>行 1, 列 1</span>
|
||
</div>
|
||
<div class="editor-status-left">
|
||
<span id="editorCharCount">128 字符</span>
|
||
<span>·</span>
|
||
<span id="editorLineCount">5 行</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Editor Area -->
|
||
<textarea class="editor-textarea" id="editorTextarea" placeholder="开始输入..." oninput="updateEditorStats()">今天天气很好,适合出去散步。
|
||
|
||
下午去了公园,看到了很多花。
|
||
樱花开了,粉色的花瓣飘落在小径上。
|
||
|
||
明天计划:
|
||
- 上午写代码
|
||
- 下午去图书馆
|
||
- 晚上看电影</textarea>
|
||
|
||
<!-- Footer -->
|
||
<div class="editor-footer">
|
||
<div class="editor-footer-left">
|
||
<span>📍 北京</span>
|
||
<span>·</span>
|
||
<span>📱 iPhone 16 Pro</span>
|
||
</div>
|
||
<div class="editor-footer-right">
|
||
<div class="sync-dot syncing"></div>
|
||
<span>自动同步中</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== Theme Switcher ========== -->
|
||
<div class="theme-switcher">
|
||
<div class="theme-dot active" style="background:#F2F2F7;" onclick="setTheme('light')" title="浅色"></div>
|
||
<div class="theme-dot" style="background:#1C1C1E;" onclick="setTheme('dark')" title="深色"></div>
|
||
<div class="theme-dot" style="background:#000000;" onclick="setTheme('amoled')" title="AMOLED"></div>
|
||
<div style="height:8px;"></div>
|
||
<div class="theme-dot" style="background:#6C5CE7;" onclick="setAccent('purple')" title="薰衣草"></div>
|
||
<div class="theme-dot" style="background:#FF6B6B;" onclick="setAccent('coral')" title="珊瑚红"></div>
|
||
<div class="theme-dot" style="background:#00B894;" onclick="setAccent('teal')" title="薄荷绿"></div>
|
||
<div class="theme-dot" style="background:#0984E3;" onclick="setAccent('blue')" title="天空蓝"></div>
|
||
<div class="theme-dot" style="background:#E17055;" onclick="setAccent('orange')" title="暖阳橙"></div>
|
||
<div class="theme-dot" style="background:#FD79A8;" onclick="setAccent('pink')" title="樱花粉"></div>
|
||
</div>
|
||
|
||
<!-- ========== Layout Switcher ========== -->
|
||
<div class="layout-switcher">
|
||
<button class="layout-btn active" onclick="switchLayout('grid',this)" title="网格">⊞</button>
|
||
<button class="layout-btn" onclick="switchLayout('list',this)" title="列表">☰</button>
|
||
<button class="layout-btn" onclick="switchLayout('timeline',this)" title="时间线">⏱</button>
|
||
</div>
|
||
|
||
<!-- ========== Toast ========== -->
|
||
<div id="toast" style="position:fixed; bottom:80px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--glass-bg); backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px); color:var(--text-primary); padding:10px 20px; border-radius:20px; font:500 14px var(--font-family); box-shadow:var(--shadow-lg); opacity:0; transition:all 0.3s ease; z-index:999; pointer-events:none; border:0.5px solid var(--separator);"></div>
|
||
|
||
<script>
|
||
// ========== CTC API ==========
|
||
const API_BASE = 'https://ctc.s2ss.com';
|
||
// 本地笔记列表(存储在localStorage)
|
||
let noteList = JSON.parse(localStorage.getItem('ctc_notes') || '[]');
|
||
// 笔记变更记录(mtime快照)
|
||
let noteMtimes = JSON.parse(localStorage.getItem('ctc_mtimes') || '{}');
|
||
|
||
function saveNoteList() { localStorage.setItem('ctc_notes', JSON.stringify(noteList)); }
|
||
function saveMtimes() { localStorage.setItem('ctc_mtimes', JSON.stringify(noteMtimes)); }
|
||
|
||
// API: 读取笔记
|
||
async function apiRead(key) {
|
||
const resp = await fetch(`${API_BASE}/${key}?raw`);
|
||
if (!resp.ok) return null;
|
||
return await resp.text();
|
||
}
|
||
|
||
// API: 写入笔记
|
||
async function apiWrite(key, text) {
|
||
const resp = await fetch(`${API_BASE}/${key}?json`, { method: 'POST', body: `text=${encodeURIComponent(text)}`, headers: {'Content-Type':'application/x-www-form-urlencoded'} });
|
||
return await resp.json();
|
||
}
|
||
|
||
// API: 获取笔记信息
|
||
async function apiInfo(key) {
|
||
const resp = await fetch(`${API_BASE}/?info¬e=${key}`);
|
||
if (!resp.ok) return null;
|
||
return await resp.json();
|
||
}
|
||
|
||
// API: 批量检查变更
|
||
async function apiCheck(keys) {
|
||
if (!keys.length) return {};
|
||
const resp = await fetch(`${API_BASE}/?check&keys=${keys.join(',')}`);
|
||
return await resp.json();
|
||
}
|
||
|
||
// API: 删除笔记
|
||
async function apiDelete(key) {
|
||
const resp = await fetch(`${API_BASE}/?delete¬e=${key}`);
|
||
return await resp.json();
|
||
}
|
||
|
||
// API: 新建随机笔记
|
||
async function apiNew(text) {
|
||
const resp = await fetch(`${API_BASE}/?new&json&text=${encodeURIComponent(text)}`);
|
||
return await resp.json();
|
||
}
|
||
|
||
// ========== Theme ==========
|
||
function setTheme(theme) {
|
||
document.body.setAttribute('data-theme', theme);
|
||
document.querySelectorAll('.theme-switcher .theme-dot').forEach((d, i) => {
|
||
d.classList.toggle('active', i === ['light','dark','amoled'].indexOf(theme));
|
||
});
|
||
}
|
||
function setAccent(accent) { document.body.setAttribute('data-accent', accent); }
|
||
|
||
// ========== Layout Switch ==========
|
||
function switchLayout(mode, btn) {
|
||
document.getElementById('gridView').style.display = mode === 'grid' ? '' : 'none';
|
||
document.getElementById('listView').style.display = mode === 'list' ? '' : 'none';
|
||
document.getElementById('timelineView').style.display = mode === 'timeline' ? '' : 'none';
|
||
document.querySelectorAll('.layout-switcher .layout-btn').forEach(b => b.classList.remove('active'));
|
||
if (btn) btn.classList.add('active');
|
||
}
|
||
|
||
// ========== Pull to Refresh (接入真实API) ==========
|
||
let pullStartY = 0, isPulling = false, isRefreshing = false;
|
||
const mainContent = document.getElementById('mainContent');
|
||
const pullIndicator = document.getElementById('pullIndicator');
|
||
|
||
mainContent.addEventListener('touchstart', function(e) {
|
||
if (mainContent.scrollTop === 0 && !isRefreshing) {
|
||
pullStartY = e.touches[0].clientY;
|
||
isPulling = true;
|
||
}
|
||
});
|
||
mainContent.addEventListener('touchmove', function(e) {
|
||
if (!isPulling || isRefreshing) return;
|
||
const diff = e.touches[0].clientY - pullStartY;
|
||
if (diff > 0 && mainContent.scrollTop === 0) {
|
||
pullIndicator.style.padding = Math.min(diff / 2, 60) + 'px 0';
|
||
pullIndicator.style.opacity = Math.min(diff / 80, 1);
|
||
if (diff > 60) {
|
||
pullIndicator.classList.add('pulling');
|
||
document.getElementById('pullText').textContent = '释放刷新';
|
||
} else {
|
||
pullIndicator.classList.remove('pulling');
|
||
document.getElementById('pullText').textContent = '下拉刷新';
|
||
}
|
||
}
|
||
});
|
||
mainContent.addEventListener('touchend', function() {
|
||
if (!isPulling) return;
|
||
isPulling = false;
|
||
if (pullIndicator.classList.contains('pulling')) {
|
||
isRefreshing = true;
|
||
pullIndicator.classList.add('refreshing');
|
||
pullIndicator.classList.remove('pulling');
|
||
document.getElementById('pullText').textContent = '刷新中...';
|
||
doRefresh();
|
||
} else {
|
||
pullIndicator.style.padding = '12px 0';
|
||
pullIndicator.style.opacity = '0.5';
|
||
}
|
||
});
|
||
|
||
async function doRefresh() {
|
||
try {
|
||
const keys = noteList.map(n => n.key);
|
||
if (keys.length === 0) {
|
||
finishRefresh('暂无笔记');
|
||
return;
|
||
}
|
||
const result = await apiCheck(keys);
|
||
let changed = 0;
|
||
if (result.code === 1 && result.data) {
|
||
for (const [key, info] of Object.entries(result.data)) {
|
||
if (info && noteMtimes[key] && info.mtime > noteMtimes[key]) {
|
||
changed++;
|
||
}
|
||
if (info) noteMtimes[key] = info.mtime;
|
||
}
|
||
saveMtimes();
|
||
}
|
||
finishRefresh(changed > 0 ? `检测到 ${changed} 条变更` : '已刷新,无变更');
|
||
} catch(e) {
|
||
finishRefresh('刷新失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function finishRefresh(msg) {
|
||
pullIndicator.classList.remove('refreshing');
|
||
document.getElementById('pullText').textContent = '下拉刷新';
|
||
pullIndicator.style.padding = '12px 0';
|
||
pullIndicator.style.opacity = '0.5';
|
||
isRefreshing = false;
|
||
showToast(msg);
|
||
}
|
||
|
||
// ========== Swipe Actions (列表模式) ==========
|
||
let swipeStartX = 0, swipeCurrentX = 0, currentSwipeEl = null;
|
||
|
||
function handleTouchStart(e) {
|
||
const container = e.currentTarget.closest('.swipe-container');
|
||
if (currentSwipeEl && currentSwipeEl !== container) { resetSwipe(currentSwipeEl); }
|
||
currentSwipeEl = container;
|
||
swipeStartX = e.touches[0].clientX;
|
||
swipeCurrentX = 0;
|
||
}
|
||
function handleTouchMove(e) {
|
||
if (!currentSwipeEl) return;
|
||
const diff = swipeStartX - e.touches[0].clientX;
|
||
swipeCurrentX = Math.max(0, Math.min(diff, 260));
|
||
const content = currentSwipeEl.querySelector('.swipe-content');
|
||
content.style.transform = 'translateX(-' + swipeCurrentX + 'px)';
|
||
content.style.transition = 'none';
|
||
}
|
||
function handleTouchEnd(e) {
|
||
if (!currentSwipeEl) return;
|
||
const content = currentSwipeEl.querySelector('.swipe-content');
|
||
content.style.transition = '';
|
||
if (swipeCurrentX > 80) { content.style.transform = 'translateX(-260px)'; }
|
||
else { content.style.transform = 'translateX(0)'; currentSwipeEl = null; }
|
||
}
|
||
function resetSwipe(container) {
|
||
const content = container.querySelector('.swipe-content');
|
||
content.style.transition = '';
|
||
content.style.transform = 'translateX(0)';
|
||
}
|
||
document.addEventListener('click', function(e) {
|
||
if (currentSwipeEl && !e.target.closest('.swipe-container')) { resetSwipe(currentSwipeEl); currentSwipeEl = null; }
|
||
});
|
||
|
||
// ========== Action Sheet ==========
|
||
function openActionSheet(key) { document.getElementById('actionSheetOverlay').classList.add('active'); }
|
||
function closeActionSheet() { document.getElementById('actionSheetOverlay').classList.remove('active'); }
|
||
|
||
// ========== Add Sheet (接入API) ==========
|
||
function openAddSheet() {
|
||
document.getElementById('addSheet').classList.add('active');
|
||
setTimeout(() => document.getElementById('keyInput').focus(), 400);
|
||
}
|
||
function closeAddSheet() {
|
||
document.getElementById('addSheet').classList.remove('active');
|
||
document.getElementById('keyInput').value = '';
|
||
document.getElementById('contentInput').value = '';
|
||
document.getElementById('keyCount').textContent = '0';
|
||
document.getElementById('contentCount').textContent = '0';
|
||
document.getElementById('urlKeyPreview').textContent = '钥匙';
|
||
document.getElementById('urlKeyPreview').classList.add('empty');
|
||
document.getElementById('createBtn').disabled = true;
|
||
document.getElementById('keyHint').textContent = '仅支持数字和字母,2-16位';
|
||
document.getElementById('keyHint').classList.remove('error');
|
||
document.getElementById('keyInput').classList.remove('error');
|
||
}
|
||
function validateKey() {
|
||
const input = document.getElementById('keyInput');
|
||
const hint = document.getElementById('keyHint');
|
||
const preview = document.getElementById('urlKeyPreview');
|
||
const counter = document.getElementById('keyCount');
|
||
const btn = document.getElementById('createBtn');
|
||
let val = input.value.replace(/[^a-zA-Z0-9]/g, '');
|
||
if (val !== input.value) input.value = val;
|
||
counter.textContent = val.length;
|
||
preview.textContent = val || '钥匙';
|
||
preview.classList.toggle('empty', !val);
|
||
if (val.length > 0 && val.length < 2) {
|
||
hint.textContent = '钥匙至少需要2个字符'; hint.classList.add('error'); input.classList.add('error'); btn.disabled = true;
|
||
} else if (val.length >= 2) {
|
||
hint.textContent = '仅支持数字和字母,2-16位'; hint.classList.remove('error'); input.classList.remove('error'); btn.disabled = false;
|
||
} else {
|
||
hint.textContent = '仅支持数字和字母,2-16位'; hint.classList.remove('error'); input.classList.remove('error'); btn.disabled = true;
|
||
}
|
||
}
|
||
function updateContentCount() { document.getElementById('contentCount').textContent = document.getElementById('contentInput').value.length; }
|
||
|
||
async function createNote() {
|
||
const key = document.getElementById('keyInput').value;
|
||
const content = document.getElementById('contentInput').value;
|
||
const btn = document.getElementById('createBtn');
|
||
btn.disabled = true;
|
||
btn.textContent = '创建中...';
|
||
try {
|
||
const result = await apiWrite(key, content);
|
||
if (result.code === 1) {
|
||
// 添加到本地列表
|
||
noteList.push({ key, star: false, addedAt: Date.now() });
|
||
if (result.data) noteMtimes[key] = result.data.mtime;
|
||
saveNoteList();
|
||
saveMtimes();
|
||
showToast('创建成功: ctc.s2ss.com/' + key);
|
||
closeAddSheet();
|
||
} else {
|
||
showToast('创建失败: ' + (result.msg || '未知错误'));
|
||
btn.disabled = false;
|
||
btn.textContent = '创建';
|
||
}
|
||
} catch(e) {
|
||
showToast('网络错误: ' + e.message);
|
||
btn.disabled = false;
|
||
btn.textContent = '创建';
|
||
}
|
||
}
|
||
|
||
// ========== Settings ==========
|
||
function openSettings() { document.getElementById('settingsPage').classList.add('active'); }
|
||
function closeSettings() { document.getElementById('settingsPage').classList.remove('active'); }
|
||
function selectSegment(btn) { btn.parentElement.querySelectorAll('.segmented-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); }
|
||
|
||
// ========== Editor (接入API) ==========
|
||
let editorKey = '';
|
||
let editorSaveTimer = null;
|
||
|
||
async function openEditor(key) {
|
||
editorKey = key;
|
||
document.getElementById('editorTitle').textContent = '📓 ' + key;
|
||
document.getElementById('editorUrlKey').textContent = key;
|
||
document.getElementById('editorPage').classList.add('active');
|
||
// 从API加载内容
|
||
const ta = document.getElementById('editorTextarea');
|
||
ta.value = '加载中...';
|
||
ta.disabled = true;
|
||
try {
|
||
const content = await apiRead(key);
|
||
ta.value = content || '';
|
||
} catch(e) {
|
||
ta.value = '加载失败: ' + e.message;
|
||
}
|
||
ta.disabled = false;
|
||
updateEditorStats();
|
||
}
|
||
|
||
function closeEditor() {
|
||
document.getElementById('editorPage').classList.remove('active');
|
||
if (editorSaveTimer) { clearTimeout(editorSaveTimer); editorSaveTimer = null; }
|
||
editorKey = '';
|
||
}
|
||
|
||
function updateEditorStats() {
|
||
const ta = document.getElementById('editorTextarea');
|
||
const text = ta.value;
|
||
const chars = text.length;
|
||
const lines = text.split('\n').length;
|
||
document.getElementById('editorCharCount').textContent = chars + ' 字符';
|
||
document.getElementById('editorLineCount').textContent = lines + ' 行';
|
||
}
|
||
|
||
// 自动保存(2秒防抖)
|
||
document.getElementById('editorTextarea').addEventListener('input', function() {
|
||
updateEditorStats();
|
||
if (editorSaveTimer) clearTimeout(editorSaveTimer);
|
||
editorSaveTimer = setTimeout(async () => {
|
||
if (!editorKey) return;
|
||
const text = document.getElementById('editorTextarea').value;
|
||
try {
|
||
const result = await apiWrite(editorKey, text);
|
||
if (result.code === 1 && result.data) {
|
||
noteMtimes[editorKey] = result.data.mtime;
|
||
saveMtimes();
|
||
}
|
||
} catch(e) {
|
||
showToast('自动保存失败');
|
||
}
|
||
}, 2000);
|
||
});
|
||
|
||
// ========== Toast ==========
|
||
function showToast(msg) {
|
||
const toast = document.getElementById('toast');
|
||
toast.textContent = msg;
|
||
toast.style.opacity = '1';
|
||
toast.style.transform = 'translateX(-50%) translateY(0)';
|
||
clearTimeout(toast._timer);
|
||
toast._timer = setTimeout(() => {
|
||
toast.style.opacity = '0';
|
||
toast.style.transform = 'translateX(-50%) translateY(20px)';
|
||
}, 2000);
|
||
}
|
||
|
||
// Close modal on overlay click
|
||
document.getElementById('addSheet').addEventListener('click', function(e) { if (e.target === this) closeAddSheet(); });
|
||
</script>
|
||
</body>
|
||
</html>
|