Skip to content

Commit d9d6794

Browse files
committed
v1.0.6
1 parent 2ed82a1 commit d9d6794

File tree

9 files changed

+229
-21
lines changed

9 files changed

+229
-21
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "DeepLab",
33
"private": true,
4-
"version": "1.0.5",
4+
"version": "1.0.6",
55
"type": "module",
66
"packageManager": "pnpm@10.30.1",
77
"scripts": {

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "deeplab"
3-
version = "1.0.5"
3+
version = "1.0.6"
44
description = "AI科研辅助平台"
55
authors = ["timechess"]
66
edition = "2021"

src-tauri/src/db.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,22 +1660,43 @@ pub async fn update_note_content_and_links(
16601660
id: i64,
16611661
title: &str,
16621662
content: &str,
1663+
expected_updated_at: Option<&str>,
16631664
links: &[NoteLinkRefInput],
16641665
) -> Result<NoteDetailDto, String> {
16651666
ensure_note_links_schema(pool).await?;
16661667
let mut tx = pool.begin().await.map_err(|e| e.to_string())?;
1667-
let result = sqlx::query(
1668-
"UPDATE notes
1669-
SET title = ?1, content = ?2, updatedAt = CURRENT_TIMESTAMP
1670-
WHERE id = ?3",
1671-
)
1672-
.bind(title)
1673-
.bind(content)
1674-
.bind(id)
1675-
.execute(&mut *tx)
1676-
.await
1677-
.map_err(|e| e.to_string())?;
1668+
let result = if let Some(expected) = expected_updated_at {
1669+
sqlx::query(
1670+
"UPDATE notes
1671+
SET title = ?1, content = ?2, updatedAt = strftime('%Y-%m-%d %H:%M:%f', 'now')
1672+
WHERE id = ?3 AND updatedAt = ?4",
1673+
)
1674+
.bind(title)
1675+
.bind(content)
1676+
.bind(id)
1677+
.bind(expected)
1678+
.execute(&mut *tx)
1679+
.await
1680+
.map_err(|e| e.to_string())?
1681+
} else {
1682+
sqlx::query(
1683+
"UPDATE notes
1684+
SET title = ?1, content = ?2, updatedAt = strftime('%Y-%m-%d %H:%M:%f', 'now')
1685+
WHERE id = ?3",
1686+
)
1687+
.bind(title)
1688+
.bind(content)
1689+
.bind(id)
1690+
.execute(&mut *tx)
1691+
.await
1692+
.map_err(|e| e.to_string())?
1693+
};
16781694
if result.rows_affected() == 0 {
1695+
if expected_updated_at.is_some() {
1696+
return Err(String::from(
1697+
"note was modified by another save, please reload and retry",
1698+
));
1699+
}
16791700
return Err(String::from("note not found"));
16801701
}
16811702

src-tauri/src/notes.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
NoteUpsertInput, NoteWorkReportOptionDto,
1111
},
1212
};
13+
use serde_json::Value;
1314
use tauri::State;
1415

1516
#[tauri::command]
@@ -59,8 +60,17 @@ pub async fn update_note_content(
5960
) -> Result<NoteDetailDto, String> {
6061
let title = normalize_title(&input.title)?;
6162
let content = normalize_content(&input.content)?;
63+
let expected_updated_at = normalize_expected_updated_at(input.expected_updated_at);
6264
let links = normalize_links(input.links);
63-
update_note_content_and_links(&state.pool, id, &title, &content, &links).await
65+
update_note_content_and_links(
66+
&state.pool,
67+
id,
68+
&title,
69+
&content,
70+
expected_updated_at.as_deref(),
71+
&links,
72+
)
73+
.await
6474
}
6575

6676
#[tauri::command]
@@ -113,9 +123,82 @@ fn normalize_content(content: &str) -> Result<String, String> {
113123
if trimmed.is_empty() {
114124
return Err(String::from("note content cannot be empty"));
115125
}
126+
if is_effectively_empty_note_document(trimmed)? {
127+
return Err(String::from(
128+
"note content is empty and was blocked to prevent accidental overwrite",
129+
));
130+
}
116131
Ok(trimmed.to_string())
117132
}
118133

134+
fn normalize_expected_updated_at(value: Option<String>) -> Option<String> {
135+
value.and_then(|raw| {
136+
let trimmed = raw.trim();
137+
if trimmed.is_empty() {
138+
None
139+
} else {
140+
Some(trimmed.to_string())
141+
}
142+
})
143+
}
144+
145+
fn is_effectively_empty_note_document(content: &str) -> Result<bool, String> {
146+
let value: Value =
147+
serde_json::from_str(content).map_err(|_| String::from("note content is invalid JSON"))?;
148+
let Some(root) = value.as_object() else {
149+
return Ok(false);
150+
};
151+
let root_type = root.get("type").and_then(Value::as_str).unwrap_or("");
152+
if root_type != "doc" {
153+
return Ok(false);
154+
}
155+
Ok(!node_has_meaningful_content(&value))
156+
}
157+
158+
fn node_has_meaningful_content(node: &Value) -> bool {
159+
let Some(map) = node.as_object() else {
160+
return false;
161+
};
162+
let node_type = map.get("type").and_then(Value::as_str).unwrap_or("");
163+
if node_type == "text" {
164+
return map
165+
.get("text")
166+
.and_then(Value::as_str)
167+
.map(|text| !text.trim().is_empty())
168+
.unwrap_or(false);
169+
}
170+
if node_type == "noteReference" {
171+
return true;
172+
}
173+
if node_type == "image" {
174+
return map
175+
.get("attrs")
176+
.and_then(Value::as_object)
177+
.and_then(|attrs| attrs.get("src"))
178+
.and_then(Value::as_str)
179+
.map(|src| !src.trim().is_empty())
180+
.unwrap_or(false);
181+
}
182+
183+
if let Some(attrs) = map.get("attrs").and_then(Value::as_object) {
184+
let latex = attrs
185+
.get("latex")
186+
.or_else(|| attrs.get("value"))
187+
.or_else(|| attrs.get("text"))
188+
.and_then(Value::as_str)
189+
.map(|s| !s.trim().is_empty())
190+
.unwrap_or(false);
191+
if latex {
192+
return true;
193+
}
194+
}
195+
196+
if let Some(children) = map.get("content").and_then(Value::as_array) {
197+
return children.iter().any(node_has_meaningful_content);
198+
}
199+
false
200+
}
201+
119202
fn normalize_links(links: Vec<NoteLinkRefInput>) -> Vec<NoteLinkRefInput> {
120203
let mut out = Vec::new();
121204
for item in links {

src-tauri/src/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ pub struct NoteUpsertInput {
347347
pub title: String,
348348
pub content: String,
349349
#[serde(default)]
350+
pub expected_updated_at: Option<String>,
351+
#[serde(default)]
350352
pub links: Vec<NoteLinkRefInput>,
351353
}
352354

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "DeepLab",
4-
"version": "1.0.5",
4+
"version": "1.0.6",
55
"identifier": "deeplab",
66
"build": {
77
"beforeDevCommand": "pnpm dev:next",

0 commit comments

Comments
 (0)