|
1 | 1 | import '@logseq/libs' |
2 | 2 |
|
3 | | -import { PropertiesUtils, filterOutChildBlocks, getChosenBlocks, insertBatchBlockBefore, isWindows, sleep, transformSelectedBlocksCommand, unique, walkBlockTreeAsync } from './utils' |
| 3 | +import { |
| 4 | + PropertiesUtils, ensureChildrenIncluded, filterOutChildBlocks, getBlocksWithReferences, getChosenBlocks, insertBatchBlockBefore, |
| 5 | + isWindows, reduceBlockTree, reduceTextWithLength, sleep, transformBlocksTreeByReplacing, transformSelectedBlocksWithMovements, unique, walkBlockTree, walkBlockTreeAsync, |
| 6 | +} from './utils' |
4 | 7 | import { BlockEntity, IBatchBlock } from '@logseq/libs/dist/LSPlugin' |
5 | 8 |
|
6 | 9 |
|
@@ -314,6 +317,116 @@ export async function splitBlocksCommand( |
314 | 317 | } |
315 | 318 | } |
316 | 319 |
|
| 320 | +export async function joinBlocksCommand( |
| 321 | + independentMode: boolean, |
| 322 | + joinAttachCallback: (root: string, level: number, children: string[]) => string, |
| 323 | + joinMapCallback?: (content: string, level: number) => string, |
| 324 | +) { |
| 325 | + let [ blocks, isSelectedState ] = await getChosenBlocks() |
| 326 | + if (blocks.length === 0) |
| 327 | + return |
| 328 | + |
| 329 | + blocks = unique(blocks, (b) => b.uuid) |
| 330 | + blocks = await Promise.all( |
| 331 | + blocks.map(async (b) => { |
| 332 | + return ( |
| 333 | + await logseq.Editor.getBlock(b.uuid, {includeChildren: true}) |
| 334 | + )! |
| 335 | + }) |
| 336 | + ) |
| 337 | + blocks = filterOutChildBlocks(blocks) |
| 338 | + |
| 339 | + if (blocks.length === 0) |
| 340 | + return |
| 341 | + if (blocks.length === 1) |
| 342 | + if (!blocks[0].children || blocks[0].children.length === 0) |
| 343 | + return // nothing to join |
| 344 | + |
| 345 | + let noWarnings = true |
| 346 | + for (const block of blocks) { |
| 347 | + // it is important to check if any block in the tree has references |
| 348 | + // (Logseq replaces references with it's text) |
| 349 | + let blocksWithReferences = await getBlocksWithReferences(block) |
| 350 | + |
| 351 | + // root can have references (in independent mode), others — not |
| 352 | + const blocksWithReferences_noRoot = blocksWithReferences.filter((b) => b.uuid !== block.uuid) |
| 353 | + block._rootHasReferences = blocksWithReferences.length !== blocksWithReferences_noRoot.length |
| 354 | + |
| 355 | + if (independentMode || blocks.length === 1) |
| 356 | + blocksWithReferences = blocksWithReferences_noRoot |
| 357 | + |
| 358 | + if (blocksWithReferences.length !== 0) { |
| 359 | + const html = blocksWithReferences.map((b) => { |
| 360 | + let content = PropertiesUtils.deleteAllProperties(b.content) |
| 361 | + content = reduceTextWithLength(content, 35) |
| 362 | + return `[:li [:i "${content}"]]` |
| 363 | + }) |
| 364 | + await logseq.UI.showMsg( |
| 365 | + `[:div |
| 366 | + [:b "${ICON} Join Blocks Command"] |
| 367 | + [:p "There are blocks that have references from another blocks: " |
| 368 | + [:ul ${html}] |
| 369 | + ] |
| 370 | + [:p "Remove references or select only blocks without them."] |
| 371 | + ]`, |
| 372 | + 'warning', |
| 373 | + {timeout: 20000}, |
| 374 | + ) |
| 375 | + noWarnings = false |
| 376 | + continue |
| 377 | + } |
| 378 | + } |
| 379 | + |
| 380 | + if (!noWarnings) |
| 381 | + return |
| 382 | + |
| 383 | + function reduceTree(block: IBatchBlock, { startLevel }: { startLevel: number }) { |
| 384 | + const preparedTree = walkBlockTree(block, (b, level) => { |
| 385 | + const content = PropertiesUtils.deleteAllProperties(b.content) |
| 386 | + return joinMapCallback ? joinMapCallback(content, level) : content |
| 387 | + }, startLevel) |
| 388 | + |
| 389 | + const reducedContent = reduceBlockTree(preparedTree, (b, level, children) => { |
| 390 | + if (children.length === 0) |
| 391 | + return b.content |
| 392 | + return joinAttachCallback(b.content, level, children) |
| 393 | + }, startLevel) |
| 394 | + |
| 395 | + return reducedContent |
| 396 | + } |
| 397 | + |
| 398 | + if (independentMode || blocks.length === 1) { |
| 399 | + for (const block of blocks) { |
| 400 | + if (!block.children || block.children.length === 0) |
| 401 | + continue // nothing to join |
| 402 | + |
| 403 | + const reducedContent = reduceTree(block as IBatchBlock, {startLevel: 0}) |
| 404 | + |
| 405 | + if (block._rootHasReferences) { |
| 406 | + await logseq.Editor.updateBlock(block.uuid, reducedContent) |
| 407 | + for (const child of block.children!) |
| 408 | + await logseq.Editor.removeBlock((child as BlockEntity).uuid) |
| 409 | + } else { |
| 410 | + await insertBatchBlockBefore(block, {content: reducedContent, properties: block.properties}) |
| 411 | + await logseq.Editor.removeBlock(block.uuid) |
| 412 | + } |
| 413 | + } |
| 414 | + } else { |
| 415 | + const top = blocks[0] |
| 416 | + const pseudoRoot: IBatchBlock = {content: '', children: blocks as IBatchBlock[]} |
| 417 | + const reducedContent = reduceTree(pseudoRoot, {startLevel: 0}) |
| 418 | + |
| 419 | + await insertBatchBlockBefore(top, {content: reducedContent, properties: top.properties}) |
| 420 | + for (const block of blocks) |
| 421 | + await logseq.Editor.removeBlock(block.uuid) |
| 422 | + } |
| 423 | + |
| 424 | + if (isSelectedState) { |
| 425 | + await sleep(20) |
| 426 | + await logseq.Editor.exitEditingMode() |
| 427 | + } |
| 428 | +} |
| 429 | + |
317 | 430 | // export async function magicSplitCommand() { |
318 | 431 | // const [ blocks, isSelectedState ] = await getChosenBlocks() |
319 | 432 | // if (blocks.length === 0) { |
|
0 commit comments