1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use uuid::Uuid;
6
7static DISALLOWED_BLOCKS_IN_TOP_LEVEL_PAGES: &[&str] = &[
8 "moocfi/exercise",
9 "moocfi/exercise-task",
10 "moocfi/exercises-in-chapter",
11 "moocfi/pages-in-chapter",
12 "moocfi/exercises-in-chapter",
13 "moocfi/chapter-progress",
14];
15
16pub use crate::attributes;
17use crate::prelude::*;
18
19#[macro_export]
20macro_rules! attributes {
21 () => {{
22 serde_json::Map::<String, serde_json::Value>::new()
23 }};
24 ($($name: tt: $value: expr_2021),+ $(,)*) => {{
25 let mut map = serde_json::Map::<String, serde_json::Value>::new();
26 $(map.insert($name.into(), serde_json::json!($value));)*
27 map
28 }};
29}
30
31#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
32pub struct GutenbergBlock {
33 #[serde(rename = "clientId")]
34 pub client_id: Uuid,
35 pub name: String,
36 #[serde(rename = "isValid")]
37 pub is_valid: bool,
38 pub attributes: Map<String, Value>,
39 #[serde(rename = "innerBlocks")]
40 pub inner_blocks: Vec<GutenbergBlock>,
41}
42
43impl GutenbergBlock {
44 pub fn paragraph(paragraph: &str) -> Self {
45 Self::block_with_name_and_attributes(
46 "core/paragraph",
47 attributes! {
48 "content": paragraph.to_string(),
49 "dropCap": false
50 },
51 )
52 }
53
54 pub fn empty_block_from_name(name: String) -> Self {
55 GutenbergBlock {
56 client_id: Uuid::new_v4(),
57 name,
58 is_valid: true,
59 attributes: Map::new(),
60 inner_blocks: vec![],
61 }
62 }
63 pub fn block_with_name_and_attributes(name: &str, attributes: Map<String, Value>) -> Self {
64 GutenbergBlock {
65 client_id: Uuid::new_v4(),
66 name: name.to_string(),
67 is_valid: true,
68 attributes,
69 inner_blocks: vec![],
70 }
71 }
72 pub fn block_with_name_attributes_and_inner_blocks(
73 name: &str,
74 attributes: Map<String, Value>,
75 inner_blocks: Vec<GutenbergBlock>,
76 ) -> Self {
77 GutenbergBlock {
78 client_id: Uuid::new_v4(),
79 name: name.to_string(),
80 is_valid: true,
81 attributes,
82 inner_blocks,
83 }
84 }
85 pub fn hero_section(title: &str, sub_title: &str) -> Self {
86 GutenbergBlock::block_with_name_and_attributes(
87 "moocfi/hero-section",
88 attributes! {
89 "title": title,
90 "subtitle": sub_title
91 },
92 )
93 }
94 pub fn landing_page_hero_section(title: &str, sub_title: &str) -> Self {
95 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
96 "moocfi/landing-page-hero-section",
97 attributes! {"title": title},
98 vec![GutenbergBlock::block_with_name_and_attributes(
99 "core/paragraph",
100 attributes! {
101 "align": "center",
102 "content": sub_title,
103 "dropCap": false,
104 "placeholder": "Insert short description of course..."
105 },
106 )],
107 )
108 }
109 pub fn course_objective_section() -> Self {
110 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
111 "moocfi/course-objective-section",
112 attributes! {
113 "title": "In this course you'll..."
114 },
115 vec![GutenbergBlock::block_with_name_attributes_and_inner_blocks(
116 "core/columns",
117 attributes! {
118 "isStackedOnMobile": true
119 },
120 vec![
121 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
122 "core/column",
123 attributes! {},
124 vec![
125 GutenbergBlock::block_with_name_and_attributes(
126 "core/heading",
127 attributes! {
128 "textAlign": "center",
129 "level": 3,
130 "content": "Objective #1",
131 "anchor": "objective-1",
132 },
133 ),
134 GutenbergBlock::block_with_name_and_attributes(
135 "core/paragraph",
136 attributes! {
137 "align": "center",
138 "dropCap": false,
139 "content": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."
140 },
141 ),
142 ],
143 ),
144 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
145 "core/column",
146 attributes! {},
147 vec![
148 GutenbergBlock::block_with_name_and_attributes(
149 "core/heading",
150 attributes! {
151 "textAlign": "center",
152 "level": 3,
153 "content": "Objective #2",
154 "anchor": "objective-2",
155 },
156 ),
157 GutenbergBlock::block_with_name_and_attributes(
158 "core/paragraph",
159 attributes! {
160 "align": "center",
161 "dropCap": false,
162 "content": "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..."
163 },
164 ),
165 ],
166 ),
167 GutenbergBlock::block_with_name_attributes_and_inner_blocks(
168 "core/column",
169 attributes! {},
170 vec![
171 GutenbergBlock::block_with_name_and_attributes(
172 "core/heading",
173 attributes! {
174 "textAlign": "center",
175 "level": 3,
176 "content": "Objective #3",
177 "anchor": "objective-3",
178 },
179 ),
180 GutenbergBlock::block_with_name_and_attributes(
181 "core/paragraph",
182 attributes! {
183 "align": "center",
184 "dropCap": false,
185 "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a tempor risus. Morbi at sapien."
186 },
187 ),
188 ],
189 ),
190 ],
191 )],
192 )
193 }
194
195 pub fn with_id(self, id: Uuid) -> Self {
196 Self {
197 client_id: id,
198 ..self
199 }
200 }
201}
202
203pub fn contains_blocks_not_allowed_in_top_level_pages(input: &[GutenbergBlock]) -> bool {
204 input
205 .iter()
206 .any(|block| DISALLOWED_BLOCKS_IN_TOP_LEVEL_PAGES.contains(&block.name.as_str()))
207}
208
209pub fn remap_ids_in_content(
210 content: &serde_json::Value,
211 chaged_ids: HashMap<Uuid, Uuid>,
212) -> UtilResult<serde_json::Value> {
213 let mut content_str = serde_json::to_string(content)?;
216 for (k, v) in chaged_ids.into_iter() {
217 content_str = content_str.replace(&k.to_string(), &v.to_string());
218 }
219 Ok(serde_json::from_str(&content_str)?)
220}
221
222pub fn remove_sensitive_attributes(input: Vec<GutenbergBlock>) -> Vec<GutenbergBlock> {
224 input
225 .into_iter()
226 .map(|mut block| {
227 if block.name == "moocfi/exercise-task" {
228 block.attributes = Map::new();
229 }
230 block.inner_blocks = remove_sensitive_attributes(block.inner_blocks);
231 block
232 })
233 .collect()
234}