feat: 新增导出功能,合并左侧操作按钮
- 新建记录输入内容超过20字时自动生成标题 - 左侧列表新增导出按钮(导出为Markdown文件) - 置顶、导出、删除三个按钮合并,hover时显示
This commit is contained in:
8
app.py
8
app.py
@@ -167,9 +167,17 @@ def api_update_note(note_id):
|
||||
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 新记录第一次输入内容时,自动生成标题
|
||||
old_content = note.get('content', '')
|
||||
need_generate_title = (note['title'] == '新记录' and len(content) >= 20)
|
||||
|
||||
note['content'] = content
|
||||
note['updated_at'] = now
|
||||
|
||||
# 如果是新记录第一次输入内容,异步生成标题
|
||||
if need_generate_title:
|
||||
threading.Thread(target=generate_title_async, args=(note_id, content)).start()
|
||||
|
||||
save_notes(notes)
|
||||
|
||||
return jsonify(note)
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
.fade-in { animation: fadeIn 0.3s ease; }
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
.gradient-bg { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); }
|
||||
.hidden-menu { display: none; }
|
||||
.note-item:hover .hidden-menu { display: flex; }
|
||||
.context-menu { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }
|
||||
.action-btn {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.note-item:hover .action-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 h-screen overflow-hidden">
|
||||
@@ -69,6 +73,9 @@
|
||||
<p id="currentTime" class="text-sm text-gray-500"></p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="exportCurrentNote()" class="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded-lg">
|
||||
<i class="ri-download-line mr-1"></i> 导出
|
||||
</button>
|
||||
<button onclick="togglePin()" id="pinBtn" class="px-3 py-1 text-sm text-gray-600 hover:bg-gray-50 rounded-lg">
|
||||
<i class="ri-pushpin-line mr-1"></i> <span id="pinBtnText">置顶</span>
|
||||
</button>
|
||||
@@ -130,21 +137,25 @@
|
||||
container.innerHTML = notes.map(n => `
|
||||
<div class="note-item relative p-4 cursor-pointer border-b ${currentNoteId === n.id ? 'active' : ''} ${n.pinned ? 'pinned' : ''}"
|
||||
onclick="selectNote('${n.id}')">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2 pr-16">
|
||||
${n.pinned ? '<i class="ri-pushpin-fill text-yellow-500 text-sm"></i>' : ''}
|
||||
<h3 class="font-medium text-gray-800 truncate flex-1">${n.title || '新记录'}</h3>
|
||||
</div>
|
||||
${showPreview && n.preview ? `<p class="text-sm text-gray-500 truncate mt-1">${n.preview}</p>` : ''}
|
||||
<p class="text-xs text-gray-400 mt-1">${n.updated_at}</p>
|
||||
|
||||
<!-- 隐藏菜单 -->
|
||||
<div class="hidden-menu context-menu gap-1">
|
||||
<!-- 操作按钮组 -->
|
||||
<div class="action-btn absolute right-2 top-1/2 -translate-y-1/2 flex gap-1">
|
||||
<button onclick="event.stopPropagation(); exportItem('${n.id}')"
|
||||
class="p-1.5 rounded hover:bg-gray-200 text-gray-500" title="导出">
|
||||
<i class="ri-download-line"></i>
|
||||
</button>
|
||||
<button onclick="event.stopPropagation(); togglePinItem('${n.id}')"
|
||||
class="p-1.5 rounded hover:bg-gray-200 text-gray-500" title="${n.pinned ? '取消置顶' : '置顶'}">
|
||||
<i class="ri-pushpin-${n.pinned ? 'fill text-yellow-500' : 'line'}"></i>
|
||||
class="p-1.5 rounded hover:bg-gray-200 text-gray-500 ${n.pinned ? 'text-yellow-500' : ''}" title="${n.pinned ? '取消置顶' : '置顶'}">
|
||||
<i class="${n.pinned ? 'ri-pushpin-fill' : 'ri-pushpin-line'}"></i>
|
||||
</button>
|
||||
<button onclick="event.stopPropagation(); deleteItem('${n.id}')"
|
||||
class="p-1.5 rounded hover:bg-red-100 text-red-500" title="删除">
|
||||
class="p-1.5 rounded hover:bg-red-100 text-gray-500 hover:text-red-500" title="删除">
|
||||
<i class="ri-delete-bin-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -309,6 +320,26 @@
|
||||
loadNotes();
|
||||
}
|
||||
|
||||
// 导出笔记
|
||||
async function exportItem(id) {
|
||||
const res = await fetch(`/api/notes/${id}`);
|
||||
const note = await res.json();
|
||||
|
||||
if (!note) return;
|
||||
|
||||
// 创建下载内容
|
||||
const content = `# ${note.title}\n\n创建时间: ${note.created_at}\n更新时间: ${note.updated_at}\n\n---\n\n${note.content}`;
|
||||
|
||||
// 创建Blob并下载
|
||||
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${note.title || '记录'}.md`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// 删除当前笔记
|
||||
async function deleteCurrentNote() {
|
||||
if (!currentNoteId) return;
|
||||
@@ -326,6 +357,27 @@
|
||||
loadNotes();
|
||||
}
|
||||
|
||||
// 导出当前笔记
|
||||
async function exportCurrentNote() {
|
||||
if (!currentNoteId) return;
|
||||
|
||||
const title = document.getElementById('titleText').textContent;
|
||||
const content = document.getElementById('editor').value;
|
||||
const timeInfo = document.getElementById('currentTime').textContent;
|
||||
|
||||
// 创建下载内容
|
||||
const exportContent = `# ${title}\n\n${timeInfo}\n\n---\n\n${content}`;
|
||||
|
||||
// 创建Blob并下载
|
||||
const blob = new Blob([exportContent], { type: 'text/markdown;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${title}.md`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// 定时检查标题更新(用于异步生成标题后刷新)
|
||||
function startTitlePolling() {
|
||||
if (titleUpdateTimer) clearInterval(titleUpdateTimer);
|
||||
|
||||
Reference in New Issue
Block a user