Repository: contentful/rich-text Branch: master Commit: 1ebea53a99e6 Files: 160 Total size: 520.0 KB Directory structure: gitextract_d499cr83/ ├── .circleci/ │ └── config.yml ├── .contentful/ │ ├── compressed-size.yml │ └── vault-secrets.yaml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── dependabot.yml │ ├── semantic.yml │ └── workflows/ │ ├── auto-approve.yml │ └── codeql.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .swcrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── baseJestConfig.js ├── catalog-info.yaml ├── commitlint.config.js ├── lerna.json ├── nx.json ├── package.json ├── packages/ │ ├── contentful-slatejs-adapter/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.ts │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ ├── contentful-helpers.ts │ │ │ │ └── contentful-to-slatejs-adapter.test.ts │ │ │ ├── contentful-to-slatejs-adapter.ts │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ ├── schema.ts │ │ │ ├── slatejs-to-contentful-adapter.ts │ │ │ └── types/ │ │ │ ├── index.ts │ │ │ └── slate.ts │ │ └── tsconfig.json │ ├── rich-text-from-markdown/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── real-world.md │ │ │ │ └── real-world.test.ts │ │ │ ├── index.ts │ │ │ └── types/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── rich-text-html-renderer/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ ├── documents/ │ │ │ │ │ ├── embedded-entry.ts │ │ │ │ │ ├── embedded-resource.ts │ │ │ │ │ ├── heading.ts │ │ │ │ │ ├── hr.ts │ │ │ │ │ ├── hyperlink.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── inline-entity.ts │ │ │ │ │ ├── invalid-marks.ts │ │ │ │ │ ├── invalid-type.ts │ │ │ │ │ ├── mark.ts │ │ │ │ │ ├── ol.ts │ │ │ │ │ ├── paragraph.ts │ │ │ │ │ ├── quote.ts │ │ │ │ │ ├── table-header.ts │ │ │ │ │ ├── table.ts │ │ │ │ │ └── ul.ts │ │ │ │ └── index.test.ts │ │ │ ├── escapeHtml.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── rich-text-links/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ └── index.test.ts │ │ │ ├── index.ts │ │ │ └── types/ │ │ │ └── utils.ts │ │ └── tsconfig.json │ ├── rich-text-plain-text-renderer/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── bin/ │ │ │ ├── benchmark/ │ │ │ │ └── get-rich-text-entity-links.ts │ │ │ └── tsconfig.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ └── index.test.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── rich-text-react-renderer/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __test__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── components/ │ │ │ │ │ ├── Document.tsx │ │ │ │ │ ├── Paragraph.tsx │ │ │ │ │ └── Strong.tsx │ │ │ │ ├── documents/ │ │ │ │ │ ├── embedded-entry.ts │ │ │ │ │ ├── embedded-resource.ts │ │ │ │ │ ├── heading.ts │ │ │ │ │ ├── hr.ts │ │ │ │ │ ├── hyperlink.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── inline-entity.ts │ │ │ │ │ ├── invalid-marks.ts │ │ │ │ │ ├── invalid-type.ts │ │ │ │ │ ├── mark.ts │ │ │ │ │ ├── multi-mark.ts │ │ │ │ │ ├── ol.ts │ │ │ │ │ ├── paragraph.ts │ │ │ │ │ ├── quote.ts │ │ │ │ │ ├── table-header.ts │ │ │ │ │ ├── table.ts │ │ │ │ │ └── ul.ts │ │ │ │ └── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── util/ │ │ │ ├── appendKeyToValidElement.ts │ │ │ └── nodeListToReactComponents.tsx │ │ └── tsconfig.json │ └── rich-text-types/ │ ├── CHANGELOG.md │ ├── README.md │ ├── __mocks__/ │ │ └── @lingui/ │ │ └── core/ │ │ └── macro.js │ ├── __test__/ │ │ ├── helpers.test.ts │ │ ├── schemaConstraints.test.ts │ │ └── validation.test.ts │ ├── jest.config.js │ ├── package.json │ ├── scripts/ │ │ └── fix-esm-import-extensions.mjs │ ├── src/ │ │ ├── blocks.ts │ │ ├── emptyDocument.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── inlines.ts │ │ ├── marks.ts │ │ ├── nodeTypes.ts │ │ ├── schemaConstraints.ts │ │ ├── types.ts │ │ └── validator/ │ │ ├── assert.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── node.ts │ │ ├── path.ts │ │ ├── text.ts │ │ └── types.ts │ └── tsconfig.json ├── pnpm-workspace.yaml ├── renovate.json ├── rollup.config.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2.1 orbs: frontend-tools: contentful/frontend-tools@2 vault: contentful/vault@1.30.0 nx: nrwl/nx@1.6.2 executors: default: docker: - image: cimg/node:24.13.1 cache-key: &cache-key key: v2-npm-cache-{{ arch }}-{{ checksum ".nvmrc" }}-{{ checksum "pnpm-lock.yaml" }}-{{ .Branch }} commands: setup_pnpm: steps: - run: name: Install pnpm package manager command: | sudo corepack enable corepack prepare pnpm --activate pnpm config set store-dir .pnpm-store pnpm_install: steps: - restore_cache: *cache-key - run: node -v - run: pnpm -v - run: pnpm install --frozen-lockfile --prefer-offline - save_cache: <<: *cache-key paths: - .pnpm-store nx_set_shas: steps: - vault/get-secrets: template-preset: nx-read-circleci - nx/set-shas: error-on-no-successful-workflow: true main-branch-name: master jobs: lint-and-test: executor: default steps: - checkout - vault/get-secrets: template-preset: packages-read - setup_pnpm - pnpm_install - nx_set_shas - run: pnpm exec nx affected --target=build --base=$NX_BASE --head=$NX_HEAD - run: pnpm exec nx affected --target=lint --base=$NX_BASE --head=$NX_HEAD - run: pnpm exec nx affected --target=prettier:check --base=$NX_BASE --head=$NX_HEAD - run: pnpm exec nx affected --target=test --base=$NX_BASE --head=$NX_HEAD - store_test_results: path: reports - persist_to_workspace: root: ~/project paths: - . resource_class: medium+ release: executor: default steps: - checkout - vault/get-secrets: template-preset: semantic-release - attach_workspace: at: ~/project - vault/configure-lerna - run: | echo "@contentful:registry=https://npm.pkg.github.com" >> ~/.npmrc echo "//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_WRITE_TOKEN}" >> ~/.npmrc - run: pnpm nx run-many -t build - run: pnpm lerna version --no-private --conventional-commits --create-release github --yes - run: pnpm lerna publish from-git --yes compressed-size: executor: default steps: - checkout - vault/get-secrets: template-preset: packages-read - vault/get-secrets: template-preset: github-comment - run: | echo "@contentful:registry=https://npm.pkg.github.com" >> ~/.npmrc echo "//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_READ_TOKEN}" >> ~/.npmrc - frontend-tools/compressed-size: package-manager: pnpm workflows: version: 2 # run on every commit commit: jobs: - lint-and-test: context: - vault - release: context: - vault filters: branches: only: master requires: - lint-and-test compressed-size: jobs: - compressed-size: context: - vault ================================================ FILE: .contentful/compressed-size.yml ================================================ version: 1 compressed-size: compression: 'gzip' pattern: './packages/*/{dist,build}/**/*.{js,css}' strip-hash: "\\b\\w{8}\\." collapse-unchanged: true minimum-change-threshold: 1024 ================================================ FILE: .contentful/vault-secrets.yaml ================================================ version: 1 services: github-action: policies: - dependabot circleci: policies: - github-comment - semantic-release - packages-read - nx-read-circleci ================================================ FILE: .editorconfig ================================================ #root = true [*] indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 100 indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintignore ================================================ node_modules/ .coverage/ .cache/ dist/ .eslintrc.js ================================================ FILE: .eslintrc.js ================================================ module.exports = { env: { browser: true, es2021: true, }, extends: [ 'eslint:recommended', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:@typescript-eslint/recommended', ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 'latest', sourceType: 'module', }, plugins: ['react', 'react-hooks', '@typescript-eslint', 'eslint-plugin-import-helpers'], rules: { 'react-hooks/exhaustive-deps': 'error', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 'import-helpers/order-imports': [ 'warn', { newlinesBetween: 'always', groups: ['/^react/', 'module', ['parent', 'sibling', 'index']], alphabetize: { order: 'asc', ignoreCase: true }, }, ], 'no-restricted-imports': ['warn'], 'react/react-in-jsx-scope': 'off', }, settings: { react: { version: 'detect' }, }, }; ================================================ FILE: .github/dependabot.yml ================================================ version: 2 registries: npm-registry-registry-npmjs-org: type: npm-registry url: https://registry.npmjs.org token: '${{secrets.NPM_REGISTRY_REGISTRY_NPMJS_ORG_TOKEN}}' npm-github: type: npm-registry url: https://npm.pkg.github.com token: ${{secrets.NPM_REGISTRY_REGISTRY_GH_ORG_TOKEN}} updates: - package-ecosystem: npm directory: '/' schedule: interval: weekly time: '00:00' timezone: Etc/UCT open-pull-requests-limit: 10 target-branch: master labels: - dependencies - dependabot commit-message: prefix: chore registries: - npm-registry-registry-npmjs-org - npm-github reviewers: - 'contentful/team-tolkien' cooldown: default-days: 15 ================================================ FILE: .github/semantic.yml ================================================ titleAndCommits: true anyCommit: true allowMergeCommits: false types: - feat - fix - improvement - docs - style - refactor - perf - test - build - ci - chore - revert ================================================ FILE: .github/workflows/auto-approve.yml ================================================ name: 'dependabot approve-and-request-merge' on: pull_request_target jobs: worker: permissions: contents: write id-token: write pull-requests: write runs-on: ubuntu-latest if: github.actor == 'dependabot[bot]' steps: - uses: contentful/github-auto-merge@v2 with: VAULT_URL: ${{ secrets.VAULT_URL }} ================================================ FILE: .github/workflows/codeql.yml ================================================ --- name: "CodeQL Scan for GitHub Actions Workflow" on: push: branches: [master] paths: [".github/workflows/**"] pull_request: branches: [master] paths: [".github/workflows/**"] jobs: analyze: name: Analyze GitHub Actions workflows runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: actions - name: Run CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: actions ================================================ FILE: .gitignore ================================================ # Logs **/logs **/*.log **/npm-debug.log* **/yarn-debug.log* **/yarn-error.log* # Coverage **/coverage # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) **/build/Release # Dependency directories **/node_modules/ **/jspm_packages/ # TypeScript v1 declaration files **/typings/ # Optional npm cache directory **/.npm # Optional eslint cache **/.eslintcache # Optional REPL history **/.node_repl_history # Output of 'npm pack' **/*.tgz # dotenv environment variables file **/.env **/dist **/.rpt2_cache .vscode/settings.json .idea # test reports reports # nx specific .nx/ # swc **/.swc ================================================ FILE: .husky/pre-commit ================================================ npx lint-staged ================================================ FILE: .npmrc ================================================ registry=https://registry.npmjs.org ignore-scripts=true shamefully-hoist=true ================================================ FILE: .nvmrc ================================================ v24 ================================================ FILE: .prettierignore ================================================ **/node_modules **/dist **/coverage ================================================ FILE: .swcrc ================================================ { "$schema": "https://swc.rs/schema.json", "jsc": { "parser": { "syntax": "typescript", "tsx": true }, "target": "es5", "loose": false, "minify": { "compress": false, "mangle": false } }, "module": { "type": "commonjs" }, "minify": false } ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) ### Bug Fixes - don't run npm install on prePublish ([#417](https://github.com/contentful/rich-text/issues/417)) ([0af2378](https://github.com/contentful/rich-text/commit/0af237897ce093e294833e7e17c3b69e4634e09a)) ### Features - adding v1 marks to rich text types [TOL-786] ([#416](https://github.com/contentful/rich-text/issues/416)) ([8885de8](https://github.com/contentful/rich-text/commit/8885de8ebba7736de46b0b8892a8aa15bb8d7ba4)) ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add --no-package-lock ([#364](https://github.com/contentful/rich-text/issues/364)) ([41643c9](https://github.com/contentful/rich-text/commit/41643c9f61f525536941b54b64e944b70cb4a241)) - add inline sources to packages ([#357](https://github.com/contentful/rich-text/issues/357)) ([bca6abb](https://github.com/contentful/rich-text/commit/bca6abb4328414b5a7a87b9b57481e161d800dcf)) - add missing -- to circleci release commands ([#360](https://github.com/contentful/rich-text/issues/360)) ([233be08](https://github.com/contentful/rich-text/commit/233be0826cc7c76b799e70262b3281f7dbe4cd92)) - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - circleci update resource class ([#359](https://github.com/contentful/rich-text/issues/359)) ([6bb3710](https://github.com/contentful/rich-text/commit/6bb3710623907a2601c218f3cea3a817790cbf59)) - revert change to monorepo tsconfig, apply to tsconfig in rich-text-types ([#358](https://github.com/contentful/rich-text/issues/358)) ([56126cc](https://github.com/contentful/rich-text/commit/56126cc9ed3704f21b89d2dbded160be0265f153)) - update Lerna, rollup in slatejs-adapter ([#366](https://github.com/contentful/rich-text/issues/366)) ([32448e3](https://github.com/contentful/rich-text/commit/32448e369ae2b76601dd81839e13c15432430d68)) ## [15.13.1](https://github.com/contentful/rich-text/compare/v15.13.0...v15.13.1) (2022-05-10) ### Bug Fixes - **markdown:** handle empty table cells ([#329](https://github.com/contentful/rich-text/issues/329)) ([55a0a16](https://github.com/contentful/rich-text/commit/55a0a16a12700fefbe7e9727a7172043fd126fc5)) # [15.13.0](https://github.com/contentful/rich-text/compare/v15.12.1...v15.13.0) (2022-05-06) ### Features - support converting tables from markdown to rich-text ([0abc0c6](https://github.com/contentful/rich-text/commit/0abc0c60b7e3e2683ebbb427b44847e6242f6e5e)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) ### Bug Fixes - upgrade react peerDependencies to support react^18.0.0 ([#323](https://github.com/contentful/rich-text/issues/323)) ([7a0bfdf](https://github.com/contentful/rich-text/commit/7a0bfdfb687cca608239e072cbc301ba1b1310d1)) # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) ### Features - enforce minItems in table-related node types ([#314](https://github.com/contentful/rich-text/issues/314)) ([1125331](https://github.com/contentful/rich-text/commit/112533100f66ae01cd9944069dc62fc95f1737a5)) ## [15.11.2](https://github.com/contentful/rich-text/compare/v15.11.1...v15.11.2) (2022-02-07) ### Bug Fixes - set "typings" for rich-text-from-markdown ([1b3a15f](https://github.com/contentful/rich-text/commit/1b3a15f1cb15eacb6d1b15f2b79c5747e2d25618)) ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) ### Bug Fixes - **rich-text-types:** remove RT validation helpers ([#302](https://github.com/contentful/rich-text/issues/302)) ([fcd3a27](https://github.com/contentful/rich-text/commit/fcd3a277952f53eb3ae6ebb559ae6a02f5553c87)), closes [#295](https://github.com/contentful/rich-text/issues/295) [#274](https://github.com/contentful/rich-text/issues/274) # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) ### Bug Fixes - **react-renderer:** wrap table rows in tbody ([#300](https://github.com/contentful/rich-text/issues/300)) ([e23d1f4](https://github.com/contentful/rich-text/commit/e23d1f4f5c63ce7ded84144b271f566327a144d1)) ### Features - **rich-text-types:** expose HEADINGS array ([#301](https://github.com/contentful/rich-text/issues/301)) ([758539d](https://github.com/contentful/rich-text/commit/758539d46f3db13c21ca2f6d74a389a6fef21803)) ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) ### Bug Fixes - remove support to convert tables from md to rich text ([a9d513c](https://github.com/contentful/rich-text/commit/a9d513c285e8cdedd384b89e195734dd3a8d2136)) # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) ### Features - **rich-text-types:** expose RT validation helper ([#292](https://github.com/contentful/rich-text/issues/292)) ([fc5a7cc](https://github.com/contentful/rich-text/commit/fc5a7cc27244f293a9a50acd785f7edcdaaa96ea)) # [15.8.0](https://github.com/contentful/rich-text/compare/v15.7.0...v15.8.0) (2021-11-11) ### Features - add toContentfulDocument() and toSlatejsDocument() empty block node handling ([#287](https://github.com/contentful/rich-text/issues/287)) ([fa79626](https://github.com/contentful/rich-text/commit/fa79626e4020d9640a920ca5d0ccb654e89cfa90)) # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) ### Features - **rich-text-types:** Add TEXT_CONTAINERS ([#286](https://github.com/contentful/rich-text/issues/286)) ([3356ea8](https://github.com/contentful/rich-text/commit/3356ea815a46901a6637f177b04bcf1926adc88d)) ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) ### Features - add support to convert tables from md to rich text ([#284](https://github.com/contentful/rich-text/issues/284)) ([213a29c](https://github.com/contentful/rich-text/commit/213a29c78d48b3e63088999c4eed4891906d1719)) ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) ### Bug Fixes - npm 15.5.0 ([#280](https://github.com/contentful/rich-text/issues/280)) ([e7aeba4](https://github.com/contentful/rich-text/commit/e7aeba49a3074fc9eae6aee569db4e30d1acb8b8)) # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) # [15.4.0](https://github.com/contentful/rich-text/compare/v15.3.6...v15.4.0) (2021-09-16) ### Features - **html+react:** render Table header as ([#269](https://github.com/contentful/rich-text/issues/269)) ([0f82905](https://github.com/contentful/rich-text/commit/0f829059be6d91e042dfc71698009177ae4ab78d)) ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) ### Bug Fixes - allow table-row type to have both cells ([#267](https://github.com/contentful/rich-text/issues/267)) ([ec40598](https://github.com/contentful/rich-text/commit/ec405982109cb1c16e7adf71a541a98270d7f45b)) ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) ### Bug Fixes - **rich-text-types:** forbid Tables inside ListItem ([#266](https://github.com/contentful/rich-text/issues/266)) ([fc338bf](https://github.com/contentful/rich-text/commit/fc338bf040b8718057717d2681f800d5e26ba59d)) ## [15.3.4](https://github.com/contentful/rich-text/compare/v15.3.3...v15.3.4) (2021-09-09) ### Bug Fixes - replace import to fix typings for html-renderer ([#265](https://github.com/contentful/rich-text/issues/265)) ([40c2670](https://github.com/contentful/rich-text/commit/40c267069b18454517b0f4283a7e155cffa410b6)) ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text # Change Log # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) ### Bug Fixes - allow the first item in Table to be a TableHeaderRow ([9a6de55](https://github.com/contentful/rich-text/commit/9a6de55ed97ca413d3922958850b8215922c06b5)) - don't export the helper TableHeaderRow type ([179c3cb](https://github.com/contentful/rich-text/commit/179c3cb10c0f65725297445afe955f0cca135005)) - typo ([2282665](https://github.com/contentful/rich-text/commit/2282665924404771b3353dd9b380d8863f1d4c41)) ### Features - add TableHeaderCell type ([5e25ac9](https://github.com/contentful/rich-text/commit/5e25ac9f35be2ad6c7ad7857cbde808cbc3437f9)) # [15.2.0](https://github.com/contentful/rich-text/compare/v15.1.0...v15.2.0) (2021-08-16) ### Bug Fixes - 🐛 html encode default inlined CF entry/asset links ([41396eb](https://github.com/contentful/rich-text/commit/41396eb800f6d5c65c634c4394a6c60cf3425255)) - 🐛 prevent html injection via `data.uri` link rendering ([ecae89a](https://github.com/contentful/rich-text/commit/ecae89ad0d25175f342833ca989928670c24b8fd)) - audit fix aiming mixing-deep vulnerability ([2b98cbd](https://github.com/contentful/rich-text/commit/2b98cbd8e85435305378dd91d70d55db5e9c0832)) - audit fix root folder ([ffe9e44](https://github.com/contentful/rich-text/commit/ffe9e44c98611b0705f697c693e9b6b9d6c757cf)) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) ### Features - 🎸 add RT html renderer tables support ([ce81375](https://github.com/contentful/rich-text/commit/ce8137577b269c62727dc64b7d47b4951597dbd6)) - 🎸 add RT react renderer tables support ([fddfb8a](https://github.com/contentful/rich-text/commit/fddfb8a943b9807efe92b749d7ffdeb1308e42bd)) - add rowspan ([e0264fb](https://github.com/contentful/rich-text/commit/e0264fbe4d9467eedf78b7c5c3a9825a584124cf)) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ### Bug Fixes - **slatejs-adapter:** release as public package ([#227](https://github.com/contentful/rich-text/issues/227)) ([0ba8197](https://github.com/contentful/rich-text/commit/0ba81974a732bc6a32109b84e688e0903cc3632c)) ### Features - add table markup support ([c6ae127](https://github.com/contentful/rich-text/commit/c6ae127aa460f4cd26cd6b671cd43fd2714bc650)) # [14.2.0](https://github.com/contentful/rich-text/compare/v14.1.3...v14.2.0) (2021-05-04) ### Features - {wip} update slate.js adapter ([c281aea](https://github.com/contentful/rich-text/commit/c281aea098e949c45ed08dd6b46babfc0a75bfb9)) - fix test types ([0c86f93](https://github.com/contentful/rich-text/commit/0c86f9362f551cf8a2fcbeacfd995052a93a936a)) - update contentful-to-slatejs adapter ([33a3b6d](https://github.com/contentful/rich-text/commit/33a3b6d69c118e53b7b8464e1c41d9da26edf8d9)) - update contentful-to-slatejs-adapter ([89a32f4](https://github.com/contentful/rich-text/commit/89a32f494e434fa63b70146da29a81e24fae3cea)) ## [14.1.3](https://github.com/contentful/rich-text/compare/v14.1.2...v14.1.3) (2021-04-12) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ### Bug Fixes - 🐛 configure rollup copy correctly ([3446a33](https://github.com/contentful/rich-text/commit/3446a33fc5711087095a58b088828dfe6066bc7f)) - 🐛 skip lib check for TS ([e8bf2ba](https://github.com/contentful/rich-text/commit/e8bf2ba61be9f36f104d0e0337829ac07b30eec6)) - Add leaf object ([#5](https://github.com/contentful/rich-text/issues/5)) ([fb0b80d](https://github.com/contentful/rich-text/commit/fb0b80d7be60eb40a8dcfb51d65db2e488ea0097)) - Add lodash.omit to externals ([#7](https://github.com/contentful/rich-text/issues/7)) ([1c65200](https://github.com/contentful/rich-text/commit/1c652002831161fa34b59b18177565db057e3bc5)) - Remove doc deployment and fix ([90bccaa](https://github.com/contentful/rich-text/commit/90bccaaa9125cfe50627b27fff7a1b88a9044d3e)) ### Features - Add a default value for Marks ([6828215](https://github.com/contentful/rich-text/commit/68282155ee41f64e7f762b6823fd6d9b982ea160)) - Add data transform ([#6](https://github.com/contentful/rich-text/issues/6)) ([9d90fa1](https://github.com/contentful/rich-text/commit/9d90fa1995939a58739dbfaa0033b92b259d0687)) - Cleans up the `mark` property from extra props ([20ec641](https://github.com/contentful/rich-text/commit/20ec641130a17169587dae21e6fb231c180496d1)) - **contentful-to-slate:** Implement new isVoid in Slate's Node ([3d2d38d](https://github.com/contentful/rich-text/commit/3d2d38d22e43c4500252cba7af21d29a89c6fbda)) - data defaults to an empty object ([0a9b8a0](https://github.com/contentful/rich-text/commit/0a9b8a01049a6c48f38b09ea7075c17620db18dc)) - first version ([11591d0](https://github.com/contentful/rich-text/commit/11591d0d32fa81028ab71946c7fd68f783b56022)) - integrate structured-text-types ([#8](https://github.com/contentful/rich-text/issues/8)) ([d6d5e37](https://github.com/contentful/rich-text/commit/d6d5e376a7562821264ad7e3cc70b7d3cdff6ba5)) - remove content from void nodes ([0ce9898](https://github.com/contentful/rich-text/commit/0ce989824ae3adb8452ae20db821500f63d95d68)) - Remove nodeClass from Contentful's Document ([23bb2d5](https://github.com/contentful/rich-text/commit/23bb2d533301bf46b4f7eb0407b61972d3b38fff)) - update dependencies ([e294233](https://github.com/contentful/rich-text/commit/e29423329e5aa3c518fd6ca6a0cdda0c7cda440c)) - Update structured-text-type version ([#9](https://github.com/contentful/rich-text/issues/9)) ([ced65da](https://github.com/contentful/rich-text/commit/ced65dab9db1391f5b969d742594e2b9670ebb22)) ### Performance Improvements - ⚡️ Remove lodash.omit dependency ([93b6b76](https://github.com/contentful/rich-text/commit/93b6b765488a6d3e003a832c953ae10c3f68672e)) ### BREAKING CHANGES - Remove nodeClass from Contentful's Document - Changed the interface of Slate->Contentful adapter. Added new parameter Schema - **contentful-to-slate:** toSlatejsDocument now accepts Schema - Only supports structured-text-types >= 2.0.0 ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) ### Features - 🎸 emptyDoc representing an empty RT document ([80315ef](https://github.com/contentful/rich-text/commit/80315ef5130b79336336ba31de8a55e42bafe319)) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) ### Bug Fixes - 🐛 detect links as children of nodes also having links ([565f423](https://github.com/contentful/rich-text/commit/565f423b2881c7bcfac40e9ee9ecb6a545fce047)) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) ### Features - 🎸 Adds BLOCKS.DOCUMENT renderer support to react-renderer ([57c922c](https://github.com/contentful/rich-text/commit/57c922cb638c47729f2189815a647ba68859394e)), closes [#104](https://github.com/contentful/rich-text/issues/104) # [13.3.0](https://github.com/contentful/rich-text/compare/v13.2.0...v13.3.0) (2019-03-21) # [13.2.0](https://github.com/contentful/rich-text/compare/v13.1.0...v13.2.0) (2019-03-18) ### Features - 🎸 renderText option to alter all text ([f684a6e](https://github.com/contentful/rich-text/commit/f684a6e91d81ab0c66c286adaa923bd2edf0f4f1)) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) ### Bug Fixes - 🐛 Fix incorrect typings file path ([cca74d5](https://github.com/contentful/rich-text/commit/cca74d5594fb0594fd1117945e087afa2000877b)) ### Features - 🎸 Support filtering by node name in rich-text-links ([5ec0dda](https://github.com/contentful/rich-text/commit/5ec0dda07cdbe3d28027d28520c553b074ca699f)) ## [13.0.1](https://github.com/contentful/rich-text/compare/v13.0.0...v13.0.1) (2019-02-11) ### Bug Fixes - 🐛 Fix wrong import in tests ([bead583](https://github.com/contentful/rich-text/commit/bead583e6e9bb71638694e466980b90e599fa47b)) - 🐛 make "time to read" great again ([6edd37d](https://github.com/contentful/rich-text/commit/6edd37d47fadcc0145188fa1fdfae811cb4145dd)) ### Features - 🎸 Initial rich-text-react-renderer implementation ([06dad9b](https://github.com/contentful/rich-text/commit/06dad9b0359325d8fa433438dac997fc9656d13f)) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) ### Bug Fixes - 🐛 use named export instead of default ([57bf365](https://github.com/contentful/rich-text/commit/57bf36510f1021f5208a3e33242cceea97940d68)) ### BREAKING CHANGES - removes default export from 'rich-text-plain-text-renderer' ## [12.2.1](https://github.com/contentful/rich-text/compare/v12.2.0...v12.2.1) (2019-01-22) ### Bug Fixes - gracefully handle empty rich text documents ([14870de](https://github.com/contentful/rich-text/commit/14870ded471f69cee84a60739bee06873ed53af9)) ### Features - 🎸 add time to read for reach text ([5f2115a](https://github.com/contentful/rich-text/commit/5f2115afd1ef41e0e5eb419368510d682524706e)) # [12.2.0](https://github.com/contentful/rich-text/compare/v12.1.2...v12.2.0) (2018-12-20) ### Bug Fixes - fix import path ([3f3edff](https://github.com/contentful/rich-text/commit/3f3edff8bc4f4463a701d0ae1d397fcf9acec325)) - ignore unsupported marks ([1757331](https://github.com/contentful/rich-text/commit/1757331a74f360353556a242a9fe20da131b9a59)) ### Features - 🎸 parse links inside marks ([11722cf](https://github.com/contentful/rich-text/commit/11722cfad85d5e15b10d2b18d3c831ec613922c3)) ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ### Bug Fixes - fix npm ignore ([5c9e58e](https://github.com/contentful/rich-text/commit/5c9e58ed7fabecf430c91a6776665d8ad73f4bf3)) ### Features - add html escaping ([4b55331](https://github.com/contentful/rich-text/commit/4b55331e86bc62787420f8081228293f1a22e1b7)) ## [12.1.1](https://github.com/contentful/rich-text/compare/v12.1.0...v12.1.1) (2018-12-12) ### Bug Fixes - handle hyperlinks ([54508d4](https://github.com/contentful/rich-text/commit/54508d495adf5055e4089f54dd62f00f8be6be46)) # [12.1.0](https://github.com/contentful/rich-text/compare/v12.0.4...v12.1.0) (2018-12-12) ### Bug Fixes - **async-handling:** move to promise-based api ([a07d3fc](https://github.com/contentful/rich-text/commit/a07d3fcbd99a2d80f86bc6dcf5cba01665221ebc)) ## [12.0.4](https://github.com/contentful/rich-text/compare/v12.0.3...v12.0.4) (2018-12-05) ### Bug Fixes - Add index.js ([383cc98](https://github.com/contentful/rich-text/commit/383cc98e5dbcf8f144c8504759c6090daff611db)) - **package:** add babel/runtime to deps; remove 'main' from package.json ([f7adbff](https://github.com/contentful/rich-text/commit/f7adbfffe704335601751e0f43f073ba867bc534)) - **package:** use caret operator for gatsby peer dep ([21478ef](https://github.com/contentful/rich-text/commit/21478ef77f96546dab561f603940d4de5553340e)) ## [12.0.3](https://github.com/contentful/rich-text/compare/v12.0.2...v12.0.3) (2018-12-05) ### Bug Fixes - **build:** rm gatsby from dev-deps on gatsby plugin ([2b3f87b](https://github.com/contentful/rich-text/commit/2b3f87b0bae48b32456a10f3a9c48c9c921df401)) - **deps:** dep to old version within monorepo ([b0da60a](https://github.com/contentful/rich-text/commit/b0da60af975cb45d94063bd78bd2b2882ad0da80)) - **readme:** mark types in readme examples ([d997b56](https://github.com/contentful/rich-text/commit/d997b56f2b8c32b2e5b478ab5444757203e2c703)) - Remove debugger ([c7c9c4f](https://github.com/contentful/rich-text/commit/c7c9c4f1a41d98825cdc782d3b529da03d237d3a)) ### Features - **transformer:** Add gatsby-transformer-contentful-richtext ([8ed2adb](https://github.com/contentful/rich-text/commit/8ed2adbe9b53cbe9992e4483a67b05e7950a743c)) ### Performance Improvements - ⚡️ prefer iterator unrolling to Array.from ([a887a92](https://github.com/contentful/rich-text/commit/a887a9213dee9dd6986cf313e67b94fd020d138b)) ## [12.0.2](https://github.com/contentful/rich-text/compare/v12.0.1...v12.0.2) (2018-12-04) ### Bug Fixes - 🐛 update path to typings ([ce5544f](https://github.com/contentful/rich-text/commit/ce5544f58712dc6a18aadda523d0c0357a66c8a5)) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) ### Bug Fixes - 🐛 add Object.values and Array.prototype.includes polyfills ([9e2ffb4](https://github.com/contentful/rich-text/commit/9e2ffb46e3564a523c74504270b29ccc8b8249ad)) - **from-markdown:** Fix list typos ([c392016](https://github.com/contentful/rich-text/commit/c392016e2f11628021cd27bff3447f548398c3b4)) - **parsing:** list item, (un)ordered list, links, quotes ([8a0c580](https://github.com/contentful/rich-text/commit/8a0c580306c154f1c1d6c2ba964c46d18881be12)) - **text:** Parse text nodes with marks correctly ([d489f90](https://github.com/contentful/rich-text/commit/d489f904a33726f42006b09871d15bcfbdd7e274)) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) ### Features - 🎸 `getRichTextEntityLinks` perf and signature changes ([fcd6d7f](https://github.com/contentful/rich-text/commit/fcd6d7f42e63df87ddb58d651b86a023d45f990e)) ### Performance Improvements - ⚡️ Add benchmark support ([ae278b7](https://github.com/contentful/rich-text/commit/ae278b7be515a52576af0a1abb6a4cefb05f71a7)) ### BREAKING CHANGES - Produces an entirely new return value and obviates the `linkType` parameter (all entities are grouped and returned by default). # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) ### Bug Fixes - 🐛 Removes cz-lerna-changelog from devDependencies ([31daac0](https://github.com/contentful/rich-text/commit/31daac0d10a66186e01a085bd466fdc18a20ab5f)) - 🐛 Removes obsolete field `title` from Hyperlinks ([8cb9027](https://github.com/contentful/rich-text/commit/8cb90279f5348a7fb59f211c2ba209a28fd432be)) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) ### Features - 🎸 return all entity links when no linkType is provided ([02970ea](https://github.com/contentful/rich-text/commit/02970ea61b5b13d99c7b629902defe704c146b17)) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) - 🎸 Rename, refactor, docu. & cache r-t-links ([9cf6d14](https://github.com/contentful/rich-text/commit/9cf6d1460833300053cb8dde5a6e29b0ddf89964)) # [10.1.0](https://github.com/contentful/rich-text/compare/v10.0.5...v10.1.0) (2018-11-16) ### Bug Fixes - 🐛 removes obsolete fields from mark nodes ([b638a56](https://github.com/contentful/rich-text/commit/b638a56652520969d3ac898ac158be81a9788f67)) ### Features - 🎸 adds json schema generation to rich text types ([8916140](https://github.com/contentful/rich-text/commit/89161404eb911f126be23ba2a146ebf748f7489e)) - 🎸 introduces top-level-block type ([a6bf35e](https://github.com/contentful/rich-text/commit/a6bf35e7c9ca35915a512de774b3a3fdc4c76e5d)) ## [10.0.5](https://github.com/contentful/rich-text/compare/v10.0.4...v10.0.5) (2018-11-12) ### Bug Fixes - 🐛 handle case of missing rootNode / rootNode.content ([e2434c7](https://github.com/contentful/rich-text/commit/e2434c7f5e1401f1188dd778c2aa9a108cea5596)) ## [10.0.4](https://github.com/contentful/rich-text/compare/v10.0.3...v10.0.4) (2018-11-09) ## [10.0.3](https://github.com/contentful/rich-text/compare/v10.0.2...v10.0.3) (2018-11-09) ## [10.0.2](https://github.com/contentful/rich-text/compare/v10.0.1...v10.0.2) (2018-11-09) ## [10.0.1](https://github.com/contentful/rich-text/compare/v10.0.0...v10.0.1) (2018-11-08) ### Features - **packages:** Add rich text from markdown package ([bc8ec41](https://github.com/contentful/rich-text/commit/bc8ec41f5615eabcc29031ee99da3f9c70b414b3)) # [10.0.0](https://github.com/contentful/rich-text/compare/v9.0.2...v10.0.0) (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## [9.0.2](https://github.com/contentful/rich-text/compare/v9.0.1...v9.0.2) (2018-10-31) ## [9.0.1](https://github.com/contentful/rich-text/compare/v9.0.0...v9.0.1) (2018-10-31) # [9.0.0](https://github.com/contentful/rich-text/compare/v8.0.3...v9.0.0) (2018-10-30) ### Bug Fixes - 🐛 Fix block divisor provision ([3aafb7a](https://github.com/contentful/rich-text/commit/3aafb7a756d8b6f96ef733eb299091404b3391aa)) - 🐛 remove exclusive mocha test ([5ff5d0e](https://github.com/contentful/rich-text/commit/5ff5d0e35024b7dccda065f54ae34138d9416525)) ### Features - 🎸 Explicitly declare nodeClass and nodeType in core types ([0749c61](https://github.com/contentful/rich-text/commit/0749c6199bb2681509608539c76bd8149cade564)) ### BREAKING CHANGES - Fundamentally alters the plain text renderer behavior. (For the better!) - Potentially breaks TypeScript libraries pulling this in as a dependency if they are not explicitly stating nodeClass and nodeType. ## [8.0.3](https://github.com/contentful/rich-text/compare/v8.0.2...v8.0.3) (2018-10-30) ## [8.0.2](https://github.com/contentful/rich-text/compare/v8.0.1...v8.0.2) (2018-10-29) ## [8.0.1](https://github.com/contentful/rich-text/compare/v8.0.0...v8.0.1) (2018-10-26) ### Bug Fixes - 🐛 Revert mock hypenation commits ([bde9432](https://github.com/contentful/rich-text/commit/bde94323fcc02bf5ab3feeef46a9d8fc8b08d59b)) - sync HTML renderer ([769bd95](https://github.com/contentful/rich-text/commit/769bd95add1aecad0e11fab377f99c5cdad99072)) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages ## [7.0.1](https://github.com/contentful/rich-text/compare/v7.0.0...v7.0.1) (2018-10-26) ### Bug Fixes - 🐛 hypenate more hyperlink tokens ([f88b377](https://github.com/contentful/rich-text/commit/f88b3775c1849fd0ca26b103999d819fd1b9b801)) # [7.0.0](https://github.com/contentful/rich-text/compare/v6.0.0...v7.0.0) (2018-10-26) ### Bug Fixes - 🐛 blockquote -> block-quote ([5b66181](https://github.com/contentful/rich-text/commit/5b66181943d835baffd7574edef5dd34ac301923)) - 🐛 hyphenate hyper-link ([662f60d](https://github.com/contentful/rich-text/commit/662f60dd966202a03dab9c49719bf34166fbdaed)) - 🐛 Remove .releaserc.js ([f1f72a7](https://github.com/contentful/rich-text/commit/f1f72a7435bcc60cdd1a63979cade47cd91c05fb)) ### chore - 🤖 :%s/ci/travis ([4731881](https://github.com/contentful/rich-text/commit/47318816edb1d9cac3f778931bb921c522e7be3a)) - 🤖 swap for semantic-release-monorepo ([cb8ca3e](https://github.com/contentful/rich-text/commit/cb8ca3ef5a4f3df9c041967352a611f4216c9d81)) ### BREAKING CHANGES - major version bump - None, but let's pretend like there are some anyway to trigger a major version bump # 6.0.0 (2018-10-24) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to `rich-text`

PRs Welcome   Semantic Release   JS Standard Style

We appreciate any community contributions to this project, whether in the form of issues or pull requests. This document outlines what we'd like you to follow in terms of commit messages and code style. It also explains what to do if you want to set up the project locally and run tests. **Working on your first Pull Request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) ## Useful npm scripts - `yarn build` builds vendored files in the `dist` directory of each package - `yarn commit` runs git commits with [commitizen](http://commitizen.github.io/cz-cli/) - `yarn clean` removes any built files and `node_modules` - `yarn lint` runs our TypeScript linter on all `.ts` files in each package - `yarn test` runs unit tests for all packages - `yarn test:watch` runs unit tests in "watch" mode (will refresh relevant code paths on save) ## Setup This project is a [Lerna](https://lernajs.io/) monorepo with several packages, each published separately to npm. ### Local Development Run `npm install` to install all necessary dependencies. As a post-install step, all Lerna dependencies are [hoisted](https://github.com/lerna/lerna/blob/master/doc/hoist.md), and hence internally reliant packages (e.g., `rich-text-html-renderer`, which depends upon `rich-text-types`) will resolve their modules via symlink. In other words, `npm install` will _both_ install external dependencies for each project, _and_ ensure packages that pull in other packages in this repository as dependencies are linked to the local version (rather than whatever the state of those packages is on npm). Each package is written in [TypeScript](https://www.typescriptlang.org/) and compiled to ES5 using [rollup](https://rollupjs.org/guide/en) to the `dist` directory. This should generally only happen at publishing time, but you may want to run `yarn build` prematurely during local development. For example, let's say you're working on a pull request that 1. adds support for a type in `rich-text-types`, and 2. adds behavior to handle that type in `rich-text-html-renderer`. If changes in the latter are dependent upon changes in the former, you'll need to run `yarn build` to update the referenced vendored files in `rich-text-html-renderer`. All necessary dependencies are installed under `node_modules` and any necessary tools can be accessed via npm scripts. There is no need to install anything globally. ### Creating commits We follow [Angular JS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#allowed-type) to generate a changelog. ### Code style This project uses [JavaScript Standard Style](https://standardjs.com/) and [Prettier](https://prettier.io/) conventions. Install a relevant editor plugin if you'd like. When in doubt, follow a style similar to the existing code :) We use a common [rollup](https://rollupjs.org/guide/en) config to compile packages from TypeScript to ES5. We also use common [TypeScript](https://www.typescriptlang.org/) and [eslint](https://eslint.org/docs/v8.x/) configs. You'll notice the `rollup.config.js` and `tsconfig.json` in each package largely reference those files on a root level - this keeps code conventions consistent across the repository as a whole. ### Running tests We use [Jest](https://jestjs.io/) for unit tests. See **Useful npm scripts** above for some relevant npm commands. ## Publishing We use [Lerna](https://github.com/lerna/lerna) to: - keep dependencies in sync - `lerna bootstrap --hoist` (which is run as a post-install step) - publish - `NPM_CONFIG_OTP={2fa_otp_goes_here} yarn publish` - As a community developer, you most likely won't have to worry about this step :) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Contentful Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # rich-text Monorepo with Typescript libraries for handling and rendering Contentful Rich Text documents. ## Packages ### Official - [`rich-text-from-markdown`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-from-markdown) - Converts markdown documents to rich text - [`rich-text-html-renderer`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-html-renderer) - Converts rich text documents to HTML - [`rich-text-links`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-links) - Entity (entry and asset) link extraction utilities - [`rich-text-plain-text-renderer`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-plain-text-renderer) - Converts rich text documents to plain text - [`rich-text-types`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-types) - Type definitions and constants for the rich text field type - [`gatsby-transformer-contentful-richtext`](https://github.com/contentful/rich-text/tree/4ef875c44b4c8617f4035a46569b240d8cc67bbe/deprecated/gatsby-transformer-contentful-richtext) [DEPRECATED] - Parses a Contentful Rich Text document to HTML in Gatsby - [`rich-text-react-renderer`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-react-renderer) - Parses a Contentful Rich Text document to React components ### Community made - [`rich-text-to-jsx`](https://github.com/connor-baer/rich-text-to-jsx) - Opinionated JSX renderer for the Contentful rich text field type - [`rich-text-flutter`](https://github.com/Kumanu/contentful-rich-text-flutter) - Flutter renderer for the Contentful rich text field type (work in progress) - [`ngx-contentful-rich-text`](https://github.com/kgajera/ngx-contentful-rich-text) - Angular renderer for the Contentful rich text field type ## About Rich Text - [Rich Text Concept](https://www.contentful.com/developers/docs/concepts/rich-text/) - [Getting Started](https://www.contentful.com/developers/docs/tutorials/general/getting-started-with-rich-text-field-type/) - [Migrate content to Rich Text](https://www.contentful.com/developers/docs/tutorials/general/migrate-to-rich-text/) ## Get involved [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?maxAge=31557600)](http://makeapullrequest.com) We appreciate any help on our repositories. For more details about how to contribute, see our [CONTRIBUTING.md](https://github.com/contentful/rich-text/blob/master/CONTRIBUTING.md) document. ## Reach out to us ### You have questions about how to use this library? - Reach out to our community forum: [![Contentful Community Forum](https://img.shields.io/badge/-Join%20Community%20Forum-3AB2E6.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MiA1OSI+CiAgPHBhdGggZmlsbD0iI0Y4RTQxOCIgZD0iTTE4IDQxYTE2IDE2IDAgMCAxIDAtMjMgNiA2IDAgMCAwLTktOSAyOSAyOSAwIDAgMCAwIDQxIDYgNiAwIDEgMCA5LTkiIG1hc2s9InVybCgjYikiLz4KICA8cGF0aCBmaWxsPSIjNTZBRUQyIiBkPSJNMTggMThhMTYgMTYgMCAwIDEgMjMgMCA2IDYgMCAxIDAgOS05QTI5IDI5IDAgMCAwIDkgOWE2IDYgMCAwIDAgOSA5Ii8+CiAgPHBhdGggZmlsbD0iI0UwNTM0RSIgZD0iTTQxIDQxYTE2IDE2IDAgMCAxLTIzIDAgNiA2IDAgMSAwLTkgOSAyOSAyOSAwIDAgMCA0MSAwIDYgNiAwIDAgMC05LTkiLz4KICA8cGF0aCBmaWxsPSIjMUQ3OEE0IiBkPSJNMTggMThhNiA2IDAgMSAxLTktOSA2IDYgMCAwIDEgOSA5Ii8+CiAgPHBhdGggZmlsbD0iI0JFNDMzQiIgZD0iTTE4IDUwYTYgNiAwIDEgMS05LTkgNiA2IDAgMCAxIDkgOSIvPgo8L3N2Zz4K&maxAge=31557600)](https://support.contentful.com/) - Jump into our community slack channel: [![Contentful Community Slack](https://img.shields.io/badge/-Join%20Community%20Slack-2AB27B.svg?logo=slack&maxAge=31557600)](https://www.contentful.com/slack/) ### You found a bug or want to propose a feature? - File an issue here on GitHub: [![File an issue](https://img.shields.io/badge/-Create%20Issue-6cc644.svg?logo=github&maxAge=31557600)](https://github.com/contentful/rich-text/issues/new). Make sure to remove any credential from your code before sharing it. ### You need to share confidential information or have other questions? - File a support ticket at our Contentful Customer Support: [![File support ticket](https://img.shields.io/badge/-Submit%20Support%20Ticket-3AB2E6.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MiA1OSI+CiAgPHBhdGggZmlsbD0iI0Y4RTQxOCIgZD0iTTE4IDQxYTE2IDE2IDAgMCAxIDAtMjMgNiA2IDAgMCAwLTktOSAyOSAyOSAwIDAgMCAwIDQxIDYgNiAwIDEgMCA5LTkiIG1hc2s9InVybCgjYikiLz4KICA8cGF0aCBmaWxsPSIjNTZBRUQyIiBkPSJNMTggMThhMTYgMTYgMCAwIDEgMjMgMCA2IDYgMCAxIDAgOS05QTI5IDI5IDAgMCAwIDkgOWE2IDYgMCAwIDAgOSA5Ii8+CiAgPHBhdGggZmlsbD0iI0UwNTM0RSIgZD0iTTQxIDQxYTE2IDE2IDAgMCAxLTIzIDAgNiA2IDAgMSAwLTkgOSAyOSAyOSAwIDAgMCA0MSAwIDYgNiAwIDAgMC05LTkiLz4KICA8cGF0aCBmaWxsPSIjMUQ3OEE0IiBkPSJNMTggMThhNiA2IDAgMSAxLTktOSA2IDYgMCAwIDEgOSA5Ii8+CiAgPHBhdGggZmlsbD0iI0JFNDMzQiIgZD0iTTE4IDUwYTYgNiAwIDEgMS05LTkgNiA2IDAgMCAxIDkgOSIvPgo8L3N2Zz4K&maxAge=31557600)](https://www.contentful.com/support/) ## License This repository is published under the [MIT](LICENSE) license. ## Code of Conduct We want to provide a safe, inclusive, welcoming, and harassment-free space and experience for all participants, regardless of gender identity and expression, sexual orientation, disability, physical appearance, socioeconomic status, body size, ethnicity, nationality, level of experience, age, religion (or lack thereof), or other identity markers. [Read our full Code of Conduct](https://github.com/contentful-developer-relations/community-code-of-conduct). ================================================ FILE: baseJestConfig.js ================================================ function getConfig(packageName) { return { collectCoverage: true, testPathIgnorePatterns: ['/dist/'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', // strip .js so TS imports resolve }, transform: { '^.+\\.(j|t)sx?$': '@swc/jest', }, testMatch: ['**/*.test.[jt]s?(x)'], reporters: [ 'default', [ 'jest-junit', { outputDirectory: '../../reports', outputName: `${packageName}-results.xml`, addFileAttribute: true, }, ], ], }; } module.exports = getConfig; ================================================ FILE: catalog-info.yaml ================================================ apiVersion: backstage.io/v1alpha1 kind: System metadata: name: rich-text description: | Monorepo with Typescript libraries for handling and rendering Contentful Rich Text documents. annotations: circleci.com/project-slug: github/contentful/rich-text github.com/project-slug: contentful/rich-text backstage.io/source-location: url:https://github.com/contentful/rich-text/ contentful.com/ci-alert-slack: prd-authoring-pub-alerts spec: type: library lifecycle: production owner: group:team-content-authoring-and-publishing ================================================ FILE: commitlint.config.js ================================================ module.exports = { extends: ['@commitlint/config-conventional'] }; ================================================ FILE: lerna.json ================================================ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "pnpm", "version": "independent", "packages": ["packages/*"], "command": { "version": { "allowBranch": "master", "conventionalCommits": true, "message": "chore(release): updated release notes and package versions [ci skip]", "ignoreChanges": [ "*.md", "*.mdx", "**/*.spec.*", "**/*.test.*", "**/*.stories.*", "**/__fixtures__/**", "**/__tests__/**" ] } } } ================================================ FILE: nx.json ================================================ { "$schema": "./node_modules/nx/schemas/nx-schema.json", "tasksRunnerOptions": { "default": { "runner": "nx/tasks-runners/default", "options": { "cacheableOperations": [ "build", "lint", "prebuild", "test", "test:prod", "generate-json-schema" ] } } }, "targetDefaults": { "build": { "dependsOn": ["^build"], "outputs": ["{projectRoot}/dist"] }, "lint": { "dependsOn": ["^lint"], "outputs": ["{projectRoot}/]"] }, "prebuild": { "dependsOn": ["^prebuild"] }, "test": { "dependsOn": ["^test"] }, "generate-json-schema": { "dependsOn": ["^generate-json-schema"], "outputs": ["{projectRoot}/src/schemas/generated"] } }, "affected": { "defaultBase": "origin/master" }, "defaultBase": "master", "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], "sharedGlobals": [], "production": ["default"] } } ================================================ FILE: package.json ================================================ { "name": "@contentful/rich-text", "private": true, "description": "A monorepo for all NPM packages related to Rich Text", "author": "Contentful GmbH", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "engines": { "node": ">=24" }, "scripts": { "build": "lerna run build", "clean": "lerna exec 'rm -rf node_modules/ dist/' && rm -rf node_modules/", "lint": "eslint ./ --ext .ts,.tsx", "lerna": "lerna", "prettier": "prettier './**/*.{js,jsx,ts,tsx,md,mdx}' --write", "prettier:check": "prettier --check '**/*.{jsx,js,ts,tsx,md,mdx}'", "prebuild": "lerna run prebuild", "start": "lerna run start", "test": "lerna run test", "prepare": "husky" }, "workspaces": [ "packages/*" ], "lint-staged": { "**/*.{jsx,js,ts,tsx,md,mdx}": [ "prettier --write" ] }, "prettier": { "singleQuote": true, "trailingComma": "all" }, "devDependencies": { "@commitlint/cli": "20.3.1", "@commitlint/config-conventional": "20.3.1", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-swc": "0.4.0", "@swc/core": "1.15.10", "@swc/cli": "0.7.10", "@swc/jest": "0.2.39", "@types/jest": "30.0.0", "@types/rollup-plugin-json": "^3.0.7", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "eslint": "8.57.1", "eslint-plugin-jest": "29.12.1", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-import": "2.32.0", "eslint-plugin-import-helpers": "2.0.0", "eslint-plugin-you-dont-need-lodash-underscore": "6.14.0", "husky": "^9.0.11", "jest": "30.2.0", "jest-junit": "16.0.0", "lerna": "8.2.3", "lint-staged": "16.2.7", "prettier": "^3.2.5", "rimraf": "6.1.2", "rollup": "^4.18.0", "rollup-plugin-json": "^4.0.0", "typescript": "5.9.3" }, "packageManager": "pnpm@10.33.0", "pnpm": { "overrides": { "react-is": "^19.2.3" } } } ================================================ FILE: packages/contentful-slatejs-adapter/.editorconfig ================================================ #root = true [*] indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 100 indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: packages/contentful-slatejs-adapter/.gitignore ================================================ node_modules coverage .nyc_output .DS_Store *.log .vscode .idea dist compiled .awcache .rpt2_cache docs .vs/slnx.sqlite ================================================ FILE: packages/contentful-slatejs-adapter/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [16.2.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.2.0...@contentful/contentful-slatejs-adapter@16.2.1) (2026-04-09) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.6...@contentful/contentful-slatejs-adapter@16.2.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [16.1.6](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.5...@contentful/contentful-slatejs-adapter@16.1.6) (2025-11-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.1.5](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.4...@contentful/contentful-slatejs-adapter@16.1.5) (2025-11-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.1.4](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.3...@contentful/contentful-slatejs-adapter@16.1.4) (2025-09-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.1.3](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.2...@contentful/contentful-slatejs-adapter@16.1.3) (2025-09-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.1.2](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.1...@contentful/contentful-slatejs-adapter@16.1.2) (2025-09-10) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.1.0...@contentful/contentful-slatejs-adapter@16.1.1) (2025-09-09) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.0.4...@contentful/contentful-slatejs-adapter@16.1.0) (2025-09-04) ### Features - migrate to swc and use lingui for i18n [TOL-3288] ([#914](https://github.com/contentful/rich-text/issues/914)) ([b6782a9](https://github.com/contentful/rich-text/commit/b6782a9658b24944ccce2676f06efb1a527d9936)) ## [16.0.4](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.0.3...@contentful/contentful-slatejs-adapter@16.0.4) (2025-08-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.0.3](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.0.2...@contentful/contentful-slatejs-adapter@16.0.3) (2025-07-28) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.0.1...@contentful/contentful-slatejs-adapter@16.0.2) (2025-07-15) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@16.0.0...@contentful/contentful-slatejs-adapter@16.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [16.0.0](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.11...@contentful/contentful-slatejs-adapter@16.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [15.18.11](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.10...@contentful/contentful-slatejs-adapter@15.18.11) (2024-09-09) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.10](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.9...@contentful/contentful-slatejs-adapter@15.18.10) (2024-08-26) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.9](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.8...@contentful/contentful-slatejs-adapter@15.18.9) (2024-07-29) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.8](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.7...@contentful/contentful-slatejs-adapter@15.18.8) (2024-07-24) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.7](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.6...@contentful/contentful-slatejs-adapter@15.18.7) (2024-07-17) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.6](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.5...@contentful/contentful-slatejs-adapter@15.18.6) (2024-07-17) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.5](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.4...@contentful/contentful-slatejs-adapter@15.18.5) (2024-07-16) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.4](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.3...@contentful/contentful-slatejs-adapter@15.18.4) (2024-07-16) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.3](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.2...@contentful/contentful-slatejs-adapter@15.18.3) (2024-07-16) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.2](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.1...@contentful/contentful-slatejs-adapter@15.18.2) (2024-06-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.18.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.18.0...@contentful/contentful-slatejs-adapter@15.18.1) (2024-06-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.18.0](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.5...@contentful/contentful-slatejs-adapter@15.18.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [15.17.5](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.4...@contentful/contentful-slatejs-adapter@15.17.5) (2024-06-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.17.4](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.3...@contentful/contentful-slatejs-adapter@15.17.4) (2024-06-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.17.3](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.2...@contentful/contentful-slatejs-adapter@15.17.3) (2024-05-28) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.17.2](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.1...@contentful/contentful-slatejs-adapter@15.17.2) (2024-05-27) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.17.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.17.0...@contentful/contentful-slatejs-adapter@15.17.1) (2024-05-24) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.17.0](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.14...@contentful/contentful-slatejs-adapter@15.17.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [15.16.14](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.13...@contentful/contentful-slatejs-adapter@15.16.14) (2024-05-22) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.13](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.12...@contentful/contentful-slatejs-adapter@15.16.13) (2024-03-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.12](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.11...@contentful/contentful-slatejs-adapter@15.16.12) (2024-01-30) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.11](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.10...@contentful/contentful-slatejs-adapter@15.16.11) (2024-01-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.10](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.9...@contentful/contentful-slatejs-adapter@15.16.10) (2024-01-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.9](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.8...@contentful/contentful-slatejs-adapter@15.16.9) (2024-01-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.8](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.7...@contentful/contentful-slatejs-adapter@15.16.8) (2023-09-12) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.7](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.6...@contentful/contentful-slatejs-adapter@15.16.7) (2023-08-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.6](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.5...@contentful/contentful-slatejs-adapter@15.16.6) (2023-05-26) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.5](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.4...@contentful/contentful-slatejs-adapter@15.16.5) (2023-05-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.4](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.3...@contentful/contentful-slatejs-adapter@15.16.4) (2023-04-03) ### Bug Fixes - entrypoint for types of package ([#454](https://github.com/contentful/rich-text/issues/454)) ([3809d57](https://github.com/contentful/rich-text/commit/3809d57891be88950798c1c2638ee5cebc376bd4)) ## [15.16.3](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.2...@contentful/contentful-slatejs-adapter@15.16.3) (2023-03-14) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.2](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.1...@contentful/contentful-slatejs-adapter@15.16.2) (2022-12-01) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.16.1](https://github.com/contentful/rich-text/compare/@contentful/contentful-slatejs-adapter@15.16.0...@contentful/contentful-slatejs-adapter@15.16.1) (2022-12-01) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # 15.16.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - update Lerna, rollup in slatejs-adapter ([#366](https://github.com/contentful/rich-text/issues/366)) ([32448e3](https://github.com/contentful/rich-text/commit/32448e369ae2b76601dd81839e13c15432430d68)) ## 15.13.1 (2022-05-10) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## 15.9.1 (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # 15.9.0 (2021-12-09) # 15.8.0 (2021-11-11) ### Features - add toContentfulDocument() and toSlatejsDocument() empty block node handling ([#287](https://github.com/contentful/rich-text/issues/287)) ([fa79626](https://github.com/contentful/rich-text/commit/fa79626e4020d9640a920ca5d0ccb654e89cfa90)) # 15.7.0 (2021-11-11) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) # 15.5.0 (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) ## 15.3.5 (2021-09-13) ### Bug Fixes - **rich-text-types:** forbid Tables inside ListItem ([#266](https://github.com/contentful/rich-text/issues/266)) ([fc338bf](https://github.com/contentful/rich-text/commit/fc338bf040b8718057717d2681f800d5e26ba59d)) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.1.0 (2021-08-02) # 15.0.0 (2021-06-15) ### Bug Fixes - **slatejs-adapter:** release as public package ([#227](https://github.com/contentful/rich-text/issues/227)) ([0ba8197](https://github.com/contentful/rich-text/commit/0ba81974a732bc6a32109b84e688e0903cc3632c)) # 14.2.0 (2021-05-04) ### Features - {wip} update slate.js adapter ([c281aea](https://github.com/contentful/rich-text/commit/c281aea098e949c45ed08dd6b46babfc0a75bfb9)) - fix test types ([0c86f93](https://github.com/contentful/rich-text/commit/0c86f9362f551cf8a2fcbeacfd995052a93a936a)) - update contentful-to-slatejs adapter ([33a3b6d](https://github.com/contentful/rich-text/commit/33a3b6d69c118e53b7b8464e1c41d9da26edf8d9)) - update contentful-to-slatejs-adapter ([89a32f4](https://github.com/contentful/rich-text/commit/89a32f494e434fa63b70146da29a81e24fae3cea)) ## 14.1.2 (2020-11-02) ### Bug Fixes - 🐛 skip lib check for TS ([e8bf2ba](https://github.com/contentful/rich-text/commit/e8bf2ba61be9f36f104d0e0337829ac07b30eec6)) ### Performance Improvements - ⚡️ Remove lodash.omit dependency ([93b6b76](https://github.com/contentful/rich-text/commit/93b6b765488a6d3e003a832c953ae10c3f68672e)) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - update Lerna, rollup in slatejs-adapter ([#366](https://github.com/contentful/rich-text/issues/366)) ([32448e3](https://github.com/contentful/rich-text/commit/32448e369ae2b76601dd81839e13c15432430d68)) ## [15.13.1](https://github.com/contentful/rich-text/compare/v15.13.0...v15.13.1) (2022-05-10) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.8.0](https://github.com/contentful/rich-text/compare/v15.7.0...v15.8.0) (2021-11-11) ### Features - add toContentfulDocument() and toSlatejsDocument() empty block node handling ([#287](https://github.com/contentful/rich-text/issues/287)) ([fa79626](https://github.com/contentful/rich-text/commit/fa79626e4020d9640a920ca5d0ccb654e89cfa90)) # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) ### Bug Fixes - **rich-text-types:** forbid Tables inside ListItem ([#266](https://github.com/contentful/rich-text/issues/266)) ([fc338bf](https://github.com/contentful/rich-text/commit/fc338bf040b8718057717d2681f800d5e26ba59d)) ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [15.2.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.1.0) (2021-09-06) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ### Bug Fixes - **slatejs-adapter:** release as public package ([#227](https://github.com/contentful/rich-text/issues/227)) ([0ba8197](https://github.com/contentful/rich-text/commit/0ba81974a732bc6a32109b84e688e0903cc3632c)) # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/contentful-slatejs-adapter # [14.2.0](https://github.com/contentful/rich-text/compare/v14.1.3...v14.2.0) (2021-05-04) ### Features - {wip} update slate.js adapter ([c281aea](https://github.com/contentful/rich-text/commit/c281aea098e949c45ed08dd6b46babfc0a75bfb9)) - fix test types ([0c86f93](https://github.com/contentful/rich-text/commit/0c86f9362f551cf8a2fcbeacfd995052a93a936a)) - update contentful-to-slatejs adapter ([33a3b6d](https://github.com/contentful/rich-text/commit/33a3b6d69c118e53b7b8464e1c41d9da26edf8d9)) - update contentful-to-slatejs-adapter ([89a32f4](https://github.com/contentful/rich-text/commit/89a32f494e434fa63b70146da29a81e24fae3cea)) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ### Bug Fixes - 🐛 skip lib check for TS ([e8bf2ba](https://github.com/contentful/rich-text/commit/e8bf2ba61be9f36f104d0e0337829ac07b30eec6)) ### Performance Improvements - ⚡️ Remove lodash.omit dependency ([93b6b76](https://github.com/contentful/rich-text/commit/93b6b765488a6d3e003a832c953ae10c3f68672e)) ================================================ FILE: packages/contentful-slatejs-adapter/README.md ================================================ # contentful-slatejs-adapter This library provides an adapter to convert Slate's document structure to Contentful's rich text document structure and vice-versa. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/contentful-slatejs-adapter ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/contentful-slatejs-adapter ``` ## Usage _TBA_ ```json { "category": "document", "content": [ { "category": "block", "type": "header-one", "content": [ { "category": "text", "type": "text", "value": "This is a headline!", "marks": [ { "object": "mark", "type": "bold", "data": {} } ] } ] }, { "category": "block", "type": "paragraph", "content": [ { "category": "text", "type": "text", "value": "", "marks": [ { "object": "mark", "type": "bold", "data": {} } ] } ] }, { "category": "block", "type": "paragraph", "content": [ { "category": "text", "type": "text", "value": "and this is a bold text", "marks": [ { "object": "mark", "type": "bold", "data": {} } ] }, { "category": "text", "type": "text", "value": " but now i am not bold anymore. ", "marks": [] }, { "category": "text", "type": "text", "value": "However, ", "marks": [ { "object": "mark", "type": "italic", "data": {} } ] }, { "category": "text", "type": "text", "value": " i am now ", "marks": [] }, { "category": "text", "type": "text", "value": "underlined with shit. ", "marks": [ { "object": "mark", "type": "underlined", "data": {} } ] } ] } ] } ``` ================================================ FILE: packages/contentful-slatejs-adapter/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/contentful-slatejs-adapter/package.json ================================================ { "name": "@contentful/contentful-slatejs-adapter", "version": "16.2.1", "description": "", "keywords": [], "main": "dist/contentful-slatejs-adapter.es5.js", "module": "dist/contentful-slatejs-adapter.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/contentful-slatejs-adapter.esm.js", "require": "./dist/contentful-slatejs-adapter.es5.js", "default": "./dist/contentful-slatejs-adapter.es5.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "author": "Contentful GmbH", "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=6.0.0" }, "scripts": { "prebuild": "rimraf dist", "build": "tsc && rollup -c --bundleConfigAsCjs rollup.config.ts", "start": "rollup -c --bundleConfigAsCjs rollup.config.ts -w", "test": "jest", "test:watch": "jest --watch", "test:prod": "yarn test -- --coverage --no-cache", "report-coverage": "cat ./coverage/lcov.info | coveralls" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7" }, "devDependencies": { "@faker-js/faker": "^10.0.0", "@types/jest": "^30.0.0", "@types/node": "^25.0.2", "colors": "^1.1.2", "coveralls": "^3.0.0", "cross-env": "^10.0.0", "cz-conventional-changelog": "^3.3.0", "lodash.camelcase": "^4.3.0", "prompt": "^1.0.0", "ts-jest": "^29.1.2" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" } } ================================================ FILE: packages/contentful-slatejs-adapter/rollup.config.ts ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; export default config(outputFile, { external: Object.keys(dependencies), }); ================================================ FILE: packages/contentful-slatejs-adapter/src/__test__/contentful-helpers.ts ================================================ import { BLOCKS, INLINES, Document, Block, Inline, Text, Mark, TopLevelBlock, TopLevelBlockEnum, } from '@contentful/rich-text-types'; export interface NodeProps { isVoid?: boolean; data?: Record; } export function document(...content: TopLevelBlock[]): Document { return { data: {}, nodeType: BLOCKS.DOCUMENT, content, }; } export function block( nodeType: TopLevelBlockEnum, ...content: Array ): TopLevelBlock; export function block(nodeType: BLOCKS, ...content: Array): Block; export function block(nodeType: BLOCKS, ...content: Array): Block { return { nodeType, content, data: {}, }; } export function inline(nodeType: INLINES, ...content: Array): Inline { return { nodeType, content, data: {}, }; } export function text(value: string, ...marks: Mark[]): Text { return { nodeType: 'text', data: {}, marks, value: value, }; } export function mark(type: string): Mark { return { type, }; } ================================================ FILE: packages/contentful-slatejs-adapter/src/__test__/contentful-to-slatejs-adapter.test.ts ================================================ import * as Contentful from '@contentful/rich-text-types'; import toSlatejsDocument from '../contentful-to-slatejs-adapter'; import toContentfulDocument from '../slatejs-to-contentful-adapter'; import { SlateNode } from '../types'; import * as contentful from './contentful-helpers'; const schema = { blocks: { [Contentful.BLOCKS.EMBEDDED_ENTRY]: { isVoid: true } } }; describe('both adapters (roundtrippable cases)', () => { const testAdapters = ( message: string, contentfulDoc: Contentful.Document, slateDoc: SlateNode[], ) => { describe('toSlatejsDocument()', () => { it(message, () => { const actualSlateDoc = toSlatejsDocument({ document: contentfulDoc, schema, }); expect(actualSlateDoc).toEqual(slateDoc); }); it('converts Contentful mentions to Slate mentions', () => { const contentfulInput = { content: [ { content: [ { data: {}, marks: [], nodeType: 'text', value: 'Hello ' }, { content: [{ data: {}, marks: [], nodeType: 'text', value: '' }], data: { target: { sys: { id: 'user-id-0', linkType: 'User', type: 'Link' } } }, nodeType: 'mention', }, { data: {}, marks: [], nodeType: 'text', value: '' }, ], data: {}, nodeType: 'paragraph', }, ], data: {}, nodeType: 'document', }; const slateOutput = toSlatejsDocument({ document: contentfulInput as unknown as Contentful.Document, }); const expectedSlateOutput = [ { type: 'paragraph', isVoid: false, data: {}, children: [ { data: {}, text: 'Hello ', }, { type: 'mention', isVoid: false, data: { target: { sys: { type: 'Link', linkType: 'User', id: 'user-id-0', }, }, }, children: [ { data: {}, text: '', }, ], }, { data: {}, text: '', }, ], }, ]; expect(slateOutput).toStrictEqual(expectedSlateOutput); }); }); describe('toContentfulDocument()', () => { it(message, () => { const actualContentfulDoc = toContentfulDocument({ document: slateDoc, schema, }); expect(actualContentfulDoc).toEqual(contentfulDoc); }); it('is converts Slate mentions to Contentful mentions', () => { const slateFormatWithMention = [ { type: 'paragraph', data: {}, children: [ { text: 'Hello ', }, { type: 'mention', data: { target: { sys: { type: 'Link', linkType: 'User', id: 'user-id-0', }, }, }, children: [ { text: '', }, ], }, { text: '', }, ], }, ]; const resultContentfulDoc = toContentfulDocument({ document: slateFormatWithMention as unknown as SlateNode[], schema, }); const expectedContentfulDoc = { content: [ { content: [ { data: {}, marks: [], nodeType: 'text', value: 'Hello ' }, { content: [{ data: {}, marks: [], nodeType: 'text', value: '' }], data: { target: { sys: { id: 'user-id-0', linkType: 'User', type: 'Link' } } }, nodeType: 'mention', }, { data: {}, marks: [], nodeType: 'text', value: '' }, ], data: {}, nodeType: 'paragraph', }, ], data: {}, nodeType: 'document', }; expect(resultContentfulDoc).toEqual(expectedContentfulDoc); }); it('converts text-only nodes', () => { const slateText = [ { type: 'paragraph', data: {}, children: [ { text: 'Hello ', }, ], }, ]; const convertedToContentful = toContentfulDocument({ document: slateText as unknown as SlateNode[], }); const expectedContentfulDoc = { content: [ { content: [{ data: {}, marks: [], nodeType: 'text', value: 'Hello ' }], data: {}, nodeType: 'paragraph', }, ], data: {}, nodeType: 'document', }; expect(convertedToContentful).toStrictEqual(expectedContentfulDoc); }); }); }; describe('document', () => { testAdapters('empty document', contentful.document(), []); testAdapters( 'document with block', contentful.document(contentful.block(Contentful.BLOCKS.PARAGRAPH, contentful.text(''))), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [ { text: '', data: {}, }, ], }, ], ); testAdapters( 'paragraph with inline', contentful.document( contentful.block( Contentful.BLOCKS.PARAGRAPH, contentful.inline(Contentful.INLINES.HYPERLINK), ), ), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [ { type: Contentful.INLINES.HYPERLINK, data: {}, isVoid: false, children: [], }, ], }, ], ); testAdapters( 'paragraph with text', contentful.document(contentful.block(Contentful.BLOCKS.PARAGRAPH, contentful.text('hi'))), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [{ text: 'hi', data: {} }], }, ], ); testAdapters( 'text with marks', contentful.document( contentful.block( Contentful.BLOCKS.PARAGRAPH, contentful.text('this'), contentful.text('is', contentful.mark('bold')), ), ), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [ { text: 'this', data: {} }, { text: 'is', data: {}, bold: true }, ], }, ], ); it('adds a default value to marks if undefined', () => { const slateDoc = [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [{ text: 'Hi', data: {} }], }, ]; const ctflDoc = toContentfulDocument({ document: slateDoc, }); expect(ctflDoc).toEqual( contentful.document( contentful.block(Contentful.BLOCKS.PARAGRAPH, { nodeType: 'text', marks: [], data: {}, value: 'Hi', }), ), ); }); testAdapters( 'text with multiple marks', contentful.document( contentful.block( Contentful.BLOCKS.PARAGRAPH, contentful.text('this'), contentful.text('is', contentful.mark('bold')), contentful.text('huge', contentful.mark('bold'), contentful.mark('italic')), ), ), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [ { text: 'this', data: {} }, { text: 'is', data: {}, bold: true }, { text: 'huge', data: {}, bold: true, italic: true }, ], }, ], ); testAdapters( 'document with nested blocks', contentful.document( contentful.block( Contentful.BLOCKS.PARAGRAPH, contentful.text('this is a test', contentful.mark('bold')), contentful.text('paragraph', contentful.mark('underline')), ), contentful.block( Contentful.BLOCKS.QUOTE, contentful.block(Contentful.BLOCKS.PARAGRAPH, contentful.text('this is it')), ), ), [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [ { text: 'this is a test', data: {}, bold: true }, { text: 'paragraph', data: {}, underline: true }, ], }, { type: Contentful.BLOCKS.QUOTE, data: {}, isVoid: false, children: [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [{ text: 'this is it', data: {} }], }, ], }, ], ); }); describe('converts additional data', () => { testAdapters( 'data in block', { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.PARAGRAPH, content: [ { nodeType: 'text', marks: [], data: {}, value: '', }, ], data: { a: 1 }, }, ], }, [ { type: Contentful.BLOCKS.PARAGRAPH, data: { a: 1 }, isVoid: false, children: [{ text: '', data: {} }], }, ], ); testAdapters( 'data in inline', { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.PARAGRAPH, data: { a: 1 }, content: [ { nodeType: Contentful.INLINES.HYPERLINK, data: { a: 2 }, content: [], }, ], }, ], }, [ { type: Contentful.BLOCKS.PARAGRAPH, data: { a: 1 }, isVoid: false, children: [ { type: Contentful.INLINES.HYPERLINK, data: { a: 2 }, isVoid: false, children: [], }, ], }, ], ); testAdapters( 'data in text', { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.PARAGRAPH, data: { a: 1 }, content: [ { nodeType: Contentful.INLINES.HYPERLINK, data: { a: 2 }, content: [ { nodeType: 'text', marks: [], data: { a: 3 }, value: 'YO', }, ], }, ], }, ], }, [ { type: Contentful.BLOCKS.PARAGRAPH, data: { a: 1 }, isVoid: false, children: [ { type: Contentful.INLINES.HYPERLINK, data: { a: 2 }, isVoid: false, children: [ { text: 'YO', data: { a: 3 }, }, ], }, ], }, ], ); }); describe('sets isVoid from schema', () => { testAdapters( 'data in block', { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.EMBEDDED_ENTRY, content: [], data: { a: 1 }, }, ], }, [ { type: Contentful.BLOCKS.EMBEDDED_ENTRY, data: { a: 1 }, isVoid: true, children: [], }, ], ); test('removes empty text nodes from void nodes content', () => { const contentfulDoc: Contentful.Document = { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.EMBEDDED_ENTRY, content: [], data: { a: 1 }, }, ], }; const slateDoc = [ { type: Contentful.BLOCKS.EMBEDDED_ENTRY, data: { a: 1 }, isVoid: true, children: [{ text: '', data: {} }], }, ]; const actualContentfulDoc = toContentfulDocument({ document: slateDoc, schema, }); expect(actualContentfulDoc).toEqual(contentfulDoc); }); }); }); describe('toSlatejsDocument() adapter (non-roundtrippable cases)', () => { // `content` for any TEXT_CONTAINER contentful node could be empty according to our // validation rules, but SlateJS could crash if there isn't a text leaf. it('inserts empty text nodes into text container blocks with empty `content`', () => { const cfDoc = { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.PARAGRAPH, content: [] as any, data: {}, }, { nodeType: Contentful.BLOCKS.HEADING_1, content: [] as any, data: {}, }, { nodeType: Contentful.BLOCKS.HEADING_6, content: [] as any, data: { a: 42 }, }, ], } as Contentful.Document; const expectedSlateDoc = [ { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [{ text: '', data: {} }], }, { type: Contentful.BLOCKS.HEADING_1, data: {}, isVoid: false, children: [{ text: '', data: {} }], }, { type: Contentful.BLOCKS.HEADING_6, data: { a: 42 }, isVoid: false, children: [{ text: '', data: {} }], }, ]; const actualSlateDoc = toSlatejsDocument({ document: cfDoc, schema, }); expect(actualSlateDoc).toEqual(expectedSlateDoc); }); }); describe('toContentfulDocument()}; adapter (non-roundtrippable cases)', () => { it('neither inserts nor removes empty text nodes on container blocks with empty `children`', () => { const slateDoc = [ { type: Contentful.BLOCKS.HEADING_1, data: {}, isVoid: false, children: [{ text: '', data: {} }], }, { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [{ text: '', data: {} }], }, { type: Contentful.BLOCKS.PARAGRAPH, data: {}, isVoid: false, children: [], }, { type: Contentful.BLOCKS.HEADING_2, data: {}, isVoid: false, children: [], }, ]; const expectedCfDoc = { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: Contentful.BLOCKS.HEADING_1, content: [ { nodeType: 'text', marks: [] as any, data: {}, value: '', }, ], data: {}, }, { nodeType: Contentful.BLOCKS.PARAGRAPH, content: [ { nodeType: 'text', marks: [], data: {}, value: '', }, ], data: {}, }, { nodeType: Contentful.BLOCKS.PARAGRAPH, content: [ { nodeType: 'text', marks: [], data: {}, value: '', }, ], data: {}, }, { nodeType: Contentful.BLOCKS.HEADING_2, content: [ { nodeType: 'text', marks: [], data: {}, value: '', }, ], data: {}, }, ], }; const actualCfDoc = toContentfulDocument({ document: slateDoc, schema, }); expect(actualCfDoc).toEqual(expectedCfDoc); }); }); ================================================ FILE: packages/contentful-slatejs-adapter/src/contentful-to-slatejs-adapter.ts ================================================ import * as Contentful from '@contentful/rich-text-types'; import { getDataOrDefault } from './helpers'; import { fromJSON, Schema, SchemaJSON } from './schema'; import { ContentfulElementNode, ContentfulNode, SlateElement, SlateMarks, SlateNode, SlateText, } from './types'; export interface ToSlatejsDocumentProperties { document: Contentful.Document; schema?: SchemaJSON; } export default function toSlatejsDocument({ document, schema, }: ToSlatejsDocumentProperties): SlateNode[] { // TODO: // We allow adding data to the root document node, but Slate >v0.5.0 // has no concept of a root document node. We should determine whether // this will be a compatibility problem for existing users. return document.content.flatMap((node) => convertNode(node, fromJSON(schema))); } function convertNode(node: ContentfulNode, schema: Schema): SlateNode { if (node.nodeType === 'text') { return convertTextNode(node as Contentful.Text); } else { const contentfulNode = node as ContentfulElementNode; const childNodes = contentfulNode.content.flatMap((childNode) => convertNode(childNode, schema), ); const slateNode = convertElementNode(contentfulNode, childNodes, schema); return slateNode; } } function convertElementNode( contentfulBlock: ContentfulElementNode, slateChildren: SlateNode[], schema: Schema, ): SlateElement { const children = slateChildren.length === 0 && schema.isTextContainer(contentfulBlock.nodeType) ? [{ text: '', data: {} }] : slateChildren; return { type: contentfulBlock.nodeType, children, isVoid: schema.isVoid(contentfulBlock), data: getDataOrDefault(contentfulBlock.data), }; } function convertTextNode(node: Contentful.Text): SlateText { return { text: node.value, data: getDataOrDefault(node.data), ...convertTextMarks(node), }; } function convertTextMarks(node: Contentful.Text): SlateMarks { const marks: SlateMarks = {}; for (const mark of node.marks) { marks[mark.type as keyof SlateMarks] = true; } return marks; } ================================================ FILE: packages/contentful-slatejs-adapter/src/helpers.ts ================================================ /** * Ensures that data defaults to an empty object. */ export const getDataOrDefault = (value?: Record) => value || {}; ================================================ FILE: packages/contentful-slatejs-adapter/src/index.ts ================================================ export { default as toSlatejsDocument } from './contentful-to-slatejs-adapter'; export { default as toContentfulDocument } from './slatejs-to-contentful-adapter'; ================================================ FILE: packages/contentful-slatejs-adapter/src/schema.ts ================================================ import { BLOCKS, TEXT_CONTAINERS } from '@contentful/rich-text-types'; import { ContentfulElementNode } from './types'; const defaultSchema: SchemaJSON = {}; // TODO: Get rid of outdated SlateJS schema concept here and instead construct // a `Schema` object based on `rich-text-types` constants. The original idea // was to decouple code from these constants for future extensibility cases // where we had to deal with custom node types that wouldn't be part of these // constants while a custom (forked) rich-text editor provided `Schema` // instance would be aware of them. /** * SlateJS Schema definition v0.33.x * * @export * @interface SchemaJSON */ export interface SchemaJSON { blocks?: Record; inlines?: Record; } // TODO: No need to extend `SchemaJSON` and change `isVoid` to take a `nodeType: string` export interface Schema extends SchemaJSON { isVoid(node: ContentfulElementNode): boolean; isTextContainer(nodeType: string): boolean; } export interface SchemaValue { isVoid?: boolean; [k: string]: any; } /** * Creates an instance of Schema from json. * * @export * @param {SchemaJSON} [schema=defaultSchema] * @returns {Schema} */ export function fromJSON(schema: SchemaJSON = defaultSchema): Schema { return { /** * Check if a `node` is void based on the schema rules. * * @param {ContentfulElementNode} node * @returns */ isVoid(node: ContentfulElementNode) { const root = Object.values(BLOCKS).includes(node.nodeType as any) ? 'blocks' : 'inlines'; return schema?.[root]?.[node.nodeType]?.['isVoid'] ?? false; }, isTextContainer(nodeType: string) { return TEXT_CONTAINERS.includes(nodeType as any); }, }; } ================================================ FILE: packages/contentful-slatejs-adapter/src/slatejs-to-contentful-adapter.ts ================================================ import * as Contentful from '@contentful/rich-text-types'; import { getDataOrDefault } from './helpers'; import { Schema, SchemaJSON, fromJSON } from './schema'; import { ContentfulElementNode, ContentfulNode, SlateElement, SlateMarks, SlateNode, SlateText, } from './types'; export interface ToContentfulDocumentProperties { document: SlateNode[]; schema?: SchemaJSON; } export default function toContentfulDocument({ document, schema, }: ToContentfulDocumentProperties): Contentful.Document { // TODO: // We allow adding data to the root document node, but Slate >v0.5.0 // has no concept of a root document node. We should determine whether // this will be a compatibility problem for existing users. return { nodeType: Contentful.BLOCKS.DOCUMENT, data: {}, content: document.flatMap( (node) => convertNode(node, fromJSON(schema)) as Contentful.TopLevelBlock[], ), }; } function convertNode(node: SlateNode, schema: Schema): ContentfulNode[] { const nodes: ContentfulNode[] = []; if (isSlateElement(node)) { const contentfulElement: ContentfulElementNode = { nodeType: node.type as Contentful.BLOCKS, data: getDataOrDefault(node.data), content: [], }; if (!schema.isVoid(contentfulElement)) { contentfulElement.content = node.children.flatMap((childNode) => convertNode(childNode, schema), ); } if (contentfulElement.content.length === 0 && schema.isTextContainer(node.type)) { contentfulElement.content.push(convertText({ text: '', data: {} })); } nodes.push(contentfulElement); } else { const contentfulText = convertText(node); nodes.push(contentfulText); } return nodes; } function convertText(node: SlateText): Contentful.Text { const { text, data, ...marks } = node; return { nodeType: 'text', value: text, marks: getMarkList(marks), data: getDataOrDefault(data), }; } function getMarkList(marks: SlateMarks): Contentful.Mark[] { const contentfulMarks: Contentful.Mark[] = []; for (const mark of Object.keys(marks)) { contentfulMarks.push({ type: mark }); } return contentfulMarks; } function isSlateElement(node: SlateNode): node is SlateElement { return 'type' in node; } ================================================ FILE: packages/contentful-slatejs-adapter/src/types/index.ts ================================================ import * as Contentful from '@contentful/rich-text-types'; export type SlateMarks = { // This is a workaround for TypeScript's limitations around // index property exclusion. Ideally we'd join the above properties // with something like // // & { [mark: string]: string } // // but TypeScript doesn't allow us to create such objects, only // work around inconsistencies in existing JavaScript. // // In reality Slate's node marks are arbitrary, but for this library // denoting marks used by the tests as optional should be okay. bold?: boolean; italic?: boolean; underline?: boolean; }; export type SlateText = SlateMarks & { text: string; data: object; }; export type SlateElement = { type: string; data: object; isVoid: boolean; children: SlateNode[]; }; export type ContentfulElementNode = Contentful.Block | Contentful.Inline; export type ContentfulNode = ContentfulElementNode | Contentful.Text; export type SlateNode = SlateElement | SlateText; ================================================ FILE: packages/contentful-slatejs-adapter/src/types/slate.ts ================================================ /* eslint-disable */ namespace Slate { // fixes "Duplicate identifier" when generating tests coverage export type NodeObject = 'document' | 'block' | 'inline' | 'text'; export interface Node { object: NodeObject; type?: string; data?: object; isVoid?: boolean; } export interface Document extends Node { object: 'document'; nodes: Block[]; } export interface Block extends Node { object: 'block'; nodes: Array; } export interface Inline extends Node { object: 'inline'; nodes: Array; } export interface Text extends Node { object: 'text'; leaves: TextLeaf[]; } export interface Mark { type: string; data: Record; object: 'mark'; } export interface TextLeaf { object: 'leaf'; text: string; marks?: Mark[]; } } ================================================ FILE: packages/contentful-slatejs-adapter/tsconfig.json ================================================ { "compilerOptions": { "moduleResolution": "node", "target": "es5", "module": "es2015", "lib": ["es2015", "es2016", "es2017", "ES2019"], "strict": true, "sourceMap": true, "declaration": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "declarationDir": "dist/types", "resolveJsonModule": true, "outDir": "dist/lib", "skipLibCheck": true, "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/typings"], "types": ["jest"] }, "include": ["src"] } ================================================ FILE: packages/rich-text-from-markdown/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [16.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.2.0...@contentful/rich-text-from-markdown@16.2.1) (2026-04-09) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.8...@contentful/rich-text-from-markdown@16.2.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [16.1.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.7...@contentful/rich-text-from-markdown@16.1.8) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.6...@contentful/rich-text-from-markdown@16.1.7) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.5...@contentful/rich-text-from-markdown@16.1.6) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.4...@contentful/rich-text-from-markdown@16.1.5) (2025-09-10) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.3...@contentful/rich-text-from-markdown@16.1.4) (2025-09-09) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.2...@contentful/rich-text-from-markdown@16.1.3) (2025-09-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.1...@contentful/rich-text-from-markdown@16.1.2) (2025-08-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.1.0...@contentful/rich-text-from-markdown@16.1.1) (2025-07-15) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.0.1...@contentful/rich-text-from-markdown@16.1.0) (2025-07-02) ### Features - **markdown:** transform strikethrough to rich-text mark ([#883](https://github.com/contentful/rich-text/issues/883)) ([ac51579](https://github.com/contentful/rich-text/commit/ac51579f5e716bb15862796177a924c7e838edb6)) ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@16.0.0...@contentful/rich-text-from-markdown@16.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [16.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.10...@contentful/rich-text-from-markdown@16.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [15.18.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.9...@contentful/rich-text-from-markdown@15.18.10) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.8...@contentful/rich-text-from-markdown@15.18.9) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.7...@contentful/rich-text-from-markdown@15.18.8) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.6...@contentful/rich-text-from-markdown@15.18.7) (2024-07-24) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.5...@contentful/rich-text-from-markdown@15.18.6) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.4...@contentful/rich-text-from-markdown@15.18.5) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.3...@contentful/rich-text-from-markdown@15.18.4) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.2...@contentful/rich-text-from-markdown@15.18.3) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.1...@contentful/rich-text-from-markdown@15.18.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.18.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.18.0...@contentful/rich-text-from-markdown@15.18.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.18.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.5...@contentful/rich-text-from-markdown@15.18.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [15.17.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.4...@contentful/rich-text-from-markdown@15.17.5) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.17.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.3...@contentful/rich-text-from-markdown@15.17.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.17.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.2...@contentful/rich-text-from-markdown@15.17.3) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.17.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.1...@contentful/rich-text-from-markdown@15.17.2) (2024-05-27) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.17.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.17.0...@contentful/rich-text-from-markdown@15.17.1) (2024-05-24) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.17.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.15...@contentful/rich-text-from-markdown@15.17.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [15.16.15](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.14...@contentful/rich-text-from-markdown@15.16.15) (2024-03-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.14](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.13...@contentful/rich-text-from-markdown@15.16.14) (2024-03-01) ### Bug Fixes - support
from markdown ([#536](https://github.com/contentful/rich-text/issues/536)) ([30a3668](https://github.com/contentful/rich-text/commit/30a36685f42c70c98ae4bfb514ba710812ec0824)) ## [15.16.13](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.12...@contentful/rich-text-from-markdown@15.16.13) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.12](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.11...@contentful/rich-text-from-markdown@15.16.12) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.11](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.10...@contentful/rich-text-from-markdown@15.16.11) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.9...@contentful/rich-text-from-markdown@15.16.10) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.8...@contentful/rich-text-from-markdown@15.16.9) (2024-01-22) ### Bug Fixes - [] Wrap inline nodes inside of a table cell in paragraph node ([#520](https://github.com/contentful/rich-text/issues/520)) ([6290f7d](https://github.com/contentful/rich-text/commit/6290f7dcae96c279d97e88ad65c42a080778fe73)) ## [15.16.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.7...@contentful/rich-text-from-markdown@15.16.8) (2023-09-12) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.6...@contentful/rich-text-from-markdown@15.16.7) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.5...@contentful/rich-text-from-markdown@15.16.6) (2023-05-26) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.4...@contentful/rich-text-from-markdown@15.16.5) (2023-05-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.3...@contentful/rich-text-from-markdown@15.16.4) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.2...@contentful/rich-text-from-markdown@15.16.3) (2023-02-09) ### Bug Fixes - update remark-parse to v9 to fix security issue of trim@0.0.1 ([#444](https://github.com/contentful/rich-text/issues/444)) ([f961a89](https://github.com/contentful/rich-text/commit/f961a8927d911b2653883601b3cfdcd1b255a60b)) ## [15.16.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.1...@contentful/rich-text-from-markdown@15.16.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.16.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-from-markdown@15.16.0...@contentful/rich-text-from-markdown@15.16.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-from-markdown # 15.16.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## 15.13.1 (2022-05-10) ### Bug Fixes - **markdown:** handle empty table cells ([#329](https://github.com/contentful/rich-text/issues/329)) ([55a0a16](https://github.com/contentful/rich-text/commit/55a0a16a12700fefbe7e9727a7172043fd126fc5)) # 15.13.0 (2022-05-06) ### Features - support converting tables from markdown to rich-text ([0abc0c6](https://github.com/contentful/rich-text/commit/0abc0c60b7e3e2683ebbb427b44847e6242f6e5e)) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ## 15.11.2 (2022-02-07) ### Bug Fixes - set "typings" for rich-text-from-markdown ([1b3a15f](https://github.com/contentful/rich-text/commit/1b3a15f1cb15eacb6d1b15f2b79c5747e2d25618)) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ## 15.10.1 (2021-12-21) ### Bug Fixes - remove support to convert tables from md to rich text ([a9d513c](https://github.com/contentful/rich-text/commit/a9d513c285e8cdedd384b89e195734dd3a8d2136)) # 15.10.0 (2021-12-15) ## 15.9.1 (2021-12-10) # 15.9.0 (2021-12-09) # 15.7.0 (2021-11-11) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ### Features - add support to convert tables from md to rich text ([#284](https://github.com/contentful/rich-text/issues/284)) ([213a29c](https://github.com/contentful/rich-text/commit/213a29c78d48b3e63088999c4eed4891906d1719)) ## 15.5.1 (2021-10-25) # 15.5.0 (2021-10-25) ## 15.3.6 (2021-09-15) ## 15.3.5 (2021-09-13) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.1.0 (2021-08-02) # 15.0.0 (2021-06-15) ## 14.1.2 (2020-11-02) ## 14.0.1 (2020-01-30) # 14.0.0 (2020-01-28) # 13.4.0 (2019-08-01) # 13.1.0 (2019-03-04) ## 13.0.1 (2019-02-11) # 13.0.0 (2019-01-22) # 12.2.0 (2018-12-20) ### Bug Fixes - fix import path ([3f3edff](https://github.com/contentful/rich-text/commit/3f3edff8bc4f4463a701d0ae1d397fcf9acec325)) - ignore unsupported marks ([1757331](https://github.com/contentful/rich-text/commit/1757331a74f360353556a242a9fe20da131b9a59)) ### Features - 🎸 parse links inside marks ([11722cf](https://github.com/contentful/rich-text/commit/11722cfad85d5e15b10d2b18d3c831ec613922c3)) ## 12.1.2 (2018-12-14) ## 12.1.1 (2018-12-12) ### Bug Fixes - handle hyperlinks ([54508d4](https://github.com/contentful/rich-text/commit/54508d495adf5055e4089f54dd62f00f8be6be46)) # 12.1.0 (2018-12-12) ### Bug Fixes - **async-handling:** move to promise-based api ([a07d3fc](https://github.com/contentful/rich-text/commit/a07d3fcbd99a2d80f86bc6dcf5cba01665221ebc)) ## 12.0.1 (2018-12-04) ### Bug Fixes - **from-markdown:** Fix list typos ([c392016](https://github.com/contentful/rich-text/commit/c392016e2f11628021cd27bff3447f548398c3b4)) - **parsing:** list item, (un)ordered list, links, quotes ([8a0c580](https://github.com/contentful/rich-text/commit/8a0c580306c154f1c1d6c2ba964c46d18881be12)) - **text:** Parse text nodes with marks correctly ([d489f90](https://github.com/contentful/rich-text/commit/d489f904a33726f42006b09871d15bcfbdd7e274)) # 12.0.0 (2018-11-29) # 11.0.0 (2018-11-27) # 10.3.0 (2018-11-26) # 10.2.0 (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # 10.1.0 (2018-11-16) ### Bug Fixes - 🐛 removes obsolete fields from mark nodes ([b638a56](https://github.com/contentful/rich-text/commit/b638a56652520969d3ac898ac158be81a9788f67)) ### Features - 🎸 introduces top-level-block type ([a6bf35e](https://github.com/contentful/rich-text/commit/a6bf35e7c9ca35915a512de774b3a3fdc4c76e5d)) ## 10.0.4 (2018-11-09) ## 10.0.3 (2018-11-09) ## 10.0.2 (2018-11-09) ## 10.0.1 (2018-11-08) ### Features - **packages:** Add rich text from markdown package ([bc8ec41](https://github.com/contentful/rich-text/commit/bc8ec41f5615eabcc29031ee99da3f9c70b414b3)) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## [15.13.1](https://github.com/contentful/rich-text/compare/v15.13.0...v15.13.1) (2022-05-10) ### Bug Fixes - **markdown:** handle empty table cells ([#329](https://github.com/contentful/rich-text/issues/329)) ([55a0a16](https://github.com/contentful/rich-text/commit/55a0a16a12700fefbe7e9727a7172043fd126fc5)) # [15.13.0](https://github.com/contentful/rich-text/compare/v15.12.1...v15.13.0) (2022-05-06) ### Features - support converting tables from markdown to rich-text ([0abc0c6](https://github.com/contentful/rich-text/commit/0abc0c60b7e3e2683ebbb427b44847e6242f6e5e)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.11.2](https://github.com/contentful/rich-text/compare/v15.11.1...v15.11.2) (2022-02-07) ### Bug Fixes - set "typings" for rich-text-from-markdown ([1b3a15f](https://github.com/contentful/rich-text/commit/1b3a15f1cb15eacb6d1b15f2b79c5747e2d25618)) ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) ### Bug Fixes - remove support to convert tables from md to rich text ([a9d513c](https://github.com/contentful/rich-text/commit/a9d513c285e8cdedd384b89e195734dd3a8d2136)) # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) ### Features - add support to convert tables from md to rich text ([#284](https://github.com/contentful/rich-text/issues/284)) ([213a29c](https://github.com/contentful/rich-text/commit/213a29c78d48b3e63088999c4eed4891906d1719)) ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-from-markdown ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/rich-text-from-markdown # [15.2.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.1.0) (2021-09-06) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) ## [13.0.1](https://github.com/contentful/rich-text/compare/v13.0.0...v13.0.1) (2019-02-11) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) # [12.2.0](https://github.com/contentful/rich-text/compare/v12.1.2...v12.2.0) (2018-12-20) ### Bug Fixes - fix import path ([3f3edff](https://github.com/contentful/rich-text/commit/3f3edff8bc4f4463a701d0ae1d397fcf9acec325)) - ignore unsupported marks ([1757331](https://github.com/contentful/rich-text/commit/1757331a74f360353556a242a9fe20da131b9a59)) ### Features - 🎸 parse links inside marks ([11722cf](https://github.com/contentful/rich-text/commit/11722cfad85d5e15b10d2b18d3c831ec613922c3)) ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ## [12.1.1](https://github.com/contentful/rich-text/compare/v12.1.0...v12.1.1) (2018-12-12) ### Bug Fixes - handle hyperlinks ([54508d4](https://github.com/contentful/rich-text/commit/54508d495adf5055e4089f54dd62f00f8be6be46)) # [12.1.0](https://github.com/contentful/rich-text/compare/v12.0.4...v12.1.0) (2018-12-12) ### Bug Fixes - **async-handling:** move to promise-based api ([a07d3fc](https://github.com/contentful/rich-text/commit/a07d3fcbd99a2d80f86bc6dcf5cba01665221ebc)) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) ### Bug Fixes - **from-markdown:** Fix list typos ([c392016](https://github.com/contentful/rich-text/commit/c392016e2f11628021cd27bff3447f548398c3b4)) - **parsing:** list item, (un)ordered list, links, quotes ([8a0c580](https://github.com/contentful/rich-text/commit/8a0c580306c154f1c1d6c2ba964c46d18881be12)) - **text:** Parse text nodes with marks correctly ([d489f90](https://github.com/contentful/rich-text/commit/d489f904a33726f42006b09871d15bcfbdd7e274)) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # [10.1.0](https://github.com/contentful/rich-text/compare/v10.0.5...v10.1.0) (2018-11-16) ### Bug Fixes - 🐛 removes obsolete fields from mark nodes ([b638a56](https://github.com/contentful/rich-text/commit/b638a56652520969d3ac898ac158be81a9788f67)) ### Features - 🎸 introduces top-level-block type ([a6bf35e](https://github.com/contentful/rich-text/commit/a6bf35e7c9ca35915a512de774b3a3fdc4c76e5d)) ## [10.0.4](https://github.com/contentful/rich-text/compare/v10.0.3...v10.0.4) (2018-11-09) ## [10.0.3](https://github.com/contentful/rich-text/compare/v10.0.2...v10.0.3) (2018-11-09) ## [10.0.2](https://github.com/contentful/rich-text/compare/v10.0.1...v10.0.2) (2018-11-09) ## [10.0.1](https://github.com/contentful/rich-text/compare/v10.0.0...v10.0.1) (2018-11-08) ### Features - **packages:** Add rich text from markdown package ([bc8ec41](https://github.com/contentful/rich-text/commit/bc8ec41f5615eabcc29031ee99da3f9c70b414b3)) ================================================ FILE: packages/rich-text-from-markdown/README.md ================================================ # rich-text-from-markdown A library to convert markdown to Contentful Rich Text document format. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/rich-text-from-markdown ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/rich-text-from-markdown ``` ## Usage ### Basic ```js const { richTextFromMarkdown } = require('@contentful/rich-text-from-markdown'); const document = await richTextFromMarkdown('# Hello World'); ``` ### Advanced The library will convert automatically the following markdown nodes: - `paragraph` - `heading` - `text` - `emphasis` - `strong` - `delete` - `inlineCode` - `link` - `thematicBreak` - `blockquote` - `list` - `listItem` - `table` - `tableRow` - `tableCell` If the markdown content has unsupported nodes like image `![image](url)` you can add a callback as a second argument and it will get called everytime an unsupported node is found. The return value of the callback will be the rich text representation of that node. #### Example: ```js const { richTextFromMarkdown } = require('@contentful/rich-text-from-markdown'); // define your own type for unsupported nodes like asset const document = await richTextFromMarkdown( "![image]('https://example.com/image.jpg')", (node) => ({ nodeType: 'embedded-[entry|asset]-[block|inline]', content: [], data: { target: { sys: { type: 'Link', linkType: 'Entry|Asset', id: '.........', }, }, }, }), ); ``` ================================================ FILE: packages/rich-text-from-markdown/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-from-markdown/package.json ================================================ { "name": "@contentful/rich-text-from-markdown", "version": "16.2.1", "description": "convert markdown to rich text", "keywords": [ "rich-text", "contentful", "markdown" ], "author": "Khaled Garbaya ", "homepage": "https://github.com/contentful/rich-text#readme", "license": "MIT", "main": "dist/rich-text-from-markdown.es5.js", "module": "dist/rich-text-from-markdown.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/rich-text-from-markdown.esm.js", "require": "./dist/rich-text-from-markdown.es5.js", "default": "./dist/rich-text-from-markdown.es5.js" }, "./package.json": "./package.json" }, "directories": { "src": "src", "test": "__tests__" }, "files": [ "dist" ], "repository": { "type": "git", "url": "git+https://github.com/contentful/rich-text.git" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "prebuild": "rimraf dist", "build": "tsc --module commonjs && rollup -c --bundleConfigAsCjs rollup.config.js", "start": "tsc && rollup -c --bundleConfigAsCjs rollup.config.js -w", "test": "jest" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7", "lodash": "^4.17.11", "remark-gfm": "^1.0.0", "remark-parse": "^9.0.0", "unified": "^9.0.0" }, "devDependencies": { "@faker-js/faker": "^10.0.0", "@types/lodash": "^4.14.172", "ts-jest": "^29.1.2" }, "bugs": { "url": "https://github.com/contentful/rich-text/issues" } } ================================================ FILE: packages/rich-text-from-markdown/rollup.config.js ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; export default config(outputFile, { external: Object.keys(dependencies), }); ================================================ FILE: packages/rich-text-from-markdown/src/__test__/helpers.ts ================================================ import { TopLevelBlock, Document, BLOCKS, Block, Inline, Text, Mark, } from '@contentful/rich-text-types'; export interface NodeProps { data?: Record; } const defaultProps: NodeProps = { data: {} }; export function document(props: NodeProps = defaultProps, ...content: TopLevelBlock[]): Document { return { nodeType: BLOCKS.DOCUMENT, data: {}, content, ...props, }; } export function block( nodeType: BLOCKS, props: NodeProps = defaultProps, ...content: Array ): T { return { nodeType, content, data: {}, ...props, } as T; } export function inline( nodeType: string, props: NodeProps = defaultProps, ...content: Array ): Inline { return { nodeType, data: {}, content, ...props, } as Inline; } export function text(value: string, ...marks: Mark[]): Text { return { nodeType: 'text', data: {}, marks, value: value, }; } export function mark(type: string): Mark { return { type, }; } ================================================ FILE: packages/rich-text-from-markdown/src/__test__/index.test.ts ================================================ import { BLOCKS, INLINES, MARKS } from '@contentful/rich-text-types'; import { richTextFromMarkdown } from '..'; import { block, document, inline, mark, text } from './helpers'; describe('rich-text-from-markdown', () => { test('should parse some markdown', async () => { const result = await richTextFromMarkdown('# Hello World'); expect(result).toEqual(document({}, block(BLOCKS.HEADING_1, {}, text('Hello World')))); }); test('should call the fallback function when a node is not supported', async () => { const fakeNode = { nodeType: 'image', data: {} }; const fallback = jest.fn(() => Promise.resolve(fakeNode)); const result = await richTextFromMarkdown( '![image](https://image.example.com/image.jpg)', fallback, ); expect(fallback).toHaveBeenCalledTimes(1); expect(result).toEqual({ nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: 'image', data: {}, }, ], }); }); }); describe.each<[string, string[], string[]?]>([ ['*This is an italic text*', ['This is an italic text', 'italic']], ['__This a bold text__', ['This a bold text', 'bold']], ['`This is code`', ['This is code', 'code']], [ '__This is bold and *this is an italic*__', ['This is bold and ', 'bold'], ['this is an italic', 'bold', 'italic'], ], ])( 'The markdown "%s" should be parsed to text with value "%s"', (markdown, ...expectedTextWithMarks) => { test(`${markdown}`, async () => { const result = await richTextFromMarkdown(markdown); expect(result).toEqual( document( {}, block( BLOCKS.PARAGRAPH, {}, ...expectedTextWithMarks.map(([expectedText, ...expectedMarkTypes]) => text(expectedText, ...expectedMarkTypes.map(mark)), ), ), ), ); }); }, ); describe('parses complex inline image markdown correctly', () => { test('incoming markdown tree calls fallback twice', async () => { const fakeNode = { nodeType: 'image', data: {} }; const fallback = jest.fn(() => Promise.resolve(fakeNode)); const result = await richTextFromMarkdown( `![image](https://image.example.com/image.jpg) ![image](https://image.example.com/image2.jpg)`, fallback, ); expect(fallback).toHaveBeenCalledTimes(2); expect(result).toEqual({ nodeType: 'document', data: {}, content: [ { nodeType: 'image', data: {}, }, block( BLOCKS.PARAGRAPH, {}, text(` `), ), { nodeType: 'image', data: {}, }, ], }); }); test('incoming markdown tree calls fallback twice', async () => { const fakeNode = { nodeType: 'image', data: {} }; const fallback = jest.fn(() => Promise.resolve(fakeNode)); const result = await richTextFromMarkdown( `some text ![image](https://image.example.com/image.jpg)![image](https://image.example.com/image2.jpg) some more text`, fallback, ); expect(fallback).toHaveBeenCalledTimes(2); expect(result).toEqual({ nodeType: 'document', data: {}, content: [ block(BLOCKS.PARAGRAPH, {}, text('some text ')), { nodeType: 'image', data: {}, }, { nodeType: 'image', data: {}, }, block(BLOCKS.PARAGRAPH, {}, text(' some more text')), ], }); }); }); describe('links', () => { test('should correctly convert a link', async () => { const result = await richTextFromMarkdown('[This is a link](https://contentful.com)'); expect(result).toEqual( document( {}, block( BLOCKS.PARAGRAPH, {}, inline( INLINES.HYPERLINK, { data: { uri: 'https://contentful.com', }, }, text('This is a link'), ), ), ), ); }); test('should convert link wrapped in a mark', async () => { const result = await richTextFromMarkdown( '*This is a test [Contentful](https://www.contentful.com/). Text text*', ); expect(result).toEqual( document( {}, block( BLOCKS.PARAGRAPH, {}, text('This is a test ', mark(MARKS.ITALIC)), inline( INLINES.HYPERLINK, { data: { uri: 'https://www.contentful.com/', }, }, text('Contentful', mark(MARKS.ITALIC)), ), text('. Text text', mark(MARKS.ITALIC)), ), ), ); }); }); ================================================ FILE: packages/rich-text-from-markdown/src/__test__/real-world.md ================================================ # h1 Heading ## h2 Heading ### h3 Heading #### h4 Heading ##### h5 Heading ###### h6 Heading ## Paragraphs This is a paragraph with a new line. This is a new paragraph. This is a paragraph
using br. ## Horizontal Rules --- --- --- ## Emphasis **This is bold text** **This is bold text** _This is italic text_ _This is italic text_ ~~Strikethrough is supported~~ ## Blockquotes > Blockquotes ## Lists Unordered - Create a list by starting a line with `+`, `-`, or `*` - Sub-lists are made by indenting 2 spaces: - Marker character change forces new list start: - Ac tristique libero volutpat at * Facilisis in pretium nisl aliquet - Nulla volutpat aliquam velit - Very easy! - Here is a list item
with a line break Ordered 1. Lorem ipsum dolor sit amet 2. Consectetur adipiscing elit 3. Integer molestie lorem at massa 4. You can use sequential numbers... 5. ...or keep all the numbers as `1.` Start numbering with offset: 57. foo 1. bar ## Code Inline `code` ## Links [link text](https://www.contentful.com) [link with title](https://www.contentful.com/blog/ 'title text!') ## Tables | Name | Country | | -------------------------------------------- | ------- | | Test 1 | Germany | | Test 2 | USA | | > Test 3 | USA | | \* Test 4 | Germany | | # Test 5 | Germany | |

Test 6
Test 7

| USA | |
  • Test 8
| USA | |
Test 9
| Germany | |

Test 10

and

Test 11

| Germany | | | Germany | | ![Image Description](image.jpg) | Brazil | | **[Test 12](https://example.com)** | USA | ## Tables with marks | **Bold Header 1** | **Bold Header 2** | | ----------------- | ----------------- | | _Italic_ | `Code` | ## Tables without body | abc | def | | --- | --- | ## Table with empty cells | | | | ------ | ------ | | Cell 1 | | | | Cell 2 | ================================================ FILE: packages/rich-text-from-markdown/src/__test__/real-world.test.ts ================================================ import { BLOCKS, INLINES, MARKS } from '@contentful/rich-text-types'; import { readFileSync } from 'fs'; import path from 'path'; import { richTextFromMarkdown } from '..'; import { block, document, inline, mark, text } from './helpers'; describe('rich-text-from-markdown', () => { it('should parse md with all formatting options', async () => { const md = readFileSync(path.resolve(__dirname, './real-world.md'), 'utf8'); const result = await richTextFromMarkdown(md); expect(result).toEqual( document( {}, block(BLOCKS.HEADING_1, {}, text('h1 Heading')), block(BLOCKS.HEADING_2, {}, text('h2 Heading')), block(BLOCKS.HEADING_3, {}, text('h3 Heading')), block(BLOCKS.HEADING_4, {}, text('h4 Heading')), block(BLOCKS.HEADING_5, {}, text('h5 Heading')), block(BLOCKS.HEADING_6, {}, text('h6 Heading')), // Paragraphs block(BLOCKS.HEADING_2, {}, text('Paragraphs')), block( BLOCKS.PARAGRAPH, {}, text(`This is a paragraph with a new line.`), ), block(BLOCKS.PARAGRAPH, {}, text('This is a new paragraph.')), // TODO:
test should be ideally the same as the new line one. block(BLOCKS.PARAGRAPH, {}, text('This is a paragraph'), text('\n'), text('using br.')), block(BLOCKS.HEADING_2, {}, text('Horizontal Rules')), block(BLOCKS.HR), block(BLOCKS.HR), block(BLOCKS.HR), block(BLOCKS.HEADING_2, {}, text('Emphasis')), block(BLOCKS.PARAGRAPH, {}, text('This is bold text', mark(MARKS.BOLD))), block(BLOCKS.PARAGRAPH, {}, text('This is bold text', mark(MARKS.BOLD))), block(BLOCKS.PARAGRAPH, {}, text('This is italic text', mark(MARKS.ITALIC))), block(BLOCKS.PARAGRAPH, {}, text('This is italic text', mark(MARKS.ITALIC))), block(BLOCKS.PARAGRAPH, {}, text('Strikethrough is supported', mark(MARKS.STRIKETHROUGH))), block(BLOCKS.HEADING_2, {}, text('Blockquotes')), block(BLOCKS.QUOTE, {}, block(BLOCKS.PARAGRAPH, {}, text('Blockquotes'))), block(BLOCKS.HEADING_2, {}, text('Lists')), block(BLOCKS.PARAGRAPH, {}, text('Unordered')), block( BLOCKS.UL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block( BLOCKS.PARAGRAPH, {}, text('Create a list by starting a line with '), text('+', mark(MARKS.CODE)), text(', '), text('-', mark(MARKS.CODE)), text(', or '), text('*', mark(MARKS.CODE)), ), ), block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Sub-lists are made by indenting 2 spaces:')), block( BLOCKS.UL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Marker character change forces new list start:')), block( BLOCKS.UL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Ac tristique libero volutpat at')), ), ), block( BLOCKS.UL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Facilisis in pretium nisl aliquet')), ), ), block( BLOCKS.UL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Nulla volutpat aliquam velit')), ), ), ), ), ), block(BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Very easy!'))), block( BLOCKS.LIST_ITEM, {}, block( BLOCKS.PARAGRAPH, {}, text('Here is a list item'), text('\n'), text('with a line break'), ), ), ), block(BLOCKS.PARAGRAPH, {}, text('Ordered')), block( BLOCKS.OL_LIST, {}, block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Lorem ipsum dolor sit amet')), ), block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Consectetur adipiscing elit')), ), block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('Integer molestie lorem at massa')), ), block( BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('You can use sequential numbers...')), ), block( BLOCKS.LIST_ITEM, {}, block( BLOCKS.PARAGRAPH, {}, text('...or keep all the numbers as '), text('1.', mark(MARKS.CODE)), ), ), ), block(BLOCKS.PARAGRAPH, {}, text('Start numbering with offset:')), block( BLOCKS.OL_LIST, {}, block(BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('foo'))), block(BLOCKS.LIST_ITEM, {}, block(BLOCKS.PARAGRAPH, {}, text('bar'))), ), block(BLOCKS.HEADING_2, {}, text('Code')), block(BLOCKS.PARAGRAPH, {}, text('Inline '), text('code', mark('code'))), block(BLOCKS.HEADING_2, {}, text('Links')), block( BLOCKS.PARAGRAPH, {}, inline( INLINES.HYPERLINK, { data: { uri: 'https://www.contentful.com' } }, text('link text'), ), ), block( BLOCKS.PARAGRAPH, {}, inline( INLINES.HYPERLINK, { data: { uri: 'https://www.contentful.com/blog/' } }, text('link with title'), ), ), // Tables block(BLOCKS.HEADING_2, {}, text('Tables')), block( BLOCKS.TABLE, {}, block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Name'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Country'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 1'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 2'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('USA'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('> Test 3'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('USA'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('* Test 4'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('# Test 5'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 6'), text('\n'), text('Test 7')), ), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('USA'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 8'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('USA'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 9'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Test 10'), text(' and '), text('Test 11')), ), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Germany'))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Brazil'))), ), block( BLOCKS.TABLE_ROW, {}, block( BLOCKS.TABLE_CELL, {}, block( BLOCKS.PARAGRAPH, {}, inline( INLINES.HYPERLINK, { data: { uri: 'https://example.com' } }, text('Test 12', mark(MARKS.BOLD)), ), ), ), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('USA'))), ), ), // Tables with marks block(BLOCKS.HEADING_2, {}, text('Tables with marks')), block( BLOCKS.TABLE, {}, block( BLOCKS.TABLE_ROW, {}, block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Bold Header 1', mark(MARKS.BOLD))), ), block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Bold Header 2', mark(MARKS.BOLD))), ), ), block( BLOCKS.TABLE_ROW, {}, block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Italic', mark(MARKS.ITALIC))), ), block( BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Code', mark(MARKS.CODE))), ), ), ), // Tables without body block(BLOCKS.HEADING_2, {}, text('Tables without body')), block( BLOCKS.TABLE, {}, block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('abc'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('def'))), ), ), // Tables with empty cells block(BLOCKS.HEADING_2, {}, text('Table with empty cells')), block( BLOCKS.TABLE, {}, block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Cell 1'))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), ), block( BLOCKS.TABLE_ROW, {}, block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text(''))), block(BLOCKS.TABLE_CELL, {}, block(BLOCKS.PARAGRAPH, {}, text('Cell 2'))), ), ), ), ); }); }); ================================================ FILE: packages/rich-text-from-markdown/src/index.ts ================================================ import { BLOCKS, Block, Document, Hyperlink, INLINES, Inline, Node, Text, TopLevelBlock, } from '@contentful/rich-text-types'; import _ from 'lodash'; import gfm from 'remark-gfm'; import markdown from 'remark-parse'; import unified from 'unified'; import { MarkdownLinkNode, MarkdownNode, MarkdownTree } from './types'; const markdownNodeTypes = new Map([ ['paragraph', BLOCKS.PARAGRAPH], ['heading', 'heading'], ['text', 'text'], ['emphasis', 'text'], ['strong', 'text'], ['delete', 'text'], ['inlineCode', 'text'], ['link', INLINES.HYPERLINK], ['thematicBreak', BLOCKS.HR], ['blockquote', BLOCKS.QUOTE], ['list', 'list'], ['listItem', BLOCKS.LIST_ITEM], ['table', BLOCKS.TABLE], ['tableRow', BLOCKS.TABLE_ROW], ['tableCell', BLOCKS.TABLE_CELL], ]); const nodeTypeFor = (node: MarkdownNode) => { const nodeType = markdownNodeTypes.get(node.type); switch (nodeType) { case 'heading': return `${nodeType}-${node.depth}`; case 'list': return `${node.ordered ? 'ordered' : 'unordered'}-list`; default: return nodeType; } }; const markTypes = new Map([ ['emphasis', 'italic'], ['strong', 'bold'], ['inlineCode', 'code'], ['delete', 'strikethrough'], ]); const markTypeFor = (node: MarkdownNode) => { return markTypes.get(node.type); }; const isLink = (node: MarkdownNode): node is MarkdownLinkNode => { return node.type === 'link'; }; const nodeContainerTypes = new Map([ ['delete', 'block'], [BLOCKS.HEADING_1, 'block'], [BLOCKS.HEADING_2, 'block'], [BLOCKS.HEADING_3, 'block'], [BLOCKS.HEADING_4, 'block'], [BLOCKS.HEADING_5, 'block'], [BLOCKS.HEADING_6, 'block'], [BLOCKS.LIST_ITEM, 'block'], [BLOCKS.UL_LIST, 'block'], [BLOCKS.OL_LIST, 'block'], [BLOCKS.QUOTE, 'block'], [BLOCKS.HR, 'block'], [BLOCKS.PARAGRAPH, 'block'], [BLOCKS.TABLE, 'block'], [BLOCKS.TABLE_CELL, 'block'], [BLOCKS.TABLE_HEADER_CELL, 'block'], [BLOCKS.TABLE_ROW, 'block'], [INLINES.HYPERLINK, 'inline'], ['text', 'text'], ['emphasis', 'text'], ['strong', 'text'], ['inlineCode', 'text'], ['delete', 'text'], ]); const isBlock = (nodeType: string) => { return nodeContainerTypes.get(nodeType) === 'block'; }; const isText = (nodeType: string) => { return nodeContainerTypes.get(nodeType) === 'text'; }; const isInline = (nodeType: string) => { return nodeContainerTypes.get(nodeType) === 'inline'; }; const isTableCell = (nodeType: string) => { return nodeType === BLOCKS.TABLE_CELL; }; const buildHyperlink = async ( node: MarkdownLinkNode, fallback: FallbackResolver, appliedMarksTypes: string[], ): Promise => { const content = (await mdToRichTextNodes(node.children, fallback, appliedMarksTypes)) as Text[]; const hyperlink: Hyperlink = { nodeType: INLINES.HYPERLINK, data: { uri: node.url }, content, }; return [hyperlink]; }; const buildGenericBlockOrInline = async ( node: MarkdownNode, fallback: FallbackResolver, appliedMarksTypes: string[], ): Promise> => { const nodeType = nodeTypeFor(node); const content = await mdToRichTextNodes(node.children, fallback, appliedMarksTypes); return [ { nodeType: nodeType, content, data: {}, } as Block | Inline, ]; }; const buildTableCell = async ( node: MarkdownNode, fallback: FallbackResolver, appliedMarksTypes: string[], ): Promise> => { const nodeChildren = await mdToRichTextNodes(node.children, fallback, appliedMarksTypes); const content = nodeChildren.reduce((result, contentNode) => { if (isText(contentNode.nodeType) || isInline(contentNode.nodeType)) { const lastNode = result[result.length - 1]; if (lastNode && lastNode.nodeType === BLOCKS.PARAGRAPH) { lastNode.content.push(contentNode); } else { result.push({ nodeType: BLOCKS.PARAGRAPH, data: {}, content: [contentNode] }); } } else { result.push(contentNode); } return result; }, []); // A table cell can't be empty if (content.length === 0) { content.push({ nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: '', } as Text, ], }); } /** * We should only support texts inside table cells. * Some markdowns might contain html inside tables such as
    ,
    , etc * but they are pretty much filtered out by markdownNodeTypes and nodeContainerTypes variables. * so we ended up receiving only `text` nodes. * We can't have table cells with text nodes directly, we must wrap text nodes inside paragraphs. */ return [ { nodeType: BLOCKS.TABLE_CELL, content, data: {}, } as Block, ]; }; const buildText = async ( node: MarkdownNode, fallback: FallbackResolver, appliedMarksTypes: string[], ): Promise> => { const nodeType = nodeTypeFor(node); const markType = markTypeFor(node); const marks = Array.from(appliedMarksTypes); if (markType) { marks.push(markType); } if (node.type !== 'text' && node.children) { return (await mdToRichTextNodes(node.children, fallback, marks)) as Array; } if (node.value) { return [ { nodeType: nodeType, value: node.value, marks: marks.map((type) => ({ type })), data: {}, } as Text, ]; } }; const buildFallbackNode = async ( node: MarkdownNode, fallback: FallbackResolver, ): Promise => { const fallbackResult = await fallback(node); if (_.isArray(fallbackResult)) { return fallbackResult; } return [fallbackResult]; }; async function mdToRichTextNode( node: MarkdownNode, fallback: FallbackResolver, appliedMarksTypes: string[] = [], ): Promise { // By default
    is parsed as html node, causing it to be stripped out. // We need to convert it manually in order to support it if (node.type === 'html' && //gi.test(node.value)) { node.value = '\n'; node.type = 'text'; } const nodeType = nodeTypeFor(node); if (isLink(node)) { return await buildHyperlink(node, fallback, appliedMarksTypes); } if (isTableCell(nodeType)) { return await buildTableCell(node, fallback, appliedMarksTypes); } if (isBlock(nodeType) || isInline(nodeType)) { return await buildGenericBlockOrInline(node, fallback, appliedMarksTypes); } if (isText(nodeType)) { return await buildText(node, fallback, appliedMarksTypes); } return await buildFallbackNode(node, fallback); } async function mdToRichTextNodes( nodes: MarkdownNode[], fallback: FallbackResolver, appliedMarksTypes: string[] = [], ): Promise { if (!nodes) { return Promise.resolve([]); } const rtNodes = await Promise.all( nodes.map((node) => mdToRichTextNode(node, fallback, appliedMarksTypes)), ); return _.flatten(rtNodes).filter(Boolean); } const astToRichTextDocument = async ( tree: MarkdownTree, fallback: FallbackResolver, ): Promise => { const content = await mdToRichTextNodes(tree.children, fallback); return { nodeType: BLOCKS.DOCUMENT, data: {}, content: content as TopLevelBlock[], }; }; function expandParagraphWithInlineImages(node: MarkdownNode): MarkdownNode[] { if (node.type !== 'paragraph') { return [node]; } const imageNodeIndices = []; for (let i = 0; i < node.children.length; i++) { if (node.children[i].type === 'image') { imageNodeIndices.push(i); } } if (imageNodeIndices.length === 0) { // If no images in children, return. return [node]; } const allNodes: MarkdownNode[] = []; let lastIndex = -1; for (let j = 0; j < imageNodeIndices.length; j++) { const index = imageNodeIndices[j]; // before if (index !== 0) { const nodesBefore: MarkdownNode[] = node.children.slice(lastIndex + 1, index); if (nodesBefore.length > 0) { allNodes.push({ ...node, children: nodesBefore, }); } } // image const imageNode = node.children[index]; allNodes.push(imageNode); // till end let nodesAfter: MarkdownNode[] = []; const rangeEnd = j + 1 < imageNodeIndices.length ? imageNodeIndices[j + 1] : node.children.length; if (index + 1 < rangeEnd && index === imageNodeIndices.slice(-1)[0]) { nodesAfter = node.children.slice(index + 1, rangeEnd); if (nodesAfter.length > 0) { allNodes.push({ ...node, children: nodesAfter, }); } } lastIndex = index; } return allNodes; } // Inline markdown images come in as nested within a MarkdownNode paragraph // so we must hoist them out before transforming to rich text. function prepareMdAST(ast: MarkdownTree): MarkdownNode { function prepareASTNodeChildren(node: MarkdownNode): MarkdownNode { if (!node.children) { return node; } const children = _.flatMap(node.children, (n) => expandParagraphWithInlineImages(n)).map((n) => prepareASTNodeChildren(n), ); return { ...node, children }; } return prepareASTNodeChildren({ depth: '0', type: 'root', value: '', ordered: true, children: ast.children, }); } // COMPAT: can resolve with either Node or an array of Nodes for back compatibility. export type FallbackResolver = (mdNode: MarkdownNode) => Promise; export async function richTextFromMarkdown( md: string, fallback: FallbackResolver = () => Promise.resolve(null), ): Promise { const processor = unified().use(markdown).use(gfm); const tree = processor.parse(md); // @ts-expect-error children is missing in the return type of processor.parse const ast = prepareMdAST(tree); return await astToRichTextDocument(ast, fallback); } ================================================ FILE: packages/rich-text-from-markdown/src/types/index.ts ================================================ export interface MarkdownNode extends MarkdownTree { depth: string; type: string; ordered: boolean; value: string; } export interface MarkdownTree { children: MarkdownNode[]; } export interface MarkdownLinkNode extends MarkdownNode { url: string; } ================================================ FILE: packages/rich-text-from-markdown/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "dist/types", "outDir": "dist/lib", "strict": false, "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/types"] }, "include": ["src"] } ================================================ FILE: packages/rich-text-html-renderer/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [17.2.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.2.1...@contentful/rich-text-html-renderer@17.2.2) (2026-04-09) ### Bug Fixes - **rich-text-types:** improve esm compability [ZEND-7778] ([#1073](https://github.com/contentful/rich-text/issues/1073)) ([204aaec](https://github.com/contentful/rich-text/commit/204aaecc3893633c081986f44896e14272fa376a)) ## [17.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.2.0...@contentful/rich-text-html-renderer@17.2.1) (2026-04-08) ### Bug Fixes - **rich-text-html-renderer:** parse and render asset [] ([#854](https://github.com/contentful/rich-text/issues/854)) ([3ca028d](https://github.com/contentful/rich-text/commit/3ca028d828979a22cb208607371ffec8990d01b7)), closes [#853](https://github.com/contentful/rich-text/issues/853) # [17.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.6...@contentful/rich-text-html-renderer@17.2.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [17.1.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.5...@contentful/rich-text-html-renderer@17.1.6) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [17.1.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.4...@contentful/rich-text-html-renderer@17.1.5) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [17.1.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.3...@contentful/rich-text-html-renderer@17.1.4) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [17.1.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.2...@contentful/rich-text-html-renderer@17.1.3) (2025-09-10) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [17.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.1...@contentful/rich-text-html-renderer@17.1.2) (2025-09-09) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [17.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.1.0...@contentful/rich-text-html-renderer@17.1.1) (2025-09-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [17.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.0.1...@contentful/rich-text-html-renderer@17.1.0) (2025-07-15) ### Features - add an option to strip empty trailing paragraphs [TOL-3193] ([#892](https://github.com/contentful/rich-text/issues/892)) ([9beff0e](https://github.com/contentful/rich-text/commit/9beff0e4cba3e79dc68e6a0725e843c5d642eb87)) ## [17.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@17.0.0...@contentful/rich-text-html-renderer@17.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [17.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.10...@contentful/rich-text-html-renderer@17.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [16.6.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.9...@contentful/rich-text-html-renderer@16.6.10) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.8...@contentful/rich-text-html-renderer@16.6.9) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.7...@contentful/rich-text-html-renderer@16.6.8) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.6...@contentful/rich-text-html-renderer@16.6.7) (2024-07-24) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.5...@contentful/rich-text-html-renderer@16.6.6) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.4...@contentful/rich-text-html-renderer@16.6.5) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.3...@contentful/rich-text-html-renderer@16.6.4) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.2...@contentful/rich-text-html-renderer@16.6.3) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.1...@contentful/rich-text-html-renderer@16.6.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.6.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.6.0...@contentful/rich-text-html-renderer@16.6.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [16.6.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.5...@contentful/rich-text-html-renderer@16.6.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [16.5.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.4...@contentful/rich-text-html-renderer@16.5.5) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.5.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.3...@contentful/rich-text-html-renderer@16.5.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.5.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.2...@contentful/rich-text-html-renderer@16.5.3) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.5.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.1...@contentful/rich-text-html-renderer@16.5.2) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.5.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.5.0...@contentful/rich-text-html-renderer@16.5.1) (2024-05-27) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [16.5.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.4.0...@contentful/rich-text-html-renderer@16.5.0) (2024-05-24) ### Features - add strikethrough support ([#562](https://github.com/contentful/rich-text/issues/562)) ([b87d0c3](https://github.com/contentful/rich-text/commit/b87d0c31bccb4012745c0479b2b5c92fc28c1e91)) # [16.4.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.5...@contentful/rich-text-html-renderer@16.4.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [16.3.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.4...@contentful/rich-text-html-renderer@16.3.5) (2024-03-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.3.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.3...@contentful/rich-text-html-renderer@16.3.4) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.3.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.2...@contentful/rich-text-html-renderer@16.3.3) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.3.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.1...@contentful/rich-text-html-renderer@16.3.2) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.3.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.3.0...@contentful/rich-text-html-renderer@16.3.1) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [16.3.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.2.0...@contentful/rich-text-html-renderer@16.3.0) (2023-10-24) ### Features - add default renderers for new resource nodes ([#504](https://github.com/contentful/rich-text/issues/504)) ([f748e6b](https://github.com/contentful/rich-text/commit/f748e6b0f0e22ce3809ca8881438b84b1baafb8f)) # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.1.2...@contentful/rich-text-html-renderer@16.2.0) (2023-10-02) ### Features - preserve formatting linebreak whitespace ([#497](https://github.com/contentful/rich-text/issues/497)) ([e62ab92](https://github.com/contentful/rich-text/commit/e62ab92f6320d970f921e751d1753cd290224224)) ## [16.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.1.1...@contentful/rich-text-html-renderer@16.1.2) (2023-09-12) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.1.0...@contentful/rich-text-html-renderer@16.1.1) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.5...@contentful/rich-text-html-renderer@16.1.0) (2023-06-09) ### Features - add default renderer for the node ([#472](https://github.com/contentful/rich-text/issues/472)) ([5a53caf](https://github.com/contentful/rich-text/commit/5a53cafc58aa8d009d2aa2d6cb4fa2d6cee6b19d)) ## [16.0.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.4...@contentful/rich-text-html-renderer@16.0.5) (2023-05-26) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.0.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.3...@contentful/rich-text-html-renderer@16.0.4) (2023-05-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.0.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.2...@contentful/rich-text-html-renderer@16.0.3) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.1...@contentful/rich-text-html-renderer@16.0.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-html-renderer@16.0.0...@contentful/rich-text-html-renderer@16.0.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-html-renderer # 16.0.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - update Lerna, rollup in slatejs-adapter ([#366](https://github.com/contentful/rich-text/issues/366)) ([32448e3](https://github.com/contentful/rich-text/commit/32448e369ae2b76601dd81839e13c15432430d68)) ## 15.13.1 (2022-05-10) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## 15.9.1 (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # 15.9.0 (2021-12-09) # 15.8.0 (2021-11-11) ### Features - add toContentfulDocument() and toSlatejsDocument() empty block node handling ([#287](https://github.com/contentful/rich-text/issues/287)) ([fa79626](https://github.com/contentful/rich-text/commit/fa79626e4020d9640a920ca5d0ccb654e89cfa90)) # 15.7.0 (2021-11-11) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ## 15.5.1 (2021-10-25) # 15.5.0 (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) # 15.4.0 (2021-09-16) ### Features - **html+react:** render Table header as ([#269](https://github.com/contentful/rich-text/issues/269)) ([0f82905](https://github.com/contentful/rich-text/commit/0f829059be6d91e042dfc71698009177ae4ab78d)) ## 15.3.6 (2021-09-15) ## 15.3.5 (2021-09-13) ## 15.3.4 (2021-09-09) ### Bug Fixes - replace import to fix typings for html-renderer ([#265](https://github.com/contentful/rich-text/issues/265)) ([40c2670](https://github.com/contentful/rich-text/commit/40c267069b18454517b0f4283a7e155cffa410b6)) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.2.0 (2021-08-16) # 15.1.0 (2021-08-02) ### Bug Fixes - 🐛 html encode default inlined CF entry/asset links ([41396eb](https://github.com/contentful/rich-text/commit/41396eb800f6d5c65c634c4394a6c60cf3425255)) - 🐛 prevent html injection via `data.uri` link rendering ([ecae89a](https://github.com/contentful/rich-text/commit/ecae89ad0d25175f342833ca989928670c24b8fd)) ### Features - 🎸 add RT html renderer tables support ([ce81375](https://github.com/contentful/rich-text/commit/ce8137577b269c62727dc64b7d47b4951597dbd6)) # 15.0.0 (2021-06-15) ## 14.1.2 (2020-11-02) ## 14.0.1 (2020-01-30) # 14.0.0 (2020-01-28) # 13.4.0 (2019-08-01) # 13.1.0 (2019-03-04) # 13.0.0 (2019-01-22) ## 12.2.1 (2019-01-22) ### Bug Fixes - gracefully handle empty rich text documents ([14870de](https://github.com/contentful/rich-text/commit/14870ded471f69cee84a60739bee06873ed53af9)) ## 12.1.2 (2018-12-14) ### Features - add html escaping ([4b55331](https://github.com/contentful/rich-text/commit/4b55331e86bc62787420f8081228293f1a22e1b7)) # 12.1.0 (2018-12-12) ## 12.0.3 (2018-12-05) ### Bug Fixes - **readme:** mark types in readme examples ([d997b56](https://github.com/contentful/rich-text/commit/d997b56f2b8c32b2e5b478ab5444757203e2c703)) ## 12.0.2 (2018-12-04) ### Bug Fixes - 🐛 update path to typings ([ce5544f](https://github.com/contentful/rich-text/commit/ce5544f58712dc6a18aadda523d0c0357a66c8a5)) ## 12.0.1 (2018-12-04) # 12.0.0 (2018-11-29) # 11.0.0 (2018-11-27) # 10.3.0 (2018-11-26) # 10.2.0 (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # 10.1.0 (2018-11-16) ## 10.0.1 (2018-11-08) # 10.0.0 (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## 9.0.2 (2018-10-31) # 9.0.0 (2018-10-30) ### Features - 🎸 Explicitly declare nodeClass and nodeType in core types ([0749c61](https://github.com/contentful/rich-text/commit/0749c6199bb2681509608539c76bd8149cade564)) ### BREAKING CHANGES - Potentially breaks TypeScript libraries pulling this in as a dependency if they are not explicitly stating nodeClass and nodeType. ## 8.0.3 (2018-10-30) ## 8.0.2 (2018-10-29) ## 8.0.1 (2018-10-26) ### Bug Fixes - 🐛 Revert mock hypenation commits ([bde9432](https://github.com/contentful/rich-text/commit/bde94323fcc02bf5ab3feeef46a9d8fc8b08d59b)) - sync HTML renderer ([769bd95](https://github.com/contentful/rich-text/commit/769bd95add1aecad0e11fab377f99c5cdad99072)) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - update Lerna, rollup in slatejs-adapter ([#366](https://github.com/contentful/rich-text/issues/366)) ([32448e3](https://github.com/contentful/rich-text/commit/32448e369ae2b76601dd81839e13c15432430d68)) ## [15.13.1](https://github.com/contentful/rich-text/compare/v15.13.0...v15.13.1) (2022-05-10) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.8.0](https://github.com/contentful/rich-text/compare/v15.7.0...v15.8.0) (2021-11-11) ### Features - add toContentfulDocument() and toSlatejsDocument() empty block node handling ([#287](https://github.com/contentful/rich-text/issues/287)) ([fa79626](https://github.com/contentful/rich-text/commit/fa79626e4020d9640a920ca5d0ccb654e89cfa90)) # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) # [15.4.0](https://github.com/contentful/rich-text/compare/v15.3.6...v15.4.0) (2021-09-16) ### Features - **html+react:** render Table header as ([#269](https://github.com/contentful/rich-text/issues/269)) ([0f82905](https://github.com/contentful/rich-text/commit/0f829059be6d91e042dfc71698009177ae4ab78d)) ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.3.4](https://github.com/contentful/rich-text/compare/v15.3.3...v15.3.4) (2021-09-09) ### Bug Fixes - replace import to fix typings for html-renderer ([#265](https://github.com/contentful/rich-text/issues/265)) ([40c2670](https://github.com/contentful/rich-text/commit/40c267069b18454517b0f4283a7e155cffa410b6)) ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-html-renderer ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/rich-text-html-renderer # [15.2.0](https://github.com/contentful/rich-text/compare/v15.1.0...v15.2.0) (2021-08-16) ### Bug Fixes - 🐛 html encode default inlined CF entry/asset links ([41396eb](https://github.com/contentful/rich-text/commit/41396eb800f6d5c65c634c4394a6c60cf3425255)) - 🐛 prevent html injection via `data.uri` link rendering ([ecae89a](https://github.com/contentful/rich-text/commit/ecae89ad0d25175f342833ca989928670c24b8fd)) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) ### Features - 🎸 add RT html renderer tables support ([ce81375](https://github.com/contentful/rich-text/commit/ce8137577b269c62727dc64b7d47b4951597dbd6)) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) ## [12.2.1](https://github.com/contentful/rich-text/compare/v12.2.0...v12.2.1) (2019-01-22) ### Bug Fixes - gracefully handle empty rich text documents ([14870de](https://github.com/contentful/rich-text/commit/14870ded471f69cee84a60739bee06873ed53af9)) ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ### Features - add html escaping ([4b55331](https://github.com/contentful/rich-text/commit/4b55331e86bc62787420f8081228293f1a22e1b7)) # [12.1.0](https://github.com/contentful/rich-text/compare/v12.0.4...v12.1.0) (2018-12-12) ## [12.0.3](https://github.com/contentful/rich-text/compare/v12.0.2...v12.0.3) (2018-12-05) ### Bug Fixes - **readme:** mark types in readme examples ([d997b56](https://github.com/contentful/rich-text/commit/d997b56f2b8c32b2e5b478ab5444757203e2c703)) ## [12.0.2](https://github.com/contentful/rich-text/compare/v12.0.1...v12.0.2) (2018-12-04) ### Bug Fixes - 🐛 update path to typings ([ce5544f](https://github.com/contentful/rich-text/commit/ce5544f58712dc6a18aadda523d0c0357a66c8a5)) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # [10.1.0](https://github.com/contentful/rich-text/compare/v10.0.5...v10.1.0) (2018-11-16) ## [10.0.1](https://github.com/contentful/rich-text/compare/v10.0.0...v10.0.1) (2018-11-08) # [10.0.0](https://github.com/contentful/rich-text/compare/v9.0.2...v10.0.0) (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## [9.0.2](https://github.com/contentful/rich-text/compare/v9.0.1...v9.0.2) (2018-10-31) # [9.0.0](https://github.com/contentful/rich-text/compare/v8.0.3...v9.0.0) (2018-10-30) ### Features - 🎸 Explicitly declare nodeClass and nodeType in core types ([0749c61](https://github.com/contentful/rich-text/commit/0749c6199bb2681509608539c76bd8149cade564)) ### BREAKING CHANGES - Potentially breaks TypeScript libraries pulling this in as a dependency if they are not explicitly stating nodeClass and nodeType. ## [8.0.3](https://github.com/contentful/rich-text/compare/v8.0.2...v8.0.3) (2018-10-30) ## [8.0.2](https://github.com/contentful/rich-text/compare/v8.0.1...v8.0.2) (2018-10-29) ## [8.0.1](https://github.com/contentful/rich-text/compare/v8.0.0...v8.0.1) (2018-10-26) ### Bug Fixes - 🐛 Revert mock hypenation commits ([bde9432](https://github.com/contentful/rich-text/commit/bde94323fcc02bf5ab3feeef46a9d8fc8b08d59b)) - sync HTML renderer ([769bd95](https://github.com/contentful/rich-text/commit/769bd95add1aecad0e11fab377f99c5cdad99072)) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ================================================ FILE: packages/rich-text-html-renderer/README.md ================================================ # rich-text-html-renderer HTML renderer for the Contentful rich text field type. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/rich-text-html-renderer ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/rich-text-html-renderer ``` ## Usage ```javascript import { documentToHtmlString } from '@contentful/rich-text-html-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello world!', marks: [], }, ], }, ], }; documentToHtmlString(document); // ->

    Hello world!

    ``` ```javascript import { documentToHtmlString } from '@contentful/rich-text-html-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello', marks: [{ type: 'bold' }], }, { nodeType: 'text', value: ' world!', marks: [{ type: 'italic' }], }, ], }, ], }; documentToHtmlString(document); // ->

    Hello world!

    ``` You can also pass custom renderers for both marks and nodes as an optional parameter like so: ```javascript import { BLOCKS, MARKS } from '@contentful/rich-text-types'; import { documentToHtmlString } from '@contentful/rich-text-html-renderer'; const document = { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data:{}, content: [ { nodeType: 'text', value: 'Hello', marks: [{ type: 'bold' }], data: {} }, { nodeType: 'text', value: ' world!', marks: [{ type: 'italic' }] data: {} }, ], }, ] }; const options = { renderMark: { [MARKS.BOLD]: text => `${text}` }, renderNode: { [BLOCKS.PARAGRAPH]: (node, next) => `${next(node.content)}` } } documentToHtmlString(document, options); // -> Hello world! ``` Last, but not least, you can pass a custom rendering component for an embedded entry: ```javascript import { BLOCKS } from '@contentful/rich-text-types'; import { documentToHtmlString } from '@contentful/rich-text-html-renderer'; const document = { nodeType: 'document', data: {}, content: [ { nodeType: 'embedded-entry-block', data: { target: (...)Link<'Entry'>(...); }, }, ] }; const options = { renderNode: { [BLOCKS.EMBEDDED_ENTRY]: (node) => `${customComponentRenderer(node)}` } } documentToHtmlString(document, options); // -> (...)Link<'Entry'>(...) ``` The `renderNode` keys should be one of the following `BLOCKS` and `INLINES` properties as defined in [`@contentful/rich-text-types`](https://www.npmjs.com/package/@contentful/rich-text-types): - `BLOCKS` - `DOCUMENT` - `PARAGRAPH` - `HEADING_1` - `HEADING_2` - `HEADING_3` - `HEADING_4` - `HEADING_5` - `HEADING_6` - `UL_LIST` - `OL_LIST` - `LIST_ITEM` - `QUOTE` - `HR` - `EMBEDDED_ENTRY` - `EMBEDDED_ASSET` - `EMBEDDED_RESOURCE` - `INLINES` - `EMBEDDED_ENTRY` (this is different from the `BLOCKS.EMBEDDED_ENTRY`) - `EMBEDDED_RESOURCE` - `HYPERLINK` - `ENTRY_HYPERLINK` - `ASSET_HYPERLINK` - `RESOURCE_HYPERLINK` The `renderMark` keys should be one of the following `MARKS` properties as defined in [`@contentful/rich-text-types`](https://www.npmjs.com/package/@contentful/rich-text-types): - `BOLD` - `ITALIC` - `UNDERLINE` - `CODE` - `SUPERSCRIPT` - `SUBSCRIPT` - `STRIKETHROUGH` #### Preserving Whitespace In your HTML rendering options, you can utilize the `preserveWhitespace` boolean flag. When set to `true`, this flag ensures that spaces and line breaks in the Contentful rich text content are preserved in the rendered HTML. Specifically, it replaces consecutive spaces with ` ` entities and retains line breaks using `
    ` tags. This capability is particularly beneficial for content that has specific formatting requirements involving spaces and line breaks. ```javascript import { documentToHtmlString } from '@contentful/rich-text-html-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello world!', marks: [], }, ], }, ], }; const options = { preserveWhitespace: true, }; documentToHtmlString(document, options); // ->

    Hello     world!

    ``` With this configuration, the HTML output retains the spaces found between "Hello" and "world!". ================================================ FILE: packages/rich-text-html-renderer/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-html-renderer/package.json ================================================ { "name": "@contentful/rich-text-html-renderer", "version": "17.2.2", "main": "dist/rich-text-html-renderer.es5.js", "module": "dist/rich-text-html-renderer.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/rich-text-html-renderer.esm.js", "require": "./dist/rich-text-html-renderer.es5.js", "default": "./dist/rich-text-html-renderer.es5.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "prebuild": "rimraf dist", "build": "tsc --module commonjs && rollup -c --bundleConfigAsCjs rollup.config.js", "start": "tsc && rollup -c --bundleConfigAsCjs rollup.config.js -w", "test": "jest" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7" }, "devDependencies": { "ts-jest": "^29.1.2" } } ================================================ FILE: packages/rich-text-html-renderer/rollup.config.js ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; export default config(outputFile, { external: Object.keys(dependencies), }); ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/embedded-entry.ts ================================================ import { BLOCKS, Document } from '@contentful/rich-text-types'; export default function (entry: Record) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, content: [], data: { target: entry, }, }, ], } as Document; } ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/embedded-resource.ts ================================================ import { Document, BLOCKS, ResourceLink } from '@contentful/rich-text-types'; export default function (resourceLink: ResourceLink) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, content: [], data: { target: resourceLink, }, }, ], } as Document; } ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/heading.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default function (heading: string) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: heading, data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], } as Document; } ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/hr.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { content: [ { content: [ { marks: [], nodeType: 'text', value: 'hello world', data: {}, }, ], data: {}, nodeType: 'paragraph', }, { content: [ { marks: [], nodeType: 'text', value: '', data: {}, }, ], data: {}, nodeType: 'hr', }, { content: [ { marks: [], nodeType: 'text', value: '', data: {}, }, ], data: {}, nodeType: 'paragraph', }, ], data: {}, nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/hyperlink.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Some text ', marks: [], data: {}, }, { nodeType: 'hyperlink', content: [ { nodeType: 'text', value: 'link', marks: [], data: {}, }, ], data: { uri: 'https://url.org', }, }, { nodeType: 'text', value: ' text.', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/index.ts ================================================ export { default as hrDoc } from './hr'; export { default as hyperlinkDoc } from './hyperlink'; export { default as invalidMarksDoc } from './invalid-marks'; export { default as invalidTypeDoc } from './invalid-type'; export { default as paragraphDoc } from './paragraph'; export { default as headingDoc } from './heading'; export { default as marksDoc } from './mark'; export { default as embeddedEntryDoc } from './embedded-entry'; export { default as embeddedResourceDoc } from './embedded-resource'; export { default as olDoc } from './ol'; export { default as ulDoc } from './ul'; export { default as quoteDoc } from './quote'; export { default as tableDoc } from './table'; export { default as tableWithHeaderDoc } from './table-header'; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/inline-entity.ts ================================================ import { BLOCKS, Document, INLINES } from '@contentful/rich-text-types'; export default function inlineEntity(entry: Record, inlineType: INLINES) { return { content: [ { data: {}, content: [ { marks: [], value: '', nodeType: 'text', data: {}, }, { data: entry, content: [ { marks: [], value: '', nodeType: 'text', data: {}, }, ], nodeType: inlineType, }, { marks: [], value: '', nodeType: 'text', data: {}, }, ], nodeType: BLOCKS.PARAGRAPH, }, ], data: {}, nodeType: BLOCKS.DOCUMENT, } as Document; } ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/invalid-marks.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Hello world!', marks: [ { type: 'UNRECOGNIZED_MARK', }, ], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/invalid-type.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: 'UNRECOGNIZED_TYPE' as BLOCKS, data: {}, content: [ { nodeType: 'text', value: 'Hello world!', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/mark.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default function (mark: string) { return { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [{ type: mark }], data: {}, }, ], }, ], } as Document; } ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/ol.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'Hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, ], nodeType: 'ordered-list', }, { data: {}, content: [ { data: {}, marks: [], value: '', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/paragraph.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/quote.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'blockquote', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/table-header.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 1', }, ], }, ], }, { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 1', }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 2', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 2', }, ], }, ], }, ], }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/table.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 1', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 1', }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 2', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 2', }, ], }, ], }, ], }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/documents/ul.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'Hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, ], nodeType: 'unordered-list', }, { data: {}, content: [ { data: {}, marks: [], value: '', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-html-renderer/src/__test__/index.test.ts ================================================ import { Block, BLOCKS, Document, INLINES, MARKS, ResourceLink } from '@contentful/rich-text-types'; import cloneDeep from 'lodash/cloneDeep'; import { documentToHtmlString, Options } from '../index'; import { embeddedEntryDoc, headingDoc, hrDoc, hyperlinkDoc, invalidMarksDoc, invalidTypeDoc, marksDoc, olDoc, paragraphDoc, tableDoc, quoteDoc, ulDoc, tableWithHeaderDoc, embeddedResourceDoc, } from './documents'; import inlineEntity from './documents/inline-entity'; describe('documentToHtmlString', () => { it('returns empty string when given an empty document', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [], }; expect(documentToHtmlString(document)).toEqual(''); }); it('renders nodes with default node renderer', () => { const docs: Array<{ doc: Document; expected: string }> = [ { doc: paragraphDoc, expected: '

    hello world

    ', }, { doc: headingDoc(BLOCKS.HEADING_1), expected: '

    hello world

    ', }, { doc: headingDoc(BLOCKS.HEADING_2), expected: '

    hello world

    ', }, { doc: headingDoc(BLOCKS.HEADING_3), expected: '

    hello world

    ', }, { doc: headingDoc(BLOCKS.HEADING_4), expected: '

    hello world

    ', }, { doc: headingDoc(BLOCKS.HEADING_5), expected: '
    hello world
    ', }, { doc: headingDoc(BLOCKS.HEADING_6), expected: '
    hello world
    ', }, ]; docs.forEach(({ doc, expected }) => { expect(documentToHtmlString(doc)).toEqual(expected); }); }); it('renders marks with default mark renderer', () => { const docs: Array<{ doc: Document; expected: string }> = [ { doc: marksDoc(MARKS.ITALIC), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.BOLD), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.UNDERLINE), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.CODE), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.SUPERSCRIPT), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.SUBSCRIPT), expected: '

    hello world

    ', }, { doc: marksDoc(MARKS.STRIKETHROUGH), expected: '

    hello world

    ', }, ]; docs.forEach(({ doc, expected }) => { expect(documentToHtmlString(doc)).toEqual(expected); }); }); it('renders nodes with passed custom node renderer', () => { const options: Options = { renderNode: { [BLOCKS.PARAGRAPH]: (node, next) => `

    ${next(node.content)}

    `, }, }; const document: Document = paragraphDoc; const expected = `

    hello world

    `; expect(documentToHtmlString(document, options)).toEqual(expected); }); it('renders marks with the passed custom mark rendered', () => { const options: Options = { renderMark: { [MARKS.UNDERLINE]: (text) => `${text}`, }, }; const document: Document = marksDoc(MARKS.UNDERLINE); const expected = '

    hello world

    '; expect(documentToHtmlString(document, options)).toEqual(expected); }); it('renders escaped html', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'foo & bar', marks: [], data: {}, }, ], }, ], }; const expected = '

    foo & bar

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders escaped html with marks', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'foo & bar', marks: [{ type: MARKS.UNDERLINE }, { type: MARKS.BOLD }], data: {}, }, ], }, ], }; const expected = '

    foo & bar

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('does not render unrecognized marks', () => { const document: Document = invalidMarksDoc; const expected = '

    Hello world!

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders empty node if type is not recognized', () => { const document: Document = invalidTypeDoc; const expected = ''; expect(documentToHtmlString(document as Document)).toEqual(expected); }); it('renders default entry link block', () => { const entrySys = { sys: { id: '9mpxT4zsRi6Iwukey8KeM', link: 'Link', linkType: 'Entry', }, }; const document: Document = embeddedEntryDoc(entrySys); const expected = `
    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders default resource link block', () => { const resourceLink: ResourceLink = { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'ResourceLink', linkType: 'Contentful:Entry', }, }; const document: Document = embeddedResourceDoc(resourceLink); const expected = `
    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders ordered lists', () => { const document: Document = olDoc; const expected = `
    1. Hello

    2. world

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders unordered lists', () => { const document: Document = ulDoc; const expected = `
    • Hello

    • world

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders blockquotes', () => { const document: Document = quoteDoc; const expected = `

    hello

    world
    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders horizontal rule', () => { const document: Document = hrDoc; const expected = '

    hello world


    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders tables', () => { const document: Document = tableDoc; const expected = '' + '' + '' + '

    A 1

    B 1

    A 2

    B 2

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders tables with header', () => { const expected = '' + '' + '' + '

    A 1

    B 1

    A 2

    B 2

    '; expect(documentToHtmlString(tableWithHeaderDoc)).toEqual(expected); }); it('does not crash with inline elements (e.g. hyperlink)', () => { const document: Document = hyperlinkDoc; expect(documentToHtmlString(document)).toBeTruthy(); }); it('renders hyperlink', () => { const document: Document = hyperlinkDoc; const expected = '

    Some text link text.

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders hyperlink without allowing html injection via `data.uri`', () => { const document: Document = cloneDeep(hyperlinkDoc); (document.content[0].content[1] as Block).data.uri = '">no html injection!no html injection!link text.

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders hyperlink without invalid non-string `data.uri` values', () => { const document: Document = cloneDeep(hyperlinkDoc); (document.content[0].content[1] as Block).data.uri = 42; const expected = '

    Some text link text.

    '; expect(documentToHtmlString(document)).toEqual(expected); }); it(`renders asset hyperlink`, () => { const asset = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Asset', }, }, }; const document: Document = inlineEntity(asset, INLINES.ASSET_HYPERLINK); const expected = `

    type: ${INLINES.ASSET_HYPERLINK} id: ${asset.target.sys.id}

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders entry hyperlink', () => { const entry = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Entry', }, }, }; const document: Document = inlineEntity(entry, INLINES.ENTRY_HYPERLINK); const expected = `

    type: ${INLINES.ENTRY_HYPERLINK} id: ${entry.target.sys.id}

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders resource hyperlink', () => { const entry = { target: { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'ResourceLink', linkType: 'Contentful:Entry', }, }, }; const document: Document = inlineEntity(entry, INLINES.RESOURCE_HYPERLINK); const expected = `

    type: ${INLINES.RESOURCE_HYPERLINK} urn: ${entry.target.sys.urn}

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders embedded entry', () => { const entry = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Entry', }, }, }; const document: Document = inlineEntity(entry, INLINES.EMBEDDED_ENTRY); const expected = `

    type: ${INLINES.EMBEDDED_ENTRY} id: ${entry.target.sys.id}

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('renders embedded resource', () => { const entry = { target: { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Contentful:Entry', }, }, }; const document: Document = inlineEntity(entry, INLINES.EMBEDDED_RESOURCE); const expected = `

    type: ${INLINES.EMBEDDED_RESOURCE} urn: ${entry.target.sys.urn}

    `; expect(documentToHtmlString(document)).toEqual(expected); }); it('does not crash with empty documents', () => { expect(documentToHtmlString({} as Document)).toEqual(''); }); it('does not crash with undefined documents', () => { expect(documentToHtmlString(undefined as Document)).toEqual(''); }); it('preserves whitespace with preserveWhitespace option', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], }; const options: Options = { preserveWhitespace: true, }; const expected = '

    hello    world

    '; expect(documentToHtmlString(document, options)).toEqual(expected); }); it('preserves line breaks with preserveWhitespace option', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello\nworld', marks: [], data: {}, }, ], }, ], }; const options: Options = { preserveWhitespace: true, }; const expected = '

    hello
    world

    '; expect(documentToHtmlString(document, options)).toEqual(expected); }); it('preserves both spaces and line breaks with preserveWhitespace option', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello \n world', marks: [], data: {}, }, ], }, ], }; const options: Options = { preserveWhitespace: true, }; const expected = '

    hello   
      world

    '; expect(documentToHtmlString(document, options)).toEqual(expected); }); }); describe('stripEmptyTrailingParagraph', () => { it('strips empty trailing paragraph when enabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    Hello world

    '); }); it('does not strip empty trailing paragraph when disabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: false, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    Hello world

    '); }); it('does not strip empty trailing paragraph when it is the only child', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    '); }); it('does not strip non-empty trailing paragraph', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Not empty', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    Hello world

    Not empty

    '); }); it('does not strip trailing paragraph with multiple text nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    Hello world

    '); }); it('does not strip trailing non-paragraph node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.HEADING_1, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToHtmlString(document, options); expect(result).toEqual('

    Hello world

    '); }); }); ================================================ FILE: packages/rich-text-html-renderer/src/escapeHtml.ts ================================================ const escapeRegExp = /["'&<>]/g; const escapeMap: Record = { '"': '"', '&': '&', "'": ''', '<': '<', '>': '>', }; export function escapeHtml(input: string): string { return input.replace(escapeRegExp, (ch) => escapeMap[ch]); } ================================================ FILE: packages/rich-text-html-renderer/src/index.ts ================================================ import { BLOCKS, Block, Document, INLINES, Inline, MARKS, Mark, Text, helpers, } from '@contentful/rich-text-types'; import { escapeHtml } from './escapeHtml'; const attributeValue = (value: string) => `"${value.replace(/"/g, '"')}"`; const defaultNodeRenderers: RenderNode = { [BLOCKS.PARAGRAPH]: (node, next) => `

    ${next(node.content)}

    `, [BLOCKS.HEADING_1]: (node, next) => `

    ${next(node.content)}

    `, [BLOCKS.HEADING_2]: (node, next) => `

    ${next(node.content)}

    `, [BLOCKS.HEADING_3]: (node, next) => `

    ${next(node.content)}

    `, [BLOCKS.HEADING_4]: (node, next) => `

    ${next(node.content)}

    `, [BLOCKS.HEADING_5]: (node, next) => `
    ${next(node.content)}
    `, [BLOCKS.HEADING_6]: (node, next) => `
    ${next(node.content)}
    `, [BLOCKS.EMBEDDED_ENTRY]: (node, next) => `
    ${next(node.content)}
    `, [BLOCKS.EMBEDDED_RESOURCE]: (node, next) => `
    ${next(node.content)}
    `, [BLOCKS.EMBEDDED_ASSET]: (node) => defaultBlockAsset(node as Block), [BLOCKS.UL_LIST]: (node, next) => `
      ${next(node.content)}
    `, [BLOCKS.OL_LIST]: (node, next) => `
      ${next(node.content)}
    `, [BLOCKS.LIST_ITEM]: (node, next) => `
  • ${next(node.content)}
  • `, [BLOCKS.QUOTE]: (node, next) => `
    ${next(node.content)}
    `, [BLOCKS.HR]: () => '
    ', [BLOCKS.TABLE]: (node, next) => `${next(node.content)}
    `, [BLOCKS.TABLE_ROW]: (node, next) => `${next(node.content)}`, [BLOCKS.TABLE_HEADER_CELL]: (node, next) => `${next(node.content)}`, [BLOCKS.TABLE_CELL]: (node, next) => `${next(node.content)}`, [INLINES.ASSET_HYPERLINK]: (node) => defaultInline(INLINES.ASSET_HYPERLINK, node as Inline), [INLINES.ENTRY_HYPERLINK]: (node) => defaultInline(INLINES.ENTRY_HYPERLINK, node as Inline), [INLINES.RESOURCE_HYPERLINK]: (node) => defaultInlineResource(INLINES.RESOURCE_HYPERLINK, node as Inline), [INLINES.EMBEDDED_ENTRY]: (node) => defaultInline(INLINES.EMBEDDED_ENTRY, node as Inline), [INLINES.EMBEDDED_RESOURCE]: (node) => defaultInlineResource(INLINES.EMBEDDED_RESOURCE, node as Inline), [INLINES.HYPERLINK]: (node, next) => { const href = typeof node.data.uri === 'string' ? node.data.uri : ''; return `${next(node.content)}`; }, }; const defaultMarkRenderers: RenderMark = { [MARKS.BOLD]: (text) => `${text}`, [MARKS.ITALIC]: (text) => `${text}`, [MARKS.UNDERLINE]: (text) => `${text}`, [MARKS.CODE]: (text) => `${text}`, [MARKS.SUPERSCRIPT]: (text) => `${text}`, [MARKS.SUBSCRIPT]: (text) => `${text}`, [MARKS.STRIKETHROUGH]: (text) => `${text}`, }; const defaultBlockAsset = (node: Block) => { const fileUrl = node.data?.target?.fields?.file?.url ?? ''; const imageUrl = fileUrl.startsWith('//') ? `https:${fileUrl}` : fileUrl; const imgDescription = node.data?.target?.fields?.description ?? ''; return `${escape(imgDescription)}`; }; const defaultInline = (type: string, node: Inline) => `type: ${escapeHtml(type)} id: ${escapeHtml(node.data.target.sys.id)}`; const defaultInlineResource = (type: string, node: Inline) => `type: ${escapeHtml(type)} urn: ${escapeHtml(node.data?.target?.sys?.urn ?? '')}`; export type CommonNode = Text | Block | Inline; export interface Next { (nodes: CommonNode[]): string; } export interface NodeRenderer { (node: Block | Inline, next: Next): string; } export interface RenderNode { [k: string]: NodeRenderer; } export interface RenderMark { [k: string]: (text: string) => string; } export interface Options { /** * Node renderers */ renderNode?: RenderNode; /** * Mark renderers */ renderMark?: RenderMark; /** * Keep line breaks and multiple spaces */ preserveWhitespace?: boolean; /** * Strip empty trailing paragraph from the document */ stripEmptyTrailingParagraph?: boolean; } /** * Serialize a Contentful Rich Text `document` to an html string. */ export function documentToHtmlString( richTextDocument: Document, options: Partial = {}, ): string { if (!richTextDocument || !richTextDocument.content) { return ''; } // Strip empty trailing paragraph if enabled let processedDocument = richTextDocument; if (options.stripEmptyTrailingParagraph) { processedDocument = helpers.stripEmptyTrailingParagraphFromDocument(richTextDocument); } return nodeListToHtmlString(processedDocument.content, { renderNode: { ...defaultNodeRenderers, ...options.renderNode, }, renderMark: { ...defaultMarkRenderers, ...options.renderMark, }, preserveWhitespace: options.preserveWhitespace, }); } function nodeListToHtmlString( nodes: CommonNode[], { renderNode, renderMark, preserveWhitespace }: Options, ): string { return nodes .map((node) => nodeToHtmlString(node, { renderNode, renderMark, preserveWhitespace })) .join(''); } function nodeToHtmlString( node: CommonNode, { renderNode, renderMark, preserveWhitespace }: Options, ): string { if (helpers.isText(node)) { let nodeValue = escapeHtml(node.value); // If preserveWhitespace is true, handle line breaks and spaces. if (preserveWhitespace) { nodeValue = nodeValue .replace(/\n/g, '
    ') .replace(/ {2,}/g, (match) => ' '.repeat(match.length)); } if (node.marks.length > 0) { return node.marks.reduce((value: string, mark: Mark) => { if (!renderMark[mark.type]) { return value; } return renderMark[mark.type](value); }, nodeValue); } return nodeValue; } else { const nextNode: Next = (nodes) => nodeListToHtmlString(nodes, { renderMark, renderNode, preserveWhitespace }); if (!node.nodeType || !renderNode[node.nodeType]) { // TODO: Figure what to return when passed an unrecognized node. return ''; } return renderNode[node.nodeType](node, nextNode); } } ================================================ FILE: packages/rich-text-html-renderer/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "dist/types", "outDir": "dist/lib", "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/typings"] }, "include": ["src"] } ================================================ FILE: packages/rich-text-links/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [17.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.1.0...@contentful/rich-text-links@17.1.1) (2026-04-09) ### Bug Fixes - **rich-text-types:** improve esm compability [ZEND-7778] ([#1073](https://github.com/contentful/rich-text/issues/1073)) ([204aaec](https://github.com/contentful/rich-text/commit/204aaecc3893633c081986f44896e14272fa376a)) # [17.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.8...@contentful/rich-text-links@17.1.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [17.0.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.7...@contentful/rich-text-links@17.0.8) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.6...@contentful/rich-text-links@17.0.7) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.5...@contentful/rich-text-links@17.0.6) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.4...@contentful/rich-text-links@17.0.5) (2025-09-10) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.3...@contentful/rich-text-links@17.0.4) (2025-09-09) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.2...@contentful/rich-text-links@17.0.3) (2025-09-04) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.1...@contentful/rich-text-links@17.0.2) (2025-07-15) **Note:** Version bump only for package @contentful/rich-text-links ## [17.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@17.0.0...@contentful/rich-text-links@17.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-links # [17.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.10...@contentful/rich-text-links@17.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [16.7.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.9...@contentful/rich-text-links@16.7.10) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.8...@contentful/rich-text-links@16.7.9) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.7...@contentful/rich-text-links@16.7.8) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.6...@contentful/rich-text-links@16.7.7) (2024-07-24) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.5...@contentful/rich-text-links@16.7.6) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.4...@contentful/rich-text-links@16.7.5) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.3...@contentful/rich-text-links@16.7.4) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.2...@contentful/rich-text-links@16.7.3) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.1...@contentful/rich-text-links@16.7.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-links ## [16.7.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.7.0...@contentful/rich-text-links@16.7.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-links # [16.7.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.5...@contentful/rich-text-links@16.7.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [16.6.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.4...@contentful/rich-text-links@16.6.5) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-links ## [16.6.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.3...@contentful/rich-text-links@16.6.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-links ## [16.6.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.2...@contentful/rich-text-links@16.6.3) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-links ## [16.6.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.1...@contentful/rich-text-links@16.6.2) (2024-05-27) **Note:** Version bump only for package @contentful/rich-text-links ## [16.6.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.6.0...@contentful/rich-text-links@16.6.1) (2024-05-24) **Note:** Version bump only for package @contentful/rich-text-links # [16.6.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.5...@contentful/rich-text-links@16.6.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [16.5.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.4...@contentful/rich-text-links@16.5.5) (2024-03-04) **Note:** Version bump only for package @contentful/rich-text-links ## [16.5.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.3...@contentful/rich-text-links@16.5.4) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-links ## [16.5.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.2...@contentful/rich-text-links@16.5.3) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-links ## [16.5.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.1...@contentful/rich-text-links@16.5.2) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-links ## [16.5.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.5.0...@contentful/rich-text-links@16.5.1) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-links # [16.5.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.4.1...@contentful/rich-text-links@16.5.0) (2023-09-12) ### Features - add new nodes [DANTE-1157] ([#494](https://github.com/contentful/rich-text/issues/494)) ([b1fa6df](https://github.com/contentful/rich-text/commit/b1fa6dffc4bd7e1d367e9ce2cfddffe4fe07be47)) ## [16.4.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.4.0...@contentful/rich-text-links@16.4.1) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-links # [16.4.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.3.0...@contentful/rich-text-links@16.4.0) (2023-07-10) ### Features - allow document parameter to be nullable ([#471](https://github.com/contentful/rich-text/issues/471)) ([e50f8d2](https://github.com/contentful/rich-text/commit/e50f8d24c60a74f43f84f415a5c393038919efd2)) # [16.3.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.2.0...@contentful/rich-text-links@16.3.0) (2023-06-09) ### Features - change visitNodes to visit nodes in depth-first order ([#475](https://github.com/contentful/rich-text/issues/475)) ([ace5225](https://github.com/contentful/rich-text/commit/ace52253af4e83035075a466b7c97a08072e35ad)) # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.1.1...@contentful/rich-text-links@16.2.0) (2023-06-07) ### Features - add deduplicate option to getRichTextResourceLinks ([#470](https://github.com/contentful/rich-text/issues/470)) ([1141d5b](https://github.com/contentful/rich-text/commit/1141d5b6848e32efce198ad2d47b6442a9660aa0)) ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.1.0...@contentful/rich-text-links@16.1.1) (2023-05-31) ### Bug Fixes - prevent crash when a null value is passed as document ([#468](https://github.com/contentful/rich-text/issues/468)) ([3c8531c](https://github.com/contentful/rich-text/commit/3c8531cbcdde623c66c4b13d49fff73cad096871)) # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.0.4...@contentful/rich-text-links@16.1.0) (2023-05-26) ### Features - 🎸 add `getRichTextResourceLinks` ([#465](https://github.com/contentful/rich-text/issues/465)) ([5746ba6](https://github.com/contentful/rich-text/commit/5746ba652ae5eac2df27d05193e19aef9e85c438)) ## [16.0.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.0.3...@contentful/rich-text-links@16.0.4) (2023-05-04) **Note:** Version bump only for package @contentful/rich-text-links ## [16.0.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.0.2...@contentful/rich-text-links@16.0.3) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-links ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.0.1...@contentful/rich-text-links@16.0.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-links ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-links@16.0.0...@contentful/rich-text-links@16.0.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-links # 16.0.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ## 15.9.1 (2021-12-10) # 15.9.0 (2021-12-09) # 15.7.0 (2021-11-11) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ## 15.5.1 (2021-10-25) # 15.5.0 (2021-10-25) ## 15.3.6 (2021-09-15) ## 15.3.5 (2021-09-13) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.1.0 (2021-08-02) # 15.0.0 (2021-06-15) ## 14.1.2 (2020-11-02) ## 14.0.1 (2020-01-30) # 14.0.0 (2020-01-28) ### Bug Fixes - 🐛 detect links as children of nodes also having links ([565f423](https://github.com/contentful/rich-text/commit/565f423b2881c7bcfac40e9ee9ecb6a545fce047)) # 13.4.0 (2019-08-01) # 13.3.0 (2019-03-21) # 13.1.0 (2019-03-04) ### Bug Fixes - 🐛 Fix incorrect typings file path ([cca74d5](https://github.com/contentful/rich-text/commit/cca74d5594fb0594fd1117945e087afa2000877b)) ### Features - 🎸 Support filtering by node name in rich-text-links ([5ec0dda](https://github.com/contentful/rich-text/commit/5ec0dda07cdbe3d28027d28520c553b074ca699f)) # 13.0.0 (2019-01-22) ## 12.1.2 (2018-12-14) ## 12.0.3 (2018-12-05) ### Performance Improvements - ⚡️ prefer iterator unrolling to Array.from ([a887a92](https://github.com/contentful/rich-text/commit/a887a9213dee9dd6986cf313e67b94fd020d138b)) ## 12.0.1 (2018-12-04) # 12.0.0 (2018-11-29) ### Features - 🎸 `getRichTextEntityLinks` perf and signature changes ([fcd6d7f](https://github.com/contentful/rich-text/commit/fcd6d7f42e63df87ddb58d651b86a023d45f990e)) ### Performance Improvements - ⚡️ Add benchmark support ([ae278b7](https://github.com/contentful/rich-text/commit/ae278b7be515a52576af0a1abb6a4cefb05f71a7)) ### BREAKING CHANGES - Produces an entirely new return value and obviates the `linkType` parameter (all entities are grouped and returned by default). # 11.0.0 (2018-11-27) # 10.3.0 (2018-11-26) ### Features - 🎸 return all entity links when no linkType is provided ([02970ea](https://github.com/contentful/rich-text/commit/02970ea61b5b13d99c7b629902defe704c146b17)) # 10.2.0 (2018-11-19) ### Features - 🎸 Rename, refactor, docu. & cache r-t-links ([9cf6d14](https://github.com/contentful/rich-text/commit/9cf6d1460833300053cb8dde5a6e29b0ddf89964)) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/rich-text-links ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-links # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) **Note:** Version bump only for package @contentful/rich-text-links ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/rich-text-links # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/rich-text-links ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/rich-text-links # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) **Note:** Version bump only for package @contentful/rich-text-links ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/rich-text-links # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) **Note:** Version bump only for package @contentful/rich-text-links ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) **Note:** Version bump only for package @contentful/rich-text-links # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/rich-text-links # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/rich-text-links ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-links ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-links # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/rich-text-links ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-links # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-links ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) **Note:** Version bump only for package @contentful/rich-text-links ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) **Note:** Version bump only for package @contentful/rich-text-links ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-links ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-links ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-links # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/rich-text-links # [15.2.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.1.0) (2021-09-06) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) ### Bug Fixes - 🐛 detect links as children of nodes also having links ([565f423](https://github.com/contentful/rich-text/commit/565f423b2881c7bcfac40e9ee9ecb6a545fce047)) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) # [13.3.0](https://github.com/contentful/rich-text/compare/v13.2.0...v13.3.0) (2019-03-21) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) ### Bug Fixes - 🐛 Fix incorrect typings file path ([cca74d5](https://github.com/contentful/rich-text/commit/cca74d5594fb0594fd1117945e087afa2000877b)) ### Features - 🎸 Support filtering by node name in rich-text-links ([5ec0dda](https://github.com/contentful/rich-text/commit/5ec0dda07cdbe3d28027d28520c553b074ca699f)) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ## [12.0.3](https://github.com/contentful/rich-text/compare/v12.0.2...v12.0.3) (2018-12-05) ### Performance Improvements - ⚡️ prefer iterator unrolling to Array.from ([a887a92](https://github.com/contentful/rich-text/commit/a887a9213dee9dd6986cf313e67b94fd020d138b)) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) ### Features - 🎸 `getRichTextEntityLinks` perf and signature changes ([fcd6d7f](https://github.com/contentful/rich-text/commit/fcd6d7f42e63df87ddb58d651b86a023d45f990e)) ### Performance Improvements - ⚡️ Add benchmark support ([ae278b7](https://github.com/contentful/rich-text/commit/ae278b7be515a52576af0a1abb6a4cefb05f71a7)) ### BREAKING CHANGES - Produces an entirely new return value and obviates the `linkType` parameter (all entities are grouped and returned by default). # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) ### Features - 🎸 return all entity links when no linkType is provided ([02970ea](https://github.com/contentful/rich-text/commit/02970ea61b5b13d99c7b629902defe704c146b17)) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Rename, refactor, docu. & cache r-t-links ([9cf6d14](https://github.com/contentful/rich-text/commit/9cf6d1460833300053cb8dde5a6e29b0ddf89964)) ================================================ FILE: packages/rich-text-links/README.md ================================================ # rich-text-links Entity (entry and asset) link extraction utilities for the Contentful rich text field type. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/rich-text-links ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/rich-text-links ``` ## Usage ```javascript import { getRichTextEntityLinks } from '@contentful/rich-text-links'; const document = { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'embedded-entry-block', data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'yXmVKmaDBm8tRfQMwA0e', }, }, }, content: [], }, { nodeType: 'embedded-asset-block', data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'jNhaW0aSc6Hu74SHVMtq', }, }, }, content: [], }, ], }, ], }; getRichTextEntityLinks(document); /** * -> * { * Entry: [ * { linkType: 'Entry', type: 'Link', id: 'yXmVKmaDBm8tRfQMwA0e' } * ], * Asset: [ * { linkType: 'Asset', type: 'Link', id: 'jNhaW0aSc6Hu74SHVMtq' } * ] * } */ ``` ================================================ FILE: packages/rich-text-links/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-links/package.json ================================================ { "name": "@contentful/rich-text-links", "version": "17.1.1", "main": "dist/rich-text-links.es5.js", "module": "dist/rich-text-links.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/rich-text-links.esm.js", "require": "./dist/rich-text-links.es5.js", "default": "./dist/rich-text-links.es5.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "build": "tsc --module commonjs && rollup -c --bundleConfigAsCjs rollup.config.js", "prebuild": "rimraf dist", "start": "tsc && rollup -c --bundleConfigAsCjs rollup.config.js -w", "test": "jest" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7" }, "devDependencies": { "ts-jest": "^29.1.2" } } ================================================ FILE: packages/rich-text-links/rollup.config.js ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; export default config(outputFile, { external: Object.keys(dependencies), }); ================================================ FILE: packages/rich-text-links/src/__test__/index.test.ts ================================================ import { Document, BLOCKS, INLINES } from '@contentful/rich-text-types'; import { getAllRichTextResourceLinks, getRichTextEntityLinks, getRichTextResourceLinks, } from '../index'; import { Maybe } from '../types/utils'; function makeResourceLink(spaceId: string, entryId: string) { return { sys: { type: 'ResourceLink', linkType: 'Contentful:Entry', urn: `crn:contentful:::content:spaces/${spaceId}/entries/${entryId}`, }, }; } describe('getRichTextEntityLinks', () => { describe('returning top-level rich text links', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'foo', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'bar', }, }, }, content: [], }, ], }, ], }; it('returns all entity link objects', () => { expect(getRichTextEntityLinks(document)).toEqual({ Entry: [ { linkType: 'Entry', type: 'Link', id: 'foo', }, ], Asset: [ { linkType: 'Asset', type: 'Link', id: 'bar', }, ], }); }); it('returns an empty array if document parameter is `null`', () => { /** * This test is important! Not all consumers of this library have correct typescript types, * we know that not handling `null` gracefully will cause issues in production. */ const documentThatIsNull: Maybe = null; expect(getRichTextEntityLinks(documentThatIsNull)).toEqual({ Asset: [], Entry: [] }); }); it('returns an empty array if document parameter is `undefined`', () => { const documentThatIsUndefined: Maybe = undefined; expect(getRichTextEntityLinks(documentThatIsUndefined)).toEqual({ Asset: [], Entry: [] }); }); }); describe('returning rich text links at an arbitrary level of depth', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'bar', }, }, }, content: [], }, { nodeType: BLOCKS.OL_LIST, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'quux', }, }, }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: INLINES.ENTRY_HYPERLINK, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'baz', }, }, }, content: [], }, ], }, ], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'qux', }, }, }, content: [], }, ], }, { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [{ data: {}, content: [], nodeType: BLOCKS.PARAGRAPH }], }, { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: INLINES.EMBEDDED_ENTRY, data: { target: { sys: { id: 'inline-header-entry', type: 'Link', linkType: 'Entry' }, }, }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: INLINES.ENTRY_HYPERLINK, data: { target: { sys: { id: 'hyperlink-cell-entry', type: 'Link', linkType: 'Entry' }, }, }, content: [], }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: INLINES.ASSET_HYPERLINK, data: { target: { sys: { id: 'hyperlink-cell-asset', type: 'Link', linkType: 'Asset', }, }, }, content: [], }, ], }, ], nodeType: BLOCKS.PARAGRAPH, }, ], }, ], }, ], }, ], }; it('returns all entity link objects in the same order as defined in the document', () => { expect(getRichTextEntityLinks(document)).toEqual({ Entry: [ { linkType: 'Entry', type: 'Link', id: 'bar', }, { linkType: 'Entry', type: 'Link', id: 'baz', }, { linkType: 'Entry', type: 'Link', id: 'inline-header-entry', }, { linkType: 'Entry', type: 'Link', id: 'hyperlink-cell-entry', }, ], Asset: [ { linkType: 'Asset', type: 'Link', id: 'quux', }, { linkType: 'Asset', type: 'Link', id: 'qux', }, { linkType: 'Asset', type: 'Link', id: 'hyperlink-cell-asset', }, ], }); }); }); describe('handling redundant links', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'foo', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'foo', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'bar', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'bar', }, }, }, content: [], }, { nodeType: BLOCKS.OL_LIST, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'foo', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'bar', }, }, }, content: [], }, ], }, ], }, ], }, ], }; it('ignores all redundant links', () => { expect(getRichTextEntityLinks(document)).toEqual({ Entry: [ { linkType: 'Entry', type: 'Link', id: 'foo', }, ], Asset: [ { linkType: 'Asset', type: 'Link', id: 'bar', }, ], }); }); }); describe('filtering links of given type', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, data: { target: { sys: { linkType: 'Entry', type: 'Link', id: 'foo', }, }, }, content: [], }, { nodeType: BLOCKS.EMBEDDED_ASSET, data: { target: { sys: { linkType: 'Asset', type: 'Link', id: 'bar', }, }, }, content: [], }, ], }, ], }; it('ignores all links of different types', () => { expect(getRichTextEntityLinks(document, BLOCKS.EMBEDDED_ENTRY)).toEqual({ Entry: [ { linkType: 'Entry', type: 'Link', id: 'foo', }, ], Asset: [], }); }); }); }); describe(`getRichTextResourceLinks`, () => { it('returns top-level rich text resource-links', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('foo', 'bar'), }, content: [], }, ], }, ], }; expect(getRichTextResourceLinks(document, BLOCKS.EMBEDDED_RESOURCE)).toEqual([ makeResourceLink('foo', 'bar'), ]); }); it('returns an empty array if document parameter is `null`', () => { const documentThatIsNull: Maybe = null; expect(getRichTextResourceLinks(documentThatIsNull, BLOCKS.EMBEDDED_RESOURCE)).toEqual([]); }); it('returns an empty array if document parameter is `undefined`', () => { const documentThatIsUndefined: Maybe = undefined; expect(getRichTextResourceLinks(documentThatIsUndefined, BLOCKS.EMBEDDED_RESOURCE)).toEqual([]); }); it(`returns all ResourceLinks from multiple levels in the same order as defined in the document`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.OL_LIST, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [{ data: {}, content: [], nodeType: BLOCKS.PARAGRAPH }], }, { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-2'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-3'), }, content: [], }, ], }, ], }, ], }, ], }, ], }; expect(getRichTextResourceLinks(document, BLOCKS.EMBEDDED_RESOURCE)).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), makeResourceLink('space-2', 'entry-1'), makeResourceLink('space-2', 'entry-2'), makeResourceLink('space-2', 'entry-3'), ]); }); it(`de-duplicates links based on urn`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, ], }, ], }; expect(getRichTextResourceLinks(document, BLOCKS.EMBEDDED_RESOURCE)).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), ]); }); it(`should return duplicate links if the deduplicate option value is passed as false`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, ], }, ], }; expect( getRichTextResourceLinks(document, BLOCKS.EMBEDDED_RESOURCE, { deduplicate: false }), ).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), makeResourceLink('space-1', 'entry-1'), ]); }); }); describe(`getAllRichTextResourceLinks`, () => { it('returns top-level rich text resource-links', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('foo', 'bar'), }, content: [], }, { nodeType: INLINES.RESOURCE_HYPERLINK, data: { target: makeResourceLink('foo2', 'bar'), }, content: [], }, { nodeType: INLINES.EMBEDDED_RESOURCE, data: { target: makeResourceLink('foo3', 'bar'), }, content: [], }, ], }, ], }; expect(getAllRichTextResourceLinks(document)).toEqual([ makeResourceLink('foo', 'bar'), makeResourceLink('foo2', 'bar'), makeResourceLink('foo3', 'bar'), ]); }); it('returns an empty array if document parameter is `null`', () => { const documentThatIsNull: Maybe = null; expect(getAllRichTextResourceLinks(documentThatIsNull)).toEqual([]); }); it('returns an empty array if document parameter is `undefined`', () => { const documentThatIsUndefined: Maybe = undefined; expect(getAllRichTextResourceLinks(documentThatIsUndefined)).toEqual([]); }); it(`returns all ResourceLinks from multiple levels in the same order as defined in the document`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.OL_LIST, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [{ data: {}, content: [], nodeType: BLOCKS.PARAGRAPH }], }, { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-2'), }, content: [], }, { nodeType: INLINES.RESOURCE_HYPERLINK, data: { target: makeResourceLink('space-2', 'entry'), }, content: [], }, { nodeType: INLINES.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-3', 'entry'), }, content: [], }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-3'), }, content: [], }, ], }, ], }, ], }, ], }, ], }; expect(getAllRichTextResourceLinks(document)).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), makeResourceLink('space-2', 'entry-1'), makeResourceLink('space-2', 'entry-2'), makeResourceLink('space-2', 'entry'), makeResourceLink('space-3', 'entry'), makeResourceLink('space-2', 'entry-3'), ]); }); it(`de-duplicates links based on urn`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: INLINES.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, { nodeType: INLINES.RESOURCE_HYPERLINK, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, ], }, ], }; expect(getAllRichTextResourceLinks(document)).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), makeResourceLink('space-2', 'entry-1'), ]); }); it(`should return duplicate links if the deduplicate option value is passed as false`, () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-2'), }, content: [], }, { nodeType: BLOCKS.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-1', 'entry-1'), }, content: [], }, { nodeType: INLINES.EMBEDDED_RESOURCE, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, { nodeType: INLINES.RESOURCE_HYPERLINK, data: { target: makeResourceLink('space-2', 'entry-1'), }, content: [], }, ], }, ], }; expect(getAllRichTextResourceLinks(document, { deduplicate: false })).toEqual([ makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-1', 'entry-2'), makeResourceLink('space-1', 'entry-1'), makeResourceLink('space-2', 'entry-1'), makeResourceLink('space-2', 'entry-1'), ]); }); }); ================================================ FILE: packages/rich-text-links/src/index.ts ================================================ import { Block, BLOCKS, Document, Inline, INLINES, Link, Node, NodeData, ResourceLink, } from '@contentful/rich-text-types'; import { Maybe } from './types/utils'; export type EntityLinks = { [type in EntityType]: EntityLink[] }; export type EntityLinkMaps = { [type in EntityType]: Map }; export type EntityType = 'Entry' | 'Asset'; export type EntityLink = SysObject['sys']; export type SysObject = Link; export type EntityLinkNodeData = { target: SysObject; }; // spare upstream dependencies the need to use rich-text-types type AcceptedResourceLinkTypes = | `${BLOCKS.EMBEDDED_RESOURCE}` | `${INLINES.EMBEDDED_RESOURCE}` | `${INLINES.RESOURCE_HYPERLINK}`; /** * Extracts all links no matter the entity they are pointing to. */ export function getRichTextResourceLinks( document: Maybe, nodeType: AcceptedResourceLinkTypes, { deduplicate = true }: { deduplicate?: boolean } = {}, ): ResourceLink[] { const isValidType = (actualNodeType: string, data: NodeData) => actualNodeType === nodeType && data.target?.sys?.type === 'ResourceLink'; return getValidResourceLinks(document, isValidType, deduplicate); } export function getAllRichTextResourceLinks( document: Maybe, { deduplicate = true }: { deduplicate?: boolean } = {}, ): ResourceLink[] { const nodeTypes: string[] = [ BLOCKS.EMBEDDED_RESOURCE, INLINES.EMBEDDED_RESOURCE, INLINES.RESOURCE_HYPERLINK, ]; const isValidType = (actualNodeType: string, data: NodeData) => nodeTypes.includes(actualNodeType) && data.target?.sys?.type === 'ResourceLink'; return getValidResourceLinks(document, isValidType, deduplicate); } function getValidResourceLinks( document: Maybe, isValidType: (actualNodeType: string, data: NodeData) => boolean, deduplicate: boolean, ): ResourceLink[] { const links = new Map(); visitNodes(document, (node) => { if (isValidType(node.nodeType, node.data)) { const key = deduplicate ? node.data.target.sys.urn : links.size; links.set(key, node.data.target); } }); return iteratorToArray(links.values()); } /** * Extracts entity links from a Rich Text document. */ export function getRichTextEntityLinks( /** * An instance of a Rich Text Document. */ document: Maybe, /** * Node type. Only the entity links with given node type will be extracted. */ type?: string, ): EntityLinks { const entityLinks: EntityLinkMaps = { Entry: new Map(), Asset: new Map(), }; // const content = (document && document.content) || ([] as Node[]); const addLink = ({ data, nodeType }: Node) => { const hasRequestedNodeType = !type || nodeType === type; if (hasRequestedNodeType && isLinkObject(data)) { entityLinks[data.target.sys.linkType].set(data.target.sys.id, data.target.sys); } }; visitNodes(document, addLink); return { Entry: iteratorToArray(entityLinks.Entry.values()), Asset: iteratorToArray(entityLinks.Asset.values()), }; } function isContentNode(node: Node): node is Inline | Block { return 'content' in node && Array.isArray(node.content); } function visitNodes(startNode: Maybe, onVisit: (node: Node) => void): void { /** * Do not remove this null check as consumers of this library do not all have good Typescript types */ const toCrawl: Node[] = startNode ? [startNode] : []; while (toCrawl.length > 0) { const node = toCrawl.pop(); onVisit(node); if (isContentNode(node)) { for (let i = node.content.length - 1; i >= 0; i--) { toCrawl.push(node.content[i]); } } } } function isLinkObject(data: NodeData): data is EntityLinkNodeData { const sys = data && (data.target as SysObject) && (data.target.sys as EntityLink); if (!sys) { return false; } const isValidLinkObject = typeof sys.id === 'string' && sys.type === 'Link'; if (!isValidLinkObject) { return false; } const isEntityLink = sys.linkType === 'Entry' || sys.linkType === 'Asset'; return isEntityLink; } /** * Used to convert the EntityLink iterators stored by the EntityLinkMap values * into a client-friendly array form. * * Alternately we could do: * 1) Array.from(EntityLinkMap) * 2) [...EntityLinkMap] * * #1, although idiomatic, is about half as slow as #2. * * #2, while faster than #1, requires transpilation of the iterator protocol[1], * which in turn is still only about half as fast as the approach below. * * [1] See https://blog.mariusschulz.com/2017/06/30/typescript-2-3-downlevel-iteration-for-es3-es5. */ function iteratorToArray(iterator: IterableIterator): T[] { const result = []; // eslint-disable-next-line no-constant-condition while (true) { const { value, done } = iterator.next(); if (done) { break; } result.push(value); } return result; } ================================================ FILE: packages/rich-text-links/src/types/utils.ts ================================================ export type Maybe = T | null | undefined; ================================================ FILE: packages/rich-text-links/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "dist/types", "outDir": "dist/lib", "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/typings"] }, "include": ["src"] } ================================================ FILE: packages/rich-text-plain-text-renderer/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [17.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.2.0...@contentful/rich-text-plain-text-renderer@17.2.1) (2026-04-09) ### Bug Fixes - **rich-text-types:** improve esm compability [ZEND-7778] ([#1073](https://github.com/contentful/rich-text/issues/1073)) ([204aaec](https://github.com/contentful/rich-text/commit/204aaecc3893633c081986f44896e14272fa376a)) # [17.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.6...@contentful/rich-text-plain-text-renderer@17.2.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [17.1.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.5...@contentful/rich-text-plain-text-renderer@17.1.6) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [17.1.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.4...@contentful/rich-text-plain-text-renderer@17.1.5) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [17.1.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.3...@contentful/rich-text-plain-text-renderer@17.1.4) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [17.1.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.2...@contentful/rich-text-plain-text-renderer@17.1.3) (2025-09-10) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [17.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.1...@contentful/rich-text-plain-text-renderer@17.1.2) (2025-09-09) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [17.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.1.0...@contentful/rich-text-plain-text-renderer@17.1.1) (2025-09-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [17.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.0.1...@contentful/rich-text-plain-text-renderer@17.1.0) (2025-07-15) ### Features - add an option to strip empty trailing paragraphs [TOL-3193] ([#892](https://github.com/contentful/rich-text/issues/892)) ([9beff0e](https://github.com/contentful/rich-text/commit/9beff0e4cba3e79dc68e6a0725e843c5d642eb87)) ## [17.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@17.0.0...@contentful/rich-text-plain-text-renderer@17.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [17.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.10...@contentful/rich-text-plain-text-renderer@17.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [16.2.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.9...@contentful/rich-text-plain-text-renderer@16.2.10) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.8...@contentful/rich-text-plain-text-renderer@16.2.9) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.7...@contentful/rich-text-plain-text-renderer@16.2.8) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.6...@contentful/rich-text-plain-text-renderer@16.2.7) (2024-07-24) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.5...@contentful/rich-text-plain-text-renderer@16.2.6) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.4...@contentful/rich-text-plain-text-renderer@16.2.5) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.3...@contentful/rich-text-plain-text-renderer@16.2.4) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.2...@contentful/rich-text-plain-text-renderer@16.2.3) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.1...@contentful/rich-text-plain-text-renderer@16.2.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.2.0...@contentful/rich-text-plain-text-renderer@16.2.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.5...@contentful/rich-text-plain-text-renderer@16.2.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [16.1.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.4...@contentful/rich-text-plain-text-renderer@16.1.5) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.1.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.3...@contentful/rich-text-plain-text-renderer@16.1.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.1.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.2...@contentful/rich-text-plain-text-renderer@16.1.3) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.1...@contentful/rich-text-plain-text-renderer@16.1.2) (2024-05-27) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.1.0...@contentful/rich-text-plain-text-renderer@16.1.1) (2024-05-24) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.13...@contentful/rich-text-plain-text-renderer@16.1.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [16.0.13](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.12...@contentful/rich-text-plain-text-renderer@16.0.13) (2024-03-06) ### Bug Fixes - correctly handle edge case [ZEND-4744] ([#541](https://github.com/contentful/rich-text/issues/541)) ([819b07d](https://github.com/contentful/rich-text/commit/819b07da3a00e6d8101e49cb0e678e3a21b4cd93)) ## [16.0.12](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.11...@contentful/rich-text-plain-text-renderer@16.0.12) (2024-03-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.11](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.10...@contentful/rich-text-plain-text-renderer@16.0.11) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.9...@contentful/rich-text-plain-text-renderer@16.0.10) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.8...@contentful/rich-text-plain-text-renderer@16.0.9) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.7...@contentful/rich-text-plain-text-renderer@16.0.8) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.6...@contentful/rich-text-plain-text-renderer@16.0.7) (2023-09-12) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.5...@contentful/rich-text-plain-text-renderer@16.0.6) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.4...@contentful/rich-text-plain-text-renderer@16.0.5) (2023-05-26) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.3...@contentful/rich-text-plain-text-renderer@16.0.4) (2023-05-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.2...@contentful/rich-text-plain-text-renderer@16.0.3) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.1...@contentful/rich-text-plain-text-renderer@16.0.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-plain-text-renderer@16.0.0...@contentful/rich-text-plain-text-renderer@16.0.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # 16.0.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ## 15.9.1 (2021-12-10) # 15.9.0 (2021-12-09) # 15.7.0 (2021-11-11) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ## 15.5.1 (2021-10-25) # 15.5.0 (2021-10-25) ## 15.3.6 (2021-09-15) ## 15.3.5 (2021-09-13) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.1.0 (2021-08-02) # 15.0.0 (2021-06-15) ## 14.1.2 (2020-11-02) ## 14.0.1 (2020-01-30) # 14.0.0 (2020-01-28) # 13.4.0 (2019-08-01) # 13.1.0 (2019-03-04) # 13.0.0 (2019-01-22) ### Bug Fixes - 🐛 use named export instead of default ([57bf365](https://github.com/contentful/rich-text/commit/57bf36510f1021f5208a3e33242cceea97940d68)) ### BREAKING CHANGES - removes default export from 'rich-text-plain-text-renderer' ## 12.1.2 (2018-12-14) ## 12.0.3 (2018-12-05) ### Bug Fixes - **readme:** mark types in readme examples ([d997b56](https://github.com/contentful/rich-text/commit/d997b56f2b8c32b2e5b478ab5444757203e2c703)) ## 12.0.2 (2018-12-04) ### Bug Fixes - 🐛 update path to typings ([ce5544f](https://github.com/contentful/rich-text/commit/ce5544f58712dc6a18aadda523d0c0357a66c8a5)) ## 12.0.1 (2018-12-04) # 12.0.0 (2018-11-29) # 11.0.0 (2018-11-27) # 10.3.0 (2018-11-26) # 10.2.0 (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # 10.1.0 (2018-11-16) ## 10.0.5 (2018-11-12) ### Bug Fixes - 🐛 handle case of missing rootNode / rootNode.content ([e2434c7](https://github.com/contentful/rich-text/commit/e2434c7f5e1401f1188dd778c2aa9a108cea5596)) ## 10.0.1 (2018-11-08) # 10.0.0 (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## 9.0.2 (2018-10-31) ## 9.0.1 (2018-10-31) # 9.0.0 (2018-10-30) ### Bug Fixes - 🐛 Fix block divisor provision ([3aafb7a](https://github.com/contentful/rich-text/commit/3aafb7a756d8b6f96ef733eb299091404b3391aa)) - 🐛 remove exclusive mocha test ([5ff5d0e](https://github.com/contentful/rich-text/commit/5ff5d0e35024b7dccda065f54ae34138d9416525)) ### BREAKING CHANGES - Fundamentally alters the plain text renderer behavior. (For the better!) ## 8.0.3 (2018-10-30) ## 8.0.2 (2018-10-29) ## 8.0.1 (2018-10-26) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/rich-text-plain-text-renderer # [15.2.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.1.0) (2021-09-06) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) ### Bug Fixes - 🐛 use named export instead of default ([57bf365](https://github.com/contentful/rich-text/commit/57bf36510f1021f5208a3e33242cceea97940d68)) ### BREAKING CHANGES - removes default export from 'rich-text-plain-text-renderer' ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ## [12.0.3](https://github.com/contentful/rich-text/compare/v12.0.2...v12.0.3) (2018-12-05) ### Bug Fixes - **readme:** mark types in readme examples ([d997b56](https://github.com/contentful/rich-text/commit/d997b56f2b8c32b2e5b478ab5444757203e2c703)) ## [12.0.2](https://github.com/contentful/rich-text/compare/v12.0.1...v12.0.2) (2018-12-04) ### Bug Fixes - 🐛 update path to typings ([ce5544f](https://github.com/contentful/rich-text/commit/ce5544f58712dc6a18aadda523d0c0357a66c8a5)) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # [10.1.0](https://github.com/contentful/rich-text/compare/v10.0.5...v10.1.0) (2018-11-16) ## [10.0.5](https://github.com/contentful/rich-text/compare/v10.0.4...v10.0.5) (2018-11-12) ### Bug Fixes - 🐛 handle case of missing rootNode / rootNode.content ([e2434c7](https://github.com/contentful/rich-text/commit/e2434c7f5e1401f1188dd778c2aa9a108cea5596)) ## [10.0.1](https://github.com/contentful/rich-text/compare/v10.0.0...v10.0.1) (2018-11-08) # [10.0.0](https://github.com/contentful/rich-text/compare/v9.0.2...v10.0.0) (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## [9.0.2](https://github.com/contentful/rich-text/compare/v9.0.1...v9.0.2) (2018-10-31) ## [9.0.1](https://github.com/contentful/rich-text/compare/v9.0.0...v9.0.1) (2018-10-31) # [9.0.0](https://github.com/contentful/rich-text/compare/v8.0.3...v9.0.0) (2018-10-30) ### Bug Fixes - 🐛 Fix block divisor provision ([3aafb7a](https://github.com/contentful/rich-text/commit/3aafb7a756d8b6f96ef733eb299091404b3391aa)) - 🐛 remove exclusive mocha test ([5ff5d0e](https://github.com/contentful/rich-text/commit/5ff5d0e35024b7dccda065f54ae34138d9416525)) ### BREAKING CHANGES - Fundamentally alters the plain text renderer behavior. (For the better!) ## [8.0.3](https://github.com/contentful/rich-text/compare/v8.0.2...v8.0.3) (2018-10-30) ## [8.0.2](https://github.com/contentful/rich-text/compare/v8.0.1...v8.0.2) (2018-10-29) ## [8.0.1](https://github.com/contentful/rich-text/compare/v8.0.0...v8.0.1) (2018-10-26) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ================================================ FILE: packages/rich-text-plain-text-renderer/README.md ================================================ # rich-text-plain-text-renderer Plain text renderer for the Rich Text document. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/rich-text-plain-text-renderer ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/rich-text-plain-text-renderer ``` ## Usage ```javascript import { documentToPlainTextString } from '@contentful/rich-text-plain-text-renderer'; const document = { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Hello', marks: [{ type: 'bold' }], data: {}, }, { nodeType: 'text', value: ' world!', marks: [{ type: 'italic' }], data: {}, }, ], }, ], }; documentToPlainTextString(document); // -> Hello world! ``` ================================================ FILE: packages/rich-text-plain-text-renderer/bin/benchmark/get-rich-text-entity-links.ts ================================================ import { BLOCKS, Document, Node } from '@contentful/rich-text-types'; import { documentToPlainTextString as docToString } from '../../src/index'; /** * Implements just a subset of _.times since all we're really doing here is * cleanly filling the field with a bunch of junk data. */ function times(n: number, node: N): N[] { return new Array(n).fill(node); } const richTextField: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: times(8000, { nodeType: BLOCKS.UL_LIST, data: {}, content: times(5, { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: times(5, { nodeType: 'text', data: {}, marks: [], value: 'x', }), }, ], }), }), }, ], }; export const documentToPlainTextString = () => docToString(richTextField, ''); ================================================ FILE: packages/rich-text-plain-text-renderer/bin/tsconfig.json ================================================ { "extends": "../../../bin/tsconfig.json" } ================================================ FILE: packages/rich-text-plain-text-renderer/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-plain-text-renderer/package.json ================================================ { "name": "@contentful/rich-text-plain-text-renderer", "version": "17.2.1", "main": "dist/rich-text-plain-text-renderer.es5.js", "module": "dist/rich-text-plain-text-renderer.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/rich-text-plain-text-renderer.esm.js", "require": "./dist/rich-text-plain-text-renderer.es5.js", "default": "./dist/rich-text-plain-text-renderer.es5.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "build": "tsc --module commonjs && rollup -c --bundleConfigAsCjs rollup.config.js", "prebuild": "rimraf dist", "start": "tsc && rollup -c --bundleConfigAsCjs rollup.config.js -w", "test": "jest" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7" }, "devDependencies": { "ts-jest": "^29.1.2" } } ================================================ FILE: packages/rich-text-plain-text-renderer/rollup.config.js ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; export default config(outputFile, { external: Object.keys(dependencies), }); ================================================ FILE: packages/rich-text-plain-text-renderer/src/__test__/index.test.ts ================================================ import { Document, BLOCKS, INLINES } from '@contentful/rich-text-types'; import { documentToPlainTextString } from '../index'; describe('documentToPlainTextString', () => { it('returns empty string when given an empty document', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [], }; expect(documentToPlainTextString(document)).toEqual(''); }); it('handles a simple case', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, value: 'Trout is a', marks: [], }, { nodeType: 'text', data: {}, value: ' seafood d', marks: [{ type: 'italic' }], }, { nodeType: 'text', data: {}, value: 'elicacy.', marks: [], }, ], }, ], }; expect(documentToPlainTextString(document)).toEqual('Trout is a seafood delicacy.'); }); describe('rendering deeply nested documents', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'This is text. ', data: {}, marks: [{ type: 'bold' }], }, { nodeType: 'text', value: '', data: {}, marks: [], }, { nodeType: 'text', value: 'This is text with some marks.', data: {}, marks: [{ type: 'italic' }], }, { nodeType: 'text', value: ' ', data: {}, marks: [], }, { nodeType: INLINES.HYPERLINK, content: [ { nodeType: 'text', value: 'This is text from a bolded hyperlink.', data: {}, marks: [{ type: 'bold' }], }, ], data: { url: 'https://example.com', title: 'qux', }, }, ], }, { nodeType: BLOCKS.UL_LIST, data: {}, content: [ { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'This is a list element in a separate block ', data: {}, marks: [], }, { nodeType: INLINES.HYPERLINK, content: [ { nodeType: 'text', value: 'with a link with marks.', data: {}, marks: [], }, ], data: { url: 'https://google.com', title: 'woo', }, }, ], }, ], }, { nodeType: BLOCKS.LIST_ITEM, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'This is a separate list element in the same block.', data: {}, marks: [], }, ], }, ], }, ], }, ], }; it('handles nested nodes gracefully', () => { expect(documentToPlainTextString(document)).toEqual( [ 'This is text.', 'This is text with some marks.', 'This is text from a bolded hyperlink.', 'This is a list element in a separate block with a link with marks.', 'This is a separate list element in the same block.', ].join(' '), ); }); it('defers to the user-supplied block divisor', () => { expect(documentToPlainTextString(document, '\n\n')).toEqual( [ 'This is text. This is text with some marks. This is text from a bolded hyperlink.', 'This is a list element in a separate block with a link with marks.', 'This is a separate list element in the same block.', ].join('\n\n'), ); }); }); }); describe('stripEmptyTrailingParagraph', () => { it('strips empty trailing paragraph when enabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: true, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual('Hello world'); }); it('does not strip empty trailing paragraph when disabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: false, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual('Hello world '); }); it('does not strip empty trailing paragraph when it is the only child', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: true, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual(''); }); it('does not strip non-empty trailing paragraph', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Not empty', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: true, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual('Hello world Not empty'); }); it('does not strip trailing paragraph with multiple text nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: true, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual('Hello world '); }); it('does not strip trailing non-paragraph node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.HEADING_1, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options = { stripEmptyTrailingParagraph: true, }; const result = documentToPlainTextString(document, ' ', options); expect(result).toEqual('Hello world '); }); }); ================================================ FILE: packages/rich-text-plain-text-renderer/src/index.ts ================================================ import { Block, Inline, Node, helpers, BLOCKS, Document } from '@contentful/rich-text-types'; export interface Options { /** * Strip empty trailing paragraph from the document */ stripEmptyTrailingParagraph?: boolean; } /** * Returns the text value of a rich text document. * * NB: This can be applied to non text node of a structured text document, * hence the flexible typing. */ export function documentToPlainTextString( rootNode: Block | Inline, blockDivisor: string = ' ', options: Options = {}, ): string { if (!rootNode || !rootNode.content || !Array.isArray(rootNode.content)) { /** * Handles edge cases, such as when the value is not set in the CMA or the * field has not been properly validated, e.g. because of a user extension. * Note that we are nevertheless strictly type-casting `rootNode` as * Block | Inline. Valid rich text documents (and their branch block nodes) * should never lack a Node[] `content` property. */ return ''; } // Strip empty trailing paragraph if enabled and rootNode is a Document let processedRootNode = rootNode; if (rootNode.nodeType === BLOCKS.DOCUMENT && options.stripEmptyTrailingParagraph) { processedRootNode = helpers.stripEmptyTrailingParagraphFromDocument(rootNode as Document); } /** * Algorithm notes: We only want to apply spacing when a node is part of a * sequence. This is tricky because nodes can often be deeply nested within * non-semantic content arrays. For example, to get the text value of an * unordered list, we have to traverse like so: * * { * nodeType: BLOCKS.UL_LIST, * data: {}, * content: [ * { * nodeType: BLOCKS.LIST_ITEM, * data: {}, * content: [{ * nodeType: BLOCKS.PARAGRAPH, * data: {}, * content: [ * { nodeType: 'text', data: {}, value: 'List ', marks: [] }, * { nodeType: 'text', data: {}, value: 'item', marks: [{ type: 'bold' }] } * ] * }] * }, * { * nodeType: BLOCKS.LIST_ITEM, * data: {}, * content: [{ * nodeType: BLOCKS.PARAGRAPH, * data: {}, * content: [ * { nodeType: 'text', data: {}, value: 'Another list item', marks: [] } * ] * }] * }, * { * nodeType: BLOCKS.LIST_ITEM, * data: {}, * content: [{ * nodeType: BLOCKS.HR, * data: {}, * content: [], * }] * }, * { * nodeType: BLOCKS.LIST_ITEM, * data: {}, * content: [{ * nodeType: BLOCKS.PARAGRAPH, * data: * content: [ * { nodeType: 'text', data: {}, value: 'Yet another list item', marks: [] } * ] * }] * }, * }] * } * * We want there to be a space between 'List item' and 'Another list item' (to * denote a visual line break, which conventionally appears between non-text * node sequences) but not a redundant space between 'List ' and 'item'. * Moreover, we want just a _singular_ space between 'Another list item' and * 'Yet another list item' - the non-semantic HR between the two nodes should * not denote an additional space. */ return (processedRootNode as Block).content.reduce( (acc: string, node: Node, i: number): string => { let nodeTextValue: string; if (helpers.isText(node)) { nodeTextValue = node.value; } else if (helpers.isBlock(node) || helpers.isInline(node)) { nodeTextValue = documentToPlainTextString(node, blockDivisor, options); if (!nodeTextValue.length) { return acc; } } const nextNode = processedRootNode.content[i + 1]; const isNextNodeBlock = nextNode && helpers.isBlock(nextNode); const divisor = isNextNodeBlock ? blockDivisor : ''; return acc + nodeTextValue + divisor; }, '', ); } ================================================ FILE: packages/rich-text-plain-text-renderer/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "dist/types", "outDir": "dist/lib", "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/typings"] }, "include": ["src"] } ================================================ FILE: packages/rich-text-react-renderer/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [16.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.2.0...@contentful/rich-text-react-renderer@16.2.1) (2026-04-09) ### Bug Fixes - **rich-text-types:** improve esm compability [ZEND-7778] ([#1073](https://github.com/contentful/rich-text/issues/1073)) ([204aaec](https://github.com/contentful/rich-text/commit/204aaecc3893633c081986f44896e14272fa376a)) # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.6...@contentful/rich-text-react-renderer@16.2.0) (2026-04-08) ### Features - add esm support [ZEND-7778] ([#1069](https://github.com/contentful/rich-text/issues/1069)) ([8c320bd](https://github.com/contentful/rich-text/commit/8c320bde7fa313572bdad84b91a6fd12224afc3a)), closes [#1068](https://github.com/contentful/rich-text/issues/1068) ## [16.1.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.5...@contentful/rich-text-react-renderer@16.1.6) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [16.1.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.4...@contentful/rich-text-react-renderer@16.1.5) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [16.1.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.3...@contentful/rich-text-react-renderer@16.1.4) (2025-09-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [16.1.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.2...@contentful/rich-text-react-renderer@16.1.3) (2025-09-10) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [16.1.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.1...@contentful/rich-text-react-renderer@16.1.2) (2025-09-09) ### Bug Fixes - bundle issues after swc migration [TOL-3396] ([#924](https://github.com/contentful/rich-text/issues/924)) ([22245f0](https://github.com/contentful/rich-text/commit/22245f0648b0164ad29dd0dfe789cd83f78283e8)) ## [16.1.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.1.0...@contentful/rich-text-react-renderer@16.1.1) (2025-09-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.0.2...@contentful/rich-text-react-renderer@16.1.0) (2025-07-15) ### Features - add an option to strip empty trailing paragraphs [TOL-3193] ([#892](https://github.com/contentful/rich-text/issues/892)) ([9beff0e](https://github.com/contentful/rich-text/commit/9beff0e4cba3e79dc68e6a0725e843c5d642eb87)) ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.0.1...@contentful/rich-text-react-renderer@16.0.2) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@16.0.0...@contentful/rich-text-react-renderer@16.0.1) (2024-12-11) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [16.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.11...@contentful/rich-text-react-renderer@16.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [15.22.11](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.10...@contentful/rich-text-react-renderer@15.22.11) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.10](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.9...@contentful/rich-text-react-renderer@15.22.10) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.9](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.8...@contentful/rich-text-react-renderer@15.22.9) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.8](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.7...@contentful/rich-text-react-renderer@15.22.8) (2024-07-24) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.6...@contentful/rich-text-react-renderer@15.22.7) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.5...@contentful/rich-text-react-renderer@15.22.6) (2024-07-17) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.4...@contentful/rich-text-react-renderer@15.22.5) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.3...@contentful/rich-text-react-renderer@15.22.4) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.2...@contentful/rich-text-react-renderer@15.22.3) (2024-07-16) ### Bug Fixes - preserveWhitespace option conflicting with renderText ([#618](https://github.com/contentful/rich-text/issues/618)) ([c146dc4](https://github.com/contentful/rich-text/commit/c146dc4a89fa980f877285ab2b0b9c1114236fc9)) ## [15.22.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.1...@contentful/rich-text-react-renderer@15.22.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.22.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.22.0...@contentful/rich-text-react-renderer@15.22.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.22.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.21.4...@contentful/rich-text-react-renderer@15.22.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [15.21.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.21.3...@contentful/rich-text-react-renderer@15.21.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.21.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.21.2...@contentful/rich-text-react-renderer@15.21.3) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.21.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.21.1...@contentful/rich-text-react-renderer@15.21.2) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.21.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.21.0...@contentful/rich-text-react-renderer@15.21.1) (2024-05-27) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.21.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.20.0...@contentful/rich-text-react-renderer@15.21.0) (2024-05-24) ### Features - add strikethrough support ([#562](https://github.com/contentful/rich-text/issues/562)) ([b87d0c3](https://github.com/contentful/rich-text/commit/b87d0c31bccb4012745c0479b2b5c92fc28c1e91)) # [15.20.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.6...@contentful/rich-text-react-renderer@15.20.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [15.19.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.5...@contentful/rich-text-react-renderer@15.19.6) (2024-03-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.19.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.4...@contentful/rich-text-react-renderer@15.19.5) (2024-03-04) ### Bug Fixes - 🐛 handle multiple space rendering when using preserveWhitespace ([#531](https://github.com/contentful/rich-text/issues/531)) ([982b21b](https://github.com/contentful/rich-text/commit/982b21b13736d8b1dcdb652b4eb41c7ab279ee33)), closes [#514](https://github.com/contentful/rich-text/issues/514) ## [15.19.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.3...@contentful/rich-text-react-renderer@15.19.4) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.19.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.2...@contentful/rich-text-react-renderer@15.19.3) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.19.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.1...@contentful/rich-text-react-renderer@15.19.2) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.19.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.19.0...@contentful/rich-text-react-renderer@15.19.1) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.19.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.18.0...@contentful/rich-text-react-renderer@15.19.0) (2023-10-24) ### Features - add default renderers for new resource nodes ([#504](https://github.com/contentful/rich-text/issues/504)) ([f748e6b](https://github.com/contentful/rich-text/commit/f748e6b0f0e22ce3809ca8881438b84b1baafb8f)) # [15.18.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.17.2...@contentful/rich-text-react-renderer@15.18.0) (2023-10-02) ### Features - preserve formatting linebreak whitespace ([#497](https://github.com/contentful/rich-text/issues/497)) ([e62ab92](https://github.com/contentful/rich-text/commit/e62ab92f6320d970f921e751d1753cd290224224)) ## [15.17.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.17.1...@contentful/rich-text-react-renderer@15.17.2) (2023-09-12) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.17.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.17.0...@contentful/rich-text-react-renderer@15.17.1) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.17.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.5...@contentful/rich-text-react-renderer@15.17.0) (2023-06-09) ### Features - add default renderer for the embedded-resource-block node ([#474](https://github.com/contentful/rich-text/issues/474)) ([844c011](https://github.com/contentful/rich-text/commit/844c011ef3d6f2887d82a39784c6fb5672c6e065)) ## [15.16.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.4...@contentful/rich-text-react-renderer@15.16.5) (2023-05-26) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.16.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.3...@contentful/rich-text-react-renderer@15.16.4) (2023-05-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.16.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.2...@contentful/rich-text-react-renderer@15.16.3) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.16.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.1...@contentful/rich-text-react-renderer@15.16.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.16.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-react-renderer@15.16.0...@contentful/rich-text-react-renderer@15.16.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-react-renderer # 15.16.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## 15.12.1 (2022-04-21) ### Bug Fixes - upgrade react peerDependencies to support react^18.0.0 ([#323](https://github.com/contentful/rich-text/issues/323)) ([7a0bfdf](https://github.com/contentful/rich-text/commit/7a0bfdfb687cca608239e072cbc301ba1b1310d1)) # 15.12.0 (2022-03-25) ## 15.11.1 (2022-01-04) # 15.11.0 (2021-12-27) ### Bug Fixes - **react-renderer:** wrap table rows in tbody ([#300](https://github.com/contentful/rich-text/issues/300)) ([e23d1f4](https://github.com/contentful/rich-text/commit/e23d1f4f5c63ce7ded84144b271f566327a144d1)) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ## 15.9.1 (2021-12-10) # 15.9.0 (2021-12-09) # 15.7.0 (2021-11-11) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ## 15.5.1 (2021-10-25) # 15.5.0 (2021-10-25) # 15.4.0 (2021-09-16) ### Features - **html+react:** render Table header as ([#269](https://github.com/contentful/rich-text/issues/269)) ([0f82905](https://github.com/contentful/rich-text/commit/0f829059be6d91e042dfc71698009177ae4ab78d)) ## 15.3.6 (2021-09-15) ## 15.3.5 (2021-09-13) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) # 15.2.0 (2021-08-16) ### Bug Fixes - audit fix aiming mixing-deep vulnerability ([2b98cbd](https://github.com/contentful/rich-text/commit/2b98cbd8e85435305378dd91d70d55db5e9c0832)) # 15.1.0 (2021-08-02) ### Features - 🎸 add RT react renderer tables support ([fddfb8a](https://github.com/contentful/rich-text/commit/fddfb8a943b9807efe92b749d7ffdeb1308e42bd)) # 15.0.0 (2021-06-15) ## 14.1.3 (2021-04-12) ## 14.1.2 (2020-11-02) ## 14.0.1 (2020-01-30) # 14.0.0 (2020-01-28) # 13.4.0 (2019-08-01) ### Features - 🎸 Adds BLOCKS.DOCUMENT renderer support to react-renderer ([57c922c](https://github.com/contentful/rich-text/commit/57c922cb638c47729f2189815a647ba68859394e)), closes [#104](https://github.com/contentful/rich-text/issues/104) # 13.2.0 (2019-03-18) ### Features - 🎸 renderText option to alter all text ([f684a6e](https://github.com/contentful/rich-text/commit/f684a6e91d81ab0c66c286adaa923bd2edf0f4f1)) # 13.1.0 (2019-03-04) ## 13.0.1 (2019-02-11) ### Bug Fixes - 🐛 Fix wrong import in tests ([bead583](https://github.com/contentful/rich-text/commit/bead583e6e9bb71638694e466980b90e599fa47b)) ### Features - 🎸 Initial rich-text-react-renderer implementation ([06dad9b](https://github.com/contentful/rich-text/commit/06dad9b0359325d8fa433438dac997fc9656d13f)) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) ### Bug Fixes - upgrade react peerDependencies to support react^18.0.0 ([#323](https://github.com/contentful/rich-text/issues/323)) ([7a0bfdf](https://github.com/contentful/rich-text/commit/7a0bfdfb687cca608239e072cbc301ba1b1310d1)) # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) ### Bug Fixes - **react-renderer:** wrap table rows in tbody ([#300](https://github.com/contentful/rich-text/issues/300)) ([e23d1f4](https://github.com/contentful/rich-text/commit/e23d1f4f5c63ce7ded84144b271f566327a144d1)) ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.4.0](https://github.com/contentful/rich-text/compare/v15.3.6...v15.4.0) (2021-09-16) ### Features - **html+react:** render Table header as ([#269](https://github.com/contentful/rich-text/issues/269)) ([0f82905](https://github.com/contentful/rich-text/commit/0f829059be6d91e042dfc71698009177ae4ab78d)) ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-react-renderer # [15.2.0](https://github.com/contentful/rich-text/compare/v15.1.0...v15.2.0) (2021-08-16) ### Bug Fixes - audit fix aiming mixing-deep vulnerability ([2b98cbd](https://github.com/contentful/rich-text/commit/2b98cbd8e85435305378dd91d70d55db5e9c0832)) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) ### Features - 🎸 add RT react renderer tables support ([fddfb8a](https://github.com/contentful/rich-text/commit/fddfb8a943b9807efe92b749d7ffdeb1308e42bd)) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) **Note:** Version bump only for package @contentful/rich-text-react-renderer ## [14.1.3](https://github.com/contentful/rich-text/compare/v14.1.2...v14.1.3) (2021-04-12) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) ### Features - 🎸 Adds BLOCKS.DOCUMENT renderer support to react-renderer ([57c922c](https://github.com/contentful/rich-text/commit/57c922cb638c47729f2189815a647ba68859394e)), closes [#104](https://github.com/contentful/rich-text/issues/104) # [13.2.0](https://github.com/contentful/rich-text/compare/v13.1.0...v13.2.0) (2019-03-18) ### Features - 🎸 renderText option to alter all text ([f684a6e](https://github.com/contentful/rich-text/commit/f684a6e91d81ab0c66c286adaa923bd2edf0f4f1)) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) ## [13.0.1](https://github.com/contentful/rich-text/compare/v13.0.0...v13.0.1) (2019-02-11) ### Bug Fixes - 🐛 Fix wrong import in tests ([bead583](https://github.com/contentful/rich-text/commit/bead583e6e9bb71638694e466980b90e599fa47b)) ### Features - 🎸 Initial rich-text-react-renderer implementation ([06dad9b](https://github.com/contentful/rich-text/commit/06dad9b0359325d8fa433438dac997fc9656d13f)) ================================================ FILE: packages/rich-text-react-renderer/README.md ================================================ # rich-text-react-renderer React renderer for the Contentful rich text field type. ## Installation Using [npm](http://npmjs.org/): ```sh npm install @contentful/rich-text-react-renderer ``` Using [yarn](https://yarnpkg.com/): ```sh yarn add @contentful/rich-text-react-renderer ``` ## Usage ```javascript import { documentToReactComponents } from '@contentful/rich-text-react-renderer'; const document = { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Hello world!', marks: [], data: {}, }, ], }, ], }; documentToReactComponents(document); // ->

    Hello world!

    ``` ```javascript import { documentToReactComponents } from '@contentful/rich-text-react-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello', marks: [{ type: 'bold' }], }, { nodeType: 'text', value: ' world!', marks: [{ type: 'italic' }], }, ], }, ], }; documentToReactComponents(document); // ->

    Hello world!

    ``` You can also pass custom renderers for both marks and nodes as an optional parameter like so: ```javascript import { BLOCKS, MARKS } from '@contentful/rich-text-types'; import { documentToReactComponents } from '@contentful/rich-text-react-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello', marks: [{ type: 'bold' }], }, { nodeType: 'text', value: ' world!', marks: [{ type: 'italic' }], }, ], }, ], }; const Bold = ({ children }) =>

    {children}

    ; const Text = ({ children }) =>

    {children}

    ; const options = { renderMark: { [MARKS.BOLD]: (text) => {text}, }, renderNode: { [BLOCKS.PARAGRAPH]: (node, children) => {children}, }, renderText: (text) => text.replace('!', '?'), }; documentToReactComponents(document, options); // ->

    Hello

    world?

    ``` Last, but not least, you can pass a custom rendering component for an embedded entry: ```javascript import { BLOCKS } from '@contentful/rich-text-types'; import { documentToReactComponents } from '@contentful/rich-text-react-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'embedded-entry-block', data: { target: (...)Link<'Entry'>(...); }, }, ] }; const CustomComponent = ({ title, description }) => (

    {title}

    {description}

    ); const options = { renderNode: { [BLOCKS.EMBEDDED_ENTRY]: (node) => { const { title, description } = node.data.target.fields; return } } }; documentToReactComponents(document, options); // ->

    [title]

    [description]

    ``` The `renderNode` keys should be one of the following `BLOCKS` and `INLINES` properties as defined in [`@contentful/rich-text-types`](https://www.npmjs.com/package/@contentful/rich-text-types): - `BLOCKS` - `DOCUMENT` - `PARAGRAPH` - `HEADING_1` - `HEADING_2` - `HEADING_3` - `HEADING_4` - `HEADING_5` - `HEADING_6` - `UL_LIST` - `OL_LIST` - `LIST_ITEM` - `QUOTE` - `HR` - `EMBEDDED_ENTRY` - `EMBEDDED_ASSET` - `EMBEDDED_RESOURCE` - `INLINES` - `EMBEDDED_ENTRY` (this is different from the `BLOCKS.EMBEDDED_ENTRY`) - `EMBEDDED_RESOURCE` - `HYPERLINK` - `ENTRY_HYPERLINK` - `ASSET_HYPERLINK` - `RESOURCE_HYPERLINK` The `renderMark` keys should be one of the following `MARKS` properties as defined in [`@contentful/rich-text-types`](https://www.npmjs.com/package/@contentful/rich-text-types): - `BOLD` - `ITALIC` - `UNDERLINE` - `CODE` The `renderText` callback is a function that has a single string argument and returns a React node. Each text node is evaluated individually by this callback. A possible use case for this is to replace instances of `\n` produced by `Shift + Enter` with `
    ` React elements. This could be accomplished in the following way: ```javascript const options = { renderText: (text) => { return text.split('\n').reduce((children, textSegment, index) => { return [...children, index > 0 &&
    , textSegment]; }, []); }, }; ``` #### Note on adding a `key` prop in custom renderers: It is possible to pass a `key` prop in the components returned by custom renderers. A good use case for this is in embeded entries using the node's `target.sys.id`. It is important not to pass anything that is index-like (e.g. 1 or "1") as it may clash with the default renderers which automatically inject a `key` prop using their index in the Contentful rich text AST. To work around this limitation, just append any non-numeric character to your custom key. ```javascript const options = { renderMark: { [MARKS.BOLD]: (text) => { return {text}; }, }, }; ``` #### Preserving Whitespace The `options` object can include a `preserveWhitespace` boolean flag. When set to `true`, this flag ensures that multiple spaces in the rich text content are preserved by replacing them with ` `, and line breaks are maintained with `
    ` tags. This is useful for content that relies on specific formatting using spaces and line breaks. Note that `preserveWhitespace` is not compatible with the `renderText` option. When the functionality of `preserveWhitespace` is desired along with a custom `renderText` callback, it should be implemented within the callback instead. ```javascript import { documentToReactComponents } from '@contentful/rich-text-react-renderer'; const document = { nodeType: 'document', content: [ { nodeType: 'paragraph', content: [ { nodeType: 'text', value: 'Hello world!', marks: [], }, ], }, ], }; const options = { preserveWhitespace: true, }; documentToReactComponents(document, options); // ->

    Hello     world!

    ``` In this example, the multiple spaces between "Hello" and "world!" are preserved in the rendered output. ================================================ FILE: packages/rich-text-react-renderer/jest.config.js ================================================ const getBaseConfig = require('../../baseJestConfig'); const package = require('./package.json'); const packageName = package.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-react-renderer/package.json ================================================ { "name": "@contentful/rich-text-react-renderer", "version": "16.2.1", "main": "dist/rich-text-react-renderer.es5.js", "module": "dist/rich-text-react-renderer.esm.js", "typings": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/rich-text-react-renderer.esm.js", "require": "./dist/rich-text-react-renderer.es5.js", "default": "./dist/rich-text-react-renderer.es5.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "prebuild": "rimraf dist", "build": "tsc --module commonjs && rollup -c --bundleConfigAsCjs rollup.config.js", "start": "tsc && rollup -c --bundleConfigAsCjs rollup.config.js -w", "test": "jest" }, "dependencies": { "@contentful/rich-text-types": "^17.2.7" }, "peerDependencies": { "react": "^16.8.6 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "devDependencies": { "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", "react": "^19.2.3", "react-dom": "^19.2.3", "ts-jest": "^29.4.6" } } ================================================ FILE: packages/rich-text-react-renderer/rollup.config.js ================================================ import config from '../../rollup.config'; import { main as outputFile, dependencies } from './package.json'; const overrides = { input: 'src/index.tsx', external: [...Object.keys(dependencies), 'react'], }; export default config(outputFile, overrides); ================================================ FILE: packages/rich-text-react-renderer/src/__test__/__snapshots__/index.test.tsx.snap ================================================ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`documentToReactComponents does not render unrecognized marks 1`] = ` [

    Hello world!

    , ] `; exports[`documentToReactComponents renders asset hyperlink 1`] = ` [

    type: asset-hyperlink id: 9mpxT4zsRi6Iwukey8KeM

    , ] `; exports[`documentToReactComponents renders blockquotes 1`] = ` [

    hello

    ,
    world
    , ] `; exports[`documentToReactComponents renders default entry link block 1`] = ` [
    , ] `; exports[`documentToReactComponents renders default resource block 1`] = ` [
    , ] `; exports[`documentToReactComponents renders embedded entry 1`] = ` [

    type: embedded-entry-inline id: 9mpxT4zsRi6Iwukey8KeM

    , ] `; exports[`documentToReactComponents renders embedded resource 1`] = ` [

    type: embedded-resource-inline urn: crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM

    , ] `; exports[`documentToReactComponents renders empty node if type is not recognized 1`] = ` [ Hello world! , ] `; exports[`documentToReactComponents renders entry hyperlink 1`] = ` [

    type: entry-hyperlink id: 9mpxT4zsRi6Iwukey8KeM

    , ] `; exports[`documentToReactComponents renders horizontal rule 1`] = ` [

    hello world

    ,
    ,

    , ] `; exports[`documentToReactComponents renders hyperlink 1`] = ` [

    Some text link text.

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 1`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 2`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 3`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 4`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 5`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 6`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with default mark renderer 7`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders marks with the passed custom mark renderer 1`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders multiple marks with default mark renderer 1`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 1`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 2`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 3`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 4`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 5`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 6`] = ` [
    hello world
    , ] `; exports[`documentToReactComponents renders nodes with default node renderer 7`] = ` [
    hello world
    , ] `; exports[`documentToReactComponents renders nodes with passed custom node renderer 1`] = ` hello
    world
    `; exports[`documentToReactComponents renders ordered lists 1`] = ` [
    1. Hello

    2. world

    ,

    , ] `; exports[`documentToReactComponents renders resource hyperlink 1`] = ` [

    type: resource-hyperlink urn: crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM

    , ] `; exports[`documentToReactComponents renders tables 1`] = ` [

    A 1

    B 1

    A 2

    B 2

    , ] `; exports[`documentToReactComponents renders tables with header 1`] = ` [

    A 1

    B 1

    A 2

    B 2

    , ] `; exports[`documentToReactComponents renders text with the passed custom text renderer 1`] = ` [

    hello Earth

    , ] `; exports[`documentToReactComponents renders unaltered text with default text renderer 1`] = ` [

    hello world

    , ] `; exports[`documentToReactComponents renders unordered lists 1`] = ` [
    • Hello

    • world

    ,

    , ] `; exports[`documentToReactComponents returns an array of elements when given a populated document 1`] = ` [

    hello world

    ,
    ,

    , ] `; exports[`nodeListToReactComponents renders children as an array with keys from its index 1`] = ` [

    hello

    , " ",

    world

    , ] `; exports[`nodeToReactComponent does not add additional tags on invalid marks 1`] = `"hello world"`; exports[`nodeToReactComponent does not render altered text with default text renderer 1`] = `

    some lines of text

    `; exports[`nodeToReactComponent renders altered text with custom text renderer 1`] = `

    some
    lines
    of
    text

    `; exports[`nodeToReactComponent renders invalid node types in React fragments 1`] = ` hello world `; exports[`nodeToReactComponent renders valid marks 1`] = ` hello world `; exports[`nodeToReactComponent renders valid nodes 1`] = `

    hello world

    `; exports[`preserveWhitespace preserves new lines 1`] = ` [

    hello
    world

    , ] `; exports[`preserveWhitespace preserves spaces between words 1`] = ` [

    hello    world

    , ] `; exports[`stripEmptyTrailingParagraph does not strip empty trailing paragraph when disabled 1`] = ` [

    Hello world

    ,

    , ] `; exports[`stripEmptyTrailingParagraph does not strip empty trailing paragraph when it is the only child 1`] = ` [

    , ] `; exports[`stripEmptyTrailingParagraph does not strip non-empty trailing paragraph 1`] = ` [

    Hello world

    ,

    Not empty

    , ] `; exports[`stripEmptyTrailingParagraph does not strip trailing non-paragraph node 1`] = ` [

    Hello world

    ,

    , ] `; exports[`stripEmptyTrailingParagraph does not strip trailing paragraph with multiple text nodes 1`] = ` [

    Hello world

    ,

    , ] `; exports[`stripEmptyTrailingParagraph strips empty trailing paragraph when enabled 1`] = ` [

    Hello world

    , ] `; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/components/Document.tsx ================================================ import React, { FunctionComponent, ReactNode } from 'react'; type DocumentProps = { children: ReactNode; }; const Document: FunctionComponent = ({ children }) => { return
    {children}
    ; }; export default Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/components/Paragraph.tsx ================================================ import React, { FunctionComponent, ReactNode } from 'react'; type ParagraphProps = { children: ReactNode; }; const Paragraph: FunctionComponent = ({ children }) => { return

    {children}

    ; }; export default Paragraph; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/components/Strong.tsx ================================================ import React, { FunctionComponent, ReactNode } from 'react'; type StrongProps = { children: ReactNode; }; const Strong: FunctionComponent = ({ children }) => { return {children}; }; export default Strong; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/embedded-entry.ts ================================================ import { BLOCKS, Document } from '@contentful/rich-text-types'; export default function (entry: Record) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_ENTRY, content: [], data: { target: entry, }, }, ], } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/embedded-resource.ts ================================================ import { Document, BLOCKS, ResourceLink } from '@contentful/rich-text-types'; export default function (resourceLink: ResourceLink) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.EMBEDDED_RESOURCE, content: [], data: { target: resourceLink, }, }, ], } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/heading.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default function (heading: string) { return { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: heading, data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/hr.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { content: [ { content: [ { marks: [], nodeType: 'text', value: 'hello world', data: {}, }, ], data: {}, nodeType: 'paragraph', }, { content: [ { marks: [], nodeType: 'text', value: '', data: {}, }, ], data: {}, nodeType: 'hr', }, { content: [ { marks: [], nodeType: 'text', value: '', data: {}, }, ], data: {}, nodeType: 'paragraph', }, ], data: {}, nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/hyperlink.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Some text ', marks: [], data: {}, }, { nodeType: 'hyperlink', content: [ { nodeType: 'text', value: 'link', marks: [], data: {}, }, ], data: { uri: 'https://url.org', }, }, { nodeType: 'text', value: ' text.', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/index.ts ================================================ export { default as hrDoc } from './hr'; export { default as hyperlinkDoc } from './hyperlink'; export { default as invalidMarksDoc } from './invalid-marks'; export { default as invalidTypeDoc } from './invalid-type'; export { default as paragraphDoc } from './paragraph'; export { default as headingDoc } from './heading'; export { default as marksDoc } from './mark'; export { default as multiMarkDoc } from './multi-mark'; export { default as embeddedEntryDoc } from './embedded-entry'; export { default as embeddedResourceDoc } from './embedded-resource'; export { default as inlineEntityDoc } from './inline-entity'; export { default as olDoc } from './ol'; export { default as ulDoc } from './ul'; export { default as quoteDoc } from './quote'; export { default as tableDoc } from './table'; export { default as tableWithHeaderDoc } from './table-header'; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/inline-entity.ts ================================================ import { BLOCKS, Document, INLINES } from '@contentful/rich-text-types'; export default function inlineEntity(entry: Record, inlineType: INLINES) { return { content: [ { data: {}, content: [ { marks: [], value: '', nodeType: 'text', data: {}, }, { data: entry, content: [ { marks: [], value: '', nodeType: 'text', data: {}, }, ], nodeType: inlineType, }, { marks: [], value: '', nodeType: 'text', data: {}, }, ], nodeType: BLOCKS.PARAGRAPH, }, ], data: {}, nodeType: BLOCKS.DOCUMENT, } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/invalid-marks.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'Hello world!', marks: [ { type: 'UNRECOGNIZED_MARK', }, ], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/invalid-type.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: 'UNRECOGNIZED_TYPE' as BLOCKS, data: {}, content: [ { nodeType: 'text', value: 'Hello world!', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/mark.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default function (mark: string) { return { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [{ type: mark }], data: {}, }, ], }, ], } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/multi-mark.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default function () { return { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [{ type: 'bold' }, { type: 'italic' }], data: {}, }, ], }, ], } as Document; } ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/ol.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'Hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, ], nodeType: 'ordered-list', }, { data: {}, content: [ { data: {}, marks: [], value: '', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/paragraph.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { nodeType: 'document', data: {}, content: [ { nodeType: 'paragraph', data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/quote.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'blockquote', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/table-header.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 1', }, ], }, ], }, { nodeType: BLOCKS.TABLE_HEADER_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 1', }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 2', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 2', }, ], }, ], }, ], }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/table.ts ================================================ import { Document, BLOCKS } from '@contentful/rich-text-types'; export default { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.TABLE, data: {}, content: [ { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 1', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 1', }, ], }, ], }, ], }, { nodeType: BLOCKS.TABLE_ROW, data: {}, content: [ { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'A 2', }, ], }, ], }, { nodeType: BLOCKS.TABLE_CELL, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', data: {}, marks: [], value: 'B 2', }, ], }, ], }, ], }, ], }, ], } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/documents/ul.ts ================================================ import { Document } from '@contentful/rich-text-types'; export default { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'Hello', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, { data: {}, content: [ { data: {}, content: [ { data: {}, marks: [], value: 'world', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'list-item', }, ], nodeType: 'unordered-list', }, { data: {}, content: [ { data: {}, marks: [], value: '', nodeType: 'text', }, ], nodeType: 'paragraph', }, ], nodeType: 'document', } as Document; ================================================ FILE: packages/rich-text-react-renderer/src/__test__/index.test.tsx ================================================ import React, { ReactNode } from 'react'; import { BLOCKS, Document, INLINES, MARKS, ResourceLink } from '@contentful/rich-text-types'; import { CommonNode, documentToReactComponents, Options } from '..'; import { appendKeyToValidElement } from '../util/appendKeyToValidElement'; import { nodeListToReactComponents, nodeToReactComponent } from '../util/nodeListToReactComponents'; import DocumentWrapper from './components/Document'; import Paragraph from './components/Paragraph'; import Strong from './components/Strong'; import { embeddedEntryDoc, headingDoc, hrDoc, hyperlinkDoc, inlineEntityDoc, invalidMarksDoc, invalidTypeDoc, marksDoc, multiMarkDoc, olDoc, paragraphDoc, quoteDoc, tableDoc, tableWithHeaderDoc, ulDoc, } from './documents'; import embeddedResource from './documents/embedded-resource'; describe('documentToReactComponents', () => { it('returns an empty array when given an empty document', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [], }; expect(documentToReactComponents(document)).toEqual([]); }); it('returns an array of elements when given a populated document', () => { const document: Document = hrDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); expect(documentToReactComponents(document)).toBeInstanceOf(Array); }); it('renders nodes with default node renderer', () => { const docs: Document[] = [ paragraphDoc, headingDoc(BLOCKS.HEADING_1), headingDoc(BLOCKS.HEADING_2), headingDoc(BLOCKS.HEADING_3), headingDoc(BLOCKS.HEADING_4), headingDoc(BLOCKS.HEADING_5), headingDoc(BLOCKS.HEADING_6), ]; docs.forEach((doc) => { expect(documentToReactComponents(doc)).toMatchSnapshot(); }); }); it('renders marks with default mark renderer', () => { const docs: Document[] = [ marksDoc(MARKS.ITALIC), marksDoc(MARKS.BOLD), marksDoc(MARKS.UNDERLINE), marksDoc(MARKS.CODE), marksDoc(MARKS.SUPERSCRIPT), marksDoc(MARKS.SUBSCRIPT), marksDoc(MARKS.STRIKETHROUGH), ]; docs.forEach((doc) => { expect(documentToReactComponents(doc)).toMatchSnapshot(); }); }); it('renders unaltered text with default text renderer', () => { const document: Document = paragraphDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders multiple marks with default mark renderer', () => { const doc: Document = multiMarkDoc(); expect(documentToReactComponents(doc)).toMatchSnapshot(); }); it('renders nodes with passed custom node renderer', () => { const options: Options = { renderNode: { [BLOCKS.DOCUMENT]: (node, children) => {children}, [BLOCKS.PARAGRAPH]: (node, children) => {children}, }, }; const document: Document = quoteDoc; expect(documentToReactComponents(document, options)).toMatchSnapshot(); }); it('renders marks with the passed custom mark renderer', () => { const options: Options = { renderMark: { [MARKS.BOLD]: (text) => {text}, }, }; const document: Document = multiMarkDoc(); expect(documentToReactComponents(document, options)).toMatchSnapshot(); }); it('renders text with the passed custom text renderer', () => { const options: Options = { renderText: (text) => text.replace(/world/, 'Earth'), }; const document: Document = paragraphDoc; expect(documentToReactComponents(document, options)).toMatchSnapshot(); }); it('does not render unrecognized marks', () => { const document: Document = invalidMarksDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders empty node if type is not recognized', () => { const document: Document = invalidTypeDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders default entry link block', () => { const entrySys = { sys: { id: '9mpxT4zsRi6Iwukey8KeM', link: 'Link', linkType: 'Entry', }, }; const document: Document = embeddedEntryDoc(entrySys); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders default resource block', () => { const resourceSys: ResourceLink = { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'ResourceLink', linkType: 'Contentful:Entry', }, }; const document: Document = embeddedResource(resourceSys); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders ordered lists', () => { const document: Document = olDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders unordered lists', () => { const document: Document = ulDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders blockquotes', () => { const document: Document = quoteDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders horizontal rule', () => { const document: Document = hrDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders tables', () => { const document: Document = tableDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders tables with header', () => { expect(documentToReactComponents(tableWithHeaderDoc)).toMatchSnapshot(); }); it('does not crash with inline elements (e.g. hyperlink)', () => { const document: Document = hyperlinkDoc; expect(documentToReactComponents(document)).toBeTruthy(); }); it('renders hyperlink', () => { const document: Document = hyperlinkDoc; expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders asset hyperlink', () => { const asset = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', link: 'Link', type: 'Asset', }, }, }; const document: Document = inlineEntityDoc(asset, INLINES.ASSET_HYPERLINK); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders entry hyperlink', () => { const entry = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Entry', }, }, }; const document: Document = inlineEntityDoc(entry, INLINES.ENTRY_HYPERLINK); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders resource hyperlink', () => { const entry = { target: { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Entry', }, }, }; const document: Document = inlineEntityDoc(entry, INLINES.RESOURCE_HYPERLINK); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders embedded entry', () => { const entry = { target: { sys: { id: '9mpxT4zsRi6Iwukey8KeM', type: 'Link', linkType: 'Entry', }, }, }; const document: Document = inlineEntityDoc(entry, INLINES.EMBEDDED_ENTRY); expect(documentToReactComponents(document)).toMatchSnapshot(); }); it('renders embedded resource', () => { const entry = { target: { sys: { urn: 'crn:contentful:::content:spaces/6fqi4ljzyr0e/environments/master/entries/9mpxT4zsRi6Iwukey8KeM', type: 'ResourceLink', linkType: 'Contentful:Entry', }, }, }; const document: Document = inlineEntityDoc(entry, INLINES.EMBEDDED_RESOURCE); expect(documentToReactComponents(document)).toMatchSnapshot(); }); }); describe('appendKeyToValidElement', () => { it('appends keys to default React components', () => { expect(appendKeyToValidElement(
    , 0)).toHaveProperty('key', '0'); }); it('appends keys to custom React components', () => { expect(appendKeyToValidElement(hello world, 0)).toHaveProperty( 'key', '0', ); }); it('does not overwrite user specified keys', () => { expect(appendKeyToValidElement(
    , 0)).toHaveProperty('key', 'xyz'); }); it('does not add keys to text nodes', () => { expect(appendKeyToValidElement('hello world', 0)).not.toHaveProperty('key'); }); it('does not add keys to node arrays', () => { expect(appendKeyToValidElement([
    ,
    ], 0)).not.toHaveProperty( 'key', ); }); it('does not add keys to null', () => { expect(appendKeyToValidElement(null, 0)).toBeNull(); }); }); describe('nodeToReactComponent', () => { const options: Options = { renderNode: { [BLOCKS.PARAGRAPH]: (node: CommonNode, children: ReactNode): ReactNode =>

    {children}

    , }, renderMark: { [MARKS.BOLD]: (text: ReactNode): ReactNode => {text}, }, }; const createBlockNode = (nodeType: BLOCKS): CommonNode => ({ nodeType, data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }); const createTextNode = (type: string): CommonNode => ({ nodeType: 'text', value: 'hello world', marks: [{ type }], data: {}, }); it('renders valid nodes', () => { expect(nodeToReactComponent(createBlockNode(BLOCKS.PARAGRAPH), options)).toMatchSnapshot(); }); it('renders invalid node types in React fragments', () => { expect(nodeToReactComponent(createBlockNode(BLOCKS.HEADING_1), options)).toMatchSnapshot(); }); it('renders valid marks', () => { expect(nodeToReactComponent(createTextNode(MARKS.BOLD), options)).toMatchSnapshot(); }); it('does not add additional tags on invalid marks', () => { expect(nodeToReactComponent(createTextNode(MARKS.ITALIC), options)).toMatchSnapshot(); }); const customTextNode: CommonNode = { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'some\nlines\nof\ntext', marks: [{ type: MARKS.BOLD }], data: {}, }, ], }; it('does not render altered text with default text renderer', () => { expect(nodeToReactComponent(customTextNode, options)).toMatchSnapshot(); }); it('renders altered text with custom text renderer', () => { expect( nodeToReactComponent(customTextNode, { ...options, renderText: (text: string): ReactNode => { return text.split('\n').reduce((children, textSegment, index) => { return [...children, index > 0 &&
    , textSegment]; }, []); }, }), ).toMatchSnapshot(); }); }); describe('nodeListToReactComponents', () => { const options: Options = { renderNode: { [BLOCKS.PARAGRAPH]: (node: CommonNode, children: ReactNode): ReactNode =>

    {children}

    , }, renderMark: { [MARKS.BOLD]: (text: ReactNode): ReactNode => {text}, }, }; const nodes: CommonNode[] = [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello', marks: [{ type: MARKS.BOLD }], data: {}, }, ], }, { nodeType: 'text', value: ' ', marks: [], data: {}, }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'world', marks: [], data: {}, }, ], }, ]; it('renders children as an array with keys from its index', () => { const renderedNodes: ReactNode[] = nodeListToReactComponents(nodes, options) as ReactNode[]; expect(renderedNodes[0]).toHaveProperty('key', '0'); expect(renderedNodes[1]).not.toHaveProperty('key'); expect(renderedNodes[2]).toHaveProperty('key', '2'); expect(renderedNodes).toMatchSnapshot(); }); }); describe('preserveWhitespace', () => { it('preserves spaces between words', () => { const options: Options = { preserveWhitespace: true, }; const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello world', marks: [], data: {}, }, ], }, ], }; expect(documentToReactComponents(document, options)).toMatchSnapshot(); }); it('preserves new lines', () => { const options: Options = { preserveWhitespace: true, }; const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'hello\nworld', marks: [], data: {}, }, ], }, ], }; expect(documentToReactComponents(document, options)).toMatchSnapshot(); }); }); describe('stripEmptyTrailingParagraph', () => { it('strips empty trailing paragraph when enabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); it('does not strip empty trailing paragraph when disabled', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: false, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); it('does not strip empty trailing paragraph when it is the only child', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); it('does not strip non-empty trailing paragraph', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Not empty', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); it('does not strip trailing paragraph with multiple text nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); it('does not strip trailing non-paragraph node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: 'Hello world', marks: [], data: {}, }, ], }, { nodeType: BLOCKS.HEADING_1, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; const options: Options = { stripEmptyTrailingParagraph: true, }; const result = documentToReactComponents(document, options); expect(result).toMatchSnapshot(); }); }); ================================================ FILE: packages/rich-text-react-renderer/src/index.tsx ================================================ import React, { ReactNode } from 'react'; import { Block, BLOCKS, Document, Inline, INLINES, MARKS, Text, helpers, } from '@contentful/rich-text-types'; import { nodeToReactComponent } from './util/nodeListToReactComponents'; const defaultNodeRenderers: RenderNode = { [BLOCKS.DOCUMENT]: (node, children) => children, [BLOCKS.PARAGRAPH]: (node, children) =>

    {children}

    , [BLOCKS.HEADING_1]: (node, children) =>

    {children}

    , [BLOCKS.HEADING_2]: (node, children) =>

    {children}

    , [BLOCKS.HEADING_3]: (node, children) =>

    {children}

    , [BLOCKS.HEADING_4]: (node, children) =>

    {children}

    , [BLOCKS.HEADING_5]: (node, children) =>
    {children}
    , [BLOCKS.HEADING_6]: (node, children) =>
    {children}
    , [BLOCKS.EMBEDDED_ENTRY]: (node, children) =>
    {children}
    , [BLOCKS.EMBEDDED_RESOURCE]: (node, children) =>
    {children}
    , [BLOCKS.UL_LIST]: (node, children) =>
      {children}
    , [BLOCKS.OL_LIST]: (node, children) =>
      {children}
    , [BLOCKS.LIST_ITEM]: (node, children) =>
  • {children}
  • , [BLOCKS.QUOTE]: (node, children) =>
    {children}
    , [BLOCKS.HR]: () =>
    , [BLOCKS.TABLE]: (node, children) => ( {children}
    ), [BLOCKS.TABLE_ROW]: (node, children) => {children}, [BLOCKS.TABLE_HEADER_CELL]: (node, children) => {children}, [BLOCKS.TABLE_CELL]: (node, children) => {children}, [INLINES.ASSET_HYPERLINK]: (node) => defaultInline(INLINES.ASSET_HYPERLINK, node as Inline), [INLINES.ENTRY_HYPERLINK]: (node) => defaultInline(INLINES.ENTRY_HYPERLINK, node as Inline), [INLINES.RESOURCE_HYPERLINK]: (node) => defaultInlineResource(INLINES.RESOURCE_HYPERLINK, node as Inline), [INLINES.EMBEDDED_ENTRY]: (node) => defaultInline(INLINES.EMBEDDED_ENTRY, node as Inline), [INLINES.EMBEDDED_RESOURCE]: (node, _children) => defaultInlineResource(INLINES.EMBEDDED_RESOURCE, node as Inline), [INLINES.HYPERLINK]: (node, children) => {children}, }; const defaultMarkRenderers: RenderMark = { [MARKS.BOLD]: (text) => {text}, [MARKS.ITALIC]: (text) => {text}, [MARKS.UNDERLINE]: (text) => {text}, [MARKS.CODE]: (text) => {text}, [MARKS.SUPERSCRIPT]: (text) => {text}, [MARKS.SUBSCRIPT]: (text) => {text}, [MARKS.STRIKETHROUGH]: (text) => {text}, }; function defaultInline(type: string, node: Inline): ReactNode { return ( type: {node.nodeType} id: {node.data.target.sys.id} ); } function defaultInlineResource(type: string, node: Inline) { return ( type: {node.nodeType} urn: {node.data.target.sys.urn} ); } export type CommonNode = Text | Block | Inline; export interface NodeRenderer { (node: Block | Inline, children: ReactNode): ReactNode; } export interface RenderNode { [k: string]: NodeRenderer; } export interface RenderMark { [k: string]: (text: ReactNode) => ReactNode; } export interface RenderText { (text: string): ReactNode; } export interface Options { /** * Node renderers */ renderNode?: RenderNode; /** * Mark renderers */ renderMark?: RenderMark; /** * Text renderer */ renderText?: RenderText; /** * Keep line breaks and multiple spaces */ preserveWhitespace?: boolean; /** * Strip empty trailing paragraph from the document */ stripEmptyTrailingParagraph?: boolean; } /** * Serialize a Contentful Rich Text `document` to React tree */ export function documentToReactComponents( richTextDocument: Document, options: Options = {}, ): ReactNode { if (!richTextDocument) { return null; } // Strip empty trailing paragraph if enabled let processedDocument = richTextDocument; if (options.stripEmptyTrailingParagraph) { processedDocument = helpers.stripEmptyTrailingParagraphFromDocument(richTextDocument); } return nodeToReactComponent(processedDocument, { renderNode: { ...defaultNodeRenderers, ...options.renderNode, }, renderMark: { ...defaultMarkRenderers, ...options.renderMark, }, renderText: options.renderText, preserveWhitespace: options.preserveWhitespace, }); } ================================================ FILE: packages/rich-text-react-renderer/src/util/appendKeyToValidElement.ts ================================================ import { cloneElement, isValidElement, ReactNode } from 'react'; export function appendKeyToValidElement(element: ReactNode, key: number): ReactNode { if (isValidElement(element) && element.key === null) { return cloneElement(element, { key }); } return element; } ================================================ FILE: packages/rich-text-react-renderer/src/util/nodeListToReactComponents.tsx ================================================ import React, { ReactNode } from 'react'; import { helpers, Mark } from '@contentful/rich-text-types'; import { CommonNode, Options } from '..'; import { appendKeyToValidElement } from './appendKeyToValidElement'; export function nodeListToReactComponents(nodes: CommonNode[], options: Options): ReactNode { return nodes.map((node: CommonNode, index: number): ReactNode => { return appendKeyToValidElement(nodeToReactComponent(node, options), index); }); } export function nodeToReactComponent(node: CommonNode, options: Options): ReactNode { const { renderNode, renderMark, renderText, preserveWhitespace } = options; if (helpers.isText(node)) { let nodeValue: ReactNode = renderText ? renderText(node.value) : node.value; // Preserving whitespace is only supported with the default transformations. if (preserveWhitespace && !renderText) { // Preserve multiple spaces. nodeValue = (nodeValue as string).replace(/ {2,}/g, (match) => '\u00A0'.repeat(match.length)); // Preserve line breaks. const lines = (nodeValue as string).split('\n'); const jsxLines: (string | React.JSX.Element)[] = []; lines.forEach((line, index) => { jsxLines.push(line); if (index !== lines.length - 1) { jsxLines.push(
    ); } }); nodeValue = jsxLines; } return node.marks.reduce((value: ReactNode, mark: Mark): ReactNode => { if (!renderMark[mark.type]) { return value; } return renderMark[mark.type](value); }, nodeValue); } else { const children: ReactNode = nodeListToReactComponents(node.content, options); if (!node.nodeType || !renderNode[node.nodeType]) { return <>{children}; } return renderNode[node.nodeType](node, children); } } ================================================ FILE: packages/rich-text-react-renderer/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "dist/types", "outDir": "dist/lib", "typeRoots": ["../../node_modules/@types", "node_modules/@types"], "jsx": "react" }, "include": ["src"] } ================================================ FILE: packages/rich-text-types/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [17.2.7](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.6...@contentful/rich-text-types@17.2.7) (2026-04-09) ### Bug Fixes - **rich-text-types:** improve esm compability [ZEND-7778] ([#1073](https://github.com/contentful/rich-text/issues/1073)) ([204aaec](https://github.com/contentful/rich-text/commit/204aaecc3893633c081986f44896e14272fa376a)) ## [17.2.6](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.5...@contentful/rich-text-types@17.2.6) (2026-04-08) **Note:** Version bump only for package @contentful/rich-text-types ## [17.2.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.4...@contentful/rich-text-types@17.2.5) (2025-11-04) **Note:** Version bump only for package @contentful/rich-text-types ## [17.2.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.3...@contentful/rich-text-types@17.2.4) (2025-09-23) ### Bug Fixes - **build:** resolve ESM import failures in rich-text-types [TOL-3435] ([#940](https://github.com/contentful/rich-text/issues/940)) ([5af12af](https://github.com/contentful/rich-text/commit/5af12af168a6005826216c2090fc989db3f8fb03)) ## [17.2.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.2...@contentful/rich-text-types@17.2.3) (2025-09-23) ### Bug Fixes - revert lingui [ZEND-6939] ([#937](https://github.com/contentful/rich-text/issues/937)) ([a55a67b](https://github.com/contentful/rich-text/commit/a55a67b1f1b9739801bc5bc3a9885701ff970cc0)) ## [17.2.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.1...@contentful/rich-text-types@17.2.2) (2025-09-10) ### Bug Fixes - **rich-text-types:** add .js extension for esm [] ([#926](https://github.com/contentful/rich-text/issues/926)) ([b218716](https://github.com/contentful/rich-text/commit/b218716f27a0fe8ad71d0dd8ce551f2ecd2334d6)) ## [17.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.2.0...@contentful/rich-text-types@17.2.1) (2025-09-09) ### Bug Fixes - bundle issues after swc migration [TOL-3396] ([#924](https://github.com/contentful/rich-text/issues/924)) ([22245f0](https://github.com/contentful/rich-text/commit/22245f0648b0164ad29dd0dfe789cd83f78283e8)) # [17.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.1.0...@contentful/rich-text-types@17.2.0) (2025-09-04) ### Features - migrate to swc and use lingui for i18n [TOL-3288] ([#914](https://github.com/contentful/rich-text/issues/914)) ([b6782a9](https://github.com/contentful/rich-text/commit/b6782a9658b24944ccce2676f06efb1a527d9936)) # [17.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.0.1...@contentful/rich-text-types@17.1.0) (2025-07-15) ### Features - add an option to strip empty trailing paragraphs [TOL-3193] ([#892](https://github.com/contentful/rich-text/issues/892)) ([9beff0e](https://github.com/contentful/rich-text/commit/9beff0e4cba3e79dc68e6a0725e843c5d642eb87)) ## [17.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@17.0.0...@contentful/rich-text-types@17.0.1) (2025-06-16) **Note:** Version bump only for package @contentful/rich-text-types # [17.0.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.5...@contentful/rich-text-types@17.0.0) (2024-10-29) ### Features - bring rich text validator [TOL-2426] ([#694](https://github.com/contentful/rich-text/issues/694)) ([30893a6](https://github.com/contentful/rich-text/commit/30893a68b171167502135b48258ba4b93c8375c7)) ### BREAKING CHANGES - removed getSchemaWithNodeType in favor of validateRichTextDocument ## [16.8.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.4...@contentful/rich-text-types@16.8.5) (2024-09-09) **Note:** Version bump only for package @contentful/rich-text-types ## [16.8.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.3...@contentful/rich-text-types@16.8.4) (2024-08-26) **Note:** Version bump only for package @contentful/rich-text-types ## [16.8.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.2...@contentful/rich-text-types@16.8.3) (2024-07-29) **Note:** Version bump only for package @contentful/rich-text-types ## [16.8.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.1...@contentful/rich-text-types@16.8.2) (2024-07-24) ### Bug Fixes - exporting schema function [] ([#632](https://github.com/contentful/rich-text/issues/632)) ([21bf8a6](https://github.com/contentful/rich-text/commit/21bf8a61883abbb2174524df2ec5be826fd429a3)) ## [16.8.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.8.0...@contentful/rich-text-types@16.8.1) (2024-07-17) ### Bug Fixes - revert schema changes [TOL-2249] ([de0157b](https://github.com/contentful/rich-text/commit/de0157bd8b7b1fd00b58e4b753befb1d9eeecbff)) # [16.8.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.7.0...@contentful/rich-text-types@16.8.0) (2024-07-17) ### Features - update inline enum order [] ([#620](https://github.com/contentful/rich-text/issues/620)) ([8643e11](https://github.com/contentful/rich-text/commit/8643e1126ce765842190a866556d7c0fa45a7ba8)) - update schema for rich text types [TOL-2249] ([#619](https://github.com/contentful/rich-text/issues/619)) ([010831e](https://github.com/contentful/rich-text/commit/010831ea1259f478543845ccd70a3aa80f643db0)) # [16.7.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.6.3...@contentful/rich-text-types@16.7.0) (2024-07-16) ### Features - support ol & ul list validation in table cell [TOL-2249] ([#616](https://github.com/contentful/rich-text/issues/616)) ([f116c33](https://github.com/contentful/rich-text/commit/f116c33d7a26401447836d1230277f6914a31ac1)) ## [16.6.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.6.2...@contentful/rich-text-types@16.6.3) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-types ## [16.6.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.6.1...@contentful/rich-text-types@16.6.2) (2024-07-16) **Note:** Version bump only for package @contentful/rich-text-types ## [16.6.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.6.0...@contentful/rich-text-types@16.6.1) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-types # [16.6.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.5.4...@contentful/rich-text-types@16.6.0) (2024-06-25) ### Features - switch from tslint to eslint [TOL-2218][tol-2203] ([#594](https://github.com/contentful/rich-text/issues/594)) ([c077b5a](https://github.com/contentful/rich-text/commit/c077b5af58f94c8dc6af4715d4b82c2771d643c5)) ## [16.5.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.5.3...@contentful/rich-text-types@16.5.4) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-types ## [16.5.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.5.2...@contentful/rich-text-types@16.5.3) (2024-06-25) **Note:** Version bump only for package @contentful/rich-text-types ## [16.5.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.5.1...@contentful/rich-text-types@16.5.2) (2024-05-28) **Note:** Version bump only for package @contentful/rich-text-types ## [16.5.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.5.0...@contentful/rich-text-types@16.5.1) (2024-05-27) ### Bug Fixes - fix breaking change for copying json ([#573](https://github.com/contentful/rich-text/issues/573)) ([e5d6533](https://github.com/contentful/rich-text/commit/e5d653360735efc395ad8920d8b2bfd24eb827c1)) # [16.5.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.4.0...@contentful/rich-text-types@16.5.0) (2024-05-24) ### Features - add strikethrough support ([#562](https://github.com/contentful/rich-text/issues/562)) ([b87d0c3](https://github.com/contentful/rich-text/commit/b87d0c31bccb4012745c0479b2b5c92fc28c1e91)) # [16.4.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.5...@contentful/rich-text-types@16.4.0) (2024-05-22) ### Features - upgrading rollup to latest version [TOL-2097] ([#559](https://github.com/contentful/rich-text/issues/559)) ([f14d197](https://github.com/contentful/rich-text/commit/f14d1974590d58f92bbf4cb56644095dba929ad9)) ## [16.3.5](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.4...@contentful/rich-text-types@16.3.5) (2024-03-04) ### Bug Fixes - 🐛 export MARKS and EMPTY_DOCUMENT as ESM friendly ([#462](https://github.com/contentful/rich-text/issues/462)) ([7ea8ab3](https://github.com/contentful/rich-text/commit/7ea8ab31bcf82242291ff22640869a23b12dde57)) ## [16.3.4](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.3...@contentful/rich-text-types@16.3.4) (2024-01-30) **Note:** Version bump only for package @contentful/rich-text-types ## [16.3.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.2...@contentful/rich-text-types@16.3.3) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-types ## [16.3.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.1...@contentful/rich-text-types@16.3.2) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-types ## [16.3.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.3.0...@contentful/rich-text-types@16.3.1) (2024-01-23) **Note:** Version bump only for package @contentful/rich-text-types # [16.3.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.2.1...@contentful/rich-text-types@16.3.0) (2023-09-12) ### Features - add new nodes [DANTE-1157] ([#494](https://github.com/contentful/rich-text/issues/494)) ([b1fa6df](https://github.com/contentful/rich-text/commit/b1fa6dffc4bd7e1d367e9ce2cfddffe4fe07be47)) ## [16.2.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.2.0...@contentful/rich-text-types@16.2.1) (2023-08-04) **Note:** Version bump only for package @contentful/rich-text-types # [16.2.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.1.0...@contentful/rich-text-types@16.2.0) (2023-05-26) ### Features - 🎸 add `getRichTextResourceLinks` ([#465](https://github.com/contentful/rich-text/issues/465)) ([5746ba6](https://github.com/contentful/rich-text/commit/5746ba652ae5eac2df27d05193e19aef9e85c438)) # [16.1.0](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.0.3...@contentful/rich-text-types@16.1.0) (2023-05-04) ### Features - add embedded-resource-block to the list of supported block elements ([#461](https://github.com/contentful/rich-text/issues/461)) ([c85fd63](https://github.com/contentful/rich-text/commit/c85fd632d5166bf6a4bd30adbc3ed35668f2e7d5)) ## [16.0.3](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.0.2...@contentful/rich-text-types@16.0.3) (2023-03-14) **Note:** Version bump only for package @contentful/rich-text-types ## [16.0.2](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.0.1...@contentful/rich-text-types@16.0.2) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-types ## [16.0.1](https://github.com/contentful/rich-text/compare/@contentful/rich-text-types@16.0.0...@contentful/rich-text-types@16.0.1) (2022-12-01) **Note:** Version bump only for package @contentful/rich-text-types # 16.0.0 (2022-12-01) ## 15.15.1 (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # 15.15.0 (2022-11-29) ### Features - adding v1 marks to rich text types [TOL-786] ([#416](https://github.com/contentful/rich-text/issues/416)) ([8885de8](https://github.com/contentful/rich-text/commit/8885de8ebba7736de46b0b8892a8aa15bb8d7ba4)) ## 15.14.1 (2022-11-23) # 15.14.0 (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## 15.13.2 (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - revert change to monorepo tsconfig, apply to tsconfig in rich-text-types ([#358](https://github.com/contentful/rich-text/issues/358)) ([56126cc](https://github.com/contentful/rich-text/commit/56126cc9ed3704f21b89d2dbded160be0265f153)) ## 15.12.1 (2022-04-21) # 15.12.0 (2022-03-25) ### Features - enforce minItems in table-related node types ([#314](https://github.com/contentful/rich-text/issues/314)) ([1125331](https://github.com/contentful/rich-text/commit/112533100f66ae01cd9944069dc62fc95f1737a5)) ## 15.11.1 (2022-01-04) ### Bug Fixes - **rich-text-types:** remove RT validation helpers ([#302](https://github.com/contentful/rich-text/issues/302)) ([fcd3a27](https://github.com/contentful/rich-text/commit/fcd3a277952f53eb3ae6ebb559ae6a02f5553c87)), closes [#295](https://github.com/contentful/rich-text/issues/295) [#274](https://github.com/contentful/rich-text/issues/274) # 15.11.0 (2021-12-27) ### Features - **rich-text-types:** expose HEADINGS array ([#301](https://github.com/contentful/rich-text/issues/301)) ([758539d](https://github.com/contentful/rich-text/commit/758539d46f3db13c21ca2f6d74a389a6fef21803)) ## 15.10.1 (2021-12-21) # 15.10.0 (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## 15.9.1 (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # 15.9.0 (2021-12-09) ### Features - **rich-text-types:** expose RT validation helper ([#292](https://github.com/contentful/rich-text/issues/292)) ([fc5a7cc](https://github.com/contentful/rich-text/commit/fc5a7cc27244f293a9a50acd785f7edcdaaa96ea)) # 15.7.0 (2021-11-11) ### Features - **rich-text-types:** Add TEXT_CONTAINERS ([#286](https://github.com/contentful/rich-text/issues/286)) ([3356ea8](https://github.com/contentful/rich-text/commit/3356ea815a46901a6637f177b04bcf1926adc88d)) ## 15.6.2 (2021-11-05) ## 15.6.1 (2021-11-05) # 15.6.0 (2021-11-04) ## 15.5.1 (2021-10-25) ### Bug Fixes - npm 15.5.0 ([#280](https://github.com/contentful/rich-text/issues/280)) ([e7aeba4](https://github.com/contentful/rich-text/commit/e7aeba49a3074fc9eae6aee569db4e30d1acb8b8)) # 15.5.0 (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) ## 15.3.6 (2021-09-15) ### Bug Fixes - allow table-row type to have both cells ([#267](https://github.com/contentful/rich-text/issues/267)) ([ec40598](https://github.com/contentful/rich-text/commit/ec405982109cb1c16e7adf71a541a98270d7f45b)) ## 15.3.5 (2021-09-13) ### Bug Fixes - **rich-text-types:** forbid Tables inside ListItem ([#266](https://github.com/contentful/rich-text/issues/266)) ([fc338bf](https://github.com/contentful/rich-text/commit/fc338bf040b8718057717d2681f800d5e26ba59d)) ## 15.3.3 (2021-09-07) ## 15.3.2 (2021-09-07) ## 15.3.1 (2021-09-07) # 15.3.0 (2021-09-06) ### Bug Fixes - allow the first item in Table to be a TableHeaderRow ([9a6de55](https://github.com/contentful/rich-text/commit/9a6de55ed97ca413d3922958850b8215922c06b5)) - don't export the helper TableHeaderRow type ([179c3cb](https://github.com/contentful/rich-text/commit/179c3cb10c0f65725297445afe955f0cca135005)) - typo ([2282665](https://github.com/contentful/rich-text/commit/2282665924404771b3353dd9b380d8863f1d4c41)) ### Features - add TableHeaderCell type ([5e25ac9](https://github.com/contentful/rich-text/commit/5e25ac9f35be2ad6c7ad7857cbde808cbc3437f9)) # 15.1.0 (2021-08-02) ### Features - add rowspan ([e0264fb](https://github.com/contentful/rich-text/commit/e0264fbe4d9467eedf78b7c5c3a9825a584124cf)) # 15.0.0 (2021-06-15) ### Features - add table markup support ([c6ae127](https://github.com/contentful/rich-text/commit/c6ae127aa460f4cd26cd6b671cd43fd2714bc650)) ## 14.1.2 (2020-11-02) ### Bug Fixes - 🐛 configure rollup copy correctly ([3446a33](https://github.com/contentful/rich-text/commit/3446a33fc5711087095a58b088828dfe6066bc7f)) ## 14.0.1 (2020-01-30) ### Features - 🎸 emptyDoc representing an empty RT document ([80315ef](https://github.com/contentful/rich-text/commit/80315ef5130b79336336ba31de8a55e42bafe319)) # 14.0.0 (2020-01-28) # 13.4.0 (2019-08-01) # 13.1.0 (2019-03-04) # 13.0.0 (2019-01-22) ## 12.1.2 (2018-12-14) ## 12.0.1 (2018-12-04) ### Bug Fixes - 🐛 add Object.values and Array.prototype.includes polyfills ([9e2ffb4](https://github.com/contentful/rich-text/commit/9e2ffb46e3564a523c74504270b29ccc8b8249ad)) # 12.0.0 (2018-11-29) # 11.0.0 (2018-11-27) ### Bug Fixes - 🐛 Removes obsolete field `title` from Hyperlinks ([8cb9027](https://github.com/contentful/rich-text/commit/8cb90279f5348a7fb59f211c2ba209a28fd432be)) # 10.3.0 (2018-11-26) # 10.2.0 (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # 10.1.0 (2018-11-16) ### Features - 🎸 adds json schema generation to rich text types ([8916140](https://github.com/contentful/rich-text/commit/89161404eb911f126be23ba2a146ebf748f7489e)) - 🎸 introduces top-level-block type ([a6bf35e](https://github.com/contentful/rich-text/commit/a6bf35e7c9ca35915a512de774b3a3fdc4c76e5d)) ## 10.0.1 (2018-11-08) # 10.0.0 (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## 9.0.2 (2018-10-31) # 9.0.0 (2018-10-30) ### Features - 🎸 Explicitly declare nodeClass and nodeType in core types ([0749c61](https://github.com/contentful/rich-text/commit/0749c6199bb2681509608539c76bd8149cade564)) ### BREAKING CHANGES - Potentially breaks TypeScript libraries pulling this in as a dependency if they are not explicitly stating nodeClass and nodeType. ## 8.0.3 (2018-10-30) ## 8.0.2 (2018-10-29) ## 8.0.1 (2018-10-26) ### Bug Fixes - 🐛 Revert mock hypenation commits ([bde9432](https://github.com/contentful/rich-text/commit/bde94323fcc02bf5ab3feeef46a9d8fc8b08d59b)) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ## [15.15.1](https://github.com/contentful/rich-text/compare/v15.15.0...v15.15.1) (2022-11-30) ### Bug Fixes - **release:** switch to yarn ([#420](https://github.com/contentful/rich-text/issues/420)) ([0e53501](https://github.com/contentful/rich-text/commit/0e53501eb94b3d1c76ac88ca30943d2675e536c8)) # [15.15.0](https://github.com/contentful/rich-text/compare/v15.14.1...v15.15.0) (2022-11-29) ### Features - adding v1 marks to rich text types [TOL-786] ([#416](https://github.com/contentful/rich-text/issues/416)) ([8885de8](https://github.com/contentful/rich-text/commit/8885de8ebba7736de46b0b8892a8aa15bb8d7ba4)) ## [15.14.1](https://github.com/contentful/rich-text/compare/v15.14.0...v15.14.1) (2022-11-23) **Note:** Version bump only for package @contentful/rich-text-types # [15.14.0](https://github.com/contentful/rich-text/compare/v15.13.2...v15.14.0) (2022-11-14) ### Features - add super/sub script types ([#391](https://github.com/contentful/rich-text/issues/391)) ([2562f66](https://github.com/contentful/rich-text/commit/2562f66278f0eff4eeeb367025d4b465773893d1)) ## [15.13.2](https://github.com/contentful/rich-text/compare/v15.13.1...v15.13.2) (2022-09-07) ### Bug Fixes - add prettier write command ([#345](https://github.com/contentful/rich-text/issues/345)) ([0edad4c](https://github.com/contentful/rich-text/commit/0edad4c3176cea85d56a55fc5f4072419d730c8a)) - revert change to monorepo tsconfig, apply to tsconfig in rich-text-types ([#358](https://github.com/contentful/rich-text/issues/358)) ([56126cc](https://github.com/contentful/rich-text/commit/56126cc9ed3704f21b89d2dbded160be0265f153)) ## [15.12.1](https://github.com/contentful/rich-text/compare/v15.12.0...v15.12.1) (2022-04-21) **Note:** Version bump only for package @contentful/rich-text-types # [15.12.0](https://github.com/contentful/rich-text/compare/v15.11.2...v15.12.0) (2022-03-25) ### Features - enforce minItems in table-related node types ([#314](https://github.com/contentful/rich-text/issues/314)) ([1125331](https://github.com/contentful/rich-text/commit/112533100f66ae01cd9944069dc62fc95f1737a5)) ## [15.11.1](https://github.com/contentful/rich-text/compare/v15.11.0...v15.11.1) (2022-01-04) ### Bug Fixes - **rich-text-types:** remove RT validation helpers ([#302](https://github.com/contentful/rich-text/issues/302)) ([fcd3a27](https://github.com/contentful/rich-text/commit/fcd3a277952f53eb3ae6ebb559ae6a02f5553c87)), closes [#295](https://github.com/contentful/rich-text/issues/295) [#274](https://github.com/contentful/rich-text/issues/274) # [15.11.0](https://github.com/contentful/rich-text/compare/v15.10.1...v15.11.0) (2021-12-27) ### Features - **rich-text-types:** expose HEADINGS array ([#301](https://github.com/contentful/rich-text/issues/301)) ([758539d](https://github.com/contentful/rich-text/commit/758539d46f3db13c21ca2f6d74a389a6fef21803)) ## [15.10.1](https://github.com/contentful/rich-text/compare/v15.10.0...v15.10.1) (2021-12-21) **Note:** Version bump only for package @contentful/rich-text-types # [15.10.0](https://github.com/contentful/rich-text/compare/v15.9.1...v15.10.0) (2021-12-15) ### Features - support custom error transformer ([#296](https://github.com/contentful/rich-text/issues/296)) ([9449b87](https://github.com/contentful/rich-text/commit/9449b87fc063a00f11cfe7b2bc0fdb4d91251c69)) ## [15.9.1](https://github.com/contentful/rich-text/compare/v15.9.0...v15.9.1) (2021-12-10) ### Bug Fixes - **rich-text-types:** resolve generated JSON schemas ([#294](https://github.com/contentful/rich-text/issues/294)) ([1e5b4c4](https://github.com/contentful/rich-text/commit/1e5b4c474e1e27e97df177748c0c8df365a2ab71)) # [15.9.0](https://github.com/contentful/rich-text/compare/v15.8.0...v15.9.0) (2021-12-09) ### Features - **rich-text-types:** expose RT validation helper ([#292](https://github.com/contentful/rich-text/issues/292)) ([fc5a7cc](https://github.com/contentful/rich-text/commit/fc5a7cc27244f293a9a50acd785f7edcdaaa96ea)) # [15.7.0](https://github.com/contentful/rich-text/compare/v15.6.2...v15.7.0) (2021-11-11) ### Features - **rich-text-types:** Add TEXT_CONTAINERS ([#286](https://github.com/contentful/rich-text/issues/286)) ([3356ea8](https://github.com/contentful/rich-text/commit/3356ea815a46901a6637f177b04bcf1926adc88d)) ## [15.6.2](https://github.com/contentful/rich-text/compare/v15.6.1...v15.6.2) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-types ## [15.6.1](https://github.com/contentful/rich-text/compare/v15.6.0...v15.6.1) (2021-11-05) **Note:** Version bump only for package @contentful/rich-text-types # [15.6.0](https://github.com/contentful/rich-text/compare/v15.5.1...v15.6.0) (2021-11-04) **Note:** Version bump only for package @contentful/rich-text-types ## [15.5.1](https://github.com/contentful/rich-text/compare/v15.5.0...v15.5.1) (2021-10-25) ### Bug Fixes - npm 15.5.0 ([#280](https://github.com/contentful/rich-text/issues/280)) ([e7aeba4](https://github.com/contentful/rich-text/commit/e7aeba49a3074fc9eae6aee569db4e30d1acb8b8)) # [15.5.0](https://github.com/contentful/rich-text/compare/v15.4.0...v15.5.0) (2021-10-25) ### Features - add v1 node types constraints ([#279](https://github.com/contentful/rich-text/issues/279)) ([5026023](https://github.com/contentful/rich-text/commit/5026023610ec1439f24fd32df9977c2cd4c13e86)) ## [15.3.6](https://github.com/contentful/rich-text/compare/v15.3.5...v15.3.6) (2021-09-15) ### Bug Fixes - allow table-row type to have both cells ([#267](https://github.com/contentful/rich-text/issues/267)) ([ec40598](https://github.com/contentful/rich-text/commit/ec405982109cb1c16e7adf71a541a98270d7f45b)) ## [15.3.5](https://github.com/contentful/rich-text/compare/v15.3.4...v15.3.5) (2021-09-13) ### Bug Fixes - **rich-text-types:** forbid Tables inside ListItem ([#266](https://github.com/contentful/rich-text/issues/266)) ([fc338bf](https://github.com/contentful/rich-text/commit/fc338bf040b8718057717d2681f800d5e26ba59d)) ## [15.3.3](https://github.com/contentful/rich-text/compare/v15.3.2...v15.3.3) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-types ## [15.3.2](https://github.com/contentful/rich-text/compare/v15.3.1...v15.3.2) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-types ## [15.3.1](https://github.com/contentful/rich-text/compare/v15.3.0...v15.3.1) (2021-09-07) **Note:** Version bump only for package @contentful/rich-text-types # [15.3.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.3.0) (2021-09-06) ### Bug Fixes - allow the first item in Table to be a TableHeaderRow ([9a6de55](https://github.com/contentful/rich-text/commit/9a6de55ed97ca413d3922958850b8215922c06b5)) - don't export the helper TableHeaderRow type ([179c3cb](https://github.com/contentful/rich-text/commit/179c3cb10c0f65725297445afe955f0cca135005)) - typo ([2282665](https://github.com/contentful/rich-text/commit/2282665924404771b3353dd9b380d8863f1d4c41)) ### Features - add TableHeaderCell type ([5e25ac9](https://github.com/contentful/rich-text/commit/5e25ac9f35be2ad6c7ad7857cbde808cbc3437f9)) # [15.2.0](https://github.com/contentful/rich-text/compare/v15.2.0...v15.1.0) (2021-09-06) ### Bug Fixes - allow the first item in Table to be a TableHeaderRow ([9a6de55](https://github.com/contentful/rich-text/commit/9a6de55ed97ca413d3922958850b8215922c06b5)) - don't export the helper TableHeaderRow type ([179c3cb](https://github.com/contentful/rich-text/commit/179c3cb10c0f65725297445afe955f0cca135005)) - typo ([2282665](https://github.com/contentful/rich-text/commit/2282665924404771b3353dd9b380d8863f1d4c41)) ### Features - add TableHeaderCell type ([5e25ac9](https://github.com/contentful/rich-text/commit/5e25ac9f35be2ad6c7ad7857cbde808cbc3437f9)) # [15.1.0](https://github.com/contentful/rich-text/compare/v15.0.0...v15.1.0) (2021-08-02) ### Features - add rowspan ([e0264fb](https://github.com/contentful/rich-text/commit/e0264fbe4d9467eedf78b7c5c3a9825a584124cf)) # [15.0.0](https://github.com/contentful/rich-text/compare/v14.2.0...v15.0.0) (2021-06-15) ### Features - add table markup support ([c6ae127](https://github.com/contentful/rich-text/commit/c6ae127aa460f4cd26cd6b671cd43fd2714bc650)) ## [14.1.2](https://github.com/contentful/rich-text/compare/v14.0.1...v14.1.2) (2020-11-02) ### Bug Fixes - 🐛 configure rollup copy correctly ([3446a33](https://github.com/contentful/rich-text/commit/3446a33fc5711087095a58b088828dfe6066bc7f)) ## [14.0.1](https://github.com/contentful/rich-text/compare/v14.0.0...v14.0.1) (2020-01-30) ### Features - 🎸 emptyDoc representing an empty RT document ([80315ef](https://github.com/contentful/rich-text/commit/80315ef5130b79336336ba31de8a55e42bafe319)) # [14.0.0](https://github.com/contentful/rich-text/compare/v13.4.0...v14.0.0) (2020-01-28) # [13.4.0](https://github.com/contentful/rich-text/compare/v13.3.0...v13.4.0) (2019-08-01) # [13.1.0](https://github.com/contentful/rich-text/compare/v13.0.1...v13.1.0) (2019-03-04) # [13.0.0](https://github.com/contentful/rich-text/compare/v12.2.1...v13.0.0) (2019-01-22) ## [12.1.2](https://github.com/contentful/rich-text/compare/v12.1.1...v12.1.2) (2018-12-14) ## [12.0.1](https://github.com/contentful/rich-text/compare/v12.0.0...v12.0.1) (2018-12-04) ### Bug Fixes - 🐛 add Object.values and Array.prototype.includes polyfills ([9e2ffb4](https://github.com/contentful/rich-text/commit/9e2ffb46e3564a523c74504270b29ccc8b8249ad)) # [12.0.0](https://github.com/contentful/rich-text/compare/v11.0.0...v12.0.0) (2018-11-29) # [11.0.0](https://github.com/contentful/rich-text/compare/v10.3.0...v11.0.0) (2018-11-27) ### Bug Fixes - 🐛 Removes obsolete field `title` from Hyperlinks ([8cb9027](https://github.com/contentful/rich-text/commit/8cb90279f5348a7fb59f211c2ba209a28fd432be)) # [10.3.0](https://github.com/contentful/rich-text/compare/v10.2.0...v10.3.0) (2018-11-26) # [10.2.0](https://github.com/contentful/rich-text/compare/v10.1.0...v10.2.0) (2018-11-19) ### Features - 🎸 Add rich-text-references ([363b4e5](https://github.com/contentful/rich-text/commit/363b4e509e94af0932fd7cece8e56beafe8d67d2)) # [10.1.0](https://github.com/contentful/rich-text/compare/v10.0.5...v10.1.0) (2018-11-16) ### Features - 🎸 adds json schema generation to rich text types ([8916140](https://github.com/contentful/rich-text/commit/89161404eb911f126be23ba2a146ebf748f7489e)) - 🎸 introduces top-level-block type ([a6bf35e](https://github.com/contentful/rich-text/commit/a6bf35e7c9ca35915a512de774b3a3fdc4c76e5d)) ## [10.0.1](https://github.com/contentful/rich-text/compare/v10.0.0...v10.0.1) (2018-11-08) # [10.0.0](https://github.com/contentful/rich-text/compare/v9.0.2...v10.0.0) (2018-11-02) ### Features - 🎸 removes nodeClass from Node interface ([09b8162](https://github.com/contentful/rich-text/commit/09b8162bcba65bc13afa14b2b5ff046c9fed7b3b)) ### BREAKING CHANGES - Removes accidentally added nodeClass ## [9.0.2](https://github.com/contentful/rich-text/compare/v9.0.1...v9.0.2) (2018-10-31) # [9.0.0](https://github.com/contentful/rich-text/compare/v8.0.3...v9.0.0) (2018-10-30) ### Features - 🎸 Explicitly declare nodeClass and nodeType in core types ([0749c61](https://github.com/contentful/rich-text/commit/0749c6199bb2681509608539c76bd8149cade564)) ### BREAKING CHANGES - Potentially breaks TypeScript libraries pulling this in as a dependency if they are not explicitly stating nodeClass and nodeType. ## [8.0.3](https://github.com/contentful/rich-text/compare/v8.0.2...v8.0.3) (2018-10-30) ## [8.0.2](https://github.com/contentful/rich-text/compare/v8.0.1...v8.0.2) (2018-10-29) ## [8.0.1](https://github.com/contentful/rich-text/compare/v8.0.0...v8.0.1) (2018-10-26) ### Bug Fixes - 🐛 Revert mock hypenation commits ([bde9432](https://github.com/contentful/rich-text/commit/bde94323fcc02bf5ab3feeef46a9d8fc8b08d59b)) ### Features - 🎸 take all packages out of "demo mode" ([815f18b](https://github.com/contentful/rich-text/commit/815f18be6a914e7e4782790ee46053689712494b)) ### BREAKING CHANGES - renames all packages # 6.0.0 (2018-10-24) ================================================ FILE: packages/rich-text-types/README.md ================================================ # rich-text-types Type definitions and constants for the Contentful rich text field type. ================================================ FILE: packages/rich-text-types/__mocks__/@lingui/core/macro.js ================================================ const t = ({ _id, message }) => message; const plural = (count, options) => { let message = options.other; if (count === 0) { message = options.zero || options[0] || options.other; } if (count === 1) { message = options.one || options.other; } return message.replace('#', count); }; module.exports = { t, plural }; ================================================ FILE: packages/rich-text-types/__test__/helpers.test.ts ================================================ import { BLOCKS } from '../src/blocks'; import { helpers } from '../src/index'; import { Document, Mark } from '../src/types'; describe('helpers', () => { describe('isEmptyParagraph', () => { it('returns true for empty paragraph', () => { const node = { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, ], }; expect(helpers.isEmptyParagraph(node)).toBe(true); }); it('returns false for non-empty paragraph', () => { const node = { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: 'Hello world', marks: [] as Mark[], data: {}, }, ], }; expect(helpers.isEmptyParagraph(node)).toBe(false); }); it('returns false for paragraph with multiple text nodes', () => { const node = { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, ], }; expect(helpers.isEmptyParagraph(node)).toBe(false); }); it('returns false for non-paragraph node', () => { const node = { nodeType: BLOCKS.HEADING_1, data: {}, content: [ { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, ], }; expect(helpers.isEmptyParagraph(node)).toBe(false); }); }); describe('stripEmptyTrailingParagraphFromDocument', () => { it('strips empty trailing paragraph', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: 'Hello world', marks: [] as Mark[], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, ], }, ], }; const result = helpers.stripEmptyTrailingParagraphFromDocument(document); expect(result.content).toHaveLength(1); expect(result.content[0].nodeType).toBe(BLOCKS.PARAGRAPH); }); it('does not strip empty trailing paragraph when it is the only child', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: '', marks: [] as Mark[], data: {}, }, ], }, ], }; const result = helpers.stripEmptyTrailingParagraphFromDocument(document); expect(result.content).toHaveLength(1); }); it('does not strip non-empty trailing paragraph', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: 'Hello world', marks: [] as Mark[], data: {}, }, ], }, { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text' as const, value: 'Not empty', marks: [] as Mark[], data: {}, }, ], }, ], }; const result = helpers.stripEmptyTrailingParagraphFromDocument(document); expect(result.content).toHaveLength(2); }); }); }); ================================================ FILE: packages/rich-text-types/__test__/schemaConstraints.test.ts ================================================ import { BLOCKS } from '../src/blocks'; import { CONTAINERS, TEXT_CONTAINERS, VOID_BLOCKS } from '../src/schemaConstraints'; const allKnownBlocks = Object.values(BLOCKS); describe('schema constraints', () => { it('all block node types are either considered a container or void', () => { const blocks = [ BLOCKS.DOCUMENT, // Root block could be in CONTAINERS but isn't. ...VOID_BLOCKS, ...TEXT_CONTAINERS, ...Object.keys(CONTAINERS), ]; expect(blocks).toEqual(expect.arrayContaining(allKnownBlocks)); expect(blocks.length).toEqual(allKnownBlocks.length); }); it('should allow UL_LIST and OL_LIST blocks as children of TABLE_CELL', () => { // Get the children of TABLE_CELL const tableCellChildren = CONTAINERS[BLOCKS.TABLE_CELL]; // Check that UL_LIST and OL_LIST are in the children array expect(tableCellChildren).toContain(BLOCKS.UL_LIST); expect(tableCellChildren).toContain(BLOCKS.OL_LIST); }); }); ================================================ FILE: packages/rich-text-types/__test__/validation.test.ts ================================================ import { BLOCKS } from '../src/blocks'; import { INLINES } from '../src/inlines'; import type { Document } from '../src/types'; import { validateRichTextDocument } from '../src/validator/index'; describe('validation', () => { it('fails if it is not document node', () => { // @ts-expect-error we force a wrong node type to check that it fails const document: Document = { nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: ['document'], name: 'in', path: ['nodeType'], value: 'paragraph', }, ]); }); it('fails if it has an invalid shape', () => { // @ts-expect-error we force a wrong node type to check that it fails const document: Document = { nodeType: BLOCKS.DOCUMENT }; expect(validateRichTextDocument(document)).toEqual([ { name: 'required', path: ['content'], details: 'The property "content" is required here', }, { name: 'required', path: ['data'], details: 'The property "data" is required here', }, ]); }); it('fails if it has nested documents', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong node type to check that it fails content: [{ nodeType: BLOCKS.DOCUMENT, content: [], data: {} }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: 'document', }, ]); }); it('fails without a nodeType property', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong node type to check that it fails content: [{ content: [], data: {} }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: undefined, }, ]); }); it('fails on custom nodeTypes (unknown nodeType)', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong node type to check that it fails content: [{ nodeType: 'custom-node-type', content: [], data: {} }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: 'custom-node-type', }, ]); }); it('fails without a content property', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong node type to check that it fails content: [{ nodeType: BLOCKS.PARAGRAPH, data: {} }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "content" is required here', name: 'required', path: ['content', 0, 'content'], value: undefined, }, ]); }); it('fails with a invalid content property', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong type to check that it fails content: [{ nodeType: BLOCKS.PARAGRAPH, content: 'Hello World', data: {} }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "content" is incorrect, expected type: Array', name: 'type', path: ['content', 0, 'content'], type: 'Array', value: 'Hello World', }, ]); }); it('fails without data property', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, // @ts-expect-error we force a wrong node type to check that it fails content: [{ nodeType: BLOCKS.PARAGRAPH, content: [] }], }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "data" is required here', name: 'required', path: ['data'], }, ]); }); it('fails with invalid data property', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: null }], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "data" is incorrect, expected type: Object', name: 'type', path: ['content', 0, 'data'], type: 'Object', value: null, }, ]); }); it('fails if undefined is in the content list', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }, undefined], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "1" is incorrect, expected type: Object', name: 'type', path: ['content', 1], type: 'Object', value: undefined, }, ]); }); it('fails if undefined is in the content list of child nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.UL_LIST, content: [ { nodeType: BLOCKS.LIST_ITEM, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }, undefined], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "1" is incorrect, expected type: Object', name: 'type', path: ['content', 0, 'content', 0, 'content', 1], type: 'Object', value: undefined, }, ]); }); it('fails with unknown properties', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [], data: {}, // @ts-expect-error we force a wrong property to check that it fails myCustomProperty: 'Hello World', }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "myCustomProperty" is not expected', name: 'unexpected', path: ['myCustomProperty'], }, ]); }); it.each([BLOCKS.LIST_ITEM, BLOCKS.TABLE_ROW, BLOCKS.TABLE_HEADER_CELL, BLOCKS.TABLE_CELL])( 'fails with a invalid block node as children (nodeType: %s) of the root node', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { // @ts-expect-error we force a wrong node type to check that it fails nodeType, content: [], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: nodeType, }, ]); }, ); it.each(Object.values(INLINES))( 'fails with a inline node (%s) as direct children of the root node', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { // @ts-expect-error we force a wrong node type to check that it fails nodeType, content: [], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: nodeType, }, ]); }, ); it('fails with text as a direct children of the root node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { // @ts-expect-error we force a wrong node type to check that it fails nodeType: 'text', data: {}, marks: [], value: 'Hello World', }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: 'text', }, ]); }); it('fails with inline node and text as a direct children of the root node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { // @ts-expect-error we force a wrong node type to check that it fails nodeType: 'text', data: {}, marks: [], value: 'Hello World', }, { // @ts-expect-error we force a wrong node type to check that it fails nodeType: INLINES.ASSET_HYPERLINK, data: { target: {} }, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'table', 'unordered-list', ], name: 'in', path: ['content', 0, 'nodeType'], value: 'text', }, ]); }); it.each([BLOCKS.OL_LIST, BLOCKS.UL_LIST] as const)( 'fails for invalid block nodes inside of (%s)', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: nodeType, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.LIST_ITEM], name: 'in', path: ['content', 0, 'content', 0, 'nodeType'], value: BLOCKS.PARAGRAPH, }, ]); }, ); it('fails on text node directly inside of a list item node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.UL_LIST, content: [ { nodeType: BLOCKS.LIST_ITEM, content: [ { nodeType: 'text', data: {}, value: 'Hello World', marks: [], }, ], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'blockquote', 'embedded-asset-block', 'embedded-entry-block', 'embedded-resource-block', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'hr', 'ordered-list', 'paragraph', 'unordered-list', ], name: 'in', path: ['content', 0, 'content', 0, 'content', 0, 'nodeType'], value: 'text', }, ]); }); it('fails on invalid block nodes inside of a table node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.TABLE_ROW], name: 'in', path: ['content', 0, 'content', 0, 'nodeType'], value: BLOCKS.PARAGRAPH, }, ]); }); it('fails on invalid block nodes inside of a table row node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL], name: 'in', path: ['content', 0, 'content', 0, 'content', 0, 'nodeType'], value: BLOCKS.PARAGRAPH, }, ]); }); it('fails on invalid block nodes inside of a table header node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL], name: 'in', path: ['content', 0, 'content', 0, 'content', 0, 'nodeType'], value: BLOCKS.PARAGRAPH, }, ]); }); it.each([BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL] as const)( 'fails on invalid node inside of %s', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [ { nodeType, content: [{ nodeType: 'text', data: {}, marks: [], value: 'Hello World' }], data: {}, }, ], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.PARAGRAPH], name: 'in', path: ['content', 0, 'content', 0, 'content', 0, 'content', 0, 'nodeType'], value: 'text', }, ]); }, ); it('fails if a table node has not at least one table row', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Size must be at least 1', min: 1, name: 'size', path: ['content', 0, 'content'], value: [], }, ]); }); it('fails if a table row node has not at least one table cell', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Size must be at least 1', min: 1, name: 'size', path: ['content', 0, 'content', 0, 'content'], value: [], }, ]); }); it.each([BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL] as const)( 'fails if a %s has not at least one child', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [ { nodeType, content: [], data: {}, }, ], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Size must be at least 1', min: 1, name: 'size', path: ['content', 0, 'content', 0, 'content', 0, 'content'], value: [], }, ]); }, ); it('fails if inline nodes contains something else as a inline node or text', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [ { nodeType: INLINES.HYPERLINK, // @ts-expect-error we force a wrong node type to check that it fails content: [{ nodeType: BLOCKS.PARAGRAPH, content: [], data: {} }], data: { uri: '' }, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: ['text'], name: 'in', path: ['content', 0, 'content', 0, 'content', 0, 'nodeType'], value: BLOCKS.PARAGRAPH, }, ]); }); it.each([ BLOCKS.HEADING_1, BLOCKS.HEADING_2, BLOCKS.HEADING_3, BLOCKS.HEADING_4, BLOCKS.HEADING_5, BLOCKS.HEADING_6, ] as const)( 'fails if the headline node (%s) contains something else as a inline or text node', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType, content: [ { nodeType: BLOCKS.QUOTE, content: [{ nodeType: 'text', value: 'Hello World', data: {}, marks: [] }], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [ 'asset-hyperlink', 'embedded-entry-inline', 'embedded-resource-inline', 'entry-hyperlink', 'hyperlink', 'resource-hyperlink', 'text', ], name: 'in', path: ['content', 0, 'content', 0, 'nodeType'], value: BLOCKS.QUOTE, }, ]); }, ); it('fails on invalid block nodes inside of a quote node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.QUOTE, content: [ { nodeType: BLOCKS.HEADING_1, content: [{ nodeType: 'text', value: 'Hello World', data: {}, marks: [] }], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'Value must be one of expected values', expected: [BLOCKS.PARAGRAPH], name: 'in', path: ['content', 0, 'content', 0, 'nodeType'], value: BLOCKS.HEADING_1, }, ]); }); it('fails without value property on text nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [ { nodeType: 'text', value: null, data: {}, marks: [], }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "value" is incorrect, expected type: String', name: 'type', path: ['content', 0, 'content', 0, 'value'], type: 'String', value: null, }, ]); }); it('fails with invalid row/colspan on table cell nodes', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [ { nodeType: BLOCKS.TABLE_CELL, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [{ nodeType: 'text', value: 'Hello Table', data: {}, marks: [] }], data: {}, }, ], data: { rowspan: 'argh' }, }, ], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The type of "rowspan" is incorrect, expected type: Number', name: 'type', path: ['content', 0, 'content', 0, 'content', 0, 'data', 'rowspan'], type: 'Number', value: 'argh', }, ]); }); it.each([INLINES.ASSET_HYPERLINK, INLINES.ENTRY_HYPERLINK, INLINES.RESOURCE_HYPERLINK] as const)( 'fails with invalid properties for %s', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [ { nodeType, data: {}, content: [{ nodeType: 'text', value: `Hello ${nodeType}`, data: {}, marks: [] }], }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "target" is required here', name: 'required', path: ['content', 0, 'content', 0, 'data', 'target'], }, ]); }, ); it('fails with invalid properties for hypperlink node', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [ { nodeType: INLINES.HYPERLINK, data: {}, content: [{ nodeType: 'text', value: 'Hello hyperlink', data: {}, marks: [] }], }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "uri" is required here', name: 'required', path: ['content', 0, 'content', 0, 'data', 'uri'], }, ]); }); it.each([INLINES.EMBEDDED_ENTRY, INLINES.EMBEDDED_RESOURCE] as const)( 'fails with invalid properties for %s', (nodeType) => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [ { nodeType, data: {}, content: [], }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([ { details: 'The property "target" is required here', name: 'required', path: ['content', 0, 'content', 0, 'data', 'target'], }, ]); }, ); it('succeeds with a valid structure', () => { const document: Document = { nodeType: BLOCKS.DOCUMENT, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [{ nodeType: 'text', value: 'Hello World', data: {}, marks: [] }], data: {}, }, { nodeType: BLOCKS.HEADING_1, content: [{ nodeType: 'text', value: 'Hello Headline', data: {}, marks: [] }], data: {}, }, { nodeType: BLOCKS.UL_LIST, content: [ { nodeType: BLOCKS.LIST_ITEM, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [{ nodeType: 'text', value: 'Hello List', data: {}, marks: [] }], data: {}, }, ], data: {}, }, ], data: {}, }, { nodeType: BLOCKS.TABLE, content: [ { nodeType: BLOCKS.TABLE_ROW, content: [ { nodeType: BLOCKS.TABLE_CELL, content: [ { nodeType: BLOCKS.PARAGRAPH, content: [{ nodeType: 'text', value: 'Hello Table', data: {}, marks: [] }], data: {}, }, ], data: { rowspan: 2, colspan: 2 }, }, ], data: {}, }, ], data: {}, }, ], data: {}, }; expect(validateRichTextDocument(document)).toEqual([]); }); }); ================================================ FILE: packages/rich-text-types/jest.config.js ================================================ /* eslint-disable */ const getBaseConfig = require('../../baseJestConfig'); const packageJson = require('./package.json'); const packageName = packageJson.name.split('@contentful/')[1]; module.exports = { ...getBaseConfig(packageName), }; ================================================ FILE: packages/rich-text-types/package.json ================================================ { "name": "@contentful/rich-text-types", "version": "17.2.7", "main": "dist/cjs/index.js", "module": "dist/esm/index.mjs", "types": "dist/types/index.d.ts", "exports": { ".": { "types": "./dist/types/index.d.ts", "import": "./dist/esm/index.mjs", "require": "./dist/cjs/index.js", "default": "./dist/cjs/index.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/contentful/rich-text.git" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "publishConfig": { "access": "public", "registry": "https://npm.pkg.github.com/" }, "scripts": { "prebuild": "rimraf dist", "build": "npm run build:types && npm run build:cjs && npm run build:esm && npm run fix-esm-imports", "build:types": "tsc --outDir dist/types --emitDeclarationOnly", "build:cjs": "swc src --strip-leading-paths --config-file ../../.swcrc -d dist/cjs -C module.type=commonjs", "build:esm": "swc src --strip-leading-paths --config-file ../../.swcrc -d dist/esm --out-file-extension mjs -C jsc.target=es2022 -C module.type=es6 -C module.resolveFully=true -C module.outFileExtension=mjs", "fix-esm-imports": "node ./scripts/fix-esm-import-extensions.mjs", "start": "tsc && swc src --strip-leading-paths --config-file ../../.swcrc -d dist/esm --out-file-extension mjs -C module.type=es6 -C module.resolveFully=true -C module.outFileExtension=mjs -w", "test": "jest" }, "devDependencies": { "@types/jest": "^30.0.0", "@types/node": "^25.0.2", "ts-jest": "^29.1.2" } } ================================================ FILE: packages/rich-text-types/scripts/fix-esm-import-extensions.mjs ================================================ // Rewrite relative `*.js` specifiers to `*.mjs` across built ESM files import { readdir, readFile, writeFile } from 'node:fs/promises'; import { dirname, join, extname } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const esmDir = join(here, '..', 'dist', 'esm'); // Recursively walk all .mjs files under `dir` async function* walk(dir) { for (const entry of await readdir(dir, { withFileTypes: true })) { const p = join(dir, entry.name); if (entry.isDirectory()) yield* walk(p); else if (extname(p) === '.mjs') yield p; } } const REWRITES = [ // import ... from './x.js' [/(from\s+['"])(\.{1,2}\/[^'"]+)\.js(['"])/g, '$1$2.mjs$3'], // export ... from './x.js' [/(export\s+(?:\*|{[\s\S]*?})\s+from\s+['"])(\.{1,2}\/[^'"]+)\.js(['"])/g, '$1$2.mjs$3'], // import('./x.js') [/(import\(\s*['"])(\.{1,2}\/[^'"]+)\.js(['"]\s*\))/g, '$1$2.mjs$3'], ]; function rewrite(code) { return REWRITES.reduce((s, [re, replacement]) => s.replace(re, replacement), code); } for await (const file of walk(esmDir)) { const original = await readFile(file, 'utf8'); const updated = rewrite(original); if (updated !== original) { await writeFile(file, updated, 'utf8'); } } ================================================ FILE: packages/rich-text-types/src/blocks.ts ================================================ /** * Map of all Contentful block types. Blocks contain inline or block nodes. */ export enum BLOCKS { DOCUMENT = 'document', PARAGRAPH = 'paragraph', HEADING_1 = 'heading-1', HEADING_2 = 'heading-2', HEADING_3 = 'heading-3', HEADING_4 = 'heading-4', HEADING_5 = 'heading-5', HEADING_6 = 'heading-6', OL_LIST = 'ordered-list', UL_LIST = 'unordered-list', LIST_ITEM = 'list-item', HR = 'hr', QUOTE = 'blockquote', EMBEDDED_ENTRY = 'embedded-entry-block', EMBEDDED_ASSET = 'embedded-asset-block', EMBEDDED_RESOURCE = 'embedded-resource-block', TABLE = 'table', TABLE_ROW = 'table-row', TABLE_CELL = 'table-cell', TABLE_HEADER_CELL = 'table-header-cell', } ================================================ FILE: packages/rich-text-types/src/emptyDocument.ts ================================================ import { BLOCKS } from './blocks.js'; import { Document } from './types.js'; /** * A rich text document considered to be empty. * Any other document structure than this is not considered empty. */ export const EMPTY_DOCUMENT: Document = { nodeType: BLOCKS.DOCUMENT, data: {}, content: [ { nodeType: BLOCKS.PARAGRAPH, data: {}, content: [ { nodeType: 'text', value: '', marks: [], data: {}, }, ], }, ], }; ================================================ FILE: packages/rich-text-types/src/helpers.ts ================================================ import { BLOCKS } from './blocks.js'; import { INLINES } from './inlines.js'; import { Block, Inline, Node, Text, Document as CDocument } from './types.js'; /** * Tiny replacement for Object.values(object).includes(key) to * avoid including CoreJS polyfills */ function hasValue(obj: Record, value: unknown) { for (const key of Object.keys(obj)) { if (value === obj[key]) { return true; } } return false; } /** * Checks if the node is an instance of Inline. */ export function isInline(node: Node): node is Inline { return hasValue(INLINES, node.nodeType); } /** * Checks if the node is an instance of Block. */ export function isBlock(node: Node): node is Block { return hasValue(BLOCKS, node.nodeType); } /** * Checks if the node is an instance of Text. */ export function isText(node: Node): node is Text { return node.nodeType === 'text'; } /** * Checks if a paragraph is empty (has only one child and that child is an empty string text node) */ export function isEmptyParagraph(node: Block): boolean { if (node.nodeType !== BLOCKS.PARAGRAPH) { return false; } if (node.content.length !== 1) { return false; } const textNode = node.content[0]; return textNode.nodeType === 'text' && (textNode as Text).value === ''; } function isValidDocument(document: unknown): document is CDocument { return ( document != null && typeof document === 'object' && 'content' in document && Array.isArray((document as CDocument).content) ); } const MIN_NODES_FOR_STRIPPING = 2; /** * Strips empty trailing paragraph from a document if enabled * @param document - The rich text document to process * @returns A new document with the empty trailing paragraph removed (if conditions are met) * @example * const processedDoc = stripEmptyTrailingParagraphFromDocument(document); */ export function stripEmptyTrailingParagraphFromDocument(document: CDocument): CDocument { if (!isValidDocument(document) || document.content.length < MIN_NODES_FOR_STRIPPING) { return document; } const lastNode = document.content[document.content.length - 1]; // Check if the last node is an empty paragraph if (isEmptyParagraph(lastNode)) { return { ...document, content: document.content.slice(0, -1), }; } return document; } ================================================ FILE: packages/rich-text-types/src/index.ts ================================================ export { BLOCKS } from './blocks.js'; export { INLINES } from './inlines.js'; export { MARKS } from './marks.js'; export * from './schemaConstraints.js'; export * from './types.js'; export * from './nodeTypes.js'; export { EMPTY_DOCUMENT } from './emptyDocument.js'; import * as helpers from './helpers.js'; export { helpers }; export { validateRichTextDocument } from './validator/index.js'; export type { ValidationError } from './validator/index.js'; ================================================ FILE: packages/rich-text-types/src/inlines.ts ================================================ /** * Map of all Contentful inline types. Inline contain inline or text nodes. * * @note This should be kept in alphabetical order since the * [validation package](https://github.com/contentful/content-stack/tree/master/packages/validation) * relies on the values being in a predictable order. */ export enum INLINES { ASSET_HYPERLINK = 'asset-hyperlink', EMBEDDED_ENTRY = 'embedded-entry-inline', EMBEDDED_RESOURCE = 'embedded-resource-inline', ENTRY_HYPERLINK = 'entry-hyperlink', HYPERLINK = 'hyperlink', RESOURCE_HYPERLINK = 'resource-hyperlink', } ================================================ FILE: packages/rich-text-types/src/marks.ts ================================================ /** * Map of all Contentful marks. */ export enum MARKS { BOLD = 'bold', ITALIC = 'italic', UNDERLINE = 'underline', CODE = 'code', SUPERSCRIPT = 'superscript', SUBSCRIPT = 'subscript', STRIKETHROUGH = 'strikethrough', } ================================================ FILE: packages/rich-text-types/src/nodeTypes.ts ================================================ import { BLOCKS } from './blocks.js'; import { INLINES } from './inlines.js'; import { Block, Inline, ListItemBlock, Text } from './types.js'; // eslint-disable-next-line @typescript-eslint/ban-types type EmptyNodeData = {}; // BLOCKS // Heading export interface Heading1 extends Block { nodeType: BLOCKS.HEADING_1; data: EmptyNodeData; content: Array; } export interface Heading2 extends Block { nodeType: BLOCKS.HEADING_2; data: EmptyNodeData; content: Array; } export interface Heading3 extends Block { nodeType: BLOCKS.HEADING_3; data: EmptyNodeData; content: Array; } export interface Heading4 extends Block { nodeType: BLOCKS.HEADING_4; data: EmptyNodeData; content: Array; } export interface Heading5 extends Block { nodeType: BLOCKS.HEADING_5; data: EmptyNodeData; content: Array; } export interface Heading6 extends Block { nodeType: BLOCKS.HEADING_6; data: EmptyNodeData; content: Array; } // Paragraph export interface Paragraph extends Block { nodeType: BLOCKS.PARAGRAPH; data: EmptyNodeData; content: Array; } // Quote export interface Quote extends Block { nodeType: BLOCKS.QUOTE; data: EmptyNodeData; content: Paragraph[]; } // Horizontal rule export interface Hr extends Block { nodeType: BLOCKS.HR; /** * * @maxItems 0 */ data: EmptyNodeData; content: Array; } // OL export interface OrderedList extends Block { nodeType: BLOCKS.OL_LIST; data: EmptyNodeData; content: ListItem[]; } // UL export interface UnorderedList extends Block { nodeType: BLOCKS.UL_LIST; data: EmptyNodeData; content: ListItem[]; } export interface ListItem extends Block { nodeType: BLOCKS.LIST_ITEM; data: EmptyNodeData; content: ListItemBlock[]; } // taken from graphql schema-generator/contentful-types/link.ts export interface Link { sys: { type: 'Link'; linkType: T; id: string; }; } export interface ResourceLink { sys: { type: 'ResourceLink'; linkType: 'Contentful:Entry'; urn: string; }; } export interface EntryLinkBlock extends Block { nodeType: BLOCKS.EMBEDDED_ENTRY; data: { target: Link<'Entry'>; }; /** * * @maxItems 0 */ content: Array; } export interface AssetLinkBlock extends Block { nodeType: BLOCKS.EMBEDDED_ASSET; data: { target: Link<'Asset'>; }; /** * * @maxItems 0 */ content: Array; } export interface ResourceLinkBlock extends Block { nodeType: BLOCKS.EMBEDDED_RESOURCE; data: { target: ResourceLink; }; /** * * @maxItems 0 */ content: Array; } // INLINE export interface EntryLinkInline extends Inline { nodeType: INLINES.EMBEDDED_ENTRY; data: { target: Link<'Entry'>; }; /** * * @maxItems 0 */ content: Text[]; } export interface ResourceLinkInline extends Inline { nodeType: INLINES.EMBEDDED_RESOURCE; data: { target: ResourceLink; }; /** * * @maxItems 0 */ content: Text[]; } export interface Hyperlink extends Inline { nodeType: INLINES.HYPERLINK; data: { uri: string; }; content: Text[]; } export interface AssetHyperlink extends Inline { nodeType: INLINES.ASSET_HYPERLINK; data: { target: Link<'Asset'>; }; content: Text[]; } export interface EntryHyperlink extends Inline { nodeType: INLINES.ENTRY_HYPERLINK; data: { target: Link<'Entry'>; }; content: Text[]; } export interface ResourceHyperlink extends Inline { nodeType: INLINES.RESOURCE_HYPERLINK; data: { target: ResourceLink; }; content: Text[]; } export interface TableCell extends Block { nodeType: BLOCKS.TABLE_HEADER_CELL | BLOCKS.TABLE_CELL; data: { colspan?: number; rowspan?: number; }; /** * @minItems 1 */ content: Paragraph[]; } export interface TableHeaderCell extends TableCell { nodeType: BLOCKS.TABLE_HEADER_CELL; } // An abstract table row can have both table cell types export interface TableRow extends Block { nodeType: BLOCKS.TABLE_ROW; data: EmptyNodeData; /** * @minItems 1 */ content: TableCell[]; } export interface Table extends Block { nodeType: BLOCKS.TABLE; data: EmptyNodeData; /** * @minItems 1 */ content: TableRow[]; } ================================================ FILE: packages/rich-text-types/src/schemaConstraints.ts ================================================ import { BLOCKS } from './blocks.js'; import { INLINES } from './inlines.js'; import { MARKS } from './marks.js'; export type TopLevelBlockEnum = | BLOCKS.PARAGRAPH | BLOCKS.HEADING_1 | BLOCKS.HEADING_2 | BLOCKS.HEADING_3 | BLOCKS.HEADING_4 | BLOCKS.HEADING_5 | BLOCKS.HEADING_6 | BLOCKS.OL_LIST | BLOCKS.UL_LIST | BLOCKS.HR | BLOCKS.QUOTE | BLOCKS.EMBEDDED_ENTRY | BLOCKS.EMBEDDED_ASSET | BLOCKS.EMBEDDED_RESOURCE | BLOCKS.TABLE; /** * Array of all top level block types. * Only these block types can be the direct children of the document. */ export const TOP_LEVEL_BLOCKS: TopLevelBlockEnum[] = [ BLOCKS.PARAGRAPH, BLOCKS.HEADING_1, BLOCKS.HEADING_2, BLOCKS.HEADING_3, BLOCKS.HEADING_4, BLOCKS.HEADING_5, BLOCKS.HEADING_6, BLOCKS.OL_LIST, BLOCKS.UL_LIST, BLOCKS.HR, BLOCKS.QUOTE, BLOCKS.EMBEDDED_ENTRY, BLOCKS.EMBEDDED_ASSET, BLOCKS.EMBEDDED_RESOURCE, BLOCKS.TABLE, ]; export type ListItemBlockEnum = | BLOCKS.PARAGRAPH | BLOCKS.HEADING_1 | BLOCKS.HEADING_2 | BLOCKS.HEADING_3 | BLOCKS.HEADING_4 | BLOCKS.HEADING_5 | BLOCKS.HEADING_6 | BLOCKS.OL_LIST | BLOCKS.UL_LIST | BLOCKS.HR | BLOCKS.QUOTE | BLOCKS.EMBEDDED_ENTRY | BLOCKS.EMBEDDED_ASSET | BLOCKS.EMBEDDED_RESOURCE; /** * Array of all allowed block types inside list items */ export const LIST_ITEM_BLOCKS: TopLevelBlockEnum[] = [ BLOCKS.PARAGRAPH, BLOCKS.HEADING_1, BLOCKS.HEADING_2, BLOCKS.HEADING_3, BLOCKS.HEADING_4, BLOCKS.HEADING_5, BLOCKS.HEADING_6, BLOCKS.OL_LIST, BLOCKS.UL_LIST, BLOCKS.HR, BLOCKS.QUOTE, BLOCKS.EMBEDDED_ENTRY, BLOCKS.EMBEDDED_ASSET, BLOCKS.EMBEDDED_RESOURCE, ]; export const TABLE_BLOCKS = [ BLOCKS.TABLE, BLOCKS.TABLE_ROW, BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL, ]; /** * Array of all void block types */ export const VOID_BLOCKS = [ BLOCKS.HR, BLOCKS.EMBEDDED_ENTRY, BLOCKS.EMBEDDED_ASSET, BLOCKS.EMBEDDED_RESOURCE, ]; /** * Dictionary of all container block types, and the set block types they accept as children. * * Note: This does not include `[BLOCKS.DOCUMENT]: TOP_LEVEL_BLOCKS` */ export const CONTAINERS = { [BLOCKS.OL_LIST]: [BLOCKS.LIST_ITEM], [BLOCKS.UL_LIST]: [BLOCKS.LIST_ITEM], [BLOCKS.LIST_ITEM]: LIST_ITEM_BLOCKS, [BLOCKS.QUOTE]: [BLOCKS.PARAGRAPH], [BLOCKS.TABLE]: [BLOCKS.TABLE_ROW], [BLOCKS.TABLE_ROW]: [BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL], [BLOCKS.TABLE_CELL]: [BLOCKS.PARAGRAPH, BLOCKS.UL_LIST, BLOCKS.OL_LIST], [BLOCKS.TABLE_HEADER_CELL]: [BLOCKS.PARAGRAPH], }; /** * Array of all heading levels */ export const HEADINGS = [ BLOCKS.HEADING_1, BLOCKS.HEADING_2, BLOCKS.HEADING_3, BLOCKS.HEADING_4, BLOCKS.HEADING_5, BLOCKS.HEADING_6, ]; /** * Array of all block types that may contain text and inline nodes. */ export const TEXT_CONTAINERS = [BLOCKS.PARAGRAPH, ...HEADINGS]; /** * Node types before `tables` release. */ export const V1_NODE_TYPES = [ BLOCKS.DOCUMENT, BLOCKS.PARAGRAPH, BLOCKS.HEADING_1, BLOCKS.HEADING_2, BLOCKS.HEADING_3, BLOCKS.HEADING_4, BLOCKS.HEADING_5, BLOCKS.HEADING_6, BLOCKS.OL_LIST, BLOCKS.UL_LIST, BLOCKS.LIST_ITEM, BLOCKS.HR, BLOCKS.QUOTE, BLOCKS.EMBEDDED_ENTRY, BLOCKS.EMBEDDED_ASSET, INLINES.HYPERLINK, INLINES.ENTRY_HYPERLINK, INLINES.ASSET_HYPERLINK, INLINES.EMBEDDED_ENTRY, 'text', ]; /** * Marks before `superscript` & `subscript` release. */ export const V1_MARKS = [MARKS.BOLD, MARKS.CODE, MARKS.ITALIC, MARKS.UNDERLINE]; ================================================ FILE: packages/rich-text-types/src/types.ts ================================================ import { BLOCKS } from './blocks.js'; import { INLINES } from './inlines.js'; import { ListItemBlockEnum, TopLevelBlockEnum } from './schemaConstraints.js'; /** * @additionalProperties true */ export type NodeData = Record; export interface Node { readonly nodeType: string; data: NodeData; } export interface Block extends Node { nodeType: BLOCKS; content: Array; } export interface Inline extends Node { nodeType: INLINES; content: Array; } export interface TopLevelBlock extends Block { nodeType: TopLevelBlockEnum; } export interface Document extends Node { nodeType: BLOCKS.DOCUMENT; content: TopLevelBlock[]; } export interface Text extends Node { nodeType: 'text'; value: string; marks: Mark[]; } export interface Mark { type: string; } export interface ListItemBlock extends Block { nodeType: ListItemBlockEnum; } ================================================ FILE: packages/rich-text-types/src/validator/assert.ts ================================================ import { maxSizeError, typeMismatchError, enumError, unknownPropertyError, requiredPropertyError, minSizeError, } from './errors.js'; import type { Path } from './path.js'; import { ValidationError } from './types.js'; export class ObjectAssertion { private _errors: ValidationError[] = []; constructor( private readonly obj: Record, private readonly path: Path, ) {} catch = (...errors: ValidationError[]): void => { this._errors.push(...errors); }; get errors(): ValidationError[] { const serializeError = (error: ValidationError): string => JSON.stringify({ details: error.details, path: error.path, }); return this._errors.filter( (error, index) => this._errors.findIndex((step) => serializeError(error) === serializeError(step)) === index, ); } /** * Asserts the key exists in the object. You probably shouldn't call this * function directly. Instead, use `$.object`, `$.number`, `$.string`, etc. */ exists = (key: string): boolean => { if (key in this.obj) { return true; } this.catch( requiredPropertyError({ property: key, path: this.path.of(key), }), ); return false; }; /** * Asserts the key exists in the object and its value is a plain object. if * no key is provided, it asserts the object itself. */ public object = (key?: string): boolean => { const value = key ? this.obj[key] : this.obj; if (key) { if (!this.exists(key)) { return false; } } if (typeof value === 'object' && !Array.isArray(value) && value !== null) { return true; } const path = key ? this.path.of(key) : this.path; const property = key ?? this.path.last() ?? 'value'; this.catch( typeMismatchError({ typeName: 'Object', property, path, value, }), ); return false; }; /** * Asserts the key exists in the object and its value is a string. */ public string = (key: string): boolean => { const value = this.obj[key]; if (key && !this.exists(key)) { return false; } if (typeof value === 'string') { return true; } this.catch( typeMismatchError({ typeName: 'String', property: key, path: this.path.of(key), value, }), ); return false; }; /** * Asserts the key exists in the object and its value is a number. */ public number = (key: string, optional?: boolean): boolean => { const value = this.obj[key]; if (optional && !(key in this.obj)) { return true; } if (!this.exists(key)) { return false; } if (typeof value === 'number' && !Number.isNaN(value)) { return true; } this.catch( typeMismatchError({ typeName: 'Number', property: key, path: this.path.of(key), value, }), ); return false; }; /** * Asserts the key exists in the object and its value is an array. You don't * need to manually call this function before `$.each` or `$.maxLength`. */ public array = (key: string): boolean => { const value = this.obj[key]; if (key && !this.exists(key)) { return false; } if (Array.isArray(value)) { return true; } this.catch( typeMismatchError({ typeName: 'Array', property: key, path: this.path.of(key), value, }), ); return false; }; /** * Asserts the value of the key is one of the expected values. */ public enum = (key: string, expected: string[]): boolean => { const value = this.obj[key]; if (typeof value === 'string' && expected.includes(value)) { return true; } this.catch( enumError({ expected, value, path: this.path.of(key), }), ); return false; }; /** * Asserts the array value of the object key is empty. If the value isn't an * array, the function captures a type error and returns false. */ public empty = (key: string): boolean => { if (!this.array(key)) { return false; } const value = this.obj[key] as Array; if (value.length === 0) { return true; } this.catch( maxSizeError({ max: 0, value, path: this.path.of(key), }), ); return false; }; /** * Asserts the length of the value of the object key is at least `min`. If the * value isn't an array, the function captures a type error and returns false. */ public minLength = (key: string, min: number): boolean => { if (!this.array(key)) { return false; } const value = this.obj[key] as Array; if (value.length >= min) { return true; } this.catch( minSizeError({ min, value, path: this.path.of(key), }), ); return false; }; /** * Asserts the object has no additional properties other than the ones * specified */ public noAdditionalProperties = (properties: string[]): boolean => { const unknowns = Object.keys(this.obj) .sort() .filter((key) => !properties.includes(key)); unknowns.forEach((property) => this.catch( unknownPropertyError({ property, path: this.path.of(property), }), ), ); return unknowns.length === 0; }; /** * Iterates over the value of the key and assert each item. If the value isn't * an array, the function captures a type error and safely exits. * * To maintain compatibility with previous implementation, we stop early if we * find any errors. */ public each = (key: string, assert: (item: any, path: Path) => ValidationError[]): void => { if (!this.array(key)) { return; } const value = this.obj[key] as Array; let foundErrors = false; value.forEach((item, index) => { if (foundErrors) { return; } const errors = assert(item, this.path.of(key).of(index)); if (errors.length > 0) { foundErrors = true; } this.catch(...errors); }); }; } ================================================ FILE: packages/rich-text-types/src/validator/errors.ts ================================================ import type { Path } from './path.js'; import { ValidationError } from './types.js'; export const typeMismatchError = ({ path, property, typeName, value, }: { path: Path; property: string | number; typeName: string; value: any; }): ValidationError => { return { details: `The type of "${property}" is incorrect, expected type: ${typeName}`, name: 'type', path: path.toArray(), type: typeName, value, }; }; export const minSizeError = ({ min, value, path, }: { min: number; value: any; path: Path; }): ValidationError => { return { name: 'size', min, path: path.toArray(), details: `Size must be at least ${min}`, value, }; }; export const maxSizeError = ({ max, value, path, }: { max: number; value: any; path: Path; }): ValidationError => { return { name: 'size', max, path: path.toArray(), details: `Size must be at most ${max}`, value, }; }; export const enumError = ({ expected, value, path, }: { expected: string[]; value: any; path: Path; }): ValidationError => { return { details: `Value must be one of expected values`, name: 'in', expected: [...expected].sort(), path: path.toArray(), value, }; }; export const unknownPropertyError = ({ property, path, }: { property: string; path: Path; }): ValidationError => { return { details: `The property "${property}" is not expected`, name: 'unexpected', path: path.toArray(), }; }; export const requiredPropertyError = ({ property, path, }: { property: string; path: Path; }): ValidationError => { return { details: `The property "${property}" is required here`, name: 'required', path: path.toArray(), }; }; ================================================ FILE: packages/rich-text-types/src/validator/index.ts ================================================ import { BLOCKS } from '../blocks.js'; import { INLINES } from '../inlines.js'; import { CONTAINERS, LIST_ITEM_BLOCKS, TOP_LEVEL_BLOCKS } from '../schemaConstraints.js'; import { Document, Text } from '../types.js'; import { ObjectAssertion } from './assert.js'; import { NodeAssertion, Node, HyperLinkAssertion, assert, assertLink, VOID_CONTENT, } from './node.js'; import { Path } from './path.js'; import { assertText } from './text.js'; import type { ValidationError } from './types.js'; export type { ValidationError }; const assertInlineOrText = assert([...Object.values(INLINES), 'text'].sort()); const assertList = assert([BLOCKS.LIST_ITEM]); const assertVoidEntryLink = assertLink('Entry', VOID_CONTENT); const assertTableCell = assert( () => ({ nodeTypes: [BLOCKS.PARAGRAPH], min: 1, }), (data, path) => { const $ = new ObjectAssertion(data, path); $.noAdditionalProperties(['colspan', 'rowspan']); $.number('colspan', true); $.number('rowspan', true); return $.errors; }, ); const nodeValidator: Record> = { [BLOCKS.DOCUMENT]: assert(TOP_LEVEL_BLOCKS), [BLOCKS.PARAGRAPH]: assertInlineOrText, [BLOCKS.HEADING_1]: assertInlineOrText, [BLOCKS.HEADING_2]: assertInlineOrText, [BLOCKS.HEADING_3]: assertInlineOrText, [BLOCKS.HEADING_4]: assertInlineOrText, [BLOCKS.HEADING_5]: assertInlineOrText, [BLOCKS.HEADING_6]: assertInlineOrText, [BLOCKS.QUOTE]: assert(CONTAINERS[BLOCKS.QUOTE]), [BLOCKS.EMBEDDED_ENTRY]: assertVoidEntryLink, [BLOCKS.EMBEDDED_ASSET]: assertLink('Asset', VOID_CONTENT), [BLOCKS.EMBEDDED_RESOURCE]: assertLink('Contentful:Entry', VOID_CONTENT), [BLOCKS.HR]: assert(VOID_CONTENT), [BLOCKS.OL_LIST]: assertList, [BLOCKS.UL_LIST]: assertList, [BLOCKS.LIST_ITEM]: assert([...LIST_ITEM_BLOCKS].sort()), [BLOCKS.TABLE]: assert(() => ({ nodeTypes: [BLOCKS.TABLE_ROW], min: 1, })), [BLOCKS.TABLE_ROW]: assert(() => ({ nodeTypes: [BLOCKS.TABLE_CELL, BLOCKS.TABLE_HEADER_CELL], min: 1, })), [BLOCKS.TABLE_CELL]: assertTableCell, [BLOCKS.TABLE_HEADER_CELL]: assertTableCell, [INLINES.HYPERLINK]: new HyperLinkAssertion(), [INLINES.EMBEDDED_ENTRY]: assertVoidEntryLink, [INLINES.EMBEDDED_RESOURCE]: assertLink('Contentful:Entry', VOID_CONTENT), [INLINES.ENTRY_HYPERLINK]: assertLink('Entry', ['text']), [INLINES.ASSET_HYPERLINK]: assertLink('Asset', ['text']), [INLINES.RESOURCE_HYPERLINK]: assertLink('Contentful:Entry', ['text']), }; function validateNode(node: Node | Text, path: Path): ValidationError[] { if (node.nodeType === 'text') { return assertText(node, path); } const errors = nodeValidator[node.nodeType].assert(node, path); if (errors.length > 0) { return errors; } const $ = new ObjectAssertion(node, path); $.each('content', (item, path) => { // We already know those are valid nodes thanks to the assertion done in // the NodeAssertion class return validateNode(item, path); }); return $.errors; } export const validateRichTextDocument = (document: Document): ValidationError[] => { const path = new Path(); const $ = new ObjectAssertion(document, path); if ($.object()) { $.enum('nodeType', [BLOCKS.DOCUMENT]); } if ($.errors.length > 0) { return $.errors; } return validateNode(document, path); }; ================================================ FILE: packages/rich-text-types/src/validator/node.ts ================================================ import { AssetHyperlink, AssetLinkBlock, EntryHyperlink, EntryLinkBlock, Hyperlink, ResourceLinkBlock, ResourceLinkInline, } from '../nodeTypes.js'; import { Block, Document, Inline } from '../types.js'; import { ObjectAssertion } from './assert.js'; import type { Path } from './path.js'; import { ValidationError } from './types.js'; export type Node = Document | Block | Inline; export type GetContentRule = | string[] | (( node: T, path: Path, ) => { nodeTypes: string[]; min?: number; }); export type ValidateData = (data: T['data'], path: Path) => ValidationError[]; export const VOID_CONTENT: GetContentRule = []; export class NodeAssertion { constructor( private contentRule: GetContentRule, private validateData?: ValidateData, ) {} assert(node: T, path: Path): ValidationError[] { const $ = new ObjectAssertion(node, path); if (!$.object()) { return $.errors; } $.noAdditionalProperties(['nodeType', 'data', 'content']); const { nodeTypes, min = 0 } = Array.isArray(this.contentRule) ? { nodeTypes: this.contentRule, } : this.contentRule(node, path); if (nodeTypes.length === 0 && min > 0) { throw new Error( `Invalid content rule. Cannot have enforce a 'min' of ${min} with no nodeTypes`, ); } $.minLength('content', min); // Is void if (nodeTypes.length === 0) { $.empty('content'); } // Ensure content nodes have valid nodeTypes without validating the full // shape which is something that's only done later if the current node is // valid. else { $.each('content', (item, path) => { const item$ = new ObjectAssertion(item, path); if (!item$.object()) { return item$.errors; } item$.enum('nodeType', nodeTypes); return item$.errors; }); } if ($.object('data')) { const dataErrors = this.validateData?.(node.data, path.of('data')) ?? []; $.catch(...dataErrors); } return $.errors; } } export class EntityLinkAssertion< T extends | EntryLinkBlock | EntryHyperlink | AssetLinkBlock | AssetHyperlink | ResourceLinkBlock | ResourceLinkInline, > extends NodeAssertion { private type: 'ResourceLink' | 'Link'; constructor( private linkType: 'Entry' | 'Asset' | 'Contentful:Entry', contentNodeTypes: GetContentRule, ) { super(contentNodeTypes, (data, path) => this.assertLink(data, path)); this.type = this.linkType.startsWith('Contentful:') ? 'ResourceLink' : 'Link'; } private assertLink = (data: T['data'], path: Path): ValidationError[] => { const $ = new ObjectAssertion(data, path); if ($.object('target')) { const sys$ = new ObjectAssertion(data.target.sys, path.of('target').of('sys')); if (sys$.object()) { sys$.enum('type', [this.type]); sys$.enum('linkType', [this.linkType]); if (this.type === 'Link') { sys$.string('id'); sys$.noAdditionalProperties(['type', 'linkType', 'id']); } else if (this.type === 'ResourceLink') { sys$.string('urn'); sys$.noAdditionalProperties(['type', 'linkType', 'urn']); } } $.catch(...sys$.errors); } $.noAdditionalProperties(['target']); return $.errors; }; } export class HyperLinkAssertion extends NodeAssertion { constructor() { super(['text'], (data, path) => this.assertLink(data, path)); } private assertLink = (data: T['data'], path: Path): ValidationError[] => { const $ = new ObjectAssertion(data, path); $.string('uri'); $.noAdditionalProperties(['uri']); return $.errors; }; } export const assert = ( contentRule: GetContentRule, validateData?: ValidateData, ): NodeAssertion => { return new NodeAssertion(contentRule, validateData); }; export const assertLink = < T extends | EntryLinkBlock | EntryHyperlink | AssetLinkBlock | AssetHyperlink | ResourceLinkBlock | ResourceLinkInline, >( linkType: 'Entry' | 'Asset' | 'Contentful:Entry', contentRule: GetContentRule, ): EntityLinkAssertion => { return new EntityLinkAssertion(linkType, contentRule); }; ================================================ FILE: packages/rich-text-types/src/validator/path.ts ================================================ export class Path { constructor(private readonly path: (string | number)[] = []) {} of = (element: string | number): Path => { return new Path([...this.path, element]); }; isRoot = (): boolean => { return this.path.length === 0; }; last = (): string | number | undefined => { return this.path[this.path.length - 1]; }; toArray = (): (string | number)[] => { return this.path; }; } ================================================ FILE: packages/rich-text-types/src/validator/text.ts ================================================ import { Text } from '../types.js'; import { ObjectAssertion } from './assert.js'; import type { Path } from './path.js'; import { ValidationError } from './types.js'; export function assertText(text: Text, path: Path): ValidationError[] { const $ = new ObjectAssertion(text, path); if (!$.object()) { return $.errors; } $.noAdditionalProperties(['nodeType', 'data', 'value', 'marks']); $.object('data'); $.each('marks', (mark, path) => { const mark$ = new ObjectAssertion(mark, path); if (!mark$.object()) { return mark$.errors; } // For historical reasons, we don't explicitly check for supported marks // e.g. bold, italic ..etc. This makes it possible for a customer to add // custom marks mark$.string('type'); return mark$.errors; }); $.string('value'); return $.errors; } ================================================ FILE: packages/rich-text-types/src/validator/types.ts ================================================ export type ValidationError = { name: string; type?: string; value?: Record | string | number | boolean | null; min?: number | string; max?: number | string; details?: string | null; path?: (string | number)[]; contentTypeId?: string | string[]; nodeType?: string; customMessage?: string; expected?: string[]; }; ================================================ FILE: packages/rich-text-types/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "target": "ES2020", "module": "esnext", "moduleResolution": "node", "declarationDir": "dist/types", "outDir": "dist", "typeRoots": ["../../node_modules/@types", "node_modules/@types", "src/typings"], "types": ["jest", "node"], "inlineSources": true, "declaration": true, "isolatedModules": true, "skipLibCheck": true }, "include": ["src"] } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'packages/*' linkWorkspacePackages: true preferWorkspacePackages: true ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "local>contentful/renovate-config" ] } ================================================ FILE: rollup.config.js ================================================ import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import swc from '@rollup/plugin-swc'; import json from 'rollup-plugin-json'; export default (outputFile, overrides = {}) => ({ input: 'src/index.ts', output: [ { file: outputFile, format: 'cjs', sourcemap: true, }, { file: outputFile.replace(/\.es5\.js$/, '.esm.js'), format: 'es', sourcemap: true, }, ], watch: { include: 'src/**', }, plugins: [ json(), swc({ swc: { jsc: { parser: { syntax: 'typescript', tsx: true, }, target: 'es2019', loose: false, minify: { compress: false, mangle: false, }, }, minify: false, }, }), commonjs(), // Allow node_modules resolution, so you can use 'external' to control // which external modules to include in the bundle // https://github.com/rollup/rollup-plugin-node-resolve#usage resolve({ extensions: ['.mjs', '.js', '.json', '.node', '.ts', '.tsx'], }), ], ...overrides, }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "es2015", "lib": [ "es2015", "es2016", "es2017", // otherwise schema generation fails with: // Cannot find name 'Window' "dom" ], "esModuleInterop": true, "strict": true, "strictNullChecks": false, "sourceMap": true, "declaration": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "jsx": "react", "types": ["jest", "node"] } }