feat(08-01): handle list pagination callbacks

- Add Answer List Callback HTTP node (prevents loading indicator)
- Add Prepare List Fetch code node
- Add Get Containers For List HTTP Request node
- Add Build Paginated List code node (reuses keyboard logic)
- Add Edit Container List HTTP node (editMessageText for in-place updates)
- Wire Route Callback list output to pagination flow
- All page transitions use message edits (no new messages)
This commit is contained in:
Lucas Berger
2026-02-03 16:18:50 -05:00
parent 01482827fb
commit 393d368dec
+140 -1
View File
@@ -2570,6 +2570,95 @@
"name": "Telegram account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
"options": {}
},
"id": "http-answer-list-callback",
"name": "Answer List Callback",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
1000
],
"credentials": {
"telegramApi": {
"id": "I0xTTiASl7C1NZhJ",
"name": "Telegram account"
}
}
},
{
"parameters": {
"jsCode": "// Prepare list pagination request\nconst data = $input.item.json;\nreturn {\n json: {\n queryId: data.queryId,\n chatId: data.chatId,\n messageId: data.messageId,\n page: data.page || 0\n }\n};"
},
"id": "code-prepare-list-fetch",
"name": "Prepare List Fetch",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
1000
]
},
{
"parameters": {
"method": "GET",
"url": "=http://docker-socket-proxy:2375/containers/json?all=true",
"options": {}
},
"id": "http-get-containers-for-list",
"name": "Get Containers For List",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1780,
1000
]
},
{
"parameters": {
"jsCode": "// Build Paginated Container List Keyboard for callback pagination\nconst containers = $input.item.json;\nconst prevData = $('Prepare List Fetch').item.json;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst page = prevData.page || 0;\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\nconst containersPerPage = 6;\n\n// Group by state (running first)\nconst running = containers.filter(c => c.State === 'running');\nconst stopped = containers.filter(c => c.State !== 'running');\nconst sortedContainers = [...running, ...stopped];\n\nconst totalPages = Math.ceil(sortedContainers.length / containersPerPage);\nconst start = page * containersPerPage;\nconst pageContainers = sortedContainers.slice(start, start + containersPerPage);\n\n// Build keyboard rows\nconst keyboard = [];\n\npageContainers.forEach(container => {\n const name = normalizeName(container.Names[0]);\n const stateIcon = container.State === 'running' ? '\\u{1F7E2}' : '\\u26AA';\n const stateText = container.State === 'running' ? 'Running' : 'Stopped';\n keyboard.push([{\n text: `${stateIcon} ${name} - ${stateText}`,\n callback_data: `select:${name}`\n }]);\n});\n\n// Add navigation row if needed\nif (totalPages > 1) {\n const navRow = [];\n if (page > 0) {\n navRow.push({ text: '\\u25C0\\uFE0F Previous', callback_data: `list:${page - 1}` });\n }\n navRow.push({ text: `${page + 1}/${totalPages}`, callback_data: 'noop' });\n if (page < totalPages - 1) {\n navRow.push({ text: 'Next \\u25B6\\uFE0F', callback_data: `list:${page + 1}` });\n }\n keyboard.push(navRow);\n}\n\n// Build header text\nconst runningCount = running.length;\nconst totalCount = sortedContainers.length;\nlet headerText = `<b>\\u{1F5C2} Containers</b> (${runningCount}/${totalCount} running)`;\nif (totalPages > 1) {\n headerText += `\\n\\nPage ${page + 1} of ${totalPages}`;\n}\nheaderText += '\\n\\nTap a container to manage it:';\n\nreturn [{\n json: {\n chatId,\n messageId,\n text: headerText,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
},
"id": "code-build-paginated-list",
"name": "Build Paginated List",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
1000
]
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, message_id: $json.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
"options": {}
},
"id": "http-edit-container-list",
"name": "Edit Container List",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2220,
1000
],
"credentials": {
"telegramApi": {
"id": "I0xTTiASl7C1NZhJ",
"name": "Telegram account"
}
}
}
],
"connections": {
@@ -2667,7 +2756,13 @@
"index": 0
}
],
[],
[
{
"node": "Answer List Callback",
"type": "main",
"index": 0
}
],
[],
[
{
@@ -2729,6 +2824,50 @@
]
]
},
"Answer List Callback": {
"main": [
[
{
"node": "Prepare List Fetch",
"type": "main",
"index": 0
}
]
]
},
"Prepare List Fetch": {
"main": [
[
{
"node": "Get Containers For List",
"type": "main",
"index": 0
}
]
]
},
"Get Containers For List": {
"main": [
[
{
"node": "Build Paginated List",
"type": "main",
"index": 0
}
]
]
},
"Build Paginated List": {
"main": [
[
{
"node": "Edit Container List",
"type": "main",
"index": 0
}
]
]
},
"Handle Cancel": {
"main": [
[