Compare commits

...

28 Commits

Author SHA1 Message Date
a7590995e4 fix: Markdown开关改用data属性存储内容,修复切换失效问题 2026-04-24 13:04:44 +08:00
d7b4b48cc7 feat: 详情弹框内容添加Markdown显示开关 2026-04-24 12:58:09 +08:00
3f5284e067 feat: 统计卡片改一行显示+列表上方增加翻页 2026-04-24 12:24:54 +08:00
50d2e62694 feat: 添加置顶功能,置顶优先级最高 2026-04-24 12:16:39 +08:00
70dee494f3 fix: 点击未读数据后更新未读状态和类别计数 2026-04-22 23:49:49 +08:00
4b2a94002b feat: 添加未读提醒功能
- 数据列表中未读数据(views=0)显示特殊样式(黄色背景+红色边框+红点标记)
- 左侧类别显示未读数量(红色badge)
- API返回unread和unread_by_type统计数据
2026-04-22 23:44:22 +08:00
9eb872391e fix: 修复alert字符串中\n导致的JS语法错误 2026-04-22 23:33:32 +08:00
784830bd62 fix: 修复systemConfig变量定义顺序导致的JS错误 2026-04-22 23:24:08 +08:00
252bda696f feat: 添加系统配置功能
- AI自动添加的大模型接口配置(URL/Key/Model)
- 服务器连接状态刷新频率配置
- 数据列表每页显示条数配置
- 草稿自动保存间隔配置
- 显示统计/阅读次数开关
- 配置保存到localStorage
2026-04-22 23:09:39 +08:00
ef822d0eba feat: 编辑和新增弹框优化 - 导航按钮、固定底部、拖拽缩放、大尺寸 2026-04-22 22:47:59 +08:00
cc54fd52c6 feat: 导航按钮移到底部操作栏左侧 2026-04-22 22:00:22 +08:00
e4115b8baa fix: 详情弹框默认宽度改为800px 2026-04-22 21:56:32 +08:00
b99e3e88c9 fix: 详情弹框默认宽度改为600px,可拖拽放大到95vw 2026-04-22 21:53:38 +08:00
35198a8edb feat: 详情弹框添加JavaScript拖拽缩放功能 2026-04-22 18:52:26 +08:00
c9142c3f8a feat: 详情弹框尺寸优化 - 更宽、占满屏幕、支持拖拽缩放 2026-04-22 18:46:07 +08:00
9797ddf3f7 fix: 导航按钮改为absolute定位,显示在内容区域右上角 2026-04-22 18:40:56 +08:00
b92239fb1b feat(v4.4.0): 详情弹框添加导航按钮和固定底部操作栏
- 右侧固定显示向上/向下导航按钮
- 底部操作栏(转为待办、编辑、关闭)固定悬浮
2026-04-22 18:35:20 +08:00
105f4d5492 feat(v4.3.0): 新增/编辑弹框中添加文件夹选择
- 新增数据时可选择所属文件夹
- 编辑数据时可更改所属文件夹
- 切换类型时自动更新文件夹列表
2026-04-22 18:08:41 +08:00
e92349e111 feat(v4.2.1): 文件夹按钮样式优化+新增数据到文件夹功能
- hover文件夹时按钮悬浮在右侧,高度一致
- 新增数据按钮(绿色+)直接添加数据到该文件夹
2026-04-22 16:59:27 +08:00
0912d658b8 feat(v4.2.0): 文件夹支持重命名和删除
- hover文件夹时显示编辑和删除按钮
- 编辑按钮打开重命名模态框
- 删除按钮确认后删除,数据移到未分类
2026-04-22 16:27:38 +08:00
8e63db4424 fix(v4.1.1): 待办事务功能优化
- 修复完成状态切换问题(可来回切换完成/未完成)
- 添加全部/未完成/已完成过滤按钮
- 翻页时保持弹窗滚动位置
2026-04-22 15:54:10 +08:00
0335937312 feat(v4.1.0): 文本类别添加待办事务功能
- 可在文本数据详情中追加待办事务
- 支持事务内容、剩余天数(默认1天)、完成状态
- 历史事务按时间倒序排列,显示创建/更新时间
- 分页显示,每页10条
2026-04-22 13:00:00 +08:00
6a0f8d7196 feat(v4.0.0): 快速插入支持选中位置插入
- 先选中部分文本,再双击可在选中位置前/后插入
- 支持替换选中内容
- 自动识别选中文本并在原内容中定位
2026-04-22 12:49:37 +08:00
407a63f3cf fix: 修复JS正则表达式在模板字符串中被破坏的问题 2026-04-22 12:22:07 +08:00
eec8b477be feat: 详情弹框内容支持Markdown格式显示 2026-04-22 12:06:42 +08:00
489e95d677 fix: 修复显示模式切换按钮无效问题(添加currentItems变量) 2026-04-22 12:02:39 +08:00
47cbcb25bc feat: 添加显示模式切换按钮(单行/双行) 2026-04-22 11:56:59 +08:00
527c411d87 fix: 卡片布局自适应,操作按钮不再被挤出可视区域 2026-04-22 11:35:13 +08:00
2 changed files with 1804 additions and 107 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -187,6 +187,31 @@ class Database:
cursor.execute("CREATE INDEX IF NOT EXISTS idx_folders_type ON folders(type)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id)")
# 检查并添加 is_pinned 字段(置顶功能,兼容旧数据库)
try:
cursor.execute("SELECT is_pinned FROM items LIMIT 1")
except sqlite3.OperationalError:
cursor.execute("ALTER TABLE items ADD COLUMN is_pinned INTEGER DEFAULT 0")
# 创建 is_pinned 索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_pinned ON items(is_pinned)")
# 待办事务表关联到文本类别的items
cursor.execute("""
CREATE TABLE IF NOT EXISTS todo_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id INTEGER NOT NULL,
content TEXT NOT NULL,
remaining_days INTEGER DEFAULT 1,
is_completed INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
)
""")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_todo_events_item ON todo_events(item_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_todo_events_created ON todo_events(created_at)")
conn.commit()
# ============ Item 操作 ============
@@ -289,18 +314,16 @@ class Database:
if conditions:
query += " WHERE " + " AND ".join(conditions)
# 排序逻辑
# 排序逻辑:置顶 > 关注 > 创建时间
if sort_by == 'updated_at':
order_field = 'i.updated_at'
elif sort_by == 'created_at':
order_field = 'i.created_at'
else:
# 默认:重点关注优先 + 创建时间降序
# 默认:置顶优先 + 关注优先 + 创建时间降序
order_field = 'i.created_at'
order_dir = 'DESC' if (sort_order == 'asc' or sort_order is None) else 'ASC'
# 这里反转逻辑:用户选择"降序"时用DESC选择"升序"时用ASC
# 确定排序方向
if sort_order == 'asc':
order_dir = 'ASC'
elif sort_order == 'desc':
@@ -308,12 +331,12 @@ class Database:
else:
order_dir = 'DESC' # 默认降序
# 如果有指定排序字段,按该字段排序;否则默认重点关注优先
# 如果有指定排序字段,按该字段排序,但置顶始终优先
if sort_by:
query += f" ORDER BY {order_field} {order_dir} LIMIT ? OFFSET ?"
query += f" ORDER BY i.is_pinned DESC, {order_field} {order_dir} LIMIT ? OFFSET ?"
else:
# 默认:重点关注优先,然后创建时间降序
query += f" ORDER BY i.is_starred DESC, i.created_at DESC LIMIT ? OFFSET ?"
# 默认:置顶 > 关注 > 创建时间降序
query += f" ORDER BY i.is_pinned DESC, i.is_starred DESC, i.created_at DESC LIMIT ? OFFSET ?"
params.extend([limit, offset])
cursor.execute(query, params)
@@ -375,7 +398,7 @@ class Database:
def update_item(self, item_id: int, **kwargs) -> bool:
"""更新条目"""
allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note', 'is_starred']
allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note', 'is_starred', 'folder_id']
update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields}
# 只有 tags 变化也算有效更新
@@ -501,6 +524,31 @@ class Database:
conn.commit()
return cursor.rowcount > 0
def toggle_pin(self, item_id: int) -> bool:
"""切换置顶状态"""
with self.get_conn() as conn:
cursor = conn.cursor()
# 先获取当前状态
cursor.execute("SELECT is_pinned FROM items WHERE id = ?", (item_id,))
row = cursor.fetchone()
if not row:
return False
new_status = 0 if row['is_pinned'] else 1
now = datetime.now().isoformat()
cursor.execute("UPDATE items SET is_pinned = ?, updated_at = ? WHERE id = ?", (new_status, now, item_id))
conn.commit()
return True
def set_pin(self, item_id: int, pinned: bool = True) -> bool:
"""设置置顶状态"""
with self.get_conn() as conn:
cursor = conn.cursor()
now = datetime.now().isoformat()
cursor.execute("UPDATE items SET is_pinned = ?, updated_at = ? WHERE id = ?", (1 if pinned else 0, now, item_id))
conn.commit()
return cursor.rowcount > 0
def increment_views(self, item_id: int) -> bool:
"""增加阅读数"""
with self.get_conn() as conn:
@@ -903,15 +951,23 @@ class Database:
stats = {}
# 总数
cursor.execute("SELECT COUNT(*) as count FROM items")
cursor.execute("SELECT COUNT(*) as count FROM items WHERE is_deleted = 0")
stats['total'] = cursor.fetchone()['count']
# 按类型统计
cursor.execute("SELECT type, COUNT(*) as count FROM items GROUP BY type")
cursor.execute("SELECT type, COUNT(*) as count FROM items WHERE is_deleted = 0 GROUP BY type")
stats['by_type'] = {row['type']: row['count'] for row in cursor.fetchall()}
# 未读数量统计views = 0
cursor.execute("SELECT COUNT(*) as count FROM items WHERE is_deleted = 0 AND (views IS NULL OR views = 0)")
stats['unread'] = cursor.fetchone()['count']
# 按类型统计未读数量
cursor.execute("SELECT type, COUNT(*) as count FROM items WHERE is_deleted = 0 AND (views IS NULL OR views = 0) GROUP BY type")
stats['unread_by_type'] = {row['type']: row['count'] for row in cursor.fetchall()}
# 待办状态统计
cursor.execute("SELECT status, COUNT(*) as count FROM items WHERE type = 'todo' GROUP BY status")
cursor.execute("SELECT status, COUNT(*) as count FROM items WHERE type = 'todo' AND is_deleted = 0 GROUP BY status")
stats['todo_status'] = {row['status']: row['count'] for row in cursor.fetchall()}
# 标签数
@@ -1166,6 +1222,93 @@ class Database:
# 删除
self.delete_backup(backup['name'])
# ============ Todo Events 待办事务操作 ============
def create_todo_event(self, item_id: int, content: str, remaining_days: int = 1) -> int:
"""创建待办事务"""
self._ensure_init()
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO todo_events (item_id, content, remaining_days, is_completed, created_at, updated_at)
VALUES (?, ?, ?, 0, ?, ?)
""", (item_id, content, remaining_days, now, now))
conn.commit()
return cursor.lastrowid
def list_todo_events(self, item_id: int, limit: int = 10, offset: int = 0) -> List[Dict[str, Any]]:
"""列出待办事务(按时间倒序)"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM todo_events
WHERE item_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
""", (item_id, limit, offset))
rows = cursor.fetchall()
return [dict(row) for row in rows]
def count_todo_events(self, item_id: int) -> int:
"""计算待办事务总数"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) as count FROM todo_events WHERE item_id = ?", (item_id,))
return cursor.fetchone()['count']
def get_todo_event(self, event_id: int) -> Optional[Dict[str, Any]]:
"""获取单个待办事务"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM todo_events WHERE id = ?", (event_id,))
row = cursor.fetchone()
return dict(row) if row else None
def update_todo_event(self, event_id: int, content: str = None, remaining_days: int = None, is_completed: bool = None) -> bool:
"""更新待办事务"""
self._ensure_init()
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
# 获取当前数据
cursor.execute("SELECT * FROM todo_events WHERE id = ?", (event_id,))
row = cursor.fetchone()
if not row:
return False
# 更新字段
new_content = content if content is not None else row['content']
new_days = remaining_days if remaining_days is not None else row['remaining_days']
new_completed = 1 if is_completed is True else (0 if is_completed is False else row['is_completed'])
cursor.execute("""
UPDATE todo_events
SET content = ?, remaining_days = ?, is_completed = ?, updated_at = ?
WHERE id = ?
""", (new_content, new_days, new_completed, now, event_id))
conn.commit()
return True
def delete_todo_event(self, event_id: int) -> bool:
"""删除待办事务"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM todo_events WHERE id = ?", (event_id,))
conn.commit()
return cursor.rowcount > 0
# 全局数据库实例
db = Database()