<div>
  <p align="center">
    <a href="https://discordx.js.org" target="_blank" rel="nofollow">
      <img src="https://discordx.js.org/discordx.svg" width="546" />
    </a>
  </p>
  <div align="center" class="badge-container">
    <a href="https://discordx.js.org/discord"
      ><img
        src="https://img.shields.io/discord/874802018361950248?color=5865F2&logo=discord&logoColor=white"
        alt="Discord server"
    /></a>
    <a href="https://www.npmjs.com/package/@discordx/pagination"
      ><img
        src="https://img.shields.io/npm/v/@discordx/pagination.svg?maxAge=3600"
        alt="NPM version"
    /></a>
    <a href="https://www.npmjs.com/package/@discordx/pagination"
      ><img
        src="https://img.shields.io/npm/dt/@discordx/pagination.svg?maxAge=3600"
        alt="NPM downloads"
    /></a>
    <a href="https://github.com/discordx-ts/discordx/actions"
      ><img
        src="https://github.com/discordx-ts/discordx/workflows/Build/badge.svg"
        alt="Build status"
    /></a>
    <a href="https://www.paypal.me/vijayxmeena"
      ><img
        src="https://img.shields.io/badge/donate-paypal-F96854.svg"
        alt="paypal"
    /></a>
  </div>
  <p align="center">
    <b> Create a discord bot with TypeScript and Decorators! </b>
  </p>
</div>

# 📖 Introduction

Add pagination to discord bot using buttons or menu.

# 💻 Installation

Version 16.6.0 or newer of Node.js is required

```
npm install @discordx/pagination
yarn add @discordx/pagination
```

# Pagination

- Embed pagination with discord's new buttons and select menu
- fully customizable (You can open an issue if you find something missing, so that we can fix it)
- Large list support (for examples 1000 items)
- Support (`embeds: (string | MessageEmbed | MessageOptions)[] | Pagination`)
- support interaction/message/channel to send pages
- page resolver for dynamic usage

![discord embed pagination](https://github.com/discordx-ts/discordx/raw/main/packages/pagination/images/discord-embed-pagination.jpg)

## Example

```ts
import { Pagination, PaginationResolver } from "@discordx/pagination";
import {
  ActionRowBuilder,
  ButtonBuilder,
  ButtonStyle,
  EmbedBuilder,
  Events,
  type CommandInteraction,
  type MessageActionRowComponentBuilder,
} from "discord.js";
import { Discord, On, Slash, type ArgsOf } from "discordx";

function GeneratePages(limit?: number): MessageOptions[] {
  const pages = Array.from(Array(limit ?? 20).keys()).map((i) => {
    return { content: `I am ${i + 1}`, embed: `Demo ${i + 1}` };
  });
  return pages.map((page) => {
    return {
      content: page.content,
      embeds: [new MessageEmbed().setTitle(page.embed)],
    };
  });
}

@Discord()
export class Example {
  // example: message
  @On({ event: Events.MessageCreate })
  async messageCreate([message]: ArgsOf<Events.MessageCreate>): Promise<void> {
    if (message.content === "paginated demo") {
      const pagination = new Pagination(message, GeneratePages());
      await pagination.send();
    }
  }

  // example: any text channel
  @On({ event: Events.MessageCreate })
  async messageCreateChannel([
    message,
  ]: ArgsOf<Events.MessageCreate>): Promise<void> {
    if (message.content === "paginated channel demo") {
      const pagination = new Pagination(message.channel, GeneratePages());
      await pagination.send();
    }
  }

  // Example: simple slash with button pagination
  @Slash({ description: "Simple slash with button pagination", name: "demo-a" })
  async demoA(interaction: CommandInteraction): Promise<void> {
    const resolver = new PaginationResolver((page, paginator) => {
      // Let's update our pagination dynamically
      if (page === 3) {
        // Set pages, this can can be resolver as well
        paginator.setPages([
          { content: "1" },
          { content: "2" },
          { content: "3" },
          { content: "4" },
          { content: "5" },
        ]);

        return { content: "Pagination updated" };
      }

      return { content: `page v2 ${page + 1}` };
    }, 25);

    const pagination = new Pagination(interaction, resolver, {
      onTimeout: async () => {
        try {
          await interaction.deleteReply();
        } catch (err) {
          // ignore
        }
      },
      buttons: {
        backward: {
          emoji: { name: "🙂" },
        },
      },
      time: 60_000,
    });

    await pagination.send();
  }

  // example: simple slash with menu pagination
  @Slash({ description: "Simple slash with menu pagination", name: "demo-b" })
  async demoB(interaction: CommandInteraction): Promise<void> {
    const pagination = new Pagination(interaction, GeneratePages(), {
      time: 60_000,
    });

    await pagination.send();
  }

  // example: simple string array
  @Slash({ description: "Simple string array", name: "demo-c" })
  async demoC(interaction: CommandInteraction): Promise<void> {
    const pagination = new Pagination(
      interaction,
      Array.from(Array(200).keys()).map((i) => ({
        content: (i + 1).toString(),
      })),
    );

    await pagination.send();
  }

  // example: array of custom message options
  @Slash({ description: "Array of custom message options", name: "demo-d" })
  async demoD(interaction: CommandInteraction): Promise<void> {
    const pagination = new Pagination(interaction, [
      {
        content: "Page 1",
      },
      {
        content: "Page 2",
        embeds: [new EmbedBuilder({ title: "It's me embed 2" })],
      },
      {
        components: [
          new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
            [
              new ButtonBuilder({
                customId: "myCustomId",
                label: "My Custom Button",
                style: ButtonStyle.Primary,
              }),
            ],
          ),
        ],
        content: "Page 3",
        embeds: [new EmbedBuilder({ title: "It's me embed 3" })],
      },
    ]);

    await pagination.send();
  }
}
```

# Options

## Basic Options

| Name         | Type     | Default   | Description                                   |
| ------------ | -------- | --------- | --------------------------------------------- |
| debug        | boolean  | false     | Enable debug logging                          |
| ephemeral    | boolean  | undefined | Set ephemeral response                        |
| initialPage  | number   | 0         | Initial page number                           |
| itemsPerPage | number   | undefined | Number of items shown per page in select menu |
| onTimeout    | Function | undefined | Callback function when pagination times out   |

## Button Navigation Options

The following options are available under the `buttons` configuration:

| Name       | Type          | Description                                             |
| ---------- | ------------- | ------------------------------------------------------- |
| previous   | ButtonOptions | Previous button configuration                           |
| backward   | ButtonOptions | Backward button configuration (-10 pages)               |
| forward    | ButtonOptions | Forward button configuration (+10 pages)                |
| next       | ButtonOptions | Next button configuration                               |
| exit       | ButtonOptions | Exit button configuration                               |
| skipAmount | number        | Number of pages to skip with skip buttons (default: 10) |

### ButtonOptions Structure

| Name    | Type                             | Description                                        |
| ------- | -------------------------------- | -------------------------------------------------- |
| enabled | Boolean                          | Show button in row                                 |
| emoji   | ComponentEmojiResolvable \| null | Button emoji                                       |
| id      | string                           | Custom button ID                                   |
| label   | string                           | Button label text                                  |
| style   | ButtonStyle                      | Button style (PRIMARY\|SECONDARY\|SUCCESS\|DANGER) |

## Select Menu Options

The following options are available under the `selectMenu` configuration:

| Name                   | Type               | Default                    | Description                                                         |
| ---------------------- | ------------------ | -------------------------- | ------------------------------------------------------------------- |
| labels.start           | string             | "Start"                    | Start label text                                                    |
| labels.end             | string             | "End"                      | End label text                                                      |
| menuId                 | string             | "discordx@pagination@menu" | Custom select menu ID                                               |
| pageText               | string \| string[] | "Page `{page}`"            | Page text format (use `{page}` for page number)                     |
| rangePlaceholderFormat | string             | undefined                  | Custom range placeholder format (use `{start}`, `{end}`, `{total}`) |

# 📜 Documentation

- [discordx.js.org](https://discordx.js.org)
- [Tutorials (dev.to)](https://dev.to/vijayymmeena/series/14317)

# ☎️ Need help?

- [Check frequently asked questions](https://discordx.js.org/docs/faq)
- [Check examples](https://github.com/discordx-ts/discordx/tree/main/packages/discordx/examples)
- Ask in the community [Discord server](https://discordx.js.org/discord)

# 💖 Thank you

You can support [discordx](https://www.npmjs.com/package/discordx) by giving it a [GitHub](https://github.com/discordx-ts/discordx) star.
