ntion 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/cli.js +25 -1
- package/dist/notion/markdown.js +207 -4
- package/dist/notion/repository.d.ts +3 -0
- package/dist/notion/repository.js +95 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,16 @@ A single "get all my tasks" workflow tells the whole story:
|
|
|
35
35
|
2. **No schema bloat** — MCP's database fetch includes ~2 KB of SQLite DDL, ~800 B of XML boilerplate, and ~1.4 KB of base64 `collectionPropertyOption://` URLs that are never used for reads. ntion returns only actionable data.
|
|
36
36
|
3. **Markdown-first** — Page content defaults to markdown, matching what agents actually consume. No manual format negotiation needed.
|
|
37
37
|
|
|
38
|
+
### Operations the official MCP can't do
|
|
39
|
+
|
|
40
|
+
The official Notion MCP server has no block delete tool. ntion does:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ntion blocks delete --block-ids <block_id>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Delete one or many blocks in a single call — useful for cleaning up content, removing broken blocks, or precise page editing.
|
|
47
|
+
|
|
38
48
|
## Agent skill
|
|
39
49
|
|
|
40
50
|
ntion ships with an [agent skill](https://docs.anthropic.com/en/docs/claude-code/skills) that teaches AI agents how to use the CLI. Install it with:
|
|
@@ -157,6 +167,10 @@ ntion blocks replace-range \
|
|
|
157
167
|
--start-selector-json '{"where":{"text_contains":"Start"}}' \
|
|
158
168
|
--end-selector-json '{"where":{"text_contains":"End"}}' \
|
|
159
169
|
--markdown "Replacement content"
|
|
170
|
+
|
|
171
|
+
# Delete blocks (not available in the official Notion MCP)
|
|
172
|
+
ntion blocks delete --block-ids <block_id>
|
|
173
|
+
ntion blocks delete --block-ids <id1> <id2> <id3>
|
|
160
174
|
```
|
|
161
175
|
|
|
162
176
|
### Health check
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { getConfigPath } from "./config/paths.js";
|
|
|
10
10
|
import { buildInitialAuthConfig, loadConfigOrNull, saveConfig } from "./config/store.js";
|
|
11
11
|
import { CliError } from "./errors/cli-error.js";
|
|
12
12
|
import { markdownToBlocks } from "./notion/markdown.js";
|
|
13
|
-
import { appendBlocks, archivePage, createPage, createPagesBulk, getBlocks, getDataSource, getDataSourceSchema, getPage, insertBlocks, listDataSources, queryDataSourcePages, replaceBlockRange, searchWorkspace, selectBlocks, setRelation, unarchivePage, updatePage, } from "./notion/repository.js";
|
|
13
|
+
import { appendBlocks, archivePage, createPage, createPagesBulk, deleteBlocks, getBlocks, getDataSource, getDataSourceSchema, getPage, insertBlocks, listDataSources, queryDataSourcePages, replaceBlockRange, searchWorkspace, selectBlocks, setRelation, unarchivePage, updatePage, } from "./notion/repository.js";
|
|
14
14
|
import { parseJsonOption } from "./utils/json.js";
|
|
15
15
|
const ROOT_HELP_EPILOG = [
|
|
16
16
|
"",
|
|
@@ -218,6 +218,7 @@ async function runInteractiveAuthSetup() {
|
|
|
218
218
|
}
|
|
219
219
|
const rl = createInterface({ input, output });
|
|
220
220
|
try {
|
|
221
|
+
console.log("Create a token at https://www.notion.so/profile/integrations\n");
|
|
221
222
|
const response = await rl.question("Paste your Notion integration token: ");
|
|
222
223
|
const trimmed = response.trim();
|
|
223
224
|
if (!trimmed) {
|
|
@@ -254,9 +255,11 @@ async function saveAuthConfigEnv(tokenEnv) {
|
|
|
254
255
|
config_path: getConfigPath(),
|
|
255
256
|
};
|
|
256
257
|
}
|
|
258
|
+
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf-8"));
|
|
257
259
|
const program = new Command();
|
|
258
260
|
program
|
|
259
261
|
.name("ntion")
|
|
262
|
+
.version(packageJson.version, "-V, --version", "print version number")
|
|
260
263
|
.description("Token-efficient, workspace-agnostic Notion CLI")
|
|
261
264
|
.showHelpAfterError()
|
|
262
265
|
.addHelpText("after", ROOT_HELP_EPILOG);
|
|
@@ -918,6 +921,27 @@ const blocksReplaceRangeCommand = blocksCommand
|
|
|
918
921
|
});
|
|
919
922
|
});
|
|
920
923
|
blocksReplaceRangeCommand.addHelpText("after", BLOCKS_REPLACE_RANGE_HELP_EPILOG);
|
|
924
|
+
blocksCommand
|
|
925
|
+
.command("delete")
|
|
926
|
+
.description("Delete one or more blocks by ID")
|
|
927
|
+
.requiredOption("--block-ids <ids...>", "Block IDs to delete")
|
|
928
|
+
.option("--pretty", "pretty-print JSON output")
|
|
929
|
+
.option("--timeout-ms <n>", "request timeout in milliseconds")
|
|
930
|
+
.action(async (options) => {
|
|
931
|
+
await runAction(Boolean(options.pretty), async (requestId) => {
|
|
932
|
+
const { notion } = await loadRuntime({ timeoutMs: options.timeoutMs });
|
|
933
|
+
const result = await executeMutationWithIdempotency({
|
|
934
|
+
commandName: "blocks.delete",
|
|
935
|
+
requestId,
|
|
936
|
+
requestShape: { block_ids: options.blockIds },
|
|
937
|
+
targetIds: options.blockIds,
|
|
938
|
+
run: () => deleteBlocks(notion, { blockIds: options.blockIds }),
|
|
939
|
+
});
|
|
940
|
+
return {
|
|
941
|
+
data: result,
|
|
942
|
+
};
|
|
943
|
+
});
|
|
944
|
+
});
|
|
921
945
|
program
|
|
922
946
|
.command("doctor")
|
|
923
947
|
.description("Validate config and auth quickly")
|
package/dist/notion/markdown.js
CHANGED
|
@@ -9,13 +9,142 @@ function chunkText(content) {
|
|
|
9
9
|
}
|
|
10
10
|
return chunks;
|
|
11
11
|
}
|
|
12
|
+
const DEFAULT_ANNOTATIONS = {
|
|
13
|
+
bold: false,
|
|
14
|
+
italic: false,
|
|
15
|
+
strikethrough: false,
|
|
16
|
+
code: false,
|
|
17
|
+
};
|
|
18
|
+
function findClosingStar(text, start) {
|
|
19
|
+
for (let i = start; i < text.length; i++) {
|
|
20
|
+
if (text[i] === "*" && text[i + 1] !== "*" && text[i - 1] !== "*") {
|
|
21
|
+
return i;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
26
|
+
function parseInlineSegments(text, inherited, linkUrl) {
|
|
27
|
+
const segments = [];
|
|
28
|
+
let i = 0;
|
|
29
|
+
let plain = "";
|
|
30
|
+
const flush = () => {
|
|
31
|
+
if (plain) {
|
|
32
|
+
segments.push({ content: plain, annotations: { ...inherited }, link: linkUrl });
|
|
33
|
+
plain = "";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
while (i < text.length) {
|
|
37
|
+
// Inline code (no nesting inside)
|
|
38
|
+
if (text[i] === "`") {
|
|
39
|
+
const close = text.indexOf("`", i + 1);
|
|
40
|
+
if (close !== -1) {
|
|
41
|
+
flush();
|
|
42
|
+
segments.push({
|
|
43
|
+
content: text.slice(i + 1, close),
|
|
44
|
+
annotations: { ...inherited, code: true },
|
|
45
|
+
link: linkUrl,
|
|
46
|
+
});
|
|
47
|
+
i = close + 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Link [text](url)
|
|
52
|
+
if (text[i] === "[") {
|
|
53
|
+
const bracketClose = text.indexOf("]", i + 1);
|
|
54
|
+
if (bracketClose !== -1 && text[bracketClose + 1] === "(") {
|
|
55
|
+
const parenClose = text.indexOf(")", bracketClose + 2);
|
|
56
|
+
if (parenClose !== -1) {
|
|
57
|
+
flush();
|
|
58
|
+
const linkText = text.slice(i + 1, bracketClose);
|
|
59
|
+
const url = text.slice(bracketClose + 2, parenClose);
|
|
60
|
+
segments.push(...parseInlineSegments(linkText, inherited, url));
|
|
61
|
+
i = parenClose + 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// *** bold+italic
|
|
67
|
+
if (text[i] === "*" && text[i + 1] === "*" && text[i + 2] === "*") {
|
|
68
|
+
const close = text.indexOf("***", i + 3);
|
|
69
|
+
if (close !== -1) {
|
|
70
|
+
flush();
|
|
71
|
+
segments.push(...parseInlineSegments(text.slice(i + 3, close), { ...inherited, bold: true, italic: true }, linkUrl));
|
|
72
|
+
i = close + 3;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ** bold
|
|
77
|
+
if (text[i] === "*" && text[i + 1] === "*") {
|
|
78
|
+
const close = text.indexOf("**", i + 2);
|
|
79
|
+
if (close !== -1) {
|
|
80
|
+
flush();
|
|
81
|
+
segments.push(...parseInlineSegments(text.slice(i + 2, close), { ...inherited, bold: true }, linkUrl));
|
|
82
|
+
i = close + 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// * italic
|
|
87
|
+
if (text[i] === "*") {
|
|
88
|
+
const close = findClosingStar(text, i + 1);
|
|
89
|
+
if (close !== -1) {
|
|
90
|
+
flush();
|
|
91
|
+
segments.push(...parseInlineSegments(text.slice(i + 1, close), { ...inherited, italic: true }, linkUrl));
|
|
92
|
+
i = close + 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ~~ strikethrough
|
|
97
|
+
if (text[i] === "~" && text[i + 1] === "~") {
|
|
98
|
+
const close = text.indexOf("~~", i + 2);
|
|
99
|
+
if (close !== -1) {
|
|
100
|
+
flush();
|
|
101
|
+
segments.push(...parseInlineSegments(text.slice(i + 2, close), { ...inherited, strikethrough: true }, linkUrl));
|
|
102
|
+
i = close + 2;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
plain += text[i];
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
flush();
|
|
110
|
+
return segments;
|
|
111
|
+
}
|
|
112
|
+
function segmentToRichText(segment) {
|
|
113
|
+
return chunkText(segment.content).map((chunk) => {
|
|
114
|
+
const textObj = { content: chunk };
|
|
115
|
+
if (segment.link) {
|
|
116
|
+
textObj.link = { url: segment.link };
|
|
117
|
+
}
|
|
118
|
+
const obj = { type: "text", text: textObj };
|
|
119
|
+
const { bold, italic, strikethrough, code } = segment.annotations;
|
|
120
|
+
if (bold || italic || strikethrough || code) {
|
|
121
|
+
const annotations = {};
|
|
122
|
+
if (bold)
|
|
123
|
+
annotations.bold = true;
|
|
124
|
+
if (italic)
|
|
125
|
+
annotations.italic = true;
|
|
126
|
+
if (strikethrough)
|
|
127
|
+
annotations.strikethrough = true;
|
|
128
|
+
if (code)
|
|
129
|
+
annotations.code = true;
|
|
130
|
+
obj.annotations = annotations;
|
|
131
|
+
}
|
|
132
|
+
return obj;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
12
135
|
function toRichText(content) {
|
|
136
|
+
const normalized = content.length > 0 ? content : " ";
|
|
137
|
+
const segments = parseInlineSegments(normalized, DEFAULT_ANNOTATIONS);
|
|
138
|
+
if (segments.length === 0) {
|
|
139
|
+
return [{ type: "text", text: { content: " " } }];
|
|
140
|
+
}
|
|
141
|
+
return segments.flatMap(segmentToRichText);
|
|
142
|
+
}
|
|
143
|
+
function toPlainRichText(content) {
|
|
13
144
|
const normalized = content.length > 0 ? content : " ";
|
|
14
145
|
return chunkText(normalized).map((chunk) => ({
|
|
15
146
|
type: "text",
|
|
16
|
-
text: {
|
|
17
|
-
content: chunk,
|
|
18
|
-
},
|
|
147
|
+
text: { content: chunk },
|
|
19
148
|
}));
|
|
20
149
|
}
|
|
21
150
|
function paragraphBlock(text) {
|
|
@@ -131,11 +260,71 @@ function codeBlock(text, language) {
|
|
|
131
260
|
object: "block",
|
|
132
261
|
type: "code",
|
|
133
262
|
code: {
|
|
134
|
-
rich_text:
|
|
263
|
+
rich_text: toPlainRichText(text),
|
|
135
264
|
language,
|
|
136
265
|
},
|
|
137
266
|
};
|
|
138
267
|
}
|
|
268
|
+
function imageBlock(url, caption) {
|
|
269
|
+
return {
|
|
270
|
+
object: "block",
|
|
271
|
+
type: "image",
|
|
272
|
+
image: {
|
|
273
|
+
type: "external",
|
|
274
|
+
external: { url },
|
|
275
|
+
caption: toRichText(caption),
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function parseMarkdownTable(lines, startIndex) {
|
|
280
|
+
const tableLines = [];
|
|
281
|
+
let i = startIndex;
|
|
282
|
+
while (i < lines.length && lines[i].trim().includes("|")) {
|
|
283
|
+
tableLines.push(lines[i].trim());
|
|
284
|
+
i++;
|
|
285
|
+
}
|
|
286
|
+
const linesConsumed = tableLines.length;
|
|
287
|
+
const parseCells = (line) => {
|
|
288
|
+
let stripped = line;
|
|
289
|
+
if (stripped.startsWith("|"))
|
|
290
|
+
stripped = stripped.slice(1);
|
|
291
|
+
if (stripped.endsWith("|"))
|
|
292
|
+
stripped = stripped.slice(0, -1);
|
|
293
|
+
return stripped.split("|").map((c) => c.trim());
|
|
294
|
+
};
|
|
295
|
+
const isSeparator = (line) => {
|
|
296
|
+
const cells = parseCells(line);
|
|
297
|
+
return cells.every((c) => /^[-:]+$/.test(c));
|
|
298
|
+
};
|
|
299
|
+
const hasColumnHeader = tableLines.length >= 2 && isSeparator(tableLines[1]);
|
|
300
|
+
const dataLines = tableLines.filter((_, idx) => !(hasColumnHeader && idx === 1));
|
|
301
|
+
const tableWidth = dataLines.length > 0 ? parseCells(dataLines[0]).length : 0;
|
|
302
|
+
const children = dataLines.map((line) => {
|
|
303
|
+
const cells = parseCells(line);
|
|
304
|
+
while (cells.length < tableWidth)
|
|
305
|
+
cells.push("");
|
|
306
|
+
return {
|
|
307
|
+
object: "block",
|
|
308
|
+
type: "table_row",
|
|
309
|
+
table_row: {
|
|
310
|
+
cells: cells.slice(0, tableWidth).map((c) => toRichText(c)),
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
block: {
|
|
316
|
+
object: "block",
|
|
317
|
+
type: "table",
|
|
318
|
+
table: {
|
|
319
|
+
table_width: tableWidth,
|
|
320
|
+
has_column_header: hasColumnHeader,
|
|
321
|
+
has_row_header: false,
|
|
322
|
+
children,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
linesConsumed,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
139
328
|
function isDivider(trimmed) {
|
|
140
329
|
return trimmed === "---" || trimmed === "***" || trimmed === "___";
|
|
141
330
|
}
|
|
@@ -183,6 +372,20 @@ export function markdownToBlocks(markdown) {
|
|
|
183
372
|
index += 1;
|
|
184
373
|
continue;
|
|
185
374
|
}
|
|
375
|
+
const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
376
|
+
if (imageMatch) {
|
|
377
|
+
flushParagraph();
|
|
378
|
+
blocks.push(imageBlock(imageMatch[2], imageMatch[1]));
|
|
379
|
+
index += 1;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (trimmed.startsWith("|")) {
|
|
383
|
+
flushParagraph();
|
|
384
|
+
const result = parseMarkdownTable(lines, index);
|
|
385
|
+
blocks.push(result.block);
|
|
386
|
+
index += result.linesConsumed;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
186
389
|
const headingMatch = trimmed.match(/^(#{1,3})\s+(.+)$/);
|
|
187
390
|
if (headingMatch) {
|
|
188
391
|
flushParagraph();
|
|
@@ -140,3 +140,6 @@ export declare function replaceBlockRange(notion: NotionClientAdapter, args: {
|
|
|
140
140
|
dryRun: boolean;
|
|
141
141
|
maxBlocks: number;
|
|
142
142
|
}): Promise<Record<string, unknown>>;
|
|
143
|
+
export declare function deleteBlocks(notion: NotionClientAdapter, args: {
|
|
144
|
+
blockIds: string[];
|
|
145
|
+
}): Promise<Record<string, unknown>>;
|
|
@@ -455,6 +455,54 @@ function extractBlockText(block) {
|
|
|
455
455
|
})
|
|
456
456
|
.join("");
|
|
457
457
|
}
|
|
458
|
+
function richTextToMarkdown(richTextArray) {
|
|
459
|
+
return richTextArray
|
|
460
|
+
.map((item) => {
|
|
461
|
+
if (!item || typeof item !== "object")
|
|
462
|
+
return "";
|
|
463
|
+
const plain = item.plain_text;
|
|
464
|
+
if (typeof plain !== "string")
|
|
465
|
+
return "";
|
|
466
|
+
const annotations = item.annotations;
|
|
467
|
+
const href = item.href ??
|
|
468
|
+
(item.text?.link?.url ?? null);
|
|
469
|
+
let result = plain;
|
|
470
|
+
if (annotations?.code) {
|
|
471
|
+
result = `\`${result}\``;
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
if (annotations?.bold && annotations?.italic) {
|
|
475
|
+
result = `***${result}***`;
|
|
476
|
+
}
|
|
477
|
+
else if (annotations?.bold) {
|
|
478
|
+
result = `**${result}**`;
|
|
479
|
+
}
|
|
480
|
+
else if (annotations?.italic) {
|
|
481
|
+
result = `*${result}*`;
|
|
482
|
+
}
|
|
483
|
+
if (annotations?.strikethrough) {
|
|
484
|
+
result = `~~${result}~~`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (typeof href === "string") {
|
|
488
|
+
result = `[${result}](${href})`;
|
|
489
|
+
}
|
|
490
|
+
return result;
|
|
491
|
+
})
|
|
492
|
+
.join("");
|
|
493
|
+
}
|
|
494
|
+
function extractBlockMarkdown(block) {
|
|
495
|
+
const type = block.type;
|
|
496
|
+
if (typeof type !== "string")
|
|
497
|
+
return null;
|
|
498
|
+
const typedData = block[type];
|
|
499
|
+
if (!typedData || typeof typedData !== "object")
|
|
500
|
+
return null;
|
|
501
|
+
const richText = typedData.rich_text;
|
|
502
|
+
if (!Array.isArray(richText))
|
|
503
|
+
return null;
|
|
504
|
+
return richTextToMarkdown(richText);
|
|
505
|
+
}
|
|
458
506
|
function toCompactBlock(block) {
|
|
459
507
|
return {
|
|
460
508
|
id: block.id ?? null,
|
|
@@ -508,7 +556,7 @@ function renderQuoteMarkdown(text, indent) {
|
|
|
508
556
|
}
|
|
509
557
|
function renderBlockToMarkdown(block, depth) {
|
|
510
558
|
const type = typeof block.type === "string" ? block.type : "unsupported";
|
|
511
|
-
const text =
|
|
559
|
+
const text = extractBlockMarkdown(block) ?? "";
|
|
512
560
|
const indent = " ".repeat(depth);
|
|
513
561
|
const children = collectRenderableChildren(block);
|
|
514
562
|
const childMarkdown = children.length > 0 ? renderBlocksToMarkdown(children, depth + 1) : "";
|
|
@@ -557,6 +605,41 @@ function renderBlockToMarkdown(block, depth) {
|
|
|
557
605
|
}
|
|
558
606
|
case "toggle":
|
|
559
607
|
return withChildren(`${indent}- ${text}`, true);
|
|
608
|
+
case "image": {
|
|
609
|
+
const img = block.image;
|
|
610
|
+
let imgUrl = "";
|
|
611
|
+
if (img) {
|
|
612
|
+
if (img.type === "external") {
|
|
613
|
+
const ext = img.external;
|
|
614
|
+
imgUrl = ext?.url ?? "";
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
const file = img.file;
|
|
618
|
+
imgUrl = file?.url ?? "";
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const caption = img && Array.isArray(img.caption) ? richTextToMarkdown(img.caption) : "";
|
|
622
|
+
return withChildren(`${indent}`);
|
|
623
|
+
}
|
|
624
|
+
case "table": {
|
|
625
|
+
const tbl = block.table;
|
|
626
|
+
const rows = children;
|
|
627
|
+
const rowLines = [];
|
|
628
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
629
|
+
const row = rows[ri];
|
|
630
|
+
const tr = row.table_row;
|
|
631
|
+
if (!tr?.cells)
|
|
632
|
+
continue;
|
|
633
|
+
const cellTexts = tr.cells.map((cell) => richTextToMarkdown(cell).replace(/\|/g, "\\|"));
|
|
634
|
+
rowLines.push(`${indent}| ${cellTexts.join(" | ")} |`);
|
|
635
|
+
if (ri === 0 && tbl?.has_column_header) {
|
|
636
|
+
rowLines.push(`${indent}| ${cellTexts.map(() => "---").join(" | ")} |`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return rowLines.join("\n");
|
|
640
|
+
}
|
|
641
|
+
case "table_row":
|
|
642
|
+
return "";
|
|
560
643
|
default: {
|
|
561
644
|
const fallback = text.length > 0 ? text : `[${type}]`;
|
|
562
645
|
return withChildren(`${indent}${fallback}`);
|
|
@@ -996,3 +1079,14 @@ export async function replaceBlockRange(notion, args) {
|
|
|
996
1079
|
deleted_count: blocksToDelete.length,
|
|
997
1080
|
};
|
|
998
1081
|
}
|
|
1082
|
+
export async function deleteBlocks(notion, args) {
|
|
1083
|
+
const deletedIds = [];
|
|
1084
|
+
for (const blockId of args.blockIds) {
|
|
1085
|
+
await notion.deleteBlock({ block_id: blockId });
|
|
1086
|
+
deletedIds.push(blockId);
|
|
1087
|
+
}
|
|
1088
|
+
return {
|
|
1089
|
+
deleted_count: deletedIds.length,
|
|
1090
|
+
deleted_ids: deletedIds,
|
|
1091
|
+
};
|
|
1092
|
+
}
|