├── .husky ├── .gitignore ├── pre-commit └── commit-msg ├── types └── global.d.ts ├── examples ├── react │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ └── App.tsx │ ├── tsconfig.node.json │ ├── index.html │ ├── .gitignore │ ├── vite.config.ts │ ├── package.json │ └── tsconfig.json ├── svelte │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.ts │ │ └── App.svelte │ ├── README.md │ ├── tsconfig.node.json │ ├── svelte.config.js │ ├── index.html │ ├── .gitignore │ ├── vite.config.ts │ ├── package.json │ └── tsconfig.json ├── vue │ ├── src │ │ ├── main.js │ │ └── App.vue │ ├── README.md │ ├── index.html │ ├── .gitignore │ ├── package.json │ └── vite.config.js └── content-example.ts ├── commitlint.config.js ├── packages ├── react-renderer │ ├── src │ │ ├── index.ts │ │ ├── elements │ │ │ ├── index.ts │ │ │ ├── Class.tsx │ │ │ ├── Audio.tsx │ │ │ ├── Video.tsx │ │ │ ├── Link.tsx │ │ │ ├── Image.tsx │ │ │ └── IFrame.tsx │ │ ├── RenderText.tsx │ │ ├── defaultElements.tsx │ │ ├── types.ts │ │ └── RichText.tsx │ ├── tsconfig.build.json │ ├── LICENSE.md │ ├── package.json │ ├── test │ │ ├── __snapshots__ │ │ │ └── RichText.test.tsx.snap │ │ └── content.ts │ ├── CHANGELOG.md │ └── README.md ├── types │ ├── tsconfig.build.json │ ├── src │ │ ├── util │ │ │ ├── isText.ts │ │ │ ├── isElement.ts │ │ │ └── isEmpty.ts │ │ └── index.ts │ ├── README.md │ ├── LICENSE.md │ ├── package.json │ └── CHANGELOG.md ├── html-renderer │ ├── src │ │ ├── elements │ │ │ ├── index.ts │ │ │ ├── Class.tsx │ │ │ ├── Audio.tsx │ │ │ ├── Video.tsx │ │ │ ├── Link.tsx │ │ │ ├── Image.tsx │ │ │ └── IFrame.tsx │ │ ├── defaultElements.tsx │ │ ├── types.ts │ │ └── index.ts │ ├── tsconfig.build.json │ ├── test │ │ ├── __snapshots__ │ │ │ └── index.test.ts.snap │ │ └── content.ts │ ├── LICENSE.md │ ├── package.json │ ├── CHANGELOG.md │ └── README.md └── html-to-slate-ast │ ├── tsconfig.build.json │ ├── test │ ├── pre.html │ ├── html_input_table.html │ ├── image.html │ ├── html_input.html │ ├── html_input_iframe.html │ └── google-docs_input.html │ ├── tsup.config.ts │ ├── examples │ ├── node-script.js │ └── graphql-request-script.js │ ├── LICENSE.md │ ├── package.json │ ├── README.md │ ├── CHANGELOG.md │ └── src │ └── index.ts ├── .lintstagedrc ├── .prettierrc ├── .gitignore ├── lerna.json ├── .eslintrc.js ├── .editorconfig ├── .github ├── label.yml ├── workflows │ ├── lint.yml │ ├── release.yml │ └── main.yml └── CONTRIBUTING.md ├── .changeset ├── config.json └── README.md ├── tsconfig.build.json ├── tsconfig.json ├── LICENSE.md ├── README.md └── package.json /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare var __DEV__: boolean; 2 | -------------------------------------------------------------------------------- /examples/react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /packages/react-renderer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RichText'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{ts,tsx}": [ 3 | "yarn lint --fix", 4 | "yarn format" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/svelte/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /examples/vue/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /packages/types/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src", "types", "../../types"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | ## Rich Text Renderer with Vue 2 | 3 | This example shows how to use the Hygraph Rich Text Renderer package with Vue. 4 | -------------------------------------------------------------------------------- /examples/svelte/README.md: -------------------------------------------------------------------------------- 1 | ## Rich Text Renderer with Svelte 2 | 3 | This example shows how to use the Hygraph Rich Text Renderer package with Svelte. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | 6 | node_modules 7 | package-lock.json 8 | yarn.lock 9 | !/yarn.lock 10 | coverage/ 11 | 12 | .idea 13 | -------------------------------------------------------------------------------- /examples/svelte/src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') as Element, 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /packages/types/src/util/isText.ts: -------------------------------------------------------------------------------- 1 | import { Node, Text } from '../'; 2 | 3 | export function isText(node: Node): node is Text { 4 | return (node as Text).text !== undefined; 5 | } 6 | -------------------------------------------------------------------------------- /examples/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/svelte/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Audio'; 2 | export * from './IFrame'; 3 | export * from './Image'; 4 | export * from './Video'; 5 | export * from './Class'; 6 | export * from './Link'; 7 | -------------------------------------------------------------------------------- /packages/types/src/util/isElement.ts: -------------------------------------------------------------------------------- 1 | import { ElementNode, Node } from '../'; 2 | 3 | export function isElement(node: Node): node is ElementNode { 4 | return (node as ElementNode).children !== undefined; 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Audio'; 2 | export * from './IFrame'; 3 | export * from './Image'; 4 | export * from './Video'; 5 | export * from './Class'; 6 | export * from './Link'; 7 | -------------------------------------------------------------------------------- /packages/types/README.md: -------------------------------------------------------------------------------- 1 | # @graphcms/rich-text-types 2 | 3 | TypeScript definitions for the Hygraph Rich Text field type. 4 | 5 | --- 6 | 7 | Made with 💜 by Hygraph 👋 [join our community](https://slack.hygraph.com)! 8 | -------------------------------------------------------------------------------- /packages/html-renderer/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src", "types", "../../types"], 4 | "compilerOptions": { 5 | "typeRoots": ["./node_modules/@types"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/react-renderer/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src", "types", "../../types"], 4 | "compilerOptions": { 5 | "typeRoots": ["./node_modules/@types"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src", "types", "../../types"], 4 | "compilerOptions": { 5 | "typeRoots": ["./node_modules/@types"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/Class.tsx: -------------------------------------------------------------------------------- 1 | import { ClassRendererProps } from '../types'; 2 | 3 | export function Class({ className, children }: ClassRendererProps) { 4 | return `
${children}
`; 5 | } 6 | -------------------------------------------------------------------------------- /examples/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import sveltePreprocess from 'svelte-preprocess' 2 | 3 | export default { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: sveltePreprocess() 7 | } 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independant", 3 | "registry": "https://registry.npmjs.org/", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "npmClient": "yarn", 8 | "useWorkspaces": true, 9 | "packages": ["packages/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/Class.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ClassRendererProps } from '../types'; 3 | 4 | export function Class({ className, children }: ClassRendererProps) { 5 | return
{children}
; 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['react-app', 'prettier/@typescript-eslint', 'prettier'], 3 | plugins: ['testing-library', 'jest-dom', 'prettier'], 4 | settings: { 5 | react: { 6 | version: '999.999.999', 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /examples/react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true -------------------------------------------------------------------------------- /packages/html-to-slate-ast/test/pre.html: -------------------------------------------------------------------------------- 1 |
 2 |   L          TE
 3 |     A       A
 4 |       C    V
 5 |        R A
 6 |        DOU
 7 |        LOU
 8 |       REUSE
 9 |       QUE TU
10 |       PORTES
11 |     ET QUI T'
12 |     ORNE O CI
13 |      VILISÉ
14 |     OTE-  TU VEUX
15 |      LA    BIEN
16 |     SI      RESPI
17 |             RER       - Apollinaire
-------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/label.yml: -------------------------------------------------------------------------------- 1 | examples: 2 | - examples/* 3 | - examples/**/* 4 | 5 | html-to-slate-ast: 6 | - packages/html-to-slate-ast/* 7 | - packages/html-to-slate-ast/**/* 8 | 9 | react-renderer: 10 | - packages/react-renderer/* 11 | - packages/react-renderer/**/* 12 | 13 | types: 14 | - packages/types/* 15 | - packages/types/**/* 16 | 17 | repo: 18 | - ./* 19 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Svelte Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "hygraph/rich-text" } 6 | ], 7 | "commit": false, 8 | "linked": [], 9 | "access": "public", 10 | "baseBranch": "main", 11 | "updateInternalDependencies": "patch", 12 | "ignore": [] 13 | } 14 | -------------------------------------------------------------------------------- /examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "vue": "^3.2.25" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^2.2.0", 15 | "vite": "^2.8.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig(options => ({ 4 | entry: ['src/index.ts'], 5 | tsconfig: 'tsconfig.build.json', 6 | minify: !options.watch, 7 | splitting: true, 8 | sourcemap: true, 9 | dts: true, 10 | treeshake: true, 11 | clean: true, 12 | format: ['esm', 'cjs'], 13 | skipNodeModulesBundle: true, 14 | })); 15 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/Audio.tsx: -------------------------------------------------------------------------------- 1 | export type AudioProps = { 2 | url: string; 3 | }; 4 | 5 | export function Audio({ url }: AudioProps) { 6 | return ` 7 | 18 | `; 19 | } 20 | -------------------------------------------------------------------------------- /examples/vue/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import path from 'path'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | resolve: { 8 | alias: { 9 | '@graphcms/rich-text-types': path.join(__dirname, '../../packages/types'), 10 | '@graphcms/rich-text-html-renderer': path.join( 11 | __dirname, 12 | '../../packages/html-renderer' 13 | ), 14 | }, 15 | }, 16 | plugins: [vue()], 17 | }); 18 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | 14 | - name: Set up Node 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: lts/* 18 | 19 | - name: Install deps and build (with cache) 20 | uses: bahmutov/npm-install@v1 21 | 22 | - name: Lint 23 | run: yarn lint 24 | -------------------------------------------------------------------------------- /examples/react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import { join } from 'path'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | resolve: { 8 | alias: { 9 | '@graphcms/rich-text-types': join(__dirname, '../../packages/types'), 10 | '@graphcms/rich-text-react-renderer': join( 11 | __dirname, 12 | '../../packages/react-renderer' 13 | ), 14 | }, 15 | }, 16 | plugins: [react()], 17 | }); 18 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/Audio.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export type AudioProps = { 4 | url: string; 5 | }; 6 | 7 | export function Audio({ url }: AudioProps) { 8 | return ( 9 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | import { join } from 'path'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | resolve: { 8 | alias: { 9 | '@graphcms/rich-text-types': join(__dirname, '../../packages/types'), 10 | '@graphcms/rich-text-html-renderer': join( 11 | __dirname, 12 | '../../packages/html-renderer' 13 | ), 14 | }, 15 | }, 16 | plugins: [svelte()], 17 | }); 18 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets). 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md). 9 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/examples/node-script.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simple script that makes sure the library works on a barebones node environment (non-browser) 3 | */ 4 | const { htmlToSlateAST } = require('../dist'); 5 | 6 | async function main() { 7 | const htmlString = ''; 8 | const ast = await htmlToSlateAST(htmlString); 9 | console.log(JSON.stringify(ast, null, 2)); 10 | } 11 | 12 | main() 13 | .then(() => process.exit(0)) 14 | .catch(e => { 15 | console.error(e); 16 | process.exit(1); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/Video.tsx: -------------------------------------------------------------------------------- 1 | import escapeHtml from 'escape-html'; 2 | import { VideoProps } from '@graphcms/rich-text-types'; 3 | 4 | export function Video({ src, width, height, title }: Partial) { 5 | return ` 6 | 13 | `; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "lib": ["dom", "esnext"], 5 | "importHelpers": true, 6 | "declaration": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "moduleResolution": "node", 14 | "jsx": "react", 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "prismjs": "^1.27.0", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2" 14 | }, 15 | "devDependencies": { 16 | "@types/prismjs": "^1.26.0", 17 | "@types/react": "^17.0.33", 18 | "@types/react-dom": "^17.0.10", 19 | "@vitejs/plugin-react": "^1.0.7", 20 | "typescript": "^4.5.4", 21 | "vite": "^2.8.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "include": ["packages", "types", "examples"], 4 | "compilerOptions": { 5 | "allowJs": false, 6 | "baseUrl": ".", 7 | "typeRoots": ["./node_modules/@types", "./types"], 8 | "paths": { 9 | "@graphcms/rich-text-types": ["packages/types/src"], 10 | "@graphcms/rich-text-react-renderer": ["packages/react-renderer/src"], 11 | "@graphcms/rich-text-html-renderer": ["packages/html-renderer/src"], 12 | "@graphcms/html-to-slate-ast": ["packages/html-to-slate-ast/src"], 13 | "$test/*": ["test/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/Link.tsx: -------------------------------------------------------------------------------- 1 | import escapeHtml from 'escape-html'; 2 | import { LinkRendererProps } from '../types'; 3 | 4 | export function Link({ children, ...rest }: LinkRendererProps) { 5 | const { href, rel, id, title, openInNewTab, className } = rest; 6 | 7 | return ` 8 | 13 | ${children} 14 | 15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/Video.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import escapeHtml from 'escape-html'; 3 | import { VideoProps } from '@graphcms/rich-text-types'; 4 | 5 | export function Video({ src, width, height, title }: Partial) { 6 | return ( 7 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json" 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", 14 | "@tsconfig/svelte": "^2.0.1", 15 | "svelte": "^3.44.0", 16 | "svelte-check": "^2.2.7", 17 | "svelte-preprocess": "^4.9.8", 18 | "tslib": "^2.3.1", 19 | "typescript": "^4.5.4", 20 | "vite": "^2.8.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/svelte/src/App.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

Svelte Example

21 | 22 |
{@html html}
23 |
24 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/test/html_input_table.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 8 | 11 | 12 | 13 | 14 | 17 | 20 | 25 | 26 | 27 |
6 |

R1C1 - BOLD TEXT

7 |
9 |
R1C2
10 |

R1C3

15 |

R2C1

16 |
18 | R2C2 - ITALIC TEXT 19 | 21 |

22 | R2C3 - BOLD TEXT 23 |

24 |
28 |
29 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/Image.tsx: -------------------------------------------------------------------------------- 1 | import escapeHtml from 'escape-html'; 2 | import { ImageProps } from '@graphcms/rich-text-types'; 3 | 4 | export function Image({ 5 | src, 6 | width, 7 | height, 8 | altText, 9 | title, 10 | }: Partial) { 11 | if (__DEV__ && !src) { 12 | console.warn( 13 | `[@graphcms/rich-text-html-renderer]: src is required. You need to include a \`url\` in your query` 14 | ); 15 | } 16 | 17 | return ` 18 | 23 | `; 24 | } 25 | -------------------------------------------------------------------------------- /examples/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "resolveJsonModule": true, 8 | "baseUrl": ".", 9 | /** 10 | * Typecheck JS in `.svelte` and `.js` files by default. 11 | * Disable checkJs if you'd like to use dynamic types in JS. 12 | * Note that setting allowJs false does not prevent the use 13 | * of JS in `.svelte` files. 14 | */ 15 | "allowJs": true, 16 | "checkJs": true 17 | }, 18 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 19 | "references": [{ "path": "./tsconfig.node.json" }] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/Link.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import escapeHtml from 'escape-html'; 3 | import { LinkElement } from '@graphcms/rich-text-types'; 4 | import { LinkRendererProps } from '../types'; 5 | 6 | export function Link({ children, ...rest }: LinkRendererProps) { 7 | const { href, rel, id, title, openInNewTab, className } = rest; 8 | 9 | const props: Pick & { 10 | target?: string; 11 | } = {}; 12 | 13 | if (rel) props.rel = rel; 14 | if (id) props.id = id; 15 | if (title) props.title = title; 16 | if (className) props.className = className; 17 | if (openInNewTab) props.target = '_blank'; 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/test/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Set up Node 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Create Release Pull Request or Publish to npm 26 | uses: changesets/action@master 27 | with: 28 | commit: 'chore(release): publish' 29 | publish: yarn release 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /packages/html-renderer/src/elements/IFrame.tsx: -------------------------------------------------------------------------------- 1 | import escapeHtml from 'escape-html'; 2 | import { IFrameProps } from '@graphcms/rich-text-types'; 3 | 4 | export function IFrame({ url }: Partial) { 5 | return ` 6 |
14 | 30 |
31 | `; 32 | } 33 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/Image.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import escapeHtml from 'escape-html'; 3 | import { ImageProps } from '@graphcms/rich-text-types'; 4 | 5 | export function Image({ 6 | src, 7 | width, 8 | height, 9 | altText, 10 | title, 11 | }: Partial) { 12 | if (__DEV__ && !src) { 13 | console.warn( 14 | `[@graphcms/rich-text-react-renderer]: src is required. You need to include a \`url\` in your query` 15 | ); 16 | } 17 | 18 | const shouldIncludeWidth = width && width > 0; 19 | const shouldIncludeHeight = height && height > 0; 20 | 21 | return ( 22 | {altText} 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Unit Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | name: Build and test on Node ${{ matrix.node }} and ${{ matrix.os }} 8 | 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | node: ['18.x', '20.x'] 13 | os: [ubuntu-latest] 14 | 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node ${{ matrix.node }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node }} 23 | 24 | - name: Install deps and build (with cache) 25 | uses: bahmutov/npm-install@v1 26 | 27 | - name: Test 28 | run: yarn test --ci --coverage --maxWorkers=2 29 | 30 | - name: Run Node.js Environment Test for @graphcms/html-to-slate-ast 31 | run: yarn test:node 32 | working-directory: ./packages/html-to-slate-ast 33 | -------------------------------------------------------------------------------- /packages/types/src/util/isEmpty.ts: -------------------------------------------------------------------------------- 1 | import { ElementNode, Text } from '../'; 2 | import { isElement } from './isElement'; 3 | import { isText } from './isText'; 4 | 5 | export function isEmpty({ 6 | children, 7 | }: { 8 | children: (ElementNode | Text)[]; 9 | }): boolean { 10 | // Checks if the children array has more than one element. 11 | // It may have a link inside, that's why we need to check this condition. 12 | if (children.length > 1) { 13 | const hasText = children.filter(function f(child): boolean | number { 14 | if (isText(child) && child.text !== '') { 15 | return true; 16 | } 17 | 18 | if (isElement(child)) { 19 | return (child.children = child.children.filter(f)).length; 20 | } 21 | 22 | return false; 23 | }); 24 | 25 | return hasText.length > 0 ? false : true; 26 | } else if (children[0].text === '') return true; 27 | 28 | return false; 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-renderer/src/elements/IFrame.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/iframe-has-title */ 2 | import React from 'react'; 3 | import escapeHtml from 'escape-html'; 4 | import { IFrameProps } from '@graphcms/rich-text-types'; 5 | 6 | export function IFrame({ url }: Partial) { 7 | return ( 8 |
16 | 22 |
23 |

 

24 |

25 | .hack is a multimedia franchise created and developed by famed 26 | Japanese developer CyberConnect2. Comprising of video games, anime, novels, 27 | and manga, the world of .hack focuses on the mysterious events 28 | surrounding a wildly popular in-universe massively multiplayer role-playing 29 | game called The World. .hack//G.U. begins after the events of the 30 | original .hack series with players assuming the role of Haseo as he 31 | tracks down a powerful Player Killer named Tri-Edge who killed his friend’s 32 | in-game avatar Shino, and put her into a coma in real life. 33 |

34 | 35 | -------------------------------------------------------------------------------- /packages/html-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphcms/rich-text-html-renderer", 3 | "description": "Hygraph Rich Text HTML renderer", 4 | "version": "0.3.1", 5 | "author": "João Pedro Schmitz (https://joaopedro.dev)", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "tsdx watch --tsconfig tsconfig.build.json --verbose --noClean", 9 | "build": "tsdx build --tsconfig tsconfig.build.json", 10 | "test": "tsdx test --passWithNoTests --silent", 11 | "lint": "tsdx lint", 12 | "prepublish": "npm run build" 13 | }, 14 | "dependencies": { 15 | "@graphcms/rich-text-types": "^0.5.0", 16 | "escape-html": "^1.0.3" 17 | }, 18 | "devDependencies": { 19 | "@types/escape-html": "^1.0.2" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "keywords": [ 25 | "html", 26 | "rich-text", 27 | "renderer", 28 | "hygraph" 29 | ], 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/hygraph/rich-text.git", 33 | "directory": "packages/html-renderer" 34 | }, 35 | "main": "dist/index.js", 36 | "module": "dist/rich-text-html-renderer.esm.js", 37 | "types": "dist/index.d.ts", 38 | "files": [ 39 | "README.md", 40 | "LICENSE.md", 41 | "dist" 42 | ], 43 | "jest": { 44 | "setupFilesAfterEnv": [ 45 | "@testing-library/jest-dom/extend-expect" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphcms/rich-text-react-renderer", 3 | "description": "Hygraph Rich Text React renderer", 4 | "version": "0.6.2", 5 | "author": "João Pedro Schmitz (https://joaopedro.dev)", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "tsdx watch --tsconfig tsconfig.build.json --verbose --noClean", 9 | "build": "tsdx build --tsconfig tsconfig.build.json", 10 | "test": "tsdx test --passWithNoTests --silent", 11 | "lint": "tsdx lint", 12 | "prepublish": "npm run build" 13 | }, 14 | "dependencies": { 15 | "@graphcms/rich-text-types": "^0.5.0", 16 | "escape-html": "^1.0.3" 17 | }, 18 | "devDependencies": { 19 | "@types/escape-html": "^1.0.2" 20 | }, 21 | "peerDependencies": { 22 | "react": ">=16", 23 | "react-dom": ">=16" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "keywords": [ 29 | "react", 30 | "rich-text", 31 | "renderer", 32 | "hygraph" 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/hygraph/rich-text.git", 37 | "directory": "packages/react-renderer" 38 | }, 39 | "main": "dist/index.js", 40 | "module": "dist/rich-text-react-renderer.esm.js", 41 | "types": "dist/index.d.ts", 42 | "files": [ 43 | "README.md", 44 | "LICENSE.md", 45 | "dist" 46 | ], 47 | "jest": { 48 | "setupFilesAfterEnv": [ 49 | "@testing-library/jest-dom/extend-expect" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/html-to-slate-ast/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@graphcms/html-to-slate-ast", 3 | "version": "0.14.2", 4 | "description": "Convert HTML to Hygraph's RichTextAST (slate)", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "tsup --watch", 8 | "build": "tsup", 9 | "test": "tsdx test --passWithNoTests", 10 | "test:watch": "tsdx test --watch --passWithNoTests", 11 | "lint": "tsdx lint", 12 | "prepublish": "npm run build", 13 | "test:node": "node examples/node-script.js" 14 | }, 15 | "peerDependencies": { 16 | "slate": "^0.66.1", 17 | "slate-hyperscript": "^0.67.0", 18 | "jsdom": "^24.0.0" 19 | }, 20 | "peerDependenciesMeta": { 21 | "jsdom": { 22 | "optional": true 23 | } 24 | }, 25 | "devDependencies": { 26 | "@types/jsdom": "^21.1.6", 27 | "jsdom": "^24.0.0", 28 | "slate": "^0.66.1", 29 | "slate-hyperscript": "^0.67.0", 30 | "tsup": "^8.0.1" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "keywords": [ 36 | "slate", 37 | "rich-text", 38 | "hygraph" 39 | ], 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/hygraph/rich-text.git", 43 | "directory": "packages/html-to-slate-ast" 44 | }, 45 | "main": "dist/index.js", 46 | "module": "dist/index.mjs", 47 | "types": "dist/index.d.ts", 48 | "files": [ 49 | "README.md", 50 | "LICENSE.md", 51 | "dist" 52 | ], 53 | "jest": {}, 54 | "dependencies": { 55 | "@braintree/sanitize-url": "^7.0.0", 56 | "@graphcms/rich-text-types": "^0.5.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/react-renderer/src/RenderText.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { Text } from '@graphcms/rich-text-types'; 3 | 4 | import { RichTextProps, NodeRendererType } from './types'; 5 | 6 | function serialize(text: string) { 7 | if (text.includes('\n')) { 8 | const splitText = text.split('\n'); 9 | 10 | return splitText.map((line, index) => ( 11 | 12 | {line} 13 | {index === splitText.length - 1 ? null :
} 14 |
15 | )); 16 | } 17 | 18 | return text; 19 | } 20 | 21 | export function RenderText({ 22 | textNode, 23 | renderers, 24 | shouldSerialize, 25 | }: { 26 | textNode: Text; 27 | renderers?: RichTextProps['renderers']; 28 | shouldSerialize: boolean; 29 | }) { 30 | const { text, bold, italic, underline, code } = textNode; 31 | 32 | let parsedText: ReactNode = shouldSerialize ? serialize(text) : text; 33 | 34 | const Bold: NodeRendererType['bold'] = renderers?.['bold']; 35 | const Italic: NodeRendererType['italic'] = renderers?.['italic']; 36 | const Underline: NodeRendererType['underline'] = renderers?.['underline']; 37 | const Code: NodeRendererType['code'] = renderers?.['code']; 38 | 39 | if (bold && Bold) { 40 | parsedText = {parsedText}; 41 | } 42 | 43 | if (italic && Italic) { 44 | parsedText = {parsedText}; 45 | } 46 | 47 | if (underline && Underline) { 48 | parsedText = {parsedText}; 49 | } 50 | 51 | if (code && Code) { 52 | parsedText = {parsedText}; 53 | } 54 | 55 | return <>{parsedText}; 56 | } 57 | -------------------------------------------------------------------------------- /packages/html-renderer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @graphcms/rich-text-html-renderer 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - [`c8a5a7c`](https://github.com/hygraph/rich-text/commit/c8a5a7c409efd8570e77bd28a66352eb9e519a42) [#127](https://github.com/hygraph/rich-text/pull/127) Thanks [@jpedroschmitz](https://github.com/jpedroschmitz)! - fix: add closing tag for iframe 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - [`7992b80`](https://github.com/hygraph/rich-text/commit/7992b80923dad0b88cd55b406770fdc15a050743) [#107](https://github.com/hygraph/rich-text/pull/107) Thanks [@rbastiansch](https://github.com/rbastiansch)! - Export `defaultElements` 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - [`b272253`](https://github.com/hygraph/rich-text/commit/b2722534275efd2c5e473d549d0f0e5a28100025) [#84](https://github.com/hygraph/rich-text/pull/84) Thanks [@jpedroschmitz](https://github.com/jpedroschmitz)! - Adds support for Link Embeds 20 | 21 | ### Patch Changes 22 | 23 | - Updated dependencies [[`b272253`](https://github.com/hygraph/rich-text/commit/b2722534275efd2c5e473d549d0f0e5a28100025)]: 24 | - @graphcms/rich-text-types@0.5.0 25 | 26 | ## 0.1.1 27 | 28 | ### Patch Changes 29 | 30 | - [`12cb7f9`](https://github.com/hygraph/rich-text/commit/12cb7f914cf9d1404e0783c168d61910e346a391) [#81](https://github.com/hygraph/rich-text/pull/81) Thanks [@jpedroschmitz](https://github.com/jpedroschmitz)! - Add escape-html as a dependency 31 | 32 | ## 0.1.0 33 | 34 | ### Minor Changes 35 | 36 | - [`b7f16fa`](https://github.com/hygraph/rich-text/commit/b7f16fa76a28ad0f5cdbe6cb1f58d7fafa63df15) [#77](https://github.com/hygraph/rich-text/pull/77) Thanks [@jpedroschmitz](https://github.com/jpedroschmitz)! - Initial version of the `html-renderer` for Rich Text content. 37 | 38 | Features 39 | 40 | - `astToHtmlString` function for returning HTML 41 | - Types for the package 42 | - Vue and Svelte examples 43 | 44 | ### Patch Changes 45 | 46 | - Updated dependencies [[`b7f16fa`](https://github.com/hygraph/rich-text/commit/b7f16fa76a28ad0f5cdbe6cb1f58d7fafa63df15), [`b7f16fa`](https://github.com/hygraph/rich-text/commit/b7f16fa76a28ad0f5cdbe6cb1f58d7fafa63df15)]: 47 | - @graphcms/rich-text-types@0.4.0 48 | -------------------------------------------------------------------------------- /examples/react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { RichText } from '@graphcms/rich-text-react-renderer'; 4 | 5 | import Prism from 'prismjs'; 6 | import 'prismjs/plugins/line-numbers/prism-line-numbers'; 7 | import 'prismjs/themes/prism-tomorrow.css'; 8 | import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; 9 | 10 | import { content, references } from '../../content-example'; 11 | 12 | export default function App() { 13 | React.useEffect(() => { 14 | Prism.highlightAll(); 15 | }, []); 16 | 17 | return ( 18 |
19 |

React example

20 | 21 |

{children}

, 26 | blockquote: ({ children }) => ( 27 |
34 | {children} 35 |
36 | ), 37 | a: ({ children, href, openInNewTab }) => ( 38 | 44 | {children} 45 | 46 | ), 47 | h2: ({ children }) => ( 48 |

{children}

49 | ), 50 | bold: ({ children }) => {children}, 51 | code_block: ({ children }) => { 52 | return ( 53 |
54 |                 {children}
55 |               
56 | ); 57 | }, 58 | Asset: { 59 | application: () => ( 60 |
61 |

Asset

62 |
63 | ), 64 | text: () => ( 65 |
66 |

text plain

67 |
68 | ), 69 | }, 70 | }} 71 | /> 72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /packages/html-renderer/src/defaultElements.tsx: -------------------------------------------------------------------------------- 1 | import { RichTextProps } from './types'; 2 | 3 | import { IFrame, Image, Video, Class, Link, Audio } from './elements'; 4 | 5 | function FallbackForCustomAsset({ mimeType }: { mimeType: string }) { 6 | if (__DEV__) { 7 | console.warn( 8 | `[@graphcms/rich-text-html-renderer]: Unsupported mimeType encountered: ${mimeType}. You need to write your renderer to render it since we are not opinionated about how this asset should be rendered (check our docs for more info).` 9 | ); 10 | } 11 | 12 | return ``; 13 | } 14 | 15 | export const defaultElements: Required = { 16 | a: Link, 17 | class: Class, 18 | video: Video, 19 | img: Image, 20 | iframe: IFrame, 21 | blockquote: ({ children }) => `
${children}
`, 22 | ul: ({ children }) => `
    ${children}
`, 23 | ol: ({ children }) => `
    ${children}
`, 24 | li: ({ children }) => `
  • ${children}
  • `, 25 | p: ({ children }) => `

    ${children}

    `, 26 | h1: ({ children }) => `

    ${children}

    `, 27 | h2: ({ children }) => `

    ${children}

    `, 28 | h3: ({ children }) => `

    ${children}

    `, 29 | h4: ({ children }) => `

    ${children}

    `, 30 | h5: ({ children }) => `
    ${children}
    `, 31 | h6: ({ children }) => `
    ${children}
    `, 32 | table: ({ children }) => `${children}
    `, 33 | table_head: ({ children }) => `${children}`, 34 | table_body: ({ children }) => `${children}`, 35 | table_row: ({ children }) => `${children}`, 36 | table_cell: ({ children }) => `${children}`, 37 | table_header_cell: ({ children }) => `${children}`, 38 | bold: ({ children }) => `${children}`, 39 | italic: ({ children }) => `${children}`, 40 | underline: ({ children }) => `${children}`, 41 | code: ({ children }) => `${children}`, 42 | code_block: ({ children }) => 43 | `
    52 |       ${children}
    53 |     
    `, 54 | list_item_child: ({ children }) => `${children}`, 55 | Asset: { 56 | audio: Audio, 57 | image: props => Image({ ...props, src: props.url }), 58 | video: props => Video({ ...props, src: props.url }), 59 | font: FallbackForCustomAsset, 60 | application: FallbackForCustomAsset, 61 | model: FallbackForCustomAsset, 62 | text: FallbackForCustomAsset, 63 | }, 64 | embed: {}, 65 | link: {}, 66 | }; 67 | -------------------------------------------------------------------------------- /packages/html-renderer/src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EmbedReferences, 3 | IFrameProps, 4 | ImageProps, 5 | RichTextContent, 6 | VideoProps, 7 | ClassProps, 8 | LinkProps, 9 | } from '@graphcms/rich-text-types'; 10 | 11 | export interface DefaultElementProps { 12 | children: string; 13 | } 14 | 15 | export interface ClassRendererProps 16 | extends DefaultElementProps, 17 | Partial {} 18 | 19 | export interface LinkRendererProps 20 | extends DefaultElementProps, 21 | Partial {} 22 | 23 | type DefaultNodeRenderer = (props: DefaultElementProps) => string; 24 | type LinkNodeRenderer = (props: LinkRendererProps) => string; 25 | type ClassNodeRenderer = (props: ClassRendererProps) => string; 26 | type ImageNodeRenderer = (props: Partial) => string; 27 | type VideoNodeRenderer = (props: Partial) => string; 28 | type IFrameNodeRenderer = (props: Partial) => string; 29 | type EmbedNodeRenderer = (props: any) => string; 30 | 31 | export type NodeRendererType = { 32 | a?: LinkNodeRenderer; 33 | class?: ClassNodeRenderer; 34 | img?: ImageNodeRenderer; 35 | video?: VideoNodeRenderer; 36 | iframe?: IFrameNodeRenderer; 37 | h1?: DefaultNodeRenderer; 38 | h2?: DefaultNodeRenderer; 39 | h3?: DefaultNodeRenderer; 40 | h4?: DefaultNodeRenderer; 41 | h5?: DefaultNodeRenderer; 42 | h6?: DefaultNodeRenderer; 43 | p?: DefaultNodeRenderer; 44 | ul?: DefaultNodeRenderer; 45 | ol?: DefaultNodeRenderer; 46 | li?: DefaultNodeRenderer; 47 | list_item_child?: DefaultNodeRenderer; 48 | table?: DefaultNodeRenderer; 49 | table_head?: DefaultNodeRenderer; 50 | table_body?: DefaultNodeRenderer; 51 | table_row?: DefaultNodeRenderer; 52 | table_cell?: DefaultNodeRenderer; 53 | table_header_cell?: DefaultNodeRenderer; 54 | blockquote?: DefaultNodeRenderer; 55 | bold?: DefaultNodeRenderer; 56 | italic?: DefaultNodeRenderer; 57 | underline?: DefaultNodeRenderer; 58 | code?: DefaultNodeRenderer; 59 | code_block?: DefaultNodeRenderer; 60 | Asset?: { 61 | application?: EmbedNodeRenderer; 62 | audio?: EmbedNodeRenderer; 63 | font?: EmbedNodeRenderer; 64 | image?: EmbedNodeRenderer; 65 | model?: EmbedNodeRenderer; 66 | text?: EmbedNodeRenderer; 67 | video?: EmbedNodeRenderer; 68 | [key: string]: EmbedNodeRenderer | undefined; 69 | }; 70 | embed?: { 71 | [key: string]: EmbedNodeRenderer | undefined; 72 | }; 73 | link?: { 74 | [key: string]: EmbedNodeRenderer | undefined; 75 | }; 76 | }; 77 | 78 | export type RichTextProps = { 79 | content: RichTextContent; 80 | references?: EmbedReferences; 81 | renderers?: NodeRendererType; 82 | }; 83 | -------------------------------------------------------------------------------- /packages/react-renderer/src/defaultElements.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { RichTextProps } from './types'; 3 | 4 | import { IFrame, Image, Video, Class, Link, Audio } from './elements'; 5 | 6 | function FallbackForCustomAsset({ mimeType }: { mimeType: string }) { 7 | if (__DEV__) { 8 | console.warn( 9 | `[@graphcms/rich-text-react-renderer]: Unsupported mimeType encountered: ${mimeType}. You need to write your renderer to render it since we are not opinionated about how this asset should be rendered (check our docs for more info).` 10 | ); 11 | } 12 | 13 | return ; 14 | } 15 | 16 | export const defaultElements: Required = { 17 | a: Link, 18 | class: Class, 19 | video: Video, 20 | img: Image, 21 | iframe: IFrame, 22 | blockquote: ({ children }) =>
    {children}
    , 23 | ul: ({ children }) =>
      {children}
    , 24 | ol: ({ children }) =>
      {children}
    , 25 | li: ({ children }) =>
  • {children}
  • , 26 | p: ({ children }) =>

    {children}

    , 27 | h1: ({ children }) =>

    {children}

    , 28 | h2: ({ children }) =>

    {children}

    , 29 | h3: ({ children }) =>

    {children}

    , 30 | h4: ({ children }) =>

    {children}

    , 31 | h5: ({ children }) =>
    {children}
    , 32 | h6: ({ children }) =>
    {children}
    , 33 | table: ({ children }) => {children}
    , 34 | table_head: ({ children }) => {children}, 35 | table_body: ({ children }) => {children}, 36 | table_row: ({ children }) => {children}, 37 | table_cell: ({ children }) => {children}, 38 | table_header_cell: ({ children }) => {children}, 39 | bold: ({ children }) => {children}, 40 | italic: ({ children }) => {children}, 41 | underline: ({ children }) => {children}, 42 | code: ({ children }) => {children}, 43 | code_block: ({ children }) => ( 44 |
    53 |       {children}
    54 |     
    55 | ), 56 | list_item_child: ({ children }) => <>{children}, 57 | Asset: { 58 | audio: props =>