headless_lms_mcp/
page_readiness.rs1use headless_lms_models::library::agent_changesets::course_state::{CourseState, PageRecord};
5use headless_lms_models::library::content_management::TEMPLATE_PLACEHOLDER_TEXTS;
6use headless_lms_utils::document_schema_processor::GutenbergBlock;
7use serde::Serialize;
8use uuid::Uuid;
9
10use crate::engine::blocks::{TEXT_ATTRIBUTES, walk_outside_exercises};
11use crate::text::plain_text;
12
13pub const PAGE_TITLE_BLOCKS: [&str; 2] =
15 ["moocfi/hero-section", "moocfi/landing-page-hero-section"];
16
17pub fn pages_with_content_changes<'a>(
19 staged: &'a CourseState,
20 live: &'a CourseState,
21) -> impl Iterator<Item = &'a PageRecord> {
22 staged.pages.iter().filter(|page| {
23 live.page(page.id)
24 .is_none_or(|live| live.content != page.content)
25 })
26}
27
28pub fn has_page_title(blocks: &[GutenbergBlock]) -> bool {
30 let mut found = false;
31 walk_outside_exercises(blocks, &mut |block, _| {
32 found = found
33 || (PAGE_TITLE_BLOCKS.contains(&block.name.as_str())
34 && block
35 .attributes
36 .get("title")
37 .and_then(serde_json::Value::as_str)
38 .is_some_and(|title| !plain_text(title).is_empty()));
39 });
40 found
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45pub struct PlaceholderText {
46 pub block: Uuid,
47 pub block_name: String,
48 pub attribute: &'static str,
49 pub text: &'static str,
50}
51
52pub fn placeholder_texts(blocks: &[GutenbergBlock]) -> Vec<PlaceholderText> {
54 let mut found = Vec::new();
55 walk_outside_exercises(blocks, &mut |block, _| {
56 for attribute in TEXT_ATTRIBUTES {
57 let Some(value) = block.attributes.get(attribute).and_then(|v| v.as_str()) else {
58 continue;
59 };
60 let text = plain_text(value);
61 if let Some(placeholder) = TEMPLATE_PLACEHOLDER_TEXTS
62 .iter()
63 .find(|placeholder| **placeholder == text)
64 {
65 found.push(PlaceholderText {
66 block: block.client_id,
67 block_name: block.name.clone(),
68 attribute,
69 text: placeholder,
70 });
71 }
72 }
73 });
74 found
75}