Add explicit post unarchive.
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 change-aware draft/published lifecycle, in-place published-frontmatter updates, metadata, tags, categories, links, media, and batch gallery-image import.
|
- Post and translation authoring with change-aware draft/published/archive lifecycle, explicit unarchive, in-place published-frontmatter updates, 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.
|
||||||
|
|||||||
@@ -199,17 +199,6 @@ pub fn update_post(
|
|||||||
post.content = published_body;
|
post.content = published_body;
|
||||||
}
|
}
|
||||||
post.status = PostStatus::Draft;
|
post.status = PostStatus::Draft;
|
||||||
} else if post.status == PostStatus::Archived {
|
|
||||||
if post.content.is_none() && !post.file_path.is_empty() {
|
|
||||||
let abs_path = data_dir.join(&post.file_path);
|
|
||||||
if abs_path.exists()
|
|
||||||
&& let Ok(file_content) = fs::read_to_string(&abs_path)
|
|
||||||
&& let Ok((_fm, body)) = read_post_file(&file_content)
|
|
||||||
{
|
|
||||||
post.content = Some(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post.status = PostStatus::Draft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post.updated_at = now_unix_ms();
|
post.updated_at = now_unix_ms();
|
||||||
@@ -389,26 +378,47 @@ pub fn archive_post(conn: &Connection, data_dir: &Path, post_id: &str) -> Engine
|
|||||||
"post is already archived".to_string(),
|
"post is already archived".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Reload content from filesystem if transitioning from published (content is NULL)
|
|
||||||
if post.status == PostStatus::Published && post.content.is_none() && !post.file_path.is_empty()
|
|
||||||
{
|
|
||||||
let abs_path = data_dir.join(&post.file_path);
|
|
||||||
if abs_path.exists()
|
|
||||||
&& let Ok(file_content) = fs::read_to_string(&abs_path)
|
|
||||||
&& let Ok((_fm, body)) = read_post_file(&file_content)
|
|
||||||
{
|
|
||||||
post.content = Some(body);
|
|
||||||
qp::update_post(conn, &post)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let now = now_unix_ms();
|
let now = now_unix_ms();
|
||||||
qp::update_post_status(conn, post_id, &PostStatus::Archived, now)?;
|
|
||||||
post.status = PostStatus::Archived;
|
post.status = PostStatus::Archived;
|
||||||
|
post.updated_at = now;
|
||||||
|
qp::update_post(conn, &post)?;
|
||||||
|
fts_index_post(conn, data_dir, &post)?;
|
||||||
emit_post(&post, NotificationAction::Updated);
|
emit_post(&post, NotificationAction::Updated);
|
||||||
crate::engine::embedding::sync_post_best_effort(conn, data_dir, &post);
|
crate::engine::embedding::sync_post_best_effort(conn, data_dir, &post);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore an archived post to an editable draft.
|
||||||
|
pub fn unarchive_post(conn: &Connection, data_dir: &Path, post_id: &str) -> EngineResult<Post> {
|
||||||
|
let mut post = qp::get_post_by_id(conn, post_id)?;
|
||||||
|
if post.status != PostStatus::Archived {
|
||||||
|
return Err(EngineError::Conflict(
|
||||||
|
"cannot unarchive a post that is not archived".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
post.content = Some(restore_content_for_unarchive(data_dir, &post));
|
||||||
|
post.status = PostStatus::Draft;
|
||||||
|
post.updated_at = now_unix_ms();
|
||||||
|
qp::update_post(conn, &post)?;
|
||||||
|
fts_index_post(conn, data_dir, &post)?;
|
||||||
|
emit_post(&post, NotificationAction::Updated);
|
||||||
|
crate::engine::embedding::sync_post_best_effort(conn, data_dir, &post);
|
||||||
|
Ok(post)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_content_for_unarchive(data_dir: &Path, post: &Post) -> String {
|
||||||
|
post.content.clone().unwrap_or_else(|| {
|
||||||
|
if post.file_path.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
fs::read_to_string(data_dir.join(&post.file_path))
|
||||||
|
.ok()
|
||||||
|
.and_then(|raw| read_post_file(&raw).ok().map(|(_, body)| body))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Discard unpublished draft changes and restore the published version.
|
/// Discard unpublished draft changes and restore the published version.
|
||||||
pub fn discard_post_draft(conn: &Connection, data_dir: &Path, post_id: &str) -> EngineResult<Post> {
|
pub fn discard_post_draft(conn: &Connection, data_dir: &Path, post_id: &str) -> EngineResult<Post> {
|
||||||
let mut post = qp::get_post_by_id(conn, post_id)?;
|
let mut post = qp::get_post_by_id(conn, post_id)?;
|
||||||
@@ -2370,6 +2380,65 @@ mod tests {
|
|||||||
|
|
||||||
let from_db = qp::get_post_by_id(db.conn(), &post.id).unwrap();
|
let from_db = qp::get_post_by_id(db.conn(), &post.id).unwrap();
|
||||||
assert_eq!(from_db.status, PostStatus::Archived);
|
assert_eq!(from_db.status, PostStatus::Archived);
|
||||||
|
assert_eq!(from_db.content, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_archived_post_keeps_archived_status() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
"p1",
|
||||||
|
"Archived",
|
||||||
|
Some("draft body"),
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
archive_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||||
|
|
||||||
|
let updated = update_post(
|
||||||
|
db.conn(),
|
||||||
|
dir.path(),
|
||||||
|
&post.id,
|
||||||
|
Some("Changed while archived"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some("changed body"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated.status, PostStatus::Archived);
|
||||||
|
assert_eq!(updated.title, "Changed while archived");
|
||||||
|
assert_eq!(updated.content.as_deref(), Some("changed body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unarchive_published_post_restores_body_from_file() {
|
||||||
|
let (db, dir) = setup();
|
||||||
|
let post = create_published_post(&db, &dir, "Published", "file body");
|
||||||
|
archive_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||||
|
let archived = qp::get_post_by_id(db.conn(), &post.id).unwrap();
|
||||||
|
assert_eq!(archived.content, None);
|
||||||
|
|
||||||
|
let unarchived = unarchive_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(unarchived.status, PostStatus::Draft);
|
||||||
|
assert_eq!(unarchived.content.as_deref(), Some("file body"));
|
||||||
|
assert!(unarchived.updated_at >= archived.updated_at);
|
||||||
|
let from_db = qp::get_post_by_id(db.conn(), &post.id).unwrap();
|
||||||
|
assert_eq!(from_db.status, PostStatus::Draft);
|
||||||
|
assert_eq!(from_db.content.as_deref(), Some("file body"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -335,6 +335,20 @@ fn representative_shared_mutations_emit_exactly_one_typed_event_each() {
|
|||||||
&post.id,
|
&post.id,
|
||||||
NotificationAction::Updated,
|
NotificationAction::Updated,
|
||||||
);
|
);
|
||||||
|
bds_core::engine::post::archive_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||||
|
assert_one_entity_event(
|
||||||
|
&subscription,
|
||||||
|
DomainEntity::Post,
|
||||||
|
&post.id,
|
||||||
|
NotificationAction::Updated,
|
||||||
|
);
|
||||||
|
bds_core::engine::post::unarchive_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||||
|
assert_one_entity_event(
|
||||||
|
&subscription,
|
||||||
|
DomainEntity::Post,
|
||||||
|
&post.id,
|
||||||
|
NotificationAction::Updated,
|
||||||
|
);
|
||||||
|
|
||||||
bds_core::engine::media::update_media(
|
bds_core::engine::media::update_media(
|
||||||
db.conn(),
|
db.conn(),
|
||||||
|
|||||||
@@ -628,7 +628,7 @@ fn persist_post_editor_preview_state_impl(
|
|||||||
Some(state.template_slug.clone())
|
Some(state.template_slug.clone())
|
||||||
};
|
};
|
||||||
post.do_not_translate = state.do_not_translate;
|
post.do_not_translate = state.do_not_translate;
|
||||||
if matches!(post.status, PostStatus::Published | PostStatus::Archived) {
|
if post.status == PostStatus::Published {
|
||||||
post.status = PostStatus::Draft;
|
post.status = PostStatus::Draft;
|
||||||
}
|
}
|
||||||
post.updated_at = bds_core::util::now_unix_ms();
|
post.updated_at = bds_core::util::now_unix_ms();
|
||||||
@@ -6875,6 +6875,33 @@ impl BdsApp {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unarchive_post_editor(&mut self, post_id: &str) -> Task<Message> {
|
||||||
|
if let Err(error) = self.persist_post_editor_state(post_id) {
|
||||||
|
self.notify_operation_failed("editor.unarchive", error);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let (Some(db), Some(data_dir)) = (self.db.as_ref(), self.data_dir.as_ref()) else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
match engine::post::unarchive_post(db.conn(), data_dir, post_id) {
|
||||||
|
Ok(post) => {
|
||||||
|
if let Some(editor) = self.post_editors.get_mut(post_id) {
|
||||||
|
editor.content = post.content.clone().unwrap_or_default();
|
||||||
|
editor.status = post.status;
|
||||||
|
editor.updated_at = post.updated_at;
|
||||||
|
editor.is_dirty = false;
|
||||||
|
editor.last_edit_at_ms = 0;
|
||||||
|
}
|
||||||
|
if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == post_id) {
|
||||||
|
tab.is_dirty = false;
|
||||||
|
}
|
||||||
|
self.notify(ToastLevel::Success, &t(self.ui_locale, "editor.unarchived"));
|
||||||
|
}
|
||||||
|
Err(error) => self.notify_operation_failed("editor.unarchive", error),
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_media_editor(&mut self, media_id: &str) -> Task<Message> {
|
fn delete_media_editor(&mut self, media_id: &str) -> Task<Message> {
|
||||||
let Some(db) = &self.db else {
|
let Some(db) = &self.db else {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
@@ -10803,6 +10830,86 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preview_persist_keeps_archived_canonical_post_archived() {
|
||||||
|
let (db, project, tmp) = setup();
|
||||||
|
let created = post::create_post(
|
||||||
|
db.conn(),
|
||||||
|
tmp.path(),
|
||||||
|
&project.id,
|
||||||
|
"Archived",
|
||||||
|
Some("Body"),
|
||||||
|
Vec::new(),
|
||||||
|
vec!["article".to_string()],
|
||||||
|
None,
|
||||||
|
Some("en"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
post::archive_post(db.conn(), tmp.path(), &created.id).unwrap();
|
||||||
|
let archived = bds_core::db::queries::post::get_post_by_id(db.conn(), &created.id).unwrap();
|
||||||
|
let mut editor = PostEditorState::from_post(
|
||||||
|
&archived,
|
||||||
|
"preview",
|
||||||
|
&["en".to_string()],
|
||||||
|
&[],
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
editor.title = "Changed while archived".to_string();
|
||||||
|
|
||||||
|
let result = persist_post_editor_preview_state_impl(&db, &editor).unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
PersistedPostState::Canonical(post) => {
|
||||||
|
assert_eq!(post.status, PostStatus::Archived);
|
||||||
|
assert_eq!(post.title, "Changed while archived");
|
||||||
|
}
|
||||||
|
PersistedPostState::Translation(_) => panic!("expected canonical post save"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn post_editor_unarchive_action_restores_draft_and_body() {
|
||||||
|
let (db, project, tmp) = setup();
|
||||||
|
let created = post::create_post(
|
||||||
|
db.conn(),
|
||||||
|
tmp.path(),
|
||||||
|
&project.id,
|
||||||
|
"Archived",
|
||||||
|
Some("File body"),
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
None,
|
||||||
|
Some("en"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
post::publish_post(db.conn(), tmp.path(), &created.id).unwrap();
|
||||||
|
post::archive_post(db.conn(), tmp.path(), &created.id).unwrap();
|
||||||
|
let archived = bds_core::db::queries::post::get_post_by_id(db.conn(), &created.id).unwrap();
|
||||||
|
assert_eq!(archived.content, None);
|
||||||
|
|
||||||
|
let mut app = make_app(db, project, &tmp);
|
||||||
|
open_post_editor(&mut app, &archived);
|
||||||
|
let _ = app.handle_post_editor_msg(PostEditorMsg::Unarchive);
|
||||||
|
|
||||||
|
let from_db = bds_core::db::queries::post::get_post_by_id(
|
||||||
|
app.db.as_ref().unwrap().conn(),
|
||||||
|
&created.id,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(from_db.status, PostStatus::Draft);
|
||||||
|
assert_eq!(from_db.content.as_deref(), Some("File body"));
|
||||||
|
assert_eq!(app.post_editors[&created.id].status, PostStatus::Draft);
|
||||||
|
assert_eq!(app.post_editors[&created.id].content, "File body");
|
||||||
|
assert!(app.toasts.iter().any(|toast| {
|
||||||
|
toast.level == ToastLevel::Success
|
||||||
|
&& toast.message == t(UiLocale::En, "editor.unarchived")
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preview_persist_bypasses_fts_for_translation_updates() {
|
fn preview_persist_bypasses_fts_for_translation_updates() {
|
||||||
let (db, project, tmp) = setup();
|
let (db, project, tmp) = setup();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ impl BdsApp {
|
|||||||
},
|
},
|
||||||
Save(String),
|
Save(String),
|
||||||
Publish(String),
|
Publish(String),
|
||||||
|
Unarchive(String),
|
||||||
Discard(String),
|
Discard(String),
|
||||||
ShowDelete {
|
ShowDelete {
|
||||||
tab_id: String,
|
tab_id: String,
|
||||||
@@ -208,6 +209,9 @@ impl BdsApp {
|
|||||||
PostEditorMsg::Publish => {
|
PostEditorMsg::Publish => {
|
||||||
deferred = DeferredPostAction::Publish(tab_id.clone());
|
deferred = DeferredPostAction::Publish(tab_id.clone());
|
||||||
}
|
}
|
||||||
|
PostEditorMsg::Unarchive => {
|
||||||
|
deferred = DeferredPostAction::Unarchive(tab_id.clone());
|
||||||
|
}
|
||||||
PostEditorMsg::Discard => {
|
PostEditorMsg::Discard => {
|
||||||
deferred = DeferredPostAction::Discard(tab_id.clone());
|
deferred = DeferredPostAction::Discard(tab_id.clone());
|
||||||
}
|
}
|
||||||
@@ -323,6 +327,7 @@ impl BdsApp {
|
|||||||
} => self.translate_post_to(&post_id, &target_language),
|
} => self.translate_post_to(&post_id, &target_language),
|
||||||
DeferredPostAction::Save(tab_id) => self.save_post_editor(&tab_id, true),
|
DeferredPostAction::Save(tab_id) => self.save_post_editor(&tab_id, true),
|
||||||
DeferredPostAction::Publish(tab_id) => self.publish_post_editor(&tab_id),
|
DeferredPostAction::Publish(tab_id) => self.publish_post_editor(&tab_id),
|
||||||
|
DeferredPostAction::Unarchive(tab_id) => self.unarchive_post_editor(&tab_id),
|
||||||
DeferredPostAction::Discard(tab_id) => self.discard_post_editor(&tab_id),
|
DeferredPostAction::Discard(tab_id) => self.discard_post_editor(&tab_id),
|
||||||
DeferredPostAction::ShowDelete { tab_id, name } => {
|
DeferredPostAction::ShowDelete { tab_id, name } => {
|
||||||
Task::done(Message::ShowModal(modal::ModalState::ConfirmDelete {
|
Task::done(Message::ShowModal(modal::ModalState::ConfirmDelete {
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ pub enum PostEditorMsg {
|
|||||||
RemoveCategory(String),
|
RemoveCategory(String),
|
||||||
Save,
|
Save,
|
||||||
Publish,
|
Publish,
|
||||||
|
Unarchive,
|
||||||
Discard,
|
Discard,
|
||||||
Delete,
|
Delete,
|
||||||
InsertLink,
|
InsertLink,
|
||||||
@@ -486,7 +487,7 @@ pub fn view<'a>(
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut header_action_items: Vec<Element<'a, Message>> = vec![
|
let mut header_action_items: Vec<Element<'a, Message>> = vec![
|
||||||
status_badge(&state.status),
|
status_badge(locale, &state.status),
|
||||||
quick_actions,
|
quick_actions,
|
||||||
button(
|
button(
|
||||||
text(t(locale, "common.save"))
|
text(t(locale, "common.save"))
|
||||||
@@ -511,6 +512,19 @@ pub fn view<'a>(
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if !on_translation && state.status == PostStatus::Archived {
|
||||||
|
header_action_items.push(
|
||||||
|
button(
|
||||||
|
text(t(locale, "editor.unarchive"))
|
||||||
|
.size(13)
|
||||||
|
.shaping(Shaping::Advanced),
|
||||||
|
)
|
||||||
|
.on_press(Message::PostEditor(PostEditorMsg::Unarchive))
|
||||||
|
.style(inputs::secondary_button)
|
||||||
|
.padding([6, 16])
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
if !on_translation && state.status == PostStatus::Draft && state.published_at.is_some() {
|
if !on_translation && state.status == PostStatus::Draft && state.published_at.is_some() {
|
||||||
header_action_items.push(
|
header_action_items.push(
|
||||||
button(
|
button(
|
||||||
@@ -1235,11 +1249,20 @@ fn content_actions_visible(mode: &str) -> bool {
|
|||||||
mode == "markdown"
|
mode == "markdown"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_badge<'a>(status: &PostStatus) -> Element<'a, Message> {
|
fn status_badge<'a>(locale: UiLocale, status: &PostStatus) -> Element<'a, Message> {
|
||||||
let (label, color) = match status {
|
let (label, color) = match status {
|
||||||
PostStatus::Draft => ("Draft", Color::from_rgb(0.8, 0.7, 0.2)),
|
PostStatus::Draft => (
|
||||||
PostStatus::Published => ("Published", Color::from_rgb(0.2, 0.7, 0.3)),
|
t(locale, "editor.statusDraft"),
|
||||||
PostStatus::Archived => ("Archived", Color::from_rgb(0.5, 0.5, 0.5)),
|
Color::from_rgb(0.8, 0.7, 0.2),
|
||||||
|
),
|
||||||
|
PostStatus::Published => (
|
||||||
|
t(locale, "editor.statusPublished"),
|
||||||
|
Color::from_rgb(0.2, 0.7, 0.3),
|
||||||
|
),
|
||||||
|
PostStatus::Archived => (
|
||||||
|
t(locale, "editor.statusArchived"),
|
||||||
|
Color::from_rgb(0.5, 0.5, 0.5),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
container(
|
container(
|
||||||
text(label)
|
text(label)
|
||||||
|
|||||||
@@ -419,6 +419,11 @@ editor-translate = Übersetzen
|
|||||||
editor-templateSlug = Vorlage
|
editor-templateSlug = Vorlage
|
||||||
editor-doNotTranslate = Nicht übersetzen
|
editor-doNotTranslate = Nicht übersetzen
|
||||||
editor-publish = Veröffentlichen
|
editor-publish = Veröffentlichen
|
||||||
|
editor-unarchive = Wiederherstellen
|
||||||
|
editor-unarchived = Als Entwurf wiederhergestellt.
|
||||||
|
editor-statusDraft = Entwurf
|
||||||
|
editor-statusPublished = Veröffentlicht
|
||||||
|
editor-statusArchived = Archiviert
|
||||||
editor-discard = Verwerfen
|
editor-discard = Verwerfen
|
||||||
editor-validate = Validieren
|
editor-validate = Validieren
|
||||||
editor-run = Ausführen
|
editor-run = Ausführen
|
||||||
|
|||||||
@@ -404,6 +404,11 @@ editor-translate = Translate
|
|||||||
editor-templateSlug = Template
|
editor-templateSlug = Template
|
||||||
editor-doNotTranslate = Do Not Translate
|
editor-doNotTranslate = Do Not Translate
|
||||||
editor-publish = Publish
|
editor-publish = Publish
|
||||||
|
editor-unarchive = Unarchive
|
||||||
|
editor-unarchived = Restored to draft.
|
||||||
|
editor-statusDraft = Draft
|
||||||
|
editor-statusPublished = Published
|
||||||
|
editor-statusArchived = Archived
|
||||||
editor-discard = Discard
|
editor-discard = Discard
|
||||||
editor-validate = Validate
|
editor-validate = Validate
|
||||||
editor-run = Run
|
editor-run = Run
|
||||||
|
|||||||
@@ -419,6 +419,11 @@ editor-translate = Traducir
|
|||||||
editor-templateSlug = Plantilla
|
editor-templateSlug = Plantilla
|
||||||
editor-doNotTranslate = No traducir
|
editor-doNotTranslate = No traducir
|
||||||
editor-publish = Publicar
|
editor-publish = Publicar
|
||||||
|
editor-unarchive = Desarchivar
|
||||||
|
editor-unarchived = Restaurado como borrador.
|
||||||
|
editor-statusDraft = Borrador
|
||||||
|
editor-statusPublished = Publicado
|
||||||
|
editor-statusArchived = Archivado
|
||||||
editor-discard = Descartar
|
editor-discard = Descartar
|
||||||
editor-validate = Validar
|
editor-validate = Validar
|
||||||
editor-run = Ejecutar
|
editor-run = Ejecutar
|
||||||
|
|||||||
@@ -419,6 +419,11 @@ editor-translate = Traduire
|
|||||||
editor-templateSlug = Modèle
|
editor-templateSlug = Modèle
|
||||||
editor-doNotTranslate = Ne pas traduire
|
editor-doNotTranslate = Ne pas traduire
|
||||||
editor-publish = Publier
|
editor-publish = Publier
|
||||||
|
editor-unarchive = Désarchiver
|
||||||
|
editor-unarchived = Restauré comme brouillon.
|
||||||
|
editor-statusDraft = Brouillon
|
||||||
|
editor-statusPublished = Publié
|
||||||
|
editor-statusArchived = Archivé
|
||||||
editor-discard = Annuler les modifications
|
editor-discard = Annuler les modifications
|
||||||
editor-validate = Valider
|
editor-validate = Valider
|
||||||
editor-run = Exécuter
|
editor-run = Exécuter
|
||||||
|
|||||||
@@ -419,6 +419,11 @@ editor-translate = Traduci
|
|||||||
editor-templateSlug = Modello
|
editor-templateSlug = Modello
|
||||||
editor-doNotTranslate = Non tradurre
|
editor-doNotTranslate = Non tradurre
|
||||||
editor-publish = Pubblica
|
editor-publish = Pubblica
|
||||||
|
editor-unarchive = Ripristina
|
||||||
|
editor-unarchived = Ripristinato come bozza.
|
||||||
|
editor-statusDraft = Bozza
|
||||||
|
editor-statusPublished = Pubblicato
|
||||||
|
editor-statusArchived = Archiviato
|
||||||
editor-discard = Scarta
|
editor-discard = Scarta
|
||||||
editor-validate = Valida
|
editor-validate = Valida
|
||||||
editor-run = Esegui
|
editor-run = Esegui
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ surface PostControlSurface {
|
|||||||
PublishPostRequested(post)
|
PublishPostRequested(post)
|
||||||
DeletePostRequested(post)
|
DeletePostRequested(post)
|
||||||
ArchivePostRequested(post)
|
ArchivePostRequested(post)
|
||||||
|
UnarchivePostRequested(post)
|
||||||
DiscardPostChangesRequested(post)
|
DiscardPostChangesRequested(post)
|
||||||
SyncPostFromFileRequested(post)
|
SyncPostFromFileRequested(post)
|
||||||
ImportOrphanPostFileRequested(project, relative_path)
|
ImportOrphanPostFileRequested(project, relative_path)
|
||||||
@@ -238,6 +239,15 @@ rule ArchivePost {
|
|||||||
ensures: post.status = archived
|
ensures: post.status = archived
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rule UnarchivePost {
|
||||||
|
when: UnarchivePostRequested(post)
|
||||||
|
requires: post.status = archived
|
||||||
|
ensures: post.status = draft
|
||||||
|
ensures: post.content = archived_database_content(post) ?? file_body(post.file_path) ?? ""
|
||||||
|
ensures: SearchIndexUpdated(post)
|
||||||
|
ensures: EmbeddingUpdated(post)
|
||||||
|
}
|
||||||
|
|
||||||
rule SyncPostFromFile {
|
rule SyncPostFromFile {
|
||||||
when: SyncPostFromFileRequested(post)
|
when: SyncPostFromFileRequested(post)
|
||||||
requires: post.file_path != ""
|
requires: post.file_path != ""
|
||||||
|
|||||||
Reference in New Issue
Block a user