Reopen published posts only for changes.
This commit is contained in:
@@ -7,7 +7,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
|||||||
## Available Features
|
## Available Features
|
||||||
|
|
||||||
- Native Iced desktop workspace with localized menus, tabs, anchored editor popovers, automatically paged post/media sidebars, dialogs, tasks, embedded Wry previews, and a live Pico CSS theme editor.
|
- Native Iced desktop workspace with localized menus, tabs, anchored editor popovers, automatically paged post/media sidebars, dialogs, tasks, embedded Wry previews, and a live Pico CSS theme editor.
|
||||||
- Post and translation authoring with draft/published lifecycle, metadata, tags, categories, links, media, and batch gallery-image import.
|
- Post and translation authoring with change-aware draft/published lifecycle, metadata, tags, categories, links, media, and batch gallery-image import.
|
||||||
- Media import, thumbnails, metadata translations, filters, validation, post assignment, and sequential drag-and-drop insertion into post editors.
|
- Media import, thumbnails, metadata translations, filters, validation, post assignment, and sequential drag-and-drop insertion into post editors.
|
||||||
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
||||||
- Post, Liquid template, and Lua script editing with dedicated syntax highlighting and explicit syntax-check feedback, including publish-time enforcement of the bDS2 Liquid tag/filter/operator subset, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms, including airplane-gated Git sync.
|
- Post, Liquid template, and Lua script editing with dedicated syntax highlighting and explicit syntax-check feedback, including publish-time enforcement of the bDS2 Liquid tag/filter/operator subset, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms, including airplane-gated Git sync.
|
||||||
|
|||||||
@@ -134,6 +134,29 @@ pub fn update_post(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let published_metadata_changed = post.status == PostStatus::Published
|
||||||
|
&& (title.is_some_and(|value| post.title != value)
|
||||||
|
|| excerpt.is_some_and(|value| post.excerpt.as_deref() != value)
|
||||||
|
|| tags
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|value| post.tags.as_slice() != value.as_slice())
|
||||||
|
|| categories
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|value| post.categories.as_slice() != value.as_slice())
|
||||||
|
|| author.is_some_and(|value| post.author.as_deref() != value)
|
||||||
|
|| language.is_some_and(|value| post.language.as_deref() != value)
|
||||||
|
|| do_not_translate.is_some_and(|value| post.do_not_translate != value));
|
||||||
|
let published_body = if post.status == PostStatus::Published
|
||||||
|
&& (published_metadata_changed || content.is_some())
|
||||||
|
{
|
||||||
|
resolve_post_fts_content(data_dir, &post)?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let reopen_published = published_metadata_changed
|
||||||
|
|| (post.status == PostStatus::Published
|
||||||
|
&& content.is_some_and(|value| published_body.as_deref() != Some(value)));
|
||||||
|
|
||||||
if let Some(t) = title {
|
if let Some(t) = title {
|
||||||
post.title = t.to_string();
|
post.title = t.to_string();
|
||||||
}
|
}
|
||||||
@@ -143,7 +166,9 @@ pub fn update_post(
|
|||||||
if let Some(exc) = excerpt {
|
if let Some(exc) = excerpt {
|
||||||
post.excerpt = exc.map(|s| s.to_string());
|
post.excerpt = exc.map(|s| s.to_string());
|
||||||
}
|
}
|
||||||
if let Some(c) = content {
|
if let Some(c) = content
|
||||||
|
&& (post.status != PostStatus::Published || reopen_published)
|
||||||
|
{
|
||||||
post.content = Some(c.to_string());
|
post.content = Some(c.to_string());
|
||||||
}
|
}
|
||||||
if let Some(t) = tags {
|
if let Some(t) = tags {
|
||||||
@@ -165,9 +190,12 @@ pub fn update_post(
|
|||||||
post.do_not_translate = dnt;
|
post.do_not_translate = dnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-transition published or archived post back to draft on content/metadata change
|
if reopen_published {
|
||||||
if post.status == PostStatus::Published || post.status == PostStatus::Archived {
|
if post.content.is_none() {
|
||||||
// Reload content from filesystem if content field is NULL (published state)
|
post.content = published_body;
|
||||||
|
}
|
||||||
|
post.status = PostStatus::Draft;
|
||||||
|
} else if post.status == PostStatus::Archived {
|
||||||
if post.content.is_none() && !post.file_path.is_empty() {
|
if post.content.is_none() && !post.file_path.is_empty() {
|
||||||
let abs_path = data_dir.join(&post.file_path);
|
let abs_path = data_dir.join(&post.file_path);
|
||||||
if abs_path.exists()
|
if abs_path.exists()
|
||||||
@@ -1171,6 +1199,23 @@ mod tests {
|
|||||||
(db, dir)
|
(db, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_published_post(db: &Database, dir: &TempDir, title: &str, body: &str) -> Post {
|
||||||
|
let post = create_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
"p1",
|
||||||
|
title,
|
||||||
|
Some(body),
|
||||||
|
vec!["tag".into()],
|
||||||
|
vec!["category".into()],
|
||||||
|
Some("Alice"),
|
||||||
|
Some("en"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
publish_post(db.conn(), dir.path(), &post.id).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_post_generates_slug_and_draft() {
|
fn create_post_generates_slug_and_draft() {
|
||||||
let (db, dir) = setup();
|
let (db, dir) = setup();
|
||||||
@@ -1372,6 +1417,111 @@ mod tests {
|
|||||||
assert!(updated.do_not_translate);
|
assert!(updated.do_not_translate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identical_published_update_stays_published() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_published_post(&db, &dir, "Published", "body");
|
||||||
|
|
||||||
|
let updated = update_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
&post.id,
|
||||||
|
Some("Published"),
|
||||||
|
None,
|
||||||
|
Some(None),
|
||||||
|
Some("body"),
|
||||||
|
Some(vec!["tag".into()]),
|
||||||
|
Some(vec!["category".into()]),
|
||||||
|
Some(Some("Alice")),
|
||||||
|
Some(Some("en")),
|
||||||
|
Some(None),
|
||||||
|
Some(false),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.status, PostStatus::Published);
|
||||||
|
assert_eq!(updated.content, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn published_title_change_reopens_draft_with_file_body() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_published_post(&db, &dir, "Published", "body");
|
||||||
|
|
||||||
|
let updated = update_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
&post.id,
|
||||||
|
Some("Changed"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.status, PostStatus::Draft);
|
||||||
|
assert_eq!(updated.content.as_deref(), Some("body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn published_template_slug_only_change_stays_published() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_published_post(&db, &dir, "Published", "body");
|
||||||
|
|
||||||
|
let updated = update_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
&post.id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(Some("page")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.status, PostStatus::Published);
|
||||||
|
assert_eq!(updated.template_slug.as_deref(), Some("page"));
|
||||||
|
assert_eq!(updated.content, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn published_content_change_reopens_draft_with_new_body() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_published_post(&db, &dir, "Published", "body");
|
||||||
|
|
||||||
|
let updated = update_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
&post.id,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some("changed body"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.status, PostStatus::Draft);
|
||||||
|
assert_eq!(updated.content.as_deref(), Some("changed body"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_post_slug_frozen_after_publish() {
|
fn update_post_slug_frozen_after_publish() {
|
||||||
let (db, dir) = setup();
|
let (db, dir) = setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user