1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::commands::workspace::resolve_workspace;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
8
9#[derive(Subcommand)]
10pub enum DocCommands {
11 List {
13 #[arg(long)]
15 creator: Option<String>,
16 #[arg(long)]
18 archived: bool,
19 },
20 Create {
22 #[arg(long)]
24 name: String,
25 #[arg(long)]
27 visibility: Option<String>,
28 #[arg(long)]
30 parent_type: Option<String>,
31 #[arg(long)]
33 parent_id: Option<String>,
34 },
35 Get {
37 id: String,
39 },
40 Pages {
42 id: String,
44 #[arg(long)]
46 content: bool,
47 #[arg(long)]
49 max_depth: Option<u32>,
50 },
51 #[command(name = "add-page")]
53 AddPage {
54 doc_id: String,
56 #[arg(long)]
58 name: String,
59 #[arg(long)]
61 parent_page: Option<String>,
62 #[arg(long)]
64 content: Option<String>,
65 },
66 Page {
68 doc_id: String,
70 page_id: String,
72 },
73 #[command(name = "edit-page")]
75 EditPage {
76 doc_id: String,
78 page_id: String,
80 #[arg(long)]
82 content: String,
83 #[arg(long, default_value = "replace")]
85 mode: String,
86 },
87}
88
89const DOC_FIELDS: &[&str] = &["id", "name", "visibility", "date_created"];
90const PAGE_FIELDS: &[&str] = &["id", "name", "date_created", "date_updated"];
91
92pub async fn execute(command: DocCommands, cli: &Cli) -> Result<(), CliError> {
93 let token = resolve_token(cli)?;
94 let client = ClickUpClient::new(&token, cli.timeout)?;
95 let ws_id = resolve_workspace(cli)?;
96 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
97 let base = format!("/v3/workspaces/{}/docs", ws_id);
98
99 match command {
100 DocCommands::List { creator, archived } => {
101 let mut params = Vec::new();
102 if let Some(c) = &creator {
103 params.push(format!("creator_id={}", c));
104 }
105 if archived {
106 params.push("archived=true".to_string());
107 }
108 let query = if params.is_empty() {
109 String::new()
110 } else {
111 format!("?{}", params.join("&"))
112 };
113 let resp = client.get(&format!("{}{}", base, query)).await?;
114 let mut docs = resp
115 .get("docs")
116 .and_then(|v| v.as_array())
117 .cloned()
118 .unwrap_or_default();
119 if let Some(limit) = cli.limit {
120 docs.truncate(limit);
121 }
122 output.print_items(&docs, DOC_FIELDS, "id");
123 Ok(())
124 }
125 DocCommands::Create {
126 name,
127 visibility,
128 parent_type,
129 parent_id,
130 } => {
131 let mut body = serde_json::json!({ "name": name });
132 if let Some(v) = visibility {
133 body["visibility"] = serde_json::Value::String(v);
134 }
135 if parent_type.is_some() || parent_id.is_some() {
136 let mut parent = serde_json::Map::new();
137 if let Some(pt) = parent_type {
138 parent.insert("type".into(), serde_json::Value::String(pt));
139 }
140 if let Some(pi) = parent_id {
141 parent.insert("id".into(), serde_json::Value::String(pi));
142 }
143 body["parent"] = serde_json::Value::Object(parent);
144 }
145 let resp = client.post(&base, &body).await?;
146 output.print_single(&resp, DOC_FIELDS, "id");
147 Ok(())
148 }
149 DocCommands::Get { id } => {
150 let resp = client.get(&format!("{}/{}", base, id)).await?;
151 output.print_single(&resp, DOC_FIELDS, "id");
152 Ok(())
153 }
154 DocCommands::Pages {
155 id,
156 content,
157 max_depth,
158 } => {
159 if content || max_depth.is_some() {
160 let mut params = Vec::new();
161 if content {
162 params.push("content=true".to_string());
163 }
164 if let Some(depth) = max_depth {
165 params.push(format!("max_page_depth={}", depth));
166 }
167 let query = if params.is_empty() {
168 String::new()
169 } else {
170 format!("?{}", params.join("&"))
171 };
172 let resp = client
173 .get(&format!("{}/{}/pages{}", base, id, query))
174 .await?;
175 let mut pages = resp
176 .get("pages")
177 .and_then(|v| v.as_array())
178 .cloned()
179 .unwrap_or_else(|| resp.as_array().cloned().unwrap_or_default());
180 if let Some(limit) = cli.limit {
181 pages.truncate(limit);
182 }
183 output.print_items(&pages, PAGE_FIELDS, "id");
184 } else {
185 let resp = client.get(&format!("{}/{}/page_listing", base, id)).await?;
186 let mut pages = resp
187 .get("pages")
188 .and_then(|v| v.as_array())
189 .cloned()
190 .unwrap_or_else(|| resp.as_array().cloned().unwrap_or_default());
191 if let Some(limit) = cli.limit {
192 pages.truncate(limit);
193 }
194 output.print_items(&pages, PAGE_FIELDS, "id");
195 }
196 Ok(())
197 }
198 DocCommands::AddPage {
199 doc_id,
200 name,
201 parent_page,
202 content,
203 } => {
204 let mut body = serde_json::json!({ "name": name });
205 if let Some(pp) = parent_page {
206 body["parent_page_id"] = serde_json::Value::String(pp);
207 }
208 if let Some(c) = content {
209 body["content"] = serde_json::Value::String(c);
210 }
211 let resp = client
212 .post(&format!("{}/{}/pages", base, doc_id), &body)
213 .await?;
214 output.print_single(&resp, PAGE_FIELDS, "id");
215 Ok(())
216 }
217 DocCommands::Page { doc_id, page_id } => {
218 let resp = client
219 .get(&format!("{}/{}/pages/{}", base, doc_id, page_id))
220 .await?;
221 output.print_single(&resp, PAGE_FIELDS, "id");
222 Ok(())
223 }
224 DocCommands::EditPage {
225 doc_id,
226 page_id,
227 content,
228 mode,
229 } => {
230 let body = serde_json::json!({
231 "content": content,
232 "content_edit_mode": mode,
233 });
234 let resp = client
235 .put(&format!("{}/{}/pages/{}", base, doc_id, page_id), &body)
236 .await?;
237 output.print_single(&resp, PAGE_FIELDS, "id");
238 Ok(())
239 }
240 }
241}