Skip to content

Commit

Permalink
feat(pagination): add pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
EdJoPaTo committed Feb 3, 2019
1 parent fd2cc3d commit 777a907
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
42 changes: 42 additions & 0 deletions inline-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {getRowsOfButtons} = require('./align-buttons')
const {buildKeyboard} = require('./build-keyboard')
const {prefixEmoji} = require('./prefix')
const {createHandlerMiddleware, isCallbackQueryActionFunc} = require('./middleware-helper')
const {paginationOptions} = require('./pagination')

class TelegrafInlineMenu {
constructor(text) {
Expand Down Expand Up @@ -298,6 +299,47 @@ class TelegrafInlineMenu {
return this.simpleButton(text, action, additionalArgs)
}

pagination(action, additionalArgs) {
const {setPage, getCurrentPage, getTotalPages} = additionalArgs

const pageFromCtx = async ctx => {
const number = Number(ctx.match[ctx.match.length - 1])
const totalPages = await getTotalPages(ctx)
return Math.max(1, Math.min(totalPages, number)) || 1
}

const handler = {
action: new ActionCode(new RegExp(`${action}-(\\d+)`))
}

const hitPageButton = async ctx => setPage(ctx, await pageFromCtx(ctx))
handler.middleware = hitPageButton

if (additionalArgs.hide) {
handler.hide = additionalArgs.hide
}

handler.setParentMenuAfter = additionalArgs.setParentMenuAfter
handler.setMenuAfter = true

this.addHandler(handler)

const createPaginationButtons = async ctx => {
// Numbers are within
// currentPage in [1..totalPages]
const totalPages = await getTotalPages(ctx)
const currentPage = await getCurrentPage(ctx)
return paginationOptions(totalPages, currentPage)
}

this.buttons.push(async ctx => {
const buttonOptions = await createPaginationButtons(ctx)
return generateSelectButtons(action, buttonOptions, additionalArgs)
})

return this
}

question(text, action, additionalArgs) {
const {questionText, setFunc, hide} = additionalArgs
assert(questionText, 'questionText is not set. set it')
Expand Down
45 changes: 45 additions & 0 deletions pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Creates ButtonOptions for the paginator
*
* @param totalPages total amount of pages. Array.length is a good way to return this one.
* @param currentPage current page. Has to be between [1..totalPages]
* @return returns the ButtonOptions
*/
function paginationOptions(totalPages, currentPage) {
// Numbers have to be within
// currentPage in [1..totalPages]
const totalPagesFixed = Math.ceil(totalPages)
const currentPageFixed = Math.max(1, Math.min(totalPagesFixed, Math.floor(currentPage)))

const buttons = {}
if (!isFinite(totalPagesFixed) || !isFinite(currentPage) || totalPagesFixed < 2) {
return buttons
}

const before = currentPageFixed - 1
const after = currentPageFixed + 1

if (currentPageFixed > 1) {
if (before > 1) {
buttons[1] = '⏪ 1'
}

buttons[before] = '◀️ ' + before
}

buttons[currentPageFixed] = String(currentPageFixed)

if (currentPageFixed < totalPagesFixed) {
buttons[after] = '▶️ ' + after

if (after < totalPagesFixed) {
buttons[totalPagesFixed] = '⏩ ' + totalPagesFixed
}
}

return buttons
}

module.exports = {
paginationOptions
}
44 changes: 44 additions & 0 deletions pagination.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import test from 'ava'
import {paginationOptions} from './pagination'

function keysCorrectMacro(t, totalPages, currentPage, expectedArr) {
const result = paginationOptions(totalPages, currentPage)
const keys = Object.keys(result).map(o => Number(o))
t.deepEqual(keys, expectedArr)
}

test('two pages on first page', keysCorrectMacro, 2, 1, [1, 2])
test('two pages on second page', keysCorrectMacro, 2, 1, [1, 2])
test('five pages on first page', keysCorrectMacro, 5, 1, [1, 2, 5])
test('five pages on second page', keysCorrectMacro, 5, 2, [1, 2, 3, 5])
test('five pages on third page', keysCorrectMacro, 5, 3, [1, 2, 3, 4, 5])
test('five pages on fourth page', keysCorrectMacro, 5, 4, [1, 3, 4, 5])
test('five pages on fifth page', keysCorrectMacro, 5, 5, [1, 4, 5])
test('go big', keysCorrectMacro, 200, 100, [1, 99, 100, 101, 200])
test('one page is ommited', keysCorrectMacro, 1, 1, [])
test('NaN pages is ommited', keysCorrectMacro, NaN, 1, [])
test('currentPage NaN is ommited', keysCorrectMacro, 5, NaN, [])
test('currentPage greater than totalPages is max page', keysCorrectMacro, 10, 15, [1, 9, 10])

// When there are 19 items / 2 per page there are... 9.5 pages -> 10
test('when totalPages is float use ceil', keysCorrectMacro, 9.5, 10, [1, 9, 10])

test('five pages all buttons', t => {
const result = paginationOptions(5, 3)
t.deepEqual(result, {
1: '⏪ 1',
2: '◀️ 2',
3: '3',
4: '▶️ 4',
5: '⏩ 5'
})
})

test('three pages are with +/-1 buttons and not first/last buttons', t => {
const result = paginationOptions(3, 2)
t.deepEqual(result, {
1: '◀️ 1',
2: '2',
3: '▶️ 3'
})
})
140 changes: 140 additions & 0 deletions test/pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import test from 'ava'
import Telegraf from 'telegraf'

import TelegrafInlineMenu from '../inline-menu'
import {emojiTrue, emojiFalse} from '../prefix'

test('creates menu', async t => {
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
getCurrentPage: () => 1,
getTotalPages: () => 2
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = (text, extra) => {
t.deepEqual(extra.reply_markup.inline_keyboard, [[
{
text: '1',
callback_data: 'a:c-1'
}, {
text: '▶️ 2',
callback_data: 'a:c-2'
}
]])
return Promise.resolve()
}

await bot.handleUpdate({callback_query: {data: 'a'}})
})

test('no pagination with 1 page', async t => {
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
getCurrentPage: () => 1,
getTotalPages: () => 1
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = (text, extra) => {
t.falsy(extra.reply_markup.inline_keyboard)
return Promise.resolve()
}

await bot.handleUpdate({callback_query: {data: 'a'}})
})

test('creates menu with async methods', async t => {
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
getCurrentPage: () => Promise.resolve(1),
getTotalPages: () => Promise.resolve(2)
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = (text, extra) => {
t.deepEqual(extra.reply_markup.inline_keyboard, [[
{
text: '1',
callback_data: 'a:c-1'
}, {
text: '▶️ 2',
callback_data: 'a:c-2'
}
]])
return Promise.resolve()
}

await bot.handleUpdate({callback_query: {data: 'a'}})
})

test('sets page', async t => {
t.plan(1)
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
setPage: (ctx, page) => Promise.resolve(t.is(page, 2)),
getCurrentPage: () => 1,
getTotalPages: () => 2
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = () => Promise.resolve()

await bot.handleUpdate({callback_query: {data: 'a:c-2'}})
})

test('sets page not outside of range', async t => {
t.plan(2)
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
setPage: (ctx, page) => Promise.resolve(t.true(page >= 1 && page <= 2)),
getCurrentPage: () => 1,
getTotalPages: () => 2
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = () => Promise.resolve()

await bot.handleUpdate({callback_query: {data: 'a:c-0'}})
await bot.handleUpdate({callback_query: {data: 'a:c-3'}})
})

test('sets page 1 when input is bad', async t => {
t.plan(1)
const menu = new TelegrafInlineMenu('foo')
menu.pagination('c', {
setPage: (ctx, page) => Promise.resolve(t.is(page, 1)),
getCurrentPage: () => 'foo',
getTotalPages: () => 'bar'
})

const bot = new Telegraf()
bot.use(menu.init({actionCode: 'a'}))

bot.context.answerCbQuery = () => Promise.resolve()
bot.context.editMessageText = () => Promise.resolve()

await bot.handleUpdate({callback_query: {data: 'a:c-5'}})
})

test('require additionalArgs', t => {
const menu = new TelegrafInlineMenu('foo')
t.throws(() => {
menu.pagination('c')
}, /Cannot.+undefined/)
})

0 comments on commit 777a907

Please sign in to comment.