[
  {
    "path": ".eslintignore",
    "content": "node_modules\ndist\ncoverage\n./data\nrequirements\n.vscode\n*.jpeg"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    ecmaVersion: 2020,\n    sourceType: 'module',\n  },\n  extends: [\n    'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.\n  ],\n  rules: {},\n};\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\njobs:\n  publish:\n    runs-on: ubuntu-20.04\n    strategy:\n      matrix:\n        node-version: [16.x]\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n      - name: Dependencies installation\n        run: npm install\n      - name: Test run\n        run: npm test\n      - name: Build\n        run: npm run build\n      - name: Publish\n        uses: JS-DevTools/npm-publish@v1\n        with:\n          token: ${{ secrets.NPM_TOKEN }}\n          access: public\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  pull_request:\n    branches: [main]\njobs:\n  build:\n    runs-on: ubuntu-20.04\n    steps:\n      - uses: actions/checkout@v2\n      - name: Build test compose\n        run: |\n          make test\n"
  },
  {
    "path": ".gitignore",
    "content": "/coverage\n/node_modules\n.vscode\n.idea\n/dist\nscript.js\ntest.html"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint:staged\n"
  },
  {
    "path": ".husky/pre-push",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run test:ci\n"
  },
  {
    "path": ".lintstagedrc.json",
    "content": "{\n  \"*.ts\": [\"eslint 'src/**' --fix\", \"npm run test:staged\"]\n}\n"
  },
  {
    "path": ".npmignore",
    "content": "__tests__\ncoverage\nnode_modules\nsrc\ndocs\njest.config.js\ntsconfig.json\n.eslintignore\n.eslintrc.js\n.gitignore\n.huskyrc.json\n.nvmrc\n.lintstagedrc.json\n.prettierrc.js\n.tool-versions\n.vscode\nscript.js\ntest.html\ndocker-compose*\nDockerfile*\nMakefile\n.husky\n.github"
  },
  {
    "path": ".nvmrc",
    "content": "12.19.0\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  semi: true,\n  trailingComma: 'all',\n  singleQuote: true,\n  printWidth: 120,\n  tabWidth: 2,\n};\n"
  },
  {
    "path": "Dockerfile.test",
    "content": "FROM node:14.17\nWORKDIR /usr/src/notion-page-to-html\nRUN npm -g i npm\nCOPY ./package*.json ./\nRUN npm install\nCOPY . ."
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Alexandre Nunes\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# TEST\ntest_compose = docker-compose -f docker-compose.test.yml\n\n.PRONY: test-build\ntest-build:\n\t$(test_compose) build\n\n.PRONY: test\ntest:\n\tmake test-build && $(test_compose) run notion-page-to-html-test && make test-down\n\n.PRONY: test-down\ntest-down:\n\t$(test_compose) down"
  },
  {
    "path": "README.md",
    "content": "![Cover image](docs/cover.png)\n\n# Notion Page To HTML\n\nNodeJS tool to convert public notion pages to HTML.\n\nAlso available as public API:\n\n[https://notion-page-to-html-api.vercel.app/](https://notion-page-to-html-api.vercel.app/)\n\n## Supported features\n\nMost of the native Notion blocks are currently supported:\n\n- Headings\n- Text With Decorations\n- Quote\n- Image\n- YouTube Videos\n- Code\n- Math Equations\n- To-do\n- Checkbox\n- Bulleted Lists\n- Numbered Lists\n- Toggle Lists\n- Divider\n- Callout\n- Nested blocks\n\nEmbeds and tables are not supported yet.\n\n## Why notion-page-to-html?\n\nIt's perfect as content manager system\n\n- This tool can get any public page from Notion and convert it to html. This is perfect\n  for the ones who want to use Notion as CMS. Once it gets page content from Notion, it becomes completely independent (images are converted to base64 so you do not have to call Notion again to get content). You can convert a page and then make it private again.\n\nIt's fully customizable\n\n- You can choose how you want to get page content. Do you want title, cover, and icon in html body? You can do that! Do you want they apart of html so you can choose where place it? You have it. Do want html without style? Without Equation and Code Highlighting scripts? Do you want body content only? You have those options too.\n\n## Basic Usage\n\nInstall it in a NodeJS project using npm\n\n```bash\nnpm install notion-page-to-html\n```\n\nThen, just import it and paste a public Notion page url\n\n```jsx\nconst NotionPageToHtml = require('notion-page-to-html');\n\n// using async/await\nasync function getPage() {\n  const { title, icon, cover, html } = await NotionPageToHtml.convert(\"https://www.notion.so/asnunes/Simple-Page-Text-4d64bbc0634d4758befa85c5a3a6c22f\");\n  console.log(title, icon, cover, html);\n}\n\ngetPage();\n```\n\n`cover` is a base64 string from original page cover image. `icon` can be an emoji or base64 image based on original page icon. `html` is a full html document by default. It has style, body, MathJax and PrismJS CDN scripts by default. You can pass some options to handle html content.\n\n```jsx\nNotionPageToHtml.convert(\n  'https://www.notion.so/asnunes/Simple-Page-Text-4d64bbc0634d4758befa85c5a3a6c22f',\n  options,\n);\n```\n\n`options` is an object with the following keys\n\n| Key                     | Default value | If true                                                |\n| ----------------------- | ------------- | ------------------------------------------------------ |\n| `excludeCSS`            | false         | returns html without style tag                         |\n| `excludeMetadata`       | false         | returns html without metatags                          |\n| `excludeScripts`        | false         | returns html without script tags                       |\n| `excludeHeaderFromBody` | false         | returns html without title, cover and icon inside body |\n| `excludeTitleFromHead`  | false         | returns html without title tag in head                 |\n| `bodyContentOnly`       | false         | returns html body tag content only                     |\n\n---\n\n## Development and testing\n\n1. Clone this application\n\n2. Make sure you have node v14 or higher and then install all dependencies\n\n````\nnpm i\n````\nRunning tests:\n\n````\nnpm test\n````\n\nInstalling locally in another project:\n````\nnpm run build\nnpm pack\n````\nInside your project:\n````\nnpm i /path/to/tar/gz\n````\n\nDocker approach for testing\n\n1. Make sure you have Docker and Docker Compose installed and then run:\n````\nmake test\n````\n\n## Contributing\n\nWe love your feedback! Feel free to:\n\n- Report a bug\n- Discuss the current state of the code\n- Submit a fix\n- Propose new features\n- Become a maintainer\n\nJust create a GitHub issue or a PR ;)\n"
  },
  {
    "path": "docker-compose.test.yml",
    "content": "version: '3.8'\nservices:\n  notion-page-to-html-test:\n    build:\n      context: .\n      dockerfile: Dockerfile.test\n    container_name: notion-page-to-html-test\n    logging:\n      driver: 'json-file'\n      options:\n        max-size: '10m'\n        max-file: '5'\n    command: |\n      npm test\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',\n  moduleDirectories: ['node_modules'],\n  transform: {\n    '.+\\\\.ts$': 'ts-jest',\n  },\n  testMatch: ['<rootDir>/src/**/*.(test|spec).ts'],\n  moduleNameMapper: {\n    '@/(.*)': '<rootDir>/src/$1',\n  },\n  collectCoverageFrom: ['src/**/*.ts', '!src/migrations/*.ts', '!src/server.ts', '!src/protocols/*.ts'],\n  coverageProvider: 'babel',\n  coverageDirectory: 'coverage',\n  restoreMocks: true,\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"notion-page-to-html\",\n  \"version\": \"1.2.0\",\n  \"description\": \"It converts public notion pages to html from url\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"prebuild\": \"rm -rf ./dist\",\n    \"build\": \"tsc -p tsconfig.build.json\",\n    \"lint\": \"eslint '*/**/*.{js,ts}' --quiet --fix\",\n    \"lint:staged\": \"lint-staged\",\n    \"test\": \"jest --passWithNoTests --silent --noStackTrace --runInBand\",\n    \"test:watch\": \"npm test -- --watch\",\n    \"test:verbose\": \"jest --passWithNoTests --runInBand\",\n    \"test:staged\": \"npm test -- --findRelatedTests\",\n    \"test:ci\": \"npm test -- --coverage\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/asnunes/notion-page-to-html.git\"\n  },\n  \"keywords\": [\n    \"notion\",\n    \"page\",\n    \"html\"\n  ],\n  \"author\": \"Alexandre Nunes\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/asnunes/notion-page-to-html/issues\"\n  },\n  \"homepage\": \"https://github.com/asnunes/notion-page-to-html#readme\",\n  \"devDependencies\": {\n    \"@types/jest\": \"^27.0.2\",\n    \"@types/nock\": \"^11.1.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.31.2\",\n    \"@typescript-eslint/parser\": \"^4.31.2\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-prettier\": \"^4.0.0\",\n    \"git-commit-msg-linter\": \"^3.2.8\",\n    \"husky\": \"^7.0.2\",\n    \"jest\": \"^27.2.2\",\n    \"lint-staged\": \"^11.1.2\",\n    \"nock\": \"^13.1.3\",\n    \"prettier\": \"^2.4.1\",\n    \"ts-jest\": \"^27.0.5\",\n    \"typescript\": \"^4.4.3\"\n  }\n}\n"
  },
  {
    "path": "src/__tests__/mocks/blocks.ts",
    "content": "import { Block, DecorableText, DecorationType, Decoration } from '../../data/protocols/blocks';\n\nexport const NO_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const SINGLE_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_CHILDREN = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f41214426',\n        type: 'text',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child',\n            decorations: [],\n          },\n        ],\n      },\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b1212114426',\n        type: 'text',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child too',\n            decorations: [],\n          },\n        ],\n      },\n    ] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_ITALIC = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD_AND_ITALIC_SEPARATED = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [{ type: 'bold' }],\n      },\n      {\n        text: 'World',\n        decorations: [{ type: 'italic' }],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_UNDERLINE = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'underline' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_STRIKETHROUGH = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'strikethrough' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_CODE_DECORATION = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'myVar',\n        decorations: [\n          {\n            type: 'code' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_LINK = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'link' as DecorationType,\n            value: 'https://www.google.com',\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: { block_color: 'red_background' },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'link' as DecorationType,\n            value: 'https://www.google.com',\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_EQUATION_DECORATION = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World ',\n        decorations: [],\n      },\n      {\n        text: '⁍',\n        decorations: [\n          {\n            type: 'equation' as DecorationType,\n            value: '2x',\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_COLOR = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello',\n        decorations: [\n          {\n            type: 'color' as DecorationType,\n            value: 'purple',\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_COLOR_BACKGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello',\n        decorations: [\n          {\n            type: 'color' as DecorationType,\n            value: 'yellow_background',\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD_AND_ITALIC = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const TEXT_WITH_DECORATION = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const MULTILINE_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World\\nIs everything alright?\\nYes, Dude!',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const TEXT_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {\n      block_color: 'red_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a text with red background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const TEXT_WITH_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {},\n    format: {\n      block_color: 'purple',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a text with purple color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H1_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h1 title',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H1_TEXT_WITH_DECORATIONS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const H1_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'header',\n    properties: {},\n    format: {\n      block_color: 'green_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h1 with red background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H1_WITH_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'header',\n    properties: {},\n    format: {\n      block_color: 'yellow',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h1 with yellow color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H2_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h2 title',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H2_TEXT_WITH_DECORATIONS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const H2_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_header',\n    properties: {},\n    format: {\n      block_color: 'yellow_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h2 with yellow background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H2_WITH_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_header',\n    properties: {},\n    format: {\n      block_color: 'gray',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h2 with gray color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H3_TEXT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_sub_header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h3 title',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H3_TEXT_WITH_DECORATIONS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_sub_header',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const H3_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_sub_header',\n    properties: {},\n    format: {\n      block_color: 'orange_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h3 with orange background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const H3_WITH_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'sub_sub_header',\n    properties: {},\n    format: {\n      block_color: 'brown',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a h3 with brown color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_SINGLE_ITEM = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_CHILDREN = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {},\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f41214426',\n        type: 'bulleted_list',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child',\n            decorations: [],\n          },\n        ],\n      },\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b1212114426',\n        type: 'bulleted_list',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child too',\n            decorations: [],\n          },\n        ],\n      },\n    ] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {\n      block_color: 'brown_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a item with background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {\n      block_color: 'orange',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a item with color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_TWO_ITEMS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d752133',\n    type: 'bulleted_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test too',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNORDERED_LIST_WITH_DECORATED_ITEMS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'bulleted_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_SINGLE_ITEM = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_CHILDREN = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {},\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f41214426',\n        type: 'numbered_list',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child',\n            decorations: [],\n          },\n        ],\n      },\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b1212114426',\n        type: 'numbered_list',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child too',\n            decorations: [],\n          },\n        ],\n      },\n    ] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {\n      block_color: 'gray_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a item with background',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT_FOREGROUND = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {\n      block_color: 'green',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a item with color',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_TWO_ITEMS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d752133',\n    type: 'numbered_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test too',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const ORDERED_LIST_WITH_DECORATED_ITEMS = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'numbered_list',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const TODO = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'to_do',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const TODO_WITH_CHILDREN = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'to_do',\n    properties: {},\n    format: {},\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f41214426',\n        type: 'to_do',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child',\n            decorations: [],\n          },\n        ],\n      },\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b1212114426',\n        type: 'to_do',\n        properties: { checked: 'Yes' },\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'This is a child too',\n            decorations: [],\n          },\n        ],\n      },\n    ] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello World',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const TODO_WITH_FORMAT = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'to_do',\n    properties: {},\n    format: {\n      block_color: 'blue_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a todo with style',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const TODO_WITH_FORMAT_FOREGROUND = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'to_do',\n    properties: {},\n    format: {\n      block_color: 'blue',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a todo with style',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const CHECKED_TODO = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'to_do',\n    properties: { checked: 'Yes' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const UNCHECKED_AND_CHECKED_TODOS = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d2332',\n    type: 'to_do',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test',\n        decorations: [],\n      },\n    ],\n  },\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'to_do',\n    properties: { checked: 'Yes' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a test too',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const CODE = [\n  {\n    id: '479c7b34-6c22-4f2d-b947-8f47d02b48d6',\n    type: 'code',\n    properties: { language: 'JavaScript' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'function test() {\\n\\tvar isTesting = true;\\n\\treturn isTesting;\\n}',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const CODE_WITH_DECORATION = [\n  {\n    id: '479c7b34-6c22-4f2d-b947-8f47d02b48d6',\n    type: 'code',\n    properties: { language: 'JavaScript' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'function test() {\\n\\tvar isTesting = true;\\n\\treturn ',\n        decorations: [],\n      },\n      {\n        text: 'isTesting',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ';\\n}',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const QUOTE = [\n  {\n    id: 'e0a0cfa3-1f64-438b-ac79-95e5c7ad4565',\n    type: 'quote',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This a quote',\n        decorations: [] as Decoration[],\n      },\n    ],\n  },\n];\n\nexport const QUOTE_WITH_FORMAT = [\n  {\n    id: 'e0a0cfa3-1f64-438b-ac79-95e5c7ad4565',\n    type: 'quote',\n    properties: {},\n    format: {\n      block_color: 'purple_background',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This a quote with background',\n        decorations: [] as Decoration[],\n      },\n    ],\n  },\n];\n\nexport const QUOTE_WITH_FORMAT_FOREGROUND = [\n  {\n    id: 'e0a0cfa3-1f64-438b-ac79-95e5c7ad4565',\n    type: 'quote',\n    properties: {},\n    format: {\n      block_color: 'pink',\n    },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This a quote with color',\n        decorations: [] as Decoration[],\n      },\n    ],\n  },\n];\n\nexport const QUOTE_WITH_DECORATION = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'quote',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'Hello ',\n        decorations: [],\n      },\n      {\n        text: 'World ',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: 'and',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n        ],\n      },\n      {\n        text: ' Sun',\n        decorations: [\n          {\n            type: 'bold' as DecorationType,\n          },\n          {\n            type: 'italic' as DecorationType,\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const TEXT_BETWEEN_DIVIDER = [\n  {\n    id: 'e0a0cfa3-438b-ac79-95e5c7ad4565',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This a text',\n        decorations: [],\n      },\n    ],\n  },\n  {\n    id: 'e0a0cfa3-1f64-438b-ac79-95e5c7ad4565',\n    type: 'divider',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n  {\n    id: 'e0a0cfa3-438b-95e5c7ad4565',\n    type: 'text',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This a text too',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const EMPTY_EQUATION = [\n  {\n    id: '9b01339a-9de6-4eb1-bd7a-4c6d537590c7',\n    type: 'equation',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const EQUATION = [\n  {\n    id: '9b01339a-9de6-4eb1-bd7a-4c6d537590c7',\n    type: 'equation',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: '\\\\int 2xdx = x^2 + C',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const NO_YOUTUBE_VIDEO = [\n  {\n    id: 'dcde43cb-7131-4687-8f22-c9789fa75f46',\n    type: 'video',\n    properties: {\n      source: 'https://www.example.com/watch?v=8G80nuEyDN4',\n    },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const YOUTUBE_VIDEO = [\n  {\n    id: 'dcde43cb-7131-4687-8f22-c9789fa75f46',\n    type: 'video',\n    properties: {\n      source: 'https://www.youtube.com/watch?v=xBFqxBfLJWc',\n    },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const TEXT_WITH_YOUTUBE_VIDEO = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: {},\n    format: {},\n    decorableTexts: [{ text: 'Simple Page Test', decorations: [] }],\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n        type: 'text',\n        properties: {},\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [\n          {\n            text: 'Hello World',\n            decorations: [] as Decoration[],\n          },\n        ],\n      },\n      {\n        id: 'dcde43cb-7131-4687-8f22-c9789fa75f46',\n        type: 'video',\n        properties: {\n          source: 'https://www.youtube.com/watch?v=xBFqxBfLJWc',\n        },\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [] as DecorableText[],\n      },\n    ],\n  },\n];\n\nexport const PAGE_WITH_YOUTUBE_VIDEO = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: {},\n    format: {},\n    decorableTexts: [{ text: 'Simple Page Test', decorations: [] }],\n    children: [\n      {\n        id: 'dcde43cb-7131-4687-8f22-c9789fa75f46',\n        type: 'video',\n        properties: {\n          source: 'https://www.youtube.com/watch?v=xBFqxBfLJWc',\n        },\n        format: {},\n        children: [] as Block[],\n        decorableTexts: [] as DecorableText[],\n      },\n    ],\n  },\n];\n\nexport const IMAGE = [\n  {\n    id: 'ec3b36fd-f77d-46b4-8592-5966488612b1',\n    type: 'image',\n    properties: {\n      source:\n        'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg',\n    },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const IMAGE_WITH_CAPTION = [\n  {\n    id: 'ec3b36fd-f77d-46b4-8592-5966488612b1',\n    type: 'image',\n    properties: {\n      source:\n        'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg',\n      caption: 'It is a caption',\n    },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const IMAGE_WITH_CUSTOM_SIZE = [\n  {\n    id: 'ec3b36fd-f77d-46b4-8592-5966488612b1',\n    type: 'image',\n    properties: {\n      source:\n        'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg',\n    },\n    format: { block_width: 240 },\n    children: [] as Block[],\n    decorableTexts: [],\n  },\n];\n\nexport const CALLOUT = [\n  {\n    id: '16431c64-3bf0-481f-a29f-d544780d84f3',\n    type: 'callout',\n    properties: { page_icon: '💡' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a callout',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const CALLOUT_WITH_IMAGE = [\n  {\n    id: '16431c64-3bf0-481f-a29f-d544780d84f3',\n    type: 'callout',\n    properties: { page_icon: 'https://example.com/image.png' },\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a callout',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const CALLOUT_WITH_BACKGROUND = [\n  {\n    id: '16431c64-3bf0-481f-a29f-d544780d84f3',\n    type: 'callout',\n    properties: { page_icon: '💡' },\n    format: { block_color: 'red_background' },\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'This is a callout',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const DETAILS_WITH_DECORATION = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: {},\n    format: {},\n    decorableTexts: [{ text: 'Simple Page Test', decorations: [] }],\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n        type: 'toggle',\n        properties: {},\n        format: {},\n        children: [\n          {\n            id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n            type: 'text',\n            properties: {},\n            format: {},\n            children: [] as Block[],\n            decorableTexts: [\n              {\n                text: 'Hello World',\n                decorations: [],\n              },\n            ],\n          },\n        ],\n        decorableTexts: [\n          {\n            text: 'Hello ',\n            decorations: [],\n          },\n          {\n            text: 'World ',\n            decorations: [\n              {\n                type: 'bold' as DecorationType,\n              },\n              {\n                type: 'italic' as DecorationType,\n              },\n            ],\n          },\n          {\n            text: 'and',\n            decorations: [\n              {\n                type: 'bold' as DecorationType,\n              },\n            ],\n          },\n          {\n            text: ' Sun',\n            decorations: [\n              {\n                type: 'bold' as DecorationType,\n              },\n              {\n                type: 'italic' as DecorationType,\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const DETAILS = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: {},\n    format: {},\n    decorableTexts: [{ text: 'Simple Page Test', decorations: [] }],\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n        type: 'toggle',\n        properties: {},\n        format: {},\n        children: [\n          {\n            id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n            type: 'text',\n            properties: {},\n            format: {},\n            children: [] as Block[],\n            decorableTexts: [\n              {\n                text: 'Hello World',\n                decorations: [],\n              },\n            ],\n          },\n        ],\n        decorableTexts: [\n          {\n            text: 'This is a detail',\n            decorations: [] as Decoration[],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const DETAILS_WITH_BG = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: {},\n    format: {},\n    decorableTexts: [{ text: 'Simple Page Test', decorations: [] }],\n    children: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n        type: 'toggle',\n        properties: {},\n        format: {\n          block_color: 'red_background',\n        },\n        children: [\n          {\n            id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n            type: 'text',\n            properties: {},\n            format: {},\n            children: [] as Block[],\n            decorableTexts: [\n              {\n                text: 'Hello World',\n                decorations: [],\n              },\n            ],\n          },\n        ],\n        decorableTexts: [\n          {\n            text: 'This is a detail',\n            decorations: [] as Decoration[],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const UNKNOWN = [\n  {\n    id: 'd1e33c43-5079-4e66-961a-df032d38d532',\n    type: 'headdfafdafader',\n    properties: {},\n    format: {},\n    children: [] as Block[],\n    decorableTexts: [\n      {\n        text: 'What?!',\n        decorations: [],\n      },\n    ],\n  },\n];\n\nexport const PAGE_WITH_TITLE = {\n  id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n  type: 'page',\n  properties: {},\n  format: {},\n  decorableTexts: [{ text: 'Simple Page Title', decorations: [] }],\n  children: [\n    {\n      id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n      type: 'text',\n      properties: {},\n      format: {},\n      children: [] as Block[],\n      decorableTexts: [\n        {\n          text: 'Hello World',\n          decorations: [],\n        },\n      ],\n    },\n  ],\n};\n\nexport const PAGE_WITHOUT_TITLE = {\n  id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n  type: 'page',\n  properties: {},\n  format: {},\n  decorableTexts: [],\n  children: [\n    {\n      id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n      type: 'text',\n      properties: {},\n      format: {},\n      children: [] as Block[],\n      decorableTexts: [\n        {\n          text: 'Hello World',\n          decorations: [],\n        },\n      ],\n    },\n  ],\n};\n\nexport const PAGE_WITH_TITLE_AND_COVER_IMAGE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_cover: '/images/page-cover/solid_blue.png' },\n    format: { page_cover_position: 0.6 },\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n\nexport const PAGE_WITH_TITLE_AND_ICON = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_icon: '🤴' },\n    format: {},\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n\nexport const PAGE_WITH_TITLE_AND_COVER_IMAGE_NOT_FROM_NOTION = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_cover: 'https://www.example.com/some_image.png' },\n    format: { page_cover_position: 0.6 },\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n\nexport const PAGE_WITH_TITLE_AND_INVALID_COVER_IMAGE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_cover: 'https://www.example.com/s.html' },\n    format: { page_cover_position: 0.6 },\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n\nexport const PAGE_WITH_TITLE_AND_EMOJI_ICON = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_icon: '🤴' },\n    format: {},\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n\nexport const PAGE_WITH_TITLE_AND_IMAGE_ICON = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { page_icon: 'https://www.example.com/some_image.png' },\n    format: {},\n    decorableTexts: [{ text: 'Page Title', decorations: [] }],\n    children: [],\n  },\n];\n"
  },
  {
    "path": "src/__tests__/mocks/html.ts",
    "content": "import base64 from './img/base64';\n\nconst STYLE_TAG = `\\\n<style>\n  html {\n    -webkit-print-color-adjust: exact;\n  }\n  * {\n    box-sizing: border-box;\n    -webkit-print-color-adjust: exact;\n  }\n\n  html,\n  body {\n    margin: 0;\n    padding: 0;\n    font-family: system-ui, sans-serif;\n    color: #37352F;\n  }\n\n  body {\n    line-height: 1.5;\n  }\n\n  @media only screen {\n    body {\n      margin: 2em auto;\n      max-width: 900px;\n      color: rgb(55, 53, 47);\n    }\n  }\n  \n  img {\n    max-width: 100%;\n    max-height: 70vh;\n  }\n\n  ol,\n  ul {\n    margin: 0;\n    margin-block-start: 0.6em;\n    margin-block-end: 0.6em;\n  }\n\n  li > ol:first-child,\n  li > ul:first-child {\n    margin-block-start: 0.6em;\n  }\n\n  ul > li {\n    list-style: disc;\n  }\n\n  ul.to-do-list {\n    text-indent: -1.7em;\n  }\n\n  ul.to-do-list > li {\n    list-style: none;\n  }\n\n  .to-do-children-checked {\n    text-decoration: line-through;\n    opacity: 0.375;\n  }\n\n  ul.toggle > li {\n    list-style: none;\n  }\n\n  ul {\n    padding-inline-start: 1.7em;\n  }\n\n  ul > li {\n    padding-left: 0.1em;\n  }\n\n  ol {\n    padding-inline-start: 1.6em;\n  }\n\n  ol > li {\n    padding-left: 0.2em;\n  }\n\n  .mono ol {\n    padding-inline-start: 2em;\n  }\n\n  .mono ol > li {\n    text-indent: -0.4em;\n  }\n\n  .toggle {\n    padding-inline-start: 0em;\n    list-style-type: none;\n  }\n\n  /* Indent toggle children */\n  .toggle > li > details {\n    padding-left: 1.7em;\n  }\n\n  .toggle > li > details > summary {\n    margin-left: -1.1em;\n  }\n\n  h1,\n  h2,\n  h3 {\n    letter-spacing: -0.01em;\n    line-height: 1.2;\n    font-weight: 600;\n    margin-bottom: 0;\n  }\n\n  .page-title {\n    font-size: 2.5rem;\n    font-weight: 700;\n    margin-top: 0;\n    margin-bottom: 0.75em;\n  }\n\n  .icon {\n    display: inline-block;\n    max-width: 1.2em;\n    max-height: 1.2em;\n    text-decoration: none;\n    vertical-align: text-bottom;\n    margin-right: 0.5em;\n  }\n\n  img.icon {\n    border-radius: 3px;\n  }\n  \n  .page-cover-image {\n    display: block;\n    object-fit: cover;\n    width: 100%;\n    height: 30vh;\n  }\n\n  .page-header-icon {\n    font-size: 3rem;\n    margin-bottom: 1rem;\n  }\n  \n  .page-header-icon-with-cover {\n    margin-top: -0.72em;\n    margin-left: 0.07em;\n  }\n  \n  .page-header-icon img {\n    border-radius: 3px;\n  }\n\n  blockquote {\n    font-size: 1.25em;\n    margin: 1em 0;\n    padding-left: 1em;\n    border-left: 3px solid rgb(55, 53, 47);\n  }  \n\n  h1 {\n    font-size: 1.875rem;\n    margin-top: 1.875rem;\n  }\n\n  h2 {\n    font-size: 1.5rem;\n    margin-top: 1.5rem;\n  }\n\n  h3 {\n    font-size: 1.25rem;\n    margin-top: 1.25rem;\n  }\n\n  .callout {\n    padding: 16px 16px 16px 12px;\n    display: flex;\n    width: 100%;\n    border-radius: 3px;\n    border-width: 1px;\n    border-style: solid;\n    border-color: transparent;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .callout-emoji {\n    height: 21.6px;\n    width: 21.6px;\n    font-size: 21.6px;\n    line-height: 1.1;\n    margin-left: 0px;\n  }\n\n  .callout p {\n    max-width: 100%;\n    width: 100%;\n    white-space: pre-wrap;\n    word-break: break-word;\n    margin-left: 10px;\n  }\n\n  .image {\n    border: none;\n    margin: 1.5em 0;\n    padding: 0;\n    border-radius: 0;\n    text-align: center;\n  }\n\n  figure {\n    margin: 1.25em 0;\n    page-break-inside: avoid;\n  }\n  \n  figcaption {\n    opacity: 0.5;\n    font-size: 85%;\n    margin-top: 0.5em;\n  }  \n\n  hr {\n    background: transparent;\n    display: block;\n    width: 100%;\n    height: 1px;\n    visibility: visible;\n    border: none;\n    border-bottom: 1px solid rgba(55, 53, 47, 0.09);\n  }\n\n  .checkbox {\n    display: inline-flex;\n    vertical-align: text-bottom;\n    width: 16px;\n    height: 16px;\n    background-size: 16px;\n    margin-left: 2px;\n    margin-right: 5px;\n  }\n  \n  .checkbox-on {\n    background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%2358A9D7%22%2F%3E%0A%3Cpath%20d%3D%22M6.71429%2012.2852L14%204.9995L12.7143%203.71436L6.71429%209.71378L3.28571%206.2831L2%207.57092L6.71429%2012.2852Z%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E\");\n  }\n  \n  .checkbox-off {\n    background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Crect%20x%3D%220.75%22%20y%3D%220.75%22%20width%3D%2214.5%22%20height%3D%2214.5%22%20fill%3D%22white%22%20stroke%3D%22%2336352F%22%20stroke-width%3D%221.5%22%2F%3E%0A%3C%2Fsvg%3E\");\n  }\n</style>\n`;\n\nconst HEADER = `\\\n<header>\n  <img class=\"page-cover-image\" src=\"${base64}\" style=\"object-position:center 40%\">\n  <div class=\"page-header-icon page-header-icon-with-cover\">\n    <span class=\"icon\">🤴</span>\n  </div>\n  <h1 class=\"page-title\">Simple Page Test</h1>\n</header>\n`;\n\nconst CONTENT_WITH_HEADER = `\\\n${HEADER}\n<p>Hello World</p>\n`;\n\nconst CONTENT_WITHOUT_HEADER = `\\\n<p>Hello World</p>\n`;\n\nexport const FULL_DOCUMENT = `\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ${STYLE_TAG}\n    <title>Simple Page Test</title>\n    <link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    ${CONTENT_WITH_HEADER}\n    <script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script>\n    MathJax = {\n      tex: {\n        inlineMath: [['$', '$']]\n      }\n    };\n    </script>\n    <script id=\"MathJax-script\" async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n    </script>\n  </body>\n</html>\n`;\n\nexport const DOCUMENT_WITHOUT_TITLE = `\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ${STYLE_TAG}\n    <link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    ${CONTENT_WITH_HEADER}\n    <script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script>\n    MathJax = {\n      tex: {\n        inlineMath: [['$', '$']]\n      }\n    };\n    </script>\n    <script id=\"MathJax-script\" async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n    </script>\n  </body>\n</html>\n`;\n\nexport const DOCUMENT_WITHOUT_CSS = `\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Simple Page Test</title>\n    <link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    ${CONTENT_WITH_HEADER}\n    <script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script>\n    MathJax = {\n      tex: {\n        inlineMath: [['$', '$']]\n      }\n    };\n    </script>\n    <script id=\"MathJax-script\" async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n    </script>\n  </body>\n</html>\n`;\n\nexport const DOCUMENT_METADATA = `\n<!DOCTYPE html>\n<html>\n  <head>\n    ${STYLE_TAG}\n    <title>Simple Page Test</title>\n    <link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    ${CONTENT_WITH_HEADER}\n    <script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script>\n    MathJax = {\n      tex: {\n        inlineMath: [['$', '$']]\n      }\n    };\n    </script>\n    <script id=\"MathJax-script\" async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n    </script>\n  </body>\n</html>\n`;\n\nexport const DOCUMENT_WITHOUT_SCRIPTS = `\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ${STYLE_TAG}\n    <title>Simple Page Test</title>\n  </head>\n  <body>\n    ${CONTENT_WITH_HEADER}\n  </body>\n</html>\n`;\n\nexport const FULL_DOCUMENT_WITHOUT_HEADER_IN_BODY = `\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ${STYLE_TAG}\n    <title>Simple Page Test</title>\n    <link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">\n  </head>\n  <body>\n    ${CONTENT_WITHOUT_HEADER}\n    <script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script>\n    MathJax = {\n      tex: {\n        inlineMath: [['$', '$']]\n      }\n    };\n    </script>\n    <script id=\"MathJax-script\" async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n    </script>\n  </body>\n</html>\n`;\n\nexport const BODY_ONLY = CONTENT_WITHOUT_HEADER;\n\nexport const HEADER_WITH_TITLE_ONLY = `\\\n<header>\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n\nexport const HEADER_WITH_TITLE_AND_COVER_IMAGE = `\\\n<header>\n  <img class=\"page-cover-image\" src=\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\" style=\"object-position:center 15%\">\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n\nexport const HEADER_WITH_TITLE_AND_COVER_IMAGE_WITHOUT_POSITION = `\\\n<header>\n  <img class=\"page-cover-image\" src=\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\" style=\"object-position:center 0%\">\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n\nexport const HEADER_WITH_TITLE_COVER_IMAGE_AND_IMAGE_ICON = `\\\n<header>\n  <img class=\"page-cover-image\" src=\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\" style=\"object-position:center 0%\">\n  <div class=\"page-header-icon page-header-icon-with-cover\">\n    <img class=\"icon\" src=\"data:image/jpeg;base64,/4QDeRXhpZgAASUkqAAgAAAAGABIBAwA\">\n  </div>\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n\nexport const HEADER_WITH_TITLE_AND_IMAGE_ICON = `\\\n<header>\n  <div class=\"page-header-icon\">\n    <img class=\"icon\" src=\"data:image/jpeg;base64,/4QDeRXhpZgAASUkqAAgAAAAGABIBAwA\">\n  </div>\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n\nexport const HEADER_WITH_TITLE_AND_EMOJI_ICON = `\\\n<header>\n  <div class=\"page-header-icon\">\n    <span class=\"icon\">🤴</span>\n  </div>\n  <h1 class=\"page-title\">This is a title</h1>\n</header>\n`;\n"
  },
  {
    "path": "src/__tests__/mocks/img/base64.ts",
    "content": "export default 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QDeRXhpZgAASUkqAAgAAAAGABIBAwABAAAAAQAAABoBBQABAAAAVgAAABsBBQABAAAAXgAAACgBAwABAAAAAgAAABMCAwABAAAAAQAAAGmHBAABAAAAZgAAAAAAAABIAAAAAQAAAEgAAAABAAAABwAAkAcABAAAADAyMTABkQcABAAAAAECAwCGkgcAFgAAAMAAAAAAoAcABAAAADAxMDABoAMAAQAAAP//AAACoAQAAQAAADIAAAADoAQAAQAAADIAAAAAAAAAQVNDSUkAAABQaWNzdW0gSUQ6IDc2N//bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAkLicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/CABEIADIAMgMBIgACEQEDEQH/xAAaAAACAwEBAAAAAAAAAAAAAAAABAIDBQEG/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQIA/9oADAMBAAIQAxAAAAHGlKUujtef0ZMgiKuMiVWdaFVp1gcFl5kOj4nmqc1lvTuUjUvotSFQQDKzC5dpDbloDSAn/8QAIBAAAgICAwADAQAAAAAAAAAAAQIAAwQREhMxECFBIv/aAAgBAQABBQITUoUc87mk1Ll0+zB8VTMclz632Sp2PBB/LZNnJxudRIGCWHE6SlmCY9e3xaNMqqi6MpyF6e6qqdrWFNkgts1MRrTbsgiNOzQW2CwKbuFo+BB40/B7+//EABkRAAIDAQAAAAAAAAAAAAAAAAERABAgAv/aAAgBAwEBPwHAis9E04xn/8QAGxEAAgMAAwAAAAAAAAAAAAAAAAEQERICIGH/2gAIAQIBAT8BhRaPTSNIXFKKMdf/xAArEAACAAQCCAcBAAAAAAAAAAAAAQIRITEQMgMSIkFRgZGhIDBhYnFyscH/2gAIAQEABj8CwX8JLSOTEUqvB6knO2EzL2OeEyj3YO9hRatxHD5NrSLkbGk61El1GvbF+kH1W82YZsqJKrZlNaC/ArOCIz9saXLlyZm8j//EACEQAAMAAgIBBQEAAAAAAAAAAAABESExUWFBIHGBkbHw/9oACAEBAAE/IUFkKXb3K2SXGUa2PJHn2MbyF8ksV4MF42SjRVNQLVSSPWULgyrJYv0rYDUOHRXCGL0R+CO03ZMHWzv6bFswS/2SdJvkvFE5G06e4FMhPoH4VpTCJjM2eZMILvyOB4Hv5kH+Fo36ESKEqPwiGst8htBOkTwiaEHRSNOjGKpwXa36G0Ws9Gbt9+lq/IdxljSrB//aAAwDAQACAAMAAAAQ6lbzVmrzq95M89+9/8QAHREAAgEEAwAAAAAAAAAAAAAAAAERECAhMUFRYf/aAAgBAwEBPxCCMnFEb0MPG6YTSJOh4W//xAAZEQEBAQEBAQAAAAAAAAAAAAABABEgMVH/2gAIAQIBAT8Q2HBJNs/a9hwSJfbHP//EACIQAQADAAICAgIDAAAAAAAAAAEAESExQVFhcYGRwRDR8P/aAAgBAQABPxDB9zPbVv4lSFaqovqv97j5MIDp9vH1+4eaR4WPmFfA7CYIa89xTk4Vcvgq6cfEtQOi8mNCB7S+oxpYqnyVz09dRQSVzyMujQNyr0Z9Ex9dvSKxbLW14gsPRvfEqbZBfRF1rE7SpyXthIcgZFjQ9oKKY00aOkuRkCjc8qIKuIOwcS9BCgmHzKdSCxOH1DNo+map33zUMtES5Tl5+2IZWE/E9QV9tZQ+42SC8wH1KVAAe2Wy4itFTLsLeqj2e50L9cwBRowx/UXV54iCnDrzGXA6DSfcuEpxYv7h5VEaFj+YJbY7afaogQVDkkf8WXPLGjrHN4j7tHd9QmDl6n//2Q==';\n"
  },
  {
    "path": "src/__tests__/mocks/notion-api-responses.ts",
    "content": "export const SUCCESSFUL_PAGE_CHUCK = {\n  recordMap: {\n    block: {\n      '4d64bbc0-634d-4758-befa-85c5a3a6c22f': {\n        role: 'reader',\n        value: {\n          id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          version: 31,\n          type: 'page',\n          properties: { title: [['Simple Page Test']] },\n          format: { page_cover: 'https://www.example.com/image.png', page_cover_position: 0.6, page_icon: '🤴' },\n          content: ['80d0fc46-5511-4d1d-a4ec-8b2f43d75226'],\n          permissions: [{ role: 'reader', type: 'public_permission', allow_duplicate: false }],\n          created_time: 1595516162445,\n          last_edited_time: 1595520360000,\n          parent_id: '8370825e-eb4c-483c-ace0-cc06e7dfc556',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n      '80d0fc46-5511-4d1d-a4ec-8b2f43d75226': {\n        role: 'reader',\n        value: {\n          id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n          version: 33,\n          type: 'text',\n          properties: { title: [['Hello World']] },\n          created_time: 1595516160000,\n          last_edited_time: 1595516160000,\n          parent_id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n    },\n    notion_user: {\n      '408c862f-f07b-4036-b414-1ae5c5ce57b3': {\n        role: 'reader',\n        value: {\n          id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          version: 5,\n          email: 'user@example.com',\n          given_name: 'User',\n          family_name: 'Name',\n          onboarding_completed: true,\n          mobile_onboarding_completed: true,\n        },\n      },\n    },\n    space: {},\n  },\n  cursor: { stack: [] },\n};\n\nexport const SUCCESSFUL_RECORDS = {\n  results: [\n    {\n      role: 'reader',\n      value: {\n        id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n        version: 31,\n        type: 'page',\n        properties: { title: [['Simple Page Test']] },\n        content: ['80d0fc46-5511-4d1d-a4ec-8b2f43d75226'],\n        permissions: [{ role: 'reader', type: 'public_permission', allow_duplicate: false }],\n      },\n    },\n  ],\n};\n\nexport const SUCCESSFUL_PAGE_CHUCK_WITH_CHILDREN = {\n  recordMap: {\n    block: {\n      '4d64bbc0-634d-4758-befa-85c5a3a6c22f': {\n        role: 'editor',\n        value: {\n          id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          version: 262,\n          type: 'page',\n          properties: { title: [['Simple Page Text 2']] },\n          content: ['f8cf7a08-bf80-4f8b-a842-3db49df95e4d'],\n          permissions: [\n            { role: 'editor', type: 'user_permission', user_id: 'user_id' },\n            { role: 'reader', type: 'public_permission' },\n          ],\n          created_time: 1595516162445,\n          last_edited_time: 1602527580000,\n          parent_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n          parent_table: 'space',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: 'user_id',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: 'user_id',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n      'f8cf7a08-bf80-4f8b-a842-3db49df95e4d': {\n        role: 'editor',\n        value: {\n          id: 'f8cf7a08-bf80-4f8b-a842-3db49df95e4d',\n          version: 49,\n          type: 'bulleted_list',\n          properties: { title: [['Estou testando']] },\n          content: ['0b73eab8-8c01-4140-ab4d-cd6a0886cd76'],\n          created_time: 1602511500000,\n          last_edited_time: 1602527580000,\n          parent_id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n      '0b73eab8-8c01-4140-ab4d-cd6a0886cd76': {\n        role: 'editor',\n        value: {\n          id: '0b73eab8-8c01-4140-ab4d-cd6a0886cd76',\n          version: 12,\n          type: 'bulleted_list',\n          properties: { title: [['isso daqui']] },\n          content: ['6bebe374-1569-4836-9de5-847c91ecb3f8'],\n          created_time: 1602527580000,\n          last_edited_time: 1602527580000,\n          parent_id: 'f8cf7a08-bf80-4f8b-a842-3db49df95e4d',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n      '6bebe374-1569-4836-9de5-847c91ecb3f8': {\n        role: 'editor',\n        value: {\n          id: '6bebe374-1569-4836-9de5-847c91ecb3f8',\n          version: 32,\n          type: 'bulleted_list',\n          properties: { title: [['vamos ver se funciona']] },\n          created_time: 1602527580000,\n          last_edited_time: 1602527580000,\n          parent_id: '0b73eab8-8c01-4140-ab4d-cd6a0886cd76',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n    },\n    space: {\n      '159177ec-0fb0-469e-a900-1a662b145a04': {},\n    },\n  },\n  cursor: { stack: [] },\n};\n\nexport const SUCCESSFUL_PAGE_CHUCK_WITH_CHILDREN_NOT_IN_CHUNK = {\n  recordMap: {\n    block: {\n      '4d64bbc0-634d-4758-befa-85c5a3a6c22f': {\n        role: 'editor',\n        value: {\n          id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          version: 356,\n          type: 'page',\n          properties: { title: [['Simple Page Text 2']] },\n          content: ['9dfff855-7aed-4a99-8e60-2e1384399200'],\n          format: {},\n          permissions: [\n            { role: 'editor', type: 'user_permission', user_id: 'user_id' },\n            { role: 'reader', type: 'public_permission' },\n          ],\n          created_time: 1595516162445,\n          last_edited_time: 1602862020000,\n          parent_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n          parent_table: 'space',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: 'user_id',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: 'user_id',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n      '9dfff855-7aed-4a99-8e60-2e1384399200': {\n        role: 'editor',\n        value: {\n          id: '9dfff855-7aed-4a99-8e60-2e1384399200',\n          version: 40,\n          type: 'toggle',\n          properties: { title: [['Isso é um detail']] },\n          content: ['12cfada5-6686-4561-9c4f-177b3be4b422'],\n          created_time: 1602689700000,\n          last_edited_time: 1602861180000,\n          parent_id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: 'use_id',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: 'use_id',\n          shard_id: 285188,\n          space_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        },\n      },\n    },\n    space: {\n      '159177ec-0fb0-469e-a900-1a662b145a04': {\n        role: 'editor',\n        value: {\n          id: '159177ec-0fb0-469e-a900-1a662b145a04',\n          version: 173,\n          name: 'workspace_name',\n          domain: 'domais',\n          permissions: [{ role: 'editor', type: 'user_permission', user_id: 'user_id' }],\n          icon: 'https://lh3.googleusercontent.com/a-/AOh14GjqdgJy-51RgCHKPgIMrNaIX0y4QUAL6Mz9OFS4=s100',\n          beta_enabled: false,\n          pages: [\n            'b0e76ef7-ec48-4aeb-9877-3b7907f4335c',\n            '8cb60e50-7dec-4a8c-9032-e6c30a8ec642',\n            'c1665d8e-8d20-4b32-9a44-320428580441',\n            '98c36805-bd5f-448f-b199-7fbff24d9963',\n            'a8624141-f0bd-4e41-9f02-83bbc7fc2b4a',\n            'b2115542-dfaa-4599-8ba9-77d467a33f94',\n            '739c26a3-770d-44e7-b652-70b3a5ae0220',\n            'c215286e-fd31-4d74-9266-e832603a3e8e',\n            '5ca7e1ba-65f4-422d-a274-529f8f8ec664',\n            '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n            'f9466b44-33b3-4cb2-9083-b820ead2c4fd',\n          ],\n          created_time: 1591708116643,\n          last_edited_time: 1602476700000,\n          created_by_table: 'notion_user',\n          created_by_id: 'user_id',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: 'user_id',\n          shard_id: 285188,\n          plan_type: 'personal',\n          invite_link_code: '09cb83f87702b6ce2be323132a369b69e23085b3',\n          invite_link_enabled: true,\n        },\n      },\n    },\n  },\n  cursor: { stack: [] },\n};\n\nexport const SUCCESSFUL_SYNC_RECORD_VALUE = {\n  recordMap: {\n    block: {\n      '12cfada5-6686-4561-9c4f-177b3be4b422': {\n        role: 'editor',\n        value: {\n          id: '12cfada5-6686-4561-9c4f-177b3be4b422',\n          version: 27,\n          type: 'text',\n          properties: { title: [['Lide com isso!']] },\n          created_time: 1602861180000,\n          last_edited_time: 1602861180000,\n          parent_id: '9dfff855-7aed-4a99-8e60-2e1384399200',\n          parent_table: 'block',\n          alive: true,\n          created_by_table: 'notion_user',\n          created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n          last_edited_by_table: 'notion_user',\n          last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n        },\n      },\n    },\n  },\n};\n\nexport const SUCCESSFUL_RECORDS_WITH_CHILDREN = {\n  results: [\n    {\n      role: 'reader',\n      value: {\n        id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n        version: 262,\n        type: 'page',\n        properties: { title: [['Simple Page Text 2']] },\n        content: ['f8cf7a08-bf80-4f8b-a842-3db49df95e4d'],\n        permissions: [\n          { role: 'editor', type: 'user_permission', user_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3' },\n          { role: 'reader', type: 'public_permission' },\n        ],\n        created_time: 1595516162445,\n        last_edited_time: 1602527580000,\n        parent_id: '159177ec-0fb0-469e-a900-1a662b145a04',\n        parent_table: 'space',\n        alive: true,\n        created_by_table: 'notion_user',\n        created_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n        last_edited_by_table: 'notion_user',\n        last_edited_by_id: '408c862f-f07b-4036-b414-1ae5c5ce57b3',\n      },\n    },\n  ],\n};\n\nexport const NO_PAGE_ACCESS_RECORDS = { results: [{ role: 'none' }] };\n\nexport const MISSING_CONTENT_RECORDS = {\n  results: [\n    {\n      role: 'reader',\n      value: {\n        id: '9a75a541-277f-4a64-80e7-5581f36672ba',\n        version: 22,\n        type: 'page',\n        permissions: [{ role: 'reader', type: 'public_permission' }],\n        created_time: 1595551283696,\n        last_edited_time: 1595551260000,\n        alive: true,\n      },\n    },\n  ],\n};\n\nexport const SINGLE_PAGE_WITH_COVER_IMAGE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    format: { page_cover: '/images/page-cover/solid_blue.png', page_cover_position: 0.6 },\n    properties: {\n      title: [['Page Title']],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_PAGE_WITH_ICON = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    format: { page_icon: '🤴' },\n    properties: {\n      title: [['Page Title']],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_AND_TITLE_NOTION_API_CONTENT_RESPONSE = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: { title: [['Hello World']] },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {\n      title: [['Hello '], ['World', [['b']]]],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD_AND_ITALIC_TOGETHER = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {\n      title: [['Hello '], ['World', [['b'], ['i']]]],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_BOLD_AND_ITALIC = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {\n      title: [\n        ['Hello ', [['b']]],\n        ['World', [['i']]],\n      ],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_COLOR = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {\n      title: [['Hello', [['h', 'purple']]]],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_EQUATION = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: {\n      title: [['Hello World '], ['⁍', [['e', '2x']]]],\n    },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_LINK = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    properties: { title: [['Hello '], ['World', [['a', 'https://www.google.com']]]] },\n    contents: [],\n  },\n];\n\nexport const SINGLE_TEXT_WITH_FORMAT = [\n  {\n    id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n    type: 'text',\n    format: { block_color: 'red_background' },\n    properties: { title: [['Hello '], ['World', [['a', 'https://www.google.com']]]] },\n    contents: [],\n  },\n];\n\nexport const CALLOUT_WITH_PAGE_ICON = [\n  {\n    id: '16431c64-3bf0-481f-a29f-d544780d84f3',\n    type: 'callout',\n    properties: { title: [['This is a callout']] },\n    format: { page_icon: '💡' },\n    contents: [],\n  },\n];\n\nexport const IMAGE_WITH_CUSTOM_SIZE = [\n  {\n    id: 'ec3b36fd-f77d-46b4-8592-5966488612b1',\n    type: 'image',\n    properties: {\n      source: [\n        [\n          'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg',\n        ],\n      ],\n    },\n    format: {\n      block_width: 240,\n      block_height: 50,\n      display_source:\n        'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg',\n      block_full_width: false,\n      block_page_width: false,\n      block_aspect_ratio: 1,\n      block_preserve_scale: true,\n    },\n    contents: [],\n  },\n];\n\nexport const TEXT_NOTION_API_CONTENT_RESPONSE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { title: [['Simple Page Test']] },\n    format: { page_cover: 'https://www.example.com/image.png', page_cover_position: 0.6, page_icon: '🤴' },\n    contents: [\n      {\n        id: '80d0fc46-5511-4d1d-a4ec-8b2f43d75226',\n        type: 'text',\n        properties: { title: [['Hello World']] },\n        contents: [],\n      },\n    ],\n  },\n];\n\nexport const VIDEO_NOTION_API_CONTENT_RESPONSE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { title: [['Simple Page Test']] },\n    contents: [\n      {\n        id: 'dcde43cb-7131-4687-8f22-c9789fa75f46',\n        type: 'video',\n        properties: { source: [['https://www.youtube.com/watch?v=xBFqxBfLJWc']] },\n        contents: [],\n      },\n    ],\n  },\n];\n\nexport const LIST_WITH_CHILDREN_RESPONSE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { title: [['Simple Page Text 2']] },\n    contents: [\n      {\n        id: 'f8cf7a08-bf80-4f8b-a842-3db49df95e4d',\n        type: 'bulleted_list',\n        properties: { title: [['Estou testando']] },\n        contents: [\n          {\n            id: '0b73eab8-8c01-4140-ab4d-cd6a0886cd76',\n            type: 'bulleted_list',\n            properties: { title: [['isso daqui']] },\n            contents: [\n              {\n                id: '6bebe374-1569-4836-9de5-847c91ecb3f8',\n                type: 'bulleted_list',\n                properties: { title: [['vamos ver se funciona']] },\n                contents: [],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport const DETAILS_RESPONSE = [\n  {\n    id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n    type: 'page',\n    properties: { title: [['Simple Page Text 2']] },\n    format: {},\n    contents: [\n      {\n        id: '9dfff855-7aed-4a99-8e60-2e1384399200',\n        type: 'toggle',\n        properties: { title: [['Isso é um detail']] },\n        contents: [\n          {\n            id: '12cfada5-6686-4561-9c4f-177b3be4b422',\n            type: 'text',\n            properties: { title: [['Lide com isso!']] },\n            contents: [],\n          },\n        ],\n      },\n    ],\n  },\n];\n"
  },
  {
    "path": "src/data/helpers/block-to-inner-html.ts",
    "content": "import { Block, DecorableText } from '../protocols/blocks';\nimport { Decorator } from '../use-cases/blocks-to-html-converter/block-parsers/decorations/decorator';\nimport { replaceLineBreakByBrTag } from './replace-line-break-to-br-tag';\n\nexport const blockToInnerHtml = async (block: Block): Promise<string> => {\n  const decorator = new Decorator(block.decorableTexts || ([] as DecorableText[]));\n  const decoratedText = await decorator.decorate();\n  return Promise.resolve(replaceLineBreakByBrTag(decoratedText));\n};\n"
  },
  {
    "path": "src/data/helpers/block-to-inner-text.ts",
    "content": "import { Block } from '../../data/protocols/blocks';\n\nexport const blockToInnerText = (block: Block): string => {\n  const decorableTexts = block.decorableTexts;\n  return decorableTexts ? decorableTexts.map((dt) => dt.text).join('') : '';\n};\n"
  },
  {
    "path": "src/data/helpers/blocks-to-html.ts",
    "content": "import { Block } from '../protocols/blocks';\nimport { ListBlocksWrapper, BlockDispatcher, BlocksToHTML } from '../use-cases/blocks-to-html-converter';\n\nexport const blocksToHtml = async (blocks: Block[]): Promise<string> => {\n  const dispatcher = new BlockDispatcher();\n  const listWrapper = new ListBlocksWrapper();\n  const blocksToHtmlConverter = new BlocksToHTML(blocks, dispatcher, listWrapper);\n  const html = await blocksToHtmlConverter.convert();\n  return Promise.resolve(html);\n};\n\nexport const indentBlocksToHtml = async (blocks: Block[]): Promise<string> => {\n  if (blocks.length === 0) return Promise.resolve('');\n\n  const html = await blocksToHtml(blocks);\n  return Promise.resolve(html);\n};\n"
  },
  {
    "path": "src/data/helpers/color-to-hex.ts",
    "content": "export const backgroundColorToHex = (color: string): string => {\n  return backgroundColorsToHex[color] || '#FFFFFF';\n};\n\nexport const foregroundColorToHex = (color: string): string => {\n  return foregroundColorTextToHEX[color] || '#37352F';\n};\n\nconst foregroundColorTextToHEX: Record<string, string> = {\n  purple: '#6940A5',\n  yellow: '#E9AB01',\n  gray: '#9B9A97',\n  brown: '#64473A',\n  orange: '#D9730D',\n  green: '#0F7B6C',\n  blue: '#0B6E99',\n  pink: '#AD1A72',\n  red: '#E03E3E',\n  none: '#37352F',\n};\n\nconst backgroundColorsToHex: Record<string, string> = {\n  gray_background: '#B4AEAE',\n  brown_background: '#E9E5E3',\n  orange_background: '#FAEBDD',\n  yellow_background: '#FBF3DB',\n  green_background: '#DDEDEA',\n  blue_background: '#DDEBF1',\n  purple_background: '#EAE4F2',\n  pink_background: '#F4DFEB',\n  red_background: '#FBE4E4',\n};\n"
  },
  {
    "path": "src/data/helpers/replace-line-break-to-br-tag.ts",
    "content": "export const replaceLineBreakByBrTag = (str: string): string => str.replace(/[\\r\\n]/g, '</br>');\n"
  },
  {
    "path": "src/data/protocols/blocks/block.ts",
    "content": "import { DecorableText } from './decorable-text';\nimport { Format } from './format';\n\nexport type Block = {\n  id: string;\n  type: string;\n  children: Block[];\n  properties: Record<string, any>;\n  format: Format;\n  decorableTexts: DecorableText[];\n};\n"
  },
  {
    "path": "src/data/protocols/blocks/decorable-text.ts",
    "content": "import { Decoration } from './decoration';\n\nexport type DecorableText = {\n  text: string;\n  decorations: Decoration[];\n};\n"
  },
  {
    "path": "src/data/protocols/blocks/decoration.ts",
    "content": "export type Decoration = {\n  type: DecorationType;\n  value?: string;\n};\n\nexport type DecorationType =\n  | 'plain'\n  | 'bold'\n  | 'italic'\n  | 'underline'\n  | 'strikethrough'\n  | 'link'\n  | 'code'\n  | 'color'\n  | 'equation';\n"
  },
  {
    "path": "src/data/protocols/blocks/format.ts",
    "content": "export type Format = {\n  block_color?: string;\n  block_width?: number;\n  page_icon?: string;\n  page_cover?: string;\n  page_cover_position?: number;\n};\n"
  },
  {
    "path": "src/data/protocols/blocks/index.ts",
    "content": "export * from './block';\nexport * from './decorable-text';\nexport * from './decoration';\nexport * from './list-blocks-wrapper';\n"
  },
  {
    "path": "src/data/protocols/blocks/list-blocks-wrapper.ts",
    "content": "import { Block } from './block';\n\nexport interface ListBlocksWrapper {\n  wrapLists(blocks: Block[]): Block[];\n}\n"
  },
  {
    "path": "src/data/protocols/html-options/html-options.ts",
    "content": "export type HtmlOptions = {\n  excludeTitleFromHead?: boolean;\n  excludeCSS?: boolean;\n  excludeMetadata?: boolean;\n  excludeScripts?: boolean;\n  excludeHeaderFromBody?: boolean;\n  bodyContentOnly?: boolean;\n};\n"
  },
  {
    "path": "src/data/protocols/http-request/http-get-client.ts",
    "content": "import { HttpResponse } from './http-response';\n\nexport interface HttpGetClient {\n  get(url: string): Promise<HttpResponse>;\n}\n"
  },
  {
    "path": "src/data/protocols/http-request/http-post-client.ts",
    "content": "import { HttpResponse } from './http-response';\n\nexport interface HttpPostClient {\n  post(url: string, body: Record<string, any>): Promise<HttpResponse>;\n}\n"
  },
  {
    "path": "src/data/protocols/http-request/http-response.ts",
    "content": "export type HttpResponse = {\n  status: number;\n  data: Record<string, any> | string;\n  headers?: Record<string, string[]>;\n};\n"
  },
  {
    "path": "src/data/protocols/http-request/index.ts",
    "content": "export * from './http-post-client';\nexport * from './http-get-client';\nexport * from './http-response';\n"
  },
  {
    "path": "src/data/protocols/page-props/image-cover.ts",
    "content": "export type ImageCover = {\n  base64: string;\n  position: number;\n};\n"
  },
  {
    "path": "src/data/protocols/page-props/index.ts",
    "content": "export * from './page-props';\nexport * from './image-cover';\n"
  },
  {
    "path": "src/data/protocols/page-props/page-props.ts",
    "content": "export type PageProps = {\n  title: string;\n  coverImageSrc?: string;\n  coverImagePosition?: number;\n  icon?: string;\n};\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-dispatcher.ts",
    "content": "import { Block } from '../../protocols/blocks';\nimport { ToHtml, ToHtmlClass } from '../../../domain/use-cases/to-html';\nimport * as blockParsers from './block-parsers';\n\nexport class BlockDispatcher {\n  dispatch(block: Block): ToHtml {\n    const ToHtmlConverter = fromBlockToHtmlConverter[block.type] || blockParsers.UnknownBlockToHtml;\n    return new ToHtmlConverter(block);\n  }\n}\n\nconst fromBlockToHtmlConverter: Record<string, ToHtmlClass> = {\n  text: blockParsers.TextBlockToHtml,\n  header: blockParsers.HeaderBlockToHtml,\n  sub_header: blockParsers.SubHeaderBlockParser,\n  sub_sub_header: blockParsers.SubSubHeaderBlockParser,\n  to_do: blockParsers.ToDoBlockToHtml,\n  code: blockParsers.CodeBlockToHtml,\n  equation: blockParsers.EquationBlockToHtml,\n  quote: blockParsers.QuoteBlockToHtml,\n  divider: blockParsers.DividerBlockToHtml,\n  list: blockParsers.ListBlockToHtml,\n  video: blockParsers.YouTubeVideoBlockToHtml,\n  image: blockParsers.ImageBlockToHtml,\n  callout: blockParsers.CalloutBlockToHtml,\n  toggle: blockParsers.ToggleBlockToHtml,\n  page: blockParsers.PageBlockToHtml,\n};\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/callout.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\nimport { Base64Converter } from '../../../../utils/base-64-converter';\n\nexport class CalloutBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    const iconHtml = await new IconToHtml(this._block.properties.page_icon, this._block.id).toHtml();\n\n    return Promise.resolve(`\n<div class=\"callout\"${style}>\n${iconHtml}\n<p>${await blockToInnerHtml(this._block)}</p>\n</div>\n    `);\n  }\n}\n\nclass IconToHtml {\n  private readonly _icon: string | undefined;\n  private readonly _id: string;\n\n  constructor(icon: string | undefined, id: string) {\n    this._icon = icon;\n    this._id = id;\n  }\n\n  async toHtml(): Promise<string> {\n    if (!this._icon) return `<div class=\"callout-emoji\">💡</div>`;\n    if (!this._icon.startsWith('http')) return `<div class=\"callout-emoji\">${this._icon}</div>`;\n\n    const url = `https://www.notion.so/image/${encodeURIComponent(this._icon)}?table=block&id=${this._id}`;\n    const imageSource = await Base64Converter.convert(url);\n    const caption = 'callout icon';\n    return `<div class=\"callout-image\"><img src=\"${imageSource}\" alt=\"${caption}\"></div>`;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/code.ts",
    "content": "import { Block } from '../../../../data/protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { blockToInnerText } from '../../../helpers/block-to-inner-text';\n\nexport class CodeBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const languageClass = this._language ? `class=\"language-${this._language}\"` : '';\n\n    return Promise.resolve(\n      `<pre><code ${languageClass}>${blockToInnerText(this._block).replace(/(\\s{4}|\\t)/g, '  ')}</code></pre>`,\n    );\n  }\n\n  private get _language(): string {\n    return this._block.properties?.language?.toLowerCase().replace(/ /g, '');\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-dispatcher.ts",
    "content": "import { Decoration } from '../../../../../data/protocols/blocks/decoration';\nimport { ToHtml, ToHtmlClass } from '../../../../../domain/use-cases/to-html';\nimport * as DecorationParsers from './decoration-parsers';\n\nexport class DecoratorDispatcher {\n  dispatch(text: string, decoration: Decoration): ToHtml {\n    const ToHtmlConverter = fromDecorationTypeToParsers[decoration.type] || DecorationParsers.UnknownDecorationToHtml;\n    return new ToHtmlConverter(text, decoration);\n  }\n}\n\nconst fromDecorationTypeToParsers: Record<string, ToHtmlClass> = {\n  bold: DecorationParsers.BoldDecorationToHtml,\n  italic: DecorationParsers.ItalicDecorationToHtml,\n  strikethrough: DecorationParsers.StrikeThroughDecorationToHtml,\n  code: DecorationParsers.CodeDecorationToHtml,\n  underline: DecorationParsers.UnderlineDecorationToHtml,\n  equation: DecorationParsers.EquationDecorationToHtml,\n  link: DecorationParsers.LinkDecorationToHtml,\n  color: DecorationParsers.ColorDecorationToHtml,\n};\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/bold.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class BoldDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<strong>${this._text}</strong>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/code.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class CodeDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<code>${this._text}</code>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/color.test.ts",
    "content": "import { ColorDecorationToHtml } from './color';\nimport { Decoration } from '../../../../../protocols/blocks';\n\ndescribe('#convert', () => {\n  describe('When color is given as foreground color', () => {\n    it('styles using css color property', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'purple' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toMatch('style=\"color:');\n    });\n  });\n\n  describe('When color is given as background color', () => {\n    it('styles using css background-color property', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'purple_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toMatch('style=\"background-color:');\n    });\n\n    it('do not preserves color value on style', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'purple_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).not.toMatch('purple_background');\n    });\n  });\n\n  describe('When purple color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'purple' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #6940A5;\">Text with color</span>');\n    });\n  });\n\n  describe('When yellow color is given as color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'yellow' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #E9AB01;\">Text with color</span>');\n    });\n  });\n\n  describe('When gray color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'gray' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #9B9A97;\">Text with color</span>');\n    });\n  });\n\n  describe('When brown color is given as color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'brown' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #64473A;\">Text with color</span>');\n    });\n  });\n\n  describe('When orange color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'orange' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #D9730D;\">Text with color</span>');\n    });\n  });\n\n  describe('When green color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'green' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #0F7B6C;\">Text with color</span>');\n    });\n  });\n\n  describe('When pink color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'pink' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #AD1A72;\">Text with color</span>');\n    });\n  });\n\n  describe('When red color is given as foreground color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'red' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #E03E3E;\">Text with color</span>');\n    });\n  });\n\n  describe('When unknown color is given as foreground color', () => {\n    it('converts to equivalent default foreground color hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'refafad' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #37352F;\">Text with color</span>');\n    });\n  });\n\n  describe('When no color value is given as foreground color', () => {\n    it('converts to equivalent default foreground color hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"color: #37352F;\">Text with color</span>');\n    });\n  });\n\n  describe('When gray color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'gray_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #B4AEAE;\">Text with color</span>');\n    });\n  });\n\n  describe('When brown color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'brown_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #E9E5E3;\">Text with color</span>');\n    });\n  });\n\n  describe('When orange color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'orange_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #FAEBDD;\">Text with color</span>');\n    });\n  });\n\n  describe('When yellow color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'yellow_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #FBF3DB;\">Text with color</span>');\n    });\n  });\n\n  describe('When green color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'green_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #DDEDEA;\">Text with color</span>');\n    });\n  });\n\n  describe('When blue color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'blue_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #DDEBF1;\">Text with color</span>');\n    });\n  });\n\n  describe('When purple color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'purple_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #EAE4F2;\">Text with color</span>');\n    });\n  });\n\n  describe('When pink color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'pink_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #F4DFEB;\">Text with color</span>');\n    });\n  });\n\n  describe('When red color is given as background color', () => {\n    it('converts to equivalent hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'red_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #FBE4E4;\">Text with color</span>');\n    });\n  });\n\n  describe('When unknown color is given as background color', () => {\n    it('converts to equivalent default background color hex code and apply style to html', async () => {\n      const text = 'Text with color';\n      const decoration: Decoration = { type: 'color', value: 'refafad_background' };\n\n      const result = await new ColorDecorationToHtml(text, decoration).convert();\n\n      expect(result).toBe('<span style=\"background-color: #FFFFFF;\">Text with color</span>');\n    });\n  });\n});\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/color.ts",
    "content": "import { Decoration } from '../../../../../../data/protocols/blocks';\nimport { ToHtml } from '../../../../../../domain/use-cases/to-html';\nimport { foregroundColorToHex, backgroundColorToHex } from '../../../../../helpers/color-to-hex';\n\nexport class ColorDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n  private readonly _decoration: Decoration;\n\n  constructor(text: string, decoration: Decoration) {\n    this._text = text;\n    this._decoration = decoration;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<span style=\"${this._style}\">${this._text}</span>`);\n  }\n\n  private _isBackground(): boolean {\n    return !!this._decoration.value?.includes('_background');\n  }\n\n  private get _style() {\n    const textColor = this._decoration.value || 'none';\n\n    if (this._isBackground()) return `background-color: ${backgroundColorToHex(textColor)};`;\n    return `color: ${foregroundColorToHex(textColor)};`;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/equation.ts",
    "content": "import { Decoration } from '../../../../../../data/protocols/blocks';\nimport { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class EquationDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n  private readonly _decoration: Decoration;\n\n  constructor(text: string, decoration: Decoration) {\n    this._text = text;\n    this._decoration = decoration;\n  }\n\n  async convert(): Promise<string> {\n    const equation = this._decoration.value;\n    return Promise.resolve(equation ? `$${equation}$` : '');\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/index.ts",
    "content": "export * from './bold';\nexport * from './code';\nexport * from './color';\nexport * from './equation';\nexport * from './italic';\nexport * from './link';\nexport * from './strikethrough';\nexport * from './underline';\nexport * from './unknown';\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/italic.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class ItalicDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<em>${this._text}</em>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/link.ts",
    "content": "import { Decoration } from '../../../../../../data/protocols/blocks';\nimport { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class LinkDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n  private readonly _decoration: Decoration;\n\n  constructor(text: string, decoration: Decoration) {\n    this._text = text;\n    this._decoration = decoration;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<a href=\"${this._link}\" target=\"_blank\">${this._text}</a>`);\n  }\n\n  private get _link(): string {\n    return this._decoration.value || '#';\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/strikethrough.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class StrikeThroughDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<del>${this._text}</del>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/underline.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\nexport class UnderlineDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<span style=\"text-decoration: underline;\">${this._text}</span>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decoration-parsers/unknown.ts",
    "content": "import { ToHtml } from '../../../../../../domain/use-cases/to-html';\n\nexport class UnknownDecorationToHtml implements ToHtml {\n  private readonly _text: string;\n\n  constructor(text: string) {\n    this._text = text;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(this._text);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/decorations/decorator.ts",
    "content": "import { DecorableText } from '../../../../../data/protocols/blocks/decorable-text';\nimport { DecoratorDispatcher } from './decoration-dispatcher';\n\nexport class Decorator {\n  private readonly _decorableTexts: DecorableText[];\n\n  constructor(decorableTexts: DecorableText[]) {\n    this._decorableTexts = decorableTexts;\n  }\n\n  async decorate(): Promise<string> {\n    const decorableTextsByDecorators = await Promise.all(\n      this._decorableTexts.map(await this._decorateByDecorableText.bind(this)),\n    );\n    return Promise.resolve(decorableTextsByDecorators.join(''));\n  }\n\n  async _decorateByDecorableText(decorableText: DecorableText): Promise<string> {\n    let html = decorableText.text;\n    for (const decoration of decorableText.decorations) {\n      const decorator = new DecoratorDispatcher().dispatch(html, decoration);\n      html = await decorator.convert();\n    }\n\n    return Promise.resolve(html);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/divider.ts",
    "content": "import { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\n\nexport class DividerBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    return Promise.resolve(`<hr>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/equation.ts",
    "content": "import { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { blockToInnerText } from '../../../helpers/block-to-inner-text';\n\nexport class EquationBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const { decorableTexts } = this._block;\n    if (decorableTexts.length === 0) return Promise.resolve('');\n\n    return Promise.resolve(decorableTexts ? `<div class=\"equation\">$$${blockToInnerText(this._block)}$$</div>` : '');\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/header.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\n\nexport class HeaderBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    return Promise.resolve(`<h1${style}>${await blockToInnerHtml(this._block)}</h1>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/image.ts",
    "content": "import { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { Base64Converter } from '../../../../utils/base-64-converter';\nimport { FormatToStyle } from '../../format-to-style';\n\nexport class ImageBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    if (!this._rawSrc) return '';\n\n    const imageSource = await Base64Converter.convert(this._rawSrc);\n    const caption = this._caption;\n    const style = new FormatToStyle(this._block.format).toStyle();\n\n    return `\n<figure class=\"image\">\n<img src=\"${imageSource}\" alt=\"${caption}\"${style}>\n${caption !== '' ? `<figcaption>${caption}</figcaption>` : ''}\n</figure>\n    `;\n  }\n\n  private get _rawSrc() {\n    const url = this._block.properties.source;\n    if (!url) return;\n\n    return `https://www.notion.so/image/${encodeURIComponent(url)}?table=block&id=${this._block.id}`;\n  }\n\n  private get _caption() {\n    return this._block.properties.caption || '';\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/index.ts",
    "content": "export * from './image';\nexport * from './list';\nexport * from './callout';\nexport * from './code';\nexport * from './divider';\nexport * from './equation';\nexport * from './header';\nexport * from './quote';\nexport * from './sub-header';\nexport * from './sub-sub-header';\nexport * from './text';\nexport * from './to-do';\nexport * from './toggle';\nexport * from './page';\nexport * from './unknown';\nexport * from './youtube-video';\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/list/index.ts",
    "content": "export * from './list';\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/list/list-item.ts",
    "content": "import { blockToInnerHtml } from '../../../../helpers/block-to-inner-html';\nimport { Block } from '../../../../protocols/blocks';\nimport { ToHtml } from '../../../../../domain/use-cases/to-html';\nimport { indentBlocksToHtml } from '../../../../helpers/blocks-to-html';\n\nexport class ListItemToHtml implements ToHtml {\n  private _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const childrenHtml = await indentBlocksToHtml(this._block.children);\n\n    return Promise.resolve(`<li>${await blockToInnerHtml(this._block)}${childrenHtml}</li>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/list/list.ts",
    "content": "import { Block } from '../../../../protocols/blocks';\nimport { ToHtml } from '../../../../../domain/use-cases/to-html';\nimport { ListItemToHtml } from './list-item';\nimport { FormatToStyle } from '../../../format-to-style';\n\nexport class ListBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const tag: string = fromTypeToTag[this._block.children[0].type] || fromTypeToTag.bulleted_list;\n    const style = new FormatToStyle(this._block.format).toStyle();\n\n    const innerHtml = await this._itemsHtml();\n\n    return Promise.resolve(`<${tag}${style}>\\n${innerHtml}\\n</${tag}>`);\n  }\n\n  private async _itemsHtml(): Promise<string> {\n    const items = await Promise.all(this._block.children.map(async (c) => new ListItemToHtml(c).convert()));\n    return Promise.resolve(items.join('\\n'));\n  }\n}\n\nconst fromTypeToTag: Record<string, string> = {\n  bulleted_list: 'ul',\n  numbered_list: 'ol',\n};\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/page.ts",
    "content": "import { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { blocksToHtml } from '../../../helpers/blocks-to-html';\n\nexport class PageBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    return blocksToHtml(this._block.children);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/quote.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\n\nexport class QuoteBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    return Promise.resolve(`<blockquote${style}>${await blockToInnerHtml(this._block)}</blockquote>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/sub-header.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\n\nexport class SubHeaderBlockParser implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    return Promise.resolve(`<h2${style}>${await blockToInnerHtml(this._block)}</h2>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/sub-sub-header.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\n\nexport class SubSubHeaderBlockParser implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    return Promise.resolve(`<h3${style}>${await blockToInnerHtml(this._block)}</h3>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/text.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\nimport { indentBlocksToHtml } from '../../../helpers/blocks-to-html';\n\nexport class TextBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    const childrenHtml = await indentBlocksToHtml(this._block.children);\n\n    return Promise.resolve(`<p${style}>${await blockToInnerHtml(this._block)}${childrenHtml}</p>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/to-do.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\nimport { indentBlocksToHtml } from '../../../helpers/blocks-to-html';\n\nexport class ToDoBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    const childrenHtml = await indentBlocksToHtml(this._block.children);\n\n    return Promise.resolve(`\\\n<ul class=\"to-do-list\"${style}>\n<li>\n<div class=\"checkbox checkbox-${this._isChecked() ? 'on' : 'off'}\"></div>\n<span class=\"to-do-children-${this._isChecked() ? 'checked' : 'unchecked'}\">${await blockToInnerHtml(\n      this._block,\n    )}</span>${childrenHtml}\n</li>\n</ul>`);\n  }\n\n  private _isChecked(): boolean {\n    return !!this._block.properties.checked;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/toggle.ts",
    "content": "import { blockToInnerHtml } from '../../../helpers/block-to-inner-html';\nimport { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\nimport { FormatToStyle } from '../../format-to-style';\nimport { indentBlocksToHtml } from '../../../helpers/blocks-to-html';\n\nexport class ToggleBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const style = new FormatToStyle(this._block.format).toStyle();\n    const childrenHtml = await indentBlocksToHtml(this._block.children);\n\n    return Promise.resolve(`\n<details open=\"\"${style}>\n<summary>${await blockToInnerHtml(this._block)}</summary>\n${childrenHtml}\n</details>`);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/unknown.ts",
    "content": "import { ToHtml } from '../../../../domain/use-cases/to-html';\n\nexport class UnknownBlockToHtml implements ToHtml {\n  async convert(): Promise<string> {\n    return Promise.resolve('');\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/block-parsers/youtube-video.ts",
    "content": "import { Block } from '../../../protocols/blocks';\nimport { ToHtml } from '../../../../domain/use-cases/to-html';\n\nexport class YouTubeVideoBlockToHtml implements ToHtml {\n  private readonly _block: Block;\n\n  constructor(block: Block) {\n    this._block = block;\n  }\n\n  async convert(): Promise<string> {\n    const id = this._youtubeId;\n    if (!id) return '';\n    return `<iframe\n    width=\"560\"\n    height=\"315\"\n    src=\"https://www.youtube.com/embed/${id}\"\n    frameborder=\"0\"\n    allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n    allowfullscreen\n  ></iframe>`;\n  }\n\n  private get _youtubeId(): string | void {\n    const youtubeIdMatcher =\n      /(?:youtube\\.com\\/(?:[^\\/]+\\/.+\\/|(?:v|e(?:mbed)?)\\/|.*[?&]v=)|youtu\\.be\\/)([^\"&?\\/\\s]{11})/gi;\n    return youtubeIdMatcher.exec(this._src)?.[1];\n  }\n\n  private get _src() {\n    return this._block.properties?.source;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/blocks-to-html-converter.test.ts",
    "content": "import nock from 'nock';\nimport { resolve } from 'path';\n\nimport { Block } from '../../protocols/blocks';\nimport * as BlockMocks from '../../../__tests__/mocks/blocks';\nimport { BlocksToHTML, BlockDispatcher, ListBlocksWrapper } from './index';\nimport { ToHtml } from '../../../domain/use-cases/to-html';\nimport { Base64Converter } from '../../../utils/base-64-converter';\nimport base64Img from '../../../__tests__/mocks/img/base64';\n\ndescribe('#convert', () => {\n  const makeSut = (blocks: Block[]): ToHtml => {\n    const blockDispatcher = new BlockDispatcher();\n    const listBlocksWrapper = new ListBlocksWrapper();\n    return new BlocksToHTML(blocks, blockDispatcher, listBlocksWrapper);\n  };\n\n  describe('When only a text block is given', () => {\n    describe('When empty text block is given', () => {\n      it('returns empty p tag', async () => {\n        const html = await makeSut(BlockMocks.NO_TEXT).convert();\n\n        expect(html).toBe('<p></p>');\n      });\n    });\n\n    describe('When single text block is given', () => {\n      it('returns html with p tag', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT).convert();\n\n        expect(html).toBe('<p>Hello World</p>');\n      });\n    });\n\n    describe('When single text block has children', () => {\n      it('returns html with p tag and children blocks inside', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_CHILDREN).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<p>Hello World\n            <p>This is a child</p>\n            <p>This is a child too</p>\n          </p>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single line text with bold part', () => {\n      it('returns html with single p paragraph with strong tag nested', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_BOLD).convert();\n\n        expect(html).toBe('<p>Hello <strong>World</strong></p>');\n      });\n    });\n\n    describe('When single line text with italic part', () => {\n      it('returns html with single p paragraph with strong tag nested', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_ITALIC).convert();\n\n        expect(html).toBe('<p>Hello <em>World</em></p>');\n      });\n    });\n\n    describe('When single line text with underline part', () => {\n      it('returns html with single p paragraph with span tag and underline style nested', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_UNDERLINE).convert();\n\n        expect(html).toBe('<p>Hello <span style=\"text-decoration: underline;\">World</span></p>');\n      });\n    });\n\n    describe('When single line text with strikethrough part', () => {\n      it('returns html with single p paragraph with del tag inside', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_STRIKETHROUGH).convert();\n\n        expect(html).toBe('<p>Hello <del>World</del></p>');\n      });\n    });\n\n    describe('When single line text with code part', () => {\n      it('returns html with single p paragraph with code tag inside', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_CODE_DECORATION).convert();\n\n        expect(html).toBe('<p>Hello <code>myVar</code></p>');\n      });\n    });\n\n    describe('When single line text with link part', () => {\n      it('returns html with single p paragraph with a tag with given link', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_LINK).convert();\n\n        expect(html).toBe('<p>Hello <a href=\"https://www.google.com\" target=\"_blank\">World</a></p>');\n      });\n    });\n\n    describe('When single line text with inline equation part', () => {\n      it('returns html with single p paragraph equation wrapped inside $$', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_EQUATION_DECORATION).convert();\n\n        expect(html).toBe('<p>Hello World $2x$</p>');\n      });\n    });\n\n    describe('When single line text with color part', () => {\n      it('returns html with single p paragraph with span tag and color style inside', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_COLOR).convert();\n\n        expect(html).toBe('<p><span style=\"color: #6940A5;\">Hello</span></p>');\n      });\n    });\n\n    describe('When single line text with color background part', () => {\n      it('returns html with single p paragraph with span tag and background color style inside', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_COLOR_BACKGROUND).convert();\n\n        expect(html).toBe('<p><span style=\"background-color: #FBF3DB;\">Hello</span></p>');\n      });\n    });\n\n    describe('When single line text with bold and italic parts together', () => {\n      it('returns html with single p paragraph with strong and em tags nested', async () => {\n        const html = await makeSut(BlockMocks.SINGLE_TEXT_WITH_BOLD_AND_ITALIC).convert();\n\n        expect(html).toBe('<p>Hello <em><strong>World</strong></em></p>');\n      });\n    });\n\n    describe('When single line text with bold and italic parts apart', () => {\n      it('returns html with single p paragraph with strong and em tags nested', async () => {\n        const html = await makeSut(BlockMocks.TEXT_WITH_DECORATION).convert();\n\n        expect(html).toBe(\n          '<p>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></p>',\n        );\n      });\n    });\n\n    describe('When multiline text block is given', () => {\n      it('returns html with two p tags', async () => {\n        const html = await makeSut(BlockMocks.MULTILINE_TEXT).convert();\n\n        expect(html).toBe('<p>Hello World</br>Is everything alright?</br>Yes, Dude!</p>');\n      });\n    });\n\n    describe('When text block has background color', () => {\n      it('returns html p tag with style and background-color prop', async () => {\n        const html = await makeSut(BlockMocks.TEXT_WITH_FORMAT).convert();\n\n        expect(html).toBe('<p style=\"background-color: #FBE4E4; \">This is a text with red background</p>');\n      });\n    });\n\n    describe('When text block has foreground color', () => {\n      it('returns html p tag with style and color prop', async () => {\n        const html = await makeSut(BlockMocks.TEXT_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<p style=\"color: #6940A5; \">This is a text with purple color</p>');\n      });\n    });\n  });\n\n  describe('When only a h1 title block is given', () => {\n    describe('When single block is given', () => {\n      it('returns html with h1 tag', async () => {\n        const html = await makeSut(BlockMocks.H1_TEXT).convert();\n\n        expect(html).toBe('<h1>This is a h1 title</h1>');\n      });\n    });\n\n    describe('When single line header with decoration', () => {\n      it('returns html with single h1 with decoration tags inside', async () => {\n        const html = await makeSut(BlockMocks.H1_TEXT_WITH_DECORATIONS).convert();\n\n        expect(html).toBe(\n          '<h1>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></h1>',\n        );\n      });\n    });\n\n    describe('When header block has background color', () => {\n      it('returns html h1 tag with style and background-color prop', async () => {\n        const html = await makeSut(BlockMocks.H1_WITH_FORMAT).convert();\n\n        expect(html).toBe('<h1 style=\"background-color: #DDEDEA; \">This is a h1 with red background</h1>');\n      });\n    });\n\n    describe('When header block has foreground color', () => {\n      it('returns html h1 tag with style and color prop', async () => {\n        const html = await makeSut(BlockMocks.H1_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<h1 style=\"color: #E9AB01; \">This is a h1 with yellow color</h1>');\n      });\n    });\n  });\n\n  describe('When only a h2 title block is given', () => {\n    describe('When single block is given', () => {\n      it('returns html with h2 tag', async () => {\n        const html = await makeSut(BlockMocks.H2_TEXT).convert();\n\n        expect(html).toBe('<h2>This is a h2 title</h2>');\n      });\n    });\n\n    describe('When single line h2 with decoration', () => {\n      it('returns html with single h1 with decoration tags inside', async () => {\n        const html = await makeSut(BlockMocks.H2_TEXT_WITH_DECORATIONS).convert();\n\n        expect(html).toBe(\n          '<h2>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></h2>',\n        );\n      });\n    });\n\n    describe('When sub header block has background color', () => {\n      it('returns html h2 tag with style and background-color prop', async () => {\n        const html = await makeSut(BlockMocks.H2_WITH_FORMAT).convert();\n\n        expect(html).toBe('<h2 style=\"background-color: #FBF3DB; \">This is a h2 with yellow background</h2>');\n      });\n    });\n\n    describe('When sub header block has foreground color', () => {\n      it('returns html h2 tag with style and color prop', async () => {\n        const html = await makeSut(BlockMocks.H2_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<h2 style=\"color: #9B9A97; \">This is a h2 with gray color</h2>');\n      });\n    });\n  });\n\n  describe('When only a h3 title block is given', () => {\n    describe('When single block is given', () => {\n      it('returns html with h3 tag', async () => {\n        const html = await makeSut(BlockMocks.H3_TEXT).convert();\n\n        expect(html).toBe('<h3>This is a h3 title</h3>');\n      });\n    });\n\n    describe('When single line h3 with decoration', () => {\n      it('returns html with single h1 with decoration tags inside', async () => {\n        const html = await makeSut(BlockMocks.H3_TEXT_WITH_DECORATIONS).convert();\n\n        expect(html).toBe(\n          '<h3>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></h3>',\n        );\n      });\n    });\n\n    describe('When sub header block has background color', () => {\n      it('returns html h3 tag with style and background-color prop', async () => {\n        const html = await makeSut(BlockMocks.H3_WITH_FORMAT).convert();\n\n        expect(html).toBe('<h3 style=\"background-color: #FAEBDD; \">This is a h3 with orange background</h3>');\n      });\n    });\n\n    describe('When sub sub header block has foreground color', () => {\n      it('returns html h3 tag with style and color prop', async () => {\n        const html = await makeSut(BlockMocks.H3_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<h3 style=\"color: #64473A; \">This is a h3 with brown color</h3>');\n      });\n    });\n  });\n\n  describe('When only an unordered list block is given', () => {\n    describe('When single block is given', () => {\n      it('returns html with ul tag with li tag inside', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_SINGLE_ITEM).convert();\n\n        expect(html).toBe('<ul>\\n<li>This is a test</li>\\n</ul>');\n      });\n    });\n\n    describe('When block has children', () => {\n      it('returns html with ul and li tags and children blocks inside', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_CHILDREN).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<ul>\n            <li>Hello World\n              <ul>\n                <li>This is a child</li>\n                <li>This is a child too</li>\n              </ul>\n            </li>\n          </ul>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single block is given with background color', () => {\n      it('returns html with ul tag with li tag inside and background', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT).convert();\n\n        expect(html).toBe('<ul style=\"background-color: #E9E5E3; \">\\n<li>This is a item with background</li>\\n</ul>');\n      });\n    });\n\n    describe('When single block is given with foreground color', () => {\n      it('returns html with ul tag with li tag inside and foreground', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<ul style=\"color: #D9730D; \">\\n<li>This is a item with color</li>\\n</ul>');\n      });\n    });\n\n    describe('When list block with two items is given', () => {\n      it('returns html with ul tag with li tag inside', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_TWO_ITEMS).convert();\n\n        expect(html).toBe('<ul>\\n<li>This is a test</li>\\n<li>This is a test too</li>\\n</ul>');\n      });\n    });\n\n    describe('When single line unordered list with decoration', () => {\n      it('returns html with ul tag with li tag and decorations tags inside', async () => {\n        const html = await makeSut(BlockMocks.UNORDERED_LIST_WITH_DECORATED_ITEMS).convert();\n\n        expect(html).toBe(\n          '<ul>\\n<li>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></li>\\n</ul>',\n        );\n      });\n    });\n  });\n\n  describe('When only an ordered list block is given', () => {\n    describe('When single block is given', () => {\n      it('returns html with ol tag with li tag inside', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_SINGLE_ITEM).convert();\n\n        expect(html).toBe('<ol>\\n<li>This is a test</li>\\n</ol>');\n      });\n    });\n\n    describe('When block has children', () => {\n      it('returns html with ul and li tags and children blocks inside', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_CHILDREN).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<ol>\n            <li>Hello World\n              <ol>\n                <li>This is a child</li>\n                <li>This is a child too</li>\n              </ol>\n            </li>\n          </ol>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single block is given with background color', () => {\n      it('returns html with ol tag with li tag inside and background', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT).convert();\n\n        expect(html).toBe('<ol style=\"background-color: #B4AEAE; \">\\n<li>This is a item with background</li>\\n</ol>');\n      });\n    });\n\n    describe('When single block is given with foreground color', () => {\n      it('returns html with ol tag with li tag inside and foreground', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_SINGLE_ITEM_AND_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<ol style=\"color: #0F7B6C; \">\\n<li>This is a item with color</li>\\n</ol>');\n      });\n    });\n\n    describe('When list block with two items is given', () => {\n      it('returns html with ol tag with li tag inside', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_TWO_ITEMS).convert();\n\n        expect(html).toBe('<ol>\\n<li>This is a test</li>\\n<li>This is a test too</li>\\n</ol>');\n      });\n    });\n\n    describe('When single line ordered list with decoration', () => {\n      it('returns html with ol tag with li tag and decorations tags inside', async () => {\n        const html = await makeSut(BlockMocks.ORDERED_LIST_WITH_DECORATED_ITEMS).convert();\n\n        expect(html).toBe(\n          '<ol>\\n<li>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></li>\\n</ol>',\n        );\n      });\n    });\n  });\n\n  describe('When only a to do list block is given', () => {\n    describe('When single unchecked block is given', () => {\n      it('returns html with a div and unchecked checkbox and label inside', async () => {\n        const html = await makeSut(BlockMocks.TODO).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\">\n          <li>\n            <div class=\"checkbox checkbox-off\"></div>\n            <span class=\"to-do-children-unchecked\">This is a test</span>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When block has children', () => {\n      it('returns html with todo and children blocks inside', async () => {\n        const html = await makeSut(BlockMocks.TODO_WITH_CHILDREN).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\">\n          <li>\n            <div class=\"checkbox checkbox-off\"></div>\n            <span class=\"to-do-children-unchecked\">Hello World</span>\\\n              <ul class=\"to-do-list\">\n                <li>\n                  <div class=\"checkbox checkbox-off\"></div>\n                  <span class=\"to-do-children-unchecked\">This is a child</span>\\\n                </li>\\\n              </ul>\\\n              <ul class=\"to-do-list\">\n                <li>\n                  <div class=\"checkbox checkbox-on\"></div>\n                  <span class=\"to-do-children-checked\">This is a child too</span>\\\n                </li>\\\n              </ul>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single unchecked block with background color is given', () => {\n      it('returns html with a div and unchecked checkbox and label inside with style on div', async () => {\n        const html = await makeSut(BlockMocks.TODO_WITH_FORMAT).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\" style=\"background-color: #DDEBF1; \">\n          <li>\n            <div class=\"checkbox checkbox-off\"></div>\n            <span class=\"to-do-children-unchecked\">This is a todo with style</span>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single unchecked block with foreground color is given', () => {\n      it('returns html with a div and unchecked checkbox and label inside with style on div', async () => {\n        const html = await makeSut(BlockMocks.TODO_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\" style=\"color: #0B6E99; \">\n          <li>\n            <div class=\"checkbox checkbox-off\"></div>\n            <span class=\"to-do-children-unchecked\">This is a todo with style</span>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When single checked block is given', () => {\n      it('returns html with a div and checked checkbox and label inside', async () => {\n        const html = await makeSut(BlockMocks.CHECKED_TODO).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\">\n          <li>\n            <div class=\"checkbox checkbox-on\"></div>\n            <span class=\"to-do-children-checked\">This is a test</span>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When to-do block with two items is given', () => {\n      it('returns html with two divs and checkbox and label inside', async () => {\n        const html = await makeSut(BlockMocks.UNCHECKED_AND_CHECKED_TODOS).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\\\n        <ul class=\"to-do-list\">\n          <li>\n            <div class=\"checkbox checkbox-off\"></div>\n            <span class=\"to-do-children-unchecked\">This is a test</span>\\\n          </li>\\\n        </ul>\\\n        <ul class=\"to-do-list\">\n          <li>\n            <div class=\"checkbox checkbox-on\"></div>\n            <span class=\"to-do-children-checked\">This is a test too</span>\\\n          </li>\\\n        </ul>\\\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n  });\n\n  describe('When single code block is given', () => {\n    describe('When there is no style on code block', () => {\n      it('returns html with pre tag and code tag inside', async () => {\n        const html = await makeSut(BlockMocks.CODE).convert();\n\n        expect(html).toBe(\n          `<pre><code class=\"language-javascript\">function test() {\\n  var isTesting = true;\\n  return isTesting;\\n}</code></pre>`,\n        );\n      });\n    });\n\n    describe('When there is style on code block', () => {\n      it('ignores styles and returns html with pre tag and code tag inside', async () => {\n        const html = await makeSut(BlockMocks.CODE_WITH_DECORATION).convert();\n\n        expect(html).toBe(\n          `<pre><code class=\"language-javascript\">function test() {\\n  var isTesting = true;\\n  return isTesting;\\n}</code></pre>`,\n        );\n      });\n    });\n  });\n\n  describe('When single quote block is given', () => {\n    describe('When there is no style on quote block', () => {\n      it('returns html with blockquote tag', async () => {\n        const html = await makeSut(BlockMocks.QUOTE).convert();\n\n        expect(html).toBe('<blockquote>This a quote</blockquote>');\n      });\n    });\n\n    describe('When there is style on quote block', () => {\n      it('returns html with blockquote tag and decorations inside', async () => {\n        const html = await makeSut(BlockMocks.QUOTE_WITH_DECORATION).convert();\n\n        expect(html).toBe(\n          `<blockquote>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></blockquote>`,\n        );\n      });\n    });\n\n    describe('When there is background color on quote', () => {\n      it('returns html with style with background color prop', async () => {\n        const html = await makeSut(BlockMocks.QUOTE_WITH_FORMAT).convert();\n\n        expect(html).toBe('<blockquote style=\"background-color: #EAE4F2; \">This a quote with background</blockquote>');\n      });\n    });\n\n    describe('When there is background color on quote', () => {\n      it('returns html with style with background color prop', async () => {\n        const html = await makeSut(BlockMocks.QUOTE_WITH_FORMAT_FOREGROUND).convert();\n\n        expect(html).toBe('<blockquote style=\"color: #AD1A72; \">This a quote with color</blockquote>');\n      });\n    });\n  });\n\n  describe('When divider block is given', () => {\n    it('returns html with hr tag', async () => {\n      const html = await makeSut(BlockMocks.TEXT_BETWEEN_DIVIDER).convert();\n\n      expect(html).toBe(`<p>This a text</p>\\n<hr>\\n<p>This a text too</p>`);\n    });\n  });\n\n  describe('When equation block is given', () => {\n    describe('When there is no equation content', () => {\n      it('returns empty string', async () => {\n        const html = await makeSut(BlockMocks.EMPTY_EQUATION).convert();\n\n        expect(html).toBe('');\n      });\n    });\n\n    describe('When there is no equation content', () => {\n      it('returns html with div tag and equation class with equation inside', async () => {\n        const html = await makeSut(BlockMocks.EQUATION).convert();\n\n        expect(html).toBe(`<div class=\"equation\">$$\\\\int 2xdx = x^2 + C$$</div>`);\n      });\n    });\n  });\n\n  describe('When video block is given', () => {\n    describe('When it is not a youtube video', () => {\n      it('returns empty string', async () => {\n        const html = await makeSut(BlockMocks.NO_YOUTUBE_VIDEO).convert();\n\n        expect(html).toBe('');\n      });\n    });\n\n    describe('When it is a youtube video', () => {\n      it('returns html with iframe tag and embed id', async () => {\n        const html = await makeSut(BlockMocks.YOUTUBE_VIDEO).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<iframe\n          width=\"560\"\n          height=\"315\"\n          src=\"https://www.youtube.com/embed/xBFqxBfLJWc\"\n          frameborder=\"0\"\n          allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n          allowfullscreen\n        ></iframe>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n  });\n\n  describe('When image block is given', () => {\n    beforeEach(() => {\n      const imageSource =\n        'https://s3-us-west-2.amazonaws.com/secure.notion-static.com/bcedd078-56cd-4137-a28a-af16b5746874/767-50x50.jpg';\n      const blockId = 'ec3b36fd-f77d-46b4-8592-5966488612b1';\n\n      nock('https://www.notion.so')\n        .get(`/image/${encodeURIComponent(imageSource)}?table=block&id=${blockId}`)\n        .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n          'content-type': 'image/jpeg',\n        });\n    });\n\n    describe('When image has no caption', () => {\n      it('returns html with img tag with src as base64', async () => {\n        const html = await makeSut(BlockMocks.IMAGE).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\n        <figure class=\"image\">\n          <img src=\"${base64Img}\" alt=\"\" >\n        </figure>\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When image has caption', () => {\n      it('returns html with img tag with src as base64 and alt attr with given caption', async () => {\n        const html = await makeSut(BlockMocks.IMAGE_WITH_CAPTION).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\n        <figure class=\"image\">\n          <img src=\"${base64Img}\" alt=\"It is a caption\">\n          <figcaption>It is a caption</figcaption>\n        </figure>\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When image has width', () => {\n      it('returns html with img tag with width in style', async () => {\n        const html = await makeSut(BlockMocks.IMAGE_WITH_CUSTOM_SIZE).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `\n        <figure class=\"image\">\n          <img src=\"${base64Img}\" alt=\"\" style=\"width: 240px; \">\n        </figure>\n        `.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When detail block is given', () => {\n      describe('When there are no style on block', () => {\n        it('returns empty string', async () => {\n          const html = await makeSut(BlockMocks.DETAILS).convert();\n\n          expect(html.replace(/\\s/g, '')).toBe(\n            `\n            <details open=\"\">\n              <summary>This is a detail</summary>\n                <p>\n                  Hello World\n                </p>\n            </details>\n        `.replace(/\\s/g, ''),\n          );\n        });\n      });\n\n      describe('When there is style block', () => {\n        it('returns html with blockquote tag and decorations inside', async () => {\n          const html = await makeSut(BlockMocks.DETAILS_WITH_DECORATION).convert();\n\n          expect(html.replace(/\\s/g, '')).toBe(\n            `\n            <details open=\"\">\n              <summary>Hello <em><strong>World </strong></em><strong>and</strong><em><strong> Sun</strong></em></summary>\n                <p>\n                  Hello World\n                </p>\n            </details>\n        `.replace(/\\s/g, ''),\n          );\n        });\n      });\n\n      describe('When there is background color', () => {\n        it('returns html with background color for the intire block', async () => {\n          const html = await makeSut(BlockMocks.DETAILS_WITH_BG).convert();\n\n          expect(html.replace(/\\s/g, '')).toBe(\n            `\n            <details open=\"\" style=\"background-color: #FBE4E4; \">\n              <summary>This is a detail</summary>\n                <p>\n                  Hello World\n                </p>\n            </details>\n        `.replace(/\\s/g, ''),\n          );\n        });\n      });\n    });\n\n    describe('When image must have a table and block id attached to url', () => {\n      it('it should attach block id to it', async () => {\n        const base64ConverterSpy = jest.spyOn(Base64Converter, 'convert');\n        const source = BlockMocks.IMAGE_WITH_CAPTION[0].properties.source;\n        const id = BlockMocks.IMAGE_WITH_CAPTION[0].id;\n\n        await makeSut(BlockMocks.IMAGE_WITH_CAPTION).convert();\n\n        const expectedImageUrl = `https://www.notion.so/image/${encodeURIComponent(source)}?table=block&id=${id}`;\n        expect(base64ConverterSpy).toBeCalledWith(expectedImageUrl);\n      });\n    });\n  });\n\n  describe('When callout block is given', () => {\n    describe('with default background and emoji icon', () => {\n      it('converts to callout html', async () => {\n        const html = await makeSut(BlockMocks.CALLOUT).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<div class=\"callout\">\n          <div class=\"callout-emoji\">💡</div>\n          <p>This is a callout</p>\n        </div>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('with default background and image icon', () => {\n      beforeEach(() => {\n        const imageSource = 'https://example.com/image.png';\n        const blockId = '16431c64-3bf0-481f-a29f-d544780d84f3';\n\n        nock('https://www.notion.so')\n          .get(`/image/${encodeURIComponent(imageSource)}?table=block&id=${blockId}`)\n          .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n            'content-type': 'image/jpeg',\n          });\n      });\n\n      it('converts to callout html and image to base64', async () => {\n        const html = await makeSut(BlockMocks.CALLOUT_WITH_IMAGE).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<div class=\"callout\">\n          <div class=\"callout-image\"><img src=\"${base64Img}\" alt=\"callout icon\"></div>\n          <p>This is a callout</p>\n        </div>`.replace(/\\s/g, ''),\n        );\n      });\n\n      it('it should attach block id to it', async () => {\n        const base64ConverterSpy = jest.spyOn(Base64Converter, 'convert');\n        const blocks = BlockMocks.CALLOUT_WITH_IMAGE;\n        const source = blocks[0].properties.page_icon;\n        const id = blocks[0].id;\n\n        await makeSut(blocks).convert();\n\n        const expectedImageUrl = `https://www.notion.so/image/${encodeURIComponent(source)}?table=block&id=${id}`;\n        expect(base64ConverterSpy).toBeCalledWith(expectedImageUrl);\n      });\n    });\n\n    describe('with given background and emoji icon', () => {\n      it('converts to callout html', async () => {\n        const html = await makeSut(BlockMocks.CALLOUT_WITH_BACKGROUND).convert();\n\n        expect(html.replace(/\\s/g, '')).toBe(\n          `<div class=\"callout\" style=\"background-color: #FBE4E4; \">\n          <div class=\"callout-emoji\">💡</div>\n          <p>This is a callout</p>\n        </div>`.replace(/\\s/g, ''),\n        );\n      });\n    });\n  });\n\n  describe('When unknown block is given', () => {\n    it('returns empty string', async () => {\n      const html = await makeSut(BlockMocks.UNKNOWN).convert();\n\n      expect(html).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/blocks-to-html-converter.ts",
    "content": "import { ToHtml } from '../../../domain/use-cases/to-html';\nimport { Block } from '../../protocols/blocks';\nimport { BlockDispatcher } from './block-dispatcher';\nimport { ListBlocksWrapper } from './list-blocks-wrapper';\n\nexport class BlocksToHTML implements ToHtml {\n  private _blocks: Block[];\n  private _dispatcher: BlockDispatcher;\n  private _listBlocksWrapper: ListBlocksWrapper;\n\n  constructor(blocks: Block[], dispatcher: BlockDispatcher, listBlocksWrapper: ListBlocksWrapper) {\n    this._dispatcher = dispatcher;\n    this._listBlocksWrapper = listBlocksWrapper;\n    this._blocks = this._wrapLists(blocks);\n  }\n\n  async convert(): Promise<string> {\n    const htmlPromises: Promise<string[]> = Promise.all(this._blocks.map(this._convertBlock.bind(this)));\n    const html = (await htmlPromises).join('\\n');\n    return new Promise((resolve) => resolve(html));\n  }\n\n  private async _convertBlock(block: Block): Promise<string> {\n    const blockToHtmlConverter = this._dispatch(block);\n    const htmlBlock = await blockToHtmlConverter.convert();\n    return new Promise((resolve) => resolve(htmlBlock));\n  }\n\n  private _wrapLists(blocks: Block[]): Block[] {\n    return this._listBlocksWrapper.wrapLists(blocks);\n  }\n\n  private _dispatch(block: Block): ToHtml {\n    return this._dispatcher.dispatch(block);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/index.ts",
    "content": "export * from './blocks-to-html-converter';\nexport * from './block-dispatcher';\nexport * from './list-blocks-wrapper';\n"
  },
  {
    "path": "src/data/use-cases/blocks-to-html-converter/list-blocks-wrapper.ts",
    "content": "import { Block } from '../../protocols/blocks';\n\nexport class ListBlocksWrapper {\n  wrapLists(blocks: Block[]): Block[] {\n    return blocks.reduce((blocks, b) => {\n      if (!this._isList(b)) return [...blocks, b];\n\n      if (this._isFirstItemOfAList(blocks, b)) return [...blocks, this._generateListBlock(b)];\n\n      const lastContent = blocks[blocks.length - 1];\n      lastContent.children.push(b);\n      return blocks;\n    }, [] as Block[]);\n  }\n\n  private _isList(block: Block): boolean {\n    return block && block.type.includes('list');\n  }\n\n  private _isFirstItemOfAList(blocks: Block[], currentBlock: Block): boolean {\n    const lastContent = blocks[blocks.length - 1];\n\n    return (\n      (!this._isList(lastContent) || (lastContent && lastContent.children[0].type !== currentBlock.type)) &&\n      this._isList(currentBlock)\n    );\n  }\n\n  private _generateListBlock(childBlock: Block): Block {\n    return {\n      id: `${childBlock.id}-parent`,\n      type: 'list',\n      properties: childBlock.properties,\n      format: childBlock.format,\n      children: [childBlock],\n      decorableTexts: [],\n    };\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/format-to-style/format-to-style.ts",
    "content": "import { Format } from 'data/protocols/blocks/format';\nimport { foregroundColorToHex, backgroundColorToHex } from '../../helpers/color-to-hex';\n\nexport class FormatToStyle {\n  private readonly _format: Format;\n\n  constructor(format: Format) {\n    this._format = format;\n  }\n\n  toStyle(): string {\n    const styleProps = [];\n\n    const blockColor = this._format.block_color;\n    if (blockColor) styleProps.push(new BlockColorToProp(blockColor).toStyle());\n\n    const blockWidth = this._format.block_width;\n    if (blockWidth) styleProps.push(new BlockWidthToProp(blockWidth).toStyle());\n\n    if (styleProps.length === 0) return '';\n    return ` style=\"${styleProps.join('')}\"`;\n  }\n}\n\nclass BlockColorToProp {\n  private readonly _blockColor: string;\n\n  constructor(blockColor: string) {\n    this._blockColor = blockColor;\n  }\n\n  toStyle(): string {\n    if (this._isBackground()) return `background-color: ${backgroundColorToHex(this._blockColor)}; `;\n    return `color: ${foregroundColorToHex(this._blockColor)}; `;\n  }\n\n  private _isBackground(): boolean {\n    return !!this._blockColor?.includes('background');\n  }\n}\n\nclass BlockWidthToProp {\n  private readonly _blockWidth: number;\n\n  constructor(blockWidth: number) {\n    this._blockWidth = blockWidth;\n  }\n\n  toStyle(): string {\n    return `width: ${this._blockWidth}px; `;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/format-to-style/index.ts",
    "content": "export * from './format-to-style';\n"
  },
  {
    "path": "src/data/use-cases/html-wrapper/header-from-template.test.ts",
    "content": "import { HeaderFromTemplate } from './header-from-template';\nimport * as html from '../../../__tests__/mocks/html';\n\ndescribe('#toHeader', () => {\n  describe('when page has title only', () => {\n    it('returns html with header and h1', () => {\n      const pageProps = { title: 'This is a title' };\n\n      const result = new HeaderFromTemplate(pageProps).toHeader();\n\n      expect(result.replace(/\\s/g, '')).toEqual(html.HEADER_WITH_TITLE_ONLY.replace(/\\s/g, ''));\n    });\n  });\n\n  describe('when page has title and cover', () => {\n    describe('when coverImagePosition is given on pageProp', () => {\n      it('returns html with header and h1 and image position on image style', () => {\n        const pageProps = {\n          title: 'This is a title',\n          coverImageSrc: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD',\n          coverImagePosition: 15,\n        };\n\n        const result = new HeaderFromTemplate(pageProps).toHeader();\n\n        expect(result.replace(/\\s/g, '')).toEqual(html.HEADER_WITH_TITLE_AND_COVER_IMAGE.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('when coverImagePosition is not given on pageProp', () => {\n      it('returns html with header and h1 and image position as 0% on image style', () => {\n        const pageProps = {\n          title: 'This is a title',\n          coverImageSrc: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD',\n        };\n\n        const result = new HeaderFromTemplate(pageProps).toHeader();\n\n        expect(result.replace(/\\s/g, '')).toEqual(\n          html.HEADER_WITH_TITLE_AND_COVER_IMAGE_WITHOUT_POSITION.replace(/\\s/g, ''),\n        );\n      });\n    });\n  });\n\n  describe('when page has title cover and icon', () => {\n    describe('when icon is an image', () => {\n      it('returns html with header with h1, image cover and image icon with page-header-icon-with-cover class', () => {\n        const pageProps = {\n          title: 'This is a title',\n          coverImageSrc: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD',\n          icon: 'data:image/jpeg;base64,/4QDeRXhpZgAASUkqAAgAAAAGABIBAwA',\n        };\n\n        const result = new HeaderFromTemplate(pageProps).toHeader();\n\n        expect(result.replace(/\\s/g, '')).toEqual(html.HEADER_WITH_TITLE_COVER_IMAGE_AND_IMAGE_ICON.replace(/\\s/g, ''));\n      });\n    });\n  });\n\n  describe('when page has title and icon', () => {\n    describe('when icon is an image', () => {\n      it('returns html with header with h1 and image icon', () => {\n        const pageProps = {\n          title: 'This is a title',\n          icon: 'data:image/jpeg;base64,/4QDeRXhpZgAASUkqAAgAAAAGABIBAwA',\n        };\n\n        const result = new HeaderFromTemplate(pageProps).toHeader();\n\n        expect(result.replace(/\\s/g, '')).toEqual(html.HEADER_WITH_TITLE_AND_IMAGE_ICON.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('when icon is an emoji', () => {\n      it('retruns html with header with h1 and emoji in a div', () => {\n        const pageProps = {\n          title: 'This is a title',\n          icon: '🤴',\n        };\n\n        const result = new HeaderFromTemplate(pageProps).toHeader();\n\n        expect(result.replace(/\\s/g, '')).toEqual(html.HEADER_WITH_TITLE_AND_EMOJI_ICON.replace(/\\s/g, ''));\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/data/use-cases/html-wrapper/header-from-template.ts",
    "content": "import { PageProps } from '../../protocols/page-props/page-props';\n\nexport class HeaderFromTemplate {\n  private readonly _pageProps: PageProps;\n\n  constructor(pageProps: PageProps) {\n    this._pageProps = pageProps;\n  }\n\n  toHeader(): string {\n    return `\\\n<header>\n${this._coverImageHtml}\n${this._iconHtml}\n${this._titleHtml}\n</header>\\\n    `;\n  }\n\n  private get _coverImageHtml(): string {\n    const { coverImageSrc, coverImagePosition } = this._pageProps;\n\n    return coverImageSrc\n      ? `<img class=\"page-cover-image\" src=\"${coverImageSrc}\" style=\"object-position:center ${\n          coverImagePosition || 0\n        }%\">`\n      : '';\n  }\n\n  private get _iconHtml(): string {\n    const { coverImageSrc, icon } = this._pageProps;\n    if (!icon) return '';\n\n    const imageCoverSrcClassName = coverImageSrc ? 'page-header-icon-with-cover' : '';\n\n    if (!icon.startsWith('data:image/'))\n      return `<div class=\"page-header-icon ${imageCoverSrcClassName}\"><span class=\"icon\">${icon}</span></div>`;\n    return `<div class=\"page-header-icon ${imageCoverSrcClassName}\"><img class=\"icon\" src=\"${icon}\"></div>`;\n  }\n\n  private get _titleHtml(): string {\n    const { title } = this._pageProps;\n\n    return `<h1 class=\"page-title\">${title}</h1>`;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/html-wrapper/options-html-wrapper.ts",
    "content": "import { PageProps } from 'data/protocols/page-props';\nimport { HtmlWrapper } from '../../../domain/use-cases/html-wrapper';\nimport { HtmlOptions } from '../../protocols/html-options/html-options';\nimport { HeaderFromTemplate } from './header-from-template';\nimport { SCRIPTS } from './scripts';\nimport { STYLE } from './styles';\n\nexport class OptionsHtmlWrapper implements HtmlWrapper {\n  private readonly _options: HtmlOptions;\n\n  constructor(options: HtmlOptions) {\n    this._options = options;\n  }\n\n  wrapHtml(pageProps: PageProps, html: string): string {\n    if (this._options.bodyContentOnly) return html;\n\n    const title = pageProps.title;\n\n    return `\\\n<!DOCTYPE html>\n<html>\n${this._headFromTemplate(title)}\n<body>\n${!this._options.excludeHeaderFromBody ? new HeaderFromTemplate(pageProps).toHeader() : ''}\n${html}\n${!this._options.excludeScripts ? SCRIPTS : ''}\n</body>\n</html>`;\n  }\n\n  private _headFromTemplate(title: string): string {\n    return `\\\n<head>\n${!this._options.excludeMetadata ? '<meta charset=\"utf-8\">' : ''}\n${!this._options.excludeMetadata ? '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">' : ''}\n${!this._options.excludeCSS ? STYLE : ''}\n${!this._options.excludeTitleFromHead ? `<title>${title}</title>` : ''}\n${\n  !this._options.excludeScripts\n    ? '<link href=\"https://unpkg.com/prismjs@1.22.0/themes/prism.css\" rel=\"stylesheet\">'\n    : ''\n}\n</head>`;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/html-wrapper/scripts.ts",
    "content": "export const SCRIPTS = `\\\n<script src=\"https://unpkg.com/prismjs@1.22.0/components/prism-core.min.js\"></script>\n<script src=\"https://unpkg.com/prismjs@1.22.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n<script>\nMathJax = {\n  tex: {\n    inlineMath: [['$', '$']]\n  }\n};\n</script>\n<script id=\"MathJax-script\" async\n  src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js\">\n</script>\\\n`;\n"
  },
  {
    "path": "src/data/use-cases/html-wrapper/styles.ts",
    "content": "export const STYLE = `\\\n<style>\nhtml {\n  -webkit-print-color-adjust: exact;\n}\n\n* {\n  box-sizing: border-box;\n  -webkit-print-color-adjust: exact;\n}\n\nhtml,\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: system-ui, sans-serif;\n  color: #37352F;\n}\n\nbody {\n  line-height: 1.5;\n}\n\n@media only screen {\n  body {\n    margin: 2em auto;\n    max-width: 900px;\n    color: rgb(55, 53, 47);\n  }\n}\n\nimg {\n  max-width: 100%;\n  max-height: 70vh;\n}\n\nol,\nul {\n  margin: 0;\n  margin-block-start: 0.6em;\n  margin-block-end: 0.6em;\n}\n\nli > ol:first-child,\nli > ul:first-child {\n  margin-block-start: 0.6em;\n}\n\nul > li {\n  list-style: disc;\n}\n\nul.to-do-list {\n  text-indent: -1.7em;\n}\n\nul.to-do-list > li {\n  list-style: none;\n}\n\n.to-do-children-checked {\n  text-decoration: line-through;\n  opacity: 0.375;\n}\n\nul.toggle > li {\n  list-style: none;\n}\n\nul {\n  padding-inline-start: 1.7em;\n}\n\nul > li {\n  padding-left: 0.1em;\n}\n\nol {\n  padding-inline-start: 1.6em;\n}\n\nol > li {\n  padding-left: 0.2em;\n}\n\n.mono ol {\n  padding-inline-start: 2em;\n}\n\n.mono ol > li {\n  text-indent: -0.4em;\n}\n\n.toggle {\n  padding-inline-start: 0em;\n  list-style-type: none;\n}\n\n/* Indent toggle children */\n.toggle > li > details {\n  padding-left: 1.7em;\n}\n\n.toggle > li > details > summary {\n  margin-left: -1.1em;\n}\n\nh1,\nh2,\nh3 {\n  letter-spacing: -0.01em;\n  line-height: 1.2;\n  font-weight: 600;\n  margin-bottom: 0;\n}\n\n.page-title {\n  font-size: 2.5rem;\n  font-weight: 700;\n  margin-top: 0;\n  margin-bottom: 0.75em;\n}\n\n.icon {\n  display: inline-block;\n  max-width: 1.2em;\n  max-height: 1.2em;\n  text-decoration: none;\n  vertical-align: text-bottom;\n  margin-right: 0.5em;\n}\n\nimg.icon {\n  border-radius: 3px;\n}\n\n.page-cover-image {\n  display: block;\n  object-fit: cover;\n  width: 100%;\n  height: 30vh;\n}\n\n.page-header-icon {\n  font-size: 3rem;\n  margin-bottom: 1rem;\n}\n\n.page-header-icon-with-cover {\n  margin-top: -0.72em;\n  margin-left: 0.07em;\n}\n\n.page-header-icon img {\n  border-radius: 3px;\n}\n\nblockquote {\n  font-size: 1.25em;\n  margin: 1em 0;\n  padding-left: 1em;\n  border-left: 3px solid rgb(55, 53, 47);\n}  \n\nh1 {\n  font-size: 1.875rem;\n  margin-top: 1.875rem;\n}\n\nh2 {\n  font-size: 1.5rem;\n  margin-top: 1.5rem;\n}\n\nh3 {\n  font-size: 1.25rem;\n  margin-top: 1.25rem;\n}\n\n.callout {\n  padding: 16px 16px 16px 12px;\n  display: flex;\n  width: 100%;\n  border-radius: 3px;\n  border-width: 1px;\n  border-style: solid;\n  border-color: transparent;\n  align-items: center;\n  justify-content: center;\n}\n\n.callout-emoji {\n  height: 21.6px;\n  width: 21.6px;\n  font-size: 21.6px;\n  line-height: 1.1;\n  margin-left: 0px;\n}\n\n.callout p {\n  max-width: 100%;\n  width: 100%;\n  white-space: pre-wrap;\n  word-break: break-word;\n  margin-left: 10px;\n}\n\n.image {\n  border: none;\n  margin: 1.5em 0;\n  padding: 0;\n  border-radius: 0;\n  text-align: center;\n}\n\nfigure {\n  margin: 1.25em 0;\n  page-break-inside: avoid;\n}\n\nfigcaption {\n  opacity: 0.5;\n  font-size: 85%;\n  margin-top: 0.5em;\n}\n\nhr {\n  background: transparent;\n  display: block;\n  width: 100%;\n  height: 1px;\n  visibility: visible;\n  border: none;\n  border-bottom: 1px solid rgba(55, 53, 47, 0.09);\n}\n\n.checkbox {\n  display: inline-flex;\n  vertical-align: text-bottom;\n  width: 16px;\n  height: 16px;\n  background-size: 16px;\n  margin-left: 2px;\n  margin-right: 5px;\n}\n\n.checkbox-on {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%2358A9D7%22%2F%3E%0A%3Cpath%20d%3D%22M6.71429%2012.2852L14%204.9995L12.7143%203.71436L6.71429%209.71378L3.28571%206.2831L2%207.57092L6.71429%2012.2852Z%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E\");\n}\n\n.checkbox-off {\n  background-image: url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Crect%20x%3D%220.75%22%20y%3D%220.75%22%20width%3D%2214.5%22%20height%3D%2214.5%22%20fill%3D%22white%22%20stroke%3D%22%2336352F%22%20stroke-width%3D%221.5%22%2F%3E%0A%3C%2Fsvg%3E\");\n}\n</style>`;\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/index.ts",
    "content": "export * from './page-block-to-page-props';\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/page-block-to-cover-image-block.ts",
    "content": "import { Block } from '../../protocols/blocks';\nimport { Base64Converter } from '../../../utils/base-64-converter';\nimport { ImageCover } from '../../protocols/page-props';\n\nexport class PageBlockToCoverImageSource {\n  private readonly _pageBlock: Block;\n\n  constructor(pageBlock: Block) {\n    this._pageBlock = pageBlock;\n  }\n\n  async toImageCover(): Promise<ImageCover | null> {\n    const pageCover = this._pageBlock.properties.page_cover;\n    if (!pageCover || !this._isImageURL(pageCover)) return Promise.resolve(null);\n\n    let head = '';\n    if (pageCover.startsWith('/')) head = 'https://www.notion.so';\n\n    const base64 = await Base64Converter.convert(this.getImageAuthenticatedSrc(head + pageCover));\n    const position = this._pageCoverPositionToPositionCenter(this._pageBlock.format.page_cover_position || 0.6);\n\n    return { base64, position };\n  }\n\n  private _isImageURL(url: string): boolean {\n    return /(?:([^:\\/?#]+):)?(?:\\/\\/([^/?#]*))?([^?#]*\\.(?:jpg|gif|png|jpeg))(?:\\?([^#]*))?(?:#(.*))?/gi.test(url);\n  }\n\n  private getImageAuthenticatedSrc(src: string): string {\n    return `https://www.notion.so/image/${encodeURIComponent(src)}?table=block&id=${this._pageBlock.id}`;\n  }\n\n  private _pageCoverPositionToPositionCenter(coverPosition: number): number {\n    return (1 - coverPosition) * 100;\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/page-block-to-icon.ts",
    "content": "import { Block } from '../../protocols/blocks';\nimport { Base64Converter } from '../../../utils/base-64-converter';\n\nexport class PageBlockToIcon {\n  private readonly _pageBlock: Block;\n\n  constructor(pageBlock: Block) {\n    this._pageBlock = pageBlock;\n  }\n\n  async toIcon(): Promise<string | null> {\n    const icon = this._pageBlock.properties.page_icon;\n    if (!icon) return Promise.resolve(null);\n    if (!icon.startsWith('http')) return icon;\n\n    const url = `https://www.notion.so/image/${encodeURIComponent(icon)}?table=block&id=${this._pageBlock.id}`;\n    return Base64Converter.convert(url);\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/page-block-to-page-props.test.ts",
    "content": "import nock from 'nock';\nimport { resolve } from 'path';\nimport { PageBlockToPageProps } from './index';\nimport { Base64Converter } from '../../../utils/base-64-converter';\nimport * as Blocks from '../../../__tests__/mocks/blocks';\nimport base64Img from '../../../__tests__/mocks/img/base64';\n\ndescribe('#toPageProps', () => {\n  describe('when page was title only', () => {\n    it('returns page prop with title only and correct value', async () => {\n      const pageBlockToPageProps = new PageBlockToPageProps(Blocks.PAGE_WITH_TITLE);\n\n      const result = await pageBlockToPageProps.toPageProps();\n\n      expect(result).toEqual({ title: 'Simple Page Title' });\n    });\n  });\n\n  describe('when page was no title', () => {\n    it('returns page prop with title setted as an empty string', async () => {\n      const pageBlockToPageProps = new PageBlockToPageProps(Blocks.PAGE_WITHOUT_TITLE);\n\n      const result = await pageBlockToPageProps.toPageProps();\n\n      expect(result).toEqual({ title: '' });\n    });\n  });\n\n  describe('when page has title and cover image', () => {\n    describe('when image is from notion', () => {\n      it('returns base64 image in coverImageSrc prop', async () => {\n        nock('https://www.notion.so')\n          .get('/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fpage-cover%2Fsolid_blue.png')\n          .query({\n            table: 'block',\n            id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          })\n          .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n            'content-type': 'image/jpeg',\n          });\n        const pageBlockToPageProps = new PageBlockToPageProps(Blocks.PAGE_WITH_TITLE_AND_COVER_IMAGE[0]);\n\n        const result = await pageBlockToPageProps.toPageProps();\n\n        expect(result).toEqual({ title: 'Page Title', coverImageSrc: base64Img, coverImagePosition: 40 });\n      });\n    });\n\n    describe('when image is not from notion', () => {\n      it('returns base64 image in coverImageSrc prop', async () => {\n        nock('https://www.notion.so')\n          .get('/image/https%3A%2F%2Fwww.example.com%2Fsome_image.png')\n          .query({\n            table: 'block',\n            id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n          })\n          .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n            'content-type': 'image/jpeg',\n          });\n\n        const pageBlockToPageProps = new PageBlockToPageProps(\n          Blocks.PAGE_WITH_TITLE_AND_COVER_IMAGE_NOT_FROM_NOTION[0],\n        );\n\n        const result = await pageBlockToPageProps.toPageProps();\n\n        expect(result).toEqual({ title: 'Page Title', coverImageSrc: base64Img, coverImagePosition: 40 });\n      });\n    });\n\n    describe('when image url is not valid', () => {\n      it('returns base64 image in coverImageSrc prop', async () => {\n        const pageBlockToPageProps = new PageBlockToPageProps(Blocks.PAGE_WITH_TITLE_AND_INVALID_COVER_IMAGE[0]);\n\n        const result = await pageBlockToPageProps.toPageProps();\n\n        expect(result).toEqual({ title: 'Page Title' });\n      });\n    });\n  });\n\n  describe('when page has title and icon', () => {\n    describe('when icon is an utf-8 emoji', () => {\n      it('returns emoji in page prop', async () => {\n        const pageBlockToPageProps = new PageBlockToPageProps(Blocks.PAGE_WITH_TITLE_AND_EMOJI_ICON[0]);\n\n        const result = await pageBlockToPageProps.toPageProps();\n\n        expect(result).toEqual({ title: 'Page Title', icon: '🤴' });\n      });\n    });\n\n    describe('when icon is an image url', () => {\n      const block = Blocks.PAGE_WITH_TITLE_AND_IMAGE_ICON[0];\n      const imageSource = block.properties.page_icon;\n\n      beforeEach(() => {\n        nock('https://www.notion.so')\n          .get(`/image/${encodeURIComponent(imageSource)}?table=block&id=${block.id}`)\n          .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n            'content-type': 'image/jpeg',\n          });\n      });\n\n      it('returns image as base64 in page prop', async () => {\n        const pageBlockToPageProps = new PageBlockToPageProps(block);\n\n        const result = await pageBlockToPageProps.toPageProps();\n\n        expect(result).toEqual({ title: 'Page Title', icon: base64Img });\n      });\n\n      it('attaches block id to image url on base64 convertion', async () => {\n        const base64ConverterSpy = jest.spyOn(Base64Converter, 'convert');\n        const pageBlockToPageProps = new PageBlockToPageProps(block);\n\n        await pageBlockToPageProps.toPageProps();\n\n        const expectedImageUrl = `https://www.notion.so/image/${encodeURIComponent(imageSource)}?table=block&id=${\n          block.id\n        }`;\n\n        expect(base64ConverterSpy).toBeCalledWith(expectedImageUrl);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/page-block-to-page-props.ts",
    "content": "import { Block } from '../../protocols/blocks';\nimport { PageProps } from '../../protocols/page-props';\nimport { PageBlockToTitle } from './page-block-to-title';\nimport { PageBlockToCoverImageSource } from './page-block-to-cover-image-block';\nimport { PageBlockToIcon } from './page-block-to-icon';\n\nexport class PageBlockToPageProps {\n  private readonly _pageBlock: Block;\n\n  constructor(pageBlock: Block) {\n    this._pageBlock = pageBlock;\n  }\n\n  async toPageProps(): Promise<PageProps> {\n    const title = new PageBlockToTitle(this._pageBlock).toTitle();\n    const coverImage = await new PageBlockToCoverImageSource(this._pageBlock).toImageCover();\n    const icon = await new PageBlockToIcon(this._pageBlock).toIcon();\n\n    return Promise.resolve({\n      title,\n      ...(coverImage && { coverImageSrc: coverImage.base64, coverImagePosition: coverImage.position }),\n      ...(icon && { icon }),\n    });\n  }\n}\n"
  },
  {
    "path": "src/data/use-cases/page-block-to-page-props/page-block-to-title.ts",
    "content": "import { Block } from '../../protocols/blocks';\n\nexport class PageBlockToTitle {\n  private readonly _pageBlock: Block;\n\n  constructor(pageBlock: Block) {\n    this._pageBlock = pageBlock;\n  }\n\n  toTitle(): string {\n    return this._pageBlock.decorableTexts[0]?.text || '';\n  }\n}\n"
  },
  {
    "path": "src/domain/use-cases/html-wrapper.ts",
    "content": "import { PageProps } from '../../data/protocols/page-props';\n\nexport interface HtmlWrapper {\n  wrapHtml(pageProps: PageProps, html: string): string;\n}\n"
  },
  {
    "path": "src/domain/use-cases/to-html.ts",
    "content": "export interface ToHtml {\n  convert(): Promise<string>;\n}\n\nexport interface ToHtmlClass {\n  new (...args: any): ToHtml;\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { NotionPageToHtml } from './main/use-cases/notion-api-to-html';\n\nexport default NotionPageToHtml;\nmodule.exports = NotionPageToHtml;\n"
  },
  {
    "path": "src/infra/errors/index.ts",
    "content": "export * from './missing-content';\nexport * from './missing-page-id';\nexport * from './notion-page-access';\nexport * from './invalid-page-url';\nexport * from './notion-page-not-found';\n"
  },
  {
    "path": "src/infra/errors/invalid-page-url.ts",
    "content": "export class InvalidPageUrlError extends Error {\n  constructor(url: string) {\n    super(`Url \"${url}\" is not a valid notion page.`);\n    this.name = 'InvalidPageUrlError';\n  }\n}\n"
  },
  {
    "path": "src/infra/errors/missing-content.ts",
    "content": "export class MissingContentError extends Error {\n  constructor(pageId: string) {\n    super(`Can not find content on page ${pageId}. Is it empty?`);\n    this.name = 'MissingContentError';\n  }\n}\n"
  },
  {
    "path": "src/infra/errors/missing-page-id.ts",
    "content": "export class MissingPageIdError extends Error {\n  constructor() {\n    super('PageId is Missing');\n    this.name = 'MissingPageIdError';\n  }\n}\n"
  },
  {
    "path": "src/infra/errors/notion-page-access.ts",
    "content": "export class NotionPageAccessError extends Error {\n  constructor(pageId: string) {\n    super(`Can not read Notion Page of id ${pageId}. Is it open for public reading?`);\n    this.name = 'NotionPageAccessError';\n  }\n}\n"
  },
  {
    "path": "src/infra/errors/notion-page-not-found.ts",
    "content": "export class NotionPageNotFound extends Error {\n  constructor(pageId: string) {\n    super(\n      `Can not find Notion Page of id ${pageId}. Is the url correct? It is the original page or a redirect page (not supported)?`,\n    );\n    this.name = 'NotionPageNotFound';\n  }\n}\n"
  },
  {
    "path": "src/infra/protocols/notion-api-content-response.ts",
    "content": "export type NotionApiContentResponse = {\n  id: string;\n  type: string;\n  properties: Record<string, any>;\n  format?: Record<string, any>;\n  contents: NotionApiContentResponse[];\n};\n"
  },
  {
    "path": "src/infra/protocols/validation.ts",
    "content": "export interface Validation<Args extends Array<unknown> = []> {\n  validate(...args: Args): Error | null;\n}\n"
  },
  {
    "path": "src/infra/use-cases/http-post/node-http-post-client.ts",
    "content": "import { HttpPostClient, HttpResponse } from '../../../data/protocols/http-request';\nimport https, { RequestOptions } from 'https';\nimport { URL } from 'url';\n\nexport class NodeHttpPostClient implements HttpPostClient {\n  async post(url: string, body: Record<string, any>): Promise<HttpResponse> {\n    const urlHandler = new URL(url);\n    const stringifiedBody = JSON.stringify(body);\n\n    const options: RequestOptions = {\n      hostname: urlHandler.hostname,\n      path: urlHandler.pathname,\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'Content-Length': stringifiedBody.length,\n      },\n    };\n\n    let status = 504;\n\n    const requestAsPromised: Promise<HttpResponse> = new Promise((resolve, reject) => {\n      const req = https\n        .request(options, (res) => {\n          status = res.statusCode || 504;\n\n          const chunks = new Array<Uint8Array>();\n\n          res.on('data', (chunk) => {\n            chunks.push(chunk);\n          });\n\n          res.on('end', () => {\n            const result = Buffer.concat(chunks).toString('utf8');\n            resolve({ status, data: JSON.parse(result) });\n          });\n        })\n        .on('error', (err) => reject(err.message));\n\n      req.write(stringifiedBody);\n      req.end();\n    });\n\n    return requestAsPromised;\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/decoration-array-to-decorations.ts",
    "content": "import { Decoration, DecorationType } from '../../../data/protocols/blocks';\n\nexport class DecorationArrayToDecorations {\n  private readonly _decorationsArray: Array<any>;\n\n  constructor(decorationsArray: Array<any>) {\n    this._decorationsArray = decorationsArray;\n  }\n\n  toDecorations(): Decoration[] {\n    if (!this._decorationsArray) return [] as Decoration[];\n\n    return this._decorationsArray.map((decorations) => {\n      const [type, value] = decorations;\n\n      return {\n        type: fromDecorationArrayTypeToDecorationType[type] || 'plain',\n        ...(value && { value }),\n      };\n    });\n  }\n}\n\nconst fromDecorationArrayTypeToDecorationType: Record<string, DecorationType> = {\n  b: 'bold',\n  i: 'italic',\n  _: 'underline',\n  s: 'strikethrough',\n  c: 'code',\n  a: 'link',\n  e: 'equation',\n  h: 'color',\n};\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/format-filter.ts",
    "content": "export class FormatFilter {\n  private readonly _format: Record<string, any>;\n\n  constructor(format: Record<string, any> | undefined) {\n    this._format = format || {};\n  }\n\n  filter(): Record<string, any> {\n    const presentAcceptableKeys = Object.keys(this._format).filter((k) => ACCEPTABLE_KEYS.includes(k));\n    return presentAcceptableKeys.reduce<Record<string, any>>((filteredFormat, key) => {\n      return {\n        ...filteredFormat,\n        [key]: this._format[key],\n      };\n    }, {} as Record<string, any>);\n  }\n}\n\nconst ACCEPTABLE_KEYS: string[] = ['block_color', 'page_cover_position', 'block_width'];\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/notion-api-content-response-to-blocks.test.ts",
    "content": "import * as NotionApiMocks from '../../../__tests__/mocks/notion-api-responses';\nimport * as BlockMocks from '../../../__tests__/mocks/blocks';\nimport { NotionApiContentResponsesToBlocks } from './notion-api-content-response-to-blocks';\n\ndescribe('#toBlocks', () => {\n  describe('when page with title and single text content is given', () => {\n    it('converts to one single text block with given content', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_AND_TITLE_NOTION_API_CONTENT_RESPONSE;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT);\n    });\n  });\n\n  describe('when page with text content with bold content is given', () => {\n    it('converts to one block with two decorations', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_BOLD;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_BOLD);\n    });\n  });\n\n  describe('when page with text content with bold and italic content is given', () => {\n    describe('when they are together', () => {\n      it('converts to one block with two decorations', () => {\n        const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_BOLD_AND_ITALIC_TOGETHER;\n        const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n        const result = notionApiContentResponsesToBlocks.toBlocks();\n\n        expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_BOLD_AND_ITALIC);\n      });\n    });\n\n    describe('when they are not together', () => {\n      it('converts to one block with two decorations', () => {\n        const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_BOLD_AND_ITALIC;\n        const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n        const result = notionApiContentResponsesToBlocks.toBlocks();\n\n        expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_BOLD_AND_ITALIC_SEPARATED);\n      });\n    });\n  });\n\n  describe('when page with color text content is given', () => {\n    it('converts to one block with decoration with value', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_COLOR;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_COLOR);\n    });\n  });\n\n  describe('when page with equation text content is given', () => {\n    it('converts to one block with decoration with value', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_EQUATION;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_EQUATION_DECORATION);\n    });\n  });\n\n  describe('when page with link text content is given', () => {\n    it('converts to one block with decoration with value', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_LINK;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_LINK);\n    });\n  });\n\n  describe('when page with format is given', () => {\n    it('passes format prop to block', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_TEXT_WITH_FORMAT;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.SINGLE_TEXT_WITH_FORMAT);\n    });\n  });\n\n  describe('when page with custom image size is given', () => {\n    it('passes block_width to format', () => {\n      const notionApiContentResponses = NotionApiMocks.IMAGE_WITH_CUSTOM_SIZE;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.IMAGE_WITH_CUSTOM_SIZE);\n    });\n  });\n\n  describe('when page with page_icon in format is given', () => {\n    it('passes format prop to properties', () => {\n      const notionApiContentResponses = NotionApiMocks.CALLOUT_WITH_PAGE_ICON;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.CALLOUT);\n    });\n  });\n\n  describe('when page with youtube link', () => {\n    it('converts to one block with decoration with value', () => {\n      const notionApiContentResponses = NotionApiMocks.VIDEO_NOTION_API_CONTENT_RESPONSE;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.PAGE_WITH_YOUTUBE_VIDEO);\n    });\n  });\n\n  describe('when page with page cover and page cover position is given', () => {\n    it('converts to page block with page_cover and page_conver_position in format prop', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_PAGE_WITH_COVER_IMAGE;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.PAGE_WITH_TITLE_AND_COVER_IMAGE);\n    });\n  });\n\n  describe('when page with page icon is given', () => {\n    it('converts to page block with page_icon in format prop', () => {\n      const notionApiContentResponses = NotionApiMocks.SINGLE_PAGE_WITH_ICON;\n      const notionApiContentResponsesToBlocks = new NotionApiContentResponsesToBlocks(notionApiContentResponses);\n\n      const result = notionApiContentResponsesToBlocks.toBlocks();\n\n      expect(result).toEqual(BlockMocks.PAGE_WITH_TITLE_AND_ICON);\n    });\n  });\n});\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/notion-api-content-response-to-blocks.ts",
    "content": "import { Block } from '../../../data/protocols/blocks';\nimport { NotionApiContentResponse } from '../../protocols/notion-api-content-response';\nimport { PropTitleToDecorableTexts } from '../to-blocks/prop-title-to-decorable-texts';\nimport { FormatFilter } from './format-filter';\nimport { PropertiesParser } from './properties-parser';\n\nexport class NotionApiContentResponsesToBlocks {\n  private readonly _notionApiContentResponses: NotionApiContentResponse[];\n\n  constructor(notionApiContentResponses: NotionApiContentResponse[]) {\n    this._notionApiContentResponses = notionApiContentResponses;\n  }\n\n  toBlocks(): Block[] {\n    if (!this._notionApiContentResponses) return [];\n\n    return this._notionApiContentResponses.map((nacr) => ({\n      id: nacr.id,\n      type: nacr.type,\n      format: new FormatFilter(nacr.format).filter(),\n      properties: new PropertiesParser(nacr.format, nacr.properties).parse(),\n      children: new NotionApiContentResponsesToBlocks(nacr.contents).toBlocks(),\n      decorableTexts: new PropTitleToDecorableTexts(nacr.properties?.title).toDecorableTexts(),\n    }));\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/prop-title-to-decorable-texts.ts",
    "content": "import { DecorableText } from '../../../data/protocols/blocks';\nimport { DecorationArrayToDecorations } from './decoration-array-to-decorations';\n\nexport class PropTitleToDecorableTexts {\n  private readonly _title: any[] | undefined;\n\n  constructor(title: any[] | undefined) {\n    this._title = title;\n  }\n\n  toDecorableTexts(): DecorableText[] {\n    if (!this._title) return [] as DecorableText[];\n\n    return this._title.map((richText: any[]) => {\n      const text = richText[0].toString();\n      const decorationsArray = richText[1];\n\n      return {\n        text,\n        decorations: new DecorationArrayToDecorations(decorationsArray).toDecorations(),\n      };\n    });\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-blocks/properties-parser.ts",
    "content": "export class PropertiesParser {\n  private readonly _format: Record<string, any>;\n  private readonly _properties: Record<string, any>;\n\n  constructor(format: Record<string, any> | undefined, properties: Record<string, any> | undefined) {\n    this._format = format || {};\n    this._properties = properties || {};\n  }\n\n  parse(): Record<string, any> {\n    const avaliableKeys = Object.keys({ ...this._format, ...this._properties }).filter((k) =>\n      KEYS_TO_PRESERVE.includes(k),\n    );\n\n    return avaliableKeys.reduce<Record<string, any>>(\n      (format, key) => ({\n        ...format,\n        [key]: this._properties[key]?.[0]?.[0] || this._format[key],\n      }),\n      {},\n    );\n  }\n}\n\nconst KEYS_TO_PRESERVE = ['source', 'caption', 'language', 'checked', 'page_icon', 'page_cover'];\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/notion-api-page-fetcher.test.ts",
    "content": "import nock from 'nock';\nimport { NotionApiPageFetcher } from './notion-api-page-fetcher';\nimport { NotionPageIdValidator, PageRecordValidator, PageChunkValidator } from './services';\nimport { NodeHttpPostClient } from '../http-post/node-http-post-client';\nimport { MissingContentError, MissingPageIdError, NotionPageAccessError } from '../../errors';\nimport * as NotionApiMocks from '../../../__tests__/mocks/notion-api-responses';\n\ndescribe('#getNotionPageContents', () => {\n  afterEach(() => {\n    nock.cleanAll();\n  });\n\n  const makeSut = (notionPageId: string): NotionApiPageFetcher => {\n    const httpPostClient = new NodeHttpPostClient();\n    const notionPageIdValidator = new NotionPageIdValidator();\n    const pageRecordValidator = new PageRecordValidator();\n    const pageChunkValidator = new PageChunkValidator();\n\n    return new NotionApiPageFetcher(\n      notionPageId,\n      httpPostClient,\n      notionPageIdValidator,\n      pageRecordValidator,\n      pageChunkValidator,\n    );\n  };\n\n  describe('when notion page id is valid and page is public', () => {\n    it('returns NotionApiContentResponse object with page content when page is valid', async () => {\n      nock('https://www.notion.so').post('/api/v3/loadPageChunk').reply(200, NotionApiMocks.SUCCESSFUL_PAGE_CHUCK);\n      nock('https://www.notion.so').post('/api/v3/getRecordValues').reply(200, NotionApiMocks.SUCCESSFUL_RECORDS);\n      const notionPageId = '4d64bbc0-634d-4758-befa-85c5a3a6c22f';\n      const apiInterface = makeSut(notionPageId);\n\n      const response = await apiInterface.getNotionPageContents();\n\n      expect(response).toEqual(NotionApiMocks.TEXT_NOTION_API_CONTENT_RESPONSE);\n    });\n\n    it('passes its children when it is available', async () => {\n      nock('https://www.notion.so')\n        .post('/api/v3/loadPageChunk')\n        .reply(200, NotionApiMocks.SUCCESSFUL_PAGE_CHUCK_WITH_CHILDREN);\n      nock('https://www.notion.so')\n        .post('/api/v3/getRecordValues')\n        .reply(200, NotionApiMocks.SUCCESSFUL_RECORDS_WITH_CHILDREN);\n\n      const notionPageId = '4d64bbc0-634d-4758-befa-85c5a3a6c22f';\n      const apiInterface = makeSut(notionPageId);\n\n      const response = await apiInterface.getNotionPageContents();\n\n      expect(response).toEqual(NotionApiMocks.LIST_WITH_CHILDREN_RESPONSE);\n    });\n\n    describe('when children is not available on page chunk but it is available by request', () => {\n      it('get out block from new request and passes in content', async () => {\n        nock('https://www.notion.so')\n          .post('/api/v3/loadPageChunk')\n          .reply(200, NotionApiMocks.SUCCESSFUL_PAGE_CHUCK_WITH_CHILDREN_NOT_IN_CHUNK);\n        nock('https://www.notion.so')\n          .post('/api/v3/syncRecordValues')\n          .reply(200, NotionApiMocks.SUCCESSFUL_SYNC_RECORD_VALUE);\n        nock('https://www.notion.so')\n          .post('/api/v3/getRecordValues')\n          .reply(200, NotionApiMocks.SUCCESSFUL_RECORDS_WITH_CHILDREN);\n\n        const notionPageId = '4d64bbc0-634d-4758-befa-85c5a3a6c22f';\n        const apiInterface = makeSut(notionPageId);\n\n        const response = await apiInterface.getNotionPageContents();\n\n        expect(response).toEqual(NotionApiMocks.DETAILS_RESPONSE);\n      });\n    });\n  });\n\n  describe('when notion page id is missing', () => {\n    it('throws MissingPageIdError', async () => {\n      const response = () => makeSut('').getNotionPageContents();\n\n      await expect(response).toThrow(new MissingPageIdError());\n    });\n  });\n\n  describe('when notion page is not open for public reading', () => {\n    it('throws NotionPageAccessError', async () => {\n      nock('https://www.notion.so').post('/api/v3/getRecordValues').reply(200, NotionApiMocks.NO_PAGE_ACCESS_RECORDS);\n\n      const notionPageId = 'b02b33d9-95cd-44cb-8e7f-01f1870c1ee8';\n      const apiInterface = makeSut(notionPageId);\n\n      const response = () => apiInterface.getNotionPageContents();\n\n      await expect(response).rejects.toThrowError(new NotionPageAccessError(notionPageId));\n    });\n  });\n\n  describe('when notion page is empty', () => {\n    it('throws MissingContentError', async () => {\n      nock('https://www.notion.so').post('/api/v3/getRecordValues').reply(200, NotionApiMocks.MISSING_CONTENT_RECORDS);\n\n      const notionPageId = '9a75a541-277f-4a64-80e7-5581f36672ba';\n      const apiInterface = makeSut(notionPageId);\n\n      const response = () => apiInterface.getNotionPageContents();\n\n      await expect(response).rejects.toThrow(new MissingContentError(notionPageId));\n    });\n  });\n});\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/notion-api-page-fetcher.ts",
    "content": "import { HttpPostClient, HttpResponse } from '../../../data/protocols/http-request';\nimport { NotionApiContentResponse } from '../../protocols/notion-api-content-response';\nimport { NotionPageIdValidator, PageRecordValidator, PageChunkValidator } from './services';\n\nconst NOTION_API_PATH = 'https://www.notion.so/api/v3/';\n\nexport class NotionApiPageFetcher {\n  constructor(\n    private readonly notionPageId: string,\n    private readonly httpPostClient: HttpPostClient,\n    private readonly notionPageIdValidator: NotionPageIdValidator,\n    private readonly pageRecordValidator: PageRecordValidator,\n    private readonly pageChunkValidator: PageChunkValidator,\n  ) {\n    const pageIdError = this.notionPageIdValidator.validate(this.notionPageId);\n    if (pageIdError) throw pageIdError;\n  }\n\n  async getNotionPageContents(): Promise<NotionApiContentResponse[]> {\n    const pageRecords = await this.fetchRecordValues();\n    const pageRecordError = this.pageRecordValidator.validate(this.notionPageId, pageRecords);\n    if (pageRecordError) throw pageRecordError;\n\n    const chunk = await this.fetchPageChunk();\n    const chunkError = await this.pageChunkValidator.validate(this.notionPageId, chunk.status);\n    if (chunkError) throw chunkError;\n\n    const pageData = pageRecords.data as Record<string, any>;\n    const chunkData = chunk.data as Record<string, any>;\n\n    const contentIds = [pageData.results[0].value.id];\n    return this.mapContentsIdToContent(contentIds, chunkData, pageData);\n  }\n\n  private async mapContentsIdToContent(\n    contentIds: string[],\n    chunkData: Record<string, any>,\n    pageData: Record<string, any>,\n  ): Promise<NotionApiContentResponse[]> {\n    const contentsNotInChunk = await this.contentsNotInChunk(contentIds, chunkData, pageData);\n    const contentsInChunk = await this.contentsInChunk(contentIds, chunkData, pageData);\n    const unorderedContents = contentsInChunk.concat(contentsNotInChunk).filter((c) => !!contentIds.includes(c.id));\n\n    return unorderedContents.sort((a, b) => contentIds.indexOf(a.id) - contentIds.indexOf(b.id));\n  }\n\n  private async contentsNotInChunk(\n    contentIds: string[],\n    chunkData: Record<string, any>,\n    pageData: Record<string, any>,\n  ): Promise<NotionApiContentResponse[]> {\n    const contentsIdsNotInChunk = contentIds.filter((id: string) => !chunkData.recordMap?.block[id]);\n    const contentsNotInChunkRecords = await this.fetchRecordValuesByContentIds(contentsIdsNotInChunk);\n    const dataNotInChunk = contentsIdsNotInChunk\n      .map((id) => {\n        const data = contentsNotInChunkRecords.data as Record<string, any>;\n        return data.recordMap?.block[id].value;\n      })\n      .filter((d) => !!d);\n\n    return Promise.all(\n      dataNotInChunk.map(async (c: Record<string, any>) => {\n        const format = c.format;\n\n        return {\n          id: c.id,\n          type: c.type,\n          properties: c.properties,\n          ...(format && { format }),\n          contents: await this.mapContentsIdToContent(c.content || [], chunkData, pageData),\n        };\n      }),\n    );\n  }\n\n  private async contentsInChunk(\n    contentIds: string[],\n    chunkData: Record<string, any>,\n    pageData: Record<string, any>,\n  ): Promise<NotionApiContentResponse[]> {\n    const dataInChunk = contentIds\n      .filter((id: string) => !!chunkData.recordMap?.block[id])\n      .map((id: string) => chunkData.recordMap?.block[id].value);\n\n    return Promise.all(\n      dataInChunk.map(async (c: Record<string, any>) => {\n        const format = c.format;\n\n        return {\n          id: c.id,\n          type: c.type,\n          properties: c.properties,\n          ...(format && { format }),\n          contents: await this.mapContentsIdToContent(c.content || [], chunkData, pageData),\n        };\n      }),\n    );\n  }\n\n  private async fetchRecordValues(): Promise<HttpResponse> {\n    return this.httpPostClient.post(NOTION_API_PATH + 'getRecordValues', {\n      requests: [\n        {\n          id: this.notionPageId,\n          table: 'block',\n        },\n      ],\n    });\n  }\n\n  private fetchPageChunk(): Promise<HttpResponse> {\n    return this.httpPostClient.post(NOTION_API_PATH + 'loadPageChunk', {\n      pageId: this.notionPageId,\n      limit: 999999,\n      cursor: {\n        stack: [],\n      },\n      chunkNumber: 0,\n      verticalColumns: false,\n    });\n  }\n\n  private fetchRecordValuesByContentIds(contentIds: string[]): Promise<HttpResponse> {\n    if (contentIds.length === 0)\n      return Promise.resolve({\n        status: 200,\n        data: {},\n      });\n\n    return this.httpPostClient.post(NOTION_API_PATH + 'syncRecordValues', {\n      requests: contentIds.map((id) => ({\n        id,\n        table: 'block',\n        version: -1,\n      })),\n    });\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/services/index.ts",
    "content": "export * from './page-record-validation.service';\nexport * from './notion-page-id-validation.service';\nexport * from './page-chunk-validation.service';\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/services/notion-page-id-validation.service.ts",
    "content": "import { Validation } from '../../../protocols/validation';\nimport { MissingPageIdError } from '../../../errors';\n\nexport class NotionPageIdValidator implements Validation<[string]> {\n  validate(notionPageId: string): Error | null {\n    if (!notionPageId || notionPageId == '') return new MissingPageIdError();\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/services/page-chunk-validation.service.test.ts",
    "content": "import { PageChunkValidator } from './index';\n\ndescribe('PageChunkValidator', () => {\n  const makeSut = () => new PageChunkValidator();\n\n  let sut: PageChunkValidator;\n  beforeEach(() => {\n    sut = makeSut();\n  });\n\n  it('should not return an error if status is 200', () => {\n    const error = sut.validate('any_id', 200);\n\n    expect(error).toBeNull();\n  });\n\n  it('should return NotionPageAccessError error if status is 401', () => {\n    const error = sut.validate('any_id', 401);\n\n    expect(error?.name).toBe('NotionPageAccessError');\n  });\n\n  it('should return NotionPageAccessError error if status is 403', () => {\n    const error = sut.validate('any_id', 403);\n\n    expect(error?.name).toBe('NotionPageAccessError');\n  });\n\n  it('should return NotionPageNotFound error if status is 404', () => {\n    const error = sut.validate('any_id', 404);\n\n    expect(error?.name).toBe('NotionPageNotFound');\n  });\n});\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/services/page-chunk-validation.service.ts",
    "content": "import { NotionPageAccessError, NotionPageNotFound } from '../../../../infra/errors';\nimport { Validation } from '../../../protocols/validation';\n\nexport class PageChunkValidator implements Validation<[string, number]> {\n  validate(notionPageId: string, pageChunkStatus: number): Error | null {\n    if ([401, 403].includes(pageChunkStatus)) {\n      return new NotionPageAccessError(notionPageId);\n    }\n\n    if (pageChunkStatus === 404) {\n      return new NotionPageNotFound(notionPageId);\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-notion-api-content-responses/services/page-record-validation.service.ts",
    "content": "import { Validation } from '../../../protocols/validation';\n\nimport { NotionPageAccessError, MissingContentError } from '../../../errors';\nimport { HttpResponse } from 'data/protocols/http-request';\n\nexport class PageRecordValidator implements Validation<[string, HttpResponse]> {\n  validate(notionPageId: string, pageRecord: HttpResponse): Error | null {\n    const data = pageRecord.data as Record<string, any>;\n\n    if (pageRecord.status === 401 || !data.results?.[0]?.value) {\n      return new NotionPageAccessError(notionPageId);\n    }\n\n    if (!data.results[0]?.value?.content) {\n      return new MissingContentError(notionPageId);\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/index.ts",
    "content": "export * from './notion-url-to-page-id';\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/notion-url-to-page-id.test.ts",
    "content": "import { NotionUrlToPageId } from './index';\nimport { InvalidPageUrlError } from '../../errors';\nimport { UrlValidator, IdNormalizer } from './services';\n\ndescribe('#toPageId', () => {\n  const makeSut = (url: string): NotionUrlToPageId => {\n    const idNormalizer = new IdNormalizer();\n    const urlValidator = new UrlValidator();\n    return new NotionUrlToPageId(url, idNormalizer, urlValidator);\n  };\n\n  describe('when invalid url is given', () => {\n    describe('when it is from another domain', () => {\n      it('throws InvalidPageUrlError', () => {\n        const url = 'https://example.com/notion_page_id';\n\n        const result = () => makeSut(url).toPageId();\n\n        expect(result).toThrow(new InvalidPageUrlError(url));\n      });\n    });\n\n    describe('when it is from the same domain, but not a page path', () => {\n      it('throws InvalidPageUrlError', () => {\n        const url = 'https://www.notion.so/onboarding';\n\n        const result = () => makeSut(url).toPageId();\n\n        expect(result).toThrow(new InvalidPageUrlError(url));\n      });\n    });\n  });\n\n  describe('when valid url is given', () => {\n    describe('when it has full page url with unnormalized page id', () => {\n      it('returns normalized page id', () => {\n        const url = 'https://www.notion.so/asnunes/Simple-Page-Text-4d64bbc0634d4758befa85c5a3a6c22f';\n\n        const result = makeSut(url).toPageId();\n\n        expect(result).toBe('4d64bbc0-634d-4758-befa-85c5a3a6c22f');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/notion-url-to-page-id.ts",
    "content": "import { IdNormalizer, UrlValidator } from './services';\n\nexport class NotionUrlToPageId {\n  constructor(\n    private readonly url: string,\n    private readonly idNormalizer: IdNormalizer,\n    private readonly urlValidator: UrlValidator,\n  ) {}\n\n  toPageId(): string {\n    const urlError = this.urlValidator.validate(this.url);\n    if (urlError) throw urlError;\n\n    return this.idNormalizer.normalizeId(this.ununormalizedPageId);\n  }\n\n  private get ununormalizedPageId(): string {\n    const tail = this.url.split('/').reverse()[0];\n    if (tail.split('-').length === 0) return tail;\n\n    return tail.split('-').reverse()[0];\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/services/id-normalizer.ts",
    "content": "export class IdNormalizer {\n  normalizeId(id: string): string {\n    const isItAlreadyNormalized = id.length === 36;\n    return isItAlreadyNormalized\n      ? id\n      : `${id.substr(0, 8)}-${id.substr(8, 4)}-${id.substr(12, 4)}-${id.substr(16, 4)}-${id.substr(20)}`;\n  }\n}\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/services/index.ts",
    "content": "export * from './id-normalizer';\nexport * from './url-validator';\n"
  },
  {
    "path": "src/infra/use-cases/to-page-id/services/url-validator.ts",
    "content": "import { Validation } from '../../../protocols/validation';\nimport { InvalidPageUrlError } from '../../../errors';\n\nexport class UrlValidator implements Validation<[string]> {\n  validate(url: string): Error | null {\n    if (!this.isNotionPargeUrl(url)) return new InvalidPageUrlError(url);\n    return null;\n  }\n\n  private isNotionPargeUrl(url: string): boolean {\n    return /^http(s?):\\/\\/((w{3}.)?notion.so|[\\w\\-]*\\.notion\\.site)\\/((\\w)+?\\/)?(\\w|-){32,}/g.test(url);\n  }\n}\n"
  },
  {
    "path": "src/main/factories/blocks-to-html.factory.ts",
    "content": "import { Block } from '../../data/protocols/blocks';\nimport { BlocksToHTML, BlockDispatcher, ListBlocksWrapper } from '../../data/use-cases/blocks-to-html-converter';\n\nexport const makeBlocksToHtml = (blocks: Block[]): BlocksToHTML => {\n  const dispatcher = new BlockDispatcher();\n  const listBlocksWrapper = new ListBlocksWrapper();\n  return new BlocksToHTML(blocks, dispatcher, listBlocksWrapper);\n};\n"
  },
  {
    "path": "src/main/factories/index.ts",
    "content": "export * from './notion-url-to-page-id.factory';\nexport * from './notion-api-page-fetcher.factory';\nexport * from './blocks-to-html.factory';\n"
  },
  {
    "path": "src/main/factories/notion-api-page-fetcher.factory.ts",
    "content": "import { NotionApiPageFetcher } from '../../infra/use-cases/to-notion-api-content-responses/notion-api-page-fetcher';\nimport { NodeHttpPostClient } from '../../infra/use-cases/http-post/node-http-post-client';\nimport {\n  NotionPageIdValidator,\n  PageChunkValidator,\n  PageRecordValidator,\n} from '../../infra/use-cases/to-notion-api-content-responses/services';\n\nexport const createNotionApiPageFetcher = async (pageId: string): Promise<NotionApiPageFetcher> => {\n  const httpPostClient = new NodeHttpPostClient();\n\n  const notionPageIdValidator = new NotionPageIdValidator();\n  const pageRecordValidator = new PageRecordValidator();\n  const pageChunkValidator = new PageChunkValidator();\n\n  return new NotionApiPageFetcher(\n    pageId,\n    httpPostClient,\n    notionPageIdValidator,\n    pageRecordValidator,\n    pageChunkValidator,\n  );\n};\n"
  },
  {
    "path": "src/main/factories/notion-url-to-page-id.factory.ts",
    "content": "import { NotionUrlToPageId } from '../../infra/use-cases/to-page-id';\nimport { IdNormalizer, UrlValidator } from '../../infra/use-cases/to-page-id/services';\n\nexport const createNotionUrlToPageId = (url: string): NotionUrlToPageId => {\n  const idNormalizer = new IdNormalizer();\n  const urlValidator = new UrlValidator();\n  return new NotionUrlToPageId(url, idNormalizer, urlValidator);\n};\n"
  },
  {
    "path": "src/main/protocols/notion-page.ts",
    "content": "export type NotionPage = {\n  html: string;\n  title?: string;\n  icon?: string;\n  cover?: string;\n};\n"
  },
  {
    "path": "src/main/use-cases/notion-api-to-html/index.ts",
    "content": "export * from './notion-page-to-html';\nexport * from '../../protocols/notion-page';\n"
  },
  {
    "path": "src/main/use-cases/notion-api-to-html/notion-page-to-html.test.ts",
    "content": "import nock from 'nock';\nimport { resolve } from 'path';\nimport { NotionPageToHtml } from './index';\nimport { InvalidPageUrlError } from '../../../infra/errors';\nimport * as NotionApiMocks from '../../../__tests__/mocks/notion-api-responses';\nimport * as HTML_RESPONSES from '../../../__tests__/mocks/html';\nimport base64 from '../../../__tests__/mocks/img/base64';\n\ndescribe('#convert', () => {\n  describe('When page is valid', () => {\n    const pageId = '4d64bbc0634d4758befa85c5a3a6c22f';\n\n    beforeEach(() => {\n      nock('https://www.notion.so')\n        .post('/api/v3/loadPageChunk', (body) => body.pageId.replace(/-/g, '') === pageId)\n        .reply(200, NotionApiMocks.SUCCESSFUL_PAGE_CHUCK);\n\n      nock('https://www.notion.so').post('/api/v3/getRecordValues').reply(200, NotionApiMocks.SUCCESSFUL_RECORDS);\n\n      nock('https://www.notion.so')\n        .get('/image/https%3A%2F%2Fwww.example.com%2Fimage.png')\n        .query({\n          table: 'block',\n          id: '4d64bbc0-634d-4758-befa-85c5a3a6c22f',\n        })\n        .replyWithFile(200, resolve('src/__tests__/mocks/img/baseImage.jpeg'), {\n          'content-type': 'image/jpeg',\n        });\n    });\n\n    describe('When no options is given', () => {\n      it('returns full html when full url is given', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url);\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.FULL_DOCUMENT.replace(/\\s/g, ''));\n      });\n\n      it('returns full html when short url is given', async () => {\n        const url = `https://www.notion.so/${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url);\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.FULL_DOCUMENT.replace(/\\s/g, ''));\n      });\n\n      it('returns page title in title prop', async () => {\n        const url = `https://www.notion.so/${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url);\n\n        expect(response.title).toEqual('Simple Page Test');\n      });\n\n      it('returns page cover in cover prop', async () => {\n        const url = `https://www.notion.so/${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url);\n\n        expect(response.cover).toEqual(base64);\n      });\n\n      it('returns page icon in icon prop', async () => {\n        const url = `https://www.notion.so/${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url);\n\n        expect(response.icon).toEqual('🤴');\n      });\n    });\n\n    describe('When excludeTitleFromHead is given', () => {\n      it('returns without title', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          excludeTitleFromHead: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.DOCUMENT_WITHOUT_TITLE.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('When excludeCSS is given', () => {\n      it('returns without style tag', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          excludeCSS: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.DOCUMENT_WITHOUT_CSS.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('When excludeMetadata is given', () => {\n      it('returns without metatags', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          excludeMetadata: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.DOCUMENT_METADATA.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('When excludeScripts is given', () => {\n      it('returns without script tags', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          excludeScripts: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.DOCUMENT_WITHOUT_SCRIPTS.replace(/\\s/g, ''));\n      });\n    });\n\n    describe('When excludeHeaderFromBody is given', () => {\n      it('returns body content only without header', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          excludeHeaderFromBody: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(\n          HTML_RESPONSES.FULL_DOCUMENT_WITHOUT_HEADER_IN_BODY.replace(/\\s/g, ''),\n        );\n      });\n    });\n\n    describe('When bodyContentOnly is given', () => {\n      it('returns body content only', async () => {\n        const url = `https://www.notion.so/asnunes/Simple-Page-Text-${pageId}`;\n\n        const response = await NotionPageToHtml.convert(url, {\n          bodyContentOnly: true,\n        });\n\n        expect(response.html.replace(/\\s/g, '')).toEqual(HTML_RESPONSES.BODY_ONLY.replace(/\\s/g, ''));\n      });\n    });\n  });\n\n  describe('When wrong link is given', () => {\n    it('throws invalid page url error', async () => {\n      nock('https://www.notion.so').post('/api/v3/loadPageChunk').reply(200, NotionApiMocks.SUCCESSFUL_PAGE_CHUCK);\n\n      nock('https://www.notion.so').post('/api/v3/getRecordValues').reply(200, NotionApiMocks.SUCCESSFUL_RECORDS);\n\n      const response = () =>\n        NotionPageToHtml.convert('https://www.example.com/asnunes/Simple-Page-Text-4d64bbc0634d4758befa85c5a3a6c22f');\n\n      await expect(response).rejects.toThrow(\n        new InvalidPageUrlError('https://www.example.com/asnunes/Simple-Page-Text-4d64bbc0634d4758befa85c5a3a6c22f'),\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "src/main/use-cases/notion-api-to-html/notion-page-to-html.ts",
    "content": "import { PageBlockToPageProps } from '../../../data/use-cases/page-block-to-page-props';\nimport { HtmlOptions } from '../../../data/protocols/html-options/html-options';\nimport { OptionsHtmlWrapper } from '../../../data/use-cases/html-wrapper/options-html-wrapper';\nimport { NotionApiContentResponsesToBlocks } from '../../../infra/use-cases/to-blocks/notion-api-content-response-to-blocks';\nimport { createNotionUrlToPageId, createNotionApiPageFetcher, makeBlocksToHtml } from '../../factories';\nimport { NotionPage } from '../../protocols/notion-page';\n\n/**\n * @class NotionPageToHtml\n * @description This class converts a Notion page to HTML using the convert method.\n */\nexport class NotionPageToHtml {\n  /**\n   * @description It converts a Notion page to HTML. Page must be public before it can be converted.\n   * It can be made private again after the conversion.\n   * @param pageURL The URL of the page to convert. Can be notion.so or notion.site URL.\n   * @param htmlOptions Options to customize the HTML output. It is an object with the following properties:\n   * @param htmlOptions.excludeCSS If true, it will return html without style tag. It is false by default.\n   * @param htmlOptions.excludeMetadata If true, it will return html without metatags. It is false by default.\n   * @param htmlOptions.excludeScripts If true, it will return html without scripts. It is false by default.\n   * @param htmlOptions.excludeHeaderFromBody If true, it will return  html without title, cover and icon inside body. It is false by default.\n   * @param htmlOptions.excludeTitleFromHead If true, it will return html without title tag in head. It is false by default.\n   * @param htmlOptions.bodyContentOnly If true, it will return html body tag content only. It is false by default.\n   *\n   * @returns The converted Page. It is an object with the following properties:\n   * - title: The title of the page.\n   * - icon: The icon of the page. Can be an emoji or a base64 encoded image string.\n   * - cover: The cover image of the page. It is a base64 encoded image string.\n   * - html: The raw HTML string of the page.\n   * @throws If the page is not public, it will throw an error.\n   * @throws If the page is not found, it will throw an error.\n   * @throws If the url is invalid, it will throw an error.\n   */\n  static async convert(pageURL: string, htmlOptions: HtmlOptions = {}): Promise<NotionPage> {\n    const pageId = createNotionUrlToPageId(pageURL).toPageId();\n    const fetcher = await createNotionApiPageFetcher(pageId);\n    const notionApiResponses = await fetcher.getNotionPageContents();\n    const blocks = new NotionApiContentResponsesToBlocks(notionApiResponses).toBlocks();\n\n    if (blocks.length === 0) return Promise.resolve({ html: '' });\n\n    const htmlBody = await makeBlocksToHtml(blocks).convert();\n    const pageProps = await new PageBlockToPageProps(blocks[0]).toPageProps();\n\n    return {\n      title: pageProps.title,\n      ...(pageProps.icon && { icon: pageProps.icon }),\n      ...(pageProps.coverImageSrc && { cover: pageProps.coverImageSrc }),\n      html: new OptionsHtmlWrapper(htmlOptions).wrapHtml(pageProps, htmlBody),\n    };\n  }\n}\n"
  },
  {
    "path": "src/utils/base-64-converter.ts",
    "content": "import { NodeHttpGetClient } from './use-cases/http-get/node-http-get';\n\nexport class Base64Converter {\n  private readonly _imageSource: string;\n\n  constructor(imageURL: string) {\n    this._imageSource = imageURL;\n  }\n\n  static async convert(imageURL: string): Promise<string> {\n    return Promise.resolve(new Base64Converter(imageURL)._convert());\n  }\n\n  async _convert(): Promise<string> {\n    const response = await new NodeHttpGetClient().get(this._imageSource);\n    return Promise.resolve(response.data.toString());\n  }\n}\n"
  },
  {
    "path": "src/utils/either.ts",
    "content": "export type Either<S, F> = Success<S, F> | Failure<S, F>;\n\nexport class Success<S, F> {\n  constructor(readonly value: S) {}\n\n  isSuccess(): this is Success<S, F> {\n    return true;\n  }\n\n  isFailure(): this is Failure<S, F> {\n    return false;\n  }\n}\n\nexport class Failure<S, F> {\n  constructor(readonly value: F) {}\n\n  isSuccess(): this is Success<S, F> {\n    return false;\n  }\n\n  isFailure(): this is Failure<S, F> {\n    return true;\n  }\n}\n\nexport function sendSuccess<S, F>(value: S): Either<S, F> {\n  return new Success<S, F>(value);\n}\n\nexport function sendFailure<S, F>(value: F): Either<S, F> {\n  return new Failure<S, F>(value);\n}\n"
  },
  {
    "path": "src/utils/errors/forbidden-error.ts",
    "content": "export class ForbiddenError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'ForbiddenError';\n  }\n}\n"
  },
  {
    "path": "src/utils/errors/image-not-found-error.ts",
    "content": "export class ImageNotFoundError extends Error {\n  constructor(path: string) {\n    super(`Image on path ${path} could not be found`);\n    this.name = 'ImageNotFoundError';\n  }\n}\n"
  },
  {
    "path": "src/utils/errors/index.ts",
    "content": "export * from './forbidden-error';\nexport * from './image-not-found-error';\n"
  },
  {
    "path": "src/utils/use-cases/http-get/node-http-get.ts",
    "content": "import { HttpGetClient, HttpResponse } from '../../../data/protocols/http-request';\nimport https from 'https';\nimport { ForbiddenError } from '../../errors';\n\nexport class NodeHttpGetClient implements HttpGetClient {\n  async get(url: string): Promise<HttpResponse> {\n    const requestAsPromised: Promise<HttpResponse> = new Promise((resolve, reject) => {\n      let stringData = '';\n      https\n        .get(url, (res) => {\n          res.setEncoding('base64');\n\n          res.on('data', (chunk) => {\n            stringData += chunk;\n          });\n\n          res.on('end', () => {\n            const format = res.headers['content-type'] || 'image/jpeg';\n\n            if (res.statusCode === 403) throw new ForbiddenError('could not fetch data from url: ' + url);\n\n            if (format.includes('image')) {\n              return resolve({\n                status: res.statusCode || 200,\n                headers: res.headers as Record<string, any>,\n                data: `data:${format};base64,${stringData}`,\n              });\n            }\n\n            return resolve({\n              status: res.statusCode || 200,\n              headers: res.headers as Record<string, any>,\n              data: JSON.parse(stringData),\n            });\n          });\n        })\n        .on('error', (err) => reject(err.message));\n    });\n\n    return requestAsPromised;\n  }\n}\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"**/*.d.ts\", \"**/*.spec.ts\", \"**/*.test.ts\", \"**/__tests__/**/*\"]\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"src\",\n    \"outDir\": \"dist\",\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"target\": \"es5\",\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"strict\": true,\n    \"rootDirs\": [\"src\",\n      \"src/__tests__\"\n    ],\n    \"allowJs\": true,\n    \"lib\": [\"es2019\"]\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"**/__tests__/*\"]\n}\n"
  }
]