Skip to content

Commit b0157e4

Browse files
feat: improve block transformation logic and bump version to v1.2.2
1 parent 01789df commit b0157e4

File tree

5 files changed

+157
-67
lines changed

5 files changed

+157
-67
lines changed

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(git add:*)"
5+
],
6+
"deny": [],
7+
"ask": []
8+
}
9+
}

CLAUDE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Commands
6+
7+
- **Development**: `npm run dev`
8+
- Runs the webpack development server.
9+
- **Build**: `npm run build`
10+
- Creates a production build of the plugin.
11+
12+
## Code Architecture
13+
14+
This is a Logseq plugin for transforming blocks of text.
15+
16+
- **Main Entrypoint**: `src/index.tsx` initializes the plugin and its UI.
17+
- **Core Logic**: `src/block_handler.ts` contains the main logic for splitting and transforming blocks based on different modes and settings.
18+
- **UI Components**: The settings and toolbar are built with React. The main UI component is `src/components/ToolbarApp.tsx`.
19+
- **Build System**: The project is bundled using Webpack, with the configuration in `webpack.config.js`.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "logseq-plugin-block-transformer",
3-
"version": "1.1.0",
3+
"version": "1.2.2",
44
"description": "a plugin to transform block in logseq",
55
"main": "dist/index.html",
66
"targets": {

src/block_handler.ts

Lines changed: 126 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class BlockTreeNode {
77
public children: BlockTreeNode[] = [];
88
public content: string = "";
99
public properties: Record<string, any> = {};
10+
public headerLevel = 0;
1011
public blankLevel = 0;
1112
}
1213

@@ -65,12 +66,29 @@ async function splitBlocksToTree(blockEntities: BlockEntity[], transformerContex
6566
// inherent refBlock properties
6667
blockTreeNode.properties = convertBlockProperties(blockEntity.properties);
6768
}
68-
// remove lower blank level
69+
70+
// Find correct parent
6971
for (let j = lastBlockTreeNodes.length - 1; j >= 0; j--) {
70-
if (lastBlockTreeNodes[j].blankLevel >= blockTreeNode.blankLevel) {
71-
lastBlockTreeNodes.pop();
72+
const parentCandidate = lastBlockTreeNodes[j];
73+
if (blockTreeNode.headerLevel > 0) { // Current is a header
74+
if (parentCandidate.headerLevel > 0 && parentCandidate.headerLevel >= blockTreeNode.headerLevel) {
75+
lastBlockTreeNodes.pop();
76+
} else if (parentCandidate.headerLevel === 0) { // if parent candidate is not a header, pop it to find a header parent
77+
lastBlockTreeNodes.pop();
78+
} else {
79+
break;
80+
}
81+
} else { // Current is not a header
82+
if (parentCandidate.headerLevel === 0 && parentCandidate.blankLevel >= blockTreeNode.blankLevel) {
83+
lastBlockTreeNodes.pop();
84+
} else if (parentCandidate.headerLevel > 0 && blockTreeNode.headerLevel === 0) {
85+
break;
86+
} else {
87+
break;
88+
}
7289
}
7390
}
91+
7492
if (lastBlockTreeNodes.length == 0) {
7593
// append output
7694
outputBlockTreeNodes.push(blockTreeNode);
@@ -118,27 +136,7 @@ async function splitBlocksToTree(blockEntities: BlockEntity[], transformerContex
118136

119137

120138
// handle lines
121-
// handle table
122-
if (line[blankNum] === '|') {
123-
let combineTable = false;
124-
if (lastBlockTreeNodes.length > 0) {
125-
let lastNodeTrim = lastBlockTreeNodes[lastBlockTreeNodes.length - 1].content.trim();
126-
if (lastNodeTrim.length > 0 && lastNodeTrim[0] === '|') {
127-
lastBlockTreeNodes[lastBlockTreeNodes.length - 1].content += '\n' + line;
128-
combineTable = true;
129-
}
130-
}
131-
if (!combineTable) {
132-
let blockTreeNode = {
133-
refBlock: undefined,
134-
content: line,
135-
children: [],
136-
properties: {},
137-
blankLevel: blankLevel
138-
}
139-
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
140-
}
141-
} else if (!completeCodeBlock) {
139+
if (!completeCodeBlock) {
142140
const position = line.indexOf('```')
143141
if (position >= 0) {
144142
lastBlockTreeNodes[lastBlockTreeNodes.length - 1].content += '\n' + line.substring(0, position + 3)
@@ -182,6 +180,7 @@ async function splitBlocksToTree(blockEntities: BlockEntity[], transformerContex
182180
content: line1,
183181
children: [],
184182
properties: {},
183+
headerLevel: 0,
185184
blankLevel: blankLevel
186185
}
187186
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
@@ -200,47 +199,87 @@ async function splitBlocksToTree(blockEntities: BlockEntity[], transformerContex
200199
content: line,
201200
children: [],
202201
properties: {},
202+
headerLevel: 0,
203203
blankLevel: blankLevel
204204
}
205205
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
206206
}
207-
}
208-
// handle order list
209-
else if (/^\s*([0-9]+|[A-z]+|[]+)[.]\s*/.test(line)) {
210-
let blockProperties: { [key: string]: string } = {};
211-
if (!transformerContext.orderedToNonOrdered) {
212-
blockProperties['logseq.order-list-type'] = 'number';
207+
} else {
208+
// handle header
209+
let match = line.match(/^\s*(#+)\s(.*)$/);
210+
if (match) {
211+
let blockTreeNode = {
212+
refBlock: undefined,
213+
content: match[1] + ' ' + match[2],
214+
children: [],
215+
properties: {heading: match[1].length},
216+
headerLevel: match[1].length,
217+
blankLevel: blankLevel
218+
};
219+
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
220+
}
221+
// handle table
222+
else if (line[blankNum] === '|') {
223+
let combineTable = false;
224+
if (lastBlockTreeNodes.length > 0) {
225+
let lastNodeTrim = lastBlockTreeNodes[lastBlockTreeNodes.length - 1].content.trim();
226+
if (lastNodeTrim.length > 0 && lastNodeTrim[0] === '|') {
227+
lastBlockTreeNodes[lastBlockTreeNodes.length - 1].content += '\n' + line;
228+
combineTable = true;
229+
}
230+
}
231+
if (!combineTable) {
232+
let blockTreeNode = {
233+
refBlock: undefined,
234+
content: line,
235+
children: [],
236+
properties: {},
237+
headerLevel: 0,
238+
blankLevel: blankLevel
239+
}
240+
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
241+
}
242+
}
243+
// handle order list
244+
else if (/^\s*([0-9]+|[A-z]+|[]+)[.]\s*/.test(line)) {
245+
let blockProperties: { [key: string]: string } = {};
246+
if (!transformerContext.orderedToNonOrdered) {
247+
blockProperties['logseq.order-list-type'] = 'number';
248+
}
249+
let blockTreeNode = {
250+
refBlock: undefined,
251+
content: line.replace(/^\s*([0-9]+|[A-z]+|[]+)[.]\s*/, ''),
252+
children: [],
253+
properties: blockProperties,
254+
headerLevel: 0,
255+
blankLevel: blankLevel
256+
};
257+
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
258+
}
259+
// handle normal list
260+
else if (/^\s*[-*]\s/.test(line)) {
261+
let blockTreeNode = {
262+
refBlock: undefined,
263+
content: line.replace(/^\s*[-*]\s/, ''),
264+
children: [],
265+
properties: {},
266+
headerLevel: 0,
267+
blankLevel: blankLevel
268+
};
269+
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
270+
}
271+
// handle normal line
272+
else {
273+
let blockTreeNode = {
274+
refBlock: undefined,
275+
content: line,
276+
children: [],
277+
properties: {},
278+
headerLevel: 0,
279+
blankLevel: blankLevel
280+
};
281+
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
213282
}
214-
let blockTreeNode = {
215-
refBlock: undefined,
216-
content: line.replace(/^\s*([0-9]+|[A-z]+|[]+)[.]\s*/, ''),
217-
children: [],
218-
properties: blockProperties,
219-
blankLevel: blankLevel
220-
};
221-
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
222-
}
223-
// handle normal list
224-
else if (/^\s*[-*]\s/.test(line)) {
225-
let blockTreeNode = {
226-
refBlock: undefined,
227-
content: line.replace(/^\s*[-*]\s/, ''),
228-
children: [],
229-
properties: {},
230-
blankLevel: blankLevel
231-
};
232-
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
233-
}
234-
// handle normal line
235-
else {
236-
let blockTreeNode = {
237-
refBlock: undefined,
238-
content: line,
239-
children: [],
240-
properties: {},
241-
blankLevel: blankLevel
242-
};
243-
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
244283
}
245284
}
246285
// append empty line for empty result
@@ -250,6 +289,7 @@ async function splitBlocksToTree(blockEntities: BlockEntity[], transformerContex
250289
content: '',
251290
children: [],
252291
properties: {},
292+
headerLevel: 0,
253293
blankLevel: 0
254294
}
255295
is_first = appendNewBlockTreeNode(blockTreeNode, is_first, blockEntity, lastBlockTreeNodes);
@@ -300,6 +340,25 @@ function getContent(blockEntity: BlockEntity) {
300340

301341

302342
async function headerModeAction(blockEntities: BlockEntity[], transformerContext: TransformerContext, headerLevel = -1) {
343+
let boldToHeader = transformerContext.boldToHeader;
344+
345+
function test_bold_header(content: string) {
346+
return !/[\r\n]/.test(content) && /^\s*\*\*([^*]+)\*\*[,.:;!?:\s]*$/.test(content);
347+
}
348+
349+
if (boldToHeader && blockEntities.length > 0) {
350+
const boldBlocks = blockEntities.filter(b => /\*\*(.*)\*\*/.test(getContent(b)));
351+
if (boldBlocks.length > 0) {
352+
const convertibleBoldBlocksCount = boldBlocks.filter(b => {
353+
const content = getContent(b);
354+
return test_bold_header(content);
355+
}).length;
356+
if (convertibleBoldBlocksCount < boldBlocks.length) {
357+
boldToHeader = false;
358+
}
359+
}
360+
}
361+
303362
for (let blockEntity of blockEntities) {
304363
if (headerLevel < 0) {
305364
headerLevel = await getHeaderLevelByParent(blockEntity);
@@ -309,7 +368,7 @@ async function headerModeAction(blockEntities: BlockEntity[], transformerContext
309368
// is header
310369
let content = getContent(blockEntity);
311370
let is_header = !/[\r\n]/.test(content) && /^\s*#+\s/.test(content);
312-
let is_bold_header = transformerContext.boldToHeader && !/[\r\n]/.test(content) && /^\s*\*\*([^*]+)\*\*[,.:;!?:\s]*$/.test(content)
371+
let is_bold_header = boldToHeader && test_bold_header(content)
313372
if (is_header || is_bold_header) {
314373
let newContent = content;
315374
newContent = newContent.replace(/^\s*#+\s/, "");
@@ -320,16 +379,18 @@ async function headerModeAction(blockEntities: BlockEntity[], transformerContext
320379
newContent = newContent.replace(/[,.:;!?:\s]*$/, "");
321380
}
322381
// remove serial number
323-
if (transformerContext.orderedToNonOrdered){
382+
if (transformerContext.orderedToNonOrdered) {
324383
newContent = newContent.replace(/^\s*([0-9]+|[A-z]+|[]+)[.]\s*(.*)$/, "$2");
325384
}
326385
// add header by header level
327386
newContent = " " + newContent.trim();
328387
for (let i = 0; i < headerLevel; i++) {
329388
newContent = '#' + newContent;
330389
}
331-
if (content !== newContent) {
332-
await logseq.Editor.updateBlock(blockEntity.uuid, newContent, {properties: convertBlockProperties(blockEntity.properties)});
390+
const newProperties = convertBlockProperties(blockEntity.properties);
391+
newProperties.heading = headerLevel;
392+
if (content !== newContent || !isEqual(newProperties, convertBlockProperties(blockEntity.properties))) {
393+
await logseq.Editor.updateBlock(blockEntity.uuid, newContent, {properties: newProperties});
333394
}
334395
}
335396
let children = await getBlockEntityChildren(blockEntity);
@@ -540,6 +601,7 @@ export async function transformAction(selectedBlockEntities: BlockEntity[]) {
540601
await headerModeAction(selectedBlockEntities, transformerContext);
541602
}
542603
}
604+
543605
async function updateHeadingProperty(blockUuid: BlockUUID, level: number) {
544606
if (level > 0) {
545607
await logseq.Editor.upsertBlockProperty(blockUuid, 'heading', level);

0 commit comments

Comments
 (0)