diff --git a/z.ai/notes/index.php b/z.ai/notes/index.php index 189725d..baa4ebd 100644 --- a/z.ai/notes/index.php +++ b/z.ai/notes/index.php @@ -170,17 +170,12 @@ function downloadAndSaveImage($url) { // 自动格式化链接 function autoFormatLinks($content) { - // 匹配非Markdown格式的URL - $pattern = '/(?exec(' + CREATE TABLE IF NOT EXISTS shares ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + note_id INTEGER NOT NULL, + share_token TEXT UNIQUE NOT NULL, + password TEXT, + view_limit INTEGER DEFAULT 0, + view_count INTEGER DEFAULT 0, + expires_at DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (note_id) REFERENCES notes (id) ON DELETE CASCADE + ) + '); + // 创建索引 $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_title ON notes(title)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_tags ON notes(tags)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created_at)'); $db->exec('CREATE INDEX IF NOT EXISTS idx_attachments_note_id ON attachments(note_id)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_shares_token ON shares(share_token)'); + $db->exec('CREATE INDEX IF NOT EXISTS idx_shares_note_id ON shares(note_id)'); return $db; } catch (Exception $e) { @@ -569,6 +581,75 @@ function isLoggedIn() { return isset($_SESSION['user_id']); } +// 创建分享 +function createShare($noteId, $password = null, $viewLimit = 0, $expiresAt = null) { + $db = getDB(); + + // 生成唯一的分享令牌 + $shareToken = bin2hex(random_bytes(16)); + + $stmt = $db->prepare('INSERT INTO shares (note_id, share_token, password, view_limit, expires_at) VALUES (?, ?, ?, ?, ?)'); + $stmt->bindValue(1, $noteId, SQLITE3_INTEGER); + $stmt->bindValue(2, $shareToken, SQLITE3_TEXT); + $stmt->bindValue(3, $password ? password_hash($password, PASSWORD_DEFAULT) : null, SQLITE3_TEXT); + $stmt->bindValue(4, $viewLimit, SQLITE3_INTEGER); + $stmt->bindValue(5, $expiresAt, SQLITE3_TEXT); + $stmt->execute(); + + return $shareToken; +} + +// 获取分享信息 +function getShareByToken($token) { + $db = getDB(); + $stmt = $db->prepare('SELECT * FROM shares WHERE share_token = ?'); + $stmt->bindValue(1, $token, SQLITE3_TEXT); + $result = $stmt->execute(); + $share = $result->fetchArray(SQLITE3_ASSOC); + + if ($share) { + // 检查是否过期 + if ($share['expires_at'] && strtotime($share['expires_at']) < time()) { + return null; + } + + // 检查查看次数限制 + if ($share['view_limit'] > 0 && $share['view_count'] >= $share['view_limit']) { + return null; + } + + // 增加查看次数 + $updateStmt = $db->prepare('UPDATE shares SET view_count = view_count + 1 WHERE id = ?'); + $updateStmt->bindValue(1, $share['id'], SQLITE3_INTEGER); + $updateStmt->execute(); + } + + return $share; +} + +// 获取笔记的分享列表 +function getNoteShares($noteId) { + $db = getDB(); + $stmt = $db->prepare('SELECT * FROM shares WHERE note_id = ? ORDER BY created_at DESC'); + $stmt->bindValue(1, $noteId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $shares = []; + + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $shares[] = $row; + } + + return $shares; +} + +// 删除分享 +function deleteShare($id) { + $db = getDB(); + $stmt = $db->prepare('DELETE FROM shares WHERE id = ?'); + $stmt->bindValue(1, $id, SQLITE3_INTEGER); + return $stmt->execute(); +} + // 处理登录 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_POST['action'] === 'init') { @@ -652,6 +733,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { } else { $error = '标题和内容不能为空'; } + } elseif ($_POST['action'] === 'create_share') { + if (!isLoggedIn()) { + header('Location: ?'); + exit; + } + + $noteId = intval($_POST['note_id'] ?? 0); + $password = trim($_POST['share_password'] ?? ''); + $viewLimit = intval($_POST['view_limit'] ?? 0); + $expiresIn = intval($_POST['expires_in'] ?? 0); + + // 计算过期时间 + $expiresAt = null; + if ($expiresIn > 0) { + $expiresAt = date('Y-m-d H:i:s', time() + $expiresIn * 3600); // 小时转换为秒 + } + + $shareToken = createShare($noteId, $password ?: null, $viewLimit, $expiresAt); + + if ($shareToken) { + $shareUrl = getBaseUrl() . '/' . getCurrentFile() . '?share=' . $shareToken; + $_SESSION['share_success'] = '分享链接已创建: ' . $shareUrl . ''; + } else { + $error = '创建分享失败'; + } + + header('Location: ?view=' . $noteId); + exit; } } @@ -671,6 +780,14 @@ if (isset($_GET['delete_attachment']) && isLoggedIn()) { exit; } +// 处理删除分享 +if (isset($_GET['delete_share']) && isLoggedIn()) { + $id = intval($_GET['delete_share']); + deleteShare($id); + header('Location: ' . $_SERVER['HTTP_REFERER']); + exit; +} + // 处理登出 if (isset($_GET['logout'])) { session_destroy(); @@ -686,6 +803,392 @@ if (isset($_GET['logout'])) { // 获取导航URL $homeUrl = getHomeUrl(); +// 处理分享访问 +if (isset($_GET['share'])) { + $shareToken = $_GET['share']; + $share = getShareByToken($shareToken); + + if (!$share) { + $shareError = '分享链接无效或已过期'; + } else { + $note = getNote($share['note_id']); + if (!$note) { + $shareError = '笔记不存在'; + } else { + // 检查是否需要密码 + if ($share['password'] && !isset($_SESSION['share_authenticated'][$shareToken])) { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'verify_share_password') { + $password = $_POST['share_password'] ?? ''; + if (password_verify($password, $share['password'])) { + $_SESSION['share_authenticated'][$shareToken] = true; + } else { + $shareError = '密码错误'; + } + } + + if (!isset($_SESSION['share_authenticated'][$shareToken])) { + // 显示密码输入页面 + ?> + + + + + + 密码验证 - <?php echo APP_NAME; ?> + + + +
+
+

+

此分享需要密码验证

+
+ + +
+ + +
+ +
+ + +
+ +
+
+ + + + + + + + + <?php echo htmlspecialchars($note['title']); ?> - <?php echo APP_NAME; ?> + + + +
+
+
+ + +
+
+
+ +
+
+
+
+
+ 创建于 + + · 更新于 + +
+
+
+ +
+ + +
+ +
+ + + +
+
+ +
+
+ + + + + @@ -1463,7 +1966,7 @@ if (isset($_GET['edit'])) { color: var(--text-secondary); } - .form-input, .form-textarea { + .form-input, .form-textarea, .form-select { width: 100%; padding: 10px; border: 1px solid var(--border-color); @@ -1475,7 +1978,7 @@ if (isset($_GET['edit'])) { transition: var(--transition); } - .form-input:focus, .form-textarea:focus { + .form-input:focus, .form-textarea:focus, .form-select:focus { outline: none; border-color: var(--accent-color); } @@ -1703,6 +2206,137 @@ if (isset($_GET['edit'])) { display: none; } + /* Share Section */ + .share-section { + margin-top: 20px; + padding: 15px; + background: var(--bg-tertiary); + border-radius: 6px; + } + + .share-title { + font-size: 14px; + font-weight: 600; + margin-bottom: 10px; + color: var(--text-primary); + } + + .share-list { + display: flex; + flex-direction: column; + gap: 10px; + } + + .share-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background: var(--bg-primary); + border-radius: 4px; + font-size: 13px; + transition: var(--transition); + } + + .share-item:hover { + background: var(--hover-bg); + } + + .share-info { + display: flex; + flex-direction: column; + gap: 5px; + } + + .share-link { + color: var(--accent-color); + text-decoration: none; + font-family: monospace; + word-break: break-all; + } + + .share-link:hover { + text-decoration: underline; + } + + .share-meta { + color: var(--text-muted); + font-size: 11px; + } + + .share-actions { + display: flex; + gap: 5px; + } + + .share-btn { + padding: 4px 8px; + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 12px; + transition: var(--transition); + } + + .share-btn:hover { + color: var(--accent-color); + } + + .share-btn.delete:hover { + color: var(--danger-color); + } + + .share-form { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 15px; + } + + .share-form-group { + flex: 1; + min-width: 150px; + } + + .share-form-label { + display: block; + margin-bottom: 5px; + color: var(--text-secondary); + font-size: 12px; + } + + .share-form-input { + width: 100%; + padding: 8px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + transition: var(--transition); + } + + .share-form-input:focus { + outline: none; + border-color: var(--accent-color); + } + + .share-form-btn { + padding: 8px 16px; + background: var(--accent-color); + color: white; + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + transition: var(--transition); + } + + .share-form-btn:hover { + opacity: 0.9; + } + /* Modal */ .modal { display: none; @@ -1767,6 +2401,21 @@ if (isset($_GET['edit'])) { margin-bottom: 20px; } + /* Success Message */ + .success-message { + background: var(--success-color); + color: white; + padding: 10px 15px; + border-radius: 6px; + margin-bottom: 20px; + font-size: 14px; + } + + .success-message a { + color: white; + text-decoration: underline; + } + /* Animations */ @keyframes slideUp { from { @@ -1818,6 +2467,14 @@ if (isset($_GET['edit'])) { padding: 4px 6px; font-size: 12px; } + + .share-form { + flex-direction: column; + } + + .share-form-group { + min-width: 100%; + } } @@ -1879,6 +2536,7 @@ if (isset($_GET['edit'])) {
+
@@ -1911,6 +2569,41 @@ if (isset($_GET['edit'])) { + + + +
+
分享链接
+
+ + + +
+
+ +
@@ -2053,6 +2746,12 @@ if (isset($_GET['edit'])) {
+ +
+ +
+ +
📝
@@ -2076,10 +2775,11 @@ if (isset($_GET['edit'])) {
+
-
...
+
...
@@ -2142,6 +2842,43 @@ if (isset($_GET['edit'])) {
+ + +